mirror of
https://github.com/PCSX2/pcsx2.git
synced 2026-08-29 17:14:36 +02:00
Compare commits
18
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73abae8cb9 | ||
|
|
7ecc7b76ba | ||
|
|
71d0bbbc25 | ||
|
|
26e691ba93 | ||
|
|
c7352d9e10 | ||
|
|
7b8f9a54ec | ||
|
|
28980af858 | ||
|
|
80dce398e0 | ||
|
|
06db8eec48 | ||
|
|
9c720efe46 | ||
|
|
cbf91a8d19 | ||
|
|
f99414708d | ||
|
|
9549a6b16a | ||
|
|
3206094545 | ||
|
|
5cfae80701 | ||
|
|
b4d140c6bb | ||
|
|
c65eb3c3ee | ||
|
|
eec0984dbe |
@@ -36,10 +36,10 @@ _Note: Recommended GPU is based on 3x Internal, ~1080p resolution requirements.
|
||||
|
||||
### Technical Notes
|
||||
|
||||
- You need the [Visual C++ 2019 x64 Redistributables](https://support.microsoft.com/en-us/help/2977003/) to run PCSX2.
|
||||
- You need the [Visual C++ 2019 x64 Redistributables](https://support.microsoft.com/en-us/help/2977003/) to run PCSX2 on Windows.
|
||||
- Windows XP and Direct3D9 support was dropped after stable release 1.4.0.
|
||||
- Windows 7, Windows 8.0, and Windows 8.1 support was dropped after stable release 1.6.0.
|
||||
- 32-bit and wxwidgets support was dropped after stable release 1.6.0, with the wxwidgets code being removed completely on 25th December 2022.
|
||||
- 32-bit and wxWidgets support was dropped after stable release 1.6.0, with the wxWidgets code being removed completely on 25th December 2022.
|
||||
- Make sure to update your operating system and drivers to ensure you have the best experience possible. Having a newer GPU is also recommended so you have the latest supported drivers.
|
||||
- Because of copyright issues, and the complexity of trying to work around it, you need a BIOS dump extracted from a legitimately-owned PS2 console to use the emulator. For more information about the BIOS and how to get it from your console, visit [this page](pcsx2/Docs/PCSX2_FAQ.md#question-13-where-do-i-get-a-ps2-bios).
|
||||
- PCSX2 uses two CPU cores for emulation by default. A third core can be used via the MTVU speed hack, which is compatible with most games. This can be a significant speedup on CPUs with 3+ cores, but it may be a slowdown on GS-limited games (or on CPUs with fewer than 2 cores). Software renderers will then additionally use however many rendering threads it is set to and will need higher core counts to run efficiently.
|
||||
|
||||
@@ -424,7 +424,6 @@ SCAJ-10008:
|
||||
name: "Taiko no Tatsujin - Atsumare! Matsuri da!! Yondaime"
|
||||
region: "NTSC-Unk"
|
||||
gsHWFixes:
|
||||
deinterlace: 5 # Game requires bob bff de-interlacing when auto.
|
||||
alignSprite: 1 # Fixes vertical lines.
|
||||
SCAJ-10009:
|
||||
name: "Psikyo Shooting Collection Vol.1 - Strikers 1-2"
|
||||
@@ -439,31 +438,26 @@ SCAJ-10011:
|
||||
name: "Taiko no Tatsujin - Go! Go! Godaime"
|
||||
region: "NTSC-Unk"
|
||||
gsHWFixes:
|
||||
deinterlace: 5 # Game requires bob bff de-interlacing when auto.
|
||||
alignSprite: 1 # Fixes vertical lines.
|
||||
SCAJ-10012:
|
||||
name: "Taiko Drum Master"
|
||||
region: "NTSC-Unk"
|
||||
gsHWFixes:
|
||||
deinterlace: 5 # Game requires bob bff de-interlacing when auto.
|
||||
alignSprite: 1 # Fixes vertical lines.
|
||||
SCAJ-10013:
|
||||
name: "Taiko no Tatsujin - Tobikkiri! Anime Special"
|
||||
region: "NTSC-Unk"
|
||||
gsHWFixes:
|
||||
deinterlace: 5 # Game requires bob bff de-interlacing when auto.
|
||||
alignSprite: 1 # Fixes vertical lines.
|
||||
SCAJ-10014:
|
||||
name: "Taiko no Tatsujin - Wai Wai Happy! Rokudaime"
|
||||
region: "NTSC-Unk"
|
||||
gsHWFixes:
|
||||
deinterlace: 5 # Game requires bob bff de-interlacing when auto.
|
||||
alignSprite: 1 # Fixes vertical lines.
|
||||
SCAJ-10015:
|
||||
name: "Taiko no Tatsujin - Doka! to Oomori Nanadaime"
|
||||
region: "NTSC-Unk"
|
||||
gsHWFixes:
|
||||
deinterlace: 5 # Game requires bob bff de-interlacing when auto.
|
||||
alignSprite: 1 # Fixes vertical lines.
|
||||
SCAJ-20001:
|
||||
name: "Ratchet & Clank"
|
||||
@@ -3246,8 +3240,6 @@ SCES-51159:
|
||||
SCES-51164:
|
||||
name: "Mark of Kri, The"
|
||||
region: "PAL-M5"
|
||||
gsHWFixes:
|
||||
deinterlace: 4 # Game requires bob de-interlacing when auto.
|
||||
SCES-51176:
|
||||
name: "Disney's Treasure Planet"
|
||||
region: "PAL-M4"
|
||||
@@ -7159,8 +7151,6 @@ SCUS-97140:
|
||||
name: "Mark of Kri, The"
|
||||
region: "NTSC-U"
|
||||
compat: 5
|
||||
gsHWFixes:
|
||||
deinterlace: 4 # Game requires bob de-interlacing when auto.
|
||||
patches:
|
||||
DBD09DD4:
|
||||
content: |-
|
||||
@@ -7390,8 +7380,6 @@ SCUS-97200:
|
||||
SCUS-97201:
|
||||
name: "Mark of Kri, The"
|
||||
region: "NTSC-U"
|
||||
gsHWFixes:
|
||||
deinterlace: 4 # Game requires bob de-interlacing when auto.
|
||||
SCUS-97203:
|
||||
name: "Wild ARMs 3"
|
||||
region: "NTSC-U"
|
||||
@@ -7473,8 +7461,6 @@ SCUS-97220:
|
||||
SCUS-97222:
|
||||
name: "Mark of Kri [Demo]"
|
||||
region: "NTSC-U"
|
||||
gsHWFixes:
|
||||
deinterlace: 4 # Game requires bob de-interlacing when auto.
|
||||
SCUS-97223:
|
||||
name: "NFL GameDay 2003 [Demo]"
|
||||
region: "NTSC-U"
|
||||
@@ -9392,6 +9378,9 @@ SLED-53512:
|
||||
SLED-53537:
|
||||
name: "187 - Ride or Die [Demo]"
|
||||
region: "PAL-E"
|
||||
gsHWFixes:
|
||||
autoFlush: 1 # Softens bloom on objects and cars.
|
||||
halfPixelOffset: 2 # Fixes misalgined bloom on objects and cars.
|
||||
SLED-53673:
|
||||
name: "007 - From Russia with Love [Demo]"
|
||||
region: "PAL-E"
|
||||
@@ -10800,6 +10789,9 @@ SLES-50632:
|
||||
SLES-50636:
|
||||
name: "Centre Court - Hard Hitter"
|
||||
region: "PAL-M3"
|
||||
gsHWFixes:
|
||||
mipmap: 2 # Improves ground texture rendering.
|
||||
trilinearFiltering: 1
|
||||
SLES-50637:
|
||||
name: "Pro Rally 2002"
|
||||
region: "PAL-M5"
|
||||
@@ -11749,6 +11741,9 @@ SLES-51056:
|
||||
SLES-51057:
|
||||
name: "Hard Hitter 2"
|
||||
region: "PAL-M3"
|
||||
gsHWFixes:
|
||||
mipmap: 2 # Improves ground texture rendering.
|
||||
trilinearFiltering: 1
|
||||
SLES-51058:
|
||||
name: "Maken Shao - Demon Sword"
|
||||
region: "PAL-E"
|
||||
@@ -14327,6 +14322,9 @@ SLES-52276:
|
||||
name: "187 - Ride or Die"
|
||||
region: "PAL-M5"
|
||||
compat: 5
|
||||
gsHWFixes:
|
||||
autoFlush: 1 # Softens bloom on objects and cars.
|
||||
halfPixelOffset: 2 # Fixes misalgined bloom on objects and cars.
|
||||
SLES-52277:
|
||||
name: "Riding Spirits 2"
|
||||
region: "PAL-M5"
|
||||
@@ -14567,7 +14565,7 @@ SLES-52372:
|
||||
gsHWFixes:
|
||||
cpuSpriteRenderBW: 1 # Fixes textures.
|
||||
autoFlush: 1 # Fixes the position of the shadow and makes it not blocky.
|
||||
halfPixelOffset: 1 # Fixes shadows.
|
||||
halfPixelOffset: 2 # Fixes shadows.
|
||||
gpuPaletteConversion: 0 # Stops potential crashes from too many palette textures.
|
||||
SLES-52373:
|
||||
name: "Champions of Norrath"
|
||||
@@ -14730,7 +14728,7 @@ SLES-52447:
|
||||
gsHWFixes:
|
||||
cpuSpriteRenderBW: 1 # Fixes textures.
|
||||
autoFlush: 1 # Fixes the position of the shadow and makes it not blocky.
|
||||
halfPixelOffset: 1 # Fixes shadows.
|
||||
halfPixelOffset: 2 # Fixes shadows.
|
||||
gpuPaletteConversion: 0 # Stops potential crashes from too many palette textures.
|
||||
SLES-52448:
|
||||
name: "Knights of the Temple"
|
||||
@@ -14843,7 +14841,7 @@ SLES-52493:
|
||||
gsHWFixes:
|
||||
cpuSpriteRenderBW: 1 # Fixes textures.
|
||||
autoFlush: 1 # Fixes the position of the shadow and makes it not blocky.
|
||||
halfPixelOffset: 1 # Fixes shadows.
|
||||
halfPixelOffset: 2 # Fixes shadows.
|
||||
gpuPaletteConversion: 0 # Stops potential crashes from too many palette textures.
|
||||
SLES-52495:
|
||||
name: "Bujingai - Swordmaster"
|
||||
@@ -15519,7 +15517,7 @@ SLES-52745:
|
||||
region: "PAL-M4"
|
||||
SLES-52746:
|
||||
name: "Hugo - Cannon Cruise"
|
||||
region: "PAL-M4"
|
||||
region: "PAL-SC"
|
||||
SLES-52747:
|
||||
name: "Dukes of Hazzard, The - The Return of General Lee"
|
||||
region: "PAL-E"
|
||||
@@ -15705,7 +15703,7 @@ SLES-52816:
|
||||
cpuCLUTRender: 1 # Fixes duplicated post processing.
|
||||
SLES-52820:
|
||||
name: "Incredibles, The"
|
||||
region: "PAL-M4"
|
||||
region: "PAL-SC"
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 1 # Fixes offset post processing.
|
||||
cpuCLUTRender: 1 # Fixes duplicated post processing.
|
||||
@@ -16119,7 +16117,6 @@ SLES-52988:
|
||||
gsHWFixes:
|
||||
autoFlush: 1 # Fixes bloom.
|
||||
roundSprite: 1 # Fixes misaligned bloom.
|
||||
deinterlace: 6 # Game requires blend tff de-interlacing when auto for 'fixing' subtitles flickering or half weaved.
|
||||
memcardFilters: # Reads Command Mission save for bonus boss.
|
||||
- "SLES-52988"
|
||||
- "SLES-52832"
|
||||
@@ -17776,7 +17773,6 @@ SLES-53621:
|
||||
region: "PAL-M5"
|
||||
compat: 5
|
||||
gsHWFixes:
|
||||
deinterlace: 4 # Game requires bob de-interlacing when auto.
|
||||
halfPixelOffset: 1 # Fixes misalignment/bloom when upscaling.
|
||||
SLES-53623:
|
||||
name: "SpongeBob SquarePants - Battle for Bikini Bottom"
|
||||
@@ -18815,7 +18811,14 @@ SLES-54013:
|
||||
region: "PAL-E"
|
||||
SLES-54015:
|
||||
name: "Disney-Pixar's Cars"
|
||||
region: "PAL-M3"
|
||||
region: "PAL-SC"
|
||||
patches:
|
||||
B36778F5:
|
||||
content: |-
|
||||
author=refraction ported by jordanthetoast
|
||||
// COP2 Rearrangement. Fixes broken collisions.
|
||||
patch=1,EE,00203df8,word,484e9000
|
||||
patch=1,EE,00203dfc,word,4bdbd9ff
|
||||
SLES-54016:
|
||||
name: "AND 1 Streetball"
|
||||
region: "PAL-M5"
|
||||
@@ -19916,7 +19919,6 @@ SLES-54455:
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 1 # Fixes misaligned bloom effects.
|
||||
mergeSprite: 1 # Fixes blurriness but removes bloom + Recommended to use Shadeboost brightness 80.
|
||||
deinterlace: 8 # Game requires adaptive (or blend) tff de-interlacing instead of auto for the whole game.
|
||||
SLES-54456:
|
||||
name: "Beverly Hills Cop"
|
||||
region: "PAL-M11"
|
||||
@@ -20082,14 +20084,12 @@ SLES-54516:
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 1 # Fixes misaligned bloom effects.
|
||||
mergeSprite: 1 # Fixes blurriness but removes bloom + Recommended to use Shadeboost brightness 80.
|
||||
deinterlace: 8 # Game requires adaptive (or blend) tff de-interlacing instead of auto for the whole game.
|
||||
SLES-54517:
|
||||
name: "Thrillville"
|
||||
region: "PAL-M3"
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 1 # Fixes misaligned bloom effects.
|
||||
mergeSprite: 1 # Fixes blurriness but removes bloom + Recommended to use Shadeboost brightness 80.
|
||||
deinterlace: 8 # Game requires adaptive (or blend) tff de-interlacing instead of auto for the whole game.
|
||||
SLES-54518:
|
||||
name: "Clumsy Shumsy"
|
||||
region: "PAL-E"
|
||||
@@ -20840,13 +20840,11 @@ SLES-54806:
|
||||
region: "PAL-E"
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 2 # Fixes misaligned bloom effects, 1 is technically more correct compared to software but looks worse.
|
||||
deinterlace: 8 # Game requires adaptive (or blend) tff de-interlacing instead of auto for the whole game.
|
||||
SLES-54807:
|
||||
name: "Thrillville - Off the Rails"
|
||||
region: "PAL-F"
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 2 # Fixes misaligned bloom effects, 1 is technically more correct compared to software but looks worse.
|
||||
deinterlace: 8 # Game requires adaptive (or blend) tff de-interlacing instead of auto for the whole game.
|
||||
SLES-54809:
|
||||
name: "Charlotte's Web"
|
||||
region: "PAL-A"
|
||||
@@ -20864,6 +20862,8 @@ SLES-54812:
|
||||
SLES-54813:
|
||||
name: "NASCAR 08"
|
||||
region: "PAL-A-E"
|
||||
gsHWFixes:
|
||||
deinterlace: 8 # Game requires AdaptiveTFF de-interlacing when auto.
|
||||
SLES-54814:
|
||||
name: "Dead Eye Jim"
|
||||
region: "PAL-E"
|
||||
@@ -21080,7 +21080,6 @@ SLES-54887:
|
||||
region: "PAL-G"
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 2 # Fixes misaligned bloom effects, 1 is technically more correct compared to software but looks worse.
|
||||
deinterlace: 8 # Game requires adaptive (or blend) tff de-interlacing instead of auto for the whole game.
|
||||
SLES-54888:
|
||||
name: "Pro Biker 2"
|
||||
region: "PAL-E"
|
||||
@@ -21442,13 +21441,11 @@ SLES-55010:
|
||||
region: "PAL-I"
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 2 # Fixes misaligned bloom effects, 1 is technically more correct compared to software but looks worse.
|
||||
deinterlace: 8 # Game requires adaptive (or blend) tff de-interlacing instead of auto for the whole game.
|
||||
SLES-55011:
|
||||
name: "Thrillville - Fuera de Control"
|
||||
region: "PAL-S"
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 2 # Fixes misaligned bloom effects, 1 is technically more correct compared to software but looks worse.
|
||||
deinterlace: 8 # Game requires adaptive (or blend) tff de-interlacing instead of auto for the whole game.
|
||||
SLES-55012:
|
||||
name: "Golden Compass, The"
|
||||
region: "PAL-M5"
|
||||
@@ -21898,6 +21895,8 @@ SLES-55199:
|
||||
name: "NASCAR 09"
|
||||
region: "PAL-M5"
|
||||
compat: 5
|
||||
gsHWFixes:
|
||||
deinterlace: 8 # Game requires AdaptiveTFF de-interlacing when auto.
|
||||
SLES-55200:
|
||||
name: "Guitar Hero - Aerosmith"
|
||||
region: "PAL-M4"
|
||||
@@ -22231,37 +22230,27 @@ SLES-55349:
|
||||
region: "PAL-E"
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 2 # Fixes blurriness.
|
||||
skipDrawStart: 1 # Removes even more blurriness and garbage texture like on top left.
|
||||
skipDrawEnd: 1 # Removes even more blurriness and garbage texture like on top left.
|
||||
SLES-55350:
|
||||
name: "Need for Speed - Undercover"
|
||||
region: "PAL-F-G"
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 2 # Fixes blurriness.
|
||||
skipDrawStart: 1 # Removes even more blurriness and garbage texture like on top left.
|
||||
skipDrawEnd: 1 # Removes even more blurriness and garbage texture like on top left.
|
||||
SLES-55351:
|
||||
name: "Need for Speed - Undercover"
|
||||
region: "PAL-I-S"
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 2 # Fixes blurriness.
|
||||
skipDrawStart: 1 # Removes even more blurriness and garbage texture like on top left.
|
||||
skipDrawEnd: 1 # Removes even more blurriness and garbage texture like on top left.
|
||||
SLES-55352:
|
||||
name: "Need for Speed - Undercover"
|
||||
region: "PAL-M4"
|
||||
compat: 5
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 2 # Fixes blurriness.
|
||||
skipDrawStart: 1 # Removes even more blurriness and garbage texture like on top left.
|
||||
skipDrawEnd: 1 # Removes even more blurriness and garbage texture like on top left.
|
||||
SLES-55353:
|
||||
name: "Need for Speed - Undercover"
|
||||
region: "PAL-M6"
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 2 # Fixes blurriness.
|
||||
skipDrawStart: 1 # Removes even more blurriness and garbage texture like on top left.
|
||||
skipDrawEnd: 1 # Removes even more blurriness and garbage texture like on top left.
|
||||
SLES-55354:
|
||||
name: "Shin Megami Tensei - Persona 3 FES"
|
||||
region: "PAL-E"
|
||||
@@ -22701,8 +22690,6 @@ SLES-55579:
|
||||
eeRoundMode: 1 # Fixes black textures in conjunction with VU0 clamping set to none.
|
||||
clampModes:
|
||||
vu0ClampMode: 0 # Fixes black textures and SPS.
|
||||
gsHWFixes:
|
||||
deinterlace: 8 # Game requires adaptive (or blend) tff de-interlacing instead of auto for the FMVs.
|
||||
SLES-55581:
|
||||
name: "FIFA 10"
|
||||
region: "PAL-M5"
|
||||
@@ -23238,6 +23225,9 @@ SLKA-15011:
|
||||
SLKA-15013:
|
||||
name: "Grand Slam 2003 Tennis" # Hard Hitter 2
|
||||
region: "NTSC-K"
|
||||
gsHWFixes:
|
||||
mipmap: 2 # Improves ground texture rendering.
|
||||
trilinearFiltering: 1
|
||||
SLKA-15015:
|
||||
name: "Ichigeki Sacchuu!! HoiHoi-San"
|
||||
region: "NTSC-K"
|
||||
@@ -24709,8 +24699,6 @@ SLPM-55127:
|
||||
region: "NTSC-J"
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 2 # Fixes blurriness.
|
||||
skipDrawStart: 1 # Removes even more blurriness and garbage texture like on top left.
|
||||
skipDrawEnd: 1 # Removes even more blurriness and garbage texture like on top left.
|
||||
SLPM-55131:
|
||||
name: "World Soccer Winning Eleven 2009"
|
||||
region: "NTSC-J"
|
||||
@@ -24905,8 +24893,6 @@ SLPM-55244:
|
||||
region: "NTSC-J"
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 2 # Fixes blurriness.
|
||||
skipDrawStart: 1 # Removes even more blurriness and garbage texture like on top left.
|
||||
skipDrawEnd: 1 # Removes even more blurriness and garbage texture like on top left.
|
||||
SLPM-55245:
|
||||
name: "Himawari - Pebble in the Sky"
|
||||
region: "NTSC-J"
|
||||
@@ -28666,8 +28652,6 @@ SLPM-65309:
|
||||
SLPM-65310:
|
||||
name: "Mark of Kri, The"
|
||||
region: "NTSC-J"
|
||||
gsHWFixes:
|
||||
deinterlace: 4 # Game requires bob de-interlacing when auto.
|
||||
SLPM-65311:
|
||||
name: "Violet no Atelier - Gramnad no Renkinjutsushi"
|
||||
region: "NTSC-J"
|
||||
@@ -29783,6 +29767,14 @@ SLPM-65657:
|
||||
SLPM-65661:
|
||||
name: "Densha de Go! Professional 2 [Taito Best]"
|
||||
region: "NTSC-J"
|
||||
SLPM-65662:
|
||||
name: "Spider-Man 2"
|
||||
region: "NTSC-J"
|
||||
gsHWFixes:
|
||||
cpuSpriteRenderBW: 1 # Fixes textures.
|
||||
autoFlush: 1 # Fixes the position of the shadow and makes it not blocky.
|
||||
halfPixelOffset: 2 # Fixes shadows.
|
||||
gpuPaletteConversion: 0 # Stops potential crashes from too many palette textures.
|
||||
SLPM-65663:
|
||||
name: "Zwei!!"
|
||||
region: "NTSC-J"
|
||||
@@ -31436,6 +31428,14 @@ SLPM-66119:
|
||||
halfPixelOffset: 2 # Fixes blurriness.
|
||||
speedHacks:
|
||||
InstantVU1SpeedHack: 0 # Fixes missing letters in text such as E.
|
||||
SLPM-66121:
|
||||
name: "Spider-Man 2 [Taito Best]"
|
||||
region: "NTSC-J"
|
||||
gsHWFixes:
|
||||
cpuSpriteRenderBW: 1 # Fixes textures.
|
||||
autoFlush: 1 # Fixes the position of the shadow and makes it not blocky.
|
||||
halfPixelOffset: 2 # Fixes shadows.
|
||||
gpuPaletteConversion: 0 # Stops potential crashes from too many palette textures.
|
||||
SLPM-66122:
|
||||
name: "Kingdom Hearts [Ultimate Hits]"
|
||||
region: "NTSC-J"
|
||||
@@ -32249,7 +32249,6 @@ SLPM-66327:
|
||||
name: "Wallace & Gromit - The Curse of the Were-Rabbit"
|
||||
region: "NTSC-J"
|
||||
gsHWFixes:
|
||||
deinterlace: 4 # Game requires bob de-interlacing when auto.
|
||||
halfPixelOffset: 1 # Fixes misalignment/bloom when upscaling.
|
||||
SLPM-66328:
|
||||
name: "Call of Duty 2 - Big Red One"
|
||||
@@ -35642,6 +35641,9 @@ SLPS-20097:
|
||||
SLPS-20098:
|
||||
name: "Magical Sports - Hard Hitter"
|
||||
region: "NTSC-J"
|
||||
gsHWFixes:
|
||||
mipmap: 2 # Improves ground texture rendering.
|
||||
trilinearFiltering: 1
|
||||
SLPS-20099:
|
||||
name: "Dream Audition 3"
|
||||
region: "NTSC-J"
|
||||
@@ -35793,6 +35795,9 @@ SLPS-20171:
|
||||
SLPS-20173:
|
||||
name: "Magical Sports - Hard Hitter 2"
|
||||
region: "NTSC-J"
|
||||
gsHWFixes:
|
||||
mipmap: 2 # Improves ground texture rendering.
|
||||
trilinearFiltering: 1
|
||||
SLPS-20174:
|
||||
name: "Typing Renai Hakusho - Boys Be... [with Keyboard]"
|
||||
region: "NTSC-J"
|
||||
@@ -36278,13 +36283,11 @@ SLPS-20382:
|
||||
name: "Taiko no Tatsujin - Atsumare! Matsuri da!! Yondaime [with Tatacon]"
|
||||
region: "NTSC-J"
|
||||
gsHWFixes:
|
||||
deinterlace: 5 # Game requires bob bff de-interlacing when auto.
|
||||
alignSprite: 1 # Fixes vertical lines.
|
||||
SLPS-20383:
|
||||
name: "Taiko no Tatsujin - Atsumare! Matsuri da!! Yondaime"
|
||||
region: "NTSC-J"
|
||||
gsHWFixes:
|
||||
deinterlace: 5 # Game requires bob bff de-interlacing when auto.
|
||||
alignSprite: 1 # Fixes vertical lines.
|
||||
SLPS-20384:
|
||||
name: "Hayarigami - Keishichou Kaii Jiken File"
|
||||
@@ -36326,13 +36329,11 @@ SLPS-20399:
|
||||
name: "Taiko no Tatsujin - Go! Go! Godaime [with Tatacon]"
|
||||
region: "NTSC-J"
|
||||
gsHWFixes:
|
||||
deinterlace: 5 # Game requires bob bff de-interlacing when auto.
|
||||
alignSprite: 1 # Fixes vertical lines.
|
||||
SLPS-20400:
|
||||
name: "Taiko no Tatsujin - Go! Go! Godaime"
|
||||
region: "NTSC-J"
|
||||
gsHWFixes:
|
||||
deinterlace: 5 # Game requires bob bff de-interlacing when auto.
|
||||
alignSprite: 1 # Fixes vertical lines.
|
||||
SLPS-20401:
|
||||
name: "Tecmo Hit Parade"
|
||||
@@ -36373,14 +36374,12 @@ SLPS-20413:
|
||||
name: "Taiko no Tatsujin - Taiko Drum Master [with Tatacon]"
|
||||
region: "NTSC-J"
|
||||
gsHWFixes:
|
||||
deinterlace: 5 # Game requires bob bff de-interlacing when auto.
|
||||
alignSprite: 1 # Fixes vertical lines.
|
||||
SLPS-20414:
|
||||
name: "Taiko no Tatsujin - Taiko Drum Master"
|
||||
region: "NTSC-J"
|
||||
compat: 5
|
||||
gsHWFixes:
|
||||
deinterlace: 5 # Game requires bob bff de-interlacing when auto.
|
||||
alignSprite: 1 # Fixes vertical lines.
|
||||
SLPS-20416:
|
||||
name: "Inyou Taisenki - Byakko Enbu [with EyeToy]"
|
||||
@@ -36409,13 +36408,11 @@ SLPS-20424:
|
||||
name: "Taiko no Tatsujin - Tobikkiri! Anime Special [with Tatacon]"
|
||||
region: "NTSC-J"
|
||||
gsHWFixes:
|
||||
deinterlace: 5 # Game requires bob bff de-interlacing when auto.
|
||||
alignSprite: 1 # Fixes vertical lines.
|
||||
SLPS-20425:
|
||||
name: "Taiko no Tatsujin - Tobikkiri! Anime Special"
|
||||
region: "NTSC-J"
|
||||
gsHWFixes:
|
||||
deinterlace: 5 # Game requires bob bff de-interlacing when auto.
|
||||
alignSprite: 1 # Fixes vertical lines.
|
||||
SLPS-20426:
|
||||
name: "Dreamworks Madagascar"
|
||||
@@ -36481,13 +36478,11 @@ SLPS-20450:
|
||||
name: "Taiko no Tatsujin - Wai Wai Happy! Rokudaime [with Tatacon]"
|
||||
region: "NTSC-J"
|
||||
gsHWFixes:
|
||||
deinterlace: 5 # Game requires bob bff de-interlacing when auto.
|
||||
alignSprite: 1 # Fixes vertical lines.
|
||||
SLPS-20451:
|
||||
name: "Taiko no Tatsujin - Wai Wai Happy! Rokudaime"
|
||||
region: "NTSC-J"
|
||||
gsHWFixes:
|
||||
deinterlace: 5 # Game requires bob bff de-interlacing when auto.
|
||||
alignSprite: 1 # Fixes vertical lines.
|
||||
SLPS-20452:
|
||||
name: "Simple 2000 Series Ultimate Vol. 30 - Kourin! Zokusha Goddo!"
|
||||
@@ -36608,13 +36603,11 @@ SLPS-20485:
|
||||
name: "Taiko no Tatsujin - Doka! to Oomori Nanadaime [with Tatacon]"
|
||||
region: "NTSC-J"
|
||||
gsHWFixes:
|
||||
deinterlace: 5 # Game requires bob bff de-interlacing when auto.
|
||||
alignSprite: 1 # Fixes vertical lines.
|
||||
SLPS-20486:
|
||||
name: "Taiko no Tatsujin - Doka! to Oomori Nanadaime"
|
||||
region: "NTSC-J"
|
||||
gsHWFixes:
|
||||
deinterlace: 5 # Game requires bob bff de-interlacing when auto.
|
||||
alignSprite: 1 # Fixes vertical lines.
|
||||
SLPS-20487:
|
||||
name: "Pachi-Slot King! Kagaku Ninja-Tai Gatchaman"
|
||||
@@ -40200,7 +40193,6 @@ SLPS-73107:
|
||||
name: "Taiko no Tatsujin - Go! Go! Godaime [PlayStation 2 The Best]"
|
||||
region: "NTSC-J"
|
||||
gsHWFixes:
|
||||
deinterlace: 5 # Game requires bob bff de-interlacing when auto.
|
||||
alignSprite: 1 # Fixes vertical lines.
|
||||
SLPS-73108:
|
||||
name: "Phantom Brave [PlayStation 2 The Best]"
|
||||
@@ -42755,8 +42747,10 @@ SLUS-20474:
|
||||
SLUS-20475:
|
||||
name: "Dual Hearts"
|
||||
region: "NTSC-U"
|
||||
gameFixes:
|
||||
- SoftwareRendererFMVHack # Fixes flashing FMV.
|
||||
gsHWFixes:
|
||||
deinterlace: 6 # Game requires blend tff de-interlacing when auto.
|
||||
deinterlace: 9 # Game looks better with Adaptive BFF.
|
||||
compat: 5
|
||||
SLUS-20476:
|
||||
name: "NBA 2K3 - Sega Sports"
|
||||
@@ -43196,6 +43190,9 @@ SLUS-20567:
|
||||
SLUS-20568:
|
||||
name: "Hard Hitter Tennis"
|
||||
region: "NTSC-U"
|
||||
gsHWFixes:
|
||||
mipmap: 2 # Improves ground texture rendering.
|
||||
trilinearFiltering: 1
|
||||
SLUS-20569:
|
||||
name: "All-Star Baseball 2004"
|
||||
region: "NTSC-U"
|
||||
@@ -44212,7 +44209,7 @@ SLUS-20776:
|
||||
gsHWFixes:
|
||||
cpuSpriteRenderBW: 1 # Fixes textures.
|
||||
autoFlush: 1 # Fixes the position of the shadow and makes it not blocky.
|
||||
halfPixelOffset: 1 # Fixes shadows.
|
||||
halfPixelOffset: 2 # Fixes shadows.
|
||||
gpuPaletteConversion: 0 # Stops potential crashes from too many palette textures.
|
||||
SLUS-20777:
|
||||
name: "Obscure"
|
||||
@@ -44305,7 +44302,6 @@ SLUS-20800:
|
||||
name: "Taiko Drum Master"
|
||||
region: "NTSC-U"
|
||||
gsHWFixes:
|
||||
deinterlace: 5 # Game requires bob bff de-interlacing when auto.
|
||||
alignSprite: 1 # Fixes vertical lines.
|
||||
SLUS-20801:
|
||||
name: "Midway Arcade Treasures"
|
||||
@@ -45042,7 +45038,6 @@ SLUS-20948:
|
||||
gsHWFixes:
|
||||
wildArmsHack: 1 # Fixes blurriness.
|
||||
roundSprite: 2 # Reduces misalignment bloom effects.
|
||||
# deinterlace: 6 # Game requires blend tff de-interlacing when auto for 'fixing' shimmer on character models and more flickering or half weaved, though the game suffers from the field order.
|
||||
SLUS-20949:
|
||||
name: "Street Fighter Anniversary Collection"
|
||||
region: "NTSC-U"
|
||||
@@ -45938,6 +45933,9 @@ SLUS-21116:
|
||||
name: "187 - Ride or Die"
|
||||
region: "NTSC-U"
|
||||
compat: 5
|
||||
gsHWFixes:
|
||||
autoFlush: 1 # Softens bloom on objects and cars.
|
||||
halfPixelOffset: 2 # Fixes misalgined bloom on objects and cars.
|
||||
SLUS-21117:
|
||||
name: "World Soccer - Winning Eleven 8 - International"
|
||||
region: "NTSC-U"
|
||||
@@ -47017,7 +47015,6 @@ SLUS-21312:
|
||||
region: "NTSC-U"
|
||||
compat: 5
|
||||
gsHWFixes:
|
||||
deinterlace: 4 # Game requires bob de-interlacing when auto.
|
||||
halfPixelOffset: 1 # Fixes misalignment/bloom when upscaling.
|
||||
SLUS-21313:
|
||||
name: "Friends - The One with all the Trivia"
|
||||
@@ -47640,7 +47637,6 @@ SLUS-21413:
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 1 # Fixes misaligned bloom effects.
|
||||
mergeSprite: 1 # Fixes blurriness but removes bloom + Recommended to use Shadeboost brightness 80.
|
||||
deinterlace: 8 # Game requires adaptive (or blend) tff de-interlacing instead of auto for the whole game.
|
||||
SLUS-21414:
|
||||
name: "Delta Force - Black Hawk Down - Team Sabre"
|
||||
region: "NTSC-U"
|
||||
@@ -48525,7 +48521,6 @@ SLUS-21611:
|
||||
compat: 5
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 2 # Fixes misaligned bloom effects, 1 is technically more correct compared to software but looks worse.
|
||||
deinterlace: 8 # Game requires adaptive (or blend) tff de-interlacing instead of auto for the whole game.
|
||||
SLUS-21612:
|
||||
name: "Legend of the Dragon"
|
||||
region: "NTSC-U"
|
||||
@@ -48683,6 +48678,8 @@ SLUS-21639:
|
||||
name: "NASCAR '08"
|
||||
region: "NTSC-U"
|
||||
compat: 5
|
||||
gsHWFixes:
|
||||
deinterlace: 8 # Game requires AdaptiveTFF de-interlacing when auto.
|
||||
SLUS-21640:
|
||||
name: "Rugby '08"
|
||||
region: "NTSC-U"
|
||||
@@ -49172,6 +49169,8 @@ SLUS-21744:
|
||||
name: "NASCAR '09"
|
||||
region: "NTSC-U"
|
||||
compat: 5
|
||||
gsHWFixes:
|
||||
deinterlace: 8 # Game requires AdaptiveTFF de-interlacing when auto.
|
||||
patches:
|
||||
1B6C22B9:
|
||||
content: |-
|
||||
@@ -49872,8 +49871,6 @@ SLUS-21902:
|
||||
eeRoundMode: 1 # Fixes black textures in conjunction with VU0 clamping set to none.
|
||||
clampModes:
|
||||
vu0ClampMode: 0 # Fixes black textures and SPS.
|
||||
gsHWFixes:
|
||||
deinterlace: 8 # Game requires adaptive (or blend) tff de-interlacing instead of auto for the FMVs.
|
||||
SLUS-21904:
|
||||
name: "Teenage Mutant Ninja Turtles - Smash-Up"
|
||||
region: "NTSC-U"
|
||||
|
||||
@@ -38,7 +38,6 @@
|
||||
#define PS_POINT_SAMPLER 0
|
||||
#define PS_SHUFFLE 0
|
||||
#define PS_READ_BA 0
|
||||
#define PS_SWAP_GA 0
|
||||
#define PS_DFMT 0
|
||||
#define PS_DEPTH_FMT 0
|
||||
#define PS_PAL_FMT 0
|
||||
@@ -876,37 +875,28 @@ PS_OUTPUT ps_main(PS_INPUT input)
|
||||
|
||||
if (PS_SHUFFLE)
|
||||
{
|
||||
if(PS_SWAP_GA)
|
||||
uint4 denorm_c = uint4(C);
|
||||
uint2 denorm_TA = uint2(float2(TA.xy) * 255.0f + 0.5f);
|
||||
|
||||
// Mask will take care of the correct destination
|
||||
if (PS_READ_BA)
|
||||
C.rb = C.bb;
|
||||
else
|
||||
C.rb = C.rr;
|
||||
|
||||
if (PS_READ_BA)
|
||||
{
|
||||
float4 RT = trunc(RtTexture.Load(int3(input.p.xy, 0)) * 255.0f + 0.1f);
|
||||
C.a = C.g;
|
||||
C.g = C.a;
|
||||
if (denorm_c.a & 0x80u)
|
||||
C.ga = (float2)(float((denorm_c.a & 0x7Fu) | (denorm_TA.y & 0x80u)));
|
||||
else
|
||||
C.ga = (float2)(float((denorm_c.a & 0x7Fu) | (denorm_TA.x & 0x80u)));
|
||||
}
|
||||
else
|
||||
{
|
||||
uint4 denorm_c = uint4(C);
|
||||
uint2 denorm_TA = uint2(float2(TA.xy) * 255.0f + 0.5f);
|
||||
|
||||
// Mask will take care of the correct destination
|
||||
if (PS_READ_BA)
|
||||
C.rb = C.bb;
|
||||
if (denorm_c.g & 0x80u)
|
||||
C.ga = (float2)(float((denorm_c.g & 0x7Fu) | (denorm_TA.y & 0x80u)));
|
||||
else
|
||||
C.rb = C.rr;
|
||||
|
||||
if (PS_READ_BA)
|
||||
{
|
||||
if (denorm_c.a & 0x80u)
|
||||
C.ga = (float2)(float((denorm_c.a & 0x7Fu) | (denorm_TA.y & 0x80u)));
|
||||
else
|
||||
C.ga = (float2)(float((denorm_c.a & 0x7Fu) | (denorm_TA.x & 0x80u)));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (denorm_c.g & 0x80u)
|
||||
C.ga = (float2)(float((denorm_c.g & 0x7Fu) | (denorm_TA.y & 0x80u)));
|
||||
else
|
||||
C.ga = (float2)(float((denorm_c.g & 0x7Fu) | (denorm_TA.x & 0x80u)));
|
||||
}
|
||||
C.ga = (float2)(float((denorm_c.g & 0x7Fu) | (denorm_TA.x & 0x80u)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -347,7 +347,6 @@ void main()
|
||||
#define PS_ZCLAMP 0
|
||||
#define PS_FEEDBACK_LOOP 0
|
||||
#define PS_TEX_IS_FB 0
|
||||
#define PS_SWAP_GA 0
|
||||
#endif
|
||||
|
||||
#define SW_BLEND (PS_BLEND_A || PS_BLEND_B || PS_BLEND_D)
|
||||
@@ -1174,32 +1173,26 @@ void main()
|
||||
vec4 C = ps_color();
|
||||
|
||||
#if PS_SHUFFLE
|
||||
#if PS_SWAP_GA
|
||||
vec4 RT = trunc(subpassLoad(RtSampler) * 255.0f + 0.1f);
|
||||
C.a = RT.g;
|
||||
C.g = RT.a;
|
||||
uvec4 denorm_c = uvec4(C);
|
||||
uvec2 denorm_TA = uvec2(vec2(TA.xy) * 255.0f + 0.5f);
|
||||
|
||||
// Mask will take care of the correct destination
|
||||
#if PS_READ_BA
|
||||
C.rb = C.bb;
|
||||
#else
|
||||
uvec4 denorm_c = uvec4(C);
|
||||
uvec2 denorm_TA = uvec2(vec2(TA.xy) * 255.0f + 0.5f);
|
||||
C.rb = C.rr;
|
||||
#endif
|
||||
|
||||
// Mask will take care of the correct destination
|
||||
#if PS_READ_BA
|
||||
C.rb = C.bb;
|
||||
#else
|
||||
C.rb = C.rr;
|
||||
#endif
|
||||
|
||||
#if PS_READ_BA
|
||||
if ((denorm_c.a & 0x80u) != 0u)
|
||||
C.ga = vec2(float((denorm_c.a & 0x7Fu) | (denorm_TA.y & 0x80u)));
|
||||
else
|
||||
C.ga = vec2(float((denorm_c.a & 0x7Fu) | (denorm_TA.x & 0x80u)));
|
||||
#else
|
||||
if ((denorm_c.g & 0x80u) != 0u)
|
||||
C.ga = vec2(float((denorm_c.g & 0x7Fu) | (denorm_TA.y & 0x80u)));
|
||||
else
|
||||
C.ga = vec2(float((denorm_c.g & 0x7Fu) | (denorm_TA.x & 0x80u)));
|
||||
#endif
|
||||
#if PS_READ_BA
|
||||
if ((denorm_c.a & 0x80u) != 0u)
|
||||
C.ga = vec2(float((denorm_c.a & 0x7Fu) | (denorm_TA.y & 0x80u)));
|
||||
else
|
||||
C.ga = vec2(float((denorm_c.a & 0x7Fu) | (denorm_TA.x & 0x80u)));
|
||||
#else
|
||||
if ((denorm_c.g & 0x80u) != 0u)
|
||||
C.ga = vec2(float((denorm_c.g & 0x7Fu) | (denorm_TA.y & 0x80u)));
|
||||
else
|
||||
C.ga = vec2(float((denorm_c.g & 0x7Fu) | (denorm_TA.x & 0x80u)));
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
@@ -445,7 +445,7 @@ ID3D12GraphicsCommandList4* Context::GetInitCommandList()
|
||||
return res.command_lists[0].get();
|
||||
}
|
||||
|
||||
void Context::ExecuteCommandList(WaitType wait_for_completion)
|
||||
bool Context::ExecuteCommandList(WaitType wait_for_completion)
|
||||
{
|
||||
CommandListResources& res = m_command_lists[m_current_command_list];
|
||||
HRESULT hr;
|
||||
@@ -463,12 +463,21 @@ void Context::ExecuteCommandList(WaitType wait_for_completion)
|
||||
if (res.init_command_list_used)
|
||||
{
|
||||
hr = res.command_lists[0]->Close();
|
||||
pxAssertRel(SUCCEEDED(hr), "Close init command list");
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Console.Error("Closing init command list failed with HRESULT %08X", hr);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Close and queue command list.
|
||||
hr = res.command_lists[1]->Close();
|
||||
pxAssertRel(SUCCEEDED(hr), "Close command list");
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Console.Error("Closing main command list failed with HRESULT %08X", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (res.init_command_list_used)
|
||||
{
|
||||
const std::array<ID3D12CommandList*, 2> execute_lists{res.command_lists[0].get(), res.command_lists[1].get()};
|
||||
@@ -487,6 +496,8 @@ void Context::ExecuteCommandList(WaitType wait_for_completion)
|
||||
MoveToNextCommandList();
|
||||
if (wait_for_completion != WaitType::None)
|
||||
WaitForFence(res.ready_fence_value, wait_for_completion == WaitType::Spin);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Context::InvalidateSamplerGroups()
|
||||
|
||||
@@ -130,7 +130,7 @@ namespace D3D12
|
||||
};
|
||||
|
||||
/// Executes the current command list.
|
||||
void ExecuteCommandList(WaitType wait_for_completion);
|
||||
bool ExecuteCommandList(WaitType wait_for_completion);
|
||||
|
||||
/// Waits for a specific fence.
|
||||
void WaitForFence(u64 fence, bool spin);
|
||||
|
||||
@@ -119,7 +119,7 @@ namespace GL
|
||||
if (!context)
|
||||
return nullptr;
|
||||
|
||||
Console.WriteLn("Created a %s context", context->IsGLES() ? "OpenGL ES" : "OpenGL");
|
||||
Console.WriteLn("Created an %s context", context->IsGLES() ? "OpenGL ES" : "OpenGL");
|
||||
|
||||
// NOTE: Not thread-safe. But this is okay, since we're not going to be creating more than one context at a time.
|
||||
static Context* context_being_created;
|
||||
|
||||
+29
-13
@@ -1122,9 +1122,13 @@ namespace Vulkan
|
||||
void Context::WaitForCommandBufferCompletion(u32 index)
|
||||
{
|
||||
// Wait for this command buffer to be completed.
|
||||
VkResult res = vkWaitForFences(m_device, 1, &m_frame_resources[index].fence, VK_TRUE, UINT64_MAX);
|
||||
const VkResult res = vkWaitForFences(m_device, 1, &m_frame_resources[index].fence, VK_TRUE, UINT64_MAX);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkWaitForFences failed: ");
|
||||
m_last_submit_failed.store(true, std::memory_order_release);
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean up any resources for command buffers between the last known completed buffer and this
|
||||
// now-completed command buffer. If we use >2 buffers, this may be more than one buffer.
|
||||
@@ -1266,11 +1270,12 @@ namespace Vulkan
|
||||
submit_info.pSignalSemaphores = &m_spin_resources[index].semaphore;
|
||||
}
|
||||
|
||||
VkResult res = vkQueueSubmit(m_graphics_queue, 1, &submit_info, resources.fence);
|
||||
const VkResult res = vkQueueSubmit(m_graphics_queue, 1, &submit_info, resources.fence);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkQueueSubmit failed: ");
|
||||
pxFailRel("Failed to submit command buffer.");
|
||||
m_last_submit_failed.store(true, std::memory_order_release);
|
||||
return;
|
||||
}
|
||||
|
||||
if (spin_cycles != 0)
|
||||
@@ -1286,14 +1291,14 @@ namespace Vulkan
|
||||
|
||||
present_swap_chain->ReleaseCurrentImage();
|
||||
|
||||
VkResult res = vkQueuePresentKHR(m_present_queue, &present_info);
|
||||
const VkResult res = vkQueuePresentKHR(m_present_queue, &present_info);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
// VK_ERROR_OUT_OF_DATE_KHR is not fatal, just means we need to recreate our swap chain.
|
||||
if (res != VK_ERROR_OUT_OF_DATE_KHR && res != VK_SUBOPTIMAL_KHR)
|
||||
LOG_VULKAN_ERROR(res, "vkQueuePresentKHR failed: ");
|
||||
|
||||
m_last_present_failed.store(true);
|
||||
m_last_present_failed.store(true, std::memory_order_release);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1460,6 +1465,9 @@ namespace Vulkan
|
||||
|
||||
void Context::ExecuteCommandBuffer(WaitType wait_for_completion)
|
||||
{
|
||||
if (m_last_submit_failed.load(std::memory_order_acquire))
|
||||
return;
|
||||
|
||||
// If we're waiting for completion, don't bother waking the worker thread.
|
||||
const u32 current_frame = m_current_frame;
|
||||
SubmitCommandBuffer();
|
||||
@@ -1481,9 +1489,12 @@ namespace Vulkan
|
||||
|
||||
bool Context::CheckLastPresentFail()
|
||||
{
|
||||
bool res = m_last_present_failed;
|
||||
m_last_present_failed = false;
|
||||
return res;
|
||||
return m_last_present_failed.exchange(false, std::memory_order_acq_rel);
|
||||
}
|
||||
|
||||
bool Context::CheckLastSubmitFail()
|
||||
{
|
||||
return m_last_submit_failed.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
void Context::DeferBufferDestruction(VkBuffer object)
|
||||
@@ -1596,7 +1607,7 @@ namespace Vulkan
|
||||
VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT,
|
||||
DebugMessengerCallback, nullptr};
|
||||
|
||||
VkResult res =
|
||||
const VkResult res =
|
||||
vkCreateDebugUtilsMessengerEXT(m_instance, &messenger_info, nullptr, &m_debug_messenger_callback);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
@@ -1688,7 +1699,7 @@ namespace Vulkan
|
||||
subpass_dependency_ptr};
|
||||
|
||||
VkRenderPass pass;
|
||||
VkResult res = vkCreateRenderPass(m_device, &pass_info, nullptr, &pass);
|
||||
const VkResult res = vkCreateRenderPass(m_device, &pass_info, nullptr, &pass);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkCreateRenderPass failed: ");
|
||||
@@ -1894,9 +1905,14 @@ void main()
|
||||
SpinResources& resources = m_spin_resources[index];
|
||||
if (!resources.in_progress)
|
||||
return;
|
||||
VkResult res = vkWaitForFences(m_device, 1, &resources.fence, VK_TRUE, UINT64_MAX);
|
||||
|
||||
const VkResult res = vkWaitForFences(m_device, 1, &resources.fence, VK_TRUE, UINT64_MAX);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkWaitForFences failed: ");
|
||||
m_last_submit_failed.store(true, std::memory_order_release);
|
||||
return;
|
||||
}
|
||||
SpinCommandCompleted(index);
|
||||
}
|
||||
|
||||
@@ -1906,7 +1922,7 @@ void main()
|
||||
resources.in_progress = false;
|
||||
const u32 timestamp_base = (index + NUM_COMMAND_BUFFERS) * 2;
|
||||
std::array<u64, 2> timestamps;
|
||||
VkResult res = vkGetQueryPoolResults(m_device, m_timestamp_query_pool, timestamp_base, static_cast<u32>(timestamps.size()),
|
||||
const VkResult res = vkGetQueryPoolResults(m_device, m_timestamp_query_pool, timestamp_base, static_cast<u32>(timestamps.size()),
|
||||
sizeof(timestamps), timestamps.data(), sizeof(u64), VK_QUERY_RESULT_64_BIT);
|
||||
if (res == VK_SUCCESS)
|
||||
{
|
||||
@@ -2014,7 +2030,7 @@ void main()
|
||||
constexpr u64 MAX_MAX_DEVIATION = 100000; // 100µs
|
||||
for (int i = 0; i < 4; i++) // 4 tries to get under MAX_MAX_DEVIATION
|
||||
{
|
||||
VkResult res = vkGetCalibratedTimestampsEXT(m_device, std::size(infos), infos, timestamps, &maxDeviation);
|
||||
const VkResult res = vkGetCalibratedTimestampsEXT(m_device, std::size(infos), infos, timestamps, &maxDeviation);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkGetCalibratedTimestampsEXT failed: ");
|
||||
|
||||
@@ -209,6 +209,7 @@ namespace Vulkan
|
||||
|
||||
// Was the last present submitted to the queue a failure? If so, we must recreate our swapchain.
|
||||
bool CheckLastPresentFail();
|
||||
bool CheckLastSubmitFail();
|
||||
|
||||
// Schedule a vulkan resource for destruction later on. This will occur when the command buffer
|
||||
// is next re-used, and the GPU has finished working with the specified resource.
|
||||
@@ -373,6 +374,7 @@ namespace Vulkan
|
||||
|
||||
StreamBuffer m_texture_upload_buffer;
|
||||
|
||||
std::atomic_bool m_last_submit_failed{false};
|
||||
std::atomic_bool m_last_present_failed{false};
|
||||
std::atomic_bool m_present_done{true};
|
||||
std::mutex m_present_mutex;
|
||||
|
||||
@@ -280,7 +280,7 @@ void Host::ReleaseHostDisplay(bool clear_state)
|
||||
g_host_display.reset();
|
||||
}
|
||||
|
||||
bool Host::BeginPresentFrame(bool frame_skip)
|
||||
HostDisplay::PresentResult Host::BeginPresentFrame(bool frame_skip)
|
||||
{
|
||||
if (s_loop_number == 0)
|
||||
{
|
||||
@@ -291,12 +291,15 @@ bool Host::BeginPresentFrame(bool frame_skip)
|
||||
std::string dump_path(fmt::format("{}_frame{}.png", s_output_prefix, s_dump_frame_number));
|
||||
GSQueueSnapshot(dump_path);
|
||||
}
|
||||
if (g_host_display->BeginPresent(frame_skip))
|
||||
return true;
|
||||
|
||||
// don't render imgui
|
||||
ImGuiManager::NewFrame();
|
||||
return false;
|
||||
const HostDisplay::PresentResult result = g_host_display->BeginPresent(frame_skip);
|
||||
if (result != HostDisplay::PresentResult::OK)
|
||||
{
|
||||
// don't render imgui
|
||||
ImGuiManager::SkipFrame();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void Host::EndPresentFrame()
|
||||
|
||||
+5
-5
@@ -967,17 +967,17 @@ void Host::ReleaseHostDisplay(bool clear_state)
|
||||
g_emu_thread->releaseHostDisplay(clear_state);
|
||||
}
|
||||
|
||||
bool Host::BeginPresentFrame(bool frame_skip)
|
||||
HostDisplay::PresentResult Host::BeginPresentFrame(bool frame_skip)
|
||||
{
|
||||
if (!g_host_display->BeginPresent(frame_skip))
|
||||
const HostDisplay::PresentResult result = g_host_display->BeginPresent(frame_skip);
|
||||
if (result != HostDisplay::PresentResult::OK)
|
||||
{
|
||||
// if we're skipping a frame, we need to reset imgui's state, since
|
||||
// we won't be calling EndPresentFrame().
|
||||
ImGuiManager::NewFrame();
|
||||
return false;
|
||||
ImGuiManager::SkipFrame();
|
||||
}
|
||||
|
||||
return true;
|
||||
return result;
|
||||
}
|
||||
|
||||
void Host::EndPresentFrame()
|
||||
|
||||
@@ -641,13 +641,10 @@ bool D3D11HostDisplay::UpdateImGuiFontTexture()
|
||||
return true;
|
||||
}
|
||||
|
||||
bool D3D11HostDisplay::BeginPresent(bool frame_skip)
|
||||
HostDisplay::PresentResult D3D11HostDisplay::BeginPresent(bool frame_skip)
|
||||
{
|
||||
if (frame_skip || !m_swap_chain)
|
||||
{
|
||||
ImGui::EndFrame();
|
||||
return false;
|
||||
}
|
||||
return PresentResult::FrameSkipped;
|
||||
|
||||
// When using vsync, the time here seems to include the time for the buffer to become available.
|
||||
// This blows our our GPU usage number considerably, so read the timestamp before the final blit
|
||||
@@ -664,7 +661,7 @@ bool D3D11HostDisplay::BeginPresent(bool frame_skip)
|
||||
const CD3D11_RECT scissor(0, 0, m_window_info.surface_width, m_window_info.surface_height);
|
||||
m_context->RSSetViewports(1, &vp);
|
||||
m_context->RSSetScissorRects(1, &scissor);
|
||||
return true;
|
||||
return PresentResult::OK;
|
||||
}
|
||||
|
||||
void D3D11HostDisplay::EndPresent()
|
||||
|
||||
@@ -65,7 +65,7 @@ public:
|
||||
|
||||
void SetVSync(VsyncMode mode) override;
|
||||
|
||||
bool BeginPresent(bool frame_skip) override;
|
||||
PresentResult BeginPresent(bool frame_skip) override;
|
||||
void EndPresent() override;
|
||||
|
||||
bool SetGPUTimingEnabled(bool enabled) override;
|
||||
|
||||
@@ -554,13 +554,13 @@ bool D3D12HostDisplay::UpdateImGuiFontTexture()
|
||||
return ImGui_ImplDX12_CreateFontsTexture();
|
||||
}
|
||||
|
||||
bool D3D12HostDisplay::BeginPresent(bool frame_skip)
|
||||
HostDisplay::PresentResult D3D12HostDisplay::BeginPresent(bool frame_skip)
|
||||
{
|
||||
if (m_device_lost)
|
||||
return HostDisplay::PresentResult::DeviceLost;
|
||||
|
||||
if (frame_skip || !m_swap_chain)
|
||||
{
|
||||
ImGui::EndFrame();
|
||||
return false;
|
||||
}
|
||||
return PresentResult::FrameSkipped;
|
||||
|
||||
static constexpr std::array<float, 4> clear_color = {};
|
||||
D3D12::Texture& swap_chain_buf = m_swap_chain_buffers[m_current_swap_chain_buffer];
|
||||
@@ -574,7 +574,7 @@ bool D3D12HostDisplay::BeginPresent(bool frame_skip)
|
||||
const D3D12_RECT scissor{0, 0, static_cast<LONG>(m_window_info.surface_width), static_cast<LONG>(m_window_info.surface_height)};
|
||||
cmdlist->RSSetViewports(1, &vp);
|
||||
cmdlist->RSSetScissorRects(1, &scissor);
|
||||
return true;
|
||||
return PresentResult::OK;
|
||||
}
|
||||
|
||||
void D3D12HostDisplay::EndPresent()
|
||||
@@ -586,7 +586,11 @@ void D3D12HostDisplay::EndPresent()
|
||||
m_current_swap_chain_buffer = ((m_current_swap_chain_buffer + 1) % static_cast<u32>(m_swap_chain_buffers.size()));
|
||||
|
||||
swap_chain_buf.TransitionToState(g_d3d12_context->GetCommandList(), D3D12_RESOURCE_STATE_PRESENT);
|
||||
g_d3d12_context->ExecuteCommandList(D3D12::Context::WaitType::None);
|
||||
if (!g_d3d12_context->ExecuteCommandList(D3D12::Context::WaitType::None))
|
||||
{
|
||||
m_device_lost = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const bool vsync = static_cast<UINT>(m_vsync_mode != VsyncMode::Off);
|
||||
if (!vsync && m_using_allow_tearing)
|
||||
|
||||
@@ -71,7 +71,7 @@ public:
|
||||
|
||||
void SetVSync(VsyncMode mode) override;
|
||||
|
||||
bool BeginPresent(bool frame_skip) override;
|
||||
PresentResult BeginPresent(bool frame_skip) override;
|
||||
void EndPresent() override;
|
||||
|
||||
bool SetGPUTimingEnabled(bool enabled) override;
|
||||
@@ -97,4 +97,5 @@ protected:
|
||||
|
||||
bool m_allow_tearing_supported = false;
|
||||
bool m_using_allow_tearing = false;
|
||||
bool m_device_lost = false;
|
||||
};
|
||||
|
||||
@@ -229,6 +229,12 @@ void ImGuiManager::NewFrame()
|
||||
}
|
||||
}
|
||||
|
||||
void ImGuiManager::SkipFrame()
|
||||
{
|
||||
ImGui::EndFrame();
|
||||
NewFrame();
|
||||
}
|
||||
|
||||
void ImGuiManager::SetStyle()
|
||||
{
|
||||
ImGuiStyle& style = ImGui::GetStyle();
|
||||
|
||||
@@ -40,6 +40,9 @@ namespace ImGuiManager
|
||||
/// Call at the beginning of the frame to set up ImGui state.
|
||||
void NewFrame();
|
||||
|
||||
/// Call when skipping rendering a frame, to update internal state.
|
||||
void SkipFrame();
|
||||
|
||||
/// Renders any on-screen display elements.
|
||||
void RenderOSD();
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ public:
|
||||
std::unique_ptr<HostDisplayTexture> CreateTexture(u32 width, u32 height, const void* data, u32 data_stride, bool dynamic = false) override;
|
||||
void UpdateTexture(id<MTLTexture> texture, u32 x, u32 y, u32 width, u32 height, const void* data, u32 data_stride);
|
||||
void UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* data, u32 data_stride) override;
|
||||
bool BeginPresent(bool frame_skip) override;
|
||||
PresentResult BeginPresent(bool frame_skip) override;
|
||||
void EndPresent() override;
|
||||
void SetVSync(VsyncMode mode) override;
|
||||
|
||||
|
||||
@@ -265,7 +265,7 @@ void MetalHostDisplay::UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y,
|
||||
|
||||
static bool s_capture_next = false;
|
||||
|
||||
bool MetalHostDisplay::BeginPresent(bool frame_skip)
|
||||
HostDisplay::PresentResult MetalHostDisplay::BeginPresent(bool frame_skip)
|
||||
{ @autoreleasepool {
|
||||
GSDeviceMTL* dev = static_cast<GSDeviceMTL*>(g_gs_device.get());
|
||||
if (dev && m_capture_start_frame && dev->FrameNo() == m_capture_start_frame)
|
||||
@@ -273,7 +273,7 @@ bool MetalHostDisplay::BeginPresent(bool frame_skip)
|
||||
if (frame_skip || m_window_info.type == WindowInfo::Type::Surfaceless || !g_gs_device)
|
||||
{
|
||||
ImGui::EndFrame();
|
||||
return false;
|
||||
return PresentResult::FrameSkipped;
|
||||
}
|
||||
id<MTLCommandBuffer> buf = dev->GetRenderCmdBuf();
|
||||
m_current_drawable = MRCRetain([m_layer nextDrawable]);
|
||||
@@ -284,13 +284,13 @@ bool MetalHostDisplay::BeginPresent(bool frame_skip)
|
||||
[buf popDebugGroup];
|
||||
dev->FlushEncoders();
|
||||
ImGui::EndFrame();
|
||||
return false;
|
||||
return PresentResult::FrameSkipped;
|
||||
}
|
||||
[m_pass_desc colorAttachments][0].texture = [m_current_drawable texture];
|
||||
id<MTLRenderCommandEncoder> enc = [buf renderCommandEncoderWithDescriptor:m_pass_desc];
|
||||
[enc setLabel:@"Present"];
|
||||
dev->m_current_render.encoder = MRCRetain(enc);
|
||||
return true;
|
||||
return PresentResult::OK;
|
||||
}}
|
||||
|
||||
void MetalHostDisplay::EndPresent()
|
||||
|
||||
@@ -335,13 +335,10 @@ bool OpenGLHostDisplay::UpdateImGuiFontTexture()
|
||||
return ImGui_ImplOpenGL3_CreateFontsTexture();
|
||||
}
|
||||
|
||||
bool OpenGLHostDisplay::BeginPresent(bool frame_skip)
|
||||
HostDisplay::PresentResult OpenGLHostDisplay::BeginPresent(bool frame_skip)
|
||||
{
|
||||
if (frame_skip || m_window_info.type == WindowInfo::Type::Surfaceless)
|
||||
{
|
||||
ImGui::EndFrame();
|
||||
return false;
|
||||
}
|
||||
return PresentResult::FrameSkipped;
|
||||
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
@@ -350,7 +347,7 @@ bool OpenGLHostDisplay::BeginPresent(bool frame_skip)
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glViewport(0, 0, m_window_info.surface_width, m_window_info.surface_height);
|
||||
|
||||
return true;
|
||||
return PresentResult::OK;
|
||||
}
|
||||
|
||||
void OpenGLHostDisplay::EndPresent()
|
||||
|
||||
@@ -58,7 +58,7 @@ public:
|
||||
|
||||
void SetVSync(VsyncMode mode) override;
|
||||
|
||||
bool BeginPresent(bool frame_skip) override;
|
||||
PresentResult BeginPresent(bool frame_skip) override;
|
||||
void EndPresent() override;
|
||||
|
||||
bool SetGPUTimingEnabled(bool enabled) override;
|
||||
|
||||
@@ -339,17 +339,18 @@ bool VulkanHostDisplay::DoneCurrent()
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VulkanHostDisplay::BeginPresent(bool frame_skip)
|
||||
HostDisplay::PresentResult VulkanHostDisplay::BeginPresent(bool frame_skip)
|
||||
{
|
||||
if (frame_skip || !m_swap_chain)
|
||||
{
|
||||
ImGui::EndFrame();
|
||||
return false;
|
||||
}
|
||||
return PresentResult::FrameSkipped;
|
||||
|
||||
// Previous frame needs to be presented before we can acquire the swap chain.
|
||||
g_vulkan_context->WaitForPresentComplete();
|
||||
|
||||
// Check if the device was lost.
|
||||
if (g_vulkan_context->CheckLastSubmitFail())
|
||||
return PresentResult::DeviceLost;
|
||||
|
||||
VkResult res = m_swap_chain->AcquireNextImage();
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
@@ -367,7 +368,7 @@ bool VulkanHostDisplay::BeginPresent(bool frame_skip)
|
||||
{
|
||||
Console.Error("Failed to recreate surface after loss");
|
||||
g_vulkan_context->ExecuteCommandBuffer(Vulkan::Context::WaitType::None);
|
||||
return false;
|
||||
return PresentResult::FrameSkipped;
|
||||
}
|
||||
|
||||
res = m_swap_chain->AcquireNextImage();
|
||||
@@ -380,7 +381,7 @@ bool VulkanHostDisplay::BeginPresent(bool frame_skip)
|
||||
// Still submit the command buffer, otherwise we'll end up with several frames waiting.
|
||||
LOG_VULKAN_ERROR(res, "vkAcquireNextImageKHR() failed: ");
|
||||
g_vulkan_context->ExecuteCommandBuffer(Vulkan::Context::WaitType::None);
|
||||
return false;
|
||||
return PresentResult::FrameSkipped;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -401,7 +402,7 @@ bool VulkanHostDisplay::BeginPresent(bool frame_skip)
|
||||
const VkRect2D scissor{{0, 0}, {static_cast<u32>(swap_chain_texture.GetWidth()), static_cast<u32>(swap_chain_texture.GetHeight())}};
|
||||
vkCmdSetViewport(g_vulkan_context->GetCurrentCommandBuffer(), 0, 1, &vp);
|
||||
vkCmdSetScissor(g_vulkan_context->GetCurrentCommandBuffer(), 0, 1, &scissor);
|
||||
return true;
|
||||
return PresentResult::OK;
|
||||
}
|
||||
|
||||
void VulkanHostDisplay::EndPresent()
|
||||
|
||||
@@ -47,7 +47,7 @@ public:
|
||||
|
||||
void SetVSync(VsyncMode mode) override;
|
||||
|
||||
bool BeginPresent(bool frame_skip) override;
|
||||
PresentResult BeginPresent(bool frame_skip) override;
|
||||
void EndPresent() override;
|
||||
|
||||
bool SetGPUTimingEnabled(bool enabled) override;
|
||||
|
||||
+52
-35
@@ -209,15 +209,15 @@ static bool DoGSOpen(GSRendererType renderer, u8* basemem)
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
if (!g_gs_device->Create())
|
||||
{
|
||||
if (!g_gs_device->Create())
|
||||
{
|
||||
g_gs_device->Destroy();
|
||||
g_gs_device.reset();
|
||||
return false;
|
||||
}
|
||||
g_gs_device->Destroy();
|
||||
g_gs_device.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!g_gs_renderer)
|
||||
{
|
||||
if (renderer == GSRendererType::Null)
|
||||
{
|
||||
g_gs_renderer = std::make_unique<GSRendererNull>();
|
||||
@@ -230,55 +230,68 @@ static bool DoGSOpen(GSRendererType renderer, u8* basemem)
|
||||
{
|
||||
g_gs_renderer = std::unique_ptr<GSRenderer>(MULTI_ISA_SELECT(makeGSRendererSW)(GSConfig.SWExtraThreads));
|
||||
}
|
||||
|
||||
g_gs_renderer->SetRegsMem(basemem);
|
||||
g_gs_renderer->ResetPCRTC();
|
||||
}
|
||||
catch (std::exception& ex)
|
||||
else
|
||||
{
|
||||
Host::ReportFormattedErrorAsync("GS", "GS error: Exception caught in GSopen: %s", ex.what());
|
||||
g_gs_renderer.reset();
|
||||
g_gs_device->Destroy();
|
||||
g_gs_device.reset();
|
||||
return false;
|
||||
Console.Warning("(DoGSOpen) Using existing renderer.");
|
||||
}
|
||||
|
||||
GSConfig.OsdShowGPU = EmuConfig.GS.OsdShowGPU && g_host_display->SetGPUTimingEnabled(true);
|
||||
|
||||
g_gs_renderer->SetRegsMem(basemem);
|
||||
g_perfmon.Reset();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GSreopen(bool recreate_display, const Pcsx2Config::GSOptions& old_config)
|
||||
bool GSreopen(bool recreate_display, bool recreate_renderer, const Pcsx2Config::GSOptions& old_config)
|
||||
{
|
||||
Console.WriteLn("Reopening GS with %s display", recreate_display ? "new" : "existing");
|
||||
|
||||
g_gs_renderer->Flush(GSState::GSFlushReason::GSREOPEN);
|
||||
if (recreate_renderer)
|
||||
g_gs_renderer->Flush(GSState::GSFlushReason::GSREOPEN);
|
||||
|
||||
freezeData fd = {};
|
||||
if (g_gs_renderer->Freeze(&fd, true) != 0)
|
||||
std::unique_ptr<u8[]> fd_data;
|
||||
if (recreate_renderer)
|
||||
{
|
||||
Console.Error("(GSreopen) Failed to get GS freeze size");
|
||||
return false;
|
||||
}
|
||||
if (g_gs_renderer->Freeze(&fd, true) != 0)
|
||||
{
|
||||
Console.Error("(GSreopen) Failed to get GS freeze size");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<u8[]> fd_data = std::make_unique<u8[]>(fd.size);
|
||||
fd.data = fd_data.get();
|
||||
if (g_gs_renderer->Freeze(&fd, false) != 0)
|
||||
fd_data = std::make_unique<u8[]>(fd.size);
|
||||
fd.data = fd_data.get();
|
||||
if (g_gs_renderer->Freeze(&fd, false) != 0)
|
||||
{
|
||||
Console.Error("(GSreopen) Failed to freeze GS");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Error("(GSreopen) Failed to freeze GS");
|
||||
return false;
|
||||
// Make sure nothing is left over.
|
||||
g_gs_renderer->PurgePool();
|
||||
g_gs_renderer->PurgeTextureCache();
|
||||
}
|
||||
|
||||
if (recreate_display)
|
||||
{
|
||||
g_gs_device->ResetAPIState();
|
||||
if (Host::BeginPresentFrame(true))
|
||||
if (Host::BeginPresentFrame(false) == HostDisplay::PresentResult::OK)
|
||||
Host::EndPresentFrame();
|
||||
}
|
||||
|
||||
u8* basemem = g_gs_renderer->GetRegsMem();
|
||||
const u32 gamecrc = g_gs_renderer->GetGameCRC();
|
||||
g_gs_renderer->Destroy();
|
||||
g_gs_renderer.reset();
|
||||
if (recreate_renderer)
|
||||
{
|
||||
g_gs_renderer->Destroy();
|
||||
g_gs_renderer.reset();
|
||||
}
|
||||
|
||||
g_gs_device->Destroy();
|
||||
g_gs_device.reset();
|
||||
|
||||
@@ -327,13 +340,17 @@ bool GSreopen(bool recreate_display, const Pcsx2Config::GSOptions& old_config)
|
||||
}
|
||||
}
|
||||
|
||||
if (g_gs_renderer->Defrost(&fd) != 0)
|
||||
if (recreate_renderer)
|
||||
{
|
||||
Console.Error("(GSreopen) Failed to defrost");
|
||||
return false;
|
||||
if (g_gs_renderer->Defrost(&fd) != 0)
|
||||
{
|
||||
Console.Error("(GSreopen) Failed to defrost");
|
||||
return false;
|
||||
}
|
||||
|
||||
g_gs_renderer->SetGameCRC(gamecrc);
|
||||
}
|
||||
|
||||
g_gs_renderer->SetGameCRC(gamecrc);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -698,7 +715,7 @@ void GSUpdateConfig(const Pcsx2Config::GSOptions& new_config)
|
||||
GSConfig.DisableShaderCache != old_config.DisableShaderCache ||
|
||||
GSConfig.DisableThreadedPresentation != old_config.DisableThreadedPresentation
|
||||
);
|
||||
if (!GSreopen(do_full_restart, old_config))
|
||||
if (!GSreopen(do_full_restart, true, old_config))
|
||||
pxFailRel("Failed to do full GS reopen");
|
||||
return;
|
||||
}
|
||||
@@ -709,7 +726,7 @@ void GSUpdateConfig(const Pcsx2Config::GSOptions& new_config)
|
||||
GSConfig.SWExtraThreads != old_config.SWExtraThreads ||
|
||||
GSConfig.SWExtraThreadsHeight != old_config.SWExtraThreadsHeight)
|
||||
{
|
||||
if (!GSreopen(false, old_config))
|
||||
if (!GSreopen(false, true, old_config))
|
||||
pxFailRel("Failed to do quick GS reopen");
|
||||
|
||||
return;
|
||||
@@ -787,7 +804,7 @@ void GSSwitchRenderer(GSRendererType new_renderer)
|
||||
const bool recreate_display = (!is_software_switch && existing_api != GetAPIForRenderer(new_renderer));
|
||||
const Pcsx2Config::GSOptions old_config(GSConfig);
|
||||
GSConfig.Renderer = new_renderer;
|
||||
if (!GSreopen(recreate_display, old_config))
|
||||
if (!GSreopen(recreate_display, true, old_config))
|
||||
pxFailRel("Failed to reopen GS for renderer switch.");
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ s16 GSLookupBeforeDrawFunctionId(const std::string_view& name);
|
||||
int GSinit();
|
||||
void GSshutdown();
|
||||
bool GSopen(const Pcsx2Config::GSOptions& config, GSRendererType renderer, u8* basemem);
|
||||
bool GSreopen(bool recreate_display, const Pcsx2Config::GSOptions& old_config);
|
||||
bool GSreopen(bool recreate_display, bool recreate_renderer, const Pcsx2Config::GSOptions& old_config);
|
||||
void GSreset(bool hardware_reset);
|
||||
void GSclose();
|
||||
void GSgifSoftReset(u32 mask);
|
||||
|
||||
@@ -24,15 +24,6 @@ const CRC::Game CRC::m_games[] =
|
||||
{
|
||||
// Note: IDs 0x7ACF7E03, 0x7D4EA48F, 0x37C53760 - shouldn't be added as it's from the multiloaders when packing games.
|
||||
{0x00000000, NoTitle /* NoRegion */},
|
||||
{0x9AAC5309, FFX2 /* EU */},
|
||||
{0x9AAC530C, FFX2 /* FR */},
|
||||
{0x9AAC530A, FFX2 /* ES */},
|
||||
{0x9AAC530D, FFX2 /* DE */},
|
||||
{0x9AAC530B, FFX2 /* IT */},
|
||||
{0x48FE0C71, FFX2 /* US */},
|
||||
{0x8A6D7F14, FFX2 /* JP */},
|
||||
{0xE1FD9A2D, FFX2 /* JP */}, // int.
|
||||
{0x11624CD6, FFX2 /* KO */},
|
||||
{0x08C1ED4D, HauntingGround /* EU */},
|
||||
{0x2CD5794C, HauntingGround /* EU */},
|
||||
{0x867BB945, HauntingGround /* JP */},
|
||||
|
||||
@@ -23,7 +23,6 @@ public:
|
||||
enum Title : u32
|
||||
{
|
||||
NoTitle,
|
||||
FFX2,
|
||||
GetawayGames,
|
||||
HauntingGround,
|
||||
ICO,
|
||||
|
||||
+10
-265
@@ -285,6 +285,14 @@ void GSState::ResetHandlers()
|
||||
m_fpGIFRegHandlers[GIF_A_D_REG_LABEL] = &GSState::GIFRegHandlerNull;
|
||||
}
|
||||
|
||||
void GSState::ResetPCRTC()
|
||||
{
|
||||
PCRTCDisplays.SetVideoMode(GetVideoMode());
|
||||
PCRTCDisplays.EnableDisplays(m_regs->PMODE, m_regs->SMODE2, isReallyInterlaced());
|
||||
PCRTCDisplays.SetRects(0, m_regs->DISP[0].DISPLAY, m_regs->DISP[0].DISPFB);
|
||||
PCRTCDisplays.SetRects(1, m_regs->DISP[1].DISPLAY, m_regs->DISP[1].DISPFB);
|
||||
}
|
||||
|
||||
void GSState::UpdateSettings(const Pcsx2Config::GSOptions& old_config)
|
||||
{
|
||||
m_mipmap = GSConfig.Mipmap;
|
||||
@@ -340,264 +348,6 @@ GSVideoMode GSState::GetVideoMode()
|
||||
__assume(0); // unreachable
|
||||
}
|
||||
|
||||
bool GSState::IsAnalogue()
|
||||
{
|
||||
return GetVideoMode() == GSVideoMode::NTSC || GetVideoMode() == GSVideoMode::PAL || GetVideoMode() == GSVideoMode::HDTV_1080I;
|
||||
}
|
||||
|
||||
GSVector4i GSState::GetFrameMagnifiedRect(int i)
|
||||
{
|
||||
GSVector4i rectangle = { 0, 0, 0, 0 };
|
||||
|
||||
if (!IsEnabled(i))
|
||||
return rectangle;
|
||||
|
||||
const int videomode = static_cast<int>(GetVideoMode()) - 1;
|
||||
const auto& DISP = m_regs->DISP[i].DISPLAY;
|
||||
const bool ignore_offset = !GSConfig.PCRTCOffsets;
|
||||
|
||||
const u32 DW = DISP.DW + 1;
|
||||
const u32 DH = DISP.DH + 1;
|
||||
;
|
||||
// The number of sub pixels to draw are given in DH and DW, the MAGH/V relates to the size of the original square in the FB
|
||||
// but the size it's drawn to uses the default size of the display mode (for PAL/NTSC this is a MAGH of 3)
|
||||
// so for example a game will have a DW of 2559 and a MAGH of 4 to make it 512 (from the FB), but because it's drawing 2560 subpixels
|
||||
// it will cover the entire 640 wide of the screen (2560 / (3+1)).
|
||||
int width;
|
||||
int height;
|
||||
if (ignore_offset)
|
||||
{
|
||||
width = (DW / (DISP.MAGH + 1));
|
||||
height = (DH / (DISP.MAGV + 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
width = (DW / (VideoModeDividers[videomode].x + 1));
|
||||
height = (DH / (VideoModeDividers[videomode].y + 1));
|
||||
}
|
||||
|
||||
int res_multi = 1;
|
||||
|
||||
if (isinterlaced() && m_regs->SMODE2.FFMD && height > 1)
|
||||
res_multi = 2;
|
||||
|
||||
// Set up the display rectangle based on the values obtained from DISPLAY registers
|
||||
rectangle.right = width;
|
||||
rectangle.bottom = height / res_multi;
|
||||
|
||||
return rectangle;
|
||||
}
|
||||
|
||||
int GSState::GetDisplayHMagnification()
|
||||
{
|
||||
// Pick one of the DISPLAY's and hope that they are both the same. Favour DISPLAY[1]
|
||||
for (int i = 1; i >= 0; i--)
|
||||
{
|
||||
if (IsEnabled(i))
|
||||
return m_regs->DISP[i].DISPLAY.MAGH + 1;
|
||||
}
|
||||
|
||||
// If neither DISPLAY is enabled, fallback to resolution offset (should never happen)
|
||||
const int videomode = static_cast<int>(GetVideoMode()) - 1;
|
||||
return VideoModeDividers[videomode].x + 1;
|
||||
}
|
||||
|
||||
GSVector4i GSState::GetDisplayRect(int i)
|
||||
{
|
||||
GSVector4i rectangle = { 0, 0, 0, 0 };
|
||||
|
||||
if (i == -1)
|
||||
{
|
||||
return GetDisplayRect(0).runion(GetDisplayRect(1));
|
||||
}
|
||||
|
||||
if (!IsEnabled(i))
|
||||
return rectangle;
|
||||
|
||||
const auto& DISP = m_regs->DISP[i].DISPLAY;
|
||||
|
||||
const u32 DW = DISP.DW + 1;
|
||||
const u32 DH = DISP.DH + 1;
|
||||
const u32 MAGH = DISP.MAGH + 1;
|
||||
const u32 MAGV = DISP.MAGV + 1;
|
||||
|
||||
const GSVector2i magnification(MAGH, MAGV);
|
||||
|
||||
const int width = DW / magnification.x;
|
||||
const int height = DH / magnification.y;
|
||||
|
||||
const GSVector2i offsets = GetResolutionOffset(i);
|
||||
|
||||
// Set up the display rectangle based on the values obtained from DISPLAY registers
|
||||
rectangle.left = offsets.x;
|
||||
rectangle.top = offsets.y;
|
||||
|
||||
rectangle.right = rectangle.left + width;
|
||||
rectangle.bottom = rectangle.top + height;
|
||||
|
||||
return rectangle;
|
||||
}
|
||||
|
||||
GSVector2i GSState::GetResolutionOffset(int i)
|
||||
{
|
||||
GSVector2i offset = { 0, 0 };
|
||||
|
||||
if (!IsEnabled(i))
|
||||
return offset;
|
||||
|
||||
const int videomode = static_cast<int>(GetVideoMode()) - 1;
|
||||
const auto& DISP = m_regs->DISP[i].DISPLAY;
|
||||
|
||||
const auto& SMODE2 = m_regs->SMODE2;
|
||||
const int res_multi = (SMODE2.INT + 1);
|
||||
const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode];
|
||||
|
||||
offset.x = (static_cast<int>(DISP.DX) - offsets.z) / (VideoModeDividers[videomode].x + 1);
|
||||
offset.y = (static_cast<int>(DISP.DY) - (offsets.w * ((IsAnalogue() && res_multi) ? res_multi : 1))) / (VideoModeDividers[videomode].y + 1);
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
GSVector2i GSState::GetResolution()
|
||||
{
|
||||
const int videomode = static_cast<int>(GetVideoMode()) - 1;
|
||||
const bool ignore_offset = !GSConfig.PCRTCOffsets;
|
||||
|
||||
const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode];
|
||||
|
||||
GSVector2i resolution(offsets.x, offsets.y);
|
||||
|
||||
// The resolution of the framebuffer is double when in FRAME mode and interlaced.
|
||||
// Also we need a special check because no-interlace patches like to render in the original height, but in non-interlaced mode
|
||||
// which means it would normally go off the bottom of the screen. Advantages of emulation, i guess... Limited to Ignore Offsets + Deinterlacing = Off.
|
||||
if ((isinterlaced() && !m_regs->SMODE2.FFMD) || (GSConfig.InterlaceMode == GSInterlaceMode::Off && !GSConfig.PCRTCOffsets && !isinterlaced()))
|
||||
resolution.y *= 2;
|
||||
|
||||
if (ignore_offset)
|
||||
{
|
||||
// Ideally we'd just cut the width at the resolution, but of course we have to hack the hack...
|
||||
// Some games (Mortal Kombat Armageddon) render the image at 834 pixels then shrink it to 624 pixels
|
||||
// which does fit, but when we ignore offsets we go on framebuffer size and some other games
|
||||
// such as Johnny Mosleys Mad Trix and Transformers render too much but design it to go off the screen.
|
||||
int magnified_width = (VideoModeDividers[videomode].z + 1) / GetDisplayHMagnification();
|
||||
|
||||
// When viewing overscan allow up to the overscan size
|
||||
if (GSConfig.PCRTCOverscan)
|
||||
magnified_width = std::max(magnified_width, offsets.x);
|
||||
|
||||
GSVector4i total_rect = GetDisplayRect(0).runion(GetDisplayRect(1));
|
||||
total_rect.z = total_rect.z - total_rect.x;
|
||||
total_rect.w = total_rect.w - total_rect.y;
|
||||
total_rect.z = std::min(total_rect.z, magnified_width);
|
||||
total_rect.w = std::min(total_rect.w, resolution.y);
|
||||
resolution.x = total_rect.z;
|
||||
resolution.y = total_rect.w;
|
||||
}
|
||||
|
||||
return resolution;
|
||||
}
|
||||
|
||||
GSVector4i GSState::GetFrameRect(int i, bool ignore_off)
|
||||
{
|
||||
// If no specific context is requested then pass the merged rectangle as return value
|
||||
if (i == -1)
|
||||
return GetFrameRect(0, ignore_off).runion(GetFrameRect(1, ignore_off));
|
||||
|
||||
GSVector4i rectangle = { 0, 0, 0, 0 };
|
||||
|
||||
if (!IsEnabled(i))
|
||||
return rectangle;
|
||||
|
||||
const auto& DISP = m_regs->DISP[i].DISPLAY;
|
||||
|
||||
const u32 DW = DISP.DW + 1;
|
||||
const u32 DH = DISP.DH + 1;
|
||||
const GSVector2i magnification(DISP.MAGH+1, DISP.MAGV + 1);
|
||||
|
||||
const u32 DBX = m_regs->DISP[i].DISPFB.DBX;
|
||||
int DBY = m_regs->DISP[i].DISPFB.DBY;
|
||||
|
||||
|
||||
const int w = DW / magnification.x;
|
||||
const int h = DH / magnification.y;
|
||||
|
||||
// If the combined height overflows 2048, it's likely adding a bit of extra data before the picture for offsetting the interlace
|
||||
// only game known to do this is NASCAR '08
|
||||
if (!ignore_off && (DBY + h) >= 2048)
|
||||
DBY = DBY - 2048;
|
||||
|
||||
rectangle.left = (ignore_off) ? 0 : DBX;
|
||||
rectangle.top = (ignore_off) ? 0 : DBY;
|
||||
|
||||
rectangle.right = rectangle.left + w;
|
||||
rectangle.bottom = rectangle.top + h;
|
||||
|
||||
if (isinterlaced() && m_regs->SMODE2.FFMD && h > 1)
|
||||
{
|
||||
rectangle.bottom += 1;
|
||||
rectangle.bottom >>= 1;
|
||||
}
|
||||
|
||||
return rectangle;
|
||||
}
|
||||
|
||||
int GSState::GetFramebufferHeight()
|
||||
{
|
||||
// Framebuffer height is 11 bits max
|
||||
constexpr int height_limit = (1 << 11);
|
||||
|
||||
const GSVector4i disp1_rect = GetFrameRect(0, true);
|
||||
const GSVector4i disp2_rect = GetFrameRect(1, true);
|
||||
const GSVector4i combined = disp1_rect.runion(disp2_rect);
|
||||
|
||||
// DBY isn't an offset to the frame memory but rather an offset to read output circuit inside
|
||||
// the frame memory, hence the top offset should also be calculated for the total height of the
|
||||
// frame memory. Also we need to wrap the value only when we're dealing with values with range of the
|
||||
// frame memory (offset + read output circuit height, IOW bottom of merged_output)
|
||||
const int max_height = std::max(disp1_rect.height(), disp2_rect.height());
|
||||
const int frame_memory_height = std::max(max_height, combined.bottom % height_limit);
|
||||
|
||||
if (frame_memory_height > 1024)
|
||||
GL_PERF("Massive framebuffer height detected! (height:%d)", frame_memory_height);
|
||||
|
||||
return frame_memory_height;
|
||||
}
|
||||
|
||||
int GSState::GetFramebufferBitDepth()
|
||||
{
|
||||
if (IsEnabled(0))
|
||||
return GSLocalMemory::m_psm[m_regs->DISP[0].DISPFB.PSM].bpp;
|
||||
else if (IsEnabled(1))
|
||||
return GSLocalMemory::m_psm[m_regs->DISP[1].DISPFB.PSM].bpp;
|
||||
|
||||
return 32;
|
||||
}
|
||||
|
||||
int GSState::GetFramebufferWidth()
|
||||
{
|
||||
const GSVector4i disp1_rect = GetFrameRect(0, true);
|
||||
const GSVector4i disp2_rect = GetFrameRect(1, true);
|
||||
|
||||
const int max_width = std::max(disp1_rect.width(), disp2_rect.width());
|
||||
|
||||
return max_width;
|
||||
}
|
||||
|
||||
bool GSState::IsEnabled(int i)
|
||||
{
|
||||
ASSERT(i >= 0 && i < 2);
|
||||
|
||||
const auto& DISP = m_regs->DISP[i].DISPLAY;
|
||||
|
||||
const bool disp1_enabled = m_regs->PMODE.EN1;
|
||||
const bool disp2_enabled = m_regs->PMODE.EN2;
|
||||
|
||||
if ((i == 0 && disp1_enabled) || (i == 1 && disp2_enabled))
|
||||
return DISP.DW && DISP.DH;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
float GSState::GetTvRefreshRate()
|
||||
{
|
||||
const GSVideoMode videomode = GetVideoMode();
|
||||
@@ -2775,6 +2525,8 @@ int GSState::Defrost(const freezeData* fd)
|
||||
|
||||
g_perfmon.SetFrame(5000);
|
||||
|
||||
ResetPCRTC();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -3517,13 +3269,6 @@ __forceinline void GSState::VertexKick(u32 skip)
|
||||
}
|
||||
|
||||
CLUTAutoFlush(prim);
|
||||
|
||||
if (GSLocalMemory::m_psm[m_prev_env.CTXT[m_prev_env.PRIM.CTXT].FRAME.PSM].bpp == 16 && GSLocalMemory::m_psm[m_prev_env.CTXT[m_prev_env.PRIM.CTXT].TEX0.PSM].bpp == 16
|
||||
&& m_prev_env.CTXT[m_prev_env.PRIM.CTXT].FRAME.FBMSK == 0x3FFF && m_prev_env.CTXT[m_prev_env.PRIM.CTXT].ALPHA.IsOpaque())
|
||||
{
|
||||
DevCon.Warning("Shuffle flush %d", s_n+1);
|
||||
Flush(AUTOFLUSH);
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if region repeat is used (applying it does something to at least one of the values in min...max)
|
||||
|
||||
+489
-40
@@ -290,59 +290,508 @@ public:
|
||||
PRIM_OVERLAP m_prim_overlap;
|
||||
std::vector<size_t> m_drawlist;
|
||||
|
||||
// The horizontal offset values (under z) for PAL and NTSC have been tweaked
|
||||
// they should be apparently 632 and 652 respectively, but that causes a thick black line on the left
|
||||
// these values leave a small black line on the right in a bunch of games, but it's not so bad.
|
||||
// The only conclusion I can come to is there is horizontal overscan expected so there would normally
|
||||
// be black borders either side anyway, or both sides slightly covered.
|
||||
const GSVector4i VideoModeOffsets[6] = {
|
||||
GSVector4i(640, 224, 642, 25),
|
||||
GSVector4i(640, 256, 676, 36),
|
||||
GSVector4i(640, 480, 276, 34),
|
||||
GSVector4i(720, 480, 232, 35),
|
||||
GSVector4i(1280, 720, 302, 24),
|
||||
GSVector4i(1920, 540, 238, 40)
|
||||
};
|
||||
struct GSPCRTCRegs
|
||||
{
|
||||
// The horizontal offset values (under z) for PAL and NTSC have been tweaked
|
||||
// they should be apparently 632 and 652 respectively, but that causes a thick black line on the left
|
||||
// these values leave a small black line on the right in a bunch of games, but it's not so bad.
|
||||
// The only conclusion I can come to is there is horizontal overscan expected so there would normally
|
||||
// be black borders either side anyway, or both sides slightly covered.
|
||||
const GSVector4i VideoModeOffsets[6] = {
|
||||
GSVector4i(640, 224, 642, 25),
|
||||
GSVector4i(640, 256, 676, 36),
|
||||
GSVector4i(640, 480, 276, 34),
|
||||
GSVector4i(720, 480, 232, 35),
|
||||
GSVector4i(1280, 720, 302, 24),
|
||||
GSVector4i(1920, 540, 238, 40)
|
||||
};
|
||||
|
||||
const GSVector4i VideoModeOffsetsOverscan[6] = {
|
||||
GSVector4i(711, 243, 498, 12),
|
||||
GSVector4i(702, 288, 532, 18),
|
||||
GSVector4i(640, 480, 276, 34),
|
||||
GSVector4i(720, 480, 232, 35),
|
||||
GSVector4i(1280, 720, 302, 24),
|
||||
GSVector4i(1920, 540, 238, 40)
|
||||
};
|
||||
const GSVector4i VideoModeOffsetsOverscan[6] = {
|
||||
GSVector4i(711, 243, 498, 12),
|
||||
GSVector4i(702, 288, 532, 18),
|
||||
GSVector4i(640, 480, 276, 34),
|
||||
GSVector4i(720, 480, 232, 35),
|
||||
GSVector4i(1280, 720, 302, 24),
|
||||
GSVector4i(1920, 540, 238, 40)
|
||||
};
|
||||
|
||||
const GSVector4i VideoModeDividers[6] = {
|
||||
GSVector4i(3, 0, 2559, 239),
|
||||
GSVector4i(3, 0, 2559, 287),
|
||||
GSVector4i(1, 0, 1279, 479),
|
||||
GSVector4i(1, 0, 1439, 479),
|
||||
GSVector4i(0, 0, 1279, 719),
|
||||
GSVector4i(0, 0, 1919, 1079)
|
||||
};
|
||||
const GSVector4i VideoModeDividers[6] = {
|
||||
GSVector4i(3, 0, 2559, 239),
|
||||
GSVector4i(3, 0, 2559, 287),
|
||||
GSVector4i(1, 0, 1279, 479),
|
||||
GSVector4i(1, 0, 1439, 479),
|
||||
GSVector4i(0, 0, 1279, 719),
|
||||
GSVector4i(0, 0, 1919, 1079)
|
||||
};
|
||||
|
||||
struct PCRTCDisplay
|
||||
{
|
||||
bool enabled;
|
||||
int FBP;
|
||||
int FBW;
|
||||
int PSM;
|
||||
GSRegDISPFB prevFramebufferReg;
|
||||
GSVector2i prevDisplayOffset;
|
||||
GSVector2i displayOffset;
|
||||
GSVector4i displayRect;
|
||||
GSVector2i magnification;
|
||||
GSVector2i prevFramebufferOffsets;
|
||||
GSVector2i framebufferOffsets;
|
||||
GSVector4i framebufferRect;
|
||||
|
||||
int Block()
|
||||
{
|
||||
return FBP << 5;
|
||||
}
|
||||
};
|
||||
|
||||
int videomode;
|
||||
int interlaced;
|
||||
int FFMD;
|
||||
bool PCRTCSameSrc;
|
||||
bool toggling_field;
|
||||
PCRTCDisplay PCRTCDisplays[2];
|
||||
|
||||
bool IsAnalogue()
|
||||
{
|
||||
GSVideoMode video = static_cast<GSVideoMode>(videomode + 1);
|
||||
return video == GSVideoMode::NTSC || video == GSVideoMode::PAL || video == GSVideoMode::HDTV_1080I;
|
||||
}
|
||||
|
||||
// Calculates which display is closest to matching zero offsets in either direction.
|
||||
GSVector2i NearestToZeroOffset()
|
||||
{
|
||||
GSVector2i returnValue = { 1, 1 };
|
||||
|
||||
if (!PCRTCDisplays[0].enabled && !PCRTCDisplays[1].enabled)
|
||||
return returnValue;
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (!PCRTCDisplays[i].enabled)
|
||||
{
|
||||
returnValue.x = 1 - i;
|
||||
returnValue.y = 1 - i;
|
||||
return returnValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (abs(PCRTCDisplays[0].displayOffset.x - VideoModeOffsets[videomode].z) <
|
||||
abs(PCRTCDisplays[1].displayOffset.x - VideoModeOffsets[videomode].z))
|
||||
returnValue.x = 0;
|
||||
|
||||
// When interlaced, the vertical base offset is doubled
|
||||
int verticalOffset = VideoModeOffsets[videomode].w * (1 << interlaced);
|
||||
|
||||
if (abs(PCRTCDisplays[0].displayOffset.y - verticalOffset) <
|
||||
abs(PCRTCDisplays[1].displayOffset.y - verticalOffset))
|
||||
returnValue.y = 0;
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
void SetVideoMode(GSVideoMode videoModeIn)
|
||||
{
|
||||
videomode = static_cast<int>(videoModeIn) - 1;
|
||||
}
|
||||
|
||||
// Enable each of the displays.
|
||||
void EnableDisplays(GSRegPMODE pmode, GSRegSMODE2 smode2, bool smodetoggle)
|
||||
{
|
||||
PCRTCDisplays[0].enabled = pmode.EN1;
|
||||
PCRTCDisplays[1].enabled = pmode.EN2;
|
||||
|
||||
interlaced = smode2.INT && IsAnalogue();
|
||||
FFMD = smode2.FFMD;
|
||||
toggling_field = smodetoggle && IsAnalogue();
|
||||
}
|
||||
|
||||
void CheckSameSource()
|
||||
{
|
||||
if (PCRTCDisplays[0].enabled != PCRTCDisplays[1].enabled || (PCRTCDisplays[0].enabled | PCRTCDisplays[1].enabled) == false)
|
||||
{
|
||||
PCRTCSameSrc = false;
|
||||
return;
|
||||
}
|
||||
|
||||
PCRTCSameSrc = PCRTCDisplays[0].FBP == PCRTCDisplays[1].FBP &&
|
||||
PCRTCDisplays[0].FBW == PCRTCDisplays[1].FBW &&
|
||||
GSUtil::HasCompatibleBits(PCRTCDisplays[0].PSM, PCRTCDisplays[1].PSM);
|
||||
}
|
||||
|
||||
bool FrameWrap()
|
||||
{
|
||||
GSVector4i combined_rect = GSVector4i(PCRTCDisplays[0].framebufferRect.runion(PCRTCDisplays[1].framebufferRect));
|
||||
return combined_rect.w >= 2048 || combined_rect.z >= 2048;
|
||||
}
|
||||
|
||||
// If the start point of both frames match, we can do a single read
|
||||
bool FrameRectMatch()
|
||||
{
|
||||
return PCRTCSameSrc;
|
||||
}
|
||||
|
||||
GSVector2i GetResolution()
|
||||
{
|
||||
GSVector2i resolution;
|
||||
|
||||
const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode];
|
||||
const bool is_full_height = interlaced || (toggling_field && GSConfig.InterlaceMode != GSInterlaceMode::Off) || GSConfig.InterlaceMode == GSInterlaceMode::Off;
|
||||
|
||||
if (!GSConfig.PCRTCOffsets)
|
||||
{
|
||||
if (PCRTCDisplays[0].enabled && PCRTCDisplays[1].enabled)
|
||||
{
|
||||
GSVector4i combined_size = PCRTCDisplays[0].displayRect.runion(PCRTCDisplays[1].displayRect);
|
||||
resolution = { combined_size.width(), combined_size.height() };
|
||||
}
|
||||
else if (PCRTCDisplays[0].enabled)
|
||||
{
|
||||
resolution = { PCRTCDisplays[0].displayRect.width(), PCRTCDisplays[0].displayRect.height() };
|
||||
}
|
||||
else
|
||||
{
|
||||
resolution = { PCRTCDisplays[1].displayRect.width(), PCRTCDisplays[1].displayRect.height() };
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const int shift = is_full_height ? 1 : 0;
|
||||
resolution = { offsets.x, offsets.y << shift };
|
||||
}
|
||||
|
||||
resolution.x = std::min(resolution.x, offsets.x);
|
||||
resolution.y = std::min(resolution.y, is_full_height ? offsets.y << 1 : offsets.y);
|
||||
|
||||
return resolution;
|
||||
}
|
||||
|
||||
GSVector4i GetFramebufferRect(int display)
|
||||
{
|
||||
if (display == -1)
|
||||
{
|
||||
return GSVector4i(PCRTCDisplays[0].framebufferRect.runion(PCRTCDisplays[1].framebufferRect));
|
||||
}
|
||||
else
|
||||
{
|
||||
return PCRTCDisplays[display].framebufferRect;
|
||||
}
|
||||
}
|
||||
|
||||
int GetFramebufferBitDepth()
|
||||
{
|
||||
if (PCRTCDisplays[0].enabled)
|
||||
return GSLocalMemory::m_psm[PCRTCDisplays[0].PSM].bpp;
|
||||
else if (PCRTCDisplays[1].enabled)
|
||||
return GSLocalMemory::m_psm[PCRTCDisplays[1].PSM].bpp;
|
||||
|
||||
return 32;
|
||||
}
|
||||
|
||||
GSVector2i GetFramebufferSize(int display)
|
||||
{
|
||||
if (display == -1)
|
||||
{
|
||||
GSVector4i combined_rect = PCRTCDisplays[0].framebufferRect.runion(PCRTCDisplays[1].framebufferRect);
|
||||
|
||||
if (combined_rect.z >= 2048)
|
||||
{
|
||||
int high_x = (PCRTCDisplays[0].framebufferRect.x > PCRTCDisplays[1].framebufferRect.x) ? PCRTCDisplays[0].framebufferRect.x : PCRTCDisplays[1].framebufferRect.x;
|
||||
combined_rect.z -= GSConfig.UseHardwareRenderer() ? 2048 : high_x;
|
||||
combined_rect.x = 0;
|
||||
}
|
||||
|
||||
if (combined_rect.w >= 2048)
|
||||
{
|
||||
int high_y = (PCRTCDisplays[0].framebufferRect.y > PCRTCDisplays[1].framebufferRect.y) ? PCRTCDisplays[0].framebufferRect.y : PCRTCDisplays[1].framebufferRect.y;
|
||||
combined_rect.w -= GSConfig.UseHardwareRenderer() ? 2048 : high_y;
|
||||
combined_rect.y = 0;
|
||||
}
|
||||
return GSVector2i(combined_rect.z, combined_rect.w);
|
||||
}
|
||||
else
|
||||
{
|
||||
GSVector4i out_rect = PCRTCDisplays[display].framebufferRect;
|
||||
|
||||
if (out_rect.z >= 2048)
|
||||
{
|
||||
out_rect.z -= GSConfig.UseHardwareRenderer() ? 2048 : out_rect.x;
|
||||
out_rect.x = 0;
|
||||
}
|
||||
|
||||
if (out_rect.w >= 2048)
|
||||
{
|
||||
out_rect.w -= GSConfig.UseHardwareRenderer() ? 2048 : out_rect.y;
|
||||
out_rect.y = 0;
|
||||
}
|
||||
return GSVector2i(out_rect.z, out_rect.w);
|
||||
}
|
||||
}
|
||||
|
||||
// Sets up the rectangles for both the framebuffer read and the displays for the merge circuit.
|
||||
void SetRects(int display, GSRegDISPLAY displayReg, GSRegDISPFB framebufferReg)
|
||||
{
|
||||
// Save framebuffer information first, while we're here.
|
||||
PCRTCDisplays[display].FBP = framebufferReg.FBP;
|
||||
PCRTCDisplays[display].FBW = framebufferReg.FBW;
|
||||
PCRTCDisplays[display].PSM = framebufferReg.PSM;
|
||||
PCRTCDisplays[display].prevFramebufferReg = framebufferReg;
|
||||
// Probably not really enabled but will cause a mess.
|
||||
// Q-Ball Billiards enables both circuits but doesn't set one of them up.
|
||||
if (PCRTCDisplays[display].FBW == 0 && displayReg.DW == 0 && displayReg.DH == 0 && displayReg.MAGH == 0)
|
||||
{
|
||||
PCRTCDisplays[display].enabled = false;
|
||||
return;
|
||||
}
|
||||
PCRTCDisplays[display].magnification = GSVector2i(displayReg.MAGH + 1, displayReg.MAGV + 1);
|
||||
const u32 DW = displayReg.DW + 1;
|
||||
const u32 DH = displayReg.DH + 1;
|
||||
|
||||
const int renderWidth = DW / PCRTCDisplays[display].magnification.x;
|
||||
const int renderHeight = DH / PCRTCDisplays[display].magnification.y;
|
||||
|
||||
u32 finalDisplayWidth = renderWidth;
|
||||
u32 finalDisplayHeight = renderHeight;
|
||||
// When using screen offsets the screen gets squashed/resized in to the actual screen size.
|
||||
if (GSConfig.PCRTCOffsets)
|
||||
{
|
||||
finalDisplayWidth = DW / (VideoModeDividers[videomode].x + 1);
|
||||
finalDisplayHeight = DH / (VideoModeDividers[videomode].y + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
finalDisplayWidth = std::min(finalDisplayWidth ,DW / (VideoModeDividers[videomode].x + 1));
|
||||
finalDisplayHeight = std::min(finalDisplayHeight, DH / (VideoModeDividers[videomode].y + 1));
|
||||
}
|
||||
|
||||
// Framebuffer size and offsets.
|
||||
PCRTCDisplays[display].prevFramebufferOffsets = PCRTCDisplays[display].framebufferOffsets;
|
||||
PCRTCDisplays[display].framebufferRect.x = 0;
|
||||
PCRTCDisplays[display].framebufferRect.y = 0;
|
||||
PCRTCDisplays[display].framebufferRect.z = renderWidth;
|
||||
|
||||
if(FFMD && interlaced) // Round up the height as if it's an odd value, this will cause havok with the merge circuit.
|
||||
PCRTCDisplays[display].framebufferRect.w = (renderHeight + 1) >> (FFMD * interlaced); // Half height read if FFMD + INT enabled.
|
||||
else
|
||||
PCRTCDisplays[display].framebufferRect.w = renderHeight;
|
||||
PCRTCDisplays[display].framebufferOffsets.x = framebufferReg.DBX;
|
||||
PCRTCDisplays[display].framebufferOffsets.y = framebufferReg.DBY;
|
||||
|
||||
const bool is_interlaced_resolution = interlaced || (toggling_field && GSConfig.InterlaceMode != GSInterlaceMode::Off);
|
||||
|
||||
// If the interlace flag isn't set, but it's still interlacing, the height is likely reported wrong.
|
||||
// Q-Ball Billiards.
|
||||
if (is_interlaced_resolution && !interlaced)
|
||||
finalDisplayHeight *= 2;
|
||||
|
||||
// Display size and offsets.
|
||||
PCRTCDisplays[display].displayRect.x = 0;
|
||||
PCRTCDisplays[display].displayRect.y = 0;
|
||||
PCRTCDisplays[display].displayRect.z = finalDisplayWidth;
|
||||
PCRTCDisplays[display].displayRect.w = finalDisplayHeight;
|
||||
PCRTCDisplays[display].prevDisplayOffset = PCRTCDisplays[display].displayOffset;
|
||||
PCRTCDisplays[display].displayOffset.x = displayReg.DX;
|
||||
PCRTCDisplays[display].displayOffset.y = displayReg.DY;
|
||||
}
|
||||
|
||||
// Calculate framebuffer read offsets, should be considered if only one circuit is enabled, or difference is more than 1 line.
|
||||
// Only considered if "Anti-blur" is enabled.
|
||||
void CalculateFramebufferOffset()
|
||||
{
|
||||
if (GSConfig.PCRTCAntiBlur && PCRTCDisplays[0].enabled && PCRTCSameSrc)
|
||||
{
|
||||
if (abs(PCRTCDisplays[1].framebufferOffsets.y - PCRTCDisplays[0].framebufferOffsets.y) == 1
|
||||
&& PCRTCDisplays[0].displayOffset.y == PCRTCDisplays[1].displayOffset.y)
|
||||
{
|
||||
if (PCRTCDisplays[1].framebufferOffsets.y < PCRTCDisplays[0].framebufferOffsets.y)
|
||||
PCRTCDisplays[0].framebufferOffsets.y = PCRTCDisplays[1].framebufferOffsets.y;
|
||||
else
|
||||
PCRTCDisplays[1].framebufferOffsets.y = PCRTCDisplays[0].framebufferOffsets.y;
|
||||
}
|
||||
}
|
||||
PCRTCDisplays[0].framebufferRect.x += PCRTCDisplays[0].framebufferOffsets.x;
|
||||
PCRTCDisplays[0].framebufferRect.z += PCRTCDisplays[0].framebufferOffsets.x;
|
||||
PCRTCDisplays[0].framebufferRect.y += PCRTCDisplays[0].framebufferOffsets.y;
|
||||
PCRTCDisplays[0].framebufferRect.w += PCRTCDisplays[0].framebufferOffsets.y;
|
||||
|
||||
PCRTCDisplays[1].framebufferRect.x += PCRTCDisplays[1].framebufferOffsets.x;
|
||||
PCRTCDisplays[1].framebufferRect.z += PCRTCDisplays[1].framebufferOffsets.x;
|
||||
PCRTCDisplays[1].framebufferRect.y += PCRTCDisplays[1].framebufferOffsets.y;
|
||||
PCRTCDisplays[1].framebufferRect.w += PCRTCDisplays[1].framebufferOffsets.y;
|
||||
}
|
||||
|
||||
// Used in software mode to align the buffer when reading. Offset is accounted for (block aligned) by GetOutput.
|
||||
void RemoveFramebufferOffset(int display)
|
||||
{
|
||||
if (display >= 0)
|
||||
{
|
||||
// Hardware needs nothing but handling for wrapped framebuffers.
|
||||
if (GSConfig.UseHardwareRenderer())
|
||||
{
|
||||
if (PCRTCDisplays[display].framebufferRect.z >= 2048)
|
||||
{
|
||||
PCRTCDisplays[display].framebufferRect.x = 0;
|
||||
PCRTCDisplays[display].framebufferRect.z -= 2048;
|
||||
}
|
||||
if (PCRTCDisplays[display].framebufferRect.w >= 2048)
|
||||
{
|
||||
PCRTCDisplays[display].framebufferRect.y = 0;
|
||||
PCRTCDisplays[display].framebufferRect.w -= 2048;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[PCRTCDisplays[display].PSM];
|
||||
|
||||
GSVector4i r = PCRTCDisplays[display].framebufferRect;
|
||||
r = r.ralign<Align_Outside>(psm.bs);
|
||||
|
||||
PCRTCDisplays[display].framebufferRect.z -= r.x;
|
||||
PCRTCDisplays[display].framebufferRect.w -= r.y;
|
||||
PCRTCDisplays[display].framebufferRect.x -= r.x;
|
||||
PCRTCDisplays[display].framebufferRect.y -= r.y;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// This code is to read the framebuffer nicely block aligned in software, then leave the remaining offset in to the block.
|
||||
// In hardware mode this doesn't happen, it reads the whole framebuffer, so we need to keep the offset.
|
||||
if (!GSConfig.UseHardwareRenderer())
|
||||
{
|
||||
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[PCRTCDisplays[1].PSM];
|
||||
|
||||
GSVector4i r = PCRTCDisplays[0].framebufferRect.runion(PCRTCDisplays[1].framebufferRect);
|
||||
r = r.ralign<Align_Outside>(psm.bs);
|
||||
|
||||
PCRTCDisplays[0].framebufferRect.x -= r.x;
|
||||
PCRTCDisplays[0].framebufferRect.y -= r.y;
|
||||
PCRTCDisplays[0].framebufferRect.z -= r.x;
|
||||
PCRTCDisplays[0].framebufferRect.w -= r.y;
|
||||
PCRTCDisplays[1].framebufferRect.x -= r.x;
|
||||
PCRTCDisplays[1].framebufferRect.y -= r.y;
|
||||
PCRTCDisplays[1].framebufferRect.z -= r.x;
|
||||
PCRTCDisplays[1].framebufferRect.w -= r.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the two displays are offset from each other, move them to the correct offsets.
|
||||
// If using screen offsets, calculate the positions here.
|
||||
void CalculateDisplayOffset(bool scanmask)
|
||||
{
|
||||
// Offsets are generally ignored, the "hacky" way of doing the displays, but direct to framebuffers.
|
||||
if (!GSConfig.PCRTCOffsets)
|
||||
{
|
||||
const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode];
|
||||
int int_off[2] = { 0, 0 };
|
||||
GSVector2i zeroDisplay = NearestToZeroOffset();
|
||||
GSVector2i baseOffset = PCRTCDisplays[zeroDisplay.y].displayOffset;
|
||||
|
||||
int blurOffset = abs(PCRTCDisplays[1].displayOffset.y - PCRTCDisplays[0].displayOffset.y);
|
||||
if (GSConfig.PCRTCAntiBlur && !scanmask && blurOffset < 4)
|
||||
{
|
||||
if (PCRTCDisplays[1].displayOffset.y > PCRTCDisplays[0].displayOffset.y)
|
||||
PCRTCDisplays[1].displayOffset.y -= blurOffset;
|
||||
else
|
||||
PCRTCDisplays[0].displayOffset.y -= blurOffset;
|
||||
}
|
||||
|
||||
// If there's a single pixel offset, account for it else it can throw interlacing out.
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (!PCRTCDisplays[i].enabled)
|
||||
continue;
|
||||
|
||||
// Should this be MAGV/H in the DISPLAY register rather than the "default" magnification?
|
||||
const int offset = (PCRTCDisplays[i].displayOffset.y - (offsets.w * (interlaced + 1))) / (VideoModeDividers[videomode].y + 1);
|
||||
|
||||
if (offset > 4)
|
||||
continue;
|
||||
|
||||
int_off[i] = offset & 1;
|
||||
if (offset < 0)
|
||||
int_off[i] = -int_off[i];
|
||||
|
||||
PCRTCDisplays[i].displayRect.y += int_off[i];
|
||||
PCRTCDisplays[i].displayRect.w += int_off[i];
|
||||
}
|
||||
|
||||
// Handle difference in offset between the two displays, used in games like DmC and Time Crisis 2 (for split screen).
|
||||
// Offset is not screen based, but relative to each other.
|
||||
if (PCRTCDisplays[0].enabled && PCRTCDisplays[1].enabled)
|
||||
{
|
||||
GSVector2i offset;
|
||||
|
||||
offset.x = (PCRTCDisplays[1 - zeroDisplay.x].displayOffset.x - PCRTCDisplays[zeroDisplay.x].displayOffset.x) / (VideoModeDividers[videomode].x + 1);
|
||||
offset.y = (PCRTCDisplays[1 - zeroDisplay.y].displayOffset.y - PCRTCDisplays[zeroDisplay.y].displayOffset.y) / (VideoModeDividers[videomode].y + 1);
|
||||
|
||||
if (offset.x >= 4 || !GSConfig.PCRTCAntiBlur)
|
||||
{
|
||||
PCRTCDisplays[1 - zeroDisplay.x].displayRect.x += offset.x;
|
||||
PCRTCDisplays[1 - zeroDisplay.x].displayRect.z += offset.x;
|
||||
}
|
||||
if (offset.y >= 4 || !GSConfig.PCRTCAntiBlur)
|
||||
{
|
||||
PCRTCDisplays[1 - zeroDisplay.y].displayRect.y += offset.y - int_off[1 - zeroDisplay.y];
|
||||
PCRTCDisplays[1 - zeroDisplay.y].displayRect.w += offset.y - int_off[1 - zeroDisplay.y];
|
||||
}
|
||||
|
||||
baseOffset = PCRTCDisplays[zeroDisplay.y].displayOffset;
|
||||
}
|
||||
|
||||
// Handle any large vertical offset from the zero position on the screen.
|
||||
// Example: Hokuto no Ken, does a rougly -14 offset to bring the screen up.
|
||||
// Ignore the lowest bit, we've already accounted for this
|
||||
int vOffset = ((static_cast<int>(baseOffset.y) - (offsets.w * (interlaced + 1))) / (VideoModeDividers[videomode].y + 1));
|
||||
|
||||
if(vOffset <= 4 && vOffset != 0)
|
||||
{
|
||||
PCRTCDisplays[0].displayRect.y += vOffset - int_off[0];
|
||||
PCRTCDisplays[0].displayRect.w += vOffset - int_off[0];
|
||||
PCRTCDisplays[1].displayRect.y += vOffset - int_off[1];
|
||||
PCRTCDisplays[1].displayRect.w += vOffset - int_off[1];
|
||||
}
|
||||
}
|
||||
else // We're using screen offsets, so just calculate the entire offset.
|
||||
{
|
||||
const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode];
|
||||
GSVector2i offset = { 0, 0 };
|
||||
|
||||
int blurOffset = abs(PCRTCDisplays[1].displayOffset.y - PCRTCDisplays[0].displayOffset.y);
|
||||
if (GSConfig.PCRTCAntiBlur && !scanmask && blurOffset < 4)
|
||||
{
|
||||
if (PCRTCDisplays[1].displayOffset.y > PCRTCDisplays[0].displayOffset.y)
|
||||
PCRTCDisplays[1].displayOffset.y -= blurOffset;
|
||||
else
|
||||
PCRTCDisplays[0].displayOffset.y -= blurOffset;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
// Should this be MAGV/H in the DISPLAY register rather than the "default" magnification?
|
||||
offset.x = (static_cast<int>(PCRTCDisplays[i].displayOffset.x) - offsets.z) / (VideoModeDividers[videomode].x + 1);
|
||||
offset.y = (static_cast<int>(PCRTCDisplays[i].displayOffset.y) - (offsets.w * (interlaced + 1))) / (VideoModeDividers[videomode].y + 1);
|
||||
|
||||
PCRTCDisplays[i].displayRect.x += offset.x;
|
||||
PCRTCDisplays[i].displayRect.z += offset.x;
|
||||
PCRTCDisplays[i].displayRect.y += offset.y;
|
||||
PCRTCDisplays[i].displayRect.w += offset.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
} PCRTCDisplays;
|
||||
|
||||
public:
|
||||
/// Returns the appropriate directory for draw dumping.
|
||||
static std::string GetDrawDumpPath(const char* format, ...);
|
||||
|
||||
void ResetHandlers();
|
||||
void ResetPCRTC();
|
||||
|
||||
int GetFramebufferHeight();
|
||||
int GetFramebufferWidth();
|
||||
int GetFramebufferBitDepth();
|
||||
int GetDisplayHMagnification();
|
||||
GSVector4i GetDisplayRect(int i = -1);
|
||||
GSVector4i GetFrameMagnifiedRect(int i = -1);
|
||||
GSVector2i GetResolutionOffset(int i = -1);
|
||||
GSVector2i GetResolution();
|
||||
GSVector4i GetFrameRect(int i = -1, bool ignore_off = false);
|
||||
GSVideoMode GetVideoMode();
|
||||
|
||||
bool IsEnabled(int i);
|
||||
bool isinterlaced();
|
||||
bool isReallyInterlaced();
|
||||
bool IsAnalogue();
|
||||
|
||||
float GetTvRefreshRate();
|
||||
|
||||
|
||||
@@ -356,6 +356,7 @@ void GSDevice::Interlace(const GSVector2i& ds, int field, int mode, float yoffse
|
||||
break;
|
||||
default:
|
||||
m_current = m_merge;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -357,7 +357,6 @@ struct alignas(16) GSHWDrawConfig
|
||||
|
||||
// Scan mask
|
||||
u32 scanmsk : 2;
|
||||
u32 swap_ga : 1;
|
||||
};
|
||||
|
||||
struct
|
||||
|
||||
@@ -50,6 +50,8 @@ std::unique_ptr<GSRenderer> g_gs_renderer;
|
||||
// we might be switching while the other thread reads it.
|
||||
static GSVector4 s_last_draw_rect;
|
||||
|
||||
// Last time we reset the renderer due to a GPU crash, if any.
|
||||
static Common::Timer::Value s_last_gpu_reset_time;
|
||||
|
||||
GSRenderer::GSRenderer()
|
||||
: m_shader_time_start(Common::Timer::GetCurrentValue())
|
||||
@@ -75,264 +77,109 @@ void GSRenderer::Destroy()
|
||||
|
||||
bool GSRenderer::Merge(int field)
|
||||
{
|
||||
bool en[2];
|
||||
|
||||
GSVector4i fr[2];
|
||||
GSVector4i dr[2];
|
||||
GSVector2i display_offsets[2];
|
||||
|
||||
GSVector2i display_baseline = {INT_MAX, INT_MAX};
|
||||
GSVector2i frame_baseline = {INT_MAX, INT_MAX};
|
||||
GSVector2i display_combined = {0, 0};
|
||||
bool feedback_merge = m_regs->EXTWRITE.WRITE == 1;
|
||||
bool display_offset = false;
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
en[i] = IsEnabled(i) || (m_regs->EXTBUF.FBIN == i && feedback_merge);
|
||||
|
||||
if (en[i])
|
||||
{
|
||||
fr[i] = GetFrameRect(i);
|
||||
dr[i] = GetDisplayRect(i);
|
||||
display_offsets[i] = GetResolutionOffset(i);
|
||||
|
||||
display_combined.x = std::max(((dr[i].right) - dr[i].left) + display_offsets[i].x, display_combined.x);
|
||||
display_combined.y = std::max((dr[i].bottom - dr[i].top) + display_offsets[i].y, display_combined.y);
|
||||
|
||||
display_baseline.x = std::min(display_offsets[i].x, display_baseline.x);
|
||||
display_baseline.y = std::min(display_offsets[i].y, display_baseline.y);
|
||||
frame_baseline.x = std::min(std::max(fr[i].left, 0), frame_baseline.x);
|
||||
frame_baseline.y = std::min(std::max(fr[i].top, 0), frame_baseline.y);
|
||||
|
||||
display_offset |= std::abs(display_baseline.y - display_offsets[i].y) == 1;
|
||||
/*DevCon.Warning("Read offset was X %d(left %d) Y %d(top %d)", display_baseline.x, dr[i].left, display_baseline.y, dr[i].top);
|
||||
DevCon.Warning("[%d]: %d %d %d %d, %d %d %d %d\n", i, fr[i].x,fr[i].y,fr[i].z,fr[i].w , dr[i].x,dr[i].y,dr[i].z,dr[i].w);
|
||||
DevCon.Warning("Offset X %d Offset Y %d", display_offsets[i].x, display_offsets[i].y);*/
|
||||
}
|
||||
}
|
||||
|
||||
if (!en[0] && !en[1])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
GL_PUSH("Renderer Merge %d (0: enabled %d 0x%x, 1: enabled %d 0x%x)", s_n, en[0], m_regs->DISP[0].DISPFB.Block(), en[1], m_regs->DISP[1].DISPFB.Block());
|
||||
|
||||
// try to avoid fullscreen blur, could be nice on tv but on a monitor it's like double vision, hurts my eyes (persona 4, guitar hero)
|
||||
//
|
||||
// NOTE: probably the technique explained in graphtip.pdf (Antialiasing by Supersampling / 4. Reading Odd/Even Scan Lines Separately with the PCRTC then Blending)
|
||||
|
||||
const bool samesrc =
|
||||
en[0] && en[1] &&
|
||||
m_regs->DISP[0].DISPFB.FBP == m_regs->DISP[1].DISPFB.FBP &&
|
||||
m_regs->DISP[0].DISPFB.FBW == m_regs->DISP[1].DISPFB.FBW &&
|
||||
GSUtil::HasCompatibleBits(m_regs->DISP[0].DISPFB.PSM, m_regs->DISP[1].DISPFB.PSM);
|
||||
bool single_fetch = false;
|
||||
|
||||
GSVector2i fs(0, 0);
|
||||
GSVector2i ds(0, 0);
|
||||
GSTexture* tex[3] = { NULL, NULL, NULL };
|
||||
int y_offset[3] = { 0, 0, 0 };
|
||||
bool feedback_merge = m_regs->EXTWRITE.WRITE == 1;
|
||||
|
||||
GSTexture* tex[3] = {NULL, NULL, NULL};
|
||||
int y_offset[3] = {0, 0, 0};
|
||||
PCRTCDisplays.SetVideoMode(GetVideoMode());
|
||||
PCRTCDisplays.EnableDisplays(m_regs->PMODE, m_regs->SMODE2, isReallyInterlaced());
|
||||
|
||||
s_n++;
|
||||
if (!PCRTCDisplays.PCRTCDisplays[0].enabled && !PCRTCDisplays.PCRTCDisplays[1].enabled)
|
||||
return false;
|
||||
|
||||
// Need to do this here, if the user has Anti-Blur enabled, these offsets can get wiped out/changed.
|
||||
const bool game_deinterlacing = (m_regs->DISP[0].DISPFB.DBY != PCRTCDisplays.PCRTCDisplays[0].prevFramebufferReg.DBY) !=
|
||||
(m_regs->DISP[1].DISPFB.DBY != PCRTCDisplays.PCRTCDisplays[1].prevFramebufferReg.DBY);
|
||||
|
||||
PCRTCDisplays.SetRects(0, m_regs->DISP[0].DISPLAY, m_regs->DISP[0].DISPFB);
|
||||
PCRTCDisplays.SetRects(1, m_regs->DISP[1].DISPLAY, m_regs->DISP[1].DISPFB);
|
||||
PCRTCDisplays.CalculateDisplayOffset(m_scanmask_used);
|
||||
PCRTCDisplays.CalculateFramebufferOffset();
|
||||
PCRTCDisplays.CheckSameSource();
|
||||
|
||||
// Only need to check the right/bottom on software renderer, hardware always gets the full texture then cuts a bit out later.
|
||||
if (samesrc && !feedback_merge && (GSConfig.UseHardwareRenderer() || (fr[0].right == fr[1].right && fr[0].bottom == fr[1].bottom)))
|
||||
if (PCRTCDisplays.FrameRectMatch() && !PCRTCDisplays.FrameWrap() && !feedback_merge)
|
||||
{
|
||||
tex[0] = GetOutput(0, y_offset[0]);
|
||||
tex[0] = GetOutput(-1, y_offset[0]);
|
||||
tex[1] = tex[0]; // saves one texture fetch
|
||||
y_offset[1] = y_offset[0];
|
||||
single_fetch = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (en[0])
|
||||
if (PCRTCDisplays.PCRTCDisplays[0].enabled)
|
||||
tex[0] = GetOutput(0, y_offset[0]);
|
||||
if (en[1])
|
||||
if (PCRTCDisplays.PCRTCDisplays[1].enabled)
|
||||
tex[1] = GetOutput(1, y_offset[1]);
|
||||
if (feedback_merge)
|
||||
tex[2] = GetFeedbackOutput();
|
||||
}
|
||||
|
||||
if (!tex[0] && !tex[1])
|
||||
return false;
|
||||
|
||||
s_n++;
|
||||
|
||||
GSVector4 src_out_rect[2];
|
||||
GSVector4 src_gs_read[2];
|
||||
GSVector4 dst[3];
|
||||
|
||||
const bool slbg = m_regs->PMODE.SLBG;
|
||||
|
||||
GSVector2i resolution(GetResolution());
|
||||
bool scanmask_frame = m_scanmask_used && !display_offset;
|
||||
const bool ignore_offset = !GSConfig.PCRTCOffsets;
|
||||
const bool is_bob = GSConfig.InterlaceMode == GSInterlaceMode::BobTFF || GSConfig.InterlaceMode == GSInterlaceMode::BobBFF;
|
||||
|
||||
// Use offset for bob deinterlacing always, extra offset added later for FFMD mode.
|
||||
float offset = is_bob ? (tex[1] ? tex[1]->GetScale().y : tex[0]->GetScale().y) : 0.0f;
|
||||
|
||||
const bool scanmask_frame = m_scanmask_used && abs(PCRTCDisplays.PCRTCDisplays[0].displayRect.y - PCRTCDisplays.PCRTCDisplays[1].displayRect.y) != 1;
|
||||
int field2 = 0;
|
||||
int mode = 3;
|
||||
int mode = 3; // If the game is manually deinterlacing then we need to bob (if we want to get away with no deinterlacing).
|
||||
bool is_bob = GSConfig.InterlaceMode == GSInterlaceMode::BobTFF || GSConfig.InterlaceMode == GSInterlaceMode::BobBFF;
|
||||
|
||||
// FFMD (half frames) requires blend deinterlacing, so automatically use that. Same when SCANMSK is used but not blended in the merge circuit (Alpine Racer 3)
|
||||
// FFMD (half frames) requires blend deinterlacing, so automatically use that. Same when SCANMSK is used but not blended in the merge circuit (Alpine Racer 3).
|
||||
if (GSConfig.InterlaceMode != GSInterlaceMode::Automatic || (!m_regs->SMODE2.FFMD && !scanmask_frame))
|
||||
{
|
||||
field2 = ((static_cast<int>(GSConfig.InterlaceMode) - 2) & 1);
|
||||
mode = ((static_cast<int>(GSConfig.InterlaceMode) - 2) >> 1);
|
||||
// If the game is offsetting each frame itself and we're using full height buffers, we can offset this with Bob.
|
||||
if (game_deinterlacing && !scanmask_frame && GSConfig.InterlaceMode == GSInterlaceMode::Automatic)
|
||||
{
|
||||
mode = 1; // Bob.
|
||||
is_bob = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
field2 = ((static_cast<int>(GSConfig.InterlaceMode) - 2) & 1);
|
||||
mode = ((static_cast<int>(GSConfig.InterlaceMode) - 2) >> 1);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (!en[i] || !tex[i])
|
||||
const GSPCRTCRegs::PCRTCDisplay& curCircuit = PCRTCDisplays.PCRTCDisplays[i];
|
||||
|
||||
if (!curCircuit.enabled || !tex[i])
|
||||
continue;
|
||||
|
||||
GSVector4i r = GetFrameMagnifiedRect(i);
|
||||
GSVector4 scale = GSVector4(tex[i]->GetScale()).xyxy();
|
||||
|
||||
|
||||
GSVector2i off(ignore_offset ? 0 : display_offsets[i]);
|
||||
GSVector2i display_diff(display_offsets[i].x - display_baseline.x, display_offsets[i].y - display_baseline.y);
|
||||
GSVector2i frame_diff(fr[i].left - frame_baseline.x, fr[i].top - frame_baseline.y);
|
||||
|
||||
if (!GSConfig.UseHardwareRenderer())
|
||||
{
|
||||
// Clear any frame offsets, offset is already done in the software GetOutput.
|
||||
fr[i].right -= fr[i].left;
|
||||
fr[i].left = 0;
|
||||
fr[i].bottom -= fr[i].top;
|
||||
fr[i].top = 0;
|
||||
|
||||
// Put any frame offset difference back if we aren't anti-blurring on a single fetch (not offset).
|
||||
if (!GSConfig.PCRTCAntiBlur && single_fetch)
|
||||
{
|
||||
fr[i].right += frame_diff.x;
|
||||
fr[i].left += frame_diff.x;
|
||||
fr[i].bottom += frame_diff.y;
|
||||
fr[i].top += frame_diff.y;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// If using scanmsk we have to keep the single line offset, regardless of upscale
|
||||
// so we handle this separately after the rect calculations.
|
||||
float interlace_offset = 0.0f;
|
||||
|
||||
if ((!GSConfig.PCRTCAntiBlur || m_scanmask_used) && display_offset)
|
||||
{
|
||||
interlace_offset = static_cast<float>(display_diff.y & 1);
|
||||
|
||||
// When the displays are offset by 1 we need to adjust for upscale to handle it (reduces bounce in MGS2 when upscaling)
|
||||
interlace_offset += (tex[i]->GetScale().y - 1.0f) / 2;
|
||||
|
||||
if (interlace_offset >= 1.0f)
|
||||
{
|
||||
if (!ignore_offset)
|
||||
off.y -= 1;
|
||||
|
||||
display_diff.y -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Start of Anti-Blur code.
|
||||
if (!ignore_offset)
|
||||
{
|
||||
if (GSConfig.PCRTCAntiBlur)
|
||||
{
|
||||
if (samesrc)
|
||||
{
|
||||
// Offset by DISPLAY setting
|
||||
if (display_diff.x < 4)
|
||||
off.x -= display_diff.x;
|
||||
if (display_diff.y < 4)
|
||||
off.y -= display_diff.y;
|
||||
|
||||
// Only functional in HW mode, software clips/positions the framebuffer on read.
|
||||
if (GSConfig.UseHardwareRenderer())
|
||||
{
|
||||
// Offset by DISPFB setting
|
||||
if (abs(frame_diff.x) < 4)
|
||||
off.x += frame_diff.x;
|
||||
if (abs(frame_diff.y) < 4)
|
||||
off.y += frame_diff.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!slbg || !feedback_merge)
|
||||
{
|
||||
// If the offsets between the two displays are quite large, it's probably intended for an effect.
|
||||
if (display_diff.x >= 4 || !GSConfig.PCRTCAntiBlur)
|
||||
off.x = display_diff.x;
|
||||
|
||||
if (display_diff.y >= 4 || !GSConfig.PCRTCAntiBlur)
|
||||
off.y = display_diff.y;
|
||||
|
||||
// Need to check if only circuit 2 is enabled. Stuntman toggles circuit 1 on and off every other frame.
|
||||
if (samesrc || m_regs->PMODE.EN == 2)
|
||||
{
|
||||
// Adjusting the screen offset when using a negative offset.
|
||||
const int videomode = static_cast<int>(GetVideoMode()) - 1;
|
||||
const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode];
|
||||
GSVector2i base_resolution(offsets.x, offsets.y);
|
||||
|
||||
if (isinterlaced() && !m_regs->SMODE2.FFMD)
|
||||
base_resolution.y *= 2;
|
||||
|
||||
// Offset by DISPLAY setting
|
||||
if (display_diff.x < 0)
|
||||
{
|
||||
off.x = 0;
|
||||
if (base_resolution.x > resolution.x)
|
||||
resolution.x -= display_diff.x;
|
||||
}
|
||||
if (display_diff.y < 0)
|
||||
{
|
||||
off.y = 0;
|
||||
if (base_resolution.y > resolution.y)
|
||||
resolution.y -= display_diff.y;
|
||||
}
|
||||
|
||||
// Don't do X, we only care about height, this would need to be tailored for games using X (Black does -5).
|
||||
// Mainly for Hokuto no Ken which does -14 Y offset.
|
||||
if (display_baseline.y < -4)
|
||||
off.y += display_baseline.y;
|
||||
|
||||
// Anti-Blur stuff
|
||||
// Only functional in HW mode, software clips/positions the framebuffer on read.
|
||||
if (GSConfig.PCRTCAntiBlur && GSConfig.UseHardwareRenderer())
|
||||
{
|
||||
// Offset by DISPFB setting
|
||||
if (abs(frame_diff.x) < 4)
|
||||
off.x += frame_diff.x;
|
||||
if (abs(frame_diff.y) < 4)
|
||||
off.y += frame_diff.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// End of Anti-Blur code.
|
||||
|
||||
if (isinterlaced() && m_regs->SMODE2.FFMD)
|
||||
off.y >>= 1;
|
||||
|
||||
// dst is the final destination rect with offset on the screen.
|
||||
dst[i] = scale * (GSVector4(off).xyxy() + GSVector4(r.rsize()));
|
||||
dst[i] = scale * GSVector4(curCircuit.displayRect);
|
||||
|
||||
// src_gs_read is the size which we're really reading from GS memory.
|
||||
src_gs_read[i] = ((GSVector4(fr[i]) + GSVector4(0, y_offset[i], 0, y_offset[i])) * scale) / GSVector4(tex[i]->GetSize()).xyxy();
|
||||
|
||||
// src_out_rect is the resized rect for output. (Not really used)
|
||||
src_out_rect[i] = (GSVector4(r) * scale) / GSVector4(tex[i]->GetSize()).xyxy();
|
||||
|
||||
if (m_regs->SMODE2.FFMD && !is_bob && !GSConfig.DisableInterlaceOffset && GSConfig.InterlaceMode != GSInterlaceMode::Off)
|
||||
src_gs_read[i] = ((GSVector4(curCircuit.framebufferRect) + GSVector4(0, y_offset[i], 0, y_offset[i])) * scale) / GSVector4(tex[i]->GetSize()).xyxy();
|
||||
|
||||
float interlace_offset = 0.0f;
|
||||
if (isReallyInterlaced() && m_regs->SMODE2.FFMD && !is_bob && !GSConfig.DisableInterlaceOffset && GSConfig.InterlaceMode != GSInterlaceMode::Off)
|
||||
{
|
||||
// We do half because FFMD is a half sized framebuffer, then we offset by 1 in the shader for the actual interlace
|
||||
if(GetUpscaleMultiplier() > 1.0f)
|
||||
interlace_offset += ((((tex[1] ? tex[1]->GetScale().y : tex[0]->GetScale().y) + 0.5f) * 0.5f) - 1.0f) * static_cast<float>(field ^ field2);
|
||||
offset = 1.0f;
|
||||
interlace_offset = (scale.y) * static_cast<float>(field ^ field2);
|
||||
}
|
||||
// Restore manually offset "interlace" lines
|
||||
// Scanmask frame offsets. It's gross, I'm sorry but it sucks.
|
||||
if (m_scanmask_used)
|
||||
{
|
||||
int displayIntOffset = PCRTCDisplays.PCRTCDisplays[i].displayRect.y - PCRTCDisplays.PCRTCDisplays[1 - i].displayRect.y;
|
||||
|
||||
if (displayIntOffset > 0)
|
||||
{
|
||||
displayIntOffset &= 1;
|
||||
dst[i].y -= displayIntOffset * scale.y;
|
||||
dst[i].w -= displayIntOffset * scale.y;
|
||||
interlace_offset += displayIntOffset;
|
||||
}
|
||||
}
|
||||
|
||||
dst[i] += GSVector4(0.0f, interlace_offset, 0.0f, interlace_offset);
|
||||
}
|
||||
|
||||
@@ -349,41 +196,18 @@ bool GSRenderer::Merge(int field)
|
||||
dst[2] = GSVector4(scale * GSVector4(feedback_rect.rsize()));
|
||||
}
|
||||
|
||||
// Set the resolution to the height of the displays (kind of a saturate height)
|
||||
if (ignore_offset && !feedback_merge)
|
||||
{
|
||||
GSVector2i max_resolution = GetResolution();
|
||||
resolution.x = display_combined.x - display_baseline.x;
|
||||
resolution.y = display_combined.y - display_baseline.y;
|
||||
|
||||
if (isinterlaced() && m_regs->SMODE2.FFMD)
|
||||
{
|
||||
resolution.y >>= 1;
|
||||
}
|
||||
|
||||
resolution.x = std::min(max_resolution.x, resolution.x);
|
||||
resolution.y = std::min(max_resolution.y, resolution.y);
|
||||
}
|
||||
|
||||
GSVector2i resolution = PCRTCDisplays.GetResolution();
|
||||
fs = GSVector2i(static_cast<int>(static_cast<float>(resolution.x) * GetUpscaleMultiplier()),
|
||||
static_cast<int>(static_cast<float>(resolution.y) * GetUpscaleMultiplier()));
|
||||
ds = fs;
|
||||
|
||||
// When interlace(FRAME) mode, the rect is half height, so it needs to be stretched.
|
||||
const bool is_interlaced_resolution = m_regs->SMODE2.INT || (isReallyInterlaced() && IsAnalogue() && GSConfig.InterlaceMode != GSInterlaceMode::Off);
|
||||
m_real_size = GSVector2i(fs.x, fs.y);
|
||||
|
||||
if (is_interlaced_resolution && m_regs->SMODE2.FFMD)
|
||||
ds.y *= 2;
|
||||
|
||||
m_real_size = GSVector2i(fs.x, is_interlaced_resolution ? ds.y : fs.y);
|
||||
|
||||
if (!tex[0] && !tex[1])
|
||||
return false;
|
||||
|
||||
if ((tex[0] == tex[1]) && (src_out_rect[0] == src_out_rect[1]).alltrue() && (dst[0] == dst[1]).alltrue() && !feedback_merge && !slbg)
|
||||
if ((tex[0] == tex[1]) && (src_out_rect[0] == src_out_rect[1]).alltrue() &&
|
||||
(PCRTCDisplays.PCRTCDisplays[0].displayRect == PCRTCDisplays.PCRTCDisplays[1].displayRect).alltrue() &&
|
||||
(PCRTCDisplays.PCRTCDisplays[0].framebufferRect == PCRTCDisplays.PCRTCDisplays[1].framebufferRect).alltrue() &&
|
||||
!feedback_merge && !m_regs->PMODE.SLBG)
|
||||
{
|
||||
// the two outputs are identical, skip drawing one of them (the one that is alpha blended)
|
||||
|
||||
tex[0] = NULL;
|
||||
}
|
||||
|
||||
@@ -392,7 +216,11 @@ bool GSRenderer::Merge(int field)
|
||||
g_gs_device->Merge(tex, src_gs_read, dst, fs, m_regs->PMODE, m_regs->EXTBUF, c);
|
||||
|
||||
if (isReallyInterlaced() && GSConfig.InterlaceMode != GSInterlaceMode::Off)
|
||||
g_gs_device->Interlace(ds, field ^ field2, mode, offset);
|
||||
{
|
||||
const float offset = is_bob ? (tex[1] ? tex[1]->GetScale().y : tex[0]->GetScale().y) : 0.0f;
|
||||
|
||||
g_gs_device->Interlace(fs, field ^ field2, mode, offset);
|
||||
}
|
||||
|
||||
if (GSConfig.ShadeBoost)
|
||||
g_gs_device->ShadeBoost();
|
||||
@@ -602,6 +430,40 @@ void GSJoinSnapshotThreads()
|
||||
}
|
||||
}
|
||||
|
||||
bool GSRenderer::BeginPresentFrame(bool frame_skip)
|
||||
{
|
||||
const HostDisplay::PresentResult result = Host::BeginPresentFrame(frame_skip);
|
||||
if (result == HostDisplay::PresentResult::OK)
|
||||
return true;
|
||||
else if (result == HostDisplay::PresentResult::FrameSkipped)
|
||||
return false;
|
||||
|
||||
// If we're constantly crashing on something in particular, we don't want to end up in an
|
||||
// endless reset loop.. that'd probably end up leaking memory and/or crashing us for other
|
||||
// reasons. So just abort in such case.
|
||||
const Common::Timer::Value current_time = Common::Timer::GetCurrentValue();
|
||||
if (s_last_gpu_reset_time != 0 &&
|
||||
Common::Timer::ConvertValueToSeconds(current_time - s_last_gpu_reset_time) < 15.0f)
|
||||
{
|
||||
pxFailRel("Host GPU lost too many times, device is probably completely wedged.");
|
||||
}
|
||||
s_last_gpu_reset_time = current_time;
|
||||
|
||||
// Device lost, something went really bad.
|
||||
// Let's just toss out everything, and try to hobble on.
|
||||
if (!GSreopen(true, false, GSConfig))
|
||||
{
|
||||
pxFailRel("Failed to recreate GS device after loss.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// First frame after reopening is definitely going to be trash, so skip it.
|
||||
Host::AddIconOSDMessage("GSDeviceLost", ICON_FA_EXCLAMATION_TRIANGLE,
|
||||
"Host GPU device encountered an error and was recovered. This may have broken rendering.",
|
||||
Host::OSD_CRITICAL_ERROR_DURATION);
|
||||
return false;
|
||||
}
|
||||
|
||||
void GSRenderer::VSync(u32 field, bool registers_written)
|
||||
{
|
||||
Flush(GSFlushReason::VSYNC);
|
||||
@@ -647,7 +509,7 @@ void GSRenderer::VSync(u32 field, bool registers_written)
|
||||
if (skip_frame)
|
||||
{
|
||||
g_gs_device->ResetAPIState();
|
||||
if (Host::BeginPresentFrame(true))
|
||||
if (BeginPresentFrame(true))
|
||||
Host::EndPresentFrame();
|
||||
g_gs_device->RestoreAPIState();
|
||||
PerformanceMetrics::Update(registers_written, fb_sprite_frame, true);
|
||||
@@ -694,7 +556,7 @@ void GSRenderer::VSync(u32 field, bool registers_written)
|
||||
}
|
||||
|
||||
g_gs_device->ResetAPIState();
|
||||
if (Host::BeginPresentFrame(false))
|
||||
if (BeginPresentFrame(false))
|
||||
{
|
||||
if (current && !blank_frame)
|
||||
{
|
||||
@@ -910,7 +772,7 @@ void GSRenderer::StopGSDump()
|
||||
void GSRenderer::PresentCurrentFrame()
|
||||
{
|
||||
g_gs_device->ResetAPIState();
|
||||
if (Host::BeginPresentFrame(false))
|
||||
if (BeginPresentFrame(false))
|
||||
{
|
||||
GSTexture* current = g_gs_device->GetCurrent();
|
||||
if (current)
|
||||
|
||||
@@ -23,6 +23,7 @@ class GSRenderer : public GSState
|
||||
{
|
||||
private:
|
||||
bool Merge(int field);
|
||||
bool BeginPresentFrame(bool frame_skip);
|
||||
|
||||
u64 m_shader_time_start = 0;
|
||||
|
||||
|
||||
@@ -821,14 +821,14 @@ void GSDevice11::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex,
|
||||
{
|
||||
// 2nd output is enabled and selected. Copy it to destination so we can blend it with 1st output
|
||||
// Note: value outside of dRect must contains the background color (c)
|
||||
StretchRect(sTex[1], sRect[1], dTex, PMODE.SLBG ? dRect[2] : dRect[1], ShaderConvert::COPY);
|
||||
StretchRect(sTex[1], sRect[1], dTex, PMODE.SLBG ? dRect[2] : dRect[1], ShaderConvert::COPY, false);
|
||||
}
|
||||
|
||||
// Save 2nd output
|
||||
if (feedback_write_2)
|
||||
{
|
||||
StretchRect(dTex, full_r, sTex[2], dRect[2], m_convert.ps[static_cast<int>(ShaderConvert::YUV)].get(),
|
||||
m_merge.cb.get(), nullptr, true);
|
||||
m_merge.cb.get(), nullptr, false);
|
||||
}
|
||||
|
||||
// Restore background color to process the normal merge
|
||||
@@ -838,13 +838,13 @@ void GSDevice11::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex,
|
||||
if (sTex[0])
|
||||
{
|
||||
// 1st output is enabled. It must be blended
|
||||
StretchRect(sTex[0], sRect[0], dTex, dRect[0], m_merge.ps[PMODE.MMOD].get(), m_merge.cb.get(), m_merge.bs.get(), true);
|
||||
StretchRect(sTex[0], sRect[0], dTex, dRect[0], m_merge.ps[PMODE.MMOD].get(), m_merge.cb.get(), m_merge.bs.get(), false);
|
||||
}
|
||||
|
||||
if (feedback_write_1)
|
||||
{
|
||||
StretchRect(sTex[0], full_r, sTex[2], dRect[2], m_convert.ps[static_cast<int>(ShaderConvert::YUV)].get(),
|
||||
m_merge.cb.get(), nullptr, true);
|
||||
m_merge.cb.get(), nullptr, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -160,7 +160,6 @@ void GSDevice11::SetupPS(const PSSelector& sel, const GSHWDrawConfig::PSConstant
|
||||
sm.AddMacro("PS_POINT_SAMPLER", sel.point_sampler);
|
||||
sm.AddMacro("PS_SHUFFLE", sel.shuffle);
|
||||
sm.AddMacro("PS_READ_BA", sel.read_ba);
|
||||
sm.AddMacro("PS_SWAP_GA", sel.swap_ga);
|
||||
sm.AddMacro("PS_CHANNEL_FETCH", sel.channel);
|
||||
sm.AddMacro("PS_TALES_OF_ABYSS_HLE", sel.tales_of_abyss_hle);
|
||||
sm.AddMacro("PS_URBAN_CHAOS_HLE", sel.urban_chaos_hle);
|
||||
|
||||
@@ -605,7 +605,7 @@ void GSDevice12::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex,
|
||||
{
|
||||
static_cast<GSTexture12*>(sTex[1])->TransitionToState(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
|
||||
OMSetRenderTargets(dTex, nullptr, darea);
|
||||
SetUtilityTexture(sTex[1], m_linear_sampler_cpu);
|
||||
SetUtilityTexture(sTex[1], m_point_sampler_cpu);
|
||||
BeginRenderPass(D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR, D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE,
|
||||
D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_NO_ACCESS, D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_NO_ACCESS,
|
||||
D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_NO_ACCESS, D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_NO_ACCESS, c);
|
||||
@@ -625,7 +625,7 @@ void GSDevice12::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex,
|
||||
EndRenderPass();
|
||||
OMSetRenderTargets(sTex[2], nullptr, fbarea);
|
||||
if (dcleared)
|
||||
SetUtilityTexture(dTex, m_linear_sampler_cpu);
|
||||
SetUtilityTexture(dTex, m_point_sampler_cpu);
|
||||
|
||||
// sTex[2] can be sTex[0], in which case it might be cleared (e.g. Xenosaga).
|
||||
BeginRenderPassForStretchRect(static_cast<GSTexture12*>(sTex[2]), fbarea, GSVector4i(dRect[2]));
|
||||
@@ -663,7 +663,7 @@ void GSDevice12::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex,
|
||||
{
|
||||
// 1st output is enabled. It must be blended
|
||||
SetUtilityRootSignature();
|
||||
SetUtilityTexture(sTex[0], m_linear_sampler_cpu);
|
||||
SetUtilityTexture(sTex[0], m_point_sampler_cpu);
|
||||
SetPipeline(m_merge[PMODE.MMOD].get());
|
||||
DrawStretchRect(sRect[0], dRect[0], dTex->GetSize());
|
||||
}
|
||||
@@ -673,7 +673,7 @@ void GSDevice12::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex,
|
||||
EndRenderPass();
|
||||
SetUtilityRootSignature();
|
||||
SetPipeline(m_convert[static_cast<int>(ShaderConvert::YUV)].get());
|
||||
SetUtilityTexture(dTex, m_linear_sampler_cpu);
|
||||
SetUtilityTexture(dTex, m_point_sampler_cpu);
|
||||
OMSetRenderTargets(sTex[2], nullptr, fbarea);
|
||||
BeginRenderPass(D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_PRESERVE, D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE);
|
||||
DrawStretchRect(full_r, dRect[2], dsize);
|
||||
|
||||
@@ -50,43 +50,7 @@ GSRendererHW::GSRendererHW()
|
||||
|
||||
GSVector2i GSRendererHW::GetOutputSize(int real_h)
|
||||
{
|
||||
GSVector2i crtc_size(GetResolution());
|
||||
|
||||
// Correct framebuffer size to get output size when offsets not considered (uses framebuffer height)
|
||||
if (!GSConfig.PCRTCOffsets)
|
||||
{
|
||||
const int videomode = static_cast<int>(GetVideoMode()) - 1;
|
||||
const int display_width = (VideoModeDividers[videomode].z + 1) / GetDisplayHMagnification();
|
||||
const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode];
|
||||
int display_height = offsets.y;
|
||||
|
||||
if (isinterlaced() && !m_regs->SMODE2.FFMD)
|
||||
display_height *= 2;
|
||||
|
||||
if (crtc_size.x < display_width || crtc_size.y < display_height)
|
||||
{
|
||||
GSVector2i display_baseline = { 4096, 4096 };
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (IsEnabled(i))
|
||||
{
|
||||
const GSVector4i dr = GetDisplayRect(i);
|
||||
|
||||
const GSVector2i display_diff(dr.left - display_baseline.x, dr.top - display_baseline.y);
|
||||
|
||||
if (display_diff.x != 0 && abs(display_diff.x) < 4 && crtc_size.x < display_width)
|
||||
crtc_size.x -= display_diff.x;
|
||||
|
||||
if (display_diff.y != 0 && abs(display_diff.y) < 4 && crtc_size.y < display_height)
|
||||
crtc_size.y -= display_diff.y;
|
||||
|
||||
display_baseline.x = std::min(dr.left, display_baseline.x);
|
||||
display_baseline.y = std::min(dr.top, display_baseline.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
GSVector2i crtc_size(PCRTCDisplays.GetResolution());
|
||||
|
||||
// Include negative display offsets in the height here.
|
||||
crtc_size.y = std::max(crtc_size.y, real_h);
|
||||
@@ -241,40 +205,33 @@ void GSRendererHW::VSync(u32 field, bool registers_written)
|
||||
|
||||
GSTexture* GSRendererHW::GetOutput(int i, int& y_offset)
|
||||
{
|
||||
const GSRegDISPFB& DISPFB = m_regs->DISP[i].DISPFB;
|
||||
int index = i >= 0 ? i : 1;
|
||||
|
||||
GIFRegTEX0 TEX0 = {};
|
||||
GSPCRTCRegs::PCRTCDisplay& curFramebuffer = PCRTCDisplays.PCRTCDisplays[index];
|
||||
GSVector2i framebufferSize = PCRTCDisplays.GetFramebufferSize(i);
|
||||
const int fb_width = framebufferSize.x;
|
||||
const int fb_height = framebufferSize.y;
|
||||
|
||||
TEX0.TBP0 = DISPFB.Block();
|
||||
TEX0.TBW = DISPFB.FBW;
|
||||
TEX0.PSM = DISPFB.PSM;
|
||||
|
||||
const int videomode = static_cast<int>(GetVideoMode()) - 1;
|
||||
const GSVector4i offsets = VideoModeOffsets[videomode];
|
||||
|
||||
const int fb_width = std::min<int>(std::min<int>(GetFramebufferWidth(), DISPFB.FBW * 64) + static_cast<int>(DISPFB.DBX), 2048);
|
||||
const int display_height = offsets.y * ((isinterlaced() && !m_regs->SMODE2.FFMD) ? 2 : 1);
|
||||
const int display_offset = GetResolutionOffset(i).y;
|
||||
int fb_height = (std::min<int>(GetFramebufferHeight(), display_height) + static_cast<int>(DISPFB.DBY)) % 2048;
|
||||
// If there is a negative vertical offset on the picture, we need to read more.
|
||||
if (display_offset < 0)
|
||||
{
|
||||
fb_height += -display_offset;
|
||||
}
|
||||
// TRACE(_T("[%d] GetOutput %d %05x (%d)\n"), static_cast<int>(g_perfmon.GetFrame()), i, static_cast<int>(TEX0.TBP0), static_cast<int>(TEX0.PSM));
|
||||
PCRTCDisplays.RemoveFramebufferOffset(i);
|
||||
// TRACE(_T("[%d] GetOutput %d %05x (%d)\n"), (int)m_perfmon.GetFrame(), i, (int)TEX0.TBP0, (int)TEX0.PSM);
|
||||
|
||||
GSTexture* t = nullptr;
|
||||
|
||||
GIFRegTEX0 TEX0 = {};
|
||||
TEX0.TBP0 = curFramebuffer.Block();
|
||||
TEX0.TBW = curFramebuffer.FBW;
|
||||
TEX0.PSM = curFramebuffer.PSM;
|
||||
|
||||
if (GSTextureCache::Target* rt = m_tc->LookupDisplayTarget(TEX0, GetOutputSize(fb_height) * GSConfig.UpscaleMultiplier, fb_width, fb_height))
|
||||
{
|
||||
t = rt->m_texture;
|
||||
|
||||
const int delta = TEX0.TBP0 - rt->m_TEX0.TBP0;
|
||||
if (delta > 0 && DISPFB.FBW != 0)
|
||||
if (delta > 0 && curFramebuffer.FBW != 0)
|
||||
{
|
||||
const int pages = delta >> 5u;
|
||||
int y_pages = pages / DISPFB.FBW;
|
||||
y_offset = y_pages * GSLocalMemory::m_psm[DISPFB.PSM].pgs.y;
|
||||
int y_pages = pages / curFramebuffer.FBW;
|
||||
y_offset = y_pages * GSLocalMemory::m_psm[curFramebuffer.PSM].pgs.y;
|
||||
GL_CACHE("Frame y offset %d pixels, unit %d", y_offset, i);
|
||||
}
|
||||
|
||||
@@ -463,47 +420,22 @@ void GSRendererHW::ExpandIndices()
|
||||
}
|
||||
|
||||
// Fix the vertex position/tex_coordinate from 16 bits color to 32 bits color
|
||||
void GSRendererHW::ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba, bool& swap_ga)
|
||||
void GSRendererHW::ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba)
|
||||
{
|
||||
const u32 count = m_vertex.next;
|
||||
GSVertex* v = &m_vertex.buff[0];
|
||||
const GIFRegXYOFFSET& o = m_context->XYOFFSET;
|
||||
int first_vertex = (v[1].XYZ.X > v[0].XYZ.X) ? 0 : 1;
|
||||
|
||||
// vertex position is 8 to 16 pixels, therefore it is the 16-31 bits of the colors
|
||||
const int pos = (v[first_vertex].XYZ.X - o.OFX) & 0xFF;
|
||||
DevCon.Warning("Draw %d Pos %d(%d)",s_n, pos, pos >> 4);
|
||||
const int pos = (v[0].XYZ.X - o.OFX) & 0xFF;
|
||||
write_ba = (pos > 112 && pos < 136);
|
||||
|
||||
const float tw = static_cast<float>(1u << m_context->TEX0.TW);
|
||||
|
||||
// Read texture is 8 to 16 pixels (same as above)
|
||||
int tex_pos = (PRIM->FST) ? v[first_vertex].U : static_cast<int>(tw * v[first_vertex].ST.S);
|
||||
const float tw = static_cast<float>(1u << m_context->TEX0.TW);
|
||||
int tex_pos = (PRIM->FST) ? v[0].U : static_cast<int>(tw * v[0].ST.S);
|
||||
tex_pos &= 0xFF;
|
||||
read_ba = (tex_pos > 112 && tex_pos < 144);
|
||||
|
||||
int coord_width = std::abs(v[first_vertex].XYZ.X - v[1 - first_vertex].XYZ.X) >> 4;
|
||||
|
||||
int tex_width = m_vt.m_max.t.x - m_vt.m_min.t.x;
|
||||
|
||||
// Probably Green/Alpha swap
|
||||
if (tex_width > 14)
|
||||
{
|
||||
/*v[1 - first_vertex].XYZ.X = ((v[1 - first_vertex].XYZ.X - o.OFX) * 2) + o.OFX;
|
||||
v[first_vertex].XYZ.X = ((v[first_vertex].XYZ.X - o.OFX) * 2) + o.OFX;
|
||||
if (m_vt.m_max.p.y > 512.0f)
|
||||
{
|
||||
DevCon.Warning("Changing width from %d to %d", v[1 - first_vertex].XYZ.X, v[1 - first_vertex].XYZ.X + (tex_width << 4));
|
||||
v[1 - first_vertex].XYZ.X -= tex_width << 4;
|
||||
}*/
|
||||
DevCon.Warning("Width %d Max y %f", tex_width, m_vt.m_max.p.y);
|
||||
//read_ba = true;
|
||||
//write_ba = false;
|
||||
swap_ga = true;
|
||||
return;
|
||||
}
|
||||
else
|
||||
swap_ga = false;
|
||||
|
||||
bool half_bottom = false;
|
||||
switch (GSConfig.UserHacks_HalfBottomOverride)
|
||||
{
|
||||
@@ -566,16 +498,15 @@ void GSRendererHW::ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba, bo
|
||||
|
||||
for (u32 i = 0; i < count; i += 2)
|
||||
{
|
||||
first_vertex = (v[i+1].XYZ.X > v[i].XYZ.X) ? 0 : 1;
|
||||
if (write_ba)
|
||||
v[i+first_vertex].XYZ.X -= 128u;
|
||||
v[i].XYZ.X -= 128u;
|
||||
else
|
||||
v[i+(1-first_vertex)].XYZ.X += 128u;
|
||||
v[i+1].XYZ.X += 128u;
|
||||
|
||||
if (read_ba)
|
||||
v[i + first_vertex].U -= 128u;
|
||||
v[i].U -= 128u;
|
||||
else
|
||||
v[i+(1 - first_vertex)].U += 128u;
|
||||
v[i+1].U += 128u;
|
||||
|
||||
if (!half_bottom)
|
||||
{
|
||||
@@ -583,13 +514,13 @@ void GSRendererHW::ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba, bo
|
||||
const int tex_offset = v[i].V & 0xF;
|
||||
const GSVector4i offset(o.OFY, tex_offset, o.OFY, tex_offset);
|
||||
|
||||
GSVector4i tmp(v[i + first_vertex].XYZ.Y, v[i +first_vertex].V, v[i + (1 - first_vertex)].XYZ.Y, v[i + (1 - first_vertex)].V);
|
||||
GSVector4i tmp(v[i].XYZ.Y, v[i].V, v[i + 1].XYZ.Y, v[i + 1].V);
|
||||
tmp = GSVector4i(tmp - offset).srl32(1) + offset;
|
||||
|
||||
v[i + first_vertex].XYZ.Y = static_cast<u16>(tmp.x);
|
||||
v[i + first_vertex].V = static_cast<u16>(tmp.y);
|
||||
v[i + (1 - first_vertex)].XYZ.Y = static_cast<u16>(tmp.z);
|
||||
v[i + (1 - first_vertex)].V = static_cast<u16>(tmp.w);
|
||||
v[i].XYZ.Y = static_cast<u16>(tmp.x);
|
||||
v[i].V = static_cast<u16>(tmp.y);
|
||||
v[i + 1].XYZ.Y = static_cast<u16>(tmp.z);
|
||||
v[i + 1].V = static_cast<u16>(tmp.w);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -600,24 +531,22 @@ void GSRendererHW::ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba, bo
|
||||
|
||||
for (u32 i = 0; i < count; i += 2)
|
||||
{
|
||||
first_vertex = (v[i + 1].XYZ.X > v[i].XYZ.X) ? 0 : 1;
|
||||
|
||||
if (write_ba)
|
||||
v[i+ first_vertex].XYZ.X -= 128u;
|
||||
v[i].XYZ.X -= 128u;
|
||||
else
|
||||
v[i+(1- first_vertex)].XYZ.X += 128u;
|
||||
v[i+1].XYZ.X += 128u;
|
||||
|
||||
if (read_ba)
|
||||
v[i + first_vertex].ST.S -= offset_8pix;
|
||||
v[i].ST.S -= offset_8pix;
|
||||
else
|
||||
v[i + (1- first_vertex)].ST.S += offset_8pix;
|
||||
v[i+1].ST.S += offset_8pix;
|
||||
|
||||
if (!half_bottom)
|
||||
{
|
||||
// Height is too big (2x).
|
||||
const GSVector4i offset(o.OFY, o.OFY);
|
||||
|
||||
GSVector4i tmp(v[i+ first_vertex].XYZ.Y, v[i + (1- first_vertex)].XYZ.Y);
|
||||
GSVector4i tmp(v[i].XYZ.Y, v[i + 1].XYZ.Y);
|
||||
tmp = GSVector4i(tmp - offset).srl32(1) + offset;
|
||||
|
||||
//fprintf(stderr, "Before %d, After %d\n", v[i + 1].XYZ.Y, tmp.y);
|
||||
@@ -825,8 +754,8 @@ GSVector2i GSRendererHW::GetTargetSize(GSVector2i* unscaled_size)
|
||||
const int page_y = GSLocalMemory::m_psm[m_context->FRAME.PSM].pgs.y - 1;
|
||||
|
||||
// Round up the page as channel shuffles are generally done in pages at a time
|
||||
width = (std::max(static_cast<u32>(GetResolution().x), width) + page_x) & ~page_x;
|
||||
min_height = (std::max(static_cast<u32>(GetResolution().y), min_height) + page_y) & ~page_y;
|
||||
width = (std::max(static_cast<u32>(PCRTCDisplays.GetResolution().x), width) + page_x) & ~page_x;
|
||||
min_height = (std::max(static_cast<u32>(PCRTCDisplays.GetResolution().y), min_height) + page_y) & ~page_y;
|
||||
}
|
||||
|
||||
// Align to even lines, reduces the chance of tiny resizes.
|
||||
@@ -1471,13 +1400,14 @@ void GSRendererHW::Draw()
|
||||
const bool clear_height_valid = (m_r.w >= 1024);
|
||||
if (clear_height_valid && context->FRAME.FBW == 1)
|
||||
{
|
||||
u32 width = ceil(static_cast<float>(m_r.w) / GetFramebufferHeight()) * 64;
|
||||
GSVector2i fb_size = PCRTCDisplays.GetFramebufferSize(-1);
|
||||
u32 width = ceil(static_cast<float>(m_r.w) / fb_size.y) * 64;
|
||||
// Framebuffer is likely to be read as 16bit later, so we will need to double the width if the write is 32bit.
|
||||
const bool double_width = GSLocalMemory::m_psm[context->FRAME.PSM].bpp == 32 && GetFramebufferBitDepth() == 16;
|
||||
const bool double_width = GSLocalMemory::m_psm[context->FRAME.PSM].bpp == 32 && PCRTCDisplays.GetFramebufferBitDepth() == 16;
|
||||
m_r.x = 0;
|
||||
m_r.y = 0;
|
||||
m_r.w = GetFramebufferHeight();
|
||||
m_r.z = std::max((width * (double_width ? 2 : 1)), static_cast<u32>(GetFramebufferWidth()));
|
||||
m_r.w = fb_size.y;
|
||||
m_r.z = std::max((width * (double_width ? 2 : 1)), static_cast<u32>(fb_size.x));
|
||||
context->FRAME.FBW = (m_r.z + 63) / 64;
|
||||
m_context->scissor.in.z = context->FRAME.FBW * 64;
|
||||
|
||||
@@ -1514,8 +1444,9 @@ void GSRendererHW::Draw()
|
||||
}
|
||||
}
|
||||
TextureMinMaxResult tmm;
|
||||
const bool process_texture = PRIM->TME && !(PRIM->ABE && m_context->ALPHA.IsBlack() && !m_context->TEX0.TCC);
|
||||
// Disable texture mapping if the blend is black and using alpha from vertex.
|
||||
if (PRIM->TME && !(PRIM->ABE && m_context->ALPHA.IsBlack() && !m_context->TEX0.TCC))
|
||||
if (process_texture)
|
||||
{
|
||||
GIFRegCLAMP MIP_CLAMP = context->CLAMP;
|
||||
GSVector2i hash_lod_range(0, 0);
|
||||
@@ -1635,7 +1566,7 @@ void GSRendererHW::Draw()
|
||||
if (!no_ds)
|
||||
ds = m_tc->LookupTarget(TEX0, t_size, GSTextureCache::DepthStencil, context->DepthWrite(), 0, false, 0, 0, preload);
|
||||
|
||||
if (PRIM->TME)
|
||||
if (process_texture)
|
||||
{
|
||||
GIFRegCLAMP MIP_CLAMP = context->CLAMP;
|
||||
|
||||
@@ -1654,9 +1585,6 @@ void GSRendererHW::Draw()
|
||||
m_texture_shuffle = (GSLocalMemory::m_psm[context->FRAME.PSM].bpp == 16) && (tex_psm.bpp == 16)
|
||||
&& draw_sprite_tex && (m_src->m_32_bits_fmt || copy_16bit_to_target_shuffle);
|
||||
|
||||
|
||||
if (copy_16bit_to_target_shuffle && m_texture_shuffle)
|
||||
DevCon.Warning("here");
|
||||
// Okami mustn't call this code
|
||||
if (m_texture_shuffle && m_vertex.next < 3 && PRIM->FST && ((m_context->FRAME.FBMSK & fm_mask) == 0))
|
||||
{
|
||||
@@ -2261,9 +2189,8 @@ void GSRendererHW::EmulateTextureShuffleAndFbmask()
|
||||
|
||||
bool write_ba;
|
||||
bool read_ba;
|
||||
bool swap_ga;
|
||||
|
||||
ConvertSpriteTextureShuffle(write_ba, read_ba, swap_ga);
|
||||
ConvertSpriteTextureShuffle(write_ba, read_ba);
|
||||
|
||||
// If date is enabled you need to test the green channel instead of the
|
||||
// alpha channel. Only enable this code in DATE mode to reduce the number
|
||||
@@ -2283,52 +2210,37 @@ void GSRendererHW::EmulateTextureShuffleAndFbmask()
|
||||
const GSVector2i rb_ga_mask = GSVector2i(fbmask & 0xFF, (fbmask >> 8) & 0xFF);
|
||||
m_conf.colormask.wrgba = 0;
|
||||
|
||||
if (swap_ga)
|
||||
// 2 Select the new mask
|
||||
if (rb_ga_mask.r != 0xFF)
|
||||
{
|
||||
m_conf.ps.swap_ga = true;
|
||||
m_conf.ps.fbmask = 0;
|
||||
GL_CACHE("I hate myself");
|
||||
m_conf.colormask.wg = 1;
|
||||
m_conf.colormask.wa = 1;
|
||||
m_skip = 1;
|
||||
DevCon.Warning("Kill me");
|
||||
m_conf.ps.tex_is_fb = true;
|
||||
return;
|
||||
if (write_ba)
|
||||
{
|
||||
GL_INS("Color shuffle %s => B", read_ba ? "B" : "R");
|
||||
m_conf.colormask.wb = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
GL_INS("Color shuffle %s => R", read_ba ? "B" : "R");
|
||||
m_conf.colormask.wr = 1;
|
||||
}
|
||||
if (rb_ga_mask.r)
|
||||
m_conf.ps.fbmask = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 2 Select the new mask
|
||||
if (rb_ga_mask.r != 0xFF)
|
||||
{
|
||||
if (write_ba)
|
||||
{
|
||||
DevCon.Warning("Color shuffle %s => B", read_ba ? "B" : "R");
|
||||
m_conf.colormask.wb = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
DevCon.Warning("Color shuffle %s => R", read_ba ? "B" : "R");
|
||||
m_conf.colormask.wr = 1;
|
||||
}
|
||||
if (rb_ga_mask.r)
|
||||
m_conf.ps.fbmask = 1;
|
||||
}
|
||||
|
||||
if (rb_ga_mask.g != 0xFF)
|
||||
if (rb_ga_mask.g != 0xFF)
|
||||
{
|
||||
if (write_ba)
|
||||
{
|
||||
if (write_ba)
|
||||
{
|
||||
DevCon.Warning("Color shuffle %s => A", read_ba ? "A" : "G");
|
||||
m_conf.colormask.wa = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
DevCon.Warning("Color shuffle %s => G", read_ba ? "A" : "G");
|
||||
m_conf.colormask.wg = 1;
|
||||
}
|
||||
if (rb_ga_mask.g)
|
||||
m_conf.ps.fbmask = 1;
|
||||
GL_INS("Color shuffle %s => A", read_ba ? "A" : "G");
|
||||
m_conf.colormask.wa = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
GL_INS("Color shuffle %s => G", read_ba ? "A" : "G");
|
||||
m_conf.colormask.wg = 1;
|
||||
}
|
||||
if (rb_ga_mask.g)
|
||||
m_conf.ps.fbmask = 1;
|
||||
}
|
||||
|
||||
if (m_conf.ps.fbmask && enable_fbmask_emulation)
|
||||
@@ -2341,12 +2253,12 @@ void GSRendererHW::EmulateTextureShuffleAndFbmask()
|
||||
// No blending so hit unsafe path.
|
||||
if (!PRIM->ABE || !features.texture_barrier)
|
||||
{
|
||||
DevCon.Warning("FBMASK Unsafe SW emulated fb_mask:%x on tex shuffle", fbmask);
|
||||
GL_INS("FBMASK Unsafe SW emulated fb_mask:%x on tex shuffle", fbmask);
|
||||
m_conf.require_one_barrier = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
DevCon.Warning("FBMASK SW emulated fb_mask:%x on tex shuffle", fbmask);
|
||||
GL_INS("FBMASK SW emulated fb_mask:%x on tex shuffle", fbmask);
|
||||
m_conf.require_full_barrier = true;
|
||||
}
|
||||
}
|
||||
@@ -2398,14 +2310,14 @@ void GSRendererHW::EmulateTextureShuffleAndFbmask()
|
||||
// No blending so hit unsafe path.
|
||||
if (!PRIM->ABE || !(~ff_fbmask & ~zero_fbmask & 0x7) || !g_gs_device->Features().texture_barrier)
|
||||
{
|
||||
DevCon.Warning("FBMASK Unsafe SW emulated fb_mask:%x on %d bits format", m_context->FRAME.FBMSK,
|
||||
GL_INS("FBMASK Unsafe SW emulated fb_mask:%x on %d bits format", m_context->FRAME.FBMSK,
|
||||
(m_conf.ps.dfmt == 2) ? 16 : 32);
|
||||
m_conf.require_one_barrier = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The safe and accurate path (but slow)
|
||||
DevCon.Warning("FBMASK SW emulated fb_mask:%x on %d bits format", m_context->FRAME.FBMSK,
|
||||
GL_INS("FBMASK SW emulated fb_mask:%x on %d bits format", m_context->FRAME.FBMSK,
|
||||
(m_conf.ps.dfmt == 2) ? 16 : 32);
|
||||
m_conf.require_full_barrier = true;
|
||||
}
|
||||
@@ -4072,8 +3984,8 @@ GSRendererHW::CLUTDrawTestResult GSRendererHW::PossibleCLUTDraw()
|
||||
(PRIM->TME && ((m_regs->DISP[0].DISPFB.Block() == m_context->TEX0.TBP0) || (m_regs->DISP[1].DISPFB.Block() == m_context->TEX0.TBP0)) && !(m_mem.m_clut.IsInvalid() & 2)))
|
||||
return CLUTDrawTestResult::NotCLUTDraw;
|
||||
|
||||
// Ignore recursive/shuffle effects, but possible it will recursively draw, but make sure it's staying in page width
|
||||
if (PRIM->TME && m_context->TEX0.TBP0 == m_context->FRAME.Block() && (m_context->FRAME.FBW != 1 && m_context->TEX0.TBW == m_context->FRAME.FBW))
|
||||
// Ignore large render targets, make sure it's staying in page width.
|
||||
if (PRIM->TME && (m_context->FRAME.FBW != 1 && m_context->TEX0.TBW == m_context->FRAME.FBW))
|
||||
return CLUTDrawTestResult::NotCLUTDraw;
|
||||
|
||||
// Hopefully no games draw a CLUT with a CLUT, that would be evil, most likely a channel shuffle.
|
||||
|
||||
@@ -140,7 +140,7 @@ public:
|
||||
void Lines2Sprites();
|
||||
bool VerifyIndices();
|
||||
template <GSHWDrawConfig::VSExpand Expand> void ExpandIndices();
|
||||
void ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba, bool& swap_ga);
|
||||
void ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba);
|
||||
GSVector4 RealignTargetTextureCoordinate(const GSTextureCache::Source* tex);
|
||||
GSVector4i ComputeBoundingBox(const GSVector2& rtscale, const GSVector2i& rtsize);
|
||||
void MergeSprite(GSTextureCache::Source* tex);
|
||||
|
||||
@@ -1579,6 +1579,24 @@ bool GSTextureCache::Move(u32 SBP, u32 SBW, u32 SPSM, int sx, int sy, u32 DBP, u
|
||||
// Look for an exact match on the targets.
|
||||
GSTextureCache::Target* src = GetExactTarget(SBP, SBW, SPSM);
|
||||
GSTextureCache::Target* dst = GetExactTarget(DBP, DBW, DPSM);
|
||||
|
||||
// Beware of the case where a game might create a larger texture by moving a bunch of chunks around.
|
||||
// We use dx/dy == 0 and the TBW check as a safeguard to make sure these go through to local memory.
|
||||
// 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)
|
||||
{
|
||||
GIFRegTEX0 new_TEX0 = {};
|
||||
new_TEX0.TBP0 = DBP;
|
||||
new_TEX0.TBW = DBW;
|
||||
new_TEX0.PSM = DPSM;
|
||||
|
||||
const int real_height = GetTargetHeight(DBP, DBW, DPSM, h);
|
||||
const GSVector2 scale(src->m_texture->GetScale());
|
||||
dst = LookupTarget(new_TEX0, GSVector2i(static_cast<int>(w * scale.x), static_cast<int>(real_height * scale.y)), src->m_type, true);
|
||||
if (dst)
|
||||
dst->m_texture->SetScale(scale);
|
||||
}
|
||||
|
||||
if (!src || !dst || src->m_texture->GetScale() != dst->m_texture->GetScale())
|
||||
return false;
|
||||
|
||||
@@ -3070,21 +3088,6 @@ void GSTextureCache::Target::Update(bool reset_age)
|
||||
m_dirty.ClearDirty();
|
||||
return;
|
||||
}
|
||||
else if (m_type == DepthStencil && g_gs_renderer->m_game.title == CRC::FFX2)
|
||||
{
|
||||
GL_INS("ERROR: bad invalidation detected, depth buffer will be cleared");
|
||||
// FFX2 menu. Invalidation of the depth is wrongly done and only the first
|
||||
// page is invalidated. Technically a CRC hack will be better but I don't expect
|
||||
// any games to only upload a single page of data for the depth.
|
||||
//
|
||||
// FFX2 menu got another bug. I'm not sure the top-left is properly written or not. It
|
||||
// could be a gs transfer bug too due to unaligned-page transfer.
|
||||
//
|
||||
// So the quick and dirty solution is just to clean the depth buffer.
|
||||
g_gs_device->ClearDepth(m_texture);
|
||||
m_dirty.ClearDirty();
|
||||
return;
|
||||
}
|
||||
|
||||
GIFRegTEXA TEXA = {};
|
||||
|
||||
@@ -3116,7 +3119,7 @@ void GSTextureCache::Target::Update(bool reset_age)
|
||||
}
|
||||
else
|
||||
{
|
||||
int pitch = ((r.width()+3) & ~3) * 4;
|
||||
const int pitch = Common::AlignUpPow2(r.width() * sizeof(u32), 32);
|
||||
g_gs_renderer->m_mem.ReadTexture(off, r, s_unswizzle_buffer, pitch, TEXA);
|
||||
|
||||
t->Update(r, s_unswizzle_buffer, pitch);
|
||||
|
||||
@@ -563,12 +563,12 @@ void GSDeviceMTL::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex,
|
||||
{
|
||||
// 2nd output is enabled and selected. Copy it to destination so we can blend it with 1st output
|
||||
// Note: value outside of dRect must contains the background color (c)
|
||||
StretchRect(sTex[1], sRect[1], dTex, dRect[1], ShaderConvert::COPY);
|
||||
StretchRect(sTex[1], sRect[1], dTex, dRect[1], ShaderConvert::COPY, false);
|
||||
}
|
||||
|
||||
// Save 2nd output
|
||||
if (feedback_write_2) // FIXME I'm not sure dRect[1] is always correct
|
||||
DoStretchRect(dTex, full_r, sTex[2], dRect[1], m_convert_pipeline[static_cast<int>(ShaderConvert::YUV)], true, LoadAction::DontCareIfFull, &cb_yuv, sizeof(cb_yuv));
|
||||
DoStretchRect(dTex, full_r, sTex[2], dRect[1], m_convert_pipeline[static_cast<int>(ShaderConvert::YUV)], false, LoadAction::DontCareIfFull, &cb_yuv, sizeof(cb_yuv));
|
||||
|
||||
if (feedback_write_2_but_blend_bg)
|
||||
ClearRenderTarget(dTex, c);
|
||||
@@ -582,17 +582,17 @@ void GSDeviceMTL::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex,
|
||||
if (PMODE.MMOD == 1)
|
||||
{
|
||||
// Blend with a constant alpha
|
||||
DoStretchRect(sTex[0], sRect[0], dTex, dRect[0], pipeline, true, LoadAction::Load, &cb_c, sizeof(cb_c));
|
||||
DoStretchRect(sTex[0], sRect[0], dTex, dRect[0], pipeline, false, LoadAction::Load, &cb_c, sizeof(cb_c));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Blend with 2 * input alpha
|
||||
DoStretchRect(sTex[0], sRect[0], dTex, dRect[0], pipeline, true, LoadAction::Load, nullptr, 0);
|
||||
DoStretchRect(sTex[0], sRect[0], dTex, dRect[0], pipeline, false, LoadAction::Load, nullptr, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (feedback_write_1) // FIXME I'm not sure dRect[0] is always correct
|
||||
StretchRect(dTex, full_r, sTex[2], dRect[0], ShaderConvert::YUV);
|
||||
StretchRect(dTex, full_r, sTex[2], dRect[0], ShaderConvert::YUV, false);
|
||||
}}
|
||||
|
||||
void GSDeviceMTL::DoInterlace(GSTexture* sTex, GSTexture* dTex, int shader, bool linear, float yoffset, int bufIdx)
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "GLLoader.h"
|
||||
#include "GS/GS.h"
|
||||
#include "Host.h"
|
||||
#include "HostSettings.h"
|
||||
|
||||
namespace ReplaceGL
|
||||
{
|
||||
@@ -112,8 +113,8 @@ namespace GLLoader
|
||||
bool vendor_id_amd = false;
|
||||
bool vendor_id_nvidia = false;
|
||||
bool vendor_id_intel = false;
|
||||
bool mesa_driver = false;
|
||||
bool buggy_pbo = false;
|
||||
bool disable_download_pbo = false;
|
||||
|
||||
bool is_gles = false;
|
||||
bool has_dual_source_blend = false;
|
||||
@@ -131,14 +132,8 @@ namespace GLLoader
|
||||
vendor_id_amd = true;
|
||||
else if (strstr(vendor, "NVIDIA Corporation"))
|
||||
vendor_id_nvidia = true;
|
||||
|
||||
#ifdef _WIN32
|
||||
else if (strstr(vendor, "Intel"))
|
||||
vendor_id_intel = true;
|
||||
#else
|
||||
// On linux assumes the free driver if it isn't nvidia or amd pro driver
|
||||
mesa_driver = !vendor_id_nvidia && !vendor_id_amd;
|
||||
#endif
|
||||
|
||||
if (GSConfig.OverrideGeometryShaders != -1)
|
||||
{
|
||||
@@ -225,10 +220,16 @@ namespace GLLoader
|
||||
|
||||
// Don't use PBOs when we don't have ARB_buffer_storage, orphaning buffers probably ends up worse than just
|
||||
// using the normal texture update routines and letting the driver take care of it.
|
||||
GLLoader::buggy_pbo = !GLAD_GL_VERSION_4_4 && !GLAD_GL_ARB_buffer_storage && !GLAD_GL_EXT_buffer_storage;
|
||||
if (GLLoader::buggy_pbo)
|
||||
buggy_pbo = !GLAD_GL_VERSION_4_4 && !GLAD_GL_ARB_buffer_storage && !GLAD_GL_EXT_buffer_storage;
|
||||
if (buggy_pbo)
|
||||
Console.Warning("Not using PBOs for texture uploads because buffer_storage is unavailable.");
|
||||
|
||||
// Give the user the option to disable PBO usage for downloads.
|
||||
// Most drivers seem to be faster with PBO.
|
||||
disable_download_pbo = Host::GetBoolSettingValue("EmuCore/GS", "DisableGLDownloadPBO", false);
|
||||
if (disable_download_pbo)
|
||||
Console.Warning("Not using PBOs for texture downloads, this may reduce performance.");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,9 +29,8 @@ namespace GLLoader
|
||||
extern bool vendor_id_amd;
|
||||
extern bool vendor_id_nvidia;
|
||||
extern bool vendor_id_intel;
|
||||
extern bool mesa_driver;
|
||||
extern bool buggy_pbo;
|
||||
extern bool in_replayer;
|
||||
extern bool disable_download_pbo;
|
||||
|
||||
// GL
|
||||
extern bool is_gles;
|
||||
|
||||
@@ -1327,7 +1327,7 @@ void GSDeviceOGL::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex,
|
||||
{
|
||||
// 2nd output is enabled and selected. Copy it to destination so we can blend it with 1st output
|
||||
// Note: value outside of dRect must contains the background color (c)
|
||||
StretchRect(sTex[1], sRect[1], dTex, PMODE.SLBG ? dRect[2] : dRect[1], ShaderConvert::COPY);
|
||||
StretchRect(sTex[1], sRect[1], dTex, PMODE.SLBG ? dRect[2] : dRect[1], ShaderConvert::COPY, false);
|
||||
}
|
||||
|
||||
// Upload constant to select YUV algo
|
||||
@@ -1340,7 +1340,7 @@ void GSDeviceOGL::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex,
|
||||
|
||||
// Save 2nd output
|
||||
if (feedback_write_2)
|
||||
StretchRect(dTex, full_r, sTex[2], dRect[2], ShaderConvert::YUV);
|
||||
StretchRect(dTex, full_r, sTex[2], dRect[2], ShaderConvert::YUV, false);
|
||||
|
||||
// Restore background color to process the normal merge
|
||||
if (feedback_write_2_but_blend_bg)
|
||||
@@ -1357,17 +1357,17 @@ void GSDeviceOGL::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex,
|
||||
// Blend with a constant alpha
|
||||
m_merge_obj.ps[1].Bind();
|
||||
m_merge_obj.ps[1].Uniform4fv(0, c.v);
|
||||
StretchRect(sTex[0], sRect[0], dTex, dRect[0], m_merge_obj.ps[1], true, OMColorMaskSelector());
|
||||
StretchRect(sTex[0], sRect[0], dTex, dRect[0], m_merge_obj.ps[1], true, OMColorMaskSelector(), false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Blend with 2 * input alpha
|
||||
StretchRect(sTex[0], sRect[0], dTex, dRect[0], m_merge_obj.ps[0], true, OMColorMaskSelector());
|
||||
StretchRect(sTex[0], sRect[0], dTex, dRect[0], m_merge_obj.ps[0], true, OMColorMaskSelector(), false);
|
||||
}
|
||||
}
|
||||
|
||||
if (feedback_write_1)
|
||||
StretchRect(dTex, full_r, sTex[2], dRect[2], ShaderConvert::YUV);
|
||||
StretchRect(dTex, full_r, sTex[2], dRect[2], ShaderConvert::YUV, false);
|
||||
}
|
||||
|
||||
void GSDeviceOGL::DoInterlace(GSTexture* sTex, GSTexture* dTex, int shader, bool linear, float yoffset, int bufIdx)
|
||||
|
||||
@@ -453,9 +453,10 @@ GSDownloadTextureOGL::~GSDownloadTextureOGL()
|
||||
|
||||
std::unique_ptr<GSDownloadTextureOGL> GSDownloadTextureOGL::Create(u32 width, u32 height, GSTexture::Format format)
|
||||
{
|
||||
const u32 buffer_size = GetBufferSize(width, height, format, GSTexture::GetCompressedBytesPerBlock(format));
|
||||
const u32 buffer_size = GetBufferSize(width, height, format, TEXTURE_UPLOAD_PITCH_ALIGNMENT);
|
||||
|
||||
const bool use_buffer_storage = (GLAD_GL_VERSION_4_4 || GLAD_GL_ARB_buffer_storage || GLAD_GL_EXT_buffer_storage);
|
||||
const bool use_buffer_storage = (GLAD_GL_VERSION_4_4 || GLAD_GL_ARB_buffer_storage || GLAD_GL_EXT_buffer_storage) &&
|
||||
!GLLoader::disable_download_pbo;
|
||||
if (use_buffer_storage)
|
||||
{
|
||||
GLuint buffer_id;
|
||||
|
||||
@@ -82,22 +82,6 @@ void GSRendererSW::VSync(u32 field, bool registers_written)
|
||||
{
|
||||
Sync(0); // IncAge might delete a cached texture in use
|
||||
|
||||
if (0) if (LOG)
|
||||
{
|
||||
fprintf(s_fp, "%llu\n", g_perfmon.GetFrame());
|
||||
|
||||
GSVector4i dr = GetDisplayRect();
|
||||
GSVector4i fr = GetFrameRect();
|
||||
|
||||
fprintf(s_fp, "dr %d %d %d %d, fr %d %d %d %d\n",
|
||||
dr.x, dr.y, dr.z, dr.w,
|
||||
fr.x, fr.y, fr.z, fr.w);
|
||||
|
||||
m_regs->Dump(s_fp);
|
||||
|
||||
fflush(s_fp);
|
||||
}
|
||||
|
||||
/*
|
||||
int draw[8], sum = 0;
|
||||
|
||||
@@ -127,27 +111,20 @@ GSTexture* GSRendererSW::GetOutput(int i, int& y_offset)
|
||||
{
|
||||
Sync(1);
|
||||
|
||||
const GSRegDISPFB& DISPFB = m_regs->DISP[i].DISPFB;
|
||||
int index = i >= 0 ? i : 1;
|
||||
GSPCRTCRegs::PCRTCDisplay& curFramebuffer = PCRTCDisplays.PCRTCDisplays[index];
|
||||
GSVector2i framebufferSize = PCRTCDisplays.GetFramebufferSize(i);
|
||||
GSVector4i framebufferRect = PCRTCDisplays.GetFramebufferRect(i);
|
||||
int w = curFramebuffer.FBW * 64;
|
||||
int h = framebufferSize.y;
|
||||
|
||||
int w = DISPFB.FBW * 64;
|
||||
|
||||
const int videomode = static_cast<int>(GetVideoMode()) - 1;
|
||||
const int display_offset = GetResolutionOffset(i).y;
|
||||
const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode];
|
||||
const int display_height = offsets.y * ((isinterlaced() && !m_regs->SMODE2.FFMD) ? 2 : 1);
|
||||
int h = std::min(GetFramebufferHeight(), display_height);
|
||||
|
||||
// If there is a negative vertical offset on the picture, we need to read more.
|
||||
if (display_offset < 0)
|
||||
{
|
||||
h += -display_offset;
|
||||
}
|
||||
|
||||
if (g_gs_device->ResizeTarget(&m_texture[i], w, h))
|
||||
if (g_gs_device->ResizeTarget(&m_texture[index], w, h))
|
||||
{
|
||||
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[curFramebuffer.PSM];
|
||||
constexpr int pitch = 1024 * 4;
|
||||
const int off_x = DISPFB.DBX & 0x7ff;
|
||||
const int off_y = DISPFB.DBY & 0x7ff;
|
||||
// Should really be framebufferOffsets rather than framebufferRect but this might be compensated with anti-blur in some games.
|
||||
const int off_x = (framebufferRect.x & 0x7ff) & ~(psm.bs.x-1);
|
||||
const int off_y = (framebufferRect.y & 0x7ff) & ~(psm.bs.y-1);
|
||||
const GSVector4i out_r(0, 0, w, h);
|
||||
GSVector4i r(off_x, off_y, w + off_x, h + off_y);
|
||||
GSVector4i rh(off_x, off_y, w + off_x, (h + off_y) & 0x7FF);
|
||||
@@ -155,6 +132,7 @@ GSTexture* GSRendererSW::GetOutput(int i, int& y_offset)
|
||||
bool h_wrap = false;
|
||||
bool w_wrap = false;
|
||||
|
||||
PCRTCDisplays.RemoveFramebufferOffset(i);
|
||||
// Need to read it in 2 parts, since you can't do a split rect.
|
||||
if (r.bottom >= 2048)
|
||||
{
|
||||
@@ -172,50 +150,51 @@ GSTexture* GSRendererSW::GetOutput(int i, int& y_offset)
|
||||
w_wrap = true;
|
||||
}
|
||||
|
||||
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[DISPFB.PSM];
|
||||
|
||||
// Display doesn't use texa, and instead uses the equivalent of this
|
||||
GIFRegTEXA texa = {};
|
||||
texa.AEM = 0;
|
||||
texa.TA0 = (DISPFB.PSM == PSM_PSMCT24 || DISPFB.PSM == PSM_PSGPU24) ? 0x80 : 0;
|
||||
texa.TA0 = (curFramebuffer.PSM == PSM_PSMCT24 || curFramebuffer.PSM == PSM_PSGPU24) ? 0x80 : 0;
|
||||
texa.TA1 = 0x80;
|
||||
|
||||
// Top left rect
|
||||
psm.rtx(m_mem, m_mem.GetOffset(DISPFB.Block(), DISPFB.FBW, DISPFB.PSM), r.ralign<Align_Outside>(psm.bs), m_output, pitch, texa);
|
||||
psm.rtx(m_mem, m_mem.GetOffset(curFramebuffer.Block(), curFramebuffer.FBW, curFramebuffer.PSM), r.ralign<Align_Outside>(psm.bs), m_output, pitch, texa);
|
||||
|
||||
// Top left rect
|
||||
psm.rtx(m_mem, m_mem.GetOffset(curFramebuffer.Block(), curFramebuffer.FBW, curFramebuffer.PSM), r.ralign<Align_Outside>(psm.bs), m_output, pitch, texa);
|
||||
|
||||
int top = (h_wrap) ? ((r.bottom - r.top) * pitch) : 0;
|
||||
int left = (w_wrap) ? (r.right - r.left) * (GSLocalMemory::m_psm[DISPFB.PSM].bpp / 8) : 0;
|
||||
int left = (w_wrap) ? (r.right - r.left) * (GSLocalMemory::m_psm[curFramebuffer.PSM].bpp / 8) : 0;
|
||||
|
||||
// The following only happen if the DBX/DBY wrap around at 2048.
|
||||
|
||||
// Top right rect
|
||||
if (w_wrap)
|
||||
psm.rtx(m_mem, m_mem.GetOffset(DISPFB.Block(), DISPFB.FBW, DISPFB.PSM), rw.ralign<Align_Outside>(psm.bs), &m_output[left], pitch, texa);
|
||||
psm.rtx(m_mem, m_mem.GetOffset(curFramebuffer.Block(), curFramebuffer.FBW, curFramebuffer.PSM), rw.ralign<Align_Outside>(psm.bs), &m_output[left], pitch, texa);
|
||||
|
||||
// Bottom left rect
|
||||
if (h_wrap)
|
||||
psm.rtx(m_mem, m_mem.GetOffset(DISPFB.Block(), DISPFB.FBW, DISPFB.PSM), rh.ralign<Align_Outside>(psm.bs), &m_output[top], pitch, texa);
|
||||
psm.rtx(m_mem, m_mem.GetOffset(curFramebuffer.Block(), curFramebuffer.FBW, curFramebuffer.PSM), rh.ralign<Align_Outside>(psm.bs), &m_output[top], pitch, texa);
|
||||
|
||||
// Bottom right rect
|
||||
if (h_wrap && w_wrap)
|
||||
{
|
||||
// Needs also rw with the start/end height of rh, fills in the bottom right rect which will be missing if both overflow.
|
||||
const GSVector4i rwh(rw.left, rh.top, rw.right, rh.bottom);
|
||||
psm.rtx(m_mem, m_mem.GetOffset(DISPFB.Block(), DISPFB.FBW, DISPFB.PSM), rwh.ralign<Align_Outside>(psm.bs), &m_output[top + left], pitch, texa);
|
||||
psm.rtx(m_mem, m_mem.GetOffset(curFramebuffer.Block(), curFramebuffer.FBW, curFramebuffer.PSM), rwh.ralign<Align_Outside>(psm.bs), &m_output[top + left], pitch, texa);
|
||||
}
|
||||
|
||||
m_texture[i]->Update(out_r, m_output, pitch);
|
||||
m_texture[index]->Update(out_r, m_output, pitch);
|
||||
|
||||
if (GSConfig.DumpGSData)
|
||||
{
|
||||
if (GSConfig.SaveFrame && s_n >= GSConfig.SaveN)
|
||||
{
|
||||
m_texture[i]->Save(GetDrawDumpPath("%05d_f%lld_fr%d_%05x_%s.bmp", s_n, g_perfmon.GetFrame(), i, (int)DISPFB.Block(), psm_str(DISPFB.PSM)));
|
||||
m_texture[i]->Save(GetDrawDumpPath("%05d_f%lld_fr%d_%05x_%s.bmp", s_n, g_perfmon.GetFrame(), i, (int)curFramebuffer.Block(), psm_str(curFramebuffer.PSM)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return m_texture[i];
|
||||
return m_texture[index];
|
||||
}
|
||||
|
||||
GSTexture* GSRendererSW::GetFeedbackOutput()
|
||||
@@ -490,18 +469,18 @@ void GSRendererSW::Draw()
|
||||
{
|
||||
// Dump the RT in 32 bits format. It helps to debug texture shuffle effect
|
||||
s = GetDrawDumpPath("%05d_f%lld_rt0_%05x_32bits.bmp", s_n, frame, m_context->FRAME.Block());
|
||||
m_mem.SaveBMP(s, m_context->FRAME.Block(), m_context->FRAME.FBW, 0, GetFrameRect().width(), 512);
|
||||
m_mem.SaveBMP(s, m_context->FRAME.Block(), m_context->FRAME.FBW, 0, PCRTCDisplays.GetFramebufferRect(-1).width(), 512);
|
||||
}
|
||||
|
||||
s = GetDrawDumpPath("%05d_f%lld_rt0_%05x_%s.bmp", s_n, frame, m_context->FRAME.Block(), psm_str(m_context->FRAME.PSM));
|
||||
m_mem.SaveBMP(s, m_context->FRAME.Block(), m_context->FRAME.FBW, m_context->FRAME.PSM, GetFrameRect().width(), 512);
|
||||
m_mem.SaveBMP(s, m_context->FRAME.Block(), m_context->FRAME.FBW, m_context->FRAME.PSM, PCRTCDisplays.GetFramebufferRect(-1).width(), 512);
|
||||
}
|
||||
|
||||
if (GSConfig.SaveDepth && s_n >= GSConfig.SaveN)
|
||||
{
|
||||
s = GetDrawDumpPath("%05d_f%lld_rz0_%05x_%s.bmp", s_n, frame, m_context->ZBUF.Block(), psm_str(m_context->ZBUF.PSM));
|
||||
|
||||
m_mem.SaveBMP(s, m_context->ZBUF.Block(), m_context->FRAME.FBW, m_context->ZBUF.PSM, GetFrameRect().width(), 512);
|
||||
m_mem.SaveBMP(s, m_context->ZBUF.Block(), m_context->FRAME.FBW, m_context->ZBUF.PSM, PCRTCDisplays.GetFramebufferRect(-1).width(), 512);
|
||||
}
|
||||
|
||||
Queue(data);
|
||||
@@ -514,18 +493,18 @@ void GSRendererSW::Draw()
|
||||
{
|
||||
// Dump the RT in 32 bits format. It helps to debug texture shuffle effect
|
||||
s = GetDrawDumpPath("%05d_f%lld_rt1_%05x_32bits.bmp", s_n, frame, m_context->FRAME.Block());
|
||||
m_mem.SaveBMP(s, m_context->FRAME.Block(), m_context->FRAME.FBW, 0, GetFrameRect().width(), 512);
|
||||
m_mem.SaveBMP(s, m_context->FRAME.Block(), m_context->FRAME.FBW, 0, PCRTCDisplays.GetFramebufferRect(-1).width(), 512);
|
||||
}
|
||||
|
||||
s = GetDrawDumpPath("%05d_f%lld_rt1_%05x_%s.bmp", s_n, frame, m_context->FRAME.Block(), psm_str(m_context->FRAME.PSM));
|
||||
m_mem.SaveBMP(s, m_context->FRAME.Block(), m_context->FRAME.FBW, m_context->FRAME.PSM, GetFrameRect().width(), 512);
|
||||
m_mem.SaveBMP(s, m_context->FRAME.Block(), m_context->FRAME.FBW, m_context->FRAME.PSM, PCRTCDisplays.GetFramebufferRect(-1).width(), 512);
|
||||
}
|
||||
|
||||
if (GSConfig.SaveDepth && s_n >= GSConfig.SaveN)
|
||||
{
|
||||
s = GetDrawDumpPath("%05d_f%lld_rz1_%05x_%s.bmp", s_n, frame, m_context->ZBUF.Block(), psm_str(m_context->ZBUF.PSM));
|
||||
|
||||
m_mem.SaveBMP(s, m_context->ZBUF.Block(), m_context->FRAME.FBW, m_context->ZBUF.PSM, GetFrameRect().width(), 512);
|
||||
m_mem.SaveBMP(s, m_context->ZBUF.Block(), m_context->FRAME.FBW, m_context->ZBUF.PSM, PCRTCDisplays.GetFramebufferRect(-1).width(), 512);
|
||||
}
|
||||
|
||||
if (GSConfig.SaveL > 0 && (s_n - GSConfig.SaveN) > GSConfig.SaveL)
|
||||
@@ -613,14 +592,14 @@ void GSRendererSW::Sync(int reason)
|
||||
{
|
||||
s = GetDrawDumpPath("%05d_f%lld_rt1_%05x_%s.bmp", s_n, g_perfmon.GetFrame(), m_context->FRAME.Block(), psm_str(m_context->FRAME.PSM));
|
||||
|
||||
m_mem.SaveBMP(s, m_context->FRAME.Block(), m_context->FRAME.FBW, m_context->FRAME.PSM, GetFrameRect().width(), 512);
|
||||
m_mem.SaveBMP(s, m_context->FRAME.Block(), m_context->FRAME.FBW, m_context->FRAME.PSM, PCRTCDisplays.GetFramebufferRect(-1).width(), 512);
|
||||
}
|
||||
|
||||
if (GSConfig.SaveDepth)
|
||||
{
|
||||
s = GetDrawDumpPath("%05d_f%lld_zb1_%05x_%s.bmp", s_n, g_perfmon.GetFrame(), m_context->ZBUF.Block(), psm_str(m_context->ZBUF.PSM));
|
||||
|
||||
m_mem.SaveBMP(s, m_context->ZBUF.Block(), m_context->FRAME.FBW, m_context->ZBUF.PSM, GetFrameRect().width(), 512);
|
||||
m_mem.SaveBMP(s, m_context->ZBUF.Block(), m_context->FRAME.FBW, m_context->ZBUF.PSM, PCRTCDisplays.GetFramebufferRect(-1).width(), 512);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -736,7 +736,7 @@ void GSDeviceVK::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex,
|
||||
{
|
||||
static_cast<GSTextureVK*>(sTex[1])->TransitionToLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
OMSetRenderTargets(dTex, nullptr, darea, false);
|
||||
SetUtilityTexture(sTex[1], m_linear_sampler);
|
||||
SetUtilityTexture(sTex[1], m_point_sampler);
|
||||
BeginClearRenderPass(m_utility_color_render_pass_clear, darea, c);
|
||||
SetPipeline(m_convert[static_cast<int>(ShaderConvert::COPY)]);
|
||||
DrawStretchRect(sRect[1], PMODE.SLBG ? dRect[2] : dRect[1], dsize);
|
||||
@@ -753,7 +753,7 @@ void GSDeviceVK::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex,
|
||||
EndRenderPass();
|
||||
OMSetRenderTargets(sTex[2], nullptr, fbarea, false);
|
||||
if (dcleared)
|
||||
SetUtilityTexture(dTex, m_linear_sampler);
|
||||
SetUtilityTexture(dTex, m_point_sampler);
|
||||
// sTex[2] can be sTex[0], in which case it might be cleared (e.g. Xenosaga).
|
||||
BeginRenderPassForStretchRect(static_cast<GSTextureVK*>(sTex[2]), fbarea, GSVector4i(dRect[2]));
|
||||
if (dcleared)
|
||||
@@ -787,7 +787,7 @@ void GSDeviceVK::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex,
|
||||
if (sTex[0] && sTex[0]->GetState() == GSTexture::State::Dirty)
|
||||
{
|
||||
// 1st output is enabled. It must be blended
|
||||
SetUtilityTexture(sTex[0], m_linear_sampler);
|
||||
SetUtilityTexture(sTex[0], m_point_sampler);
|
||||
SetPipeline(m_merge[PMODE.MMOD]);
|
||||
SetUtilityPushConstants(&c, sizeof(c));
|
||||
DrawStretchRect(sRect[0], dRect[0], dTex->GetSize());
|
||||
@@ -797,7 +797,7 @@ void GSDeviceVK::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex,
|
||||
{
|
||||
EndRenderPass();
|
||||
SetPipeline(m_convert[static_cast<int>(ShaderConvert::YUV)]);
|
||||
SetUtilityTexture(dTex, m_linear_sampler);
|
||||
SetUtilityTexture(dTex, m_point_sampler);
|
||||
SetUtilityPushConstants(yuv_constants, sizeof(yuv_constants));
|
||||
OMSetRenderTargets(sTex[2], nullptr, fbarea, false);
|
||||
BeginRenderPass(m_utility_color_render_pass_load, fbarea);
|
||||
@@ -1996,7 +1996,6 @@ VkShaderModule GSDeviceVK::GetTFXFragmentShader(const GSHWDrawConfig::PSSelector
|
||||
AddMacro(ss, "PS_NO_COLOR1", sel.no_color1);
|
||||
AddMacro(ss, "PS_NO_ABLEND", sel.no_ablend);
|
||||
AddMacro(ss, "PS_ONLY_ALPHA", sel.only_alpha);
|
||||
AddMacro(ss, "PS_SWAP_GA", sel.swap_ga);
|
||||
ss << m_tfx_source;
|
||||
|
||||
VkShaderModule mod = g_vulkan_shader_cache->GetFragmentShader(ss.str());
|
||||
|
||||
+9
-2
@@ -60,6 +60,13 @@ public:
|
||||
RightOrBottom
|
||||
};
|
||||
|
||||
enum class PresentResult
|
||||
{
|
||||
OK,
|
||||
FrameSkipped,
|
||||
DeviceLost
|
||||
};
|
||||
|
||||
struct AdapterAndModeList
|
||||
{
|
||||
std::vector<std::string> adapter_names;
|
||||
@@ -137,7 +144,7 @@ public:
|
||||
|
||||
/// Returns false if the window was completely occluded. If frame_skip is set, the frame won't be
|
||||
/// displayed, but the GPU command queue will still be flushed.
|
||||
virtual bool BeginPresent(bool frame_skip) = 0;
|
||||
virtual PresentResult BeginPresent(bool frame_skip) = 0;
|
||||
|
||||
/// Presents the frame to the display, and renders OSD elements.
|
||||
virtual void EndPresent() = 0;
|
||||
@@ -184,7 +191,7 @@ namespace Host
|
||||
|
||||
/// Returns false if the window was completely occluded. If frame_skip is set, the frame won't be
|
||||
/// displayed, but the GPU command queue will still be flushed.
|
||||
bool BeginPresentFrame(bool frame_skip);
|
||||
HostDisplay::PresentResult BeginPresentFrame(bool frame_skip);
|
||||
|
||||
/// Presents the frame to the display, and renders OSD elements.
|
||||
void EndPresentFrame();
|
||||
|
||||
@@ -114,9 +114,9 @@ void Host::ReleaseHostDisplay(bool clear_state)
|
||||
{
|
||||
}
|
||||
|
||||
bool Host::BeginPresentFrame(bool frame_skip)
|
||||
HostDisplay::PresentResult Host::BeginPresentFrame(bool frame_skip)
|
||||
{
|
||||
return false;
|
||||
return HostDisplay::PresentResult::FrameSkipped;
|
||||
}
|
||||
|
||||
void Host::EndPresentFrame()
|
||||
|
||||
Reference in New Issue
Block a user