Compare commits

...
83 Commits
Author SHA1 Message Date
JordanTheToasterandrefractionpcsx2 a2e6a8e41f GameDB: DBZ Budokai Tenkaichi fixes
Adds half pixel offset special and full round sprite to Dragon Ball Z Budokai Tenkaichi
2022-08-15 15:29:34 +01:00
Mrlinkwiiandrefractionpcsx2 ec5f614a25 Gamedb: add upscaling fixes to 'Charlie's Angels' 2022-08-15 09:40:03 +01:00
lightningterror 23338319a0 PAD: Fix Wsign-compare warning. 2022-08-14 20:48:04 +02:00
lightningterror 191c7fdf30 GS: Fix Wunused-private-field warning. 2022-08-14 20:48:04 +02:00
refractionpcsx2 1342ed2afe GS: Revert #6838 2022-08-14 16:57:59 +01:00
Left-EmptyandGitHub 69b0193535 gameDB: Kessen 2 NTSC-J refraction patch fix (#6842) 2022-08-14 12:22:04 +01:00
Connor McLaughlinandrefractionpcsx2 db2489a555 GS/TextureCache: Fix texture resize in DX11 2022-08-14 03:54:19 +01:00
Silentandrefractionpcsx2 b191126f36 Workflows: Download cheats from pcsx2_patches repo on build time 2022-08-14 02:58:10 +01:00
JordanTheToasterandrefractionpcsx2 ef0de38624 GameDB: Add various Burnout fixes
Adds various Burnout fixes and adds a missing demo entry for Burnout Revenge
2022-08-14 01:56:46 +01:00
noigeaRandrefractionpcsx2 3d0a43d0e7 GameDB: Fix typos in game names 2022-08-13 21:27:52 +01:00
Dreadmothandrefractionpcsx2 874be7ea8b GameDB: Remove half-pixel offset from Mercenaries
Remove half-pixel offset from Mercenaries - it breaks the skybox.
2022-08-13 21:26:45 +01:00
Mrlinkwiiandrefractionpcsx2 a93034ad59 Gamedb: add fixes for 'World Rally Championship' NTSC-J versions 2022-08-13 17:02:18 +01:00
JordanTheToasterandrefractionpcsx2 134cee6adc WX: Change default sound module
Changes the default sound module from XAudio2 to Cubeb to prevent USB crashing and parity with Qt
2022-08-13 16:48:58 +01:00
TellowKrinkleandlightningterror cb64e8d504 GS:HW: More accurate blend equation for blend mix 2022-08-13 00:01:23 +02:00
JordanTheToasterandrefractionpcsx2 35a6dfb52a Config: Change default dump compression
Changes default dump compression from uncompressed to LZMA (xy)
2022-08-12 19:30:08 +01:00
Mrlinkwiiandrefractionpcsx2 1c276db76f Gamedb: add missing 'Ace Combat 04' entry 2022-08-12 18:13:04 +01:00
Mrlinkwiiandrefractionpcsx2 400e0f1dd4 Gamedb: add missing Alpine Racer 3 entries 2022-08-11 14:05:57 +01:00
Connor McLaughlinandrefractionpcsx2 9a7ed81759 Qt: Improve controller settings and add macro UI 2022-08-10 17:25:11 +01:00
Connor McLaughlinandrefractionpcsx2 6f40cf9beb Qt: Only hide global texture offset/skipdraw in release builds 2022-08-10 17:25:11 +01:00
Connor McLaughlinandrefractionpcsx2 307028734f Qt: Fix several issues in SettingWidgetBinder
- Possible nullopt value().
 - Incorrect loading of some folder values.
 - Enable per-game setting of spinboxes.
2022-08-10 17:25:11 +01:00
refractionpcsx2 cd2d7c91c3 GS: Add slow path for odd width 4bpp host->local transfers 2022-08-10 16:52:07 +01:00
TellowKrinkleandtellowkrinkle 8b375d66fa GHActions:MacOS: Don't fail on failed brew unlink
If brew can't unlink them, they're not there and there's no need to do anything
2022-08-10 04:28:31 -05:00
JordanTheToasterandrefractionpcsx2 8f367abe0b GameDB: Ace Combat 5 Fixes
Adds texture preloading partial to Ace Combat 5 for better performance.
2022-08-10 06:44:20 +01:00
Mrlinkwiiandrefractionpcsx2 fdc84be0d9 Gamedb: upscaling fixes for Shadow of Rome 2022-08-10 06:44:00 +01:00
BreakPointsandrefractionpcsx2 edfceaad14 Qt: Force helpText size to avoid overflow at default size
Increase minimum size of helpText to match max
2022-08-09 15:35:52 +01:00
JordanTheToasterandrefractionpcsx2 40fda5067d GameDB: Add DBZ Budokai Tenkaichi 2 fixes
Adds half pixel offset normal to DBZ BT 2 and removes full round sprite to fix the insane amount of ghosting in some areas
2022-08-09 15:33:42 +01:00
lightningterror 573706e5e4 GS-hw: Don't disable second output on blend mix when using constant factor.
We don't want to disable second output when using blend mix1 above 1.0f Af.
Reason is we change Af to As and later output it on second output for hw blending.
2022-08-08 18:11:28 +02:00
PCSX2 Botandlightningterror 1ef4acc2af PAD: Update to latest controller database. 2022-08-08 18:11:16 +02:00
lightningterror 6035db3231 GS-d3d11: Disable blending when color isn't written.
Fixes d3d11 crashing on mgs3 using intel igpu.
2022-08-08 12:51:17 +02:00
Mrlinkwiiandrefractionpcsx2 2cd44412f6 Gamedb: upscaling fixes for Poison Pink/ Eternal Poison 2022-08-08 11:21:14 +01:00
JordanTheToasterandrefractionpcsx2 c149fe31fb GameDB: Add missing Korean serials
Adds missing Korean serials as well as the accompanying fixes
2022-08-08 04:40:02 +01:00
JordanTheToasterandrefractionpcsx2 0e24d638c9 GameDB: Fixes for Devil Kings
Adds Special HPO to Devil Kings
2022-08-07 20:37:31 +01:00
JordanTheToasterandrefractionpcsx2 5c056f0c4d GameDB: Add Disaster Report fixes
Adds auto flush to Disaster Report to correct an inaccurate blur filter
2022-08-07 18:57:35 +01:00
lightningterror 62b838427f GS-hw: Purge tri ace crc hacks.
Star Ocean 3, Valkyrie Profile 2, Radiata Stories.

The crc hacks aren't that needed anymore since right now D3D is much faster than it used to be.
2022-08-07 14:13:56 +02:00
BreakPointsandrefractionpcsx2 ad529c10ab Qt Updater: Copy new icon to updater
So that the updater benefits from the new icon changes in #6785 (file size, sharpness of small icon sizes)
2022-08-07 13:09:26 +01:00
noigeaRandGitHub 8ef7399612 GameDB: Add missing Japanese games (#6790) 2022-08-07 13:08:59 +01:00
JordanTheToasterandrefractionpcsx2 939c1f0f29 GameDB: Add 007 NightFire fixes
Adds half pixel offset special to 007 NightFire
2022-08-07 13:08:22 +01:00
lightningterror 6b48cf574d GS-hw: Adjust how we handle specific blend mix cases.
Replace Cs*As + Cd*(1 - As) with Cs*As - Cd*(As - 1).
Replace Cs*F + Cd*(1 - F) with Cs*F - Cd*(F - 1).
As - 1 or F - 1 subtraction is only done for the dual source output (hw blending part)
since we are changing the equation.
Af will be replaced with As in shader and send it to dual source output.

Also check if A*Alpha in the shader overflows, if it does then adjust the
alpha that is sent for HW blending further to compensate.
2022-08-07 14:08:04 +02:00
Mrlinkwiiandlightningterror 1478819258 GS-hw: Purge SkyGunner crc hacks. 2022-08-07 03:07:04 +02:00
Mrlinkwiiandlightningterror f53fbd6282 GS-hw: Purge MidnightClub3 crc hacks. 2022-08-07 03:06:51 +02:00
lightningterror 60e6e1f242 Qt: Fix Wunused-but-set-variable warnings. 2022-08-07 03:00:22 +02:00
lightningterror 7a5f63e0f5 GS-metal: Move PABE shader bit to the top of sw blending.
Early return when there is no sw blending, no need to run the blend code.
Optimization.
2022-08-06 17:36:24 +02:00
refractionpcsx2 c0e325dfbf GS-HW: Correct FBMask masks for texture shuffles 2022-08-06 12:35:16 +01:00
JordanTheToasterandrefractionpcsx2 62e775b7c2 GameDB: Correct The Incredible Hulks name
Corrects the name of the PAL version of The Incredible Hulk and adds missing demo version to the db
2022-08-06 11:52:44 +01:00
BreakPointsandGitHub 37676a296a UI: Update all sizes of App Icon (#6785) 2022-08-06 10:12:52 +01:00
Connor McLaughlinandrefractionpcsx2 bcc4548f7b GS/HW: Clamp draw rect to unscaled, not scaled coordinates
Fixes broken shuffle effect in Haunting Ground when upscale is set above
1x.
2022-08-06 06:13:30 +01:00
JordanTheToasterandlightningterror 60714b5ca4 Qt Updater: Fix use of old icons
Fixes the updater seemingly using the old style of PCSX2 icon.
2022-08-06 00:13:59 +02:00
Mrlinkwiiandrefractionpcsx2 d632634e56 Gamedb: add basic mipmap to Blood Omen 2 2022-08-05 19:17:14 +01:00
lightningterror bec8b40edb GS-wx: Remove some unused upscale code for renderer tab. 2022-08-05 11:41:29 +02:00
lightningterror ab361eef72 GS-wx: Fix Wunused-but-set-variable warning. 2022-08-05 11:37:50 +02:00
Connor McLaughlinandrefractionpcsx2 1f16adbca7 GS/Vulkan: Don't use clear attachments on cleared RT/DS
Redundant.
2022-08-05 07:16:53 +01:00
Connor McLaughlinandrefractionpcsx2 1677ef3189 GS/TextureCache: Elide copies when source matches target
Assuming everything matches up, instead of copying the target, we can just sample it directly.
It's the same as doing the copy first, except we save GPU time.
2022-08-05 07:16:53 +01:00
Connor McLaughlinandrefractionpcsx2 36defdfbe9 GS: Use tex-is-fb for sprites/shuffles 2022-08-05 07:16:53 +01:00
Mrlinkwiiandrefractionpcsx2 7ba6e4adc3 Gamedb: remove not needed upscaling fixes for God of war 2022-08-05 00:19:55 +01:00
TheLastRarandlightningterror ee2cbc80b8 DEV9: Sockets: Set Arp Hardware Type 2022-08-03 21:05:40 +02:00
lightningterror b4d960e795 GS-ogl: Remove bad driver messages for intel/amd.
AMD has new gl drivers which work well now.
For intel it's not really needed as the log may be useful for broadwell or lower igpu.
2022-08-03 20:57:47 +02:00
lightningterror 0f716d9053 GS-hw: Fix dithering log. 2022-08-03 20:54:30 +02:00
JordanTheToasterandrefractionpcsx2 cdbfd22296 GameDB: Corrects name of Asterix & Obelix XXL2
Fixes the incorrect name of Asterix & Obelix XXL
2022-08-03 19:34:10 +01:00
Goatman13andlightningterror de801a4f23 GameDB: Replace Evolution Snowboarding patches.
Instead of skipping videos, patches extend stack in problematic function. So required data is no longer overwritten. Workaround for missing EE data cache emulation.
2022-08-03 11:46:51 +02:00
JordanTheToasterandrefractionpcsx2 1376a615b7 GameDB: Misc compat level fixes
Fixes some out of date compat levels
2022-08-03 09:51:23 +01:00
noigeaRandrefractionpcsx2 1fbc9ab85e GameDB: add Panzer Elite Action upscaling fix 2022-08-03 09:51:00 +01:00
refractionpcsx2 9a8a3601a7 UI-Qt: Add Bilinear dropdown to SW options 2022-08-02 23:35:48 +01:00
refractionpcsx2 f1e68e9bd0 GS: Don't double res on No-Interlace patch when interlaced 2022-08-02 23:35:35 +01:00
icheeandrefractionpcsx2 5228e6e6c7 GameDB: Fixes Dual Hearts FMV's not being deinterlaced
Deinterlacing is not being applied causing the FMV's to be offset. This forces blend tff.

Fixes #6715.
2022-08-02 11:36:24 +01:00
PCSX2 Botandlightningterror 5b977cef54 PAD: Update to latest controller database. 2022-08-01 23:43:45 +02:00
refractionpcsx2 a802deeb51 GameDB: Add round sprite full for DMC3, reduces blur. 2022-08-01 21:07:30 +01:00
JordanTheToasterandrefractionpcsx2 eaf6375410 GameDB: Maximo vs Army of Zin Fixes
Adds fixes and adds a missing entry in the DB for the Korean version.
2022-08-01 16:54:00 +01:00
JordanTheToasterandrefractionpcsx2 d1daf21a3f GameDB: Misc fixes
Fixes many problems with many games and adds missing hacks and fixes.
2022-08-01 14:02:13 +01:00
Risaeandrefractionpcsx2 98c83c8509 GameDB: Add fixes for Growlanser V & Growlanser VI 2022-08-01 09:02:10 +01:00
Mrlinkwiiandrefractionpcsx2 b25731f621 Gamedb:upscaling fixes for "dot hack Infection Part 1" 2022-07-31 19:50:01 +01:00
Mrlinkwiiandrefractionpcsx2 563f5ec713 Gamedb: add partial preload to "Hitman Contracts" 2022-07-31 12:36:48 +01:00
refractionpcsx2 ad25ae43d5 GameDB: Disable Instant VU for Juiced 2022-07-31 10:58:51 +01:00
refractionpcsx2 b058e72fdd VIF: Wait for VU on flush + clang format
Improves performance in MTVU + Non-instant a little.
2022-07-31 10:58:51 +01:00
refractionpcsx2 38ee8ccfe3 VU: Fix some bugs, ignore VU's in EE Timing Hack 2022-07-31 10:58:51 +01:00
refractionpcsx2 53161f76a6 VU-MTVU: Simulate VU times when in MTVU w/o Instant VU. 2022-07-31 10:58:51 +01:00
Connor McLaughlinandrefractionpcsx2 742a929966 GS: Purge sparse texture
Only worked on a limited number of drivers, not reliable, and not
necessary now that we're reducing target sizes, which works better
anyway.
2022-07-31 10:58:39 +01:00
Connor McLaughlinandrefractionpcsx2 63b3646e73 GS/TextureCache: Expand perfect-matched targets for display when needed
Hitman Contracts draws a line near the bottom of the screen, presumably
with a smaller scissor, then scans out 448 lines. Which meant the merge
oversampled and smeared.
2022-07-31 10:39:35 +01:00
refractionpcsx2 794c73a613 GS-debug: Fix GSDumps when rt or ds is null. 2022-07-31 08:21:22 +01:00
JordanTheToasterandrefractionpcsx2 59fc2a4c15 GameDB: WRC 4 Fixes
Fixes misaligned text and car shadow softness.
2022-07-30 22:15:44 +01:00
Connor McLaughlinandrefractionpcsx2 6a144f86cf GS: Improve target size calcs and remove conservative framebuffer 2022-07-30 17:16:59 +01:00
refractionpcsx2 f218e11d78 GS: Rearrange GS Dump names to be less annoying to navigate 2022-07-30 17:16:35 +01:00
JordanTheToasterandrefractionpcsx2 f6bcd8a2c2 GameDB: Various texture preloading fixes
Fixes performance problems when texture preloading is set to Full (Hash Cache).
2022-07-30 17:08:23 +01:00
JordanTheToasterandrefractionpcsx2 bf0cd5a6cd Qt: Recommended text fixes
Fixes some missing or incorrect recommended boxes.
2022-07-29 13:42:31 +01:00
95 changed files with 2926 additions and 1473 deletions
+9
View File
@@ -32,6 +32,10 @@ on:
required: false
type: string
default: ""
cheats_url:
required: false
type: string
default: https://github.com/PCSX2/pcsx2_patches/releases/latest/download
jobs:
build_linux:
@@ -97,6 +101,11 @@ jobs:
if: steps.cache-deps.outputs.cache-hit != 'true'
run: .github/workflows/scripts/linux/build-dependencies-qt.sh
- name: Download cheats
run: |
cd bin/resources
aria2c -Z "${{ inputs.cheats_url }}/cheats_ni.zip" "${{ inputs.cheats_url }}/cheats_ws.zip"
- name: Generate CMake
env:
COMPILER: ${{ inputs.compiler }}
+9
View File
@@ -28,6 +28,10 @@ on:
required: false
type: string
default: ""
cheats_url:
required: false
type: string
default: https://github.com/PCSX2/pcsx2_patches/releases/latest/download
jobs:
build_linux:
@@ -115,6 +119,11 @@ jobs:
fi
sudo make -C 3rdparty/${{ env.SDL }} install
- name: Download cheats
run: |
cd bin/resources
aria2c -Z "${{ inputs.cheats_url }}/cheats_ni.zip" "${{ inputs.cheats_url }}/cheats_ws.zip"
- name: Generate CMake
env:
PLATFORM: ${{ inputs.platform }}
+11 -1
View File
@@ -17,6 +17,10 @@ on:
gui:
required: true
type: string
cheats_url:
required: false
type: string
default: https://github.com/PCSX2/pcsx2_patches/releases/latest/download
jobs:
build_macos:
@@ -58,7 +62,8 @@ jobs:
HOMEBREW_NO_INSTALL_CLEANUP: 1
HOMEBREW_NO_ANALYTICS: 1
run: |
brew unlink libjpeg libpng # Conflicts with our self-built dependencies
brew unlink libjpeg || true # Conflicts with our self-built dependencies
brew unlink libpng || true
# Unlike other packages, brew's MoltenVK build uses MoltenVK's minimum macOS version of 10.13 so we can use it
if ! brew install molten-vk; then
brew update
@@ -78,6 +83,11 @@ jobs:
GUI: ${{ inputs.gui }}
run: .github/workflows/scripts/macos/build-dependencies.sh
- name: Download cheats
run: |
cd bin/resources
aria2c -Z "${{ inputs.cheats_url }}/cheats_ni.zip" "${{ inputs.cheats_url }}/cheats_ws.zip"
- name: Generate CMake Files
run: |
QT_BUILD=$([ "${{ inputs.gui }}" == "Qt" ] && echo "ON" || echo "OFF")
+10
View File
@@ -29,6 +29,10 @@ on:
required: false
type: string
default: https://github.com/PCSX2/pcsx2-windows-dependencies/releases/download/2022-07-24/qt-6.3.1-x64.7z
cheats_url:
required: false
type: string
default: https://github.com/PCSX2/pcsx2_patches/releases/latest/download
jobs:
build_windows_qt:
@@ -77,6 +81,12 @@ jobs:
7z x qt-*-x64.7z
del qt-*-x64.7z
- name: Download cheats
shell: cmd
run: |
cd bin/resources
aria2c -Z "${{ inputs.cheats_url }}/cheats_ni.zip" "${{ inputs.cheats_url }}/cheats_ws.zip"
- name: Build PCSX2
shell: cmd
run: |
+10
View File
@@ -25,6 +25,10 @@ on:
configuration:
required: true
type: string
cheats_url:
required: false
type: string
default: https://github.com/PCSX2/pcsx2_patches/releases/latest/download
jobs:
build_windows_wx:
@@ -71,6 +75,12 @@ jobs:
if: inputs.configuration != 'CMake'
uses: microsoft/setup-msbuild@v1
- name: Download cheats
shell: cmd
run: |
cd bin/resources
aria2c -Z "${{ inputs.cheats_url }}/cheats_ni.zip" "${{ inputs.cheats_url }}/cheats_ws.zip"
- name: Generate CMake
if: inputs.configuration == 'CMake'
id: cmake
File diff suppressed because it is too large Load Diff
Binary file not shown.
Binary file not shown.
+35 -14
View File
@@ -11,7 +11,7 @@
03000000d0160000600a000000000000,4Play Adapter,a:b1,b:b3,back:b4,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b6,leftstick:b14,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b15,righttrigger:b9,rightx:a3,righty:a4,start:b5,x:b0,y:b2,platform:Windows,
03000000c82d00000031000000000000,8BitDo Adapter,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00000531000000000000,8BitDo Adapter 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00000951000000000000,8BitDo Dogbone Modkit,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b11,platform:Windows,
03000000c82d00000951000000000000,8BitDo Dogbone,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a2,rightx:a3,righty:a5,start:b11,platform:Windows,
03000000008000000210000000000000,8BitDo F30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows,
030000003512000011ab000000000000,8BitDo F30 Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
03000000c82d00001028000000000000,8BitDo F30 Arcade Joystick,a:b0,b:b1,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Windows,
@@ -19,14 +19,15 @@
03000000801000000900000000000000,8BitDo F30 Arcade Stick,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00001038000000000000,8BitDo F30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows,
03000000c82d00000090000000000000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,
03000000c82d00001151000000000000,8BitDo Lite SE,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,
03000000c82d00000151000000000000,8BitDo M30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a2,rightshoulder:b6,righttrigger:b7,rightx:a3,righty:a5,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00000650000000000000,8BitDo M30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b8,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00005106000000000000,8BitDo M30,a:b0,b:b1,back:b10,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,guide:b2,leftshoulder:b8,lefttrigger:b9,rightshoulder:b6,righttrigger:b7,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00000151000000000000,8BitDo M30 Modkit,a:b0,b:b1,back:b10,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,rightshoulder:b6,righttrigger:b7,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00000310000000000000,8BitDo N30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00000451000000000000,8BitDo N30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a2,rightx:a3,righty:a5,start:b11,platform:Windows,
03000000c82d00002028000000000000,8BitDo N30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows,
03000000c82d00008010000000000000,8BitDo N30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d0000e002000000000000,8BitDo N30,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a1,start:b6,platform:Windows,
03000000c82d00000451000000000000,8BitDo N30 Modkit,a:b1,b:b0,back:b10,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,start:b11,platform:Windows,
03000000c82d00000190000000000000,8BitDo N30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,
03000000c82d00001590000000000000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows,
03000000c82d00006528000000000000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,
@@ -37,7 +38,8 @@
03000000022000000090000000000000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,
03000000203800000900000000000000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,
03000000c82d00002038000000000000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,
03000000c82d00000751000000000000,8BitDo P30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00000751000000000000,8BitDo P30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a2,rightshoulder:b7,righttrigger:b9,rightx:a3,righty:a5,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00000851000000000000,8BitDo P30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a2,rightshoulder:b7,righttrigger:b9,rightx:a3,righty:a5,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00000360000000000000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,
03000000c82d00000361000000000000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,
03000000c82d00000660000000000000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,
@@ -45,7 +47,7 @@
03000000c82d00000231000000000000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,
03000000c82d00000331000000000000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,
03000000c82d00000431000000000000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,
03000000c82d00002867000000000000,8BitDo S30 Modkit,a:b0,b:b1,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,leftshoulder:b8,lefttrigger:b9,rightshoulder:b6,righttrigger:b7,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00002867000000000000,8BitDo S30,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,lefttrigger:b9,leftx:a0,lefty:a2,rightshoulder:b6,righttrigger:b7,rightx:a3,righty:a5,start:b10,x:b3,y:b4,platform:Windows,
03000000c82d00000130000000000000,8BitDo SF30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows,
03000000c82d00000060000000000000,8BitDo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows,
03000000c82d00000061000000000000,8BitDo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows,
@@ -54,11 +56,11 @@
03000000c82d00003028000000000000,8BitDo SFC30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows,
030000003512000020ab000000000000,8BitDo SN30,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b11,x:b4,y:b3,platform:Windows,
03000000c82d00000030000000000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows,
03000000c82d00000351000000000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a2,rightshoulder:b7,rightx:a3,righty:a5,start:b11,x:b4,y:b3,platform:Windows,
03000000c82d00001290000000000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows,
03000000c82d000020ab000000000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows,
03000000c82d00004028000000000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows,
03000000c82d00006228000000000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows,
03000000c82d00000351000000000000,8BitDo SN30 Modkit,a:b1,b:b0,back:b10,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows,
03000000c82d00000021000000000000,8BitDo SN30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00000160000000000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows,
03000000c82d00000161000000000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows,
@@ -120,6 +122,7 @@
030000006b1400000103000000000000,Bigben PS3 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Windows,
03000000120c0000200e000000000000,Brook Mars PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
03000000120c0000210e000000000000,Brook Mars PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
03000000120c0000f10e000000000000,Brook PS2 Adapter,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
03000000120c0000310c000000000000,Brook Super Converter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Windows,
03000000d81d00000b00000000000000,Buffalo BSGP1601 Series,a:b5,b:b3,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b9,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b13,x:b4,y:b2,platform:Windows,
030000005b1c00002400000000000000,Capcom Home Arcade Controller,a:b3,b:b4,back:b7,leftshoulder:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b6,x:b0,y:b1,platform:Windows,
@@ -234,14 +237,14 @@
030000000d0f00005100000000000000,Hori Fighting Commander PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
030000000d0f00008600000000000000,Hori Fighting Commander Xbox 360,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
030000000d0f0000ba00000000000000,Hori Fighting Commander Xbox 360,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
030000000d0f00008800000000000000,Hori Fighting Stick mini 4 (PS3),a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b8,x:b0,y:b3,platform:Windows,
030000000d0f00008700000000000000,Hori Fighting Stick mini 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,
030000000d0f00001000000000000000,Hori Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
030000000d0f00003200000000000000,Hori Fightstick 3W,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
030000000d0f0000c000000000000000,Hori Fightstick 4,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
030000000d0f00000d00000000000000,Hori Fightstick EX2,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Windows,
030000000d0f00003701000000000000,Hori Fightstick Mini,a:b1,b:b0,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,rightx:a3,righty:a4,start:b7,x:b3,y:b2,platform:Windows,
030000000d0f00004000000000000000,Hori Fightstick Mini 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b4,rightshoulder:b7,righttrigger:b6,start:b9,x:b0,y:b3,platform:Windows,
030000000d0f00008800000000000000,Hori Fighting Stick mini 4 (PS3),a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b8,x:b0,y:b3,platform:Windows,
030000000d0f00008700000000000000,Hori Fighting Stick mini 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,
030000000d0f00002100000000000000,Hori Fightstick V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
030000000d0f00002700000000000000,Hori Fightstick V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,
030000000d0f0000a000000000000000,Hori Grip TAC4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b13,x:b0,y:b3,platform:Windows,
@@ -482,7 +485,6 @@
03000000120c00001e0e000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
03000000120c0000a957000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
03000000120c0000aa57000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
03000000120c0000f10e000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
03000000120c0000f21c000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
03000000120c0000f31c000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
03000000120c0000f41c000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
@@ -740,11 +742,17 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
030000008f0e00000300000009010000,2 In 1 Joystick,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X,
03000000c82d00000031000001000000,8BitDo Adapter,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
03000000c82d00000531000000020000,8BitDo Adapter 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
03000000c82d00000951000000010000,8BitDo Dogbone,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a1,rightx:a2,righty:a3,start:b11,platform:Mac OS X,
03000000c82d00000090000001000000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00001038000000010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00001251000000010000,8BitDo Lite 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00001151000000010000,8BitDo Lite SE,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00001151000000020000,8BitDo Lite SE,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000a30c00002400000006020000,8BitDo M30,a:b2,b:b1,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,guide:b9,leftshoulder:b6,lefttrigger:b5,rightshoulder:b4,righttrigger:b7,start:b8,x:b3,y:b0,platform:Mac OS X,
03000000c82d00000151000000010000,8BitDo M30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
03000000c82d00000650000001000000,8BitDo M30,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,start:b11,x:b3,y:b4,platform:Mac OS X,
03000000c82d00005106000000010000,8BitDo M30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,guide:b2,leftshoulder:b6,lefttrigger:a5,rightshoulder:b7,righttrigger:a4,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00000451000000010000,8BitDo N30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a1,rightx:a2,righty:a3,start:b11,platform:Mac OS X,
03000000c82d00001590000001000000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00006528000000010000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
030000003512000012ab000001000000,8BitDo NES30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X,
@@ -753,14 +761,17 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000022000000090000001000000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000203800000900000000010000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00000190000001000000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00001251000000010000,8BitDo Lite 2,a:b1,b:b0,x:b4,y:b3,back:b10,guide:b12,start:b11,leftstick:b13,rightstick:b14,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a5,righttrigger:a4,platform:Mac OS X,
03000000c82d00000751000000010000,8BitDo P30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
03000000c82d00000851000000010000,8BitDo P30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
03000000c82d00000660000000010000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00000660000000020000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00000131000001000000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00000231000001000000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00000331000001000000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00000431000001000000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00002867000000010000,8BitDo S30,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b3,y:b4,platform:Mac OS X,
03000000102800000900000000000000,8BitDo SFC30 Joystick,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00000351000000010000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00001290000001000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00004028000000010000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00000160000001000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
@@ -958,11 +969,16 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
# Linux
030000005e0400008e02000020010000,8BitDo Adapter,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
03000000c82d00000031000011010000,8BitDo Adapter,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000c82d00000951000000010000,8BitDo Dogbone,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a1,rightx:a2,righty:a3,start:b11,platform:Linux,
03000000021000000090000011010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
03000000c82d00000090000011010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
05000000c82d00001038000000010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
03000000c82d00001151000011010000,8BitDo Lite SE,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
05000000c82d00001151000000010000,8BitDo Lite SE,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
03000000c82d00000151000000010000,8BitDo M30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000c82d00000650000011010000,8BitDo M30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,start:b11,x:b3,y:b4,platform:Linux,
05000000c82d00005106000000010000,8BitDo M30,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,start:b11,x:b3,y:b4,platform:Linux,
03000000c82d00000451000000010000,8BitDo N30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a1,rightx:a2,righty:a3,start:b11,platform:Linux,
03000000c82d00001590000011010000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
05000000c82d00006528000000010000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
03000000008000000210000011010000,8BitDo NES30,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,
@@ -972,12 +988,15 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000c82d00000190000011010000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
05000000203800000900000000010000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
05000000c82d00002038000000010000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
03000000c82d00000751000000010000,8BitDo P30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:a8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
05000000c82d00000851000000010000,8BitDo P30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:a8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000c82d00000660000011010000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
05000000c82d00000660000000010000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
03000000c82d00000131000011010000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
03000000c82d00000231000011010000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
03000000c82d00000331000011010000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
03000000c82d00000431000011010000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
03000000c82d00002867000000010000,8BitDo S30,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b3,y:b4,platform:Linux,
05000000c82d00000060000000010000,8BitDo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
05000000c82d00000061000000010000,8BitDo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
030000003512000012ab000010010000,8BitDo SFC30,a:b2,b:b1,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b0,platform:Linux,
@@ -985,6 +1004,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000c82d000021ab000010010000,8BitDo SFC30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux,
05000000102800000900000000010000,8BitDo SFC30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux,
05000000c82d00003028000000010000,8BitDo SFC30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux,
05000000c82d00000351000000010000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
03000000c82d00000160000000000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Linux,
03000000c82d00000160000011010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
03000000c82d00000161000000000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Linux,
@@ -1079,10 +1099,10 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
030000000d0f00005001000009040000,Hori Fighting Commander OCTA Xbox One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000000d0f00008500000010010000,Hori Fighting Commander PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
030000000d0f00008600000002010000,Hori Fighting Commander Xbox 360,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
030000000d0f00001000000011010000,Hori Fightstick 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,
03000000ad1b000003f5000033050000,Hori Fightstick VX,+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b8,guide:b10,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b2,y:b3,platform:Linux,
030000000d0f00008800000011010000,Hori Fighting Stick mini 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,
030000000d0f00008700000011010000,Hori Fighting Stick mini 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,rightshoulder:b5,rightstick:b11,righttrigger:a4,start:b9,x:b0,y:b3,platform:Linux,
030000000d0f00001000000011010000,Hori Fightstick 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,
03000000ad1b000003f5000033050000,Hori Fightstick VX,+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b8,guide:b10,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b2,y:b3,platform:Linux,
030000000d0f00004d00000011010000,Hori Gem Pad 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
03000000ad1b000001f5000033050000,Hori Pad EX Turbo 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000000d0f00003801000011010000,Hori PC Engine Mini Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b9,platform:Linux,
@@ -1209,8 +1229,8 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
050000007e0500000620000001800000,Nintendo Switch Left Joy-Con,a:b16,b:b15,back:b4,leftshoulder:b6,leftstick:b12,leftx:a1,lefty:a0~,rightshoulder:b8,start:b9,x:b14,y:b17,platform:Linux,
03000000d620000013a7000011010000,Nintendo Switch PowerA Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
03000000d620000011a7000011010000,Nintendo Switch PowerA Core Plus Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
030000007e0500000920000011810000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,,misc1:b4,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,platform:Linux,
050000007e0500000920000001000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,,misc1:b4,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
030000007e0500000920000011810000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,misc1:b4,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,platform:Linux,
050000007e0500000920000001000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b4,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
050000007e0500000920000001800000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,platform:Linux,
050000007e0500000720000001800000,Nintendo Switch Right Joy-Con,a:b1,b:b2,back:b9,leftshoulder:b4,leftstick:b10,leftx:a1~,lefty:a0,rightshoulder:b6,start:b8,x:b0,y:b3,platform:Linux,
05000000010000000100000003000000,Nintendo Wii Remote,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
@@ -1440,6 +1460,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
050000005e040000130b000013050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
060000005e040000120b00000b050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
050000005e040000130b000011050000,Xbox Series X Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
030000005e040000120b000007050000,Xbox Series X Controller,a:b0,b:b1,x:b2,y:b3,back:b6,guide:b8,start:b7,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,misc1:b11,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Linux,
050000005e040000130b000007050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000450c00002043000010010000,XEOX SL6556 BK,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
05000000172700004431000029010000,XiaoMi Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Linux,
+32 -4
View File
@@ -750,7 +750,7 @@ void ps_color_clamp_wrap(inout float3 C)
}
}
void ps_blend(inout float4 Color, float As, float2 pos_xy)
void ps_blend(inout float4 Color, inout float As, float2 pos_xy)
{
if (SW_BLEND)
{
@@ -775,10 +775,38 @@ void ps_blend(inout float4 Color, float As, float2 pos_xy)
float3 D = (PS_BLEND_D == 0) ? Cs : ((PS_BLEND_D == 1) ? Cd : (float3)0.0f);
// As/Af clamp alpha for Blend mix
if (PS_BLEND_MIX)
C = min(C, (float)1.0f);
// We shouldn't clamp blend mix with clr1 as we want alpha higher
if (PS_BLEND_MIX && PS_CLR_HW != 1)
C = min(C, 1.0f);
Color.rgb = (PS_BLEND_A == PS_BLEND_B) ? D : trunc(((A - B) * C) + D);
if (PS_BLEND_A == PS_BLEND_B)
Color.rgb = D;
else if (PS_BLEND_MIX)
// In blend_mix, HW adds on some alpha factor * dst.
// Truncating here wouldn't quite get the right result because it prevents the <1 bit here from combining with a <1 bit in dst to form a ≥1 amount that pushes over the truncation.
// Instead, apply an offset to convert HW's round to a floor.
// Since alpha is in 1/128 increments, subtracting (0.5 - 0.5/128 == 127/256) would get us what we want if GPUs blended in full precision.
// But they don't. Details here: https://github.com/PCSX2/pcsx2/pull/6809#issuecomment-1211473399
// Based on the scripts at the above link, the ideal choice for Intel GPUs is 126/256, AMD 120/256. Nvidia is a lost cause.
// 124/256 seems like a reasonable compromise, providing the correct answer 99.3% of the time on Intel (vs 99.6% for 126/256), and 97% of the time on AMD (vs 97.4% for 120/256).
Color.rgb = ((A - B) * C + D) - (124.0f/256.0f);
else
Color.rgb = trunc(((A - B) * C) + D);
if (PS_CLR_HW == 1)
{
// Replace Af with As so we can do proper compensation for Alpha.
if (PS_BLEND_C == 2)
As = Af;
// Subtract 1 for alpha to compensate for the changed equation,
// if c.rgb > 255.0f then we further need to adjust alpha accordingly,
// we pick the lowest overflow from all colors because it's the safest,
// we divide by 255 the color because we don't know Cd value,
// changed alpha should only be done for hw blend.
float min_color = min(min(Color.r, Color.g), Color.b);
float alpha_compensate = max(1.0f, min_color / 255.0f);
As -= alpha_compensate;
}
}
else
{
+28 -3
View File
@@ -689,7 +689,7 @@ void ps_color_clamp_wrap(inout vec3 C)
#endif
}
void ps_blend(inout vec4 Color, float As)
void ps_blend(inout vec4 Color, inout float As)
{
#if SW_BLEND
@@ -754,16 +754,41 @@ void ps_blend(inout vec4 Color, float As)
#endif
// As/Af clamp alpha for Blend mix
#if PS_BLEND_MIX
C = min(C, float(1.0f));
// We shouldn't clamp blend mix with clr1 as we want alpha higher
#if PS_BLEND_MIX && PS_CLR_HW != 1
C = min(C, 1.0f);
#endif
#if PS_BLEND_A == PS_BLEND_B
Color.rgb = D;
#elif PS_BLEND_MIX
// In blend_mix, HW adds on some alpha factor * dst.
// Truncating here wouldn't quite get the right result because it prevents the <1 bit here from combining with a <1 bit in dst to form a ≥1 amount that pushes over the truncation.
// Instead, apply an offset to convert HW's round to a floor.
// Since alpha is in 1/128 increments, subtracting (0.5 - 0.5/128 == 127/256) would get us what we want if GPUs blended in full precision.
// But they don't. Details here: https://github.com/PCSX2/pcsx2/pull/6809#issuecomment-1211473399
// Based on the scripts at the above link, the ideal choice for Intel GPUs is 126/256, AMD 120/256. Nvidia is a lost cause.
// 124/256 seems like a reasonable compromise, providing the correct answer 99.3% of the time on Intel (vs 99.6% for 126/256), and 97% of the time on AMD (vs 97.4% for 120/256).
Color.rgb = ((A - B) * C + D) - (124.0f/256.0f);
#else
Color.rgb = trunc((A - B) * C + D);
#endif
#if PS_CLR_HW == 1
// Replace Af with As so we can do proper compensation for Alpha.
#if PS_BLEND_C == 2
As = Af;
#endif
// Subtract 1 for alpha to compensate for the changed equation,
// if c.rgb > 255.0f then we further need to adjust alpha accordingly,
// we pick the lowest overflow from all colors because it's the safest,
// we divide by 255 the color because we don't know Cd value,
// changed alpha should only be done for hw blend.
float min_color = min(min(Color.r, Color.g), Color.b);
float alpha_compensate = max(1.0f, min_color / 255.0f);
As -= alpha_compensate;
#endif
#else
// Needed for Cd * (As/Ad/F + 1) blending modes
#if PS_CLR_HW == 1 || PS_CLR_HW == 5
+27 -2
View File
@@ -990,7 +990,7 @@ void ps_color_clamp_wrap(inout vec3 C)
#endif
}
void ps_blend(inout vec4 Color, float As)
void ps_blend(inout vec4 Color, inout float As)
{
#if SW_BLEND
@@ -1053,16 +1053,41 @@ void ps_blend(inout vec4 Color, float As)
#endif
// As/Af clamp alpha for Blend mix
#if PS_BLEND_MIX
// We shouldn't clamp blend mix with clr1 as we want alpha higher
#if PS_BLEND_MIX && PS_CLR_HW != 1
C = min(C, 1.0f);
#endif
#if PS_BLEND_A == PS_BLEND_B
Color.rgb = D;
#elif PS_BLEND_MIX
// In blend_mix, HW adds on some alpha factor * dst.
// Truncating here wouldn't quite get the right result because it prevents the <1 bit here from combining with a <1 bit in dst to form a ≥1 amount that pushes over the truncation.
// Instead, apply an offset to convert HW's round to a floor.
// Since alpha is in 1/128 increments, subtracting (0.5 - 0.5/128 == 127/256) would get us what we want if GPUs blended in full precision.
// But they don't. Details here: https://github.com/PCSX2/pcsx2/pull/6809#issuecomment-1211473399
// Based on the scripts at the above link, the ideal choice for Intel GPUs is 126/256, AMD 120/256. Nvidia is a lost cause.
// 124/256 seems like a reasonable compromise, providing the correct answer 99.3% of the time on Intel (vs 99.6% for 126/256), and 97% of the time on AMD (vs 97.4% for 120/256).
Color.rgb = ((A - B) * C + D) - (124.0f/256.0f);
#else
Color.rgb = trunc((A - B) * C + D);
#endif
#if PS_CLR_HW == 1
// Replace Af with As so we can do proper compensation for Alpha.
#if PS_BLEND_C == 2
As = Af;
#endif
// Subtract 1 for alpha to compensate for the changed equation,
// if c.rgb > 255.0f then we further need to adjust alpha accordingly,
// we pick the lowest overflow from all colors because it's the safest,
// we divide by 255 the color because we don't know Cd value,
// changed alpha should only be done for hw blend.
float min_color = min(min(Color.r, Color.g), Color.b);
float alpha_compensate = max(1.0f, min_color / 255.0f);
As -= alpha_compensate;
#endif
#else
#if PS_CLR_HW == 1 || PS_CLR_HW == 5
// Needed for Cd * (As/Ad/F + 1) blending modes
+2
View File
@@ -56,6 +56,8 @@ target_sources(pcsx2-qt PRIVATE
Settings/ControllerGlobalSettingsWidget.cpp
Settings/ControllerGlobalSettingsWidget.h
Settings/ControllerGlobalSettingsWidget.ui
Settings/ControllerMacroEditWidget.ui
Settings/ControllerMacroWidget.ui
Settings/ControllerSettingsDialog.cpp
Settings/ControllerSettingsDialog.h
Settings/ControllerSettingsDialog.ui
-3
View File
@@ -639,13 +639,10 @@ void MainWindow::setStyleFromSettings()
// Alternative dark theme.
qApp->setStyle(QStyleFactory::create("Fusion"));
const QColor lighterGray(75, 75, 75);
const QColor gray(128, 128, 128);
const QColor black(25, 25, 25);
const QColor slate(18, 18, 18);
const QColor rubyish(172, 21, 31);
QPalette darkPalette;
darkPalette.setColor(QPalette::Window, slate);
darkPalette.setColor(QPalette::WindowText, Qt::white);
+224 -34
View File
@@ -26,6 +26,7 @@
#include <QtWidgets/QDoubleSpinBox>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QLineEdit>
#include <QtWidgets/QMenu>
#include <QtWidgets/QSlider>
#include <QtWidgets/QSpinBox>
@@ -41,6 +42,10 @@
namespace SettingWidgetBinder
{
static constexpr const char* NULLABLE_PROPERTY = "SettingWidgetBinder_isNullable";
static constexpr const char* IS_NULL_PROPERTY = "SettingWidgetBinder_isNull";
static constexpr const char* GLOBAL_VALUE_PROPERTY = "SettingWidgetBinder_globalValue";
template <typename T>
struct SettingAccessor
{
@@ -179,7 +184,7 @@ namespace SettingWidgetBinder
}
static void setNullableStringValue(QComboBox* widget, std::optional<QString> value)
{
isNullValue(widget) ? widget->setCurrentIndex(0) : setStringValue(widget, value.value());
value.has_value() ? setStringValue(widget, value.value()) : widget->setCurrentIndex(0);
}
template <typename F>
@@ -286,71 +291,256 @@ namespace SettingWidgetBinder
template <>
struct SettingAccessor<QSpinBox>
{
static bool isNullable(const QSpinBox* widget) { return widget->property(NULLABLE_PROPERTY).toBool(); }
static void updateNullState(QSpinBox* widget, bool isNull)
{
widget->setPrefix(isNull ? qApp->translate("SettingWidgetBinder", "Default: ") : QString());
}
static bool getBoolValue(const QSpinBox* widget) { return widget->value() > 0; }
static void setBoolValue(QSpinBox* widget, bool value) { widget->setValue(value ? 1 : 0); }
static void makeNullableBool(QSpinBox* widget, bool globalSetting) { widget->setEnabled(false); }
static std::optional<bool> getNullableBoolValue(const QSpinBox* widget) { return getBoolValue(widget); }
static void setNullableBoolValue(QSpinBox* widget, std::optional<bool> value) { setBoolValue(widget, value.value_or(false)); }
static void makeNullableBool(QSpinBox* widget, bool globalSetting)
{
widget->setProperty(NULLABLE_PROPERTY, QVariant(true));
widget->setProperty(GLOBAL_VALUE_PROPERTY, QVariant(globalSetting));
}
static std::optional<bool> getNullableBoolValue(const QSpinBox* widget)
{
if (widget->property(IS_NULL_PROPERTY).toBool())
return std::nullopt;
return getBoolValue(widget);
}
static void setNullableBoolValue(QSpinBox* widget, std::optional<bool> value)
{
widget->setProperty(IS_NULL_PROPERTY, QVariant(!value.has_value()));
setBoolValue(widget, value.has_value() ? value.value() : widget->property(GLOBAL_VALUE_PROPERTY).toBool());
updateNullState(widget, !value.has_value());
}
static int getIntValue(const QSpinBox* widget) { return widget->value(); }
static void setIntValue(QSpinBox* widget, int value) { widget->setValue(value); }
static void makeNullableInt(QSpinBox* widget, int globalValue) { widget->setEnabled(false); }
static std::optional<int> getNullableIntValue(const QSpinBox* widget) { return getIntValue(widget); }
static void setNullableIntValue(QSpinBox* widget, std::optional<int> value) { setIntValue(widget, value.value_or(0)); }
static void makeNullableInt(QSpinBox* widget, int globalValue)
{
widget->setProperty(NULLABLE_PROPERTY, QVariant(true));
widget->setProperty(GLOBAL_VALUE_PROPERTY, QVariant(globalValue));
}
static std::optional<int> getNullableIntValue(const QSpinBox* widget)
{
if (widget->property(IS_NULL_PROPERTY).toBool())
return std::nullopt;
return getIntValue(widget);
}
static void setNullableIntValue(QSpinBox* widget, std::optional<int> value)
{
widget->setProperty(IS_NULL_PROPERTY, QVariant(!value.has_value()));
setIntValue(widget, value.has_value() ? value.value() : widget->property(GLOBAL_VALUE_PROPERTY).toInt());
updateNullState(widget, !value.has_value());
}
static float getFloatValue(const QSpinBox* widget) { return static_cast<float>(widget->value()); }
static void setFloatValue(QSpinBox* widget, float value) { widget->setValue(static_cast<int>(value)); }
static void makeNullableFloat(QSpinBox* widget, float globalValue) { widget->setEnabled(false); }
static std::optional<float> getNullableFloatValue(const QSpinBox* widget) { return getFloatValue(widget); }
static void setNullableFloatValue(QSpinBox* widget, std::optional<float> value) { setFloatValue(widget, value.value_or(0.0f)); }
static void makeNullableFloat(QSpinBox* widget, float globalValue)
{
widget->setProperty(NULLABLE_PROPERTY, QVariant(true));
widget->setProperty(GLOBAL_VALUE_PROPERTY, QVariant(globalValue));
}
static std::optional<float> getNullableFloatValue(const QSpinBox* widget)
{
if (widget->property(IS_NULL_PROPERTY).toBool())
return std::nullopt;
return getFloatValue(widget);
}
static void setNullableFloatValue(QSpinBox* widget, std::optional<float> value)
{
widget->setProperty(IS_NULL_PROPERTY, QVariant(!value.has_value()));
setFloatValue(widget, value.has_value() ? value.value() : widget->property(GLOBAL_VALUE_PROPERTY).toFloat());
updateNullState(widget, !value.has_value());
}
static QString getStringValue(const QSpinBox* widget) { return QString::number(widget->value()); }
static void setStringValue(QSpinBox* widget, const QString& value) { widget->setValue(value.toInt()); }
static void makeNullableString(QSpinBox* widget, const QString& globalValue) { widget->setEnabled(false); }
static std::optional<QString> getNullableStringValue(const QSpinBox* widget) { return getStringValue(widget); }
static void setNullableStringValue(QSpinBox* widget, std::optional<QString> value) { setStringValue(widget, value.value_or(QString())); }
static void makeNullableString(QSpinBox* widget, const QString& globalValue)
{
widget->setProperty(NULLABLE_PROPERTY, QVariant(true));
widget->setProperty(GLOBAL_VALUE_PROPERTY, QVariant(globalValue));
}
static std::optional<QString> getNullableStringValue(const QSpinBox* widget)
{
if (widget->property(IS_NULL_PROPERTY).toBool())
return std::nullopt;
return getStringValue(widget);
}
static void setNullableStringValue(QSpinBox* widget, std::optional<QString> value)
{
widget->setProperty(IS_NULL_PROPERTY, QVariant(!value.has_value()));
setStringValue(widget, value.has_value() ? value.value() : widget->property(GLOBAL_VALUE_PROPERTY).toString());
updateNullState(widget, !value.has_value());
}
template <typename F>
static void connectValueChanged(QSpinBox* widget, F func)
{
widget->connect(widget, QOverload<int>::of(&QSpinBox::valueChanged), func);
if (!isNullable(widget))
{
widget->connect(widget, QOverload<int>::of(&QSpinBox::valueChanged), func);
}
else
{
widget->setContextMenuPolicy(Qt::CustomContextMenu);
widget->connect(widget, &QSpinBox::customContextMenuRequested, widget, [widget, func](const QPoint& pt) {
QMenu menu(widget);
widget->connect(menu.addAction(qApp->translate("SettingWidgetBinder", "Reset")), &QAction::triggered, widget,
[widget, func = std::move(func)]() {
const bool old = widget->blockSignals(true);
setNullableIntValue(widget, std::nullopt);
widget->blockSignals(old);
updateNullState(widget, true);
func();
});
menu.exec(widget->mapToGlobal(pt));
});
widget->connect(widget, &QSpinBox::valueChanged, widget, [widget, func = std::move(func)]() {
if (widget->property(IS_NULL_PROPERTY).toBool())
{
widget->setProperty(IS_NULL_PROPERTY, QVariant(false));
updateNullState(widget, false);
}
func();
});
}
}
};
template <>
struct SettingAccessor<QDoubleSpinBox>
{
static bool isNullable(const QDoubleSpinBox* widget) { return widget->property(NULLABLE_PROPERTY).toBool(); }
static void updateNullState(QDoubleSpinBox* widget, bool isNull)
{
widget->setPrefix(isNull ? qApp->translate("SettingWidgetBinder", "Default: ") : QString());
}
static bool getBoolValue(const QDoubleSpinBox* widget) { return widget->value() > 0.0; }
static void setBoolValue(QDoubleSpinBox* widget, bool value) { widget->setValue(value ? 1.0 : 0.0); }
static void makeNullableBool(QDoubleSpinBox* widget, bool globalSetting) { widget->setEnabled(false); }
static std::optional<bool> getNullableBoolValue(const QDoubleSpinBox* widget) { return getBoolValue(widget); }
static void setNullableBoolValue(QDoubleSpinBox* widget, std::optional<bool> value) { setBoolValue(widget, value.value_or(false)); }
static void makeNullableBool(QDoubleSpinBox* widget, bool globalSetting)
{
widget->setProperty(NULLABLE_PROPERTY, QVariant(true));
widget->setProperty(GLOBAL_VALUE_PROPERTY, QVariant(globalSetting));
}
static std::optional<bool> getNullableBoolValue(const QDoubleSpinBox* widget)
{
if (widget->property(IS_NULL_PROPERTY).toBool())
return std::nullopt;
return getBoolValue(widget);
}
static void setNullableBoolValue(QDoubleSpinBox* widget, std::optional<bool> value)
{
widget->setProperty(IS_NULL_PROPERTY, QVariant(!value.has_value()));
setBoolValue(widget, value.has_value() ? value.value() : widget->property(GLOBAL_VALUE_PROPERTY).toBool());
updateNullState(widget, !value.has_value());
}
static int getIntValue(const QDoubleSpinBox* widget) { return static_cast<int>(widget->value()); }
static void setIntValue(QDoubleSpinBox* widget, int value) { widget->setValue(static_cast<double>(value)); }
static void makeNullableInt(QDoubleSpinBox* widget, int globalValue) { widget->setEnabled(false); }
static std::optional<int> getNullableIntValue(const QDoubleSpinBox* widget) { return getIntValue(widget); }
static void setNullableIntValue(QDoubleSpinBox* widget, std::optional<int> value) { setIntValue(widget, value.value_or(0)); }
static void makeNullableInt(QDoubleSpinBox* widget, int globalValue)
{
widget->setProperty(NULLABLE_PROPERTY, QVariant(true));
widget->setProperty(GLOBAL_VALUE_PROPERTY, QVariant(globalValue));
}
static std::optional<int> getNullableIntValue(const QDoubleSpinBox* widget)
{
if (widget->property(IS_NULL_PROPERTY).toBool())
return std::nullopt;
return getIntValue(widget);
}
static void setNullableIntValue(QDoubleSpinBox* widget, std::optional<int> value)
{
widget->setProperty(IS_NULL_PROPERTY, QVariant(!value.has_value()));
setIntValue(widget, value.has_value() ? value.value() : widget->property(GLOBAL_VALUE_PROPERTY).toInt());
updateNullState(widget, !value.has_value());
}
static float getFloatValue(const QDoubleSpinBox* widget) { return static_cast<float>(widget->value()); }
static void setFloatValue(QDoubleSpinBox* widget, float value) { widget->setValue(static_cast<double>(value)); }
static void makeNullableFloat(QDoubleSpinBox* widget, float globalValue) { widget->setEnabled(false); }
static std::optional<float> getNullableFloatValue(const QDoubleSpinBox* widget) { return getFloatValue(widget); }
static void setNullableFloatValue(QDoubleSpinBox* widget, std::optional<float> value) { setFloatValue(widget, value.value_or(0.0f)); }
static void makeNullableFloat(QDoubleSpinBox* widget, float globalValue)
{
widget->setProperty(NULLABLE_PROPERTY, QVariant(true));
widget->setProperty(GLOBAL_VALUE_PROPERTY, QVariant(globalValue));
}
static std::optional<float> getNullableFloatValue(const QDoubleSpinBox* widget)
{
if (widget->property(IS_NULL_PROPERTY).toBool())
return std::nullopt;
return getFloatValue(widget);
}
static void setNullableFloatValue(QDoubleSpinBox* widget, std::optional<float> value)
{
widget->setProperty(IS_NULL_PROPERTY, QVariant(!value.has_value()));
setFloatValue(widget, value.has_value() ? value.value() : widget->property(GLOBAL_VALUE_PROPERTY).toFloat());
updateNullState(widget, !value.has_value());
}
static QString getStringValue(const QDoubleSpinBox* widget) { return QString::number(widget->value()); }
static void setStringValue(QDoubleSpinBox* widget, const QString& value) { widget->setValue(value.toDouble()); }
static void makeNullableString(QDoubleSpinBox* widget, const QString& globalValue) { widget->setEnabled(false); }
static std::optional<QString> getNullableStringValue(const QDoubleSpinBox* widget) { return getStringValue(widget); }
static void makeNullableString(QDoubleSpinBox* widget, const QString& globalValue)
{
widget->setProperty(NULLABLE_PROPERTY, QVariant(true));
widget->setProperty(GLOBAL_VALUE_PROPERTY, QVariant(globalValue));
}
static std::optional<QString> getNullableStringValue(const QDoubleSpinBox* widget)
{
if (widget->property(IS_NULL_PROPERTY).toBool())
return std::nullopt;
return getStringValue(widget);
}
static void setNullableStringValue(QDoubleSpinBox* widget, std::optional<QString> value)
{
setStringValue(widget, value.value_or(QString()));
widget->setProperty(IS_NULL_PROPERTY, QVariant(!value.has_value()));
setStringValue(widget, value.has_value() ? value.value() : widget->property(GLOBAL_VALUE_PROPERTY).toString());
updateNullState(widget, !value.has_value());
}
template <typename F>
static void connectValueChanged(QDoubleSpinBox* widget, F func)
{
widget->connect(widget, QOverload<double>::of(&QDoubleSpinBox::valueChanged), func);
if (!isNullable(widget))
{
widget->connect(widget, QOverload<double>::of(&QDoubleSpinBox::valueChanged), func);
}
else
{
widget->setContextMenuPolicy(Qt::CustomContextMenu);
widget->connect(widget, &QDoubleSpinBox::customContextMenuRequested, widget, [widget, func](const QPoint& pt) {
QMenu menu(widget);
widget->connect(menu.addAction(qApp->translate("SettingWidgetBinder", "Reset")), &QAction::triggered, widget,
[widget, func = std::move(func)]() {
const bool old = widget->blockSignals(true);
setNullableFloatValue(widget, std::nullopt);
widget->blockSignals(old);
updateNullState(widget, true);
func();
});
menu.exec(widget->mapToGlobal(pt));
});
widget->connect(widget, QOverload<double>::of(&QDoubleSpinBox::valueChanged), widget, [widget, func = std::move(func)]() {
if (widget->property(IS_NULL_PROPERTY).toBool())
{
widget->setProperty(IS_NULL_PROPERTY, QVariant(false));
updateNullState(widget, false);
}
func();
});
}
}
};
@@ -776,14 +966,15 @@ namespace SettingWidgetBinder
}
template <typename WidgetType>
static void BindWidgetToFolderSetting(SettingsInterface* sif, WidgetType* widget,
QAbstractButton* browse_button, QAbstractButton* open_button, QAbstractButton* reset_button,
std::string section, std::string key, std::string default_value)
static void BindWidgetToFolderSetting(SettingsInterface* sif, WidgetType* widget, QAbstractButton* browse_button, QAbstractButton* open_button,
QAbstractButton* reset_button, std::string section, std::string key, std::string default_value)
{
using Accessor = SettingAccessor<WidgetType>;
std::string current_path(Host::GetBaseStringSettingValue(section.c_str(), key.c_str(), default_value.c_str()));
if (!Path::IsAbsolute(current_path))
if (current_path.empty())
current_path = default_value;
else if (!Path::IsAbsolute(current_path))
current_path = Path::Combine(EmuFolders::DataRoot, current_path);
const QString value(QString::fromStdString(current_path));
@@ -836,9 +1027,8 @@ namespace SettingWidgetBinder
}
if (reset_button)
{
QObject::connect(reset_button, &QAbstractButton::clicked, reset_button, [widget, default_value = std::move(default_value)]() {
Accessor::setStringValue(widget, QString::fromStdString(Path::Combine(EmuFolders::AppRoot, default_value)));
});
QObject::connect(reset_button, &QAbstractButton::clicked, reset_button,
[widget, default_value = std::move(default_value)]() { Accessor::setStringValue(widget, QString::fromStdString(default_value)); });
}
}
} // namespace SettingWidgetBinder
+2 -2
View File
@@ -39,11 +39,11 @@ BIOSSettingsWidget::BIOSSettingsWidget(SettingsDialog* dialog, QWidget* parent)
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.patchRegion, "EmuCore", "PatchBios", false);
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.regionComboBox, "EmuCore", "PatchRegion", BiosZoneStrings, BiosZoneBytes, BiosZoneBytes[0]);
SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.searchDirectory, m_ui.browseSearchDirectory, m_ui.openSearchDirectory,
m_ui.resetSearchDirectory, "Folders", "Bios", "bios");
m_ui.resetSearchDirectory, "Folders", "Bios", Path::Combine(EmuFolders::DataRoot, "bios"));
dialog->registerWidgetHelp(m_ui.patchRegion, tr("Patch Region"), tr("Unchecked"),
tr("Patches the BIOS region byte in ROM. Not recommended unless you really know what you're doing."));
dialog->registerWidgetHelp(m_ui.fastBoot, tr("Fast Boot"), tr("Unchecked"),
dialog->registerWidgetHelp(m_ui.fastBoot, tr("Fast Boot"), tr("Checked"),
tr("Patches the BIOS to skip the console's boot animation."));
refreshList();
+81 -8
View File
@@ -7,13 +7,13 @@
<x>0</x>
<y>0</y>
<width>833</width>
<height>560</height>
<height>617</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0">
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,1">
<property name="leftMargin">
<number>0</number>
</property>
@@ -31,9 +31,73 @@
<property name="title">
<string>Controller Type</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QComboBox" name="controllerType"/>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QComboBox" name="controllerType"/>
</item>
<item>
<widget class="QToolButton" name="bindings">
<property name="text">
<string>Bindings</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset theme="gamepad-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="settings">
<property name="text">
<string>Settings</string>
</property>
<property name="icon">
<iconset theme="checkbox-multiple-blank-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="macros">
<property name="text">
<string>Macros</string>
</property>
<property name="icon">
<iconset theme="flashlight-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="horizontalSpacer">
@@ -51,25 +115,31 @@
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="automaticBinding">
<widget class="QToolButton" name="automaticBinding">
<property name="text">
<string>Automatic binding</string>
<string>Automatic Mapping</string>
</property>
<property name="icon">
<iconset theme="gamepad-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="clearBindings">
<widget class="QToolButton" name="clearBindings">
<property name="text">
<string>Clear Bindings</string>
<string>Clear Mapping</string>
</property>
<property name="icon">
<iconset theme="file-reduce-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
</widget>
</item>
</layout>
@@ -77,6 +147,9 @@
</layout>
</widget>
</item>
<item>
<widget class="QStackedWidget" name="stackedWidget" />
</item>
</layout>
</widget>
<resources>
@@ -415,25 +415,6 @@
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QDoubleSpinBox" name="LargeMotorScale">
<property name="minimumSize">
<size>
<width>80</width>
<height>0</height>
</size>
</property>
<property name="suffix">
<string>x</string>
</property>
<property name="maximum">
<double>3.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@@ -488,74 +469,6 @@
</layout>
</widget>
</item>
<item row="0" column="1">
<widget class="QGroupBox" name="groupBox_25">
<property name="title">
<string>Select</string>
</property>
<layout class="QGridLayout" name="gridLayout_25">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="Select">
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="2">
<widget class="QGroupBox" name="groupBox_26">
<property name="title">
<string>Start</string>
</property>
<layout class="QGridLayout" name="gridLayout_26">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="Start">
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="3">
<widget class="QGroupBox" name="groupBox_23">
<property name="title">
@@ -624,62 +537,6 @@
</layout>
</widget>
</item>
<item row="1" column="1" colspan="2">
<widget class="QGroupBox" name="groupBox_34">
<property name="title">
<string>Pressure Modifier</string>
</property>
<layout class="QGridLayout" name="gridLayout_34">
<item row="0" column="1">
<widget class="QDoubleSpinBox" name="PressureModifier">
<property name="minimumSize">
<size>
<width>80</width>
<height>0</height>
</size>
</property>
<property name="suffix">
<string>x</string>
</property>
<property name="maximum">
<double>1.000000000000000</double>
</property>
<property name="singleStep">
<double>0.010000000000000</double>
</property>
<property name="value">
<double>0.500000000000000</double>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="InputBindingWidget" name="Pressure">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="3">
<widget class="QGroupBox" name="groupBox_24">
<property name="title">
@@ -714,6 +571,74 @@
</layout>
</widget>
</item>
<item row="1" column="2">
<widget class="QGroupBox" name="groupBox_26">
<property name="title">
<string>Start</string>
</property>
<layout class="QGridLayout" name="gridLayout_26">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="Start">
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="1">
<widget class="QGroupBox" name="groupBox_25">
<property name="title">
<string>Select</string>
</property>
<layout class="QGridLayout" name="gridLayout_25">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="InputBindingWidget" name="Select">
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item row="0" column="2" rowspan="3">
@@ -1093,19 +1018,6 @@
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QDoubleSpinBox" name="SmallMotorScale">
<property name="suffix">
<string>x</string>
</property>
<property name="maximum">
<double>3.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@@ -1181,6 +1093,19 @@
</item>
<item row="2" column="1">
<layout class="QGridLayout" name="gridLayout_32">
<item row="2" column="0" colspan="4">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox_27">
<property name="title">
@@ -1221,19 +1146,6 @@
</layout>
</widget>
</item>
<item row="2" column="0" colspan="4">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="3">
<widget class="QGroupBox" name="groupBox_28">
<property name="title">
@@ -1274,59 +1186,63 @@
</layout>
</widget>
</item>
<item row="1" column="2" colspan="2">
<widget class="QGroupBox" name="groupBox_30">
<item row="0" column="1">
<widget class="QGroupBox" name="groupBox_34">
<property name="title">
<string>Analog Sensitivity</string>
<string>Pressure Modifier</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QSlider" name="AxisScale">
<property name="maximum">
<number>200</number>
<layout class="QGridLayout" name="gridLayout_34">
<item row="0" column="1">
<widget class="QDoubleSpinBox" name="PressureModifier">
<property name="minimumSize">
<size>
<width>80</width>
<height>0</height>
</size>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
<property name="suffix">
<string>x</string>
</property>
<property name="maximum">
<double>1.000000000000000</double>
</property>
<property name="singleStep">
<double>0.010000000000000</double>
</property>
<property name="value">
<double>0.500000000000000</double>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="AxisScaleLabel">
<item row="0" column="0">
<widget class="InputBindingWidget" name="Pressure">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>100%</string>
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_33">
<property name="title">
<string>Analog Deadzone</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QSlider" name="Deadzone">
<property name="maximum">
<number>100</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="DeadzoneLabel">
<property name="text">
<string>0%</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="1" colspan="2">
<item row="1" column="1">
<widget class="QGroupBox" name="groupBox_31">
<property name="title">
<string>Analog</string>
@@ -1366,7 +1282,6 @@
</customwidgets>
<resources>
<include location="../resources/resources.qrc"/>
<include location="../resources/resources.qrc"/>
</resources>
<connections/>
</ui>
+470 -19
View File
@@ -15,6 +15,7 @@
#include "PrecompiledHeader.h"
#include <QtWidgets/QInputDialog>
#include <QtWidgets/QMenu>
#include <QtWidgets/QMessageBox>
#include <algorithm>
@@ -45,15 +46,18 @@ ControllerBindingWidget::ControllerBindingWidget(QWidget* parent, ControllerSett
m_ui.controllerType, m_config_section, "Type", PAD::GetDefaultPadType(port));
connect(m_ui.controllerType, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ControllerBindingWidget::onTypeChanged);
connect(m_ui.automaticBinding, &QPushButton::clicked, this, &ControllerBindingWidget::doAutomaticBinding);
connect(m_ui.clearBindings, &QPushButton::clicked, this, &ControllerBindingWidget::doClearBindings);
connect(m_ui.bindings, &QPushButton::clicked, this, &ControllerBindingWidget::onBindingsClicked);
connect(m_ui.settings, &QPushButton::clicked, this, &ControllerBindingWidget::onSettingsClicked);
connect(m_ui.macros, &QPushButton::clicked, this, &ControllerBindingWidget::onMacrosClicked);
connect(m_ui.automaticBinding, &QPushButton::clicked, this, &ControllerBindingWidget::onAutomaticBindingClicked);
connect(m_ui.clearBindings, &QPushButton::clicked, this, &ControllerBindingWidget::onClearBindingsClicked);
}
ControllerBindingWidget::~ControllerBindingWidget() = default;
QIcon ControllerBindingWidget::getIcon() const
{
return m_current_widget->getIcon();
return m_bindings_widget->getIcon();
}
void ControllerBindingWidget::populateControllerTypes()
@@ -64,36 +68,101 @@ void ControllerBindingWidget::populateControllerTypes()
void ControllerBindingWidget::onTypeChanged()
{
const bool is_initializing = (m_current_widget == nullptr);
const bool is_initializing = (m_ui.stackedWidget->count() == 0);
m_controller_type = m_dialog->getStringValue(m_config_section.c_str(), "Type", PAD::GetDefaultPadType(m_port_number));
if (!is_initializing)
if (m_bindings_widget)
{
m_ui.verticalLayout->removeWidget(m_current_widget);
delete m_current_widget;
m_current_widget = nullptr;
m_ui.stackedWidget->removeWidget(m_bindings_widget);
delete m_bindings_widget;
m_bindings_widget = nullptr;
}
if (m_settings_widget)
{
m_ui.stackedWidget->removeWidget(m_settings_widget);
delete m_settings_widget;
m_settings_widget = nullptr;
}
if (m_macros_widget)
{
m_ui.stackedWidget->removeWidget(m_macros_widget);
delete m_macros_widget;
m_macros_widget = nullptr;
}
const int index = m_ui.controllerType->findData(QString::fromStdString(m_controller_type));
if (index >= 0 && index != m_ui.controllerType->currentIndex())
{
QSignalBlocker sb(m_ui.controllerType);
m_ui.controllerType->setCurrentIndex(index);
}
const PAD::ControllerInfo* cinfo = PAD::GetControllerInfo(m_controller_type);
const bool has_settings = (cinfo && cinfo->num_settings > 0);
const bool has_macros = (cinfo && cinfo->num_bindings > 0);
m_ui.settings->setEnabled(has_settings);
m_ui.macros->setEnabled(has_macros);
if (m_controller_type == "DualShock2")
m_current_widget = ControllerBindingWidget_DualShock2::createInstance(this);
m_bindings_widget = ControllerBindingWidget_DualShock2::createInstance(this);
else
m_current_widget = new ControllerBindingWidget_Base(this);
m_bindings_widget = new ControllerBindingWidget_Base(this);
m_ui.verticalLayout->addWidget(m_current_widget, 1);
m_ui.stackedWidget->addWidget(m_bindings_widget);
m_ui.stackedWidget->setCurrentWidget(m_bindings_widget);
if (has_settings)
{
m_settings_widget = new ControllerCustomSettingsWidget(this, m_ui.stackedWidget);
m_ui.stackedWidget->addWidget(m_settings_widget);
}
if (has_macros)
{
m_macros_widget = new ControllerMacroWidget(this);
m_ui.stackedWidget->addWidget(m_macros_widget);
}
updateHeaderToolButtons();
// no need to do this on first init, only changes
if (!is_initializing)
m_dialog->updateListDescription(m_port_number, this);
}
void ControllerBindingWidget::doAutomaticBinding()
void ControllerBindingWidget::updateHeaderToolButtons()
{
const QWidget* current_widget = m_ui.stackedWidget->currentWidget();
const QSignalBlocker bindings_sb(m_ui.bindings);
const QSignalBlocker settings_sb(m_ui.settings);
const QSignalBlocker macros_sb(m_ui.macros);
const bool is_bindings = (current_widget == m_bindings_widget);
m_ui.bindings->setChecked(is_bindings);
m_ui.automaticBinding->setEnabled(is_bindings);
m_ui.clearBindings->setEnabled(is_bindings);
m_ui.macros->setChecked(current_widget == m_macros_widget);
m_ui.settings->setChecked((current_widget == m_settings_widget));
}
void ControllerBindingWidget::onBindingsClicked()
{
m_ui.stackedWidget->setCurrentWidget(m_bindings_widget);
updateHeaderToolButtons();
}
void ControllerBindingWidget::onSettingsClicked()
{
if (!m_settings_widget)
return;
m_ui.stackedWidget->setCurrentWidget(m_settings_widget);
updateHeaderToolButtons();
}
void ControllerBindingWidget::onMacrosClicked()
{
if (!m_macros_widget)
return;
m_ui.stackedWidget->setCurrentWidget(m_macros_widget);
updateHeaderToolButtons();
}
void ControllerBindingWidget::onAutomaticBindingClicked()
{
QMenu menu(this);
bool added = false;
@@ -118,7 +187,7 @@ void ControllerBindingWidget::doAutomaticBinding()
menu.exec(QCursor::pos());
}
void ControllerBindingWidget::doClearBindings()
void ControllerBindingWidget::onClearBindingsClicked()
{
if (QMessageBox::question(QtUtils::GetRootWidget(this), tr("Clear Bindings"),
tr("Are you sure you want to clear all bindings for this controller? This action cannot be undone.")) != QMessageBox::Yes)
@@ -174,6 +243,388 @@ void ControllerBindingWidget::saveAndRefresh()
g_emu_thread->applySettings();
}
//////////////////////////////////////////////////////////////////////////
ControllerMacroWidget::ControllerMacroWidget(ControllerBindingWidget* parent)
: QWidget(parent)
{
m_ui.setupUi(this);
setWindowTitle(tr("Controller Port %1 Macros").arg(parent->getPortNumber() + 1u));
createWidgets(parent);
}
ControllerMacroWidget::~ControllerMacroWidget() = default;
void ControllerMacroWidget::updateListItem(u32 index)
{
m_ui.portList->item(static_cast<int>(index))
->setText(tr("Macro %1\n%2").arg(index + 1).arg(m_macros[index]->getSummary()));
}
void ControllerMacroWidget::createWidgets(ControllerBindingWidget* parent)
{
for (u32 i = 0; i < NUM_MACROS; i++)
{
m_macros[i] = new ControllerMacroEditWidget(this, parent, i);
m_ui.container->addWidget(m_macros[i]);
QListWidgetItem* item = new QListWidgetItem();
item->setIcon(QIcon::fromTheme(QStringLiteral("flashlight-line")));
m_ui.portList->addItem(item);
updateListItem(i);
}
m_ui.portList->setCurrentRow(0);
m_ui.container->setCurrentIndex(0);
connect(m_ui.portList, &QListWidget::currentRowChanged, m_ui.container, &QStackedWidget::setCurrentIndex);
}
//////////////////////////////////////////////////////////////////////////
ControllerMacroEditWidget::ControllerMacroEditWidget(ControllerMacroWidget* parent, ControllerBindingWidget* bwidget,
u32 index)
: QWidget(parent)
, m_parent(parent)
, m_bwidget(bwidget)
, m_index(index)
{
m_ui.setupUi(this);
ControllerSettingsDialog* dialog = m_bwidget->getDialog();
const std::string& section = m_bwidget->getConfigSection();
const PAD::ControllerInfo* cinfo = PAD::GetControllerInfo(m_bwidget->getControllerType());
if (!cinfo)
{
// Shouldn't ever happen.
return;
}
// load binds (single string joined by &)
const std::string binds_string(
dialog->getStringValue(section.c_str(), fmt::format("Macro{}Binds", index + 1u).c_str(), ""));
const std::vector<std::string_view> buttons_split(StringUtil::SplitString(binds_string, '&', true));
for (const std::string_view& button : buttons_split)
{
for (u32 i = 0; i < cinfo->num_bindings; i++)
{
if (button == cinfo->bindings[i].name)
{
m_binds.push_back(&cinfo->bindings[i]);
break;
}
}
}
// populate list view
for (u32 i = 0; i < cinfo->num_bindings; i++)
{
const PAD::ControllerBindingInfo& bi = cinfo->bindings[i];
if (bi.type == PAD::ControllerBindingType::Motor)
continue;
QListWidgetItem* item = new QListWidgetItem();
item->setText(QString::fromUtf8(bi.display_name));
item->setCheckState((std::find(m_binds.begin(), m_binds.end(), &bi) != m_binds.end()) ? Qt::Checked :
Qt::Unchecked);
m_ui.bindList->addItem(item);
}
m_frequency = dialog->getIntValue(section.c_str(), fmt::format("Macro{}Frequency", index + 1u).c_str(), 0);
updateFrequencyText();
m_ui.trigger->initialize(dialog->getProfileSettingsInterface(), section, fmt::format("Macro{}", index + 1u));
connect(m_ui.increaseFrequency, &QAbstractButton::clicked, this, [this]() { modFrequency(1); });
connect(m_ui.decreateFrequency, &QAbstractButton::clicked, this, [this]() { modFrequency(-1); });
connect(m_ui.setFrequency, &QAbstractButton::clicked, this, &ControllerMacroEditWidget::onSetFrequencyClicked);
connect(m_ui.bindList, &QListWidget::itemChanged, this, &ControllerMacroEditWidget::updateBinds);
}
ControllerMacroEditWidget::~ControllerMacroEditWidget() = default;
QString ControllerMacroEditWidget::getSummary() const
{
QString str;
for (const PAD::ControllerBindingInfo* bi : m_binds)
{
if (!str.isEmpty())
str += static_cast<QChar>('/');
str += QString::fromUtf8(bi->name);
}
return str.isEmpty() ? tr("Not Configured") : str;
}
void ControllerMacroEditWidget::onSetFrequencyClicked()
{
bool okay;
int new_freq = QInputDialog::getInt(this, tr("Set Frequency"), tr("Frequency: "), static_cast<int>(m_frequency), 0,
std::numeric_limits<int>::max(), 1, &okay);
if (!okay)
return;
m_frequency = static_cast<u32>(new_freq);
updateFrequency();
}
void ControllerMacroEditWidget::modFrequency(s32 delta)
{
if (delta < 0 && m_frequency == 0)
return;
m_frequency = static_cast<u32>(static_cast<s32>(m_frequency) + delta);
updateFrequency();
}
void ControllerMacroEditWidget::updateFrequency()
{
m_bwidget->getDialog()->setIntValue(m_bwidget->getConfigSection().c_str(),
fmt::format("Macro{}Frequency", m_index + 1u).c_str(),
static_cast<s32>(m_frequency));
updateFrequencyText();
}
void ControllerMacroEditWidget::updateFrequencyText()
{
if (m_frequency == 0)
m_ui.frequencyText->setText(tr("Macro will not repeat."));
else
m_ui.frequencyText->setText(tr("Macro will toggle buttons every %1 frames.").arg(m_frequency));
}
void ControllerMacroEditWidget::updateBinds()
{
ControllerSettingsDialog* dialog = m_bwidget->getDialog();
const PAD::ControllerInfo* cinfo = PAD::GetControllerInfo(m_bwidget->getControllerType());
if (!cinfo)
return;
std::vector<const PAD::ControllerBindingInfo*> new_binds;
for (u32 i = 0, bind_index = 0; i < cinfo->num_bindings; i++)
{
const PAD::ControllerBindingInfo& bi = cinfo->bindings[i];
if (bi.type == PAD::ControllerBindingType::Motor)
continue;
const QListWidgetItem* item = m_ui.bindList->item(static_cast<int>(bind_index));
bind_index++;
if (!item)
{
// shouldn't happen
continue;
}
if (item->checkState() == Qt::Checked)
new_binds.push_back(&bi);
}
if (m_binds == new_binds)
return;
m_binds = std::move(new_binds);
std::string binds_string;
for (const PAD::ControllerBindingInfo* bi : m_binds)
{
if (!binds_string.empty())
binds_string.append(" & ");
binds_string.append(bi->name);
}
const std::string& section = m_bwidget->getConfigSection();
const std::string key(fmt::format("Macro{}Binds", m_index + 1u));
if (binds_string.empty())
dialog->clearSettingValue(section.c_str(), key.c_str());
else
dialog->setStringValue(section.c_str(), key.c_str(), binds_string.c_str());
m_parent->updateListItem(m_index);
}
//////////////////////////////////////////////////////////////////////////
ControllerCustomSettingsWidget::ControllerCustomSettingsWidget(ControllerBindingWidget* parent, QWidget* parent_widget)
: QWidget(parent_widget)
, m_parent(parent)
{
const PAD::ControllerInfo* cinfo = PAD::GetControllerInfo(parent->getControllerType());
if (!cinfo || cinfo->num_settings == 0)
return;
QGroupBox* gbox = new QGroupBox(tr("%1 Settings").arg(qApp->translate("PAD", cinfo->display_name)), this);
QGridLayout* gbox_layout = new QGridLayout(gbox);
createSettingWidgets(parent, gbox, gbox_layout, cinfo);
QVBoxLayout* layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(gbox);
QHBoxLayout* bottom_hlayout = new QHBoxLayout();
QPushButton* restore_defaults = new QPushButton(tr("Restore Default Settings"), this);
restore_defaults->setIcon(QIcon::fromTheme(QStringLiteral("restart-line")));
connect(restore_defaults, &QPushButton::clicked, this, &ControllerCustomSettingsWidget::restoreDefaults);
bottom_hlayout->addStretch(1);
bottom_hlayout->addWidget(restore_defaults);
layout->addLayout(bottom_hlayout);
layout->addStretch(1);
}
ControllerCustomSettingsWidget::~ControllerCustomSettingsWidget() = default;
void ControllerCustomSettingsWidget::createSettingWidgets(ControllerBindingWidget* parent, QWidget* widget_parent,
QGridLayout* layout, const PAD::ControllerInfo* cinfo)
{
const std::string& section = parent->getConfigSection();
SettingsInterface* sif = parent->getDialog()->getProfileSettingsInterface();
int current_row = 0;
for (u32 i = 0; i < cinfo->num_settings; i++)
{
const PAD::ControllerSettingInfo& si = cinfo->settings[i];
std::string key_name = si.key;
switch (si.type)
{
case PAD::ControllerSettingInfo::Type::Boolean:
{
QCheckBox* cb = new QCheckBox(qApp->translate(cinfo->name, si.visible_name), widget_parent);
cb->setObjectName(QString::fromUtf8(si.key));
ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(sif, cb, section, std::move(key_name),
si.BooleanDefaultValue());
layout->addWidget(cb, current_row, 0, 1, 4);
current_row++;
}
break;
case PAD::ControllerSettingInfo::Type::Integer:
{
QSpinBox* sb = new QSpinBox(widget_parent);
sb->setObjectName(QString::fromUtf8(si.key));
sb->setMinimum(si.IntegerMinValue());
sb->setMaximum(si.IntegerMaxValue());
sb->setSingleStep(si.IntegerStepValue());
SettingWidgetBinder::BindWidgetToIntSetting(sif, sb, section, std::move(key_name), si.IntegerDefaultValue());
layout->addWidget(new QLabel(qApp->translate(cinfo->name, si.visible_name), widget_parent), current_row, 0);
layout->addWidget(sb, current_row, 1, 1, 3);
current_row++;
}
break;
case PAD::ControllerSettingInfo::Type::Float:
{
QDoubleSpinBox* sb = new QDoubleSpinBox(widget_parent);
sb->setObjectName(QString::fromUtf8(si.key));
sb->setMinimum(si.FloatMinValue());
sb->setMaximum(si.FloatMaxValue());
sb->setSingleStep(si.FloatStepValue());
SettingWidgetBinder::BindWidgetToFloatSetting(sif, sb, section, std::move(key_name), si.FloatDefaultValue());
layout->addWidget(new QLabel(qApp->translate(cinfo->name, si.visible_name), widget_parent), current_row, 0);
layout->addWidget(sb, current_row, 1, 1, 3);
current_row++;
}
break;
case PAD::ControllerSettingInfo::Type::String:
{
QLineEdit* le = new QLineEdit(widget_parent);
le->setObjectName(QString::fromUtf8(si.key));
SettingWidgetBinder::BindWidgetToStringSetting(sif, le, section, std::move(key_name), si.StringDefaultValue());
layout->addWidget(new QLabel(qApp->translate(cinfo->name, si.visible_name), widget_parent), current_row, 0);
layout->addWidget(le, current_row, 1, 1, 3);
current_row++;
}
break;
case PAD::ControllerSettingInfo::Type::Path:
{
QLineEdit* le = new QLineEdit(widget_parent);
le->setObjectName(QString::fromUtf8(si.key));
QPushButton* browse_button = new QPushButton(tr("Browse..."), widget_parent);
SettingWidgetBinder::BindWidgetToStringSetting(sif, le, section, std::move(key_name), si.StringDefaultValue());
connect(browse_button, &QPushButton::clicked, [this, le]() {
QString path = QFileDialog::getOpenFileName(this, tr("Select File"));
if (!path.isEmpty())
le->setText(path);
});
QHBoxLayout* hbox = new QHBoxLayout();
hbox->addWidget(le, 1);
hbox->addWidget(browse_button);
layout->addWidget(new QLabel(qApp->translate(cinfo->name, si.visible_name), widget_parent), current_row, 0);
layout->addLayout(hbox, current_row, 1, 1, 3);
current_row++;
}
break;
}
QLabel* label = new QLabel(si.description ? qApp->translate(cinfo->name, si.description) : QString(), widget_parent);
label->setWordWrap(true);
layout->addWidget(label, current_row++, 0, 1, 4);
layout->addItem(new QSpacerItem(1, 10, QSizePolicy::Minimum, QSizePolicy::Fixed), current_row++, 0, 1, 4);
}
}
void ControllerCustomSettingsWidget::restoreDefaults()
{
const PAD::ControllerInfo* cinfo = PAD::GetControllerInfo(m_parent->getControllerType());
if (!cinfo || cinfo->num_settings == 0)
return;
for (u32 i = 0; i < cinfo->num_settings; i++)
{
const PAD::ControllerSettingInfo& si = cinfo->settings[i];
const QString key(QString::fromStdString(si.key));
switch (si.type)
{
case PAD::ControllerSettingInfo::Type::Boolean:
{
QCheckBox* widget = findChild<QCheckBox*>(QString::fromStdString(si.key));
if (widget)
widget->setChecked(si.BooleanDefaultValue());
}
break;
case PAD::ControllerSettingInfo::Type::Integer:
{
QSpinBox* widget = findChild<QSpinBox*>(QString::fromStdString(si.key));
if (widget)
widget->setValue(si.IntegerDefaultValue());
}
break;
case PAD::ControllerSettingInfo::Type::Float:
{
QDoubleSpinBox* widget = findChild<QDoubleSpinBox*>(QString::fromStdString(si.key));
if (widget)
widget->setValue(si.FloatDefaultValue());
}
break;
case PAD::ControllerSettingInfo::Type::String:
{
QLineEdit* widget = findChild<QLineEdit*>(QString::fromStdString(si.key));
if (widget)
widget->setText(QString::fromUtf8(si.StringDefaultValue()));
}
break;
case PAD::ControllerSettingInfo::Type::Path:
{
QLineEdit* widget = findChild<QLineEdit*>(QString::fromStdString(si.key));
if (widget)
widget->setText(QString::fromUtf8(si.StringDefaultValue()));
}
break;
}
}
}
//////////////////////////////////////////////////////////////////////////
ControllerBindingWidget_Base::ControllerBindingWidget_Base(ControllerBindingWidget* parent)
+92 -3
View File
@@ -15,13 +15,20 @@
#pragma once
#include "PAD/Host/PAD.h"
#include <QtWidgets/QWidget>
#include "ui_ControllerBindingWidget.h"
#include "ui_ControllerBindingWidget_DualShock2.h"
#include "ui_ControllerMacroWidget.h"
#include "ui_ControllerMacroEditWidget.h"
class InputBindingWidget;
class ControllerSettingsDialog;
class ControllerCustomSettingsWidget;
class ControllerMacroWidget;
class ControllerMacroEditWidget;
class ControllerBindingWidget_Base;
class ControllerBindingWidget final : public QWidget
@@ -41,11 +48,15 @@ public:
private Q_SLOTS:
void onTypeChanged();
void doAutomaticBinding();
void doClearBindings();
void onAutomaticBindingClicked();
void onClearBindingsClicked();
void onBindingsClicked();
void onSettingsClicked();
void onMacrosClicked();
private:
void populateControllerTypes();
void updateHeaderToolButtons();
void doDeviceAutomaticBinding(const QString& device);
void saveAndRefresh();
@@ -57,9 +68,87 @@ private:
std::string m_controller_type;
u32 m_port_number;
ControllerBindingWidget_Base* m_current_widget = nullptr;
ControllerBindingWidget_Base* m_bindings_widget = nullptr;
ControllerCustomSettingsWidget* m_settings_widget = nullptr;
ControllerMacroWidget* m_macros_widget = nullptr;
};
//////////////////////////////////////////////////////////////////////////
class ControllerMacroWidget : public QWidget
{
Q_OBJECT
public:
ControllerMacroWidget(ControllerBindingWidget* parent);
~ControllerMacroWidget();
void updateListItem(u32 index);
private:
static constexpr u32 NUM_MACROS = PAD::NUM_MACRO_BUTTONS_PER_CONTROLLER;
void createWidgets(ControllerBindingWidget* parent);
Ui::ControllerMacroWidget m_ui;
ControllerSettingsDialog* m_dialog;
std::array<ControllerMacroEditWidget*, NUM_MACROS> m_macros;
};
//////////////////////////////////////////////////////////////////////////
class ControllerMacroEditWidget : public QWidget
{
Q_OBJECT
public:
ControllerMacroEditWidget(ControllerMacroWidget* parent, ControllerBindingWidget* bwidget, u32 index);
~ControllerMacroEditWidget();
QString getSummary() const;
private Q_SLOTS:
void onSetFrequencyClicked();
void updateBinds();
private:
void modFrequency(s32 delta);
void updateFrequency();
void updateFrequencyText();
Ui::ControllerMacroEditWidget m_ui;
ControllerMacroWidget* m_parent;
ControllerBindingWidget* m_bwidget;
u32 m_index;
std::vector<const PAD::ControllerBindingInfo*> m_binds;
u32 m_frequency = 0;
};
//////////////////////////////////////////////////////////////////////////
class ControllerCustomSettingsWidget : public QWidget
{
Q_OBJECT
public:
ControllerCustomSettingsWidget(ControllerBindingWidget* parent, QWidget* parent_widget);
~ControllerCustomSettingsWidget();
void createSettingWidgets(ControllerBindingWidget* parent, QWidget* widget_parent, QGridLayout* layout, const PAD::ControllerInfo* cinfo);
private Q_SLOTS:
void restoreDefaults();
private:
ControllerBindingWidget* m_parent;
};
//////////////////////////////////////////////////////////////////////////
class ControllerBindingWidget_Base : public QWidget
{
Q_OBJECT
@@ -0,0 +1,157 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ControllerMacroEditWidget</class>
<widget class="QWidget" name="ControllerMacroEditWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>595</width>
<height>473</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Binds/Buttons</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Select the buttons which you want to trigger with this macro. All buttons are activated concurrently.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QListWidget" name="bindList"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Trigger</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label">
<property name="text">
<string>Select the trigger to activate this macro. This can be a single button, or combination of buttons (chord). Shift-click for multiple triggers.</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="InputBindingWidget" name="trigger">
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Frequency</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0,0,0">
<item>
<widget class="QLabel" name="frequencyText">
<property name="text">
<string>Macro will toggle every N frames.</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="setFrequency">
<property name="text">
<string>Set...</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="increaseFrequency">
<property name="minimumSize">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
<property name="arrowType">
<enum>Qt::UpArrow</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="decreateFrequency">
<property name="minimumSize">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
<property name="arrowType">
<enum>Qt::DownArrow</enum>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>InputBindingWidget</class>
<extends>QPushButton</extends>
<header>Settings/InputBindingWidget.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ControllerMacroWidget</class>
<widget class="QWidget" name="ControllerMacroWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>799</width>
<height>493</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QListWidget" name="portList">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>150</width>
<height>16777215</height>
</size>
</property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QStackedWidget" name="container"/>
</item>
</layout>
</widget>
<resources>
<include location="../resources/resources.qrc"/>
</resources>
<connections/>
</ui>
+24 -2
View File
@@ -266,6 +266,14 @@ bool ControllerSettingsDialog::getBoolValue(const char* section, const char* key
return Host::GetBaseBoolSettingValue(section, key, default_value);
}
s32 ControllerSettingsDialog::getIntValue(const char* section, const char* key, s32 default_value) const
{
if (m_profile_interface)
return m_profile_interface->GetIntValue(section, key, default_value);
else
return Host::GetBaseIntSettingValue(section, key, default_value);
}
std::string ControllerSettingsDialog::getStringValue(const char* section, const char* key, const char* default_value) const
{
std::string value;
@@ -291,6 +299,21 @@ void ControllerSettingsDialog::setBoolValue(const char* section, const char* key
}
}
void ControllerSettingsDialog::setIntValue(const char* section, const char* key, s32 value)
{
if (m_profile_interface)
{
m_profile_interface->SetIntValue(section, key, value);
m_profile_interface->Save();
g_emu_thread->reloadGameSettings();
}
else
{
QtHost::SetBaseIntSettingValue(section, key, value);
g_emu_thread->applySettings();
}
}
void ControllerSettingsDialog::setStringValue(const char* section, const char* key, const char* value)
{
if (m_profile_interface)
@@ -301,7 +324,7 @@ void ControllerSettingsDialog::setStringValue(const char* section, const char* k
}
else
{
QtHost::SetBaseStringSettingValue(key, section, value);
QtHost::SetBaseStringSettingValue(section, key, value);
g_emu_thread->applySettings();
}
}
@@ -406,7 +429,6 @@ void ControllerSettingsDialog::updateListDescription(u32 global_slot, Controller
const QVariant data(item->data(Qt::UserRole));
if (data.type() == QVariant::UInt && data.toUInt() == global_slot)
{
const bool is_mtap_port = sioPadIsMultitapSlot(global_slot);
const auto [port, slot] = sioConvertPadToPortAndSlot(global_slot);
const bool mtap_enabled = getBoolValue("Pad", (port == 0) ? "MultitapPort1" : "MultitapPort2", false);
@@ -64,8 +64,10 @@ public:
// Helper functions for updating setting values globally or in the profile.
bool getBoolValue(const char* section, const char* key, bool default_value) const;
s32 getIntValue(const char* section, const char* key, s32 default_value) const;
std::string getStringValue(const char* section, const char* key, const char* default_value) const;
void setBoolValue(const char* section, const char* key, bool value);
void setIntValue(const char* section, const char* key, s32 value);
void setStringValue(const char* section, const char* key, const char* value);
void clearSettingValue(const char* section, const char* key);
@@ -9,8 +9,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>1276</width>
<height>672</height>
<width>1300</width>
<height>680</height>
</rect>
</property>
<property name="sizePolicy">
+4 -4
View File
@@ -29,10 +29,10 @@ FolderSettingsWidget::FolderSettingsWidget(SettingsDialog* dialog, QWidget* pare
m_ui.setupUi(this);
SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.cache, m_ui.cacheBrowse, m_ui.cacheOpen, m_ui.cacheReset, "Folders", "Cache", "cache");
SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.covers, m_ui.coversBrowse, m_ui.coversOpen, m_ui.coversReset, "Folders", "Covers", "covers");
SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.snapshots, m_ui.snapshotsBrowse, m_ui.snapshotsOpen, m_ui.snapshotsReset, "Folders", "Snapshots", "snaps");
SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.saveStates, m_ui.saveStatesBrowse, m_ui.saveStatesOpen, m_ui.saveStatesReset, "Folders", "SaveStates", "sstates");
SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.cache, m_ui.cacheBrowse, m_ui.cacheOpen, m_ui.cacheReset, "Folders", "Cache", Path::Combine(EmuFolders::DataRoot, "cache"));
SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.covers, m_ui.coversBrowse, m_ui.coversOpen, m_ui.coversReset, "Folders", "Covers", Path::Combine(EmuFolders::DataRoot, "covers"));
SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.snapshots, m_ui.snapshotsBrowse, m_ui.snapshotsOpen, m_ui.snapshotsReset, "Folders", "Snapshots", Path::Combine(EmuFolders::DataRoot, "snaps"));
SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.saveStates, m_ui.saveStatesBrowse, m_ui.saveStatesOpen, m_ui.saveStatesReset, "Folders", "SaveStates", Path::Combine(EmuFolders::DataRoot, "sstates"));
}
FolderSettingsWidget::~FolderSettingsWidget() = default;
+32 -15
View File
@@ -186,7 +186,6 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget*
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.crcFixLevel, "EmuCore/GS", "crc_hack_level", static_cast<int>(CRCHackLevel::Automatic), -1);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.blending, "EmuCore/GS", "accurate_blending_unit", static_cast<int>(AccBlendLevel::Basic));
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.accurateDATE, "EmuCore/GS", "accurate_date", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.conservativeBufferAllocation, "EmuCore/GS", "conservative_framebuffer", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.gpuPaletteConversion, "EmuCore/GS", "paltex", false);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.texturePreloading, "EmuCore/GS", "texture_preloading",
static_cast<int>(TexturePreloadingLevel::Off));
@@ -233,7 +232,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget*
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.loadTextureReplacementsAsync, "EmuCore/GS", "LoadTextureReplacementsAsync", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.precacheTextureReplacements, "EmuCore/GS", "PrecacheTextureReplacements", false);
SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.texturesDirectory, m_ui.texturesBrowse, m_ui.texturesOpen, m_ui.texturesReset,
"Folders", "Textures", "textures");
"Folders", "Textures", Path::Combine(EmuFolders::DataRoot, "textures"));
//////////////////////////////////////////////////////////////////////////
// Advanced Settings
@@ -243,7 +242,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget*
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.skipPresentingDuplicateFrames, "EmuCore/GS", "SkipDuplicateFrames", false);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.overrideTextureBarriers, "EmuCore/GS", "OverrideTextureBarriers", -1, -1);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.overrideGeometryShader, "EmuCore/GS", "OverrideGeometryShaders", -1, -1);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.gsDumpCompression, "EmuCore/GS", "GSDumpCompression", static_cast<int>(GSDumpCompressionMethod::Uncompressed));
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.gsDumpCompression, "EmuCore/GS", "GSDumpCompression", static_cast<int>(GSDumpCompressionMethod::LZMA));
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.disableFramebufferFetch, "EmuCore/GS", "DisableFramebufferFetch", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.disableDualSource, "EmuCore/GS", "DisableDualSourceBlend", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.disableHardwareReadbacks, "EmuCore/GS", "HWDisableReadbacks", false);
@@ -251,6 +250,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget*
//////////////////////////////////////////////////////////////////////////
// SW Settings
//////////////////////////////////////////////////////////////////////////
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.swTextureFiltering, "EmuCore/GS", "filter", static_cast<int>(BiFiltering::PS2));
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.extraSWThreads, "EmuCore/GS", "extrathreads", 2);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.swAutoFlush, "EmuCore/GS", "autoflush_sw", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.swMipmap, "EmuCore/GS", "mipmap", true);
@@ -286,18 +286,25 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget*
connect(m_ui.renderer, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &GraphicsSettingsWidget::onRendererChanged);
connect(m_ui.enableHWFixes, &QCheckBox::stateChanged, this, &GraphicsSettingsWidget::onEnableHardwareFixesChanged);
connect(m_ui.textureFiltering, &QComboBox::currentIndexChanged, this, &GraphicsSettingsWidget::onTextureFilteringChange);
connect(m_ui.swTextureFiltering, &QComboBox::currentIndexChanged, this, &GraphicsSettingsWidget::onSWTextureFilteringChange);
updateRendererDependentOptions();
// only allow disabling readbacks for per-game settings, it's too dangerous
#ifndef PCSX2_DEVBUILD
m_ui.disableHardwareReadbacks->setEnabled(m_dialog->isPerGameSettings());
// allow Texture Offset for per-game settings only
m_ui.textureOffsetX->setEnabled(m_dialog->isPerGameSettings());
m_ui.textureOffsetY->setEnabled(m_dialog->isPerGameSettings());
// allow Skipdraw Range for per-game settings only
m_ui.skipDrawStart->setEnabled(m_dialog->isPerGameSettings());
m_ui.skipDrawEnd->setEnabled(m_dialog->isPerGameSettings());
// Remove texture offset and skipdraw range for global settings.
if (!m_dialog->isPerGameSettings())
{
m_ui.upscalingFixesLayout->removeRow(2);
m_ui.hardwareFixesLayout->removeRow(2);
m_ui.skipDrawStart = nullptr;
m_ui.skipDrawEnd = nullptr;
m_ui.textureOffsetX = nullptr;
m_ui.textureOffsetY = nullptr;
}
#endif
// Display tab
{
@@ -347,7 +354,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget*
"The higher the setting, the more blending is emulated in the shader accurately, and the higher the speed penalty will be. "
"Do note that Direct3D's blending is reduced in capability compared to OpenGL/Vulkan"));
dialog->registerWidgetHelp(m_ui.texturePreloading, tr("Texture Preloading"), tr(""),
dialog->registerWidgetHelp(m_ui.texturePreloading, tr("Texture Preloading"), tr("Full (Hash Cache)"),
tr("Uploads entire textures at once instead of small pieces, avoiding redundant uploads when possible. "
"Improves performance in most games, but can make a small selection slower."));
@@ -355,10 +362,6 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget*
tr("Implement a more accurate algorithm to compute GS destination alpha testing. "
"It improves shadow and transparency rendering."));
dialog->registerWidgetHelp(m_ui.conservativeBufferAllocation, tr("Conservative Buffer Allocation"), tr("Checked"),
tr("Disabled: Reserves a larger framebuffer to prevent FMV flickers. Increases GPU/memory requirements. "
"Disabling this can amplify stuttering due to low RAM/VRAM."));
dialog->registerWidgetHelp(m_ui.gpuPaletteConversion, tr("GPU Palette Conversion"), tr("Unchecked"),
tr("When enabled GPU converts colormap-textures, otherwise the CPU will. "
"It is a trade-off between GPU and CPU."));
@@ -478,6 +481,20 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget*
GraphicsSettingsWidget::~GraphicsSettingsWidget() = default;
void GraphicsSettingsWidget::onTextureFilteringChange()
{
const QSignalBlocker block(m_ui.swTextureFiltering);
m_ui.swTextureFiltering->setCurrentIndex(m_ui.textureFiltering->currentIndex());
}
void GraphicsSettingsWidget::onSWTextureFilteringChange()
{
const QSignalBlocker block(m_ui.textureFiltering);
m_ui.textureFiltering->setCurrentIndex(m_ui.swTextureFiltering->currentIndex());
}
void GraphicsSettingsWidget::onRendererChanged(int index)
{
if (m_dialog->isPerGameSettings())
@@ -35,6 +35,8 @@ Q_SIGNALS:
void fullscreenModesChanged(const QStringList& modes);
private Q_SLOTS:
void onTextureFilteringChange();
void onSWTextureFilteringChange();
void onRendererChanged(int index);
void onAdapterChanged(int index);
void onEnableHardwareFixesChanged();
+95 -50
View File
@@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>657</width>
<width>658</width>
<height>890</height>
</rect>
</property>
@@ -642,38 +642,6 @@
</item>
</widget>
</item>
<item row="9" column="0" colspan="2">
<layout class="QGridLayout" name="basicCheckboxGridLayout">
<item row="1" column="0">
<widget class="QCheckBox" name="gpuPaletteConversion">
<property name="text">
<string>GPU Palette Conversion</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="conservativeBufferAllocation">
<property name="text">
<string>Conservative Buffer Allocation</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="accurateDATE">
<property name="text">
<string>Accurate Destination Alpha Test</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="enableHWFixes">
<property name="text">
<string>Manual Hardware Renderer Fixes</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_20">
<property name="text">
@@ -700,13 +668,38 @@
</item>
</widget>
</item>
<item row="9" column="0" colspan="2">
<layout class="QGridLayout" name="basicCheckboxGridLayout">
<item row="0" column="0">
<widget class="QCheckBox" name="accurateDATE">
<property name="text">
<string>Accurate Destination Alpha Test</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="gpuPaletteConversion">
<property name="text">
<string>GPU Palette Conversion</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="enableHWFixes">
<property name="text">
<string>Manual Hardware Renderer Fixes</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QGroupBox" name="hardwareFixesTab">
<attribute name="title">
<string>Hardware Fixes</string>
</attribute>
<layout class="QFormLayout" name="formLayout_2">
<layout class="QFormLayout" name="hardwareFixesLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
@@ -890,7 +883,7 @@
<attribute name="title">
<string>Upscaling Fixes</string>
</attribute>
<layout class="QFormLayout" name="formLayout_5">
<layout class="QFormLayout" name="upscalingFixesLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_14">
<property name="text">
@@ -949,16 +942,16 @@
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_16">
<widget class="QLabel" name="textureOffsetLabel">
<property name="text">
<string>Texture Offsets:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="0,1,0,1">
<layout class="QHBoxLayout" name="textureOffsetLayout" stretch="0,1,0,1">
<item>
<widget class="QLabel" name="label_17">
<widget class="QLabel" name="textureOffsetXLabel">
<property name="text">
<string>X:</string>
</property>
@@ -972,7 +965,7 @@
</widget>
</item>
<item>
<widget class="QLabel" name="label_18">
<widget class="QLabel" name="textureOffsetYLabel">
<property name="text">
<string>Y:</string>
</property>
@@ -1523,6 +1516,15 @@
</item>
<item>
<widget class="QTabWidget" name="softwareRendererGroup">
<property name="enabled">
<bool>true</bool>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>203</height>
</size>
</property>
<property name="documentMode">
<bool>true</bool>
</property>
@@ -1531,29 +1533,65 @@
<string>Rendering</string>
</attribute>
<layout class="QFormLayout" name="formLayout_3">
<item row="0" column="1">
<widget class="QComboBox" name="swTextureFiltering">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<item>
<property name="text">
<string>Nearest</string>
</property>
</item>
<item>
<property name="text">
<string>Bilinear (Forced)</string>
</property>
</item>
<item>
<property name="text">
<string>Bilinear (PS2)</string>
</property>
</item>
<item>
<property name="text">
<string>Bilinear (Forced excluding sprite)</string>
</property>
</item>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_9">
<widget class="QLabel" name="label_37">
<property name="text">
<string>Extra Rendering Threads:</string>
<string>Texture Filtering:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<item row="1" column="1">
<widget class="QSpinBox" name="extraSWThreads">
<property name="suffix">
<string> threads</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<item row="1" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Extra Rendering Threads:</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QCheckBox" name="swAutoFlush">
<property name="text">
<string>Auto Flush</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="swMipmap">
<property name="text">
@@ -1561,6 +1599,13 @@
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="swAutoFlush">
<property name="text">
<string>Auto Flush</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
@@ -50,7 +50,7 @@ MemoryCardSettingsWidget::MemoryCardSettingsWidget(SettingsDialog* dialog, QWidg
// since the group box hasn't been resized at that point.
m_ui.cardGroupBox->installEventFilter(this);
SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.directory, m_ui.browse, m_ui.open, m_ui.reset, "Folders", "MemoryCards", "memcards");
SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.directory, m_ui.browse, m_ui.open, m_ui.reset, "Folders", "MemoryCards", Path::Combine(EmuFolders::DataRoot, "memcards"));
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.autoEject, "EmuCore", "McdEnableEjection", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.automaticManagement, "EmuCore", "McdFolderAutoManage", true);
+3
View File
@@ -214,6 +214,9 @@ void SettingsDialog::addWidget(QWidget* widget, QString title, QString icon, QSt
void SettingsDialog::registerWidgetHelp(QObject* object, QString title, QString recommended_value, QString text)
{
if (!object)
return;
// construct rich text with formatted description
QString full_text;
full_text += "<table width='100%' cellpadding='0' cellspacing='0'><tr><td><strong>";
+1 -1
View File
@@ -66,7 +66,7 @@
<property name="minimumSize">
<size>
<width>0</width>
<height>100</height>
<height>120</height>
</size>
</property>
<property name="maximumSize">
+2 -10
View File
@@ -80,7 +80,7 @@ SystemSettingsWidget::SystemSettingsWidget(SettingsDialog* dialog, QWidget* pare
tr("Higher values may increase internal framerate in games, but will increase CPU requirements substantially. "
"Lower values will reduce the CPU load allowing lightweight games to run full speed on weaker CPUs."));
dialog->registerWidgetHelp(m_ui.eeCycleSkipping, tr("Cycle Skip"), tr("Normal Speed"),
dialog->registerWidgetHelp(m_ui.eeCycleSkipping, tr("Cycle Skip"), tr("None"),
tr("Makes the emulated Emotion Engine skip cycles. "
"Helps a small subset of games like SOTC. Most of the time it's harmful to performance."));
@@ -89,23 +89,15 @@ SystemSettingsWidget::SystemSettingsWidget(SettingsDialog* dialog, QWidget* pare
"Safe for most games, but a few are incompatible and may hang."));
dialog->registerWidgetHelp(m_ui.instantVU1, tr("Instant VU1"), tr("Checked"),
tr("Runs VU1 instantly (when MTVU is disabled). Provides a modest speed improvement. "
tr("Runs VU1 instantly. Provides a modest speed improvement in most games. "
"Safe for most games, but a few games may exhibit graphical errors."));
dialog->registerWidgetHelp(m_ui.fastCDVD, tr("Enable Fast CDVD"), tr("Unchecked"),
tr("Fast disc access, less loading times. Check HDLoader compatibility lists for known games that have issues with this."));
updateVU1InstantState();
connect(m_ui.MTVU, &QCheckBox::stateChanged, this, &SystemSettingsWidget::updateVU1InstantState);
}
SystemSettingsWidget::~SystemSettingsWidget() = default;
void SystemSettingsWidget::updateVU1InstantState()
{
m_ui.instantVU1->setEnabled(!m_dialog->getEffectiveBoolValue("EmuCore/Speedhacks", "vuThread", false));
}
int SystemSettingsWidget::getGlobalClampingModeIndex(bool vu) const
{
if (Host::GetBaseBoolSettingValue("EmuCore/CPU/Recompiler", vu ? "vuSignOverflow" : "fpuFullMode", false))
-3
View File
@@ -29,9 +29,6 @@ public:
SystemSettingsWidget(SettingsDialog* dialog, QWidget* parent);
~SystemSettingsWidget();
private Q_SLOTS:
void updateVU1InstantState();
private:
int getGlobalClampingModeIndex(bool vu) const;
int getClampingModeIndex(bool vu) const;
+6
View File
@@ -335,6 +335,12 @@
</QtUi>
</ItemGroup>
<ItemGroup>
<QtUi Include="Settings\ControllerMacroEditWidget.ui">
<FileType>Document</FileType>
</QtUi>
<QtUi Include="Settings\ControllerMacroWidget.ui">
<FileType>Document</FileType>
</QtUi>
<None Include="Settings\FolderSettingsWidget.ui" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+6
View File
@@ -405,6 +405,12 @@
<QtUi Include="Tools\InputRecording\NewInputRecordingDlg.ui">
<Filter>Tools\Input Recording</Filter>
</QtUi>
<QtUi Include="Settings\ControllerMacroWidget.ui">
<Filter>Settings</Filter>
</QtUi>
<QtUi Include="Settings\ControllerMacroEditWidget.ui">
<Filter>Settings</Filter>
</QtUi>
</ItemGroup>
<ItemGroup>
<None Include="Settings\FolderSettingsWidget.ui">
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M7 7V3a1 1 0 0 1 1-1h13a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-4v3.993c0 .556-.449 1.007-1.007 1.007H3.007A1.006 1.006 0 0 1 2 20.993l.003-12.986C2.003 7.451 2.452 7 3.01 7H7zm2 0h6.993C16.549 7 17 7.449 17 8.007V15h3V4H9v3zM4.003 9L4 20h11V9H4.003z" fill="#000000"/></svg>

After

Width:  |  Height:  |  Size: 395 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M13 9h8L11 24v-9H4l9-15v9zm-2 2V7.22L7.532 13H13v4.394L17.263 11H11z" fill="#000000"/></svg>

After

Width:  |  Height:  |  Size: 221 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M7 7V3a1 1 0 0 1 1-1h13a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-4v3.993c0 .556-.449 1.007-1.007 1.007H3.007A1.006 1.006 0 0 1 2 20.993l.003-12.986C2.003 7.451 2.452 7 3.01 7H7zm2 0h6.993C16.549 7 17 7.449 17 8.007V15h3V4H9v3zM4.003 9L4 20h11V9H4.003z" fill="#ffffff"/></svg>

After

Width:  |  Height:  |  Size: 395 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M13 9h8L11 24v-9H4l9-15v9zm-2 2V7.22L7.532 13H13v4.394L17.263 11H11z" fill="#ffffff"/></svg>

After

Width:  |  Height:  |  Size: 221 B

+4
View File
@@ -7,6 +7,7 @@
<file>icons/black/svg/artboard-2-line.svg</file>
<file>icons/black/svg/book-open-line.svg</file>
<file>icons/black/svg/brush-line.svg</file>
<file>icons/black/svg/checkbox-multiple-blank-line.svg</file>
<file>icons/black/svg/close-line.svg</file>
<file>icons/black/svg/dashboard-line.svg</file>
<file>icons/black/svg/disc-line.svg</file>
@@ -21,6 +22,7 @@
<file>icons/black/svg/file-search-line.svg</file>
<file>icons/black/svg/file-settings-line.svg</file>
<file>icons/black/svg/filter-line.svg</file>
<file>icons/black/svg/flashlight-line.svg</file>
<file>icons/black/svg/flask-line.svg</file>
<file>icons/black/svg/folder-add-line.svg</file>
<file>icons/black/svg/folder-open-line.svg</file>
@@ -59,6 +61,7 @@
<file>icons/white/svg/artboard-2-line.svg</file>
<file>icons/white/svg/book-open-line.svg</file>
<file>icons/white/svg/brush-line.svg</file>
<file>icons/white/svg/checkbox-multiple-blank-line.svg</file>
<file>icons/white/svg/close-line.svg</file>
<file>icons/white/svg/dashboard-line.svg</file>
<file>icons/white/svg/disc-line.svg</file>
@@ -73,6 +76,7 @@
<file>icons/white/svg/file-search-line.svg</file>
<file>icons/white/svg/file-settings-line.svg</file>
<file>icons/white/svg/filter-line.svg</file>
<file>icons/white/svg/flashlight-line.svg</file>
<file>icons/white/svg/flask-line.svg</file>
<file>icons/white/svg/folder-add-line.svg</file>
<file>icons/white/svg/folder-open-line.svg</file>
+1 -2
View File
@@ -461,7 +461,6 @@ struct Pcsx2Config
HWDisableReadbacks : 1,
AccurateDATE : 1,
GPUPaletteConversion : 1,
ConservativeFramebuffer : 1,
AutoFlushSW : 1,
PreloadFrameWithGSData : 1,
WrapGSMem : 1,
@@ -532,7 +531,7 @@ struct Pcsx2Config
CRCHackLevel CRCHack{CRCHackLevel::Automatic};
BiFiltering TextureFiltering{BiFiltering::PS2};
TexturePreloadingLevel TexturePreloading{TexturePreloadingLevel::Full};
GSDumpCompressionMethod GSDumpCompression{GSDumpCompressionMethod::Uncompressed};
GSDumpCompressionMethod GSDumpCompression{GSDumpCompressionMethod::LZMA};
int Dithering{2};
int MaxAnisotropy{0};
int SWExtraThreads{2};
+1
View File
@@ -390,6 +390,7 @@ bool SocketAdapter::send(NetPacket* pkt)
memcpy(arpRet->senderProtocolAddress.get(), arpPkt.targetProtocolAddress.get(), 4);
arpRet->op = 2,
arpRet->protocol = arpPkt.protocol;
arpRet->hardwareType = arpPkt.hardwareType;
EthernetFrame* retARP = new EthernetFrame(arpRet);
memcpy(retARP->destinationMAC, ps2MAC, 6);
-4
View File
@@ -829,7 +829,6 @@ void GSUpdateConfig(const Pcsx2Config::GSOptions& new_config)
// reload texture cache when trilinear filtering or TC options change
if (
(GSConfig.UseHardwareRenderer() && GSConfig.HWMipmap != old_config.HWMipmap) ||
GSConfig.ConservativeFramebuffer != old_config.ConservativeFramebuffer ||
GSConfig.TexturePreloading != old_config.TexturePreloading ||
GSConfig.UserHacks_TriFilter != old_config.UserHacks_TriFilter ||
GSConfig.GPUPaletteConversion != old_config.GPUPaletteConversion ||
@@ -1456,7 +1455,6 @@ void GSApp::Init()
m_default_configuration["pcrtc_overscan"] = "0";
m_default_configuration["IntegerScaling"] = "0";
m_default_configuration["deinterlace"] = "7";
m_default_configuration["conservative_framebuffer"] = "1";
m_default_configuration["linear_present"] = "1";
m_default_configuration["LoadTextureReplacements"] = "0";
m_default_configuration["LoadTextureReplacementsAsync"] = "1";
@@ -1479,8 +1477,6 @@ void GSApp::Init()
m_default_configuration["override_GL_ARB_draw_buffers_blend"] = "-1";
m_default_configuration["override_GL_ARB_gpu_shader5"] = "-1";
m_default_configuration["override_GL_ARB_shader_image_load_store"] = "-1";
m_default_configuration["override_GL_ARB_sparse_texture"] = "-1";
m_default_configuration["override_GL_ARB_sparse_texture2"] = "-1";
m_default_configuration["override_GL_ARB_texture_barrier"] = "-1";
m_default_configuration["OverrideTextureBarriers"] = "-1";
m_default_configuration["OverrideGeometryShaders"] = "-1";
+7 -1
View File
@@ -395,8 +395,11 @@ static wil::com_ptr_nothrow<IPin> GetFirstPin(IBaseFilter* pBF, PIN_DIRECTION di
//
GSCapture::GSCapture()
: m_capturing(false), m_frame(0)
: m_capturing(false)
, m_out_dir("/tmp/GS_Capture") // FIXME Later add an option
#if defined(__unix__)
, m_frame(0)
#endif
{
}
@@ -545,6 +548,9 @@ bool GSCapture::BeginCapture(float fps, GSVector2i recommendedResolution, float
m_capturing = true;
filename = m_out_dir + "/audio_recording.wav";
return true;
#else
// FIXME: MACOS
return false;
#endif
}
+1 -1
View File
@@ -28,7 +28,6 @@ class GSCapture
std::recursive_mutex m_lock;
bool m_capturing;
GSVector2i m_size;
u64 m_frame;
std::string m_out_dir;
int m_threads;
@@ -39,6 +38,7 @@ class GSCapture
#elif defined(__unix__)
u64 m_frame;
std::vector<std::unique_ptr<GSPng::Worker>> m_workers;
int m_compression_level;
-21
View File
@@ -174,18 +174,6 @@ const CRC::Game CRC::m_games[] =
{0xAA5EC3A3, TalesOfAbyss, JP, 0},
{0xFB236A46, SonicUnleashed, US, 0},
{0x8C913264, SonicUnleashed, EU, 0},
{0xE04EA200, TriAceGames, EU, 0}, // StarOcean3
{0x23A97857, TriAceGames, US, 0}, // StarOcean3
{0xBEC32D49, TriAceGames, JP, 0}, // StarOcean3
{0x8192A241, TriAceGames, JP, 0}, // StarOcean3 directors cut
{0xCC96CE93, TriAceGames, US, 0}, // ValkyrieProfile2
{0x774DE8E2, TriAceGames, JP, 0}, // ValkyrieProfile2
{0x04CCB600, TriAceGames, EU, 0}, // ValkyrieProfile2
{0xB65E141B, TriAceGames, DE, 0}, // ValkyrieProfile2
{0x8510854E, TriAceGames, FR, 0}, // ValkyrieProfile2
{0xC70FC973, TriAceGames, IT, 0}, // ValkyrieProfile2
{0x47B9B2FD, TriAceGames, US, 0}, // RadiataStories
{0xAC73005E, TriAceGames, JP, 0}, // RadiataStories
{0xE8FCF8EC, SMTNocturne, US, 0},
{0xF0A31EE3, SMTNocturne, EU, 0}, // SMTNocturne (Lucifers Call in EU)
{0xAE0DE7B7, SMTNocturne, EU, 0}, // SMTNocturne (Lucifers Call in EU)
@@ -237,9 +225,6 @@ const CRC::Game CRC::m_games[] =
{0xDDF76A98, BurnoutGames, JP, 0}, // BurnoutDominator
{0x8C9576B4, BurnoutGames, EU, 0}, // BurnoutDominator
{0x8C9C76B4, BurnoutGames, EU, 0}, // BurnoutDominator
{0x4A0E5B3A, MidnightClub3, US, 0}, // dub
{0xEBE1972D, MidnightClub3, EU, 0}, // dub
{0x60A42FF5, MidnightClub3, US, 0}, // remix
{0x43AB7214, TalesOfLegendia, US, 0},
{0x1F8640E0, TalesOfLegendia, JP, 0},
{0xE4F5DA2B, TalesOfLegendia, KO, 0},
@@ -258,12 +243,6 @@ const CRC::Game CRC::m_games[] =
{0x97E9C87E, YakuzaGames, US, 0}, // Yakuza2
{0xB1EBD841, YakuzaGames, US, 0}, // Yakuza2
{0xC6B95C48, YakuzaGames, JP, 0}, // Yakuza2
{0x9000252A, SkyGunner, JP, 0},
{0x93092623, SkyGunner, JP, 0},
{0xA9461CB2, SkyGunner, US, 0},
{0xC71DE999, SkyGunner, US, 0}, // Regular Demo
{0xAADF3287, SkyGunner, US, 0}, // Trade Demo
{0xB799A60C, SkyGunner, NoRegion, 0},
{0x2905C5C6, ZettaiZetsumeiToshi2, US, 0}, // Raw Danger!
{0xC988ECBB, ZettaiZetsumeiToshi2, JP, 0},
{0x90F4B057, ZettaiZetsumeiToshi2, CH, 0},
-3
View File
@@ -50,7 +50,6 @@ public:
Lamune,
Manhunt2,
MetalSlug6,
MidnightClub3,
Okami,
Oneechanbara2Special,
PolyphonyDigitalGames,
@@ -63,7 +62,6 @@ public:
ShadowofRome,
ShinOnimusha,
Simple2000Vol114,
SkyGunner,
SMTNocturne,
SonicUnleashed,
Spartan,
@@ -77,7 +75,6 @@ public:
TombRaiderAnniversary,
TombRaiderLegend,
TombRaiderUnderworld,
TriAceGames,
UltramanFightingEvolution,
UrbanReign,
XenosagaE3,
-6
View File
@@ -76,12 +76,6 @@ __forceinline bool BitEqual(const T& a, const T& b)
return eqb;
}
#ifdef ENABLE_ACCURATE_BUFFER_EMULATION
static const GSVector2i default_rt_size(2048, 2048);
#else
static const GSVector2i default_rt_size(0, 0);
#endif
extern Pcsx2Config::GSOptions GSConfig;
// Maximum texture size to skip preload/hash path.
+25 -5
View File
@@ -815,8 +815,8 @@ void GSLocalMemory::WriteImage(int& tx, int& ty, const u8* src, int len, GIFRegB
if (TRXREG.RRW == 0)
return;
int l = (int)TRXPOS.DSAX;
int r = l + (int)TRXREG.RRW;
const int l = (int)TRXPOS.DSAX;
const int r = l + (int)TRXREG.RRW;
// finish the incomplete row first
@@ -828,12 +828,32 @@ void GSLocalMemory::WriteImage(int& tx, int& ty, const u8* src, int len, GIFRegB
len -= n;
}
int la = (l + (bsx - 1)) & ~(bsx - 1);
int ra = r & ~(bsx - 1);
const int la = (l + (bsx - 1)) & ~(bsx - 1);
const int ra = r & ~(bsx - 1);
// Round up to the nearest byte (NFL 2K5 does r = 1, l = 0 bpp =4, causing divide by zero)
int srcpitch = (((r - l) * trbpp) + 7) >> 3;
const int srcpitch = (((r - l) * trbpp) + 7) >> 3;
int h = len / srcpitch;
// Slow path for odd width 4bpp, the fast path expects everything to be perfectly aligned and great,
// but things get hairy with 4bpp pixels and odd widths since the lowest size we can address is 8bits, it goes out of sync.
// Although I call this a slow path, it's probably faster than modifying the data alignment every other line.
// GT3 demo, Jak 2 Japanese subtitles, and the BG Dark Alliance minimap do this.
if (trbpp == 4 && (TRXREG.RRW & 0x1))
{
int count = 0;
const int t = TRXPOS.DSAY;
const int b = t + (int)TRXREG.RRH;
for (int y = t; y < b; y++)
{
for (int x = l; x < r; x++)
{
WritePixel4(x, y, src[count >> 1] >> ((count & 1) << 2), BITBLTBUF.DBP, BITBLTBUF.DBW);
count++;
}
}
return;
}
if (ra - la >= bsx && h > 0) // "transfer width" >= "block width" && there is at least one full row
{
const u8* s = &src[-l * trbpp >> 3];
+1 -1
View File
@@ -482,7 +482,7 @@ GSVector2i GSState::GetResolution()
// 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))
if ((isinterlaced() && !m_regs->SMODE2.FFMD) || (GSConfig.InterlaceMode == GSInterlaceMode::Off && !GSConfig.PCRTCOffsets && !isinterlaced()))
resolution.y *= 2;
if (ignore_offset)
+1 -20
View File
@@ -161,12 +161,10 @@ GSTexture* GSDevice::FetchSurface(GSTexture::Type type, int width, int height, i
}
t->SetScale(GSVector2(1, 1)); // Things seem to assume that all textures come out of here with scale 1...
t->Commit(); // Clear won't be done if the texture isn't committed.
switch (type)
{
case GSTexture::Type::RenderTarget:
case GSTexture::Type::SparseRenderTarget:
{
if (clear)
ClearRenderTarget(t, 0);
@@ -175,7 +173,6 @@ GSTexture* GSDevice::FetchSurface(GSTexture::Type type, int width, int height, i
}
break;
case GSTexture::Type::DepthStencil:
case GSTexture::Type::SparseDepthStencil:
{
if (clear)
ClearDepth(t);
@@ -215,12 +212,6 @@ void GSDevice::Recycle(GSTexture* t)
{
if (t)
{
#ifdef _DEBUG
// Uncommit saves memory but it means a futur allocation when we want to reuse the texture.
// Which is slow and defeat the purpose of the m_pool cache.
// However, it can help to spot part of texture that we forgot to commit
t->Uncommit();
#endif
t->last_frame_used = m_frame;
m_pool.push_front(t);
@@ -259,16 +250,6 @@ void GSDevice::ClearSamplerCache()
{
}
GSTexture* GSDevice::CreateSparseRenderTarget(int w, int h, GSTexture::Format format, bool clear)
{
return FetchSurface(HasColorSparse() ? GSTexture::Type::SparseRenderTarget : GSTexture::Type::RenderTarget, w, h, 1, format, clear, true);
}
GSTexture* GSDevice::CreateSparseDepthStencil(int w, int h, GSTexture::Format format, bool clear)
{
return FetchSurface(HasDepthSparse() ? GSTexture::Type::SparseDepthStencil : GSTexture::Type::DepthStencil, w, h, 1, format, clear, true);
}
GSTexture* GSDevice::CreateRenderTarget(int w, int h, GSTexture::Format format, bool clear)
{
return FetchSurface(GSTexture::Type::RenderTarget, w, h, 1, format, clear, true);
@@ -292,7 +273,7 @@ GSTexture* GSDevice::CreateOffscreen(int w, int h, GSTexture::Format format)
GSTexture::Format GSDevice::GetDefaultTextureFormat(GSTexture::Type type)
{
if (type == GSTexture::Type::DepthStencil || type == GSTexture::Type::SparseDepthStencil)
if (type == GSTexture::Type::DepthStencil)
return GSTexture::Format::DepthStencil;
else
return GSTexture::Format::Color;
-5
View File
@@ -729,9 +729,6 @@ public:
virtual void BeginScene() {}
virtual void EndScene();
virtual bool HasDepthSparse() { return false; }
virtual bool HasColorSparse() { return false; }
virtual void ClearRenderTarget(GSTexture* t, const GSVector4& c) {}
virtual void ClearRenderTarget(GSTexture* t, u32 c) {}
virtual void InvalidateRenderTarget(GSTexture* t) {}
@@ -742,8 +739,6 @@ public:
virtual void PopDebugGroup() {}
virtual void InsertDebugMessage(DebugMessageCategory category, const char* fmt, ...) {}
GSTexture* CreateSparseRenderTarget(int w, int h, GSTexture::Format format, bool clear = true);
GSTexture* CreateSparseDepthStencil(int w, int h, GSTexture::Format format, bool clear = true);
GSTexture* CreateRenderTarget(int w, int h, GSTexture::Format format, bool clear = true);
GSTexture* CreateDepthStencil(int w, int h, GSTexture::Format format, bool clear = true);
GSTexture* CreateTexture(int w, int h, bool mipmap, GSTexture::Format format, bool prefer_reuse = false);
+21 -16
View File
@@ -762,6 +762,23 @@ void GSRenderer::QueueSnapshot(const std::string& path, u32 gsdump_frames)
}
else
{
m_snapshot = "";
// append the game serial and title
if (std::string name(GetDumpName()); !name.empty())
{
Path::SanitizeFileName(name);
if (name.length() > 219)
name.resize(219);
m_snapshot += name;
}
if (std::string serial(GetDumpSerial()); !serial.empty())
{
Path::SanitizeFileName(serial);
m_snapshot += '_';
m_snapshot += serial;
}
time_t cur_time = time(nullptr);
char local_time[16];
@@ -774,30 +791,18 @@ void GSRenderer::QueueSnapshot(const std::string& path, u32 gsdump_frames)
// the captured image is the 2nd image captured at this specific time.
static int n = 2;
m_snapshot += '_';
if (cur_time == prev_snap)
m_snapshot = fmt::format("gs_{0}_({1})", local_time, n++);
m_snapshot += fmt::format("{0}_({1})", local_time, n++);
else
{
n = 2;
m_snapshot = fmt::format("gs_{}", local_time);
m_snapshot += fmt::format("{}", local_time);
}
prev_snap = cur_time;
}
// append the game serial and title
if (std::string name(GetDumpName()); !name.empty())
{
Path::SanitizeFileName(name);
m_snapshot += '_';
m_snapshot += name;
}
if (std::string serial(GetDumpSerial()); !serial.empty())
{
Path::SanitizeFileName(serial);
m_snapshot += '_';
m_snapshot += serial;
}
// prepend snapshots directory
m_snapshot = Path::Combine(EmuFolders::Snapshots, m_snapshot);
}
-57
View File
@@ -22,13 +22,10 @@
GSTexture::GSTexture()
: m_scale(1, 1)
, m_size(0, 0)
, m_committed_size(0, 0)
, m_gpu_page_size(0, 0)
, m_mipmap_levels(0)
, m_type(Type::Invalid)
, m_format(Format::Invalid)
, m_state(State::Dirty)
, m_sparse(false)
, m_needs_mipmaps_generated(true)
, last_frame_used(0)
, OffsetHack_modxy(0.0f)
@@ -73,12 +70,10 @@ void GSTexture::Swap(GSTexture* tex)
{
std::swap(m_scale, tex->m_scale);
std::swap(m_size, tex->m_size);
std::swap(m_committed_size, tex->m_committed_size);
std::swap(m_mipmap_levels, tex->m_mipmap_levels);
std::swap(m_type, tex->m_type);
std::swap(m_format, tex->m_format);
std::swap(m_state, tex->m_state);
std::swap(m_sparse, tex->m_sparse);
std::swap(m_needs_mipmaps_generated, tex->m_needs_mipmaps_generated);
std::swap(last_frame_used, tex->last_frame_used);
std::swap(OffsetHack_modxy, tex->OffsetHack_modxy);
@@ -133,55 +128,3 @@ void GSTexture::GenerateMipmapsIfNeeded()
m_needs_mipmaps_generated = false;
GenerateMipmap();
}
void GSTexture::CommitRegion(const GSVector2i& region)
{
if (!m_sparse)
return;
GSVector2i aligned_region = RoundUpPage(region);
aligned_region.x = std::max(m_committed_size.x, aligned_region.x);
aligned_region.y = std::max(m_committed_size.y, aligned_region.y);
if (aligned_region != m_committed_size)
CommitPages(aligned_region, true);
}
void GSTexture::Commit()
{
if (!m_sparse)
return;
if (m_committed_size != m_size)
CommitPages(m_size, true);
}
void GSTexture::Uncommit()
{
if (!m_sparse)
return;
GSVector2i zero = GSVector2i(0, 0);
if (m_committed_size != zero)
CommitPages(m_committed_size, false);
}
void GSTexture::SetGpuPageSize(const GSVector2i& page_size)
{
pxAssert(std::bitset<32>(page_size.x + 1).count() == 1);
pxAssert(std::bitset<32>(page_size.y + 1).count() == 1);
m_gpu_page_size = page_size;
}
GSVector2i GSTexture::RoundUpPage(GSVector2i v)
{
v.x = std::min(m_size.x, v.x);
v.y = std::min(m_size.y, v.y);
v.x += m_gpu_page_size.x;
v.y += m_gpu_page_size.y;
v.x &= ~m_gpu_page_size.x;
v.y &= ~m_gpu_page_size.y;
return v;
}
+4 -24
View File
@@ -33,8 +33,6 @@ public:
DepthStencil,
Texture,
Offscreen,
SparseRenderTarget,
SparseDepthStencil,
};
enum class Format : u8
@@ -63,25 +61,16 @@ public:
protected:
GSVector2 m_scale;
GSVector2i m_size;
GSVector2i m_committed_size;
GSVector2i m_gpu_page_size;
int m_mipmap_levels;
Type m_type;
Format m_format;
State m_state;
bool m_sparse;
bool m_needs_mipmaps_generated;
public:
GSTexture();
virtual ~GSTexture() {}
virtual operator bool()
{
pxAssert(0);
return false;
}
// Returns the native handle of a texture.
virtual void* GetNativeHandle() const = 0;
@@ -113,16 +102,15 @@ public:
bool IsRenderTargetOrDepthStencil() const
{
return (m_type >= Type::RenderTarget && m_type <= Type::DepthStencil) ||
(m_type >= Type::SparseRenderTarget && m_type <= Type::SparseDepthStencil);
return (m_type >= Type::RenderTarget && m_type <= Type::DepthStencil);
}
bool IsRenderTarget() const
{
return (m_type == Type::RenderTarget || m_type == Type::SparseRenderTarget);
return (m_type == Type::RenderTarget);
}
bool IsDepthStencil() const
{
return (m_type == Type::DepthStencil || m_type == Type::SparseDepthStencil);
return (m_type == Type::DepthStencil);
}
State GetState() const { return m_state; }
@@ -131,14 +119,6 @@ public:
void GenerateMipmapsIfNeeded();
void ClearMipmapGenerationFlag() { m_needs_mipmaps_generated = false; }
virtual void CommitPages(const GSVector2i& region, bool commit) {}
void CommitRegion(const GSVector2i& region);
void Commit();
void Uncommit();
GSVector2i GetCommittedSize() const { return m_committed_size; }
void SetGpuPageSize(const GSVector2i& page_size);
GSVector2i RoundUpPage(GSVector2i v);
// frame number (arbitrary base) the texture was recycled on
// different purpose than texture cache ages, do not attempt to merge
unsigned last_frame_used;
@@ -146,7 +126,7 @@ public:
float OffsetHack_modxy;
// Typical size of a RGBA texture
virtual u32 GetMemUsage() { return m_size.x * m_size.y * (m_format == Format::UNorm8 ? 1 : 4); }
u32 GetMemUsage() const { return m_size.x * m_size.y * (m_format == Format::UNorm8 ? 1 : 4); }
// Helper routines for formats/types
static bool IsCompressedFormat(Format format) { return (format >= Format::BC1 && format <= Format::BC7); }
-1
View File
@@ -1370,7 +1370,6 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config)
const GSVector4 dRect(config.drawarea);
const GSVector4 sRect = dRect / GSVector4(size.x, size.y).xyxy();
hdr_rt = CreateRenderTarget(size.x, size.y, GSTexture::Format::FloatColor);
hdr_rt->CommitRegion(GSVector2i(config.drawarea.z, config.drawarea.w));
// Warning: StretchRect must be called before BeginScene otherwise
// vertices will be overwritten. Trust me you don't want to do that.
StretchRect(config.rt, sRect, hdr_rt, dRect, ShaderConvert::COPY, false);
+1 -1
View File
@@ -350,7 +350,7 @@ void GSDevice11::SetupOM(OMDepthStencilSelector dssel, OMBlendSelector bsel, u8
memset(&bd, 0, sizeof(bd));
if (bsel.blend_enable)
if (bsel.blend_enable && (bsel.wrgba & 0x7))
{
bd.RenderTarget[0].BlendEnable = TRUE;
bd.RenderTarget[0].BlendOp = s_d3d11_blend_ops[bsel.blend_op];
+1 -2
View File
@@ -319,8 +319,7 @@ void GSDevice12::LookupNativeFormat(GSTexture::Format format, DXGI_FORMAT* d3d_f
GSTexture* GSDevice12::CreateSurface(GSTexture::Type type, int width, int height, int levels, GSTexture::Format format)
{
pxAssert(type != GSTexture::Type::Offscreen && type != GSTexture::Type::SparseRenderTarget &&
type != GSTexture::Type::SparseDepthStencil);
pxAssert(type != GSTexture::Type::Offscreen);
const u32 clamped_width = static_cast<u32>(std::clamp<int>(1, width, D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION));
const u32 clamped_height = static_cast<u32>(std::clamp<int>(1, height, D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION));
-71
View File
@@ -390,22 +390,6 @@ bool GSC_BurnoutGames(const GSFrameInfo& fi, int& skip)
return true;
}
bool GSC_MidnightClub3(const GSFrameInfo& fi, int& skip)
{
if (skip == 0)
{
if (fi.TME && (fi.FBP > 0x01d00 && fi.FBP <= 0x02a00) && fi.FPSM == PSM_PSMCT32 && (fi.FBP >= 0x01600 && fi.FBP < 0x03260) && fi.TPSM == PSM_PSMT8H)
{
// Vram usage.
// Tested: tokyo default cruise.
// Move around a bit, stop car, wait as vram goes down, start moving again, vram spike.
skip = 1;
}
}
return true;
}
bool GSC_TalesOfLegendia(const GSFrameInfo& fi, int& skip)
{
if (skip == 0)
@@ -639,19 +623,6 @@ bool GSC_UrbanReign(const GSFrameInfo& fi, int& skip)
return true;
}
bool GSC_SkyGunner(const GSFrameInfo& fi, int& skip)
{
if (skip == 0)
{
if (!fi.TME && !(fi.FBP == 0x0 || fi.FBP == 0x00800 || fi.FBP == 0x008c0 || fi.FBP == 0x03e00) && fi.FPSM == PSM_PSMCT32 && (fi.TBP0 == 0x0 || fi.TBP0 == 0x01800) && fi.TPSM == PSM_PSMCT32)
{
skip = 1; // Huge Vram usage
}
}
return true;
}
bool GSC_SteambotChronicles(const GSFrameInfo& fi, int& skip)
{
if (skip == 0)
@@ -710,41 +681,6 @@ bool GSC_GetawayGames(const GSFrameInfo& fi, int& skip)
return true;
}
bool GSC_TriAceGames(const GSFrameInfo& fi, int& skip)
{
// Tri Ace Games: ValkyrieProfile2, RadiataStories, StarOcean3
//
// The games emulate a stencil buffer with the alpha channel of the RT
// The operation of the stencil is selected with the palette
// For example -1 wrap will be [240, 16, 32, 48 ....]
// i.e. p[A>>4] = (A - 16) % 256
//
// The fastest and accurate solution will be to replace this pseudo stencil
// by a dedicated GPU draw call
// 1/ Use future GPU capabilities to do a "kind" of SW blending
// 2/ Use a real stencil/atomic image, and then compute the RT alpha value
//
// Both of those solutions will increase code complexity (and only avoid upscaling
// glitches)
if (skip == 0)
{
if (fi.TME && fi.FBP == fi.TBP0 && fi.FPSM == PSM_PSMCT32 && fi.TPSM == PSM_PSMT4HH)
{
skip = 1000;
}
}
else
{
if (!(fi.TME && fi.FBP == fi.TBP0 && fi.FPSM == PSM_PSMCT32 && fi.TPSM == PSM_PSMT4HH))
{
skip = 0;
}
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
// Aggressive only hack
////////////////////////////////////////////////////////////////////////////////
@@ -895,7 +831,6 @@ void GSState::SetupCrcHack()
lut[CRC::KnightsOfTheTemple2] = GSC_KnightsOfTheTemple2;
lut[CRC::Kunoichi] = GSC_Kunoichi;
lut[CRC::Manhunt2] = GSC_Manhunt2;
lut[CRC::MidnightClub3] = GSC_MidnightClub3;
lut[CRC::SacredBlaze] = GSC_SacredBlaze;
lut[CRC::SakuraTaisen] = GSC_SakuraTaisen;
lut[CRC::SakuraWarsSoLongMyLove] = GSC_SakuraWarsSoLongMyLove;
@@ -913,7 +848,6 @@ void GSState::SetupCrcHack()
// Channel Effect
lut[CRC::CrashBandicootWoC] = GSC_CrashBandicootWoC;
lut[CRC::GiTS] = GSC_GiTS;
lut[CRC::SkyGunner] = GSC_SkyGunner; // Maybe not a channel effect
lut[CRC::Spartan] = GSC_Spartan;
lut[CRC::SteambotChronicles] = GSC_SteambotChronicles;
@@ -941,11 +875,6 @@ void GSState::SetupCrcHack()
{
// Accurate Blending
lut[CRC::GetawayGames] = GSC_GetawayGames; // Blending High
// These games emulate a stencil buffer with the alpha channel of the RT (too slow to move to CRC_Aggressive)
// Needs at least Basic Blending,
// see https://github.com/PCSX2/pcsx2/pull/2921
lut[CRC::TriAceGames] = GSC_TriAceGames;
}
if (CRC_Aggressive)
+211 -243
View File
@@ -20,15 +20,15 @@
#include "GS/Renderers/SW/GSTextureCacheSW.h"
#include "GS/Renderers/SW/GSDrawScanline.h"
#include "Host.h"
#include "common/Align.h"
#include "common/StringUtil.h"
GSRendererHW::GSRendererHW()
: GSRenderer()
, m_width(default_rt_size.x)
, m_height(default_rt_size.y)
, m_tc(new GSTextureCache())
, m_src(nullptr)
, m_reset(false)
, m_tex_is_fb(false)
, m_channel_shuffle(false)
, m_userhacks_tcoffset(false)
, m_userhacks_tcoffset_x(0)
@@ -50,7 +50,7 @@ GSRendererHW::GSRendererHW()
ResetStates();
}
void GSRendererHW::SetScaling()
GSVector2i GSRendererHW::GetOutputSize(int real_h)
{
GSVector2i crtc_size(GetResolution());
@@ -90,63 +90,10 @@ void GSRendererHW::SetScaling()
}
}
// Details of (potential) perf impact of a big framebuffer
// 1/ extra memory
// 2/ texture cache framebuffer rescaling/copy
// 3/ upload of framebuffer (preload hack)
// 4/ framebuffer clear (color/depth/stencil)
// 5/ read back of the frambuffer
//
// With the solution
// 1/ Nothing to do.Except the texture cache bug (channel shuffle effect)
// most of the market is 1GB of VRAM (and soon 2GB)
// 2/ limit rescaling/copy to the valid data of the framebuffer
// 3/ ??? no solution so far
// 4a/ stencil can be limited to valid data.
// 4b/ is it useful to clear color? depth? (in any case, it ought to be few operation)
// 5/ limit the read to the valid data
// Include negative display offsets in the height here.
crtc_size.y = std::max(crtc_size.y, real_h);
// Framebuffer width is always a multiple of 64 so at certain cases it can't cover some weird width values.
// 480P , 576P use width as 720 which is not referencable by FBW * 64. so it produces 704 ( the closest value multiple by 64).
// In such cases, let's just use the CRTC width.
const int fb_width = std::max({(int)m_context->FRAME.FBW * 64, crtc_size.x, 512});
// GS doesn't have a specific register for the FrameBuffer height. so we get the height
// from physical units of the display rectangle in case the game uses a heigher value of height.
//
// Gregory: the framebuffer must have enough room to draw
// * at least 2 frames such as FMV (see OI_BlitFMV)
// * high resolution game such as snowblind engine game
//
// Autodetection isn't a good idea because it will create flickering
// If memory consumption is an issue, there are 2 possibilities
// * 1/ Avoid to create hundreds of RT
// * 2/ Use sparse texture (requires recent HW)
//
// Avoid to alternate between 640x1280 and 1280x1024 on snow blind engine game
// int fb_height = (fb_width < 1024) ? 1280 : 1024;
//
// Until performance issue is properly fixed, let's keep an option to reduce the framebuffer size.
//
// m_large_framebuffer has been inverted to m_conservative_framebuffer, it isn't an option that benefits being enabled all the time for everyone.
int fb_height = MAX_FRAMEBUFFER_HEIGHT;
if (GSConfig.ConservativeFramebuffer)
{
fb_height = fb_width < 1024 ? std::max(512, crtc_size.y) : 1024;
}
const int upscaled_fb_w = fb_width * GSConfig.UpscaleMultiplier;
const int upscaled_fb_h = fb_height * GSConfig.UpscaleMultiplier;
const bool good_rt_size = m_width >= upscaled_fb_w && m_height >= upscaled_fb_h;
// No need to resize for native/custom resolutions as default size will be enough for native and we manually get RT Buffer size for custom.
// don't resize until the display rectangle and register states are stabilized.
if (good_rt_size)
return;
m_tc->RemovePartial();
m_width = upscaled_fb_w;
m_height = upscaled_fb_h;
printf("Frame buffer size set to %dx%d (%dx%d)\n", fb_width, fb_height, m_width, m_height);
return crtc_size * GSVector2i(static_cast<int>(GSConfig.UpscaleMultiplier), static_cast<int>(GSConfig.UpscaleMultiplier));
}
void GSRendererHW::SetTCOffset()
@@ -174,10 +121,42 @@ void GSRendererHW::PurgeTextureCache()
m_tc->RemoveAll();
}
bool GSRendererHW::IsPossibleTextureShuffle(GSTextureCache::Source* src) const
bool GSRendererHW::UpdateTexIsFB(GSTextureCache::Target* dst, const GIFRegTEX0& TEX0)
{
if (GSConfig.AccurateBlendingUnit == AccBlendLevel::Minimum || !g_gs_device->Features().texture_barrier)
return false;
// Texture is actually the frame buffer. Stencil emulation to compute shadow (Jak series/tri-ace game)
// Will hit the "m_ps_sel.tex_is_fb = 1" path in the draw
if (m_vt.m_primclass == GS_TRIANGLE_CLASS)
{
if (m_context->FRAME.FBMSK == 0x00FFFFFF && TEX0.TBP0 == m_context->FRAME.Block())
m_tex_is_fb = true;
}
else if (m_vt.m_primclass == GS_SPRITE_CLASS)
{
if (TEX0.TBP0 == m_context->FRAME.Block())
{
m_tex_is_fb = IsPossibleTextureShuffle(dst, TEX0);
if (!m_tex_is_fb && !m_vt.IsLinear())
{
// Make sure that we're not sampling away from the area we're rendering.
// We need to take the absolute here, because Beyond Good and Evil undithers itself using a -1,-1 offset.
const GSVector4 diff(m_vt.m_min.p.xyxy(m_vt.m_max.p) - m_vt.m_min.t.xyxy(m_vt.m_max.t));
if ((diff.abs() < GSVector4(1.0f)).alltrue())
m_tex_is_fb = true;
}
}
}
return m_tex_is_fb;
}
bool GSRendererHW::IsPossibleTextureShuffle(GSTextureCache::Target* dst, const GIFRegTEX0& TEX0) const
{
return (PRIM->TME && m_vt.m_primclass == GS_SPRITE_CLASS &&
src->m_32_bits_fmt && GSLocalMemory::m_psm[src->m_TEX0.PSM].bpp == 16 &&
dst->m_32_bits_fmt && GSLocalMemory::m_psm[TEX0.PSM].bpp == 16 &&
GSLocalMemory::m_psm[m_context->FRAME.PSM].bpp == 16);
}
@@ -192,11 +171,6 @@ void GSRendererHW::SetGameCRC(u32 crc, int options)
bool GSRendererHW::CanUpscale()
{
if (m_hacks.m_cu && !(this->*m_hacks.m_cu)())
{
return false;
}
return GSConfig.UpscaleMultiplier != 1;
}
@@ -227,20 +201,12 @@ void GSRendererHW::VSync(u32 field, bool registers_written)
if (m_reset)
{
m_tc->RemoveAll();
// Reset RT size.
m_width = default_rt_size.x;
m_height = default_rt_size.y;
m_reset = false;
}
if (GSConfig.LoadTextureReplacements)
GSTextureReplacements::ProcessAsyncLoadedTextures();
//Check if the frame buffer width or display width has changed
SetScaling();
GSRenderer::VSync(field, registers_written);
m_tc->IncAge();
@@ -286,7 +252,7 @@ GSTexture* GSRendererHW::GetOutput(int i, int& y_offset)
GSTexture* t = NULL;
if (GSTextureCache::Target* rt = m_tc->LookupTarget(TEX0, GetTargetSize(), fb_height))
if (GSTextureCache::Target* rt = m_tc->LookupDisplayTarget(TEX0, GetOutputSize(fb_height), fb_height))
{
t = rt->m_texture;
@@ -321,7 +287,8 @@ GSTexture* GSRendererHW::GetFeedbackOutput()
TEX0.TBW = m_regs->EXTBUF.EXBW;
TEX0.PSM = m_regs->DISP[m_regs->EXTBUF.FBIN & 1].DISPFB.PSM;
GSTextureCache::Target* rt = m_tc->LookupTarget(TEX0, GetTargetSize(), /*GetFrameRect(i).bottom*/ m_regs->DISP[m_regs->EXTBUF.FBIN & 1].DISPLAY.DH);
const int fb_height = /*GetFrameRect(i).bottom*/ m_regs->DISP[m_regs->EXTBUF.FBIN & 1].DISPLAY.DH;
GSTextureCache::Target* rt = m_tc->LookupDisplayTarget(TEX0, GetOutputSize(fb_height), fb_height);
GSTexture* t = rt->m_texture;
@@ -763,35 +730,46 @@ void GSRendererHW::MergeSprite(GSTextureCache::Source* tex)
}
}
GSVector2 GSRendererHW::GetTextureScaleFactor(const bool force_upscaling)
{
GSVector2 scale_factor{ 1.0f, 1.0f };
if (force_upscaling || CanUpscale())
{
const int multiplier = GetUpscaleMultiplier();
scale_factor.x = multiplier;
scale_factor.y = multiplier;
}
return scale_factor;
}
GSVector2 GSRendererHW::GetTextureScaleFactor()
{
return GetTextureScaleFactor(false);
const float f_upscale = static_cast<float>(GetUpscaleMultiplier());
return GSVector2(f_upscale, f_upscale);
}
GSVector2i GSRendererHW::GetTargetSize()
GSVector2i GSRendererHW::GetTargetSize(GSVector2i* unscaled_size)
{
const GSVector2i t_size = { m_width, m_height };
if (GetUpscaleMultiplier() == 1 || CanUpscale())
return t_size;
// Undo the upscaling for native resolution draws.
const GSVector2 up_s = GetTextureScaleFactor(true);
return {
static_cast<int>(std::ceil(static_cast<float>(t_size.x) / up_s.x)),
static_cast<int>(std::ceil(static_cast<float>(t_size.y) / up_s.y)),
};
// Don't blindly expand out to the scissor size if we're not drawing to it.
// e.g. Burnout 3, God of War II, etc.
u32 min_height = std::min<u32>(m_context->scissor.in.w, m_r.w);
// Another thing these games like to do, is draw a 512x896 shuffle, which would result in us
// expanding the target out to 896 height, but the extra area would all be black, with the
// draw effectively changing nothing for the new area. So, instead, lets try to detect these
// draws by double-checking we're not stretching the texture (gradient of <1).
if (PRIM->TME && m_vt.m_primclass == GS_SPRITE_CLASS && m_src && (m_src->m_target || m_src->m_from_target))
{
const float diff = std::abs((m_vt.m_max.p.y - m_vt.m_min.p.y) - (m_vt.m_max.t.y - m_vt.m_min.t.y));
if (diff <= 1.0f)
{
// Clamp to the texture size. We're working in unscaled coordinates here, so undo the upscaling.
min_height = std::min(min_height, static_cast<u32>(static_cast<float>(m_src->m_texture->GetHeight()) / m_src->m_texture->GetScale().y));
}
}
// Align to even lines, reduces the chance of tiny resizes.
min_height = Common::AlignUpPow2(min_height, 2);
const u32 width = m_context->FRAME.FBW * 64u;
const u32 height = m_tc->GetTargetHeight(m_context->FRAME.FBP, m_context->FRAME.FBW, m_context->FRAME.PSM, min_height);
if (unscaled_size)
{
unscaled_size->x = static_cast<int>(width);
unscaled_size->y = static_cast<int>(height);
}
GL_INS("Target size for %x %u %u: %ux%u", m_context->FRAME.FBP, m_context->FRAME.FBW, m_context->FRAME.PSM, width, height);
return GSVector2i(static_cast<int>(width * GSConfig.UpscaleMultiplier), static_cast<int>(height * GSConfig.UpscaleMultiplier));
}
void GSRendererHW::InvalidateVideoMem(const GIFRegBITBLTBUF& BITBLTBUF, const GSVector4i& r)
@@ -1380,6 +1358,7 @@ void GSRendererHW::Draw()
m_src = nullptr;
m_texture_shuffle = false;
m_tex_is_fb = false;
if (PRIM->TME)
{
@@ -1605,31 +1584,29 @@ void GSRendererHW::Draw()
}
}
const GSVector2i t_size = GetTargetSize();
// The rectangle of the draw
m_r = GSVector4i(m_vt.m_min.p.xyxy(m_vt.m_max.p)).rintersect(GSVector4i(context->scissor.in));
GSVector2i unscaled_size;
const GSVector2i t_size = GetTargetSize(&unscaled_size);
// Ensure draw rect is clamped to framebuffer size. Necessary for updating valid area.
m_r = m_r.rintersect(GSVector4i(0, 0, unscaled_size.x, unscaled_size.y));
TEX0.TBP0 = context->FRAME.Block();
TEX0.TBW = context->FRAME.FBW;
TEX0.PSM = context->FRAME.PSM;
GSTextureCache::Target* rt = nullptr;
GSTexture* rt_tex = nullptr;
if (!no_rt)
{
rt = m_tc->LookupTarget(TEX0, t_size, GSTextureCache::RenderTarget, true, fm);
rt_tex = rt->m_texture;
}
TEX0.TBP0 = context->ZBUF.Block();
TEX0.TBW = context->FRAME.FBW;
TEX0.PSM = context->ZBUF.PSM;
GSTextureCache::Target* ds = nullptr;
GSTexture* ds_tex = nullptr;
if (!no_ds)
{
ds = m_tc->LookupTarget(TEX0, t_size, GSTextureCache::DepthStencil, context->DepthWrite());
ds_tex = ds->m_texture;
}
if (rt)
{
@@ -1639,38 +1616,21 @@ void GSRendererHW::Draw()
rt->m_32_bits_fmt = m_texture_shuffle || (GSLocalMemory::m_psm[context->FRAME.PSM].bpp != 16);
}
// The rectangle of the draw
m_r = GSVector4i(m_vt.m_min.p.xyxy(m_vt.m_max.p)).rintersect(GSVector4i(context->scissor.in));
{
const GSVector2 up_s = GetTextureScaleFactor();
const int up_w = static_cast<int>(std::ceil(static_cast<float>(m_r.z) * up_s.x));
const int up_h = static_cast<int>(std::ceil(static_cast<float>(m_r.w) * up_s.y));
const int new_w = std::max(up_w, std::max(rt_tex ? rt_tex->GetWidth() : 0, ds_tex ? ds_tex->GetWidth() : 0));
const int new_h = std::max(up_h, std::max(rt_tex ? rt_tex->GetHeight() : 0, ds_tex ? ds_tex->GetHeight() : 0));
std::array<GSTextureCache::Target*, 2> ts{ rt, ds };
for (GSTextureCache::Target* t : ts)
// We still need to make sure the dimensions of the targets match.
const GSVector2 up_s(GetTextureScaleFactor());
const int new_w = std::max(t_size.x, std::max(rt ? rt->m_texture->GetWidth() : 0, ds ? ds->m_texture->GetWidth() : 0));
const int new_h = std::max(t_size.y, std::max(rt ? rt->m_texture->GetHeight() : 0, ds ? ds->m_texture->GetHeight() : 0));
if (rt)
{
if (t)
{
// Adjust texture size to fit current draw if necessary.
GSTexture* tex = t->m_texture;
assert(up_s == tex->GetScale());
const int w = tex->GetWidth();
const int h = tex->GetHeight();
if (w != new_w || h != new_h)
{
const bool is_rt = t == rt;
t->m_texture = is_rt ?
g_gs_device->CreateSparseRenderTarget(new_w, new_h, tex->GetFormat()) :
g_gs_device->CreateSparseDepthStencil(new_w, new_h, tex->GetFormat());
const GSVector4i r{ 0, 0, w, h };
g_gs_device->CopyRect(tex, t->m_texture, r, 0, 0);
g_gs_device->Recycle(tex);
t->m_texture->SetScale(up_s);
(is_rt ? rt_tex : ds_tex) = t->m_texture;
}
}
pxAssert(rt->m_texture->GetScale() == up_s);
rt->ResizeTexture(new_w, new_h, up_s);
}
if (ds)
{
pxAssert(ds->m_texture->GetScale() == up_s);
ds->ResizeTexture(new_w, new_h, up_s);
}
}
@@ -1704,24 +1664,24 @@ void GSRendererHW::Draw()
}
}
if (s_save && s_n >= s_saven)
if (rt && s_save && s_n >= s_saven)
{
s = StringUtil::StdStringFromFormat("%05d_f%lld_rt0_%05x_%s.bmp", s_n, frame, context->FRAME.Block(), psm_str(context->FRAME.PSM));
if (rt_tex)
rt_tex->Save(m_dump_root + s);
if (rt->m_texture)
rt->m_texture->Save(m_dump_root + s);
}
if (s_savez && s_n >= s_saven)
if (ds && s_savez && s_n >= s_saven)
{
s = StringUtil::StdStringFromFormat("%05d_f%lld_rz0_%05x_%s.bmp", s_n, frame, context->ZBUF.Block(), psm_str(context->ZBUF.PSM));
if (ds_tex)
ds_tex->Save(m_dump_root + s);
if (ds->m_texture)
ds->m_texture->Save(m_dump_root + s);
}
}
if (m_hacks.m_oi && !(this->*m_hacks.m_oi)(rt_tex, ds_tex, m_src))
if (m_hacks.m_oi && !(this->*m_hacks.m_oi)(rt ? rt->m_texture : nullptr, ds ? ds->m_texture : nullptr, m_src))
{
GL_INS("Warning skipping a draw call (%d)", s_n);
return;
@@ -1746,7 +1706,7 @@ void GSRendererHW::Draw()
OI_GsMemClear();
OI_DoubleHalfClear(rt_tex, ds_tex);
OI_DoubleHalfClear(rt, ds);
}
}
@@ -1801,7 +1761,7 @@ void GSRendererHW::Draw()
//
DrawPrims(rt_tex, ds_tex, m_src);
DrawPrims(rt ? rt->m_texture : nullptr, ds ? ds->m_texture : nullptr, m_src);
//
@@ -1853,16 +1813,16 @@ void GSRendererHW::Draw()
{
s = StringUtil::StdStringFromFormat("%05d_f%lld_rt1_%05x_%s.bmp", s_n, frame, context->FRAME.Block(), psm_str(context->FRAME.PSM));
if (rt_tex)
rt_tex->Save(m_dump_root + s);
if (rt)
rt->m_texture->Save(m_dump_root + s);
}
if (s_savez && s_n >= s_saven)
{
s = StringUtil::StdStringFromFormat("%05d_f%lld_rz1_%05x_%s.bmp", s_n, frame, context->ZBUF.Block(), psm_str(context->ZBUF.PSM));
if (ds_tex)
ds_tex->Save(m_dump_root + s);
if (ds)
rt->m_texture->Save(m_dump_root + s);
}
if (s_savel > 0 && (s_n - s_saven) > s_savel)
@@ -2080,14 +2040,17 @@ void GSRendererHW::EmulateTextureShuffleAndFbmask()
// Please bang my head against the wall!
// 1/ Reduce the frame mask to a 16 bit format
const u32 m = m_context->FRAME.FBMSK & GSLocalMemory::m_psm[m_context->FRAME.PSM].fmsk;
// fbmask is converted to a 16bit version to represent the 2 32bit channels it's writing to.
// The lower 8 bits represents the Red/Blue channels, the top 8 bits is Green/Alpha, depending on write_ba.
const u32 fbmask = ((m >> 3) & 0x1F) | ((m >> 6) & 0x3E0) | ((m >> 9) & 0x7C00) | ((m >> 16) & 0x8000);
// FIXME GSVector will be nice here
const u8 rg_mask = fbmask & 0xFF;
const u8 ba_mask = (fbmask >> 8) & 0xFF;
const u8 rb_mask = fbmask & 0xFF;
const u8 ga_mask = (fbmask >> 8) & 0xFF;
m_conf.colormask.wrgba = 0;
// 2 Select the new mask (Please someone put SSE here)
if (rg_mask != 0xFF)
if (rb_mask != 0xFF)
{
if (write_ba)
{
@@ -2099,11 +2062,11 @@ void GSRendererHW::EmulateTextureShuffleAndFbmask()
GL_INS("Color shuffle %s => R", read_ba ? "B" : "R");
m_conf.colormask.wr = 1;
}
if (rg_mask)
if (rb_mask)
m_conf.ps.fbmask = 1;
}
if (ba_mask != 0xFF)
if (ga_mask != 0xFF)
{
if (write_ba)
{
@@ -2115,16 +2078,16 @@ void GSRendererHW::EmulateTextureShuffleAndFbmask()
GL_INS("Color shuffle %s => G", read_ba ? "A" : "G");
m_conf.colormask.wg = 1;
}
if (ba_mask)
if (ga_mask)
m_conf.ps.fbmask = 1;
}
if (m_conf.ps.fbmask && enable_fbmask_emulation)
{
m_conf.cb_ps.FbMask.r = rg_mask;
m_conf.cb_ps.FbMask.g = rg_mask;
m_conf.cb_ps.FbMask.b = ba_mask;
m_conf.cb_ps.FbMask.a = ba_mask;
m_conf.cb_ps.FbMask.r = rb_mask;
m_conf.cb_ps.FbMask.g = ga_mask;
m_conf.cb_ps.FbMask.b = rb_mask;
m_conf.cb_ps.FbMask.a = ga_mask;
// No blending so hit unsafe path.
if (!PRIM->ABE || !features.texture_barrier)
@@ -2336,21 +2299,8 @@ void GSRendererHW::EmulateChannelShuffle(const GSTextureCache::Source* tex)
m_conf.tex = *tex->m_from_target;
if (m_conf.tex)
{
if (m_conf.tex == m_conf.rt)
{
// sample from fb instead
m_conf.tex = nullptr;
m_conf.ps.tex_is_fb = true;
m_conf.require_one_barrier = true;
}
else if (m_conf.tex == m_conf.ds)
{
// if depth testing is disabled, we don't need to copy, and can just unbind the depth buffer
// no need for a barrier for GL either, since it's not bound to depth and texture concurrently
// otherwise, the backend should recognise the hazard, and copy the buffer (D3D/Vulkan).
if (m_conf.depth.ztst == ZTST_ALWAYS)
m_conf.ds = nullptr;
}
// Identify when we're sampling the current buffer, defer fixup for later.
m_tex_is_fb |= (m_conf.tex == m_conf.rt || m_conf.tex == m_conf.ds);
}
// Replace current draw with a fullscreen sprite
@@ -2409,6 +2359,7 @@ void GSRendererHW::EmulateBlending(bool& DATE_PRIMID, bool& DATE_BARRIER, bool&
// Get alpha value
const bool alpha_c0_zero = (m_conf.ps.blend_c == 0 && GetAlphaMinMax().max == 0) && !IsCoverageAlpha();
const bool alpha_c0_one = (m_conf.ps.blend_c == 0 && (GetAlphaMinMax().min == 128) && (GetAlphaMinMax().max == 128)) || IsCoverageAlpha();
const bool alpha_c0_high_min_one = (m_conf.ps.blend_c == 0 && GetAlphaMinMax().min > 128) && !IsCoverageAlpha();
const bool alpha_c0_high_max_one = (m_conf.ps.blend_c == 0 && GetAlphaMinMax().max > 128) && !IsCoverageAlpha();
const bool alpha_c2_zero = (m_conf.ps.blend_c == 2 && ALPHA.FIX == 0u);
const bool alpha_c2_one = (m_conf.ps.blend_c == 2 && ALPHA.FIX == 128u);
@@ -2762,12 +2713,26 @@ void GSRendererHW::EmulateBlending(bool& DATE_PRIMID, bool& DATE_BARRIER, bool&
// For mixed blend, the source blend is done in the shader (so we use CONST_ONE as a factor).
m_conf.blend = {true, GSDevice::CONST_ONE, blend.dst, blend.op, m_conf.ps.blend_c == 2, ALPHA.FIX};
m_conf.ps.blend_mix = 1;
// Elide DSB colour output if not used by dest.
m_conf.ps.no_color1 |= !GSDevice::IsDualSourceBlendFactor(blend.dst);
if (blend_mix1)
{
if (m_conf.ps.blend_b == m_conf.ps.blend_d && (alpha_c0_high_min_one || alpha_c2_high_one))
{
// Replace Cs*As + Cd*(1 - As) with Cs*As - Cd*(As - 1).
// Replace Cs*F + Cd*(1 - F) with Cs*F - Cd*(F - 1).
// As - 1 or F - 1 subtraction is only done for the dual source output (hw blending part) since we are changing the equation.
// Af will be replaced with As in shader and send it to dual source output.
m_conf.blend = {true, GSDevice::CONST_ONE, GSDevice::SRC1_ALPHA, GSDevice::OP_SUBTRACT, false, 0};
// clr_hw 1 will disable alpha clamp, we can reuse the old bits.
m_conf.ps.clr_hw = 1;
//m_conf.ps.blend_mix = 0;
// DSB output will always be used.
m_conf.ps.no_color1 = false;
}
m_conf.ps.blend_a = 0;
m_conf.ps.blend_b = 2;
m_conf.ps.blend_d = 2;
@@ -3150,6 +3115,41 @@ void GSRendererHW::EmulateTextureSampler(const GSTextureCache::Source* tex)
if (!m_channel_shuffle)
m_conf.tex = tex->m_texture;
m_conf.pal = tex->m_palette;
// Detect framebuffer read that will need special handling
if (m_tex_is_fb)
{
if (m_conf.tex == m_conf.rt)
{
// This pattern is used by several games to emulate a stencil (shadow)
// Ratchet & Clank, Jak do alpha integer multiplication (tfx) which is mostly equivalent to +1/-1
// Tri-Ace (Star Ocean 3/RadiataStories/VP2) uses a palette to handle the +1/-1
GL_DBG("Source and Target are the same! Let's sample the framebuffer");
m_conf.tex = nullptr;
m_conf.ps.tex_is_fb = true;
if (m_prim_overlap == PRIM_OVERLAP_NO || !g_gs_device->Features().texture_barrier)
m_conf.require_one_barrier = true;
else
m_conf.require_full_barrier = true;
}
else if (m_conf.tex == m_conf.ds)
{
// if depth testing is disabled, we don't need to copy, and can just unbind the depth buffer
// no need for a barrier for GL either, since it's not bound to depth and texture concurrently
// otherwise, the backend should recognise the hazard, and copy the buffer (D3D/Vulkan).
if (m_conf.depth.ztst == ZTST_ALWAYS)
{
m_conf.ds = nullptr;
m_tex_is_fb = false;
}
}
else
{
// weird... we detected a fb read, but didn't end up using it?
DevCon.WriteLn("Tex-is-FB set but not used?");
m_tex_is_fb = false;
}
}
}
void GSRendererHW::EmulateATST(float& AREF, GSHWDrawConfig::PSSelector& ps, bool pass_2)
@@ -3217,10 +3217,6 @@ void GSRendererHW::DrawPrims(GSTexture* rt, GSTexture* ds, GSTextureCache::Sourc
area_out.x, area_out.y, area_out.z, area_out.w);
#endif
const GSVector2i& rtsize = ds ? ds->GetSize() : rt->GetSize();
const GSVector2& rtscale = ds ? ds->GetScale() : rt->GetScale();
const GSDevice::FeatureSupport features(g_gs_device->Features());
const bool DATE = m_context->TEST.DATE && m_context->FRAME.PSM != PSM_PSMCT24;
bool DATE_PRIMID = false;
bool DATE_BARRIER = false;
@@ -3250,32 +3246,12 @@ void GSRendererHW::DrawPrims(GSTexture* rt, GSTexture* ds, GSTextureCache::Sourc
// Upscaling hack to avoid various line/grid issues
MergeSprite(tex);
const GSDevice::FeatureSupport features(g_gs_device->Features());
if (!features.framebuffer_fetch)
m_prim_overlap = PrimitiveOverlap();
else
m_prim_overlap = PRIM_OVERLAP_UNKNOW;
// Detect framebuffer read that will need special handling
if (features.texture_barrier && (m_context->FRAME.Block() == m_context->TEX0.TBP0) && PRIM->TME && GSConfig.AccurateBlendingUnit != AccBlendLevel::Minimum)
{
const u32 fb_mask = GSLocalMemory::m_psm[m_context->FRAME.PSM].fmsk;
if (((m_context->FRAME.FBMSK & fb_mask) == (fb_mask & 0x00FFFFFF)) && (m_vt.m_primclass == GS_TRIANGLE_CLASS))
{
// This pattern is used by several games to emulate a stencil (shadow)
// Ratchet & Clank, Jak do alpha integer multiplication (tfx) which is mostly equivalent to +1/-1
// Tri-Ace (Star Ocean 3/RadiataStories/VP2) uses a palette to handle the +1/-1
GL_DBG("Source and Target are the same! Let's sample the framebuffer");
m_conf.ps.tex_is_fb = 1;
m_conf.require_full_barrier = !features.framebuffer_fetch;
}
else if (m_prim_overlap != PRIM_OVERLAP_NO)
{
// Note: It is fine if the texture fits in a single GS page. First access will cache
// the page in the GS texture buffer.
GL_INS("ERROR: Source and Target are the same!");
}
}
EmulateTextureShuffleAndFbmask();
// DATE: selection of the algorithm. Must be done before blending because GL42 is not compatible with blending
@@ -3418,6 +3394,8 @@ void GSRendererHW::DrawPrims(GSTexture* rt, GSTexture* ds, GSTextureCache::Sourc
m_conf.vs.fst = PRIM->FST;
// FIXME D3D11 and GL support half pixel center. Code could be easier!!!
const GSVector2i rtsize(m_conf.ds ? m_conf.ds->GetSize() : m_conf.rt->GetSize());
const GSVector2 rtscale(m_conf.ds ? m_conf.ds->GetScale() : m_conf.rt->GetScale());
const float sx = 2.0f * rtscale.x / (rtsize.x << 4);
const float sy = 2.0f * rtscale.y / (rtsize.y << 4);
const float ox = (float)(int)m_context->XYOFFSET.OFX;
@@ -3471,7 +3449,7 @@ void GSRendererHW::DrawPrims(GSTexture* rt, GSTexture* ds, GSTextureCache::Sourc
if (m_conf.ps.dither)
{
GL_DBG("DITHERING mode ENABLED (%d)", m_dithering);
GL_DBG("DITHERING mode ENABLED (%d)", GSConfig.Dithering);
m_conf.ps.dither = GSConfig.Dithering;
m_conf.cb_ps.DitherMatrix[0] = GSVector4(m_env.DIMX.DM00, m_env.DIMX.DM01, m_env.DIMX.DM02, m_env.DIMX.DM03);
@@ -3601,12 +3579,6 @@ void GSRendererHW::DrawPrims(GSTexture* rt, GSTexture* ds, GSTextureCache::Sourc
SetupIA(sx, sy);
if (rt)
rt->CommitRegion(GSVector2i(m_conf.drawarea.z, m_conf.drawarea.w));
if (ds)
ds->CommitRegion(GSVector2i(m_conf.drawarea.z, m_conf.drawarea.w));
m_conf.alpha_second_pass.enable = ate_second_pass;
if (ate_second_pass)
@@ -3722,13 +3694,6 @@ void GSRendererHW::DrawPrims(GSTexture* rt, GSTexture* ds, GSTextureCache::Sourc
g_gs_device->RenderHW(m_conf);
}
bool GSRendererHW::IsDummyTexture() const
{
// Texture is actually the frame buffer. Stencil emulation to compute shadow (Jak series/tri-ace game)
// Will hit the "m_ps_sel.tex_is_fb = 1" path in the draw
return g_gs_device->Features().texture_barrier && (m_context->FRAME.Block() == m_context->TEX0.TBP0) && PRIM->TME && GSConfig.AccurateBlendingUnit != AccBlendLevel::Minimum && m_vt.m_primclass == GS_TRIANGLE_CLASS && (m_context->FRAME.FBMSK == 0x00FFFFFF);
}
bool GSRendererHW::CanUseSwPrimRender(bool no_rt, bool no_ds, bool draw_sprite_tex)
{
// Master enable.
@@ -4177,10 +4142,8 @@ bool GSRendererHW::SwPrimRender()
GSRendererHW::Hacks::Hacks()
: m_oi_map(m_oi_list)
, m_oo_map(m_oo_list)
, m_cu_map(m_cu_list)
, m_oi(NULL)
, m_oo(NULL)
, m_cu(NULL)
{
m_oi_list.push_back(HackEntry<OI_Ptr>(CRC::BigMuthaTruckers, CRC::RegionCount, &GSRendererHW::OI_BigMuthaTruckers));
m_oi_list.push_back(HackEntry<OI_Ptr>(CRC::DBZBT2, CRC::RegionCount, &GSRendererHW::OI_DBZBTGames));
@@ -4206,7 +4169,6 @@ void GSRendererHW::Hacks::SetGameCRC(const CRC::Game& game)
m_oi = m_oi_map[hash];
m_oo = m_oo_map[hash];
m_cu = m_cu_map[hash];
if (GSConfig.PointListPalette)
{
@@ -4220,7 +4182,7 @@ void GSRendererHW::Hacks::SetGameCRC(const CRC::Game& game)
// Trick to do a fast clear on the GS
// Set frame buffer pointer on the start of the buffer. Set depth buffer pointer on the half buffer
// FB + depth write will fill the full buffer.
void GSRendererHW::OI_DoubleHalfClear(GSTexture* rt, GSTexture* ds)
void GSRendererHW::OI_DoubleHalfClear(GSTextureCache::Target*& rt, GSTextureCache::Target*& ds)
{
// Note gs mem clear must be tested before calling this function
@@ -4266,20 +4228,35 @@ void GSRendererHW::OI_DoubleHalfClear(GSTexture* rt, GSTexture* ds)
GL_INS("OI_DoubleHalfClear:%s: base %x half %x. w_pages %d h_pages %d fbw %d. Color %x",
clear_depth ? "depth" : "target", base << 5, half << 5, w_pages, h_pages, m_context->FRAME.FBW, color);
// Commit texture with a factor 2 on the height
GSTexture* t = clear_depth ? ds : rt;
const GSVector4i commitRect = ComputeBoundingBox(t->GetScale(), t->GetSize());
t->CommitRegion(GSVector2i(commitRect.z, 2 * commitRect.w));
// Handle the case where the game stacks FBP and ZBP immediately after one another.
// We incorrectly compute the height here, because both the scissor and draw rectangle will only be half
// the height of what's effectively being cleared. Spider-Man 2's shadows are a good test case here: it
// draws the shadow map to a 128x128 texture, but relies on a 1 pixel border around the edge to "cut off"
// the shadows. We cap it to a 256 height, because having a >=512 height framebuffer is very rare, and it
// stops us doubling actual framebuffers unintentionally (very common).
GSTextureCache::Target* t = clear_depth ? ds : rt;
const u32 unscaled_height = static_cast<u32>(static_cast<float>(t->m_texture->GetHeight()) / t->m_texture->GetScale().y);
if (unscaled_height == m_context->scissor.in.w && unscaled_height <= 256)
{
t->ResizeTexture(t->m_texture->GetWidth(), t->m_texture->GetHeight() * 2, t->m_texture->GetScale());
if (clear_depth)
rt = nullptr;
else
ds = nullptr;
// Feed it back into the height cache.
m_tc->GetTargetHeight(t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM, unscaled_height * 2);
}
if (clear_depth)
{
// Only pure clear are supported for depth
ASSERT(color == 0);
g_gs_device->ClearDepth(t);
g_gs_device->ClearDepth(ds->m_texture);
}
else
{
g_gs_device->ClearRenderTarget(t, color);
g_gs_device->ClearRenderTarget(rt->m_texture, color);
}
}
}
@@ -4295,10 +4272,7 @@ void GSRendererHW::OI_DoubleHalfClear(GSTexture* rt, GSTexture* ds)
// If both buffers are side by side we can expect a fast clear in on-going
const u32 color = v[1].RGBAQ.U32[0];
const GSVector4i commitRect = ComputeBoundingBox(rt->GetScale(), rt->GetSize());
rt->CommitRegion(GSVector2i(commitRect.z, commitRect.w));
g_gs_device->ClearRenderTarget(rt, color);
g_gs_device->ClearRenderTarget(rt->m_texture, color);
}
}
@@ -4582,8 +4556,6 @@ bool GSRendererHW::OI_FFX(GSTexture* rt, GSTexture* ds, GSTextureCache::Source*
{
// random battle transition (z buffer written directly, clear it now)
GL_INS("OI_FFX ZB clear");
if (ds)
ds->Commit(); // Don't bother to save few MB for a single game
g_gs_device->ClearDepth(ds);
}
@@ -4635,7 +4607,6 @@ bool GSRendererHW::OI_RozenMaidenGebetGarden(GSTexture* rt, GSTexture* ds, GSTex
if (GSTextureCache::Target* tmp_rt = m_tc->LookupTarget(TEX0, GetTargetSize(), GSTextureCache::RenderTarget, true))
{
GL_INS("OI_RozenMaidenGebetGarden FB clear");
tmp_rt->m_texture->Commit(); // Don't bother to save few MB for a single game
g_gs_device->ClearRenderTarget(tmp_rt->m_texture, 0);
}
@@ -4654,7 +4625,6 @@ bool GSRendererHW::OI_RozenMaidenGebetGarden(GSTexture* rt, GSTexture* ds, GSTex
if (GSTextureCache::Target* tmp_ds = m_tc->LookupTarget(TEX0, GetTargetSize(), GSTextureCache::DepthStencil, true))
{
GL_INS("OI_RozenMaidenGebetGarden ZB clear");
tmp_ds->m_texture->Commit(); // Don't bother to save few MB for a single game
g_gs_device->ClearDepth(tmp_ds->m_texture);
}
@@ -4689,12 +4659,14 @@ bool GSRendererHW::OI_SonicUnleashed(GSTexture* rt, GSTexture* ds, GSTextureCach
GL_INS("OI_SonicUnleashed replace draw by a copy");
GSTextureCache::Target* src = m_tc->LookupTarget(Texture, GetTargetSize(), GSTextureCache::RenderTarget, true);
GSTextureCache::Target* src = m_tc->LookupTarget(Texture, GSVector2i(1, 1), GSTextureCache::RenderTarget, true);
const GSVector2i size = rt->GetSize();
const GSVector2i rt_size(rt->GetSize());
const GSVector2i src_size(src->m_texture->GetSize());
const GSVector2i copy_size(std::min(rt_size.x, src_size.x), std::min(rt_size.y, src_size.y));
const GSVector4 sRect(0, 0, 1, 1);
const GSVector4 dRect(0, 0, size.x, size.y);
const GSVector4 sRect(0.0f, 0.0f, static_cast<float>(copy_size.x) / static_cast<float>(src_size.x), static_cast<float>(copy_size.y) / static_cast<float>(src_size.y));
const GSVector4 dRect(0, 0, copy_size.x, copy_size.y);
g_gs_device->StretchRect(src->m_texture, sRect, rt, dRect, true, true, true, false);
@@ -4772,8 +4744,6 @@ bool GSRendererHW::OI_SuperManReturns(GSTexture* rt, GSTexture* ds, GSTextureCac
ASSERT((v->RGBAQ.A << 24 | v->RGBAQ.B << 16 | v->RGBAQ.G << 8 | v->RGBAQ.R) == (int)v->XYZ.Z);
// Do a direct write
if (rt)
rt->Commit(); // Don't bother to save few MB for a single game
g_gs_device->ClearRenderTarget(rt, GSVector4(m_vt.m_min.c));
m_tc->InvalidateVideoMemType(GSTextureCache::DepthStencil, ctx->FRAME.Block());
@@ -4810,8 +4780,6 @@ bool GSRendererHW::OI_ArTonelico2(GSTexture* rt, GSTexture* ds, GSTextureCache::
if (m_vertex.next == 2 && !PRIM->TME && m_context->FRAME.FBW == 10 && v->XYZ.Z == 0 && m_context->TEST.ZTST == ZTST_ALWAYS)
{
GL_INS("OI_ArTonelico2");
if (ds)
ds->Commit(); // Don't bother to save few MB for a single game
g_gs_device->ClearDepth(ds);
}
+6 -12
View File
@@ -29,9 +29,6 @@ public:
static constexpr int MAX_FRAMEBUFFER_HEIGHT = 1280;
private:
int m_width;
int m_height;
static constexpr float SSR_UV_TOLERANCE = 1.0f;
#pragma region hacks
@@ -43,7 +40,7 @@ private:
// Require special argument
bool OI_BlitFMV(GSTextureCache::Target* _rt, GSTextureCache::Source* t, const GSVector4i& r_draw);
void OI_GsMemClear(); // always on
void OI_DoubleHalfClear(GSTexture* rt, GSTexture* ds); // always on
void OI_DoubleHalfClear(GSTextureCache::Target*& rt, GSTextureCache::Target*& ds); // always on
bool OI_BigMuthaTruckers(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t);
bool OI_DBZBTGames(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t);
@@ -108,16 +105,13 @@ private:
std::list<HackEntry<OI_Ptr>> m_oi_list;
std::list<HackEntry<OO_Ptr>> m_oo_list;
std::list<HackEntry<CU_Ptr>> m_cu_list;
FunctionMap<OI_Ptr> m_oi_map;
FunctionMap<OO_Ptr> m_oo_map;
FunctionMap<CU_Ptr> m_cu_map;
public:
OI_Ptr m_oi;
OO_Ptr m_oo;
CU_Ptr m_cu;
Hacks();
@@ -157,6 +151,7 @@ private:
GSTextureCache::Source* m_src;
bool m_reset;
bool m_tex_is_fb;
bool m_channel_shuffle;
bool m_userhacks_tcoffset;
float m_userhacks_tcoffset_x;
@@ -183,16 +178,15 @@ public:
void SetGameCRC(u32 crc, int options) override;
bool CanUpscale() override;
int GetUpscaleMultiplier() override;
void SetScaling();
void Lines2Sprites();
void EmulateAtst(GSVector4& FogColor_AREF, u8& atst, const bool pass_2);
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);
GSVector2 GetTextureScaleFactor(const bool force_upscaling);
GSVector2 GetTextureScaleFactor() override;
GSVector2i GetTargetSize();
GSVector2i GetOutputSize(int real_h);
GSVector2i GetTargetSize(GSVector2i* unscaled_size = nullptr);
void Reset(bool hardware_reset) override;
void UpdateSettings(const Pcsx2Config::GSOptions& old_config) override;
@@ -208,8 +202,8 @@ public:
void PurgeTextureCache() override;
// Called by the texture cache to know if current texture is useful
bool IsDummyTexture() const;
bool UpdateTexIsFB(GSTextureCache::Target* src, const GIFRegTEX0& TEX0);
// Called by the texture cache when optimizing the copy range for sources
bool IsPossibleTextureShuffle(GSTextureCache::Source* src) const;
bool IsPossibleTextureShuffle(GSTextureCache::Target* dst, const GIFRegTEX0& TEX0) const;
};
+177 -39
View File
@@ -83,6 +83,7 @@ void GSTextureCache::RemoveAll()
m_hash_cache_memory_usage = 0;
m_palette_map.Clear();
m_target_heights.clear();
}
GSTextureCache::Source* GSTextureCache::LookupDepthSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GSVector4i& r, bool palette)
@@ -416,7 +417,7 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const GIFRegTEX0& TEX0, con
GSTextureCache::Target* GSTextureCache::LookupTarget(const GIFRegTEX0& TEX0, const GSVector2i& size, int type, bool used, u32 fbmask, const bool is_frame, const int real_h)
{
const GSLocalMemory::psm_t& psm_s = GSLocalMemory::m_psm[TEX0.PSM];
const GSVector2& new_s = g_gs_renderer->GetTextureScaleFactor();
const GSVector2& new_s = static_cast<GSRendererHW*>(g_gs_renderer.get())->GetTextureScaleFactor();
const u32 bp = TEX0.TBP0;
GSVector2 res_size{ 0, 0 };
GSVector2i new_size{ 0, 0 };
@@ -468,6 +469,9 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(const GIFRegTEX0& TEX0, con
{
dst = t;
GL_CACHE("TC: Lookup Frame %dx%d, perfect hit: %d (0x%x -> 0x%x %s)", size.x, size.y, dst->m_texture->GetID(), bp, t->m_end_block, psm_str(TEX0.PSM));
if (real_h > 0)
ScaleTargetForDisplay(dst, TEX0, real_h);
break;
}
}
@@ -517,8 +521,8 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(const GIFRegTEX0& TEX0, con
if (new_s != old_s)
{
calcRescale(dst->m_texture);
GSTexture* tex = type == RenderTarget ? g_gs_device->CreateSparseRenderTarget(new_size.x, new_size.y, GSTexture::Format::Color, clear) :
g_gs_device->CreateSparseDepthStencil(new_size.x, new_size.y, GSTexture::Format::DepthStencil, clear);
GSTexture* tex = type == RenderTarget ? g_gs_device->CreateRenderTarget(new_size.x, new_size.y, GSTexture::Format::Color, clear) :
g_gs_device->CreateDepthStencil(new_size.x, new_size.y, GSTexture::Format::DepthStencil, clear);
g_gs_device->StretchRect(dst->m_texture, sRect, tex, dRect, (type == RenderTarget) ? ShaderConvert::COPY : ShaderConvert::DEPTH_COPY, false);
g_gs_device->Recycle(dst->m_texture);
tex->SetScale(new_s);
@@ -619,7 +623,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(const GIFRegTEX0& TEX0, con
return dst;
}
GSTextureCache::Target* GSTextureCache::LookupTarget(const GIFRegTEX0& TEX0, const GSVector2i& size, const int real_h)
GSTextureCache::Target* GSTextureCache::LookupDisplayTarget(const GIFRegTEX0& TEX0, const GSVector2i& size, const int real_h)
{
return LookupTarget(TEX0, size, RenderTarget, true, 0, true, real_h);
}
@@ -675,6 +679,9 @@ void GSTextureCache::ScaleTargetForDisplay(Target* t, const GIFRegTEX0& dispfb,
// We unconditionally preload the frame here, because otherwise we'll end up with blackness for one frame (when the expand happens).
t->m_dirty.push_back(GSDirtyRect(GSVector4i(0, 0, t->m_TEX0.TBW * 64, needed_height), t->m_TEX0.PSM, t->m_TEX0.TBW));
// Inject the new height back into the cache.
GetTargetHeight(t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM, static_cast<u32>(needed_height));
}
// Goal: Depth And Target at the same address is not possible. On GS it is
@@ -1187,6 +1194,20 @@ bool GSTextureCache::Move(u32 SBP, u32 SBW, u32 SPSM, int sx, int sy, u32 DBP, u
const int scaled_w = static_cast<int>(w * scale.x);
const int scaled_h = static_cast<int>(h * scale.y);
// Expand the target when we used a more conservative size.
const int required_dh = scaled_dy + scaled_h;
if ((scaled_dx + scaled_w) <= dst->m_texture->GetWidth() && required_dh > dst->m_texture->GetHeight())
{
const int new_height = dy + h;
if (new_height > GSRendererHW::MAX_FRAMEBUFFER_HEIGHT)
return false;
const int scaled_new_height = static_cast<int>(static_cast<float>(new_height) * scale.y);
GL_INS("Resize %dx%d target to %dx%d for move", dst->m_texture->GetWidth(), dst->m_texture->GetHeight(), dst->m_texture->GetHeight(), scaled_new_height);
dst->ResizeTexture(dst->m_texture->GetWidth(), scaled_new_height);
GetTargetHeight(DBP, DBW, DPSM, new_height);
}
// Make sure the copy doesn't go out of bounds (it shouldn't).
if ((scaled_sx + scaled_w) > src->m_texture->GetWidth() || (scaled_sy + scaled_h) > src->m_texture->GetHeight() ||
(scaled_dx + scaled_w) > dst->m_texture->GetWidth() || (scaled_dy + scaled_h) > dst->m_texture->GetHeight())
@@ -1230,6 +1251,36 @@ GSTextureCache::Target* GSTextureCache::GetTargetWithSharedBits(u32 BP, u32 PSM)
return nullptr;
}
u32 GSTextureCache::GetTargetHeight(u32 fbp, u32 fbw, u32 psm, u32 min_height)
{
TargetHeightElem search = {};
search.fbp = fbp;
search.fbw = fbw;
search.psm = psm;
search.height = min_height;
for (auto it = m_target_heights.begin(); it != m_target_heights.end(); ++it)
{
TargetHeightElem& elem = const_cast<TargetHeightElem&>(*it);
if (elem.bits == search.bits)
{
if (elem.height < min_height)
{
DbgCon.WriteLn("Expand height at %x %u %u from %u to %u", fbp, fbw, psm, elem.height, min_height);
elem.height = min_height;
}
m_target_heights.MoveFront(it.Index());
elem.age = 0;
return elem.height;
}
}
DbgCon.WriteLn("New height at %x %u %u: %u", fbp, fbw, psm, min_height);
m_target_heights.push_front(search);
return min_height;
}
// Hack: remove Target that are strictly included in current rt. Typically uses for FMV
// For example, game is rendered at 0x800->0x1000, fmv will be uploaded to 0x0->0x2800
// FIXME In theory, we ought to report the data from the sub rt to the main rt. But let's
@@ -1345,6 +1396,20 @@ void GSTextureCache::IncAge()
}
}
}
for (auto it = m_target_heights.begin(); it != m_target_heights.end();)
{
TargetHeightElem& elem = const_cast<TargetHeightElem&>(*it);
if (elem.age >= max_rt_age)
{
it = m_target_heights.erase(it);
}
else
{
elem.age++;
++it;
}
}
}
//Fixme: Several issues in here. Not handling depth stencil, pitch conversion doesnt work.
@@ -1393,7 +1458,7 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con
AttachPaletteToSource(src, psm.pal, true);
}
}
else if (dst && GSRendererHW::GetInstance()->IsDummyTexture())
else if (dst && GSRendererHW::GetInstance()->UpdateTexIsFB(dst, TEX0))
{
// This shortcut is a temporary solution. It isn't a good solution
// as it won't work with Channel Shuffle/Texture Shuffle pattern
@@ -1405,21 +1470,23 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con
// Be aware that you can't use StrechRect between BeginScene/EndScene.
// So it could be tricky to put in the middle of the DrawPrims
// Texture is created to keep code compatibility
GSTexture* dTex = g_gs_device->CreateTexture(tw, th, false, GSTexture::Format::Color, true);
// Keep a trace of origin of the texture
src->m_texture = dTex;
src->m_texture = dst->m_texture;
src->m_target = true;
src->m_shared_texture = true;
src->m_from_target = &dst->m_texture;
src->m_from_target_TEX0 = dst->m_TEX0;
src->m_end_block = dst->m_end_block;
src->m_texture->SetScale(dst->m_texture->GetScale());
src->m_32_bits_fmt = dst->m_32_bits_fmt;
// Even if we sample the framebuffer directly we might need the palette
// to handle the format conversion on GPU
if (psm.pal > 0)
AttachPaletteToSource(src, psm.pal, true);
// This will get immediately invalidated.
m_temporary_source = src;
}
else if (dst)
{
@@ -1561,7 +1628,7 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con
{
// if it looks like a texture shuffle, we might read up to +/- 8 pixels on either side.
GSVector4 adjusted_src_range(*src_range);
if (GSRendererHW::GetInstance()->IsPossibleTextureShuffle(src))
if (GSRendererHW::GetInstance()->IsPossibleTextureShuffle(dst, TEX0))
adjusted_src_range += GSVector4(-8.0f, 0.0f, 8.0f, 0.0f);
// don't forget to scale the copy range
@@ -1575,13 +1642,55 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con
}
}
// Don't be fooled by the name. 'dst' is the old target (hence the input)
// 'src' is the new texture cache entry (hence the output)
GSTexture* sTex = dst->m_texture;
GSTexture* dTex = use_texture ?
g_gs_device->CreateTexture(w, h, false, GSTexture::Format::Color, true) :
g_gs_device->CreateRenderTarget(w, h, GSTexture::Format::Color, false);
src->m_texture = dTex;
// Assuming everything matches up, instead of copying the target, we can just sample it directly.
// It's the same as doing the copy first, except we save GPU time.
if (!half_right && // not the size change from above
use_texture && // not reinterpreting the RT
w == dst->m_texture->GetWidth() && h == dst->m_texture->GetHeight() && // same dimensions
!m_temporary_source // not the shuffle case above
)
{
// sample the target directly
src->m_texture = dst->m_texture;
src->m_shared_texture = true;
src->m_target = true; // So renderer can check if a conversion is required
src->m_from_target = &dst->m_texture; // avoid complex condition on the renderer
src->m_from_target_TEX0 = dst->m_TEX0;
src->m_32_bits_fmt = dst->m_32_bits_fmt;
src->m_valid_rect = dst->m_valid;
src->m_end_block = dst->m_end_block;
// kill the source afterwards, since we don't want to have to track changes to the target
m_temporary_source = src;
}
else
{
// Don't be fooled by the name. 'dst' is the old target (hence the input)
// 'src' is the new texture cache entry (hence the output)
GSTexture* sTex = dst->m_texture;
GSTexture* dTex = use_texture ?
g_gs_device->CreateTexture(w, h, false, GSTexture::Format::Color, true) :
g_gs_device->CreateRenderTarget(w, h, GSTexture::Format::Color, false);
src->m_texture = dTex;
if (use_texture)
{
g_gs_device->CopyRect(sTex, dTex, sRect, destX, destY);
}
else
{
GSVector4 sRectF(sRect);
sRectF.z /= sTex->GetWidth();
sRectF.w /= sTex->GetHeight();
g_gs_device->StretchRect(sTex, sRectF, dTex, GSVector4(destX, destY, w, h), shader, false);
}
if (src->m_texture)
src->m_texture->SetScale(scale);
else
ASSERT(0);
}
// GH: by default (m_paltex == 0) GS converts texture to the 32 bit format
// However it is different here. We want to reuse a Render Target as a texture.
@@ -1591,24 +1700,6 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con
AttachPaletteToSource(src, psm.pal, true);
}
if (use_texture)
{
g_gs_device->CopyRect(sTex, dTex, sRect, destX, destY);
}
else
{
GSVector4 sRectF(sRect);
sRectF.z /= sTex->GetWidth();
sRectF.w /= sTex->GetHeight();
g_gs_device->StretchRect(sTex, sRectF, dTex, GSVector4(destX, destY, w, h), shader, false);
}
if (src->m_texture)
src->m_texture->SetScale(scale);
else
ASSERT(0);
// Offset hack. Can be enabled via GS options.
// The offset will be used in Draw().
float modxy = 0.0f;
@@ -1813,16 +1904,16 @@ GSTextureCache::Target* GSTextureCache::CreateTarget(const GIFRegTEX0& TEX0, int
if (type == RenderTarget)
{
t->m_texture = g_gs_device->CreateSparseRenderTarget(w, h, GSTexture::Format::Color, clear);
t->m_texture = g_gs_device->CreateRenderTarget(w, h, GSTexture::Format::Color, clear);
t->m_used = true; // FIXME
}
else if (type == DepthStencil)
{
t->m_texture = g_gs_device->CreateSparseDepthStencil(w, h, GSTexture::Format::DepthStencil, clear);
t->m_texture = g_gs_device->CreateDepthStencil(w, h, GSTexture::Format::DepthStencil, clear);
}
t->m_texture->SetScale(g_gs_renderer->GetTextureScaleFactor());
t->m_texture->SetScale(static_cast<GSRendererHW*>(g_gs_renderer.get())->GetTextureScaleFactor());
m_dst[type].push_front(t);
@@ -1999,6 +2090,47 @@ bool GSTextureCache::Surface::Overlaps(u32 bp, u32 bw, u32 psm, const GSVector4i
return overlap;
}
void GSTextureCache::Surface::ResizeTexture(int new_width, int new_height)
{
ResizeTexture(new_width, new_height, m_texture->GetScale());
}
void GSTextureCache::Surface::ResizeTexture(int new_width, int new_height, GSVector2 new_scale)
{
const int width = m_texture->GetWidth();
const int height = m_texture->GetHeight();
if (width == new_width && height == new_height)
return;
const bool clear = (new_width > width || new_height > height);
GSTexture* tex = m_texture->IsDepthStencil() ?
g_gs_device->CreateDepthStencil(new_width, new_height, m_texture->GetFormat(), clear) :
g_gs_device->CreateRenderTarget(new_width, new_height, m_texture->GetFormat(), clear);
if (!tex)
{
Console.Error("(GSTextureCache::Surface::ResizeTexture) Failed to allocate %dx%d texture", new_width, new_height);
return;
}
tex->SetScale(new_scale);
const GSVector4i rc(0, 0, std::min(width, new_width), std::min(height, new_height));
if (tex->IsDepthStencil())
{
// Can't do partial copies in DirectX for depth textures, and it's probably not ideal in other
// APIs either. So use a fullscreen quad setting depth instead.
g_gs_device->StretchRect(m_texture, tex, GSVector4(rc), ShaderConvert::DEPTH_COPY, false);
}
else
{
// Fast memcpy()-like path for color targets.
g_gs_device->CopyRect(m_texture, tex, rc, 0, 0);
}
g_gs_device->Recycle(m_texture);
m_texture = tex;
}
// GSTextureCache::Source
GSTextureCache::Source::Source(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, bool dummy_container)
@@ -2410,9 +2542,15 @@ void GSTextureCache::Target::UpdateIfDirtyIntersects(const GSVector4i& rc)
void GSTextureCache::Target::UpdateValidity(const GSVector4i& rect)
{
m_valid = m_valid.runion(rect);
if (m_valid.eq(GSVector4i::zero()))
m_valid = rect;
else
m_valid = m_valid.runion(rect);
// Block of the bottom right texel of the validity rectangle, last valid block of the texture
// TODO: This is not correct when the PSM changes. e.g. a 512x448 target being shuffled will become 512x896 temporarily, and
// at the moment, we blow the valid rect out to twice the size. The only thing stopping everything breaking is the fact
// that we clamp the draw rect to the target size in GSRendererHW::Draw().
m_end_block = GSLocalMemory::m_psm[m_TEX0.PSM].info.bn(m_valid.z - 1, m_valid.w - 1, m_TEX0.TBP0, m_TEX0.TBW); // Valid only for color formats
// GL_CACHE("UpdateValidity (0x%x->0x%x) from R:%d,%d Valid: %d,%d", m_TEX0.TBP0, m_end_block, rect.z, rect.w, m_valid.z, m_valid.w);
+30 -5
View File
@@ -92,6 +92,9 @@ public:
void UpdateAge();
bool Inside(u32 bp, u32 bw, u32 psm, const GSVector4i& rect);
bool Overlaps(u32 bp, u32 bw, u32 psm, const GSVector4i& rect);
void ResizeTexture(int new_width, int new_height);
void ResizeTexture(int new_width, int new_height, GSVector2 new_scale);
};
struct PaletteKey
@@ -243,6 +246,25 @@ public:
void RemoveAt(Source* s);
};
struct TargetHeightElem
{
union
{
u32 bits;
struct
{
u32 fbp : 9;
u32 fbw : 6;
u32 psm : 6;
u32 pad : 11;
};
};
u32 height;
u32 age;
};
struct SurfaceOffsetKeyElem
{
u32 psm;
@@ -278,6 +300,7 @@ protected:
std::unordered_map<HashCacheKey, HashCacheEntry, HashCacheKeyHash> m_hash_cache;
u64 m_hash_cache_memory_usage = 0;
FastList<Target*> m_dst[2];
FastList<TargetHeightElem> m_target_heights;
static u8* m_temp;
constexpr static size_t S_SURFACE_OFFSET_CACHE_MAX_SIZE = std::numeric_limits<u16>::max();
std::unordered_map<SurfaceOffsetKey, SurfaceOffset, SurfaceOffsetKeyHash, SurfaceOffsetKeyEqual> m_surface_offset_cache;
@@ -286,6 +309,10 @@ protected:
Source* CreateSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, Target* t = NULL, bool half_right = false, int x_offset = 0, int y_offset = 0, const GSVector2i* lod = nullptr, const GSVector4i* src_range = nullptr);
Target* CreateTarget(const GIFRegTEX0& TEX0, int w, int h, int type, const bool clear);
/// Expands a target when the block pointer for a display framebuffer is within another target, but the read offset
/// plus the height is larger than the current size of the target.
void ScaleTargetForDisplay(Target* t, const GIFRegTEX0& dispfb, int real_h);
HashCacheEntry* LookupHashCache(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, bool& paltex, const u32* clut, const GSVector2i* lod);
static void PreloadTexture(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, GSLocalMemory& mem, bool paltex, GSTexture* tex, u32 level);
@@ -309,12 +336,14 @@ public:
Source* LookupDepthSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GSVector4i& r, bool palette = false);
Target* LookupTarget(const GIFRegTEX0& TEX0, const GSVector2i& size, int type, bool used, u32 fbmask = 0, const bool is_frame = false, const int real_h = 0);
Target* LookupTarget(const GIFRegTEX0& TEX0, const GSVector2i& size, const int real_h);
Target* LookupDisplayTarget(const GIFRegTEX0& TEX0, const GSVector2i& size, const int real_h);
/// Looks up a target in the cache, and only returns it if the BP/BW/PSM match exactly.
Target* GetExactTarget(u32 BP, u32 BW, u32 PSM) const;
Target* GetTargetWithSharedBits(u32 BP, u32 PSM) const;
u32 GetTargetHeight(u32 fbp, u32 fbw, u32 psm, u32 min_height);
void InvalidateVideoMemType(int type, u32 bp);
void InvalidateVideoMemSubTarget(GSTextureCache::Target* rt);
void InvalidateVideoMem(const GSOffset& off, const GSVector4i& r, bool target = true);
@@ -335,10 +364,6 @@ public:
SurfaceOffset ComputeSurfaceOffset(const uint32_t bp, const uint32_t bw, const uint32_t psm, const GSVector4i& r, const Target* t);
SurfaceOffset ComputeSurfaceOffset(const SurfaceOffsetKey& sok);
/// Expands a target when the block pointer for a display framebuffer is within another target, but the read offset
/// plus the height is larger than the current size of the target.
static void ScaleTargetForDisplay(Target* t, const GIFRegTEX0& dispfb, int real_h);
/// Invalidates a temporary source, a partial copy only created from the current RT/DS for the current draw.
void InvalidateTemporarySource();
-1
View File
@@ -402,7 +402,6 @@ GSTexture* GSDeviceMTL::CreateSurface(GSTexture::Type type, int width, int heigh
[desc setUsage:MTLTextureUsageRenderTarget];
break;
case GSTexture::Type::RenderTarget:
case GSTexture::Type::SparseRenderTarget:
if (m_dev.features.slow_color_compression)
[desc setUsage:MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget | MTLTextureUsagePixelFormatView]; // Force color compression off by including PixelFormatView
else
+34 -4
View File
@@ -721,10 +721,17 @@ struct PSMain
return selector == 0 ? zero : selector == 1 ? one : two;
}
void ps_blend(thread float4& Color, float As)
void ps_blend(thread float4& Color, thread float& As)
{
if (SW_BLEND)
{
// PABE
if (PS_PABE)
{
// No blending so early exit
if (As < 1.f)
return;
}
float Ad = PS_DFMT == FMT_24 ? 1.f : trunc(current_color.a * 255.5f) / 128.f;
@@ -736,16 +743,39 @@ struct PSMain
float C = pick(PS_BLEND_C, As, Ad, cb.alpha_fix);
float3 D = pick(PS_BLEND_D, Cs, Cd, float3(0.f));
if (PS_BLEND_MIX)
// As/Af clamp alpha for Blend mix
// We shouldn't clamp blend mix with clr1 as we want alpha higher
if (PS_BLEND_MIX && PS_CLR_HW != 1)
C = min(C, 1.f);
if (PS_BLEND_A == PS_BLEND_B)
Color.rgb = D;
else if (PS_BLEND_MIX)
// In blend_mix, HW adds on some alpha factor * dst.
// Truncating here wouldn't quite get the right result because it prevents the <1 bit here from combining with a <1 bit in dst to form a ≥1 amount that pushes over the truncation.
// Instead, apply an offset to convert HW's round to a floor.
// Since alpha is in 1/128 increments, subtracting (0.5 - 0.5/128 == 127/256) would get us what we want if GPUs blended in full precision.
// But they don't. Details here: https://github.com/PCSX2/pcsx2/pull/6809#issuecomment-1211473399
// Based on the scripts at the above link, the ideal choice for Intel GPUs is 126/256, AMD 120/256. Nvidia is a lost cause.
// 124/256 seems like a reasonable compromise, providing the correct answer 99.3% of the time on Intel (vs 99.6% for 126/256), and 97% of the time on AMD (vs 97.4% for 120/256).
Color.rgb = ((A - B) * C + D) - (124.f/256.f);
else
Color.rgb = trunc((A - B) * C + D);
if (PS_PABE)
Color.rgb = (As >= 1.f) ? Color.rgb : Cs;
if (PS_CLR_HW == 1)
{
// Replace Af with As so we can do proper compensation for Alpha.
if (PS_BLEND_C == 2)
As = cb.alpha_fix;
// Subtract 1 for alpha to compensate for the changed equation,
// if c.rgb > 255.0f then we further need to adjust alpha accordingly,
// we pick the lowest overflow from all colors because it's the safest,
// we divide by 255 the color because we don't know Cd value,
// changed alpha should only be done for hw blend.
float min_color = min(min(Color.r, Color.g), Color.b);
float alpha_compensate = max(1.f, min_color / 255.f);
As -= alpha_compensate;
}
}
else
{
+5 -106
View File
@@ -122,7 +122,7 @@ namespace Emulate_DSA
// Replace function pointer to emulate DSA behavior
void Init()
{
fprintf(stderr, "DSA is not supported. Expect slower performance\n");
Console.Warning("DSA is not supported. Expect slower performance");
glBindTextureUnit = BindTextureUnit;
glCreateTextures = CreateTexture;
glTextureStorage2D = TextureStorage;
@@ -139,15 +139,6 @@ namespace Emulate_DSA
namespace GLLoader
{
#define fprintf_once(out, ...) \
do \
if (s_first_load) \
fprintf(out, __VA_ARGS__); \
while (0);
bool s_first_load = true;
bool vendor_id_amd = false;
bool vendor_id_nvidia = false;
bool vendor_id_intel = false;
@@ -162,10 +153,6 @@ namespace GLLoader
bool found_GL_ARB_gpu_shader5 = false; // Require IvyBridge
bool found_GL_ARB_shader_image_load_store = false; // Intel IB. Nvidia/AMD miss Mesa implementation.
// In case sparse2 isn't supported
bool found_compatible_GL_ARB_sparse_texture2 = false;
bool found_compatible_sparse_depth = false;
static bool mandatory(const std::string& ext)
{
if (!GLExtension::Has(ext))
@@ -183,11 +170,11 @@ namespace GLLoader
if (!found)
{
fprintf_once(stdout, "INFO: %s is NOT SUPPORTED\n", name.c_str());
DevCon.Warning("INFO: %s is NOT SUPPORTED", name.c_str());
}
else
{
fprintf_once(stdout, "INFO: %s is available\n", name.c_str());
DevCon.WriteLn("INFO: %s is available", name.c_str());
}
std::string opt("override_");
@@ -276,9 +263,6 @@ namespace GLLoader
// Extra
{
// Bonus
optional("GL_ARB_sparse_texture");
optional("GL_ARB_sparse_texture2");
// GL4.0
found_GL_ARB_gpu_shader5 = optional("GL_ARB_gpu_shader5");
// GL4.2
@@ -300,32 +284,17 @@ namespace GLLoader
}
}
if (vendor_id_amd)
{
fprintf_once(stderr, "The OpenGL hardware renderer is slow on AMD GPUs due to an inefficient driver.\n"
"Check out the link below for further information.\n"
"https://github.com/PCSX2/pcsx2/wiki/OpenGL-and-AMD-GPUs---All-you-need-to-know\n");
}
if (vendor_id_intel && (!GLExtension::Has("GL_ARB_texture_barrier") || !GLExtension::Has("GL_ARB_direct_state_access")))
{
// Assume that driver support is good when texture barrier and DSA is supported, disable the log then.
fprintf_once(stderr, "The OpenGL renderer is inefficient on Intel GPUs due to an inefficient driver.\n"
"Check out the link below for further information.\n"
"https://github.com/PCSX2/pcsx2/wiki/OpenGL-and-Intel-GPUs-All-you-need-to-know\n");
}
if (!GLExtension::Has("GL_ARB_viewport_array"))
{
glScissorIndexed = ReplaceGL::ScissorIndexed;
glViewportIndexedf = ReplaceGL::ViewportIndexedf;
fprintf_once(stderr, "GL_ARB_viewport_array is not supported! Function pointer will be replaced\n");
Console.Warning("GL_ARB_viewport_array is not supported! Function pointer will be replaced");
}
if (!GLExtension::Has("GL_ARB_texture_barrier"))
{
glTextureBarrier = ReplaceGL::TextureBarrier;
fprintf_once(stderr, "GL_ARB_texture_barrier is not supported! Blending emulation will not be supported\n");
Console.Warning("GL_ARB_texture_barrier is not supported! Blending emulation will not be supported");
}
#ifdef _WIN32
@@ -339,70 +308,6 @@ namespace GLLoader
return true;
}
bool is_sparse2_compatible(const char* name, GLenum internal_fmt, int x_max, int y_max)
{
GLint index_count = 0;
glGetInternalformativ(GL_TEXTURE_2D, internal_fmt, GL_NUM_VIRTUAL_PAGE_SIZES_ARB, 1, &index_count);
if (!index_count)
{
fprintf_once(stdout, "%s isn't sparse compatible. No index found\n", name);
return false;
}
GLint x, y;
glGetInternalformativ(GL_TEXTURE_2D, internal_fmt, GL_VIRTUAL_PAGE_SIZE_X_ARB, 1, &x);
glGetInternalformativ(GL_TEXTURE_2D, internal_fmt, GL_VIRTUAL_PAGE_SIZE_Y_ARB, 1, &y);
if (x > x_max && y > y_max)
{
fprintf_once(stdout, "%s isn't sparse compatible. Page size (%d,%d) is too big (%d, %d)\n",
name, x, y, x_max, y_max);
return false;
}
return true;
}
static void check_sparse_compatibility()
{
if (!GLExtension::Has("GL_ARB_sparse_texture") ||
!GLExtension::Has("GL_EXT_direct_state_access") ||
theApp.GetConfigI("override_GL_ARB_sparse_texture") != 1)
{
found_compatible_GL_ARB_sparse_texture2 = false;
found_compatible_sparse_depth = false;
return;
}
found_compatible_GL_ARB_sparse_texture2 = true;
if (!GLExtension::Has("GL_ARB_sparse_texture2"))
{
// Only check format from GSTextureOGL
found_compatible_GL_ARB_sparse_texture2 &= is_sparse2_compatible("GL_R8", GL_R8, 256, 256);
found_compatible_GL_ARB_sparse_texture2 &= is_sparse2_compatible("GL_R16UI", GL_R16UI, 256, 128);
found_compatible_GL_ARB_sparse_texture2 &= is_sparse2_compatible("GL_R32UI", GL_R32UI, 128, 128);
found_compatible_GL_ARB_sparse_texture2 &= is_sparse2_compatible("GL_R32I", GL_R32I, 128, 128);
found_compatible_GL_ARB_sparse_texture2 &= is_sparse2_compatible("GL_RGBA8", GL_RGBA8, 128, 128);
found_compatible_GL_ARB_sparse_texture2 &= is_sparse2_compatible("GL_RGBA16", GL_RGBA16, 128, 64);
found_compatible_GL_ARB_sparse_texture2 &= is_sparse2_compatible("GL_RGBA16I", GL_RGBA16I, 128, 64);
found_compatible_GL_ARB_sparse_texture2 &= is_sparse2_compatible("GL_RGBA16UI", GL_RGBA16UI, 128, 64);
found_compatible_GL_ARB_sparse_texture2 &= is_sparse2_compatible("GL_RGBA16F", GL_RGBA16F, 128, 64);
found_compatible_GL_ARB_sparse_texture2 &= is_sparse2_compatible("GL_RGBA32F", GL_RGBA32F, 64, 64);
}
// Can fit in 128x64 but 128x128 is enough
// Disable sparse depth for AMD. Bad driver strikes again.
// driver reports a compatible sparse format for depth texture but it isn't attachable to a frame buffer.
found_compatible_sparse_depth = !vendor_id_amd && is_sparse2_compatible("GL_DEPTH32F_STENCIL8", GL_DEPTH32F_STENCIL8, 128, 128);
fprintf_once(stdout, "INFO: sparse color texture is %s\n", found_compatible_GL_ARB_sparse_texture2 ? "available" : "NOT SUPPORTED");
fprintf_once(stdout, "INFO: sparse depth texture is %s\n", found_compatible_sparse_depth ? "available" : "NOT SUPPORTED");
}
bool check_gl_requirements()
{
if (!check_gl_version(3, 3))
@@ -411,12 +316,6 @@ namespace GLLoader
if (!check_gl_supported_extension())
return false;
// Bonus for sparse texture
check_sparse_compatibility();
fprintf_once(stdout, "\n");
s_first_load = false;
return true;
}
} // namespace GLLoader
-3
View File
@@ -45,7 +45,4 @@ namespace GLLoader
extern bool found_GL_ARB_gpu_shader5;
extern bool found_GL_ARB_shader_image_load_store;
extern bool found_GL_ARB_clear_texture;
extern bool found_compatible_GL_ARB_sparse_texture2;
extern bool found_compatible_sparse_depth;
} // namespace GLLoader
+1 -4
View File
@@ -774,7 +774,7 @@ void GSDeviceOGL::InvalidateRenderTarget(GSTexture* t)
{
OMSetFBO(m_fbo);
if (T->GetType() == GSTexture::Type::DepthStencil || T->GetType() == GSTexture::Type::SparseDepthStencil)
if (T->GetType() == GSTexture::Type::DepthStencil)
{
OMAttachDs(T);
const GLenum attachments[] = {GL_DEPTH_STENCIL_ATTACHMENT};
@@ -1192,7 +1192,6 @@ void GSDeviceOGL::CopyRect(GSTexture* sTex, GSTexture* dTex, const GSVector4i& r
PSSetShaderResource(6, sTex);
#endif
dTex->CommitRegion(GSVector2i(r.z, r.w));
g_perfmon.Put(GSPerfMon::TextureCopies, 1);
ASSERT(GLExtension::Has("GL_ARB_copy_image") && glCopyImageSubData);
@@ -1242,7 +1241,6 @@ void GSDeviceOGL::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture
BeginScene();
GL_PUSH("StretchRect from %d to %d", sTex->GetID(), dTex->GetID());
dTex->CommitRegion(GSVector2i((int)dRect.z + 1, (int)dRect.w + 1));
if (draw_in_depth)
OMSetRenderTargets(NULL, dTex);
else
@@ -1885,7 +1883,6 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
{
GSVector2i size = config.rt->GetSize();
hdr_rt = CreateRenderTarget(size.x, size.y, GSTexture::Format::FloatColor, false);
hdr_rt->CommitRegion(GSVector2i(config.drawarea.z, config.drawarea.w));
OMSetRenderTargets(hdr_rt, config.ds, &config.scissor);
// save blend state, since BlitRect destroys it
-3
View File
@@ -377,9 +377,6 @@ public:
void OMSetRenderTargets(GSTexture* rt, GSTexture* ds, const GSVector4i* scissor = NULL);
void OMSetColorMaskState(OMColorMaskSelector sel = OMColorMaskSelector());
bool HasColorSparse() final { return GLLoader::found_compatible_GL_ARB_sparse_texture2; }
bool HasDepthSparse() final { return GLLoader::found_compatible_sparse_depth; }
bool CreateTextureFX();
std::string GetShaderSource(const std::string_view& entry, GLenum type, const std::string_view& common_header, const std::string_view& glsl_h_code, const std::string_view& macro_sel);
std::string GenGlslHeader(const std::string_view& entry, GLenum type, const std::string_view& macro);
+13 -124
View File
@@ -181,7 +181,6 @@ GSTextureOGL::GSTextureOGL(Type type, int width, int height, int levels, Format
m_type = type;
m_fbo_read = fbo_read;
m_texture_id = 0;
m_sparse = false;
m_mipmap_levels = 1;
int gl_fmt = 0;
@@ -287,56 +286,9 @@ GSTextureOGL::GSTextureOGL(Type type, int width, int height, int levels, Format
ASSERT(0);
}
switch (m_type)
{
case Type::Texture:
// Only 32 bits input texture will be supported for mipmap
m_mipmap_levels = levels;
break;
case Type::SparseRenderTarget:
case Type::SparseDepthStencil:
m_sparse = true;
break;
default:
break;
}
switch (m_format)
{
case Format::UInt16:
case Format::UNorm8:
m_sparse &= GLLoader::found_compatible_GL_ARB_sparse_texture2;
SetGpuPageSize(GSVector2i(255, 255));
break;
case Format::Color:
case Format::UInt32:
case Format::PrimID:
m_sparse &= GLLoader::found_compatible_GL_ARB_sparse_texture2;
SetGpuPageSize(GSVector2i(127, 127));
break;
case Format::FloatColor:
m_sparse &= GLLoader::found_compatible_GL_ARB_sparse_texture2;
SetGpuPageSize(GSVector2i(63, 63));
break;
case Format::DepthStencil:
m_sparse &= GLLoader::found_compatible_sparse_depth;
SetGpuPageSize(GSVector2i(127, 127));
break;
case GSTexture::Format::BC1:
case GSTexture::Format::BC2:
case GSTexture::Format::BC3:
case GSTexture::Format::BC7:
m_sparse = false;
SetGpuPageSize(GSVector2i(127, 127));
break;
case Format::Invalid:
ASSERT(0);
}
// Only 32 bits input texture will be supported for mipmap
if (m_type == Type::Texture)
m_mipmap_levels = levels;
// Create a gl object (texture isn't allocated here)
glCreateTextures(GL_TEXTURE_2D, 1, &m_texture_id);
@@ -347,26 +299,8 @@ GSTextureOGL::GSTextureOGL(Type type, int width, int height, int levels, Format
glTextureParameteri(m_texture_id, GL_TEXTURE_SWIZZLE_A, GL_RED);
}
if (m_sparse)
{
GSVector2i old_size = m_size;
m_size = RoundUpPage(m_size);
if (m_size != old_size)
{
fprintf(stderr, "Sparse texture size (%dx%d) isn't a multiple of gpu page size (%dx%d)\n",
old_size.x, old_size.y, m_gpu_page_size.x, m_gpu_page_size.y);
}
glTextureParameteri(m_texture_id, GL_TEXTURE_SPARSE_ARB, true);
}
else
{
m_committed_size = m_size;
}
m_mem_usage = (m_committed_size.x * m_committed_size.y) << m_int_shift;
static int every_512 = 0;
GLState::available_vram -= m_mem_usage;
GLState::available_vram -= GetMemUsage();
if ((GLState::available_vram < 0) && (every_512 % 512 == 0))
{
fprintf(stderr, "Available VRAM is very low (%lld), a crash is expected! Enable conservative buffer allocation or reduce upscaling!\n", GLState::available_vram);
@@ -394,7 +328,7 @@ GSTextureOGL::~GSTextureOGL()
glDeleteTextures(1, &m_texture_id);
GLState::available_vram += m_mem_usage;
GLState::available_vram += GetMemUsage();
}
void* GSTextureOGL::GetNativeHandle() const
@@ -560,44 +494,6 @@ void GSTextureOGL::GenerateMipmap()
glGenerateTextureMipmap(m_texture_id);
}
void GSTextureOGL::CommitPages(const GSVector2i& region, bool commit)
{
GLState::available_vram += m_mem_usage;
if (commit)
{
if (m_committed_size.x == 0)
{
// Nothing allocated so far
GL_INS("CommitPages initial %dx%d of %u", region.x, region.y, m_texture_id);
glTexturePageCommitmentEXT(m_texture_id, GL_TEX_LEVEL_0, 0, 0, 0, region.x, region.y, 1, commit);
}
else
{
GL_INS("CommitPages extend %dx%d to %dx%d of %u", m_committed_size.x, m_committed_size.y, region.x, region.y, m_texture_id);
int w = region.x - m_committed_size.x;
int h = region.y - m_committed_size.y;
// Extend width
glTexturePageCommitmentEXT(m_texture_id, GL_TEX_LEVEL_0, m_committed_size.x, 0, 0, w, m_committed_size.y, 1, commit);
// Extend height
glTexturePageCommitmentEXT(m_texture_id, GL_TEX_LEVEL_0, 0, m_committed_size.y, 0, region.x, h, 1, commit);
}
m_committed_size = region;
}
else
{
// Release everything
GL_INS("CommitPages release of %u", m_texture_id);
glTexturePageCommitmentEXT(m_texture_id, GL_TEX_LEVEL_0, 0, 0, 0, m_committed_size.x, m_committed_size.y, 1, commit);
m_committed_size = GSVector2i(0, 0);
}
m_mem_usage = (m_committed_size.x * m_committed_size.y) << m_int_shift;
GLState::available_vram -= m_mem_usage;
}
GSTexture::GSMap GSTextureOGL::Read(const GSVector4i& r, AlignedBuffer<u8, 32>& buffer)
{
GSMap m;
@@ -625,8 +521,8 @@ GSTexture::GSMap GSTextureOGL::Read(const GSVector4i& r, AlignedBuffer<u8, 32>&
bool GSTextureOGL::Save(const std::string& fn)
{
// Collect the texture data
u32 pitch = 4 * m_committed_size.x;
u32 buf_size = pitch * m_committed_size.y * 2; // Note *2 for security (depth/stencil)
u32 pitch = 4 * m_size.x;
u32 buf_size = pitch * m_size.y * 2; // Note *2 for security (depth/stencil)
std::unique_ptr<u8[]> image(new u8[buf_size]);
#ifdef PCSX2_DEVBUILD
GSPng::Format fmt = GSPng::RGB_A_PNG;
@@ -634,12 +530,12 @@ bool GSTextureOGL::Save(const std::string& fn)
GSPng::Format fmt = GSPng::RGB_PNG;
#endif
if (IsDepth())
if (IsDepthStencil())
{
glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo_read);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_texture_id, 0);
glReadPixels(0, 0, m_committed_size.x, m_committed_size.y, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, image.get());
glReadPixels(0, 0, m_size.x, m_size.y, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, image.get());
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
@@ -648,7 +544,6 @@ bool GSTextureOGL::Save(const std::string& fn)
else if (m_format == Format::PrimID)
{
// Note: 4.5 function used for accurate DATE
// barely used outside of dev and not sparse anyway
glGetTextureImage(m_texture_id, 0, GL_RED_INTEGER, GL_INT, buf_size, image.get());
fmt = GSPng::R32I_PNG;
@@ -661,24 +556,24 @@ bool GSTextureOGL::Save(const std::string& fn)
if (m_format == Format::Color)
{
glReadPixels(0, 0, m_committed_size.x, m_committed_size.y, GL_RGBA, GL_UNSIGNED_BYTE, image.get());
glReadPixels(0, 0, m_size.x, m_size.y, GL_RGBA, GL_UNSIGNED_BYTE, image.get());
}
else if (m_format == Format::UInt16)
{
glReadPixels(0, 0, m_committed_size.x, m_committed_size.y, GL_RED_INTEGER, GL_UNSIGNED_SHORT, image.get());
glReadPixels(0, 0, m_size.x, m_size.y, GL_RED_INTEGER, GL_UNSIGNED_SHORT, image.get());
fmt = GSPng::R16I_PNG;
}
else if (m_format == Format::UNorm8)
{
fmt = GSPng::R8I_PNG;
glReadPixels(0, 0, m_committed_size.x, m_committed_size.y, GL_RED, GL_UNSIGNED_BYTE, image.get());
glReadPixels(0, 0, m_size.x, m_size.y, GL_RED, GL_UNSIGNED_BYTE, image.get());
}
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
}
int compression = theApp.GetConfigI("png_compression_level");
return GSPng::Save(fmt, fn, image.get(), m_committed_size.x, m_committed_size.y, pitch, compression);
return GSPng::Save(fmt, fn, image.get(), m_size.x, m_size.y, pitch, compression);
}
void GSTextureOGL::Swap(GSTexture* tex)
@@ -696,10 +591,4 @@ void GSTextureOGL::Swap(GSTexture* tex)
std::swap(m_int_format, static_cast<GSTextureOGL*>(tex)->m_int_format);
std::swap(m_int_type, static_cast<GSTextureOGL*>(tex)->m_int_type);
std::swap(m_int_shift, static_cast<GSTextureOGL*>(tex)->m_int_shift);
std::swap(m_mem_usage, static_cast<GSTextureOGL*>(tex)->m_mem_usage);
}
u32 GSTextureOGL::GetMemUsage()
{
return m_mem_usage;
}
-8
View File
@@ -54,9 +54,6 @@ private:
GLenum m_int_type;
u32 m_int_shift;
// Allow to track size of allocated memory
u32 m_mem_usage;
public:
explicit GSTextureOGL(Type type, int width, int height, int levels, Format format, GLuint fbo_read);
virtual ~GSTextureOGL();
@@ -71,7 +68,6 @@ public:
void Swap(GSTexture* tex) final;
GSMap Read(const GSVector4i& r, AlignedBuffer<u8, 32>& buffer);
bool IsDepth() { return (m_type == Type::DepthStencil || m_type == Type::SparseDepthStencil); }
bool IsIntegerFormat() const
{
return (m_int_format == GL_RED_INTEGER || m_int_format == GL_RGBA_INTEGER);
@@ -88,8 +84,4 @@ public:
void Clear(const void* data);
void Clear(const void* data, const GSVector4i& area);
void CommitPages(const GSVector2i& region, bool commit) final;
u32 GetMemUsage() final;
};
+51 -30
View File
@@ -405,8 +405,7 @@ VkFormat GSDeviceVK::LookupNativeFormat(GSTexture::Format format) const
GSTexture* GSDeviceVK::CreateSurface(GSTexture::Type type, int width, int height, int levels, GSTexture::Format format)
{
pxAssert(type != GSTexture::Type::Offscreen && type != GSTexture::Type::SparseRenderTarget &&
type != GSTexture::Type::SparseDepthStencil);
pxAssert(type != GSTexture::Type::Offscreen);
const u32 clamped_width = static_cast<u32>(std::clamp<int>(1, width, g_vulkan_context->GetMaxImageDimension2D()));
const u32 clamped_height = static_cast<u32>(std::clamp<int>(1, height, g_vulkan_context->GetMaxImageDimension2D()));
@@ -508,25 +507,38 @@ void GSDeviceVK::CopyRect(GSTexture* sTex, GSTexture* dTex, const GSVector4i& r,
return;
}
else
if (dTexVK->GetState() == GSTexture::State::Cleared)
{
// otherwise we need to do an attachment clear
const bool depth = (dTexVK->GetType() == GSTexture::Type::DepthStencil);
OMSetRenderTargets(depth ? nullptr : dTexVK, depth ? dTexVK : nullptr, dtex_rc, false);
BeginRenderPassForStretchRect(dTexVK, dtex_rc, GSVector4i(destX, destY, destX + r.width(), destY + r.height()));
// so use an attachment clear
VkClearAttachment ca;
ca.aspectMask = depth ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT;
GSVector4::store<false>(ca.clearValue.color.float32, sTexVK->GetClearColor());
ca.clearValue.depthStencil.depth = sTexVK->GetClearDepth();
ca.clearValue.depthStencil.stencil = 0;
ca.colorAttachment = 0;
const VkClearRect cr = { {{0, 0}, {static_cast<u32>(r.width()), static_cast<u32>(r.height())}}, 0u, 1u };
vkCmdClearAttachments(g_vulkan_context->GetCurrentCommandBuffer(), 1, &ca, 1, &cr);
return;
// destination is cleared, if it's the same colour and rect, we can just avoid this entirely
if (dTexVK->IsDepthStencil())
{
if (dTexVK->GetClearDepth() == sTexVK->GetClearDepth())
return;
}
else
{
if ((dTexVK->GetClearColor() == (sTexVK->GetClearColor())).alltrue())
return;
}
}
// otherwise we need to do an attachment clear
const bool depth = (dTexVK->GetType() == GSTexture::Type::DepthStencil);
OMSetRenderTargets(depth ? nullptr : dTexVK, depth ? dTexVK : nullptr, dtex_rc, false);
BeginRenderPassForStretchRect(dTexVK, dtex_rc, GSVector4i(destX, destY, destX + r.width(), destY + r.height()));
// so use an attachment clear
VkClearAttachment ca;
ca.aspectMask = depth ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT;
GSVector4::store<false>(ca.clearValue.color.float32, sTexVK->GetClearColor());
ca.clearValue.depthStencil.depth = sTexVK->GetClearDepth();
ca.clearValue.depthStencil.stencil = 0;
ca.colorAttachment = 0;
const VkClearRect cr = { {{0, 0}, {static_cast<u32>(r.width()), static_cast<u32>(r.height())}}, 0u, 1u };
vkCmdClearAttachments(g_vulkan_context->GetCurrentCommandBuffer(), 1, &ca, 1, &cr);
return;
}
// commit the clear to the source first, then do normal copy
@@ -1957,7 +1969,6 @@ VkShaderModule GSDeviceVK::GetTFXFragmentShader(const GSHWDrawConfig::PSSelector
AddMacro(ss, "PS_CHANNEL_FETCH", sel.channel);
AddMacro(ss, "PS_URBAN_CHAOS_HLE", sel.urban_chaos_hle);
AddMacro(ss, "PS_TALES_OF_ABYSS_HLE", sel.tales_of_abyss_hle);
AddMacro(ss, "PS_TEX_IS_FB", sel.tex_is_fb);
AddMacro(ss, "PS_INVALID_TEX0", sel.invalid_tex0);
AddMacro(ss, "PS_AEM", sel.aem);
AddMacro(ss, "PS_TFX", sel.tfx);
@@ -2980,22 +2991,32 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config)
}
}
if (config.tex && config.tex == config.ds)
if (config.tex)
{
// requires a copy of the depth buffer. this is mainly for ico.
copy_ds = static_cast<GSTextureVK*>(CreateDepthStencil(rtsize.x, rtsize.y, GSTexture::Format::DepthStencil, false));
if (copy_ds)
if (config.tex == config.ds)
{
EndRenderPass();
// requires a copy of the depth buffer. this is mainly for ico.
copy_ds = static_cast<GSTextureVK*>(CreateDepthStencil(rtsize.x, rtsize.y, GSTexture::Format::DepthStencil, false));
if (copy_ds)
{
EndRenderPass();
GL_PUSH("Copy depth to temp texture for shuffle {%d,%d %dx%d}",
config.drawarea.left, config.drawarea.top,
config.drawarea.width(), config.drawarea.height());
GL_PUSH("Copy depth to temp texture for shuffle {%d,%d %dx%d}",
config.drawarea.left, config.drawarea.top,
config.drawarea.width(), config.drawarea.height());
CopyRect(config.ds, copy_ds, config.drawarea, config.drawarea.left, config.drawarea.top);
PSSetShaderResource(0, copy_ds, true);
CopyRect(config.ds, copy_ds, config.drawarea, config.drawarea.left, config.drawarea.top);
PSSetShaderResource(0, copy_ds, true);
}
}
}
// clear texture binding when it's bound to RT or DS
if (!config.tex && m_tfx_textures[0] &&
((!pipe.feedback_loop && config.rt && static_cast<GSTextureVK*>(config.rt)->GetView() == m_tfx_textures[0]) ||
(config.ds && static_cast<GSTextureVK*>(config.ds)->GetView() == m_tfx_textures[0])))
{
PSSetShaderResource(0, nullptr, false);
}
const bool render_area_okay =
(!hdr_rt && DATE_rp != DATE_RENDER_PASS_STENCIL_ONE && CheckRenderPassArea(render_area));
-9
View File
@@ -172,9 +172,6 @@ const char* dialog_message(int ID, bool* updateText)
return cvtString("Allows advanced atomic operations to speed up Accurate DATE.\n"
"Only disable this if using Accurate DATE causes (GPU driver) issues.\n\n"
"Note: This option is only supported by GPUs which support at least Direct3D 11.");
case IDC_SPARSE_TEXTURE:
return cvtString("Allows to reduce VRAM usage on the GPU.\n\n"
"Note: Feature is currently experimental and works only on Nvidia GPUs.");
case IDC_LINEAR_PRESENT:
return cvtString("Use bilinear filtering when Upscaling/Downscaling the image to the screen. Disable it if you want a sharper/pixelated output.");
// Exclusive for Hardware Renderer
@@ -191,12 +188,6 @@ const char* dialog_message(int ID, bool* updateText)
case IDC_DISABLE_PARTIAL_TC_INV:
return cvtString("By default, the texture cache handles partial invalidations. Unfortunately it is very costly to compute CPU wise."
"\n\nThis hack replaces the partial invalidation with a complete deletion of the texture to reduce the CPU load.\n\nIt helps snowblind engine games.");
case IDC_CONSERVATIVE_FB:
return cvtString("Disabled: Reserves a larger framebuffer to prevent FMV flickers.\n"
"Increases GPU/memory requirements.\n"
"Disabling this can amplify stuttering due to low RAM/VRAM.\n\n"
"Note: It should be enabled for Armored Core, Destroy All Humans, Gran Turismo and possibly others.\n"
"This option does not improve the graphics or the FPS.");
case IDC_DITHERING:
return cvtString("In the PS2's case, it reduces banding between colors and improves the perceived color depth.\n"
"In the PS1's case, it was used more aggressively due to 16-bit colour.\n"
-2
View File
@@ -50,7 +50,6 @@ enum
IDC_PRELOAD_TEXTURES,
IDC_ACCURATE_DATE,
IDC_PALTEX,
IDC_CONSERVATIVE_FB,
IDC_AFCOMBO,
IDC_DITHERING,
IDC_MIPMAP_HW,
@@ -90,7 +89,6 @@ enum
// OpenGL Advanced Settings
IDC_GEOMETRY_SHADER_OVERRIDE,
IDC_IMAGE_LOAD_STORE,
IDC_SPARSE_TEXTURE,
// On-screen Display
IDC_OSD_LOG,
IDC_OSD_MONITOR,
-4
View File
@@ -275,7 +275,6 @@ RendererTab::RendererTab(wxWindow* parent)
const int space = wxSizerFlags().Border().GetBorderInPixels();
auto hw_prereq = [this]{ return m_is_hardware; };
auto sw_prereq = [this]{ return !m_is_hardware; };
auto upscale_prereq = [this]{ return !m_is_native_res; };
PaddedBoxSizer<wxBoxSizer> tab_box(wxVERTICAL);
PaddedBoxSizer<wxStaticBoxSizer> general_box(wxVERTICAL, this, "General GS Settings");
@@ -285,7 +284,6 @@ RendererTab::RendererTab(wxWindow* parent)
auto* hw_checks_box = new wxWrapSizer(wxHORIZONTAL);
m_ui.addCheckBox(hw_checks_box, "Accurate Destination Alpha Test", "accurate_date", IDC_ACCURATE_DATE, hw_prereq);
m_ui.addCheckBox(hw_checks_box, "Conservative Buffer Allocation", "conservative_framebuffer", IDC_CONSERVATIVE_FB, upscale_prereq);
auto* paltex_prereq = m_ui.addCheckBox(hw_checks_box, "GPU Palette Conversion", "paltex", IDC_PALTEX, hw_prereq);
auto aniso_prereq = [this, paltex_prereq]{ return m_is_hardware && paltex_prereq->GetValue() == false; };
@@ -589,7 +587,6 @@ DebugTab::DebugTab(wxWindow* parent)
m_ui.addComboBoxAndLabel(ogl_grid, "Texture Barriers:", "OverrideTextureBarriers", &theApp.m_gs_generic_list, -1, vk_ogl_hw_prereq);
m_ui.addComboBoxAndLabel(ogl_grid, "Geometry Shader:", "OverrideGeometryShaders", &theApp.m_gs_generic_list, IDC_GEOMETRY_SHADER_OVERRIDE, vk_ogl_hw_prereq);
m_ui.addComboBoxAndLabel(ogl_grid, "Image Load Store:", "override_GL_ARB_shader_image_load_store", &theApp.m_gs_generic_list, IDC_IMAGE_LOAD_STORE, ogl_hw_prereq);
m_ui.addComboBoxAndLabel(ogl_grid, "Sparse Texture:", "override_GL_ARB_sparse_texture", &theApp.m_gs_generic_list, IDC_SPARSE_TEXTURE, ogl_hw_prereq);
m_ui.addComboBoxAndLabel(ogl_grid, "Dump Compression:", "GSDumpCompression", &theApp.m_gs_dump_compression, -1);
ogl_box->Add(ogl_grid);
@@ -804,7 +801,6 @@ void Dialog::Update()
m_hacks_panel->m_is_native_res = !is_hw || !is_upscale;
m_hacks_panel->m_is_hardware = is_hw;
m_renderer_panel->m_is_hardware = is_hw;
m_renderer_panel->m_is_native_res = !is_hw || !is_upscale;
m_post_panel->m_is_vk_hw = renderer == GSRendererType::VK;
m_debug_panel->m_is_ogl_hw = renderer == GSRendererType::OGL;
m_debug_panel->m_is_vk_hw = renderer == GSRendererType::VK;
-1
View File
@@ -106,7 +106,6 @@ namespace GSSettingsDialog
GSUIElementHolder m_ui;
wxChoice* m_internal_resolution;
bool m_is_hardware = false;
bool m_is_native_res = false;
RendererTab(wxWindow* parent);
void Load() { m_ui.Load(); }
-9
View File
@@ -270,7 +270,6 @@ void GameDatabase::parseAndInsert(const std::string_view& serial, const c4::yml:
static const char* s_gs_hw_fix_names[] = {
"autoFlush",
"conservativeFramebuffer",
"cpuFramebufferConversion",
"disableDepthSupport",
"wrapGSMem",
@@ -317,7 +316,6 @@ bool GameDatabaseSchema::isUserHackHWFix(GSHWFixId id)
case GSHWFixId::Deinterlace:
case GSHWFixId::Mipmap:
case GSHWFixId::TexturePreloading:
case GSHWFixId::ConservativeFramebuffer:
case GSHWFixId::PointListPalette:
return false;
@@ -446,9 +444,6 @@ bool GameDatabaseSchema::GameEntry::configMatchesHWFix(const Pcsx2Config::GSOpti
case GSHWFixId::AutoFlush:
return (static_cast<int>(config.UserHacks_AutoFlush) == value);
case GSHWFixId::ConservativeFramebuffer:
return (static_cast<int>(config.ConservativeFramebuffer) == value);
case GSHWFixId::CPUFramebufferConversion:
return (static_cast<int>(config.UserHacks_CPUFBConversion) == value);
@@ -542,10 +537,6 @@ u32 GameDatabaseSchema::GameEntry::applyGSHardwareFixes(Pcsx2Config::GSOptions&
config.UserHacks_AutoFlush = (value > 0);
break;
case GSHWFixId::ConservativeFramebuffer:
config.ConservativeFramebuffer = (value > 0);
break;
case GSHWFixId::CPUFramebufferConversion:
config.UserHacks_CPUFBConversion = (value > 0);
break;
-1
View File
@@ -60,7 +60,6 @@ namespace GameDatabaseSchema
{
// boolean settings
AutoFlush,
ConservativeFramebuffer,
CPUFramebufferConversion,
DisableDepthSupport,
WrapGSMem,
+12 -4
View File
@@ -416,7 +416,8 @@ void VU_Thread::Get_MTVUChanges()
{
mtvuInterrupts.fetch_and(~InterruptFlagVUEBit, std::memory_order_relaxed);
VU0.VI[REG_VPU_STAT].UL &= ~0xFF00;
if(INSTANT_VU1)
VU0.VI[REG_VPU_STAT].UL &= ~0xFF00;
//DevCon.Warning("E-Bit registered %x", VU0.VI[REG_VPU_STAT].UL);
}
if (interrupts & InterruptFlagVUTBit)
@@ -458,10 +459,17 @@ void VU_Thread::ExecuteVU(u32 vu_addr, u32 vif_top, u32 vif_itop, u32 fbrst)
CommitWritePos();
gifUnit.TransferGSPacketData(GIF_TRANS_MTVU, NULL, 0);
KickStart();
u32 cycles = std::min(Get_vuCycles(), 3000u);
cpuRegs.cycle += cycles * EmuConfig.Speedhacks.EECycleSkip;
VU0.cycle += cycles * EmuConfig.Speedhacks.EECycleSkip;
u32 cycles = std::max(Get_vuCycles(), 4u);
u32 skip_cycles = std::min(cycles, 3000u);
cpuRegs.cycle += skip_cycles * EmuConfig.Speedhacks.EECycleSkip;
VU0.cycle += skip_cycles * EmuConfig.Speedhacks.EECycleSkip;
Get_MTVUChanges();
if (!INSTANT_VU1)
{
VU0.VI[REG_VPU_STAT].UL |= 0x100;
CPU_INT(VU_MTVU_BUSY, cycles);
}
}
void VU_Thread::VifUnpack(vifStruct& _vif, VIFregisters& _vifRegs, u8* data, u32 size)
+1 -1
View File
@@ -133,7 +133,7 @@ void KeyStatus::Set(u32 pad, u32 index, float value)
if (index == PAD_PRESSURE)
{
const float adjust_pmod = ((m_button[pad] & (1u << PAD_PRESSURE)) == 0) ? m_pressure_modifier[pad] : (1.0f / m_pressure_modifier[pad]);
for (int i = 0; i < MAX_KEYS; i++)
for (u32 i = 0; i < MAX_KEYS; i++)
{
if (i == index || IsAnalogKey(i) || IsTriggerKey(i))
continue;
+80 -2
View File
@@ -183,6 +183,62 @@ u8 PADpoll(u8 value)
return pad_poll(value);
}
const char* PAD::ControllerSettingInfo::StringDefaultValue() const
{
return default_value ? default_value : "";
}
bool PAD::ControllerSettingInfo::BooleanDefaultValue() const
{
return default_value ? StringUtil::FromChars<bool>(default_value).value_or(false) : false;
}
s32 PAD::ControllerSettingInfo::IntegerDefaultValue() const
{
return default_value ? StringUtil::FromChars<s32>(default_value).value_or(0) : 0;
}
s32 PAD::ControllerSettingInfo::IntegerMinValue() const
{
static constexpr s32 fallback_value = std::numeric_limits<s32>::min();
return min_value ? StringUtil::FromChars<s32>(min_value).value_or(fallback_value) : fallback_value;
}
s32 PAD::ControllerSettingInfo::IntegerMaxValue() const
{
static constexpr s32 fallback_value = std::numeric_limits<s32>::max();
return max_value ? StringUtil::FromChars<s32>(max_value).value_or(fallback_value) : fallback_value;
}
s32 PAD::ControllerSettingInfo::IntegerStepValue() const
{
static constexpr s32 fallback_value = 1;
return step_value ? StringUtil::FromChars<s32>(step_value).value_or(fallback_value) : fallback_value;
}
float PAD::ControllerSettingInfo::FloatDefaultValue() const
{
return default_value ? StringUtil::FromChars<float>(default_value).value_or(0.0f) : 0.0f;
}
float PAD::ControllerSettingInfo::FloatMinValue() const
{
static constexpr float fallback_value = std::numeric_limits<float>::min();
return min_value ? StringUtil::FromChars<float>(min_value).value_or(fallback_value) : fallback_value;
}
float PAD::ControllerSettingInfo::FloatMaxValue() const
{
static constexpr float fallback_value = std::numeric_limits<float>::max();
return max_value ? StringUtil::FromChars<float>(max_value).value_or(fallback_value) : fallback_value;
}
float PAD::ControllerSettingInfo::FloatStepValue() const
{
static constexpr float fallback_value = 0.1f;
return step_value ? StringUtil::FromChars<float>(step_value).value_or(fallback_value) : fallback_value;
}
std::string PAD::GetConfigSection(u32 pad_index)
{
return fmt::format("Pad{}", pad_index + 1);
@@ -350,9 +406,31 @@ static const PAD::ControllerBindingInfo s_dualshock2_binds[] = {
{"SmallMotor", "Small (High Frequency) Motor", PAD::ControllerBindingType::Motor, GenericInputBinding::SmallMotor},
};
static const PAD::ControllerSettingInfo s_dualshock2_settings[] = {
{PAD::ControllerSettingInfo::Type::Float, "Deadzone", "Analog Deadzone",
"Sets the analog stick deadzone, i.e. the fraction of the stick movement which will be ignored.",
"0.00", "0.00", "1.00", "0.01"},
{PAD::ControllerSettingInfo::Type::Float, "AxisScale", "Analog Sensitivity",
"Sets the analog stick axis scaling factor. A value between 1.30 and 1.40 is recommended when using recent "
"controllers, e.g. DualShock 4, Xbox One Controller.",
"1.33", "0.01", "2.00", "0.01"},
{PAD::ControllerSettingInfo::Type::Float, "LargeMotorScale", "Large Motor Vibration Scale",
"Increases or decreases the intensity of low frequency vibration sent by the game.",
"1.00", "0.00", "2.00", "0.01"},
{PAD::ControllerSettingInfo::Type::Float, "SmallMotorScale", "Small Motor Vibration Scale",
"Increases or decreases the intensity of high frequency vibration sent by the game.",
"1.00", "0.00", "2.00", "0.01"},
};
static const PAD::ControllerInfo s_controller_info[] = {
{"None", "Not Connected", nullptr, 0, PAD::ControllerType::NotConnected, PAD::VibrationCapabilities::NoVibration},
{"DualShock2", "DualShock 2", s_dualshock2_binds, std::size(s_dualshock2_binds), PAD::ControllerType::DualShock2, PAD::VibrationCapabilities::LargeSmallMotors},
{PAD::ControllerType::NotConnected, "None", "Not Connected",
nullptr, 0,
nullptr, 0,
PAD::VibrationCapabilities::NoVibration},
{PAD::ControllerType::DualShock2, "DualShock2", "DualShock 2",
s_dualshock2_binds, std::size(s_dualshock2_binds),
s_dualshock2_settings, std::size(s_dualshock2_settings),
PAD::VibrationCapabilities::LargeSmallMotors},
};
const PAD::ControllerInfo* PAD::GetControllerInfo(ControllerType type)
+36 -1
View File
@@ -71,13 +71,48 @@ namespace PAD
GenericInputBinding generic_mapping;
};
struct ControllerSettingInfo
{
enum class Type
{
Boolean,
Integer,
Float,
String,
Path,
};
Type type;
const char* key;
const char* visible_name;
const char* description;
const char* default_value;
const char* min_value;
const char* max_value;
const char* step_value;
const char* StringDefaultValue() const;
bool BooleanDefaultValue() const;
s32 IntegerDefaultValue() const;
s32 IntegerMinValue() const;
s32 IntegerMaxValue() const;
s32 IntegerStepValue() const;
float FloatDefaultValue() const;
float FloatMinValue() const;
float FloatMaxValue() const;
float FloatStepValue() const;
};
struct ControllerInfo
{
ControllerType type;
const char* name;
const char* display_name;
const ControllerBindingInfo* bindings;
u32 num_bindings;
ControllerType type;
const ControllerSettingInfo* settings;
u32 num_settings;
PAD::VibrationCapabilities vibration_caps;
};
-2
View File
@@ -325,7 +325,6 @@ Pcsx2Config::GSOptions::GSOptions()
HWDisableReadbacks = false;
AccurateDATE = true;
GPUPaletteConversion = false;
ConservativeFramebuffer = true;
AutoFlushSW = true;
PreloadFrameWithGSData = false;
WrapGSMem = false;
@@ -546,7 +545,6 @@ void Pcsx2Config::GSOptions::ReloadIniSettings()
GSSettingBool(HWDisableReadbacks);
GSSettingBoolEx(AccurateDATE, "accurate_date");
GSSettingBoolEx(GPUPaletteConversion, "paltex");
GSSettingBoolEx(ConservativeFramebuffer, "conservative_framebuffer");
GSSettingBoolEx(AutoFlushSW, "autoflush_sw");
GSSettingBoolEx(PreloadFrameWithGSData, "preload_frame_with_gs_data");
GSSettingBoolEx(WrapGSMem, "wrap_gs_mem");
+14 -2
View File
@@ -246,6 +246,18 @@ __fi void cpuSetNextEventDelta( s32 delta )
cpuSetNextEvent( cpuRegs.cycle, delta );
}
__fi int cpuGetCycles(int interrupt)
{
if(interrupt == VU_MTVU_BUSY && (!THREAD_VU1 || INSTANT_VU1))
return 1;
else
{
const int cycles = (cpuRegs.sCycle[interrupt] + cpuRegs.eCycle[interrupt]) - cpuRegs.cycle;
return std::max(1, cycles);
}
}
// tests the cpu cycle against the given start and delta values.
// Returns true if the delta time has passed.
__fi int cpuTestCycle( u32 startCycle, s32 delta )
@@ -292,7 +304,7 @@ static __fi void _cpuTestInterrupts()
}
/* These are 'pcsx2 interrupts', they handle asynchronous stuff
that depends on the cycle timings */
TESTINT(VU_MTVU_BUSY, MTVUInterrupt);
TESTINT(DMAC_VIF1, vif1Interrupt);
TESTINT(DMAC_GIF, gifInterrupt);
TESTINT(DMAC_SIF0, EEsif0Interrupt);
@@ -521,7 +533,7 @@ __fi void CPU_INT( EE_EventType n, s32 ecycle)
// EE events happen 8 cycles in the future instead of whatever was requested.
// This can be used on games with PATH3 masking issues for example, or when
// some FMV look bad.
if(CHECK_EETIMINGHACK) ecycle = 8;
if(CHECK_EETIMINGHACK && n < VIF_VU0_FINISH) ecycle = 8;
cpuRegs.interrupt|= 1 << n;
cpuRegs.sCycle[n] = cpuRegs.cycle;
+3 -1
View File
@@ -415,7 +415,8 @@ enum EE_EventType
DMAC_GIF_UNIT,
VIF_VU0_FINISH,
VIF_VU1_FINISH,
IPU_PROCESS
IPU_PROCESS,
VU_MTVU_BUSY
};
extern void CPU_INT( EE_EventType n, s32 ecycle );
@@ -435,6 +436,7 @@ extern void cpuSetNextEvent( u32 startCycle, s32 delta );
extern void cpuSetNextEventDelta( s32 delta );
extern int cpuTestCycle( u32 startCycle, s32 delta );
extern void cpuSetEvent();
extern int cpuGetCycles(int interrupt);
extern void _cpuEventTest_Shared(); // for internal use by the Dynarecs and Ints inside R5900:
+1 -1
View File
@@ -118,7 +118,7 @@ void ReadSettings()
// Let's use xaudio2 until this is sorted (rama).
// CfgReadStr(L"OUTPUT", L"Output_Module", omodid, 127, PortaudioOut->GetIdent());
CfgReadStr(L"OUTPUT", L"Output_Module", omodid, 127, StringUtil::UTF8StringToWideString(XAudio2Out->GetIdent()).c_str());
CfgReadStr(L"OUTPUT", L"Output_Module", omodid, 127, StringUtil::UTF8StringToWideString(CubebOut->GetIdent()).c_str());
// Find the driver index of this module:
OutputModule = FindOutputModuleById(StringUtil::WideStringToUTF8String(omodid).c_str());
+9 -2
View File
@@ -38,7 +38,10 @@ void vu1ResetRegs()
void vu1Finish(bool add_cycles) {
if (THREAD_VU1) {
//if (VU0.VI[REG_VPU_STAT].UL & 0x100) DevCon.Error("MTVU: VU0.VI[REG_VPU_STAT].UL & 0x100");
vu1Thread.WaitVU();
if (INSTANT_VU1 || add_cycles)
{
vu1Thread.WaitVU();
}
vu1Thread.Get_MTVUChanges();
return;
}
@@ -70,7 +73,6 @@ void vu1ExecMicro(u32 addr)
// VU0.VI[REG_VPU_STAT].UL |= 0x0100;
// }
// Update 25/06/2022: Disabled this for now, let games YOLO it, if it breaks MTVU, disable MTVU (it doesn't work properly anyway) - Refraction
vu1Thread.ExecuteVU(addr, vif1Regs.top, vif1Regs.itop, VU0.VI[REG_FBRST].UL);
return;
}
@@ -90,3 +92,8 @@ void vu1ExecMicro(u32 addr)
else
CpuVU1->Execute(vu1RunCycles);
}
void MTVUInterrupt()
{
VU0.VI[REG_VPU_STAT].UL &= ~0xFF00;
}
+1
View File
@@ -256,6 +256,7 @@ extern void vu1ResetRegs();
extern void vu1ExecMicro(u32 addr);
extern void vu1Exec(VURegs* VU);
extern void iDumpVU1Registers();
extern void MTVUInterrupt();
#ifdef VUM_LOG
+129 -86
View File
@@ -40,7 +40,8 @@ void vif1TransferToMemory()
u128* pMem = (u128*)dmaGetAddr(vif1ch.madr, false);
// VIF from gsMemory
if (pMem == NULL) { // Is vif0ptag empty?
if (pMem == NULL)
{ // Is vif0ptag empty?
Console.WriteLn("Vif1 Tag BUSERR");
dmacRegs.stat.BEIS = true; // Bus Error
vif1Regs.stat.FQC = 0;
@@ -55,10 +56,11 @@ void vif1TransferToMemory()
// stuff from the GS. The *only* way to handle this case safely is to flush the GS
// completely and execute the transfer there-after.
//Console.Warning("Real QWC %x", vif1ch.qwc);
const u32 size = std::min(vif1.GSLastDownloadSize, (u32)vif1ch.qwc);
const u32 size = std::min(vif1.GSLastDownloadSize, (u32)vif1ch.qwc);
//const u128* pMemEnd = vif1.GSLastDownloadSize + pMem;
if (size) {
if (size)
{
// Checking if any crazy game does a partial
// gs primitive and then does a gs download...
Gif_Path& p1 = gifUnit.gifPath[GIF_PATH_1];
@@ -70,7 +72,7 @@ void vif1TransferToMemory()
}
GetMTGS().InitAndReadFIFO(reinterpret_cast<u8*>(pMem), size);
// pMem += size;
// pMem += size;
//Some games such as Alex Ferguson's Player Manager 2001 reads less than GSLastDownloadSize by VIF then reads the remainder by FIFO
//Clearing the memory is clearing memory it shouldn't be and kills it.
@@ -89,25 +91,25 @@ void vif1TransferToMemory()
g_vif1Cycles += size * 2;
vif1ch.madr += size * 16; // mgs3 scene changes
if (vif1.GSLastDownloadSize >= vif1ch.qwc) {
if (vif1.GSLastDownloadSize >= vif1ch.qwc)
{
vif1.GSLastDownloadSize -= vif1ch.qwc;
vif1Regs.stat.FQC = std::min((u32)16, vif1.GSLastDownloadSize);
vif1ch.qwc = 0;
}
else {
else
{
vif1Regs.stat.FQC = 0;
vif1ch.qwc -= vif1.GSLastDownloadSize;
vif1.GSLastDownloadSize = 0;
//This could be potentially bad and cause hangs. I guess we will find out.
DevCon.Warning("QWC left on VIF FIFO Reverse");
}
}
bool _VIF1chain()
{
u32 *pMem;
u32* pMem;
if (vif1ch.qwc == 0)
{
@@ -135,7 +137,7 @@ bool _VIF1chain()
}
VIF_LOG("VIF1chain size=%d, madr=%lx, tadr=%lx",
vif1ch.qwc, vif1ch.madr, vif1ch.tadr);
vif1ch.qwc, vif1ch.madr, vif1ch.tadr);
if (vif1.irqoffset.enabled)
return VIF1transfer(pMem + vif1.irqoffset.value, vif1ch.qwc * 4 - vif1.irqoffset.value, false);
@@ -145,20 +147,21 @@ bool _VIF1chain()
__fi void vif1SetupTransfer()
{
tDMA_TAG *ptag;
tDMA_TAG* ptag;
ptag = dmaGetAddr(vif1ch.tadr, false); //Set memory pointer to TADR
if (!(vif1ch.transfer("Vif1 Tag", ptag))) return;
if (!(vif1ch.transfer("Vif1 Tag", ptag)))
return;
vif1ch.madr = ptag[1]._u32; //MADR = ADDR field + SPR
vif1ch.madr = ptag[1]._u32; //MADR = ADDR field + SPR
g_vif1Cycles += 1; // Add 1 g_vifCycles from the QW read for the tag
vif1.inprogress &= ~1;
VIF_LOG("VIF1 Tag %8.8x_%8.8x size=%d, id=%d, madr=%lx, tadr=%lx",
ptag[1]._u32, ptag[0]._u32, vif1ch.qwc, ptag->ID, vif1ch.madr, vif1ch.tadr);
ptag[1]._u32, ptag[0]._u32, vif1ch.qwc, ptag->ID, vif1ch.madr, vif1ch.tadr);
if (!vif1.done && ((dmacRegs.ctrl.STD == STD_VIF1) && (ptag->ID == TAG_REFS))) // STD == VIF1
if (!vif1.done && ((dmacRegs.ctrl.STD == STD_VIF1) && (ptag->ID == TAG_REFS))) // STD == VIF1
{
// there are still bugs, need to also check if gif->madr +16*qwc >= stadr, if not, stall
if ((vif1ch.madr + vif1ch.qwc * 16) > dmacRegs.stadr.ADDR)
@@ -177,7 +180,7 @@ __fi void vif1SetupTransfer()
bool ret;
alignas(16) static u128 masked_tag;
masked_tag._u64[0] = 0;
masked_tag._u64[1] = *((u64*)ptag + 1);
@@ -185,7 +188,7 @@ __fi void vif1SetupTransfer()
if (vif1.irqoffset.enabled)
{
ret = VIF1transfer((u32*)&masked_tag + vif1.irqoffset.value, 4 - vif1.irqoffset.value, true); //Transfer Tag on stall
ret = VIF1transfer((u32*)&masked_tag + vif1.irqoffset.value, 4 - vif1.irqoffset.value, true); //Transfer Tag on stall
//ret = VIF1transfer((u32*)ptag + (2 + vif1.irqoffset), 2 - vif1.irqoffset); //Transfer Tag on stall
}
else
@@ -194,7 +197,7 @@ __fi void vif1SetupTransfer()
// to the VU's, which breaks stuff, this is where the 128bit packet will fail, so we ignore the first 2 words
vif1.irqoffset.value = 2;
vif1.irqoffset.enabled = true;
ret = VIF1transfer((u32*)&masked_tag + 2, 2, true); //Transfer Tag
ret = VIF1transfer((u32*)&masked_tag + 2, 2, true); //Transfer Tag
//ret = VIF1transfer((u32*)ptag + 2, 2); //Transfer Tag
}
@@ -202,7 +205,7 @@ __fi void vif1SetupTransfer()
{
vif1.inprogress &= ~1; // Better clear this so it has to do it again (Jak 1)
vif1ch.qwc = 0; // Gumball 3000 pauses the DMA when the tag stalls so we need to reset the QWC, it'll be gotten again later
return; // IRQ set by VIFTransfer
return; // IRQ set by VIFTransfer
}
}
vif1.irqoffset.value = 0;
@@ -210,14 +213,15 @@ __fi void vif1SetupTransfer()
vif1.done |= hwDmacSrcChainWithStack(vif1ch, ptag->ID);
if(vif1ch.qwc > 0) vif1.inprogress |= 1;
if (vif1ch.qwc > 0)
vif1.inprogress |= 1;
//Check TIE bit of CHCR and IRQ bit of tag
if (vif1ch.chcr.TIE && ptag->IRQ)
{
VIF_LOG("dmaIrq Set");
//End Transfer
//End Transfer
vif1.done = true;
return;
}
@@ -229,23 +233,29 @@ __fi void vif1VUFinish()
{
vu1Thread.Get_MTVUChanges();
CPU_INT(VIF_VU1_FINISH, 128);
if (THREAD_VU1 && !INSTANT_VU1 && (VU0.VI[REG_VPU_STAT].UL & 0x100))
CPU_INT(VIF_VU1_FINISH, cpuGetCycles(VU_MTVU_BUSY));
else
CPU_INT(VIF_VU1_FINISH, 128);
return;
}
if (VU0.VI[REG_VPU_STAT].UL & 0x100)
{
u32 _cycles = VU1.cycle;
//DevCon.Warning("Finishing VU1");
vu1Finish(false);
CPU_INT(VIF_VU1_FINISH, VU1.cycle - _cycles);
if (THREAD_VU1 && !INSTANT_VU1 && (VU0.VI[REG_VPU_STAT].UL & 0x100))
CPU_INT(VIF_VU1_FINISH, cpuGetCycles(VU_MTVU_BUSY));
else
CPU_INT(VIF_VU1_FINISH, VU1.cycle - _cycles);
return;
}
vif1Regs.stat.VEW = false;
VIF_LOG("VU1 finished");
if(vif1.waitforvu)
if (vif1.waitforvu)
{
vif1.waitforvu = false;
//Check if VIF is already scheduled to interrupt, if it's waiting, kick it :P
@@ -257,7 +267,7 @@ __fi void vif1VUFinish()
vif1Interrupt();
}
}
//DevCon.Warning("VU1 state cleared");
}
@@ -267,19 +277,22 @@ __fi void vif1Interrupt()
g_vif1Cycles = 0;
if( gifRegs.stat.APATH == 2 && gifUnit.gifPath[GIF_PATH_2].isDone())
if (gifRegs.stat.APATH == 2 && gifUnit.gifPath[GIF_PATH_2].isDone())
{
gifRegs.stat.APATH = 0;
gifRegs.stat.OPH = 0;
vif1Regs.stat.VGW = false; //Let vif continue if it's stuck on a flush
if(gifUnit.checkPaths(1,0,1)) gifUnit.Execute(false, true);
if (gifUnit.checkPaths(1, 0, 1))
gifUnit.Execute(false, true);
}
//Some games (Fahrenheit being one) start vif first, let it loop through blankness while it sets MFIFO mode, so we need to check it here.
if (dmacRegs.ctrl.MFD == MFD_VIF1) {
if (dmacRegs.ctrl.MFD == MFD_VIF1)
{
//Console.WriteLn("VIFMFIFO\n");
// Test changed because the Final Fantasy 12 opening somehow has the tag in *Undefined* mode, which is not in the documentation that I saw.
if (vif1ch.chcr.MOD == NORMAL_MODE) Console.WriteLn("MFIFO mode is normal (which isn't normal here)! %x", vif1ch.chcr._u32);
if (vif1ch.chcr.MOD == NORMAL_MODE)
Console.WriteLn("MFIFO mode is normal (which isn't normal here)! %x", vif1ch.chcr._u32);
vif1Regs.stat.FQC = std::min((u32)0x10, vif1ch.qwc);
vifMFIFOInterrupt();
return;
@@ -287,42 +300,46 @@ __fi void vif1Interrupt()
// We need to check the direction, if it is downloading
// from the GS then we handle that separately (KH2 for testing)
if (vif1ch.chcr.DIR) {
bool isDirect = (vif1.cmd & 0x7f) == 0x50;
if (vif1ch.chcr.DIR)
{
bool isDirect = (vif1.cmd & 0x7f) == 0x50;
bool isDirectHL = (vif1.cmd & 0x7f) == 0x51;
if((isDirect && !gifUnit.CanDoPath2())
|| (isDirectHL && !gifUnit.CanDoPath2HL())) {
if ((isDirect && !gifUnit.CanDoPath2()) || (isDirectHL && !gifUnit.CanDoPath2HL()))
{
GUNIT_WARN("vif1Interrupt() - Waiting for Path 2 to be ready");
CPU_INT(DMAC_VIF1, 128);
if(gifRegs.stat.APATH == 3) vif1Regs.stat.VGW = 1; //We're waiting for path 3. Gunslinger II
if (gifRegs.stat.APATH == 3)
vif1Regs.stat.VGW = 1; //We're waiting for path 3. Gunslinger II
return;
}
vif1Regs.stat.VGW = 0; //Path 3 isn't busy so we don't need to wait for it.
vif1Regs.stat.FQC = std::min(vif1ch.qwc, (u32)16);
//Simulated GS transfer time done, clear the flags
}
if(vif1.waitforvu)
if (vif1.waitforvu)
{
//DevCon.Warning("Waiting on VU1");
//CPU_INT(DMAC_VIF1, 16);
CPU_INT(VIF_VU1_FINISH, 16);
CPU_INT(VIF_VU1_FINISH, std::max(16, cpuGetCycles(VU_MTVU_BUSY)));
return;
}
if (vif1Regs.stat.VGW)
return;
if (!vif1ch.chcr.STR) Console.WriteLn("Vif1 running when CHCR == %x", vif1ch.chcr._u32);
if (!vif1ch.chcr.STR)
Console.WriteLn("Vif1 running when CHCR == %x", vif1ch.chcr._u32);
if (vif1.irq && vif1.vifstalled.enabled && vif1.vifstalled.value == VIF_IRQ_STALL)
{
VIF_LOG("VIF IRQ Firing");
if (!vif1Regs.stat.ER1)
vif1Regs.stat.INT = true;
//Yakuza watches VIF_STAT so lets do this here.
if (((vif1Regs.code >> 24) & 0x7f) != 0x7) {
if (((vif1Regs.code >> 24) & 0x7f) != 0x7)
{
vif1Regs.stat.VIS = true;
}
@@ -336,7 +353,7 @@ __fi void vif1Interrupt()
//NFSHPS stalls when the whole packet has gone across (it stalls in the last 32bit cmd)
//In this case VIF will end
vif1Regs.stat.FQC = std::min((u32)0x10, vif1ch.qwc);
if((vif1ch.qwc > 0 || !vif1.done) && !CHECK_VIF1STALLHACK)
if ((vif1ch.qwc > 0 || !vif1.done) && !CHECK_VIF1STALLHACK)
{
vif1Regs.stat.VPS = VPS_DECODING; //If there's more data you need to say it's decoding the next VIF CMD (Onimusha - Blade Warriors)
VIF_LOG("VIF1 Stalled");
@@ -350,40 +367,62 @@ __fi void vif1Interrupt()
//Mirroring change to VIF0
if (vif1.cmd)
{
if (vif1.done && (vif1ch.qwc == 0)) vif1Regs.stat.VPS = VPS_WAITING;
if (vif1.done && (vif1ch.qwc == 0))
vif1Regs.stat.VPS = VPS_WAITING;
}
else
{
vif1Regs.stat.VPS = VPS_IDLE;
}
if (vif1.inprogress & 0x1)
{
_VIF1chain();
// VIF_NORMAL_FROM_MEM_MODE is a very slow operation.
// Timesplitters 2 depends on this beeing a bit higher than 128.
if (vif1ch.chcr.DIR) vif1Regs.stat.FQC = std::min(vif1ch.qwc, (u32)16);
if(!(vif1Regs.stat.VGW && gifUnit.gifPath[GIF_PATH_3].state != GIF_PATH_IDLE)) //If we're waiting on GIF, stop looping, (can be over 1000 loops!)
{
_VIF1chain();
// VIF_NORMAL_FROM_MEM_MODE is a very slow operation.
// Timesplitters 2 depends on this beeing a bit higher than 128.
if (vif1ch.chcr.DIR)
vif1Regs.stat.FQC = std::min(vif1ch.qwc, (u32)16);
if (!(vif1Regs.stat.VGW && gifUnit.gifPath[GIF_PATH_3].state != GIF_PATH_IDLE)) //If we're waiting on GIF, stop looping, (can be over 1000 loops!)
{
if (vif1.waitforvu)
{
//if (cpuGetCycles(VU_MTVU_BUSY) > static_cast<int>(g_vif1Cycles))
// DevCon.Warning("Waiting %d instead of %d", cpuGetCycles(VU_MTVU_BUSY), static_cast<int>(g_vif1Cycles));
CPU_INT(DMAC_VIF1, std::max(static_cast<int>(g_vif1Cycles), cpuGetCycles(VU_MTVU_BUSY)));
}
else
CPU_INT(DMAC_VIF1, g_vif1Cycles);
return;
}
}
return;
}
if (!vif1.done)
{
if (!vif1.done)
{
if (!(dmacRegs.ctrl.DMAE) || vif1Regs.stat.VSS) //Stopped or DMA Disabled
{
//Console.WriteLn("vif1 dma masked");
return;
}
if (!(dmacRegs.ctrl.DMAE) || vif1Regs.stat.VSS) //Stopped or DMA Disabled
{
//Console.WriteLn("vif1 dma masked");
return;
}
if ((vif1.inprogress & 0x1) == 0) vif1SetupTransfer();
if (vif1ch.chcr.DIR) vif1Regs.stat.FQC = std::min(vif1ch.qwc, (u32)16);
if ((vif1.inprogress & 0x1) == 0)
vif1SetupTransfer();
if (vif1ch.chcr.DIR)
vif1Regs.stat.FQC = std::min(vif1ch.qwc, (u32)16);
if(!(vif1Regs.stat.VGW && gifUnit.gifPath[GIF_PATH_3].state != GIF_PATH_IDLE)) //If we're waiting on GIF, stop looping, (can be over 1000 loops!)
CPU_INT(DMAC_VIF1, g_vif1Cycles);
return;
if (!(vif1Regs.stat.VGW && gifUnit.gifPath[GIF_PATH_3].state != GIF_PATH_IDLE)) //If we're waiting on GIF, stop looping, (can be over 1000 loops!)
{
if (vif1.waitforvu)
{
//if (cpuGetCycles(VU_MTVU_BUSY) > static_cast<int>(g_vif1Cycles))
// DevCon.Warning("Waiting %d instead of %d", cpuGetCycles(VU_MTVU_BUSY), static_cast<int>(g_vif1Cycles));
CPU_INT(DMAC_VIF1, std::max(static_cast<int>(g_vif1Cycles), cpuGetCycles(VU_MTVU_BUSY)));
}
else
CPU_INT(DMAC_VIF1, g_vif1Cycles);
}
return;
}
if (vif1.vifstalled.enabled && vif1.done)
@@ -393,52 +432,55 @@ __fi void vif1Interrupt()
return; //Dont want to end if vif is stalled.
}
#ifdef PCSX2_DEVBUILD
if (vif1ch.qwc > 0) DevCon.WriteLn("VIF1 Ending with %x QWC left", vif1ch.qwc);
if (vif1.cmd != 0) DevCon.WriteLn("vif1.cmd still set %x tag size %x", vif1.cmd, vif1.tag.size);
if (vif1ch.qwc > 0)
DevCon.WriteLn("VIF1 Ending with %x QWC left", vif1ch.qwc);
if (vif1.cmd != 0)
DevCon.WriteLn("vif1.cmd still set %x tag size %x", vif1.cmd, vif1.tag.size);
#endif
if((vif1ch.chcr.DIR == VIF_NORMAL_TO_MEM_MODE) && vif1.GSLastDownloadSize <= 16)
if ((vif1ch.chcr.DIR == VIF_NORMAL_TO_MEM_MODE) && vif1.GSLastDownloadSize <= 16)
{
//Reverse fifo has finished and nothing is left, so lets clear the outputting flag
gifRegs.stat.OPH = false;
}
if (vif1ch.chcr.DIR) vif1Regs.stat.FQC = std::min(vif1ch.qwc, (u32)16);
if (vif1ch.chcr.DIR)
vif1Regs.stat.FQC = std::min(vif1ch.qwc, (u32)16);
vif1ch.chcr.STR = false;
vif1.vifstalled.enabled = false;
vif1.irqoffset.enabled = false;
if(vif1.queued_program) vifExecQueue(1);
if (vif1.queued_program)
vifExecQueue(1);
g_vif1Cycles = 0;
VIF_LOG("VIF1 DMA End");
hwDmacIrq(DMAC_VIF1);
}
void dmaVIF1()
{
VIF_LOG("dmaVIF1 chcr = %lx, madr = %lx, qwc = %lx\n"
" tadr = %lx, asr0 = %lx, asr1 = %lx",
vif1ch.chcr._u32, vif1ch.madr, vif1ch.qwc,
vif1ch.tadr, vif1ch.asr0, vif1ch.asr1);
" tadr = %lx, asr0 = %lx, asr1 = %lx",
vif1ch.chcr._u32, vif1ch.madr, vif1ch.qwc,
vif1ch.tadr, vif1ch.asr0, vif1ch.asr1);
g_vif1Cycles = 0;
vif1.inprogress = 0;
if (vif1ch.qwc > 0) // Normal Mode
if (vif1ch.qwc > 0) // Normal Mode
{
// ignore tag if it's a GS download (Def Jam Fight for NY)
if(vif1ch.chcr.MOD == CHAIN_MODE && vif1ch.chcr.DIR)
if (vif1ch.chcr.MOD == CHAIN_MODE && vif1ch.chcr.DIR)
{
vif1.dmamode = VIF_CHAIN_MODE;
//DevCon.Warning(L"VIF1 QWC on Chain CHCR " + vif1ch.chcr.desc());
if ((vif1ch.chcr.tag().ID == TAG_REFE) || (vif1ch.chcr.tag().ID == TAG_END) || (vif1ch.chcr.tag().IRQ && vif1ch.chcr.TIE))
{
vif1.done = true;
}
else
else
{
vif1.done = false;
}
@@ -448,12 +490,13 @@ void dmaVIF1()
if (dmacRegs.ctrl.STD == STD_VIF1)
Console.WriteLn("DMA Stall Control on VIF1 normal not implemented - Report which game to PCSX2 Team");
if (vif1ch.chcr.DIR) // from Memory
if (vif1ch.chcr.DIR) // from Memory
vif1.dmamode = VIF_NORMAL_FROM_MEM_MODE;
else
vif1.dmamode = VIF_NORMAL_TO_MEM_MODE;
if(vif1.irqoffset.enabled && !vif1.done) DevCon.Warning("Warning! VIF1 starting a Normal transfer with vif offset set (Possible force stop?)");
if (vif1.irqoffset.enabled && !vif1.done)
DevCon.Warning("Warning! VIF1 starting a Normal transfer with vif offset set (Possible force stop?)");
vif1.done = true;
}
@@ -464,10 +507,10 @@ void dmaVIF1()
vif1.inprogress &= ~0x1;
vif1.dmamode = VIF_CHAIN_MODE;
vif1.done = false;
}
if (vif1ch.chcr.DIR) vif1Regs.stat.FQC = std::min((u32)0x10, vif1ch.qwc);
if (vif1ch.chcr.DIR)
vif1Regs.stat.FQC = std::min((u32)0x10, vif1ch.qwc);
// Check VIF isn't stalled before starting the loop.
// Batman Vengence does something stupid and instead of cancelling a stall it tries to restart VIF, THEN check the stall
+91 -60
View File
@@ -27,9 +27,9 @@ static u32 qwctag(u32 mask)
static u32 QWCinVIFMFIFO(u32 DrainADDR, u32 qwc)
{
u32 ret;
//Calculate what we have in the fifo.
if(DrainADDR <= spr0ch.madr)
if (DrainADDR <= spr0ch.madr)
{
//Drain is below the tadr, calculate the difference between them
ret = (spr0ch.madr - DrainADDR) >> 4;
@@ -37,7 +37,7 @@ static u32 QWCinVIFMFIFO(u32 DrainADDR, u32 qwc)
else
{
u32 limit = dmacRegs.rbor.ADDR + dmacRegs.rbsr.RMSK + 16;
//Drain is higher than SPR so it has looped round,
//Drain is higher than SPR so it has looped round,
//calculate from base to the SPR tag addr and what is left in the top of the ring
ret = ((spr0ch.madr - dmacRegs.rbor.ADDR) + (limit - DrainADDR)) >> 4;
}
@@ -50,18 +50,19 @@ static __fi bool mfifoVIF1rbTransfer()
{
u32 msize = dmacRegs.rbor.ADDR + dmacRegs.rbsr.RMSK + 16;
u32 mfifoqwc = std::min(QWCinVIFMFIFO(vif1ch.madr, vif1ch.qwc), vif1ch.qwc);
u32 *src;
u32* src;
bool ret;
if (mfifoqwc == 0) {
if (mfifoqwc == 0)
{
DevCon.Warning("VIF MFIFO no QWC before transfer (in transfer function, bit late really)");
return true; //Cant do anything, lets forget it
return true; //Cant do anything, lets forget it
}
/* Check if the transfer should wrap around the ring buffer */
if ((vif1ch.madr + (mfifoqwc << 4)) > (msize))
{
int s1 = ((msize) - vif1ch.madr) >> 2;
int s1 = ((msize)-vif1ch.madr) >> 2;
VIF_LOG("Split MFIFO");
@@ -69,7 +70,8 @@ static __fi bool mfifoVIF1rbTransfer()
vif1ch.madr = qwctag(vif1ch.madr);
src = (u32*)PSM(vif1ch.madr);
if (src == NULL) return false;
if (src == NULL)
return false;
if (vif1.irqoffset.enabled)
ret = VIF1transfer(src + vif1.irqoffset.value, s1 - vif1.irqoffset.value);
@@ -78,15 +80,17 @@ static __fi bool mfifoVIF1rbTransfer()
if (ret)
{
if(vif1.irqoffset.value != 0) DevCon.Warning("VIF1 MFIFO Offest != 0! vifoffset=%x", vif1.irqoffset.value);
/* and second copy 's2' bytes from 'maddr' to '&data[s1]' */
if (vif1.irqoffset.value != 0)
DevCon.Warning("VIF1 MFIFO Offest != 0! vifoffset=%x", vif1.irqoffset.value);
/* and second copy 's2' bytes from 'maddr' to '&data[s1]' */
//DevCon.Warning("Loopyloop");
vif1ch.tadr = qwctag(vif1ch.tadr);
vif1ch.madr = qwctag(vif1ch.madr);
src = (u32*)PSM(vif1ch.madr);
if (src == NULL) return false;
VIF1transfer(src, ((mfifoqwc << 2) - s1));
src = (u32*)PSM(vif1ch.madr);
if (src == NULL)
return false;
VIF1transfer(src, ((mfifoqwc << 2) - s1));
}
}
else
@@ -95,7 +99,8 @@ static __fi bool mfifoVIF1rbTransfer()
/* it doesn't, so just transfer 'qwc*4' words */
src = (u32*)PSM(vif1ch.madr);
if (src == NULL) return false;
if (src == NULL)
return false;
if (vif1.irqoffset.enabled)
ret = VIF1transfer(src + vif1.irqoffset.value, mfifoqwc * 4 - vif1.irqoffset.value);
@@ -115,10 +120,11 @@ static __fi void mfifo_VIF1chain()
}
if (vif1ch.madr >= dmacRegs.rbor.ADDR &&
vif1ch.madr < (dmacRegs.rbor.ADDR + dmacRegs.rbsr.RMSK + 16u))
vif1ch.madr < (dmacRegs.rbor.ADDR + dmacRegs.rbsr.RMSK + 16u))
{
//if(vif1ch.madr == (dmacRegs.rbor.ADDR + dmacRegs.rbsr.RMSK + 16)) DevCon.Warning("Edge VIF1");
if (QWCinVIFMFIFO(vif1ch.madr, vif1ch.qwc) == 0) {
if (QWCinVIFMFIFO(vif1ch.madr, vif1ch.qwc) == 0)
{
VIF_LOG("VIF MFIFO Empty before transfer");
vif1.inprogress |= 0x10;
g_vif1Cycles += 4;
@@ -132,16 +138,16 @@ static __fi void mfifo_VIF1chain()
//It does an END tag (which normally doesn't increment TADR because it breaks Soul Calibur 2)
//with a QWC of 1 (rare) so we need to increment the TADR in the case of MFIFO.
vif1ch.tadr = vif1ch.madr;
}
else
{
tDMA_TAG *pMem = dmaGetAddr(vif1ch.madr, !vif1ch.chcr.DIR);
tDMA_TAG* pMem = dmaGetAddr(vif1ch.madr, !vif1ch.chcr.DIR);
VIF_LOG("Non-MFIFO Location");
//No need to exit on non-mfifo as it is indirect anyway, so it can be transferring this while spr refills the mfifo
if (pMem == NULL) return;
if (pMem == NULL)
return;
if (vif1.irqoffset.enabled)
VIF1transfer((u32*)pMem + vif1.irqoffset.value, vif1ch.qwc * 4 - vif1.irqoffset.value);
@@ -152,19 +158,20 @@ static __fi void mfifo_VIF1chain()
void mfifoVifMaskMem(int id)
{
switch (id) {
switch (id)
{
//These five transfer data following the tag, need to check its within the buffer (Front Mission 4)
case TAG_CNT:
case TAG_NEXT:
case TAG_CALL:
case TAG_CALL:
case TAG_RET:
case TAG_END:
if(vif1ch.madr < dmacRegs.rbor.ADDR) //probably not needed but we will check anyway.
if (vif1ch.madr < dmacRegs.rbor.ADDR) //probably not needed but we will check anyway.
{
//DevCon.Warning("VIF MFIFO MADR below bottom of ring buffer, wrapping VIF MADR = %x Ring Bottom %x", vif1ch.madr, dmacRegs.rbor.ADDR);
vif1ch.madr = qwctag(vif1ch.madr);
}
if(vif1ch.madr > (dmacRegs.rbor.ADDR + (u32)dmacRegs.rbsr.RMSK)) //Usual scenario is the tag is near the end (Front Mission 4)
if (vif1ch.madr > (dmacRegs.rbor.ADDR + (u32)dmacRegs.rbsr.RMSK)) //Usual scenario is the tag is near the end (Front Mission 4)
{
//DevCon.Warning("VIF MFIFO MADR outside top of ring buffer, wrapping VIF MADR = %x Ring Top %x", vif1ch.madr, (dmacRegs.rbor.ADDR + dmacRegs.rbsr.RMSK)+16);
vif1ch.madr = qwctag(vif1ch.madr);
@@ -178,13 +185,14 @@ void mfifoVifMaskMem(int id)
void mfifoVIF1transfer()
{
tDMA_TAG *ptag;
tDMA_TAG* ptag;
g_vif1Cycles = 0;
if (vif1ch.qwc == 0)
{
if (QWCinVIFMFIFO(vif1ch.tadr, 1) == 0) {
if (QWCinVIFMFIFO(vif1ch.tadr, 1) == 0)
{
VIF_LOG("VIF MFIFO Empty before tag");
vif1.inprogress |= 0x10;
g_vif1Cycles += 4;
@@ -212,21 +220,20 @@ void mfifoVIF1transfer()
if (vif1.irqoffset.enabled)
{
ret = VIF1transfer((u32*)&masked_tag + vif1.irqoffset.value, 4 - vif1.irqoffset.value, true); //Transfer Tag on stall
ret = VIF1transfer((u32*)&masked_tag + vif1.irqoffset.value, 4 - vif1.irqoffset.value, true); //Transfer Tag on stall
//ret = VIF1transfer((u32*)ptag + (2 + vif1.irqoffset), 2 - vif1.irqoffset); //Transfer Tag on stall
}
else
{
vif1.irqoffset.value = 2;
vif1.irqoffset.enabled = true;
ret = VIF1transfer((u32*)&masked_tag + 2, 2, true); //Transfer Tag
ret = VIF1transfer((u32*)&masked_tag + 2, 2, true); //Transfer Tag
}
if (!ret && vif1.irqoffset.enabled)
{
vif1.inprogress &= ~1;
return; //IRQ set by VIFTransfer
return; //IRQ set by VIFTransfer
}
g_vif1Cycles += 2;
}
@@ -234,12 +241,12 @@ void mfifoVIF1transfer()
vif1.irqoffset.value = 0;
vif1.irqoffset.enabled = false;
vif1ch.unsafeTransfer(ptag);
vif1ch.unsafeTransfer(ptag);
vif1ch.madr = ptag[1]._u32;
VIF_LOG("dmaChain %8.8x_%8.8x size=%d, id=%d, madr=%lx, tadr=%lx spr0 madr = %x",
ptag[1]._u32, ptag[0]._u32, vif1ch.qwc, ptag->ID, vif1ch.madr, vif1ch.tadr, spr0ch.madr);
ptag[1]._u32, ptag[0]._u32, vif1ch.qwc, ptag->ID, vif1ch.madr, vif1ch.tadr, spr0ch.madr);
vif1.done |= hwDmacSrcChainWithStack(vif1ch, ptag->ID);
@@ -253,13 +260,14 @@ void mfifoVIF1transfer()
vif1ch.tadr = qwctag(vif1ch.tadr);
if(vif1ch.qwc > 0) vif1.inprogress |= 1;
if (vif1ch.qwc > 0)
vif1.inprogress |= 1;
}
else
{
DevCon.Warning("Vif MFIFO QWC not 0 on tag");
}
VIF_LOG("mfifoVIF1transfer end %x madr %x, tadr %x", vif1ch.chcr._u32, vif1ch.madr, vif1ch.tadr);
}
@@ -269,33 +277,36 @@ void vifMFIFOInterrupt()
g_vif1Cycles = 0;
VIF_LOG("vif mfifo interrupt");
if (dmacRegs.ctrl.MFD != MFD_VIF1) {
if (dmacRegs.ctrl.MFD != MFD_VIF1)
{
vif1Interrupt();
return;
}
if( gifRegs.stat.APATH == 2 && gifUnit.gifPath[1].isDone())
if (gifRegs.stat.APATH == 2 && gifUnit.gifPath[1].isDone())
{
gifRegs.stat.APATH = 0;
gifRegs.stat.OPH = 0;
if(gifUnit.checkPaths(1,0,1)) gifUnit.Execute(false, true);
if (gifUnit.checkPaths(1, 0, 1))
gifUnit.Execute(false, true);
}
if (vif1ch.chcr.DIR) {
bool isDirect = (vif1.cmd & 0x7f) == 0x50;
if (vif1ch.chcr.DIR)
{
bool isDirect = (vif1.cmd & 0x7f) == 0x50;
bool isDirectHL = (vif1.cmd & 0x7f) == 0x51;
if((isDirect && !gifUnit.CanDoPath2())
|| (isDirectHL && !gifUnit.CanDoPath2HL())) {
if ((isDirect && !gifUnit.CanDoPath2()) || (isDirectHL && !gifUnit.CanDoPath2HL()))
{
GUNIT_WARN("vifMFIFOInterrupt() - Waiting for Path 2 to be ready");
CPU_INT(DMAC_MFIFO_VIF, 128);
return;
}
}
if(vif1.waitforvu)
if (vif1.waitforvu)
{
// DevCon.Warning("Waiting on VU1 MFIFO");
CPU_INT(VIF_VU1_FINISH, 16);
//DevCon.Warning("Waiting on VU1 MFIFO");
CPU_INT(VIF_VU1_FINISH, std::max(16, cpuGetCycles(VU_MTVU_BUSY)));
return;
}
@@ -303,25 +314,29 @@ void vifMFIFOInterrupt()
// we handle that separately (KH2 for testing)
// Simulated GS transfer time done, clear the flags
if (vif1.irq && vif1.vifstalled.enabled && vif1.vifstalled.value == VIF_IRQ_STALL) {
if (vif1.irq && vif1.vifstalled.enabled && vif1.vifstalled.value == VIF_IRQ_STALL)
{
VIF_LOG("VIF MFIFO Code Interrupt detected");
vif1Regs.stat.INT = true;
if (((vif1Regs.code >> 24) & 0x7f) != 0x7) {
if (((vif1Regs.code >> 24) & 0x7f) != 0x7)
{
vif1Regs.stat.VIS = true;
}
hwIntcIrq(INTC_VIF1);
--vif1.irq;
if (vif1Regs.stat.test(VIF1_STAT_VSS | VIF1_STAT_VIS | VIF1_STAT_VFS)) {
if (vif1Regs.stat.test(VIF1_STAT_VSS | VIF1_STAT_VIS | VIF1_STAT_VFS))
{
//vif1Regs.stat.FQC = 0; // FQC=0
//vif1ch.chcr.STR = false;
vif1Regs.stat.FQC = std::min((u32)0x10, vif1ch.qwc);
VIF_LOG("VIF1 MFIFO Stalled qwc = %x done = %x inprogress = %x", vif1ch.qwc, vif1.done, vif1.inprogress & 0x10);
//Used to check if the MFIFO was empty, there's really no need if it's finished what it needed.
if((vif1ch.qwc > 0 || !vif1.done)) {
if ((vif1ch.qwc > 0 || !vif1.done))
{
vif1Regs.stat.VPS = VPS_DECODING; //If there's more data you need to say it's decoding the next VIF CMD (Onimusha - Blade Warriors)
VIF_LOG("VIF1 MFIFO Stalled");
return;
@@ -330,33 +345,48 @@ void vifMFIFOInterrupt()
}
//Mirroring change to VIF0
if (vif1.cmd) {
if (vif1.done && vif1ch.qwc == 0) vif1Regs.stat.VPS = VPS_WAITING;
if (vif1.cmd)
{
if (vif1.done && vif1ch.qwc == 0)
vif1Regs.stat.VPS = VPS_WAITING;
}
else {
else
{
vif1Regs.stat.VPS = VPS_IDLE;
}
if(vif1.inprogress & 0x10) {
if (vif1.inprogress & 0x10)
{
FireMFIFOEmpty();
return;
}
vif1.vifstalled.enabled = false;
if (!vif1.done || vif1ch.qwc) {
switch(vif1.inprogress & 1) {
if (!vif1.done || vif1ch.qwc)
{
switch (vif1.inprogress & 1)
{
case 0: //Set up transfer
mfifoVIF1transfer();
vif1Regs.stat.FQC = std::min((u32)0x10, vif1ch.qwc);
[[fallthrough]];
case 1: //Transfer data
if(vif1.inprogress & 0x1) //Just in case the tag breaks early (or something wierd happens)!
if (vif1.inprogress & 0x1) //Just in case the tag breaks early (or something wierd happens)!
mfifo_VIF1chain();
//Sanity check! making sure we always have non-zero values
if(!(vif1Regs.stat.VGW && gifUnit.gifPath[GIF_PATH_3].state != GIF_PATH_IDLE)) //If we're waiting on GIF, stop looping, (can be over 1000 loops!)
CPU_INT(DMAC_MFIFO_VIF, (g_vif1Cycles == 0 ? 4 : g_vif1Cycles) );
if (!(vif1Regs.stat.VGW && gifUnit.gifPath[GIF_PATH_3].state != GIF_PATH_IDLE)) //If we're waiting on GIF, stop looping, (can be over 1000 loops!)
{
if (vif1.waitforvu)
{
//if (cpuGetCycles(VU_MTVU_BUSY) > static_cast<int>(g_vif1Cycles))
// DevCon.Warning("Waiting %d instead of %d", cpuGetCycles(VU_MTVU_BUSY), static_cast<int>(g_vif1Cycles));
CPU_INT(DMAC_MFIFO_VIF, std::max(static_cast<int>((g_vif1Cycles == 0 ? 4 : g_vif1Cycles)), cpuGetCycles(VU_MTVU_BUSY)));
}
else
CPU_INT(DMAC_MFIFO_VIF, (g_vif1Cycles == 0 ? 4 : g_vif1Cycles));
}
vif1Regs.stat.FQC = std::min((u32)0x10, vif1ch.qwc);
return;
@@ -368,7 +398,8 @@ void vifMFIFOInterrupt()
vif1.irqoffset.enabled = false;
vif1.done = 1;
if (spr0ch.madr == vif1ch.tadr) {
if (spr0ch.madr == vif1ch.tadr)
{
FireMFIFOEmpty();
}
-1
View File
@@ -341,7 +341,6 @@ namespace Panels
void Defaults_Click(wxCommandEvent& evt);
void EECycleRate_Scroll(wxScrollEvent& event);
void VUCycleRate_Scroll(wxScrollEvent& event);
void VUThread_Enable(wxCommandEvent& evt);
};
// --------------------------------------------------------------------------------------
+3 -15
View File
@@ -167,16 +167,16 @@ Panels::SpeedHacksPanel::SpeedHacksPanel( wxWindow* parent )
m_check_vuThread = new pxCheckBox( vuHacksPanel, _("MTVU (Multi-Threaded microVU1)"),
_("Good Speedup and High Compatibility; may cause hanging... [Recommended on 3+ cores]") );
m_check_vu1Instant = new pxCheckBox(vuHacksPanel, _("Instant VU1 (without MTVU only)"),
m_check_vu1Instant = new pxCheckBox(vuHacksPanel, _("Instant VU1"),
_("Good Speedup and High Compatibility; may cause some graphical errors"));
m_check_vuFlagHack->SetToolTip( pxEt( L"Updates Status Flags only on blocks which will read them, instead of all the time. This is safe most of the time."
) );
m_check_vuThread->SetToolTip( pxEt( L"Runs VU1 on its own thread (microVU1-only). Generally a speedup on CPUs with 3 or more cores. This is safe for most games, but a few games are incompatible and may hang. In the case of GS limited games, it may be a slowdown (especially on dual core CPUs)."
m_check_vuThread->SetToolTip( pxEt( L"Runs VU1 on its own thread. Generally a speedup on CPUs with 3 or more cores. This is safe for most games, but a few games are incompatible and may hang. In the case of GS limited games, it may be a slowdown (especially on dual core CPUs)."
) );
m_check_vu1Instant->SetToolTip(pxEt(L"Runs VU1 instantly (when MTVU is disabled). Provides a modest speed improvement. This is safe for most games, but a few games may exhibit graphical errors."
m_check_vu1Instant->SetToolTip(pxEt(L"Runs VU1 instantly. Provides a modest speed improvement in most games. This is safe for most games, but a few games may exhibit graphical errors."
));
// ------------------------------------------------------------------------
@@ -237,7 +237,6 @@ Panels::SpeedHacksPanel::SpeedHacksPanel( wxWindow* parent )
Bind(wxEVT_SCROLL_CHANGED, &SpeedHacksPanel::VUCycleRate_Scroll, this, m_slider_eeSkip->GetId());
Bind(wxEVT_CHECKBOX, &SpeedHacksPanel::OnEnable_Toggled, this, m_check_Enable->GetId());
Bind(wxEVT_BUTTON, &SpeedHacksPanel::Defaults_Click, this, wxID_DEFAULT);
Bind(wxEVT_CHECKBOX, &SpeedHacksPanel::VUThread_Enable, this, m_check_vuThread->GetId());
}
// Doesn't modify values - only locks(gray out)/unlocks as necessary.
@@ -267,11 +266,6 @@ void Panels::SpeedHacksPanel::EnableStuff( AppConfig* configToUse )
// Grayout MTVU on safest preset
m_check_vuThread->Enable(hacksEnabled && (!hasPreset || configToUse->PresetIndex != 0));
// Disables the Instant VU1 checkbox when MTVU is checked in the GUI as reflected in the code.
// Makes Instant VU1 toggleable when MTVU is unchecked in the GUI.
// Some may think that having MTVU + Instant VU1 checked, can have bad side-effects when it doesn't.
m_check_vu1Instant->Enable(hacksEnabled && !m_check_vuThread->GetValue());
// Layout necessary to ensure changed slider text gets re-aligned properly
// and to properly gray/ungray pxStaticText stuff (I suspect it causes a
// paint event to be sent on Windows)
@@ -370,9 +364,3 @@ void Panels::SpeedHacksPanel::VUCycleRate_Scroll(wxScrollEvent &event)
event.Skip();
}
void Panels::SpeedHacksPanel::VUThread_Enable(wxCommandEvent& evt)
{
m_check_vu1Instant->Enable(!m_check_vuThread->GetValue());
Layout();
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 30 KiB