Compare commits

...

110 Commits

Author SHA1 Message Date
refractionpcsx2
d2202a4e33 sadfs 2023-02-10 20:05:39 +00:00
Stenzek
9b1163f959 GS/HW: Use sampler for clamp/repeat when it's redundant
But only for local memory sources (not targets), it causes issues with
upscaling otherwise.
2023-02-10 11:20:37 +00:00
Stenzek
5b5edc506d Misc: Warning fixes 2023-02-10 11:20:37 +00:00
refractionpcsx2
b30b4375e7 GS: Detect 16bit source copy in to 32bit target shuffle 2023-02-10 11:19:41 +00:00
Stenzek
1b0c03d892 GS/HW: Fix fixed/invalid TEX0 case in TimeSplitters 2 2023-02-10 03:43:10 +00:00
Ziemas
bca49184e7 Debugger: Fix missing return path. (#8094) 2023-02-09 19:48:54 +00:00
Stenzek
26d6c33163 GSRunner: Clear bindings and disable game settings 2023-02-09 17:29:47 +01:00
Ziemas
9420615317 Implement thread listing for IOP
Abstracts away threads behind a common interface for both EE and IOP
2023-02-09 15:49:56 +00:00
Stenzek
7198c6b8c6 GS: Use u32 instead of size_t for index storage
These are never going to be greater than 2^32-1, and they're truncated
in a bunch of places anyway.
2023-02-09 14:54:42 +00:00
Stenzek
9513864851 GS: Make prim a template parameter for auto flush
It's force inlined into VertexKick(), which also has prim as a template
parameter.
2023-02-09 14:54:42 +00:00
Stenzek
f9b8aa1862 GS: Handle index swap case for auto flush
DirectX and OpenGL no longer have different draw splitting.
2023-02-09 14:54:42 +00:00
refractionpcsx2
34371c070c Mcd-Folder: Prevent crash trying to delete non-existent node. 2023-02-09 14:06:21 +00:00
Stenzek
d6099dd263 GS/Vulkan: Use Bresenham line rasterization when supported 2023-02-09 13:42:05 +00:00
Stenzek
a1bc39141e PAD: Fix trigger deadzone/scale setting 2023-02-09 12:30:20 +00:00
Connor McLaughlin
13ed41d077 GS/DX11: Disable multisampling in rasterizer state 2023-02-09 11:13:42 +00:00
Stenzek
8a0a8f718f GameDB: Remove preloading disable for a bunch of games
These are fine now.
2023-02-09 10:51:06 +00:00
Stenzek
7d08a54ad9 GS/HW: Optimize TC source size based on CLAMP 2023-02-09 10:51:06 +00:00
Stenzek
b9b47e3ec7 GS/OGL: Fix upload row length for texture Update() 2023-02-09 10:51:06 +00:00
refractionpcsx2
16989f2122 GS-HW: On overwrite of large incompatible format texture, erase old. 2023-02-09 10:50:40 +00:00
Berylskid
892d3a370d GameDB: Fix texture flickers of Armored Core Nexus 2023-02-09 09:37:17 +00:00
Stenzek
6af7ca9867 PAD: Fix invert in deadzone computation 2023-02-09 10:03:55 +01:00
Stenzek
c3c354f794 PAD: Add trigger deadzone/sensitivity settings 2023-02-09 10:03:55 +01:00
Stenzek
43572a1560 Qt: Mark widget binder functions as inline
Should also fix the unused warning without using [[maybe_unused]];
2023-02-09 10:03:55 +01:00
Stenzek
79daed63ee Qt: Handle multiplier in controller settings 2023-02-09 10:03:55 +01:00
Stenzek
21d3ad86d4 Qt: Make controller settings page scrollable 2023-02-09 10:03:55 +01:00
Stenzek
31ebe842e8 Qt: Add per-bind sensitivity/deadzone controls (shift-click) 2023-02-09 10:03:55 +01:00
eyfix
88487de72f USB: Add values to GunCon2 config. (#8078) 2023-02-09 09:55:23 +01:00
Haruka
616da8c99d CDVD: simplify code 2023-02-09 09:52:00 +01:00
Haruka
ec8712cceb CDVD: use DevCon logging instead of stdout / stderr 2023-02-09 09:52:00 +01:00
Haruka
3d6923b2a1 CDVD: implement SetSpindleSpeed on macOS 2023-02-09 09:52:00 +01:00
Haruka
f04337becf CDVD: fix macOS CD reading 2023-02-09 09:52:00 +01:00
Haruka
9a5dd4c19d CDVD: macOS physical disc support 2023-02-09 09:52:00 +01:00
lightningterror
643e0b1039 GameDB: Add Autoflush hw fix to MGS3 Subsistence.
Fixes lens flare.
2023-02-09 07:06:21 +01:00
Stenzek
330061a6e7 GS/OGL: Fix offseted downloads not being offset
Fixes background screen effect in Gradius V.
2023-02-09 07:04:50 +01:00
Stenzek
0a292715cf GS/OGL: Align texture uploads to 64 bytes
Fixes potential crash in some games with odd-sized targets
and preloading (e.g. Densha De Go).
2023-02-09 07:04:50 +01:00
Berylskid
52f034a513 GameDB: Add missing quatation marks in memcardFilters for Armored Core Last Raven 2023-02-08 17:32:49 +01:00
Stenzek
25e05388ba Qt: Open fullscreen window on same display as main 2023-02-08 17:29:59 +01:00
JordanTheToaster
7ad9a1af03 GameDB: Add Paltex to Gacharoku
Stops the hash cache from being silly and exploding.
2023-02-08 17:29:22 +01:00
refractionpcsx2
9346c69343 GS-HW: Iterate dirty rects in reverse to join existing ones. 2023-02-08 00:05:44 +00:00
refractionpcsx2
4a3f0ccf96 GS-HW: Process dirty rects separately, improved Tex in RT compatibility 2023-02-08 00:05:44 +00:00
refractionpcsx2
52a1396e29 GSDumpRunner: Fix new options, add missing Preload Frame 2023-02-07 23:40:01 +00:00
JordanTheToaster
f556dd2584 GameDB: Add missing The Godfather fixes
Adds missing fixes for other entry's of The Godfarther.
2023-02-07 23:26:04 +00:00
refractionpcsx2
aea5c09825 GSDumpRunner: Ability to enable manual hw hacks for testing 2023-02-07 22:31:36 +00:00
refractionpcsx2
8c3c9a1219 GSDumpRunner: Tweaks to reduce console messages and redundant frame dumps 2023-02-07 22:31:36 +00:00
eyfix
a4f1f383a7 USB/LightGun/GunCon2: Fix wrong code of GunCon2. (#8066)
240 is used for the default screen height elsewhere in the file.
BID_SHOOT_OFFSCREEN is already handled at line 295.
2023-02-07 06:21:48 +01:00
JordanTheToaster
ee73c5c1b5 GameDB: Various small fixes
Fixes for Transformers The Game to fix bloom and mipmapping issues and add missing db entrys.
2023-02-07 06:19:59 +01:00
refractionpcsx2
c2904a4633 GS-HW: Don't enable merge sprite in native resolution 2023-02-06 23:59:06 +00:00
JordanTheToaster
6faa2249f9 GameDB: Add Disable InstantVU to MGS 3 Subsistence
Disables InstantVU1 to fix missing letters in text such as the letter E also adds more fixes and missing fixes for Forever Kingdom / Evergrace 2.
2023-02-07 00:24:47 +01:00
lightningterror
119c3acfe7 GS-tc: Cleanup hw texturecache.
Initializations, constants, casts.
2023-02-07 00:22:46 +01:00
lightningterror
138a2683f2 USB: Fix Variable is assigned a value that is never used warning.
Codacy.
2023-02-07 00:22:46 +01:00
PCSX2 Bot
463637fa10 PAD: Update to latest controller database. 2023-02-07 00:19:55 +01:00
refractionpcsx2
d5aab926bf GameDB: Add Game Fix for Scandal 2023-02-05 17:40:02 +00:00
KamFretoZ
37ba04b770 GameDB: Fixes for "Toy Story 3" & "WALL-E" (#8045) 2023-02-05 14:53:09 +00:00
refractionpcsx2
3d0b7dee71 GameDB: Update K-1 Grand Prix fixes to use Instant DMA instead.
This seems to be more reliable in correcting the issue, and for some reason the old way is no longer working properly.
2023-02-05 14:50:36 +00:00
JordanTheToaster
a287c2caac GameDB: Add Align Sprite to Football Kingdom
Fixes vertical lines.
2023-02-05 14:42:58 +00:00
Stenzek
1b673d9dd0 GS/HW: Simplify m_vertex/m_index
Switches DX11 to a larger streaming buffer, adds missing texture copy
counter for colclip.
2023-02-04 12:30:24 +00:00
Stenzek
d10621c201 GS/DX11: Use annotations for debug messages 2023-02-04 12:30:24 +00:00
Ziemas
e1d6dfc324 Add command line boot and debug option 2023-02-04 12:30:07 +00:00
Stenzek
130ea2a7ca PAD: Fix pressure getting set to 1 after modifier 2023-02-04 12:29:53 +00:00
Stenzek
c8d53253d2 Qt: Defer application quit on window close
Fixes Mac builds crashing when the Metal renderer tries to clear the
layer on a non-existant window.
2023-02-04 12:29:44 +00:00
Stenzek
5c67438925 VMManager: Only exit CPU execution if we were running
Fixes crash if you were paused when you shut down the VM with the EE
interpreter enabled.
2023-02-04 12:29:44 +00:00
lightningterror
1012dba8d7 GameDB: Add basic mipmap to Narc and Tom & Jerry War of the Whiskers.
Fixes textures.
2023-02-03 23:51:05 +01:00
Mrlinkwii
e8d43f53d9 GameDB: fixes for 'Tom & Jerry - War of the Whiskers' & 'Brave - The Search for Spirit Dancer'' 2023-02-03 18:19:42 +00:00
RedDevilus
a0e8ce4b13 Qt: Fix compatibility string
Playable rating was missing which is the most common rating for PCSX2. There are some other issues like the images look bad for star rating and some other stuff. Also resize the compatibility table width a bit.
2023-02-03 16:09:46 +00:00
refractionpcsx2
0e35b3edcb GS: Extend things to handle the alpha blended clear in NARC/Brave 2023-02-03 15:17:10 +00:00
refractionpcsx2
25bb5851ec GS-HW: Fix special CLUT case that uses preloading. 2023-02-03 15:17:10 +00:00
refractionpcsx2
80b0bc0869 GS-HW: Adjust clear detection for clears with FBMSK 2023-02-03 15:17:10 +00:00
refractionpcsx2
c05743b7b9 GS-HW: Improve FBW=1 clear for Singstar games 2023-02-03 15:17:10 +00:00
Stenzek
7c2a1f0f37 GS: Vectorize CLUT rect update 2023-02-03 14:36:37 +00:00
Stenzek
0b8b9e75d1 GS: Don't do CLUT auto flush test on invalid PRIM
Fixes OOB array access in Xenosaga I's cutscenes.
2023-02-03 14:36:37 +00:00
refractionpcsx2
d65c343e91 GameDB: Add preload to Siren 2 2023-02-03 06:54:56 +00:00
refractionpcsx2
cd8e7cc947 GS-HW: Queue preloads from Local->Local moves on CPU also 2023-02-03 06:54:56 +00:00
refractionpcsx2
0df5cf2e91 GS-CLUT: Handle invalidation on wrapping writes 2023-02-03 01:55:25 +00:00
refractionpcsx2
412275e40d GS: Convert ee write list to vector, fix some bugs. 2023-02-03 00:09:54 +00:00
refractionpcsx2
4c7ad66bd7 GS-HW: Check for CLUT upload before invalidating or running on GPU.
Clean up some preload code and improve the detection.
2023-02-03 00:09:54 +00:00
refractionpcsx2
8ac21357c3 GS-HW: Force preload 1 frame after reset 2023-02-03 00:09:54 +00:00
refractionpcsx2
2fd88b901b GS-TC: Don't clear on reset, only invalidate frames.
Fixes bug with counting writes before flushing.
Track what textures are used as frames.

It's possible that further to this the PCRTC disables on CSR reset, but we need to test this first, but that would fix the logo at the bottom when booting through the bios in Software mode.
2023-02-03 00:09:54 +00:00
refractionpcsx2
5697759d9e GS-HW: Try to make Preload Frame Data slightly less gross.
These changes make sure there's a matching EE transfer to texture allocation in that frame, if not, then don't allocate anything. This should reduce bad data being loaded in to the Rt.

Preload replaced for Simple 2000 Series Vol. 114 - The Onna Okappichi Torimonochou, preload worked by chance (but no more), disable depth works.
2023-02-03 00:09:54 +00:00
refractionpcsx2
ee0042c768 GameDB: Remove no longer needed Preload Frame settings 2023-02-03 00:09:54 +00:00
refractionpcsx2
a6212f1388 GS-HW: Always preload new frames for PCRTC if none found. 2023-02-03 00:09:54 +00:00
refractionpcsx2
7bfea60b35 GS-TC: Tex In RT expand target match 2023-02-02 23:46:05 +00:00
refractionpcsx2
beee740dc8 GameDB: Add Mipmapping to Jak X, replace FMV fix on Snowboard Heaven 2023-02-02 23:46:05 +00:00
refractionpcsx2
e31387b8bc GS-TC: Don't Tex in RT on old targets/PCRTC only frames. 2023-02-02 23:46:05 +00:00
TheLastRar
3586a12c46 DEV9: Remove pcap dumper
Never worked in Qt
2023-02-02 21:13:52 +00:00
TheLastRar
bf21254b13 DEV9: Pcap loop instead of returning on invalid packet
Should improve pcap bridged performance when lots of unrelated network traffic is present.
2023-02-02 21:13:52 +00:00
TheLastRar
dc9f61e70a DEV9: Add classes for editing packets and make PCAP use them 2023-02-02 21:13:52 +00:00
TheLastRar
3028998a43 DEV9: Move most of the pcap methods into PCAPAdapter
Also includes the following changes;
pcap_io_running checks replaced with assets in send/recv
pcap_io_running checks replaces checks on hpcap being non null.
Don't cast header->len from unsigned to signed when checking for jumbo frames
Log dropping jumbo frames
Free pcap filter code
Check return value of GetMACAddress() and handle appropriately.
2023-02-02 21:13:52 +00:00
TheLastRar
42511ce8d8 DEV9: Remove unneeded headers
by providing a typedef for Adapter
2023-02-02 21:13:52 +00:00
RedDevilus
c245d2134f GameDB: Driver Parallel Lines + Godfather + ...
Godfather: remove on-screen garbage
Driver Parallel Lines: Reduce misaligned bloom issues
Simple 2000 Series Vol. 109 - The Taxi 2: name fix to be Chauffeur and not like train driver (Utenshi to Untenshu)
Rimococoron
2023-02-02 14:22:06 +00:00
JordanTheToaster
f01884537d GameDB: Assorted fixes
Fixes the name of GT-R 400 and adds round sprite to fix lines in menus and text and corrects the name of 40k Fire Warrior to correctly have a space.
2023-02-01 10:23:11 +00:00
Mrlinkwii
b48fb0d4da Misc : label & comment fixes
Misc : remove old comments
labeler: Add labels for Translations
2023-02-01 10:22:12 +00:00
RedDevilus
7a6470a19d CI: Add RetroAchievements/Rcheevos labels for PRs
Beats doing it manually labels for Rcheevos.
2023-02-01 09:39:30 +00:00
Silent
bfd8fc771a Qt: Clear the status text after gamelist scanning
Improves UI parity with DuckStation.
2023-02-01 09:38:46 +00:00
Connor McLaughlin
f96ca4ac1f GS/OGL: Fix syntax error in fragment shader 2023-02-01 09:04:52 +01:00
TheLastRar
937bfce68e DEV9: Better match HDD size per-game UI to rest of settings 2023-01-31 21:23:05 +00:00
TheLastRar
5869d35d85 DEV9: Consider placeholderText when opening File dialog
Per-game settings store the global setting as placeholderText.
If no per-game setting is present we can prefill the dialog with the global setting, before falling back to a default value
2023-01-31 21:23:05 +00:00
TheLastRar
8d3325e6cd DEV9: Fix HDD file overwrite check 2023-01-31 21:23:05 +00:00
TheLastRar
4badb5b975 DEV9: Fix HddEnable not enabling UI correctly in per-game settings 2023-01-31 21:23:05 +00:00
TheLastRar
7e4ff233ec DEV9: Fix Per-game HDD path
SettingWidgetBinder isn't capable of handling this yet
2023-01-31 21:23:05 +00:00
refractionpcsx2
0e3397239d GS: Correct GSIMR/GSCSR reg init, regression from previous release 2023-01-31 16:55:45 +00:00
refractionpcsx2
08bae3da2e GS: Fix h/vsync counters on mode change/sync. 2023-01-31 14:56:48 +00:00
refractionpcsx2
4b49c8bd6e GS: Correct CSR behaviour on mode change 2023-01-31 14:56:48 +00:00
refractionpcsx2
c1bd1fcbd4 GS: Clear Privilage registers on GS Reset via CSR 2023-01-31 14:56:48 +00:00
TheLastRar
1c3379f082 Qt: Connect close instead of accept for the close button 2023-01-31 02:06:09 +00:00
Mrlinkwii
86c97a8ba3 GameDB: fixes for Megaman X Command Mission 2023-01-30 20:55:18 +00:00
lightningterror
8e6c18d3f4 emitter: Ignore Wmissing-braces warnings on clang. 2023-01-30 20:32:33 +01:00
Goatman13
f1e80c466d IPU: Reset threshold on IPU reset. 2023-01-30 19:13:50 +00:00
Mrlinkwii
9a542bcb20 GameDB: fixes for Simple 2000 Series Vol. 48 2023-01-30 17:31:00 +00:00
JordanTheToaster
290c8ec420 GameDB: Various Tekken fixes
Disables preloading entirely on Tekken Tag Tournament and Tekken 4 and adds missing fix comments and missing db entry's for both games.
2023-01-30 14:03:58 +00:00
Mrlinkwii
517ccd5e40 GameDB:fixes for Dragon Quest VIII - Journey of the Cursed King 2023-01-29 17:14:17 +00:00
103 changed files with 3727 additions and 1962 deletions

5
.github/labeler.yml vendored
View File

@@ -32,6 +32,8 @@
- '**/GameIndex.*'
'Installer | Package':
- 'build.sh'
'Translations':
- 'pcsx2-qt/Translations/*'
# Tools / Features
'Debugger':
@@ -45,6 +47,9 @@
'TAS Functionality':
- 'pcsx2/Recording/*'
- 'pcsx2/Recording/**/*'
'RetroAchievements':
- 'pcsx2/Frontend/Achievements.*'
- 'pcsx2/Achievements.*'
# Emulation Components
'Counters':

File diff suppressed because it is too large Load Diff

View File

@@ -543,6 +543,7 @@
030000009b2800001e00000000000000,Raphnet Vectrex Adapter,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a1,lefty:a2,x:b2,y:b3,platform:Windows,
030000009b2800002b00000000000000,Raphnet Wii Classic Adapter,a:b1,b:b4,back:b2,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,guide:b10,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a3,righty:a4,start:b3,x:b0,y:b5,platform:Windows,
030000009b2800002c00000000000000,Raphnet Wii Classic Adapter,a:b1,b:b4,back:b2,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,guide:b10,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a3,righty:a4,start:b3,x:b0,y:b5,platform:Windows,
030000009b2800008000000000000000,Raphnet Wii Classic Adapter,a:b1,b:b4,x:b0,y:b5,back:b2,guide:b10,start:b3,leftshoulder:b6,rightshoulder:b7,dpup:b12,dpleft:b14,dpdown:b13,dpright:b15,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:b8,righttrigger:b9,platform:Windows,
03000000321500000003000000000000,Razer Hydra,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,righttrigger:a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
03000000321500000204000000000000,Razer Panthera PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
03000000321500000104000000000000,Razer Panthera 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,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,

View File

@@ -21,6 +21,8 @@
#define PS_FST 0
#define PS_WMS 0
#define PS_WMT 0
#define PS_ADJS 0
#define PS_ADJT 0
#define PS_AEM_FMT FMT_32
#define PS_AEM 0
#define PS_TFX 0
@@ -36,13 +38,13 @@
#define PS_POINT_SAMPLER 0
#define PS_SHUFFLE 0
#define PS_READ_BA 0
#define PS_SWAP_GA 0
#define PS_DFMT 0
#define PS_DEPTH_FMT 0
#define PS_PAL_FMT 0
#define PS_CHANNEL_FETCH 0
#define PS_TALES_OF_ABYSS_HLE 0
#define PS_URBAN_CHAOS_HLE 0
#define PS_INVALID_TEX0 0
#define PS_SCALE_FACTOR 1.0
#define PS_HDR 0
#define PS_COLCLIP 0
@@ -158,10 +160,10 @@ cbuffer cb1
float2 TA;
float MaxDepthPS;
float Af;
uint4 MskFix;
uint4 FbMask;
float4 HalfTexel;
float4 MinMax;
float4 STRange;
int4 ChannelShuffle;
float2 TC_OffsetHack;
float2 STScale;
@@ -183,7 +185,20 @@ float4 sample_c(float2 uv, float uv_w)
// As of 2018 this issue is still present.
uv = (trunc(uv * WH.zw) + float2(0.5, 0.5)) / WH.zw;
}
#if !PS_ADJS && !PS_ADJT
uv *= STScale;
#else
#if PS_ADJS
uv.x = (uv.x - STRange.x) * STRange.z;
#else
uv.x = uv.x * STScale.x;
#endif
#if PS_ADJT
uv.y = (uv.y - STRange.y) * STRange.w;
#else
uv.y = uv.y * STScale.y;
#endif
#endif
#if PS_AUTOMATIC_LOD == 1
return Texture.Sample(TextureSampler, uv);
@@ -218,12 +233,7 @@ float4 sample_p_norm(float u)
float4 clamp_wrap_uv(float4 uv)
{
float4 tex_size;
if (PS_INVALID_TEX0 == 1)
tex_size = WH.zwzw;
else
tex_size = WH.xyxy;
float4 tex_size = WH.xyxy;
if(PS_WMS == PS_WMT)
{
@@ -238,7 +248,7 @@ float4 clamp_wrap_uv(float4 uv)
// textures. Fixes Xenosaga's hair issue.
uv = frac(uv);
#endif
uv = (float4)(((uint4)(uv * tex_size) & MskFix.xyxy) | MskFix.zwzw) / tex_size;
uv = (float4)(((uint4)(uv * tex_size) & asuint(MinMax.xyxy)) | asuint(MinMax.zwzw)) / tex_size;
}
}
else
@@ -252,7 +262,7 @@ float4 clamp_wrap_uv(float4 uv)
#if PS_FST == 0
uv.xz = frac(uv.xz);
#endif
uv.xz = (float2)(((uint2)(uv.xz * tex_size.xx) & MskFix.xx) | MskFix.zz) / tex_size.xx;
uv.xz = (float2)(((uint2)(uv.xz * tex_size.xx) & asuint(MinMax.xx)) | asuint(MinMax.zz)) / tex_size.xx;
}
if(PS_WMT == 2)
{
@@ -263,7 +273,7 @@ float4 clamp_wrap_uv(float4 uv)
#if PS_FST == 0
uv.yw = frac(uv.yw);
#endif
uv.yw = (float2)(((uint2)(uv.yw * tex_size.yy) & MskFix.yy) | MskFix.ww) / tex_size.yy;
uv.yw = (float2)(((uint2)(uv.yw * tex_size.yy) & asuint(MinMax.yy)) | asuint(MinMax.ww)) / tex_size.yy;
}
}
@@ -353,7 +363,7 @@ float4 fetch_c(int2 uv)
int2 clamp_wrap_uv_depth(int2 uv)
{
int4 mask = (int4)MskFix << 4;
int4 mask = asint(MinMax) << 4;
if (PS_WMS == PS_WMT)
{
if (PS_WMS == 2)
@@ -676,11 +686,7 @@ float4 fog(float4 c, float f)
float4 ps_color(PS_INPUT input)
{
#if PS_FST == 0 && PS_INVALID_TEX0 == 1
// Re-normalize coordinate from invalid GS to corrected texture size
float2 st = (input.t.xy * WH.xy) / (input.t.w * WH.zw);
float2 st_int = (input.ti.zw * WH.xy) / (input.t.w * WH.zw);
#elif PS_FST == 0
#if PS_FST == 0
float2 st = input.t.xy / input.t.w;
float2 st_int = input.ti.zw / input.t.w;
#else
@@ -870,28 +876,37 @@ PS_OUTPUT ps_main(PS_INPUT input)
if (PS_SHUFFLE)
{
uint4 denorm_c = uint4(C);
uint2 denorm_TA = uint2(float2(TA.xy) * 255.0f + 0.5f);
// Mask will take care of the correct destination
if (PS_READ_BA)
C.rb = C.bb;
else
C.rb = C.rr;
if (PS_READ_BA)
if(PS_SWAP_GA)
{
if (denorm_c.a & 0x80u)
C.ga = (float2)(float((denorm_c.a & 0x7Fu) | (denorm_TA.y & 0x80u)));
else
C.ga = (float2)(float((denorm_c.a & 0x7Fu) | (denorm_TA.x & 0x80u)));
float4 RT = trunc(RtTexture.Load(int3(input.p.xy, 0)) * 255.0f + 0.1f);
C.a = C.g;
C.g = C.a;
}
else
{
if (denorm_c.g & 0x80u)
C.ga = (float2)(float((denorm_c.g & 0x7Fu) | (denorm_TA.y & 0x80u)));
uint4 denorm_c = uint4(C);
uint2 denorm_TA = uint2(float2(TA.xy) * 255.0f + 0.5f);
// Mask will take care of the correct destination
if (PS_READ_BA)
C.rb = C.bb;
else
C.ga = (float2)(float((denorm_c.g & 0x7Fu) | (denorm_TA.x & 0x80u)));
C.rb = C.rr;
if (PS_READ_BA)
{
if (denorm_c.a & 0x80u)
C.ga = (float2)(float((denorm_c.a & 0x7Fu) | (denorm_TA.y & 0x80u)));
else
C.ga = (float2)(float((denorm_c.a & 0x7Fu) | (denorm_TA.x & 0x80u)));
}
else
{
if (denorm_c.g & 0x80u)
C.ga = (float2)(float((denorm_c.g & 0x7Fu) | (denorm_TA.y & 0x80u)));
else
C.ga = (float2)(float((denorm_c.g & 0x7Fu) | (denorm_TA.x & 0x80u)));
}
}
}

View File

@@ -75,13 +75,12 @@ layout(std140, binding = 0) uniform cb21
float MaxDepthPS;
float Af;
uvec4 MskFix;
uvec4 FbMask;
vec4 HalfTexel;
vec4 MinMax;
vec4 STRange;
ivec4 ChannelShuffle;
@@ -92,11 +91,6 @@ layout(std140, binding = 0) uniform cb21
};
#endif
//layout(std140, binding = 22) uniform cb22
//{
// vec4 rt_size;
//};
//////////////////////////////////////////////////////////////////////
// Default Sampler
//////////////////////////////////////////////////////////////////////

View File

@@ -109,7 +109,20 @@ vec4 sample_c(vec2 uv)
// As of 2018 this issue is still present.
uv = (trunc(uv * WH.zw) + vec2(0.5, 0.5)) / WH.zw;
#endif
uv *= STScale;
#if !PS_ADJS && !PS_ADJT
uv *= STScale;
#else
#if PS_ADJS
uv.x = (uv.x - STRange.x) * STRange.z;
#else
uv.x = uv.x * STScale.x;
#endif
#if PS_ADJT
uv.y = (uv.y - STRange.y) * STRange.w;
#else
uv.y = uv.y * STScale.y;
#endif
#endif
#if PS_AUTOMATIC_LOD == 1
return texture(TextureSampler, uv);
@@ -146,11 +159,7 @@ vec4 sample_p_norm(float u)
vec4 clamp_wrap_uv(vec4 uv)
{
vec4 uv_out = uv;
#if PS_INVALID_TEX0 == 1
vec4 tex_size = WH.zwzw;
#else
vec4 tex_size = WH.xyxy;
#endif
#if PS_WMS == PS_WMT
@@ -162,7 +171,7 @@ vec4 clamp_wrap_uv(vec4 uv)
// textures. Fixes Xenosaga's hair issue.
uv = fract(uv);
#endif
uv_out = vec4((uvec4(uv * tex_size) & MskFix.xyxy) | MskFix.zwzw) / tex_size;
uv_out = vec4((uvec4(uv * tex_size) & floatBitsToUint(MinMax.xyxy)) | floatBitsToUint(MinMax.zwzw)) / tex_size;
#endif
#else // PS_WMS != PS_WMT
@@ -174,7 +183,7 @@ vec4 clamp_wrap_uv(vec4 uv)
#if PS_FST == 0
uv.xz = fract(uv.xz);
#endif
uv_out.xz = vec2((uvec2(uv.xz * tex_size.xx) & MskFix.xx) | MskFix.zz) / tex_size.xx;
uv_out.xz = vec2((uvec2(uv.xz * tex_size.xx) & floatBitsToUint(MinMax.xx)) | floatBitsToUint(MinMax.zz)) / tex_size.xx;
#endif
@@ -185,7 +194,7 @@ vec4 clamp_wrap_uv(vec4 uv)
#if PS_FST == 0
uv.yw = fract(uv.yw);
#endif
uv_out.yw = vec2((uvec2(uv.yw * tex_size.yy) & MskFix.yy) | MskFix.ww) / tex_size.yy;
uv_out.yw = vec2((uvec2(uv.yw * tex_size.yy) & floatBitsToUint(MinMax.yy)) | floatBitsToUint(MinMax.ww)) / tex_size.yy;
#endif
#endif
@@ -227,7 +236,7 @@ uvec4 sample_4_index(vec4 uv)
#if PS_PAL_FMT == 1
// 4HL
return i & 0xFu
return i & 0xFu;
#elif PS_PAL_FMT == 2
// 4HH
return i >> 4u;
@@ -288,7 +297,7 @@ ivec2 clamp_wrap_uv_depth(ivec2 uv)
// Keep the full precision
// It allow to multiply the ScalingFactor before the 1/16 coeff
ivec4 mask = ivec4(MskFix) << 4;
ivec4 mask = floatBitsToInt(MinMax) << 4;
#if PS_WMS == PS_WMT
@@ -591,11 +600,7 @@ void fog(inout vec4 C, float f)
vec4 ps_color()
{
//FIXME: maybe we can set gl_Position.w = q in VS
#if (PS_FST == 0) && (PS_INVALID_TEX0 == 1)
// Re-normalize coordinate from invalid GS to corrected texture size
vec2 st = (PSin.t_float.xy * WH.xy) / (vec2(PSin.t_float.w) * WH.zw);
vec2 st_int = (PSin.t_int.zw * WH.xy) / (vec2(PSin.t_float.w) * WH.zw);
#elif (PS_FST == 0)
#if (PS_FST == 0)
vec2 st = PSin.t_float.xy / vec2(PSin.t_float.w);
vec2 st_int = PSin.t_int.zw / vec2(PSin.t_float.w);
#else

View File

@@ -312,6 +312,8 @@ void main()
#define PS_FST 0
#define PS_WMS 0
#define PS_WMT 0
#define PS_ADJS 0
#define PS_ADJT 0
#define PS_FMT FMT_32
#define PS_AEM 0
#define PS_TFX 0
@@ -332,7 +334,6 @@ void main()
#define PS_CHANNEL_FETCH 0
#define PS_TALES_OF_ABYSS_HLE 0
#define PS_URBAN_CHAOS_HLE 0
#define PS_INVALID_TEX0 0
#define PS_SCALE_FACTOR 1.0
#define PS_HDR 0
#define PS_COLCLIP 0
@@ -346,6 +347,7 @@ void main()
#define PS_ZCLAMP 0
#define PS_FEEDBACK_LOOP 0
#define PS_TEX_IS_FB 0
#define PS_SWAP_GA 0
#endif
#define SW_BLEND (PS_BLEND_A || PS_BLEND_B || PS_BLEND_D)
@@ -361,10 +363,10 @@ layout(std140, set = 0, binding = 1) uniform cb1
vec2 TA;
float MaxDepthPS;
float Af;
uvec4 MskFix;
uvec4 FbMask;
vec4 HalfTexel;
vec4 MinMax;
vec4 STRange;
ivec4 ChannelShuffle;
vec2 TC_OffsetHack;
vec2 STScale;
@@ -420,7 +422,20 @@ vec4 sample_c(vec2 uv)
// As of 2018 this issue is still present.
uv = (trunc(uv * WH.zw) + vec2(0.5, 0.5)) / WH.zw;
#endif
#if !PS_ADJS && !PS_ADJT
uv *= STScale;
#else
#if PS_ADJS
uv.x = (uv.x - STRange.x) * STRange.z;
#else
uv.x = uv.x * STScale.x;
#endif
#if PS_ADJT
uv.y = (uv.y - STRange.y) * STRange.w;
#else
uv.y = uv.y * STScale.y;
#endif
#endif
#if PS_AUTOMATIC_LOD == 1
return texture(Texture, uv);
@@ -455,13 +470,7 @@ vec4 sample_p_norm(float u)
vec4 clamp_wrap_uv(vec4 uv)
{
vec4 tex_size;
#if PS_INVALID_TEX0
tex_size = WH.zwzw;
#else
tex_size = WH.xyxy;
#endif
vec4 tex_size = WH.xyxy;
#if PS_WMS == PS_WMT
{
@@ -476,7 +485,7 @@ vec4 clamp_wrap_uv(vec4 uv)
// textures. Fixes Xenosaga's hair issue.
uv = fract(uv);
#endif
uv = vec4((uvec4(uv * tex_size) & MskFix.xyxy) | MskFix.zwzw) / tex_size;
uv = vec4((uvec4(uv * tex_size) & floatBitsToUint(MinMax.xyxy)) | floatBitsToUint(MinMax.zwzw)) / tex_size;
}
#endif
}
@@ -491,7 +500,7 @@ vec4 clamp_wrap_uv(vec4 uv)
#if PS_FST == 0
uv.xz = fract(uv.xz);
#endif
uv.xz = vec2((uvec2(uv.xz * tex_size.xx) & MskFix.xx) | MskFix.zz) / tex_size.xx;
uv.xz = vec2((uvec2(uv.xz * tex_size.xx) & floatBitsToUint(MinMax.xx)) | floatBitsToUint(MinMax.zz)) / tex_size.xx;
}
#endif
#if PS_WMT == 2
@@ -503,7 +512,7 @@ vec4 clamp_wrap_uv(vec4 uv)
#if PS_FST == 0
uv.yw = fract(uv.yw);
#endif
uv.yw = vec2((uvec2(uv.yw * tex_size.yy) & MskFix.yy) | MskFix.ww) / tex_size.yy;
uv.yw = vec2((uvec2(uv.yw * tex_size.yy) & floatBitsToUint(MinMax.yy)) | floatBitsToUint(MinMax.ww)) / tex_size.yy;
}
#endif
}
@@ -590,7 +599,7 @@ vec4 fetch_c(ivec2 uv)
ivec2 clamp_wrap_uv_depth(ivec2 uv)
{
ivec4 mask = ivec4(MskFix << 4);
ivec4 mask = floatBitsToInt(MinMax) << 4;
#if (PS_WMS == PS_WMT)
{
#if (PS_WMS == 2)
@@ -907,11 +916,7 @@ vec4 fog(vec4 c, float f)
vec4 ps_color()
{
#if PS_FST == 0 && PS_INVALID_TEX0 == 1
// Re-normalize coordinate from invalid GS to corrected texture size
vec2 st = (vsIn.t.xy * WH.xy) / (vsIn.t.w * WH.zw);
vec2 st_int = (vsIn.ti.zw * WH.xy) / (vsIn.t.w * WH.zw);
#elif PS_FST == 0
#if PS_FST == 0
vec2 st = vsIn.t.xy / vsIn.t.w;
vec2 st_int = vsIn.ti.zw / vsIn.t.w;
#else
@@ -1169,26 +1174,32 @@ void main()
vec4 C = ps_color();
#if PS_SHUFFLE
uvec4 denorm_c = uvec4(C);
uvec2 denorm_TA = uvec2(vec2(TA.xy) * 255.0f + 0.5f);
// Mask will take care of the correct destination
#if PS_READ_BA
C.rb = C.bb;
#if PS_SWAP_GA
vec4 RT = trunc(subpassLoad(RtSampler) * 255.0f + 0.1f);
C.a = RT.g;
C.g = RT.a;
#else
C.rb = C.rr;
#endif
uvec4 denorm_c = uvec4(C);
uvec2 denorm_TA = uvec2(vec2(TA.xy) * 255.0f + 0.5f);
#if PS_READ_BA
if ((denorm_c.a & 0x80u) != 0u)
C.ga = vec2(float((denorm_c.a & 0x7Fu) | (denorm_TA.y & 0x80u)));
else
C.ga = vec2(float((denorm_c.a & 0x7Fu) | (denorm_TA.x & 0x80u)));
#else
if ((denorm_c.g & 0x80u) != 0u)
C.ga = vec2(float((denorm_c.g & 0x7Fu) | (denorm_TA.y & 0x80u)));
else
C.ga = vec2(float((denorm_c.g & 0x7Fu) | (denorm_TA.x & 0x80u)));
// Mask will take care of the correct destination
#if PS_READ_BA
C.rb = C.bb;
#else
C.rb = C.rr;
#endif
#if PS_READ_BA
if ((denorm_c.a & 0x80u) != 0u)
C.ga = vec2(float((denorm_c.a & 0x7Fu) | (denorm_TA.y & 0x80u)));
else
C.ga = vec2(float((denorm_c.a & 0x7Fu) | (denorm_TA.x & 0x80u)));
#else
if ((denorm_c.g & 0x80u) != 0u)
C.ga = vec2(float((denorm_c.g & 0x7Fu) | (denorm_TA.y & 0x80u)));
else
C.ga = vec2(float((denorm_c.g & 0x7Fu) | (denorm_TA.x & 0x80u)));
#endif
#endif
#endif

View File

@@ -153,6 +153,9 @@ namespace Vulkan
m_provoking_vertex = {};
m_provoking_vertex.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_PROVOKING_VERTEX_STATE_CREATE_INFO_EXT;
m_line_rasterization_state = {};
m_line_rasterization_state.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_LINE_STATE_CREATE_INFO_EXT;
// set defaults
SetNoCullRasterizationState();
SetNoDepthTestState();
@@ -255,7 +258,17 @@ namespace Vulkan
m_ci.pRasterizationState = &m_rasterization_state;
}
void GraphicsPipelineBuilder::SetLineWidth(float width) { m_rasterization_state.lineWidth = width; }
void GraphicsPipelineBuilder::SetLineWidth(float width)
{
m_rasterization_state.lineWidth = width;
}
void GraphicsPipelineBuilder::SetLineRasterizationMode(VkLineRasterizationModeEXT mode)
{
Util::AddPointerToChain(&m_rasterization_state, &m_line_rasterization_state);
m_line_rasterization_state.lineRasterizationMode = mode;
}
void GraphicsPipelineBuilder::SetMultisamples(u32 multisamples, bool per_sample_shading)
{

View File

@@ -96,6 +96,7 @@ namespace Vulkan
void SetRasterizationState(VkPolygonMode polygon_mode, VkCullModeFlags cull_mode, VkFrontFace front_face);
void SetLineWidth(float width);
void SetLineRasterizationMode(VkLineRasterizationModeEXT mode);
void SetMultisamples(u32 multisamples, bool per_sample_shading);
void SetNoCullRasterizationState();
@@ -157,6 +158,7 @@ namespace Vulkan
VkPipelineMultisampleStateCreateInfo m_multisample_state;
VkPipelineRasterizationProvokingVertexStateCreateInfoEXT m_provoking_vertex;
VkPipelineRasterizationLineStateCreateInfoEXT m_line_rasterization_state;
};
class ComputePipelineBuilder

View File

@@ -458,6 +458,8 @@ namespace Vulkan
SupportsExtension(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME, false);
m_optional_extensions.vk_ext_calibrated_timestamps =
SupportsExtension(VK_EXT_CALIBRATED_TIMESTAMPS_EXTENSION_NAME, false);
m_optional_extensions.vk_ext_line_rasterization =
SupportsExtension(VK_EXT_LINE_RASTERIZATION_EXTENSION_NAME, false);
m_optional_extensions.vk_khr_driver_properties =
SupportsExtension(VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME, false);
m_optional_extensions.vk_arm_rasterization_order_attachment_access =
@@ -654,12 +656,19 @@ namespace Vulkan
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_FEATURES_EXT};
VkPhysicalDeviceRasterizationOrderAttachmentAccessFeaturesARM rasterization_order_access_feature = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_FEATURES_ARM};
VkPhysicalDeviceLineRasterizationFeaturesEXT line_rasterization_feature = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LINE_RASTERIZATION_FEATURES_EXT};
if (m_optional_extensions.vk_ext_provoking_vertex)
{
provoking_vertex_feature.provokingVertexLast = VK_TRUE;
Util::AddPointerToChain(&device_info, &provoking_vertex_feature);
}
if (m_optional_extensions.vk_ext_line_rasterization)
{
line_rasterization_feature.bresenhamLines = VK_TRUE;
Util::AddPointerToChain(&device_info, &line_rasterization_feature);
}
if (m_optional_extensions.vk_arm_rasterization_order_attachment_access)
{
rasterization_order_access_feature.rasterizationOrderColorAttachmentAccess = VK_TRUE;
@@ -724,12 +733,16 @@ namespace Vulkan
VkPhysicalDeviceFeatures2 features2 = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2};
VkPhysicalDeviceProvokingVertexFeaturesEXT provoking_vertex_features = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_FEATURES_EXT};
VkPhysicalDeviceLineRasterizationFeaturesEXT line_rasterization_feature = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LINE_RASTERIZATION_FEATURES_EXT};
VkPhysicalDeviceRasterizationOrderAttachmentAccessFeaturesARM rasterization_order_access_feature = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_FEATURES_ARM};
// add in optional feature structs
if (m_optional_extensions.vk_ext_provoking_vertex)
Util::AddPointerToChain(&features2, &provoking_vertex_features);
if (m_optional_extensions.vk_ext_line_rasterization)
Util::AddPointerToChain(&features2, &line_rasterization_feature);
if (m_optional_extensions.vk_arm_rasterization_order_attachment_access)
Util::AddPointerToChain(&features2, &rasterization_order_access_feature);
@@ -739,6 +752,7 @@ namespace Vulkan
// confirm we actually support it
m_optional_extensions.vk_ext_provoking_vertex &= (provoking_vertex_features.provokingVertexLast == VK_TRUE);
m_optional_extensions.vk_arm_rasterization_order_attachment_access &= (rasterization_order_access_feature.rasterizationOrderColorAttachmentAccess == VK_TRUE);
m_optional_extensions.vk_ext_line_rasterization &= (line_rasterization_feature.bresenhamLines == VK_TRUE);
VkPhysicalDeviceProperties2 properties2 = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2};
void** pNext = &properties2.pNext;
@@ -789,6 +803,8 @@ namespace Vulkan
Console.WriteLn("VK_EXT_provoking_vertex is %s",
m_optional_extensions.vk_ext_provoking_vertex ? "supported" : "NOT supported");
Console.WriteLn("VK_EXT_line_rasterization is %s",
m_optional_extensions.vk_ext_line_rasterization ? "supported" : "NOT supported");
Console.WriteLn("VK_EXT_calibrated_timestamps is %s",
m_optional_extensions.vk_ext_calibrated_timestamps ? "supported" : "NOT supported");
Console.WriteLn("VK_ARM_rasterization_order_attachment_access is %s",

View File

@@ -52,6 +52,7 @@ namespace Vulkan
bool vk_ext_provoking_vertex : 1;
bool vk_ext_memory_budget : 1;
bool vk_ext_calibrated_timestamps : 1;
bool vk_ext_line_rasterization : 1;
bool vk_khr_driver_properties : 1;
bool vk_arm_rasterization_order_attachment_access : 1;
bool vk_khr_fragment_shader_barycentric : 1;

View File

@@ -16,6 +16,12 @@
#include "common/emitter/internal.h"
#include "common/emitter/tools.h"
// warning: suggest braces around initialization of subobject [-Wmissing-braces]
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wmissing-braces"
#endif
namespace x86Emitter
{
const xImplAVX_Move xVMOVAPS = {0x00, 0x28, 0x29};

View File

@@ -48,6 +48,7 @@
#include "pcsx2/HostDisplay.h"
#include "pcsx2/HostSettings.h"
#include "pcsx2/INISettingsInterface.h"
#include "pcsx2/PAD/Host/PAD.h"
#include "pcsx2/PerformanceMetrics.h"
#include "pcsx2/VMManager.h"
@@ -79,6 +80,7 @@ static std::optional<bool> s_use_window;
// Owned by the GS thread.
static u32 s_dump_frame_number = 0;
static u32 s_loop_number = s_loop_count;
bool GSRunner::InitializeConfig()
{
@@ -102,6 +104,13 @@ bool GSRunner::InitializeConfig()
// we don't need any sound output
si.SetStringValue("SPU2/Output", "OutputModule", "nullout");
// none of the bindings are going to resolve to anything
PAD::ClearPortBindings(si, 0);
si.ClearSection("Hotkeys");
// make sure any gamesettings inis in your tree don't get loaded
si.SetBoolValue("EmuCore", "EnablePerGameSettings", false);
// force logging
si.SetBoolValue("Logging", "EnableSystemConsole", true);
si.SetBoolValue("Logging", "EnableTimestamps", true);
@@ -273,13 +282,15 @@ void Host::ReleaseHostDisplay(bool clear_state)
bool Host::BeginPresentFrame(bool frame_skip)
{
// when we wrap around, don't race other files
GSJoinSnapshotThreads();
// queue dumping of this frame
std::string dump_path(fmt::format("{}_frame{}.png", s_output_prefix, s_dump_frame_number));
GSQueueSnapshot(dump_path);
if (s_loop_number == 0)
{
// when we wrap around, don't race other files
GSJoinSnapshotThreads();
// queue dumping of this frame
std::string dump_path(fmt::format("{}_frame{}.png", s_output_prefix, s_dump_frame_number));
GSQueueSnapshot(dump_path);
}
if (g_host_display->BeginPresent(frame_skip))
return true;
@@ -519,6 +530,29 @@ static bool ParseCommandLineArgs(int argc, char* argv[], VMBootParameters& param
s_settings_interface.SetIntValue("EmuCore/GS", "Renderer", static_cast<int>(type));
continue;
}
else if (CHECK_ARG_PARAM("-renderhacks"))
{
std::string str(argv[++i]);
s_settings_interface.SetBoolValue("EmuCore/GS", "UserHacks", true);
if(str.find("af") != std::string::npos)
s_settings_interface.SetBoolValue("EmuCore/GS", "UserHacks_AutoFlush", true);
if (str.find("cpufb") != std::string::npos)
s_settings_interface.SetBoolValue("EmuCore/GS", "UserHacks_CPU_FB_Conversion", true);
if (str.find("dds") != std::string::npos)
s_settings_interface.SetBoolValue("EmuCore/GS", "UserHacks_DisableDepthSupport", true);
if (str.find("dpi") != std::string::npos)
s_settings_interface.SetBoolValue("EmuCore/GS", "UserHacks_DisablePartialInvalidation", true);
if (str.find("dsf") != std::string::npos)
s_settings_interface.SetBoolValue("EmuCore/GS", "UserHacks_DisableSafeFeatures", true);
if (str.find("tinrt") != std::string::npos)
s_settings_interface.SetBoolValue("EmuCore/GS", "UserHacks_TextureInsideRt", true);
if (str.find("plf") != std::string::npos)
s_settings_interface.SetBoolValue("EmuCore/GS", "preload_frame_with_gs_data", true);
continue;
}
else if (CHECK_ARG_PARAM("-logfile"))
{
const char* logfile = argv[++i];
@@ -627,6 +661,7 @@ int main(int argc, char* argv[])
// apply new settings (e.g. pick up renderer change)
VMManager::ApplySettings();
GSDumpReplayer::SetIsDumpRunner(true);
if (VMManager::Initialize(params))
{
@@ -651,6 +686,7 @@ void Host::CPUThreadVSync()
{
// update GS thread copy of frame number
GetMTGS().RunOnGSThread([frame_number = GSDumpReplayer::GetFrameNumber()]() { s_dump_frame_number = frame_number; });
GetMTGS().RunOnGSThread([loop_number = GSDumpReplayer::GetLoopCount()]() { s_loop_number = loop_number; });
// process any window messages (but we shouldn't really have any)
GSRunner::PumpPlatformMessages();

View File

@@ -16,7 +16,7 @@ def is_gs_path(path):
return False
def run_regression_test(runner, dumpdir, renderer, parallel, gspath):
def run_regression_test(runner, dumpdir, renderer, parallel, renderhacks, gspath):
args = [runner]
gsname = Path(gspath).name
while gsname.rfind('.') >= 0:
@@ -28,6 +28,10 @@ def run_regression_test(runner, dumpdir, renderer, parallel, gspath):
if renderer is not None:
args.extend(["-renderer", renderer])
if renderhacks is not None:
args.extend(["-renderhacks", renderhacks])
args.extend(["-dumpdir", real_dumpdir])
args.extend(["-logfile", os.path.join(real_dumpdir, "emulog.txt")])
@@ -46,7 +50,7 @@ def run_regression_test(runner, dumpdir, renderer, parallel, gspath):
subprocess.run(args)
def run_regression_tests(runner, gsdir, dumpdir, renderer, parallel=1):
def run_regression_tests(runner, gsdir, dumpdir, renderer, renderhacks, parallel=1):
paths = glob.glob(gsdir + "/*.*", recursive=True)
gamepaths = list(filter(is_gs_path, paths))
@@ -57,10 +61,10 @@ def run_regression_tests(runner, gsdir, dumpdir, renderer, parallel=1):
if parallel <= 1:
for game in gamepaths:
run_regression_test(runner, dumpdir, renderer, parallel, game)
run_regression_test(runner, dumpdir, renderer, parallel, renderhacks, game)
else:
print("Processing %u games on %u processors" % (len(gamepaths), parallel))
func = partial(run_regression_test, runner, dumpdir, renderer, parallel)
func = partial(run_regression_test, runner, dumpdir, renderer, parallel, renderhacks)
pool = multiprocessing.Pool(parallel)
pool.map(func, gamepaths)
pool.close()
@@ -76,10 +80,11 @@ if __name__ == "__main__":
parser.add_argument("-dumpdir", action="store", required=True, help="Base directory to dump frames to")
parser.add_argument("-renderer", action="store", required=False, help="Renderer to use")
parser.add_argument("-parallel", action="store", type=int, default=1, help="Number of proceeses to run")
parser.add_argument("-renderhacks", action="store", required=False, help="Enable HW Renering hacks")
args = parser.parse_args()
if not run_regression_tests(args.runner, os.path.realpath(args.gsdir), os.path.realpath(args.dumpdir), args.renderer, args.parallel):
if not run_regression_tests(args.runner, os.path.realpath(args.gsdir), os.path.realpath(args.dumpdir), args.renderer, args.renderhacks, args.parallel):
sys.exit(1)
else:
sys.exit(0)

View File

@@ -107,18 +107,15 @@ QVariant StackModel::headerData(int section, Qt::Orientation orientation, int ro
void StackModel::refreshData()
{
if (m_cpu.getCpuType() == BREAKPOINT_IOP)
return;
// Hopefully in the near future we can get a stack frame for
// each thread
beginResetModel();
for (const auto& thread : getEEThreads())
for (const auto& thread : m_cpu.GetThreadList())
{
if (thread.data.status == THS_RUN)
if (thread->Status() == ThreadStatus::THS_RUN)
{
m_stackFrames = MipsStackWalk::Walk(&m_cpu, m_cpu.getPC(), m_cpu.getRegister(0, 31), m_cpu.getRegister(0, 29),
thread.data.entry_init, thread.data.stack);
thread->EntryPoint(), thread->StackTop());
break;
}
}

View File

@@ -27,10 +27,7 @@ ThreadModel::ThreadModel(DebugInterface& cpu, QObject* parent)
int ThreadModel::rowCount(const QModelIndex&) const
{
if (m_cpu.getCpuType() == BREAKPOINT_EE)
return getEEThreads().size();
else
return 0;
return m_cpu.GetThreadList().size();
}
int ThreadModel::columnCount(const QModelIndex&) const
@@ -40,66 +37,65 @@ int ThreadModel::columnCount(const QModelIndex&) const
QVariant ThreadModel::data(const QModelIndex& index, int role) const
{
const auto threads = m_cpu.GetThreadList();
auto* const thread = threads.at(index.row()).get();
if (role == Qt::DisplayRole)
{
const auto thread = getEEThreads().at(index.row());
switch (index.column())
{
case ThreadModel::ID:
return thread.tid;
return thread->TID();
case ThreadModel::PC:
{
if (thread.data.status == THS_RUN)
if (thread->Status() == ThreadStatus::THS_RUN)
return QtUtils::FilledQStringFromValue(m_cpu.getPC(), 16);
else
return QtUtils::FilledQStringFromValue(thread.data.entry, 16);
return QtUtils::FilledQStringFromValue(thread->PC(), 16);
}
case ThreadModel::ENTRY:
return QtUtils::FilledQStringFromValue(thread.data.entry_init, 16);
return QtUtils::FilledQStringFromValue(thread->EntryPoint(), 16);
case ThreadModel::PRIORITY:
return QString::number(thread.data.currentPriority);
return QString::number(thread->Priority());
case ThreadModel::STATE:
{
const auto& state = ThreadStateStrings.find(thread.data.status);
const auto& state = ThreadStateStrings.find(thread->Status());
if (state != ThreadStateStrings.end())
return state->second;
else
return tr("INVALID");
return tr("INVALID");
}
case ThreadModel::WAIT_TYPE:
{
const auto& waitType = ThreadWaitStrings.find(thread.data.waitType);
const auto& waitType = ThreadWaitStrings.find(thread->Wait());
if (waitType != ThreadWaitStrings.end())
return waitType->second;
else
return tr("INVALID");
return tr("INVALID");
}
}
}
else if (role == Qt::UserRole)
{
const auto thread = getEEThreads().at(index.row());
switch (index.column())
{
case ThreadModel::ID:
return thread.tid;
return thread->TID();
case ThreadModel::PC:
{
if (thread.data.status == THS_RUN)
if (thread->Status() == ThreadStatus::THS_RUN)
return m_cpu.getPC();
else
return thread.data.entry;
return thread->PC();
}
case ThreadModel::ENTRY:
return thread.data.entry_init;
return thread->EntryPoint();
case ThreadModel::PRIORITY:
return thread.data.currentPriority;
return thread->Priority();
case ThreadModel::STATE:
return thread.data.status;
return static_cast<u32>(thread->Status());
case ThreadModel::WAIT_TYPE:
return thread.data.waitType;
return static_cast<u32>(thread->Wait());
default:
return QVariant();
}

View File

@@ -48,19 +48,27 @@ public:
void refreshData();
private:
const std::map<int, QString> ThreadStateStrings{
{THS_BAD, tr("BAD")},
{THS_RUN, tr("RUN")},
{THS_READY, tr("READY")},
{THS_WAIT, tr("WAIT")},
{THS_SUSPEND, tr("SUSPEND")},
{THS_WAIT_SUSPEND, tr("WAIT SUSPEND")},
{THS_DORMANT, tr("DORMANT")}};
const std::map<ThreadStatus, QString> ThreadStateStrings{
{ThreadStatus::THS_BAD, tr("BAD")},
{ThreadStatus::THS_RUN, tr("RUN")},
{ThreadStatus::THS_READY, tr("READY")},
{ThreadStatus::THS_WAIT, tr("WAIT")},
{ThreadStatus::THS_SUSPEND, tr("SUSPEND")},
{ThreadStatus::THS_WAIT_SUSPEND, tr("WAIT SUSPEND")},
{ThreadStatus::THS_DORMANT, tr("DORMANT")},
};
const std::map<int, QString> ThreadWaitStrings{
{WAIT_NONE, tr("NONE")},
{WAIT_WAKEUP_REQ, tr("WAKEUP REQUEST")},
{WAIT_SEMA, tr("SEMAPHORE")}};
const std::map<WaitState, QString> ThreadWaitStrings{
{WaitState::NONE, tr("NONE")},
{WaitState::WAKEUP_REQ, tr("WAKEUP REQUEST")},
{WaitState::SEMA, tr("SEMAPHORE")},
{WaitState::SLEEP, tr("SLEEP")},
{WaitState::DELAY, tr("DELAY")},
{WaitState::EVENTFLAG, tr("EVENTFLAG")},
{WaitState::MBOX, tr("MBOX")},
{WaitState::VPOOL, tr("VPOOL")},
{WaitState::FIXPOOL, tr("FIXPOOL")},
};
DebugInterface& m_cpu;
};

View File

@@ -488,7 +488,7 @@ void GameListWidget::resizeTableViewColumnsToFit()
80, // last played
80, // size
60, // region
100 // compatibility
120 // compatibility
});
}

View File

@@ -1363,6 +1363,7 @@ void MainWindow::onGameListRefreshProgress(const QString& status, int current, i
void MainWindow::onGameListRefreshComplete()
{
m_ui.statusBar->clearMessage();
clearProgressBar();
}
@@ -1463,7 +1464,6 @@ void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point)
if (m_ui.menuDebug->menuAction()->isVisible())
{
// TODO: Hook this up once it's implemented.
action = menu.addAction(tr("Boot and Debug"));
connect(action, &QAction::triggered, [this, entry]() { DebugInterface::setPauseOnEntry(true); startGameListEntry(entry); getDebuggerWindow()->show(); });
}
@@ -1932,6 +1932,7 @@ void MainWindow::onVMStopped()
// If we're closing or in batch mode, quit the whole application now.
if (m_is_closing || QtHost::InBatchMode())
{
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 1);
QCoreApplication::quit();
return;
}
@@ -1975,16 +1976,24 @@ void MainWindow::showEvent(QShowEvent* event)
void MainWindow::closeEvent(QCloseEvent* event)
{
if (!requestShutdown(true, true, EmuConfig.SaveStateOnShutdown))
// If there's no VM, we can just exit as normal.
if (!s_vm_valid)
{
event->ignore();
QMainWindow::closeEvent(event);
return;
}
// But if there is, we have to cancel the action, regardless of whether we ended exiting
// or not. The window still needs to be visible while GS is shutting down.
event->ignore();
// Exit cancelled?
if (!requestShutdown(true, true, EmuConfig.SaveStateOnShutdown))
return;
// Application will be exited in VM stopped handler.
saveStateToConfig();
m_is_closing = true;
QMainWindow::closeEvent(event);
}
static QString getFilenameFromMimeData(const QMimeData* md)
@@ -2249,7 +2258,12 @@ void MainWindow::createDisplayWidget(bool fullscreen, bool render_to_main, bool
// Don't risk doing this on Wayland, it really doesn't like window state changes,
// and positioning has no effect anyway.
if (!s_use_central_widget)
restoreDisplayWindowGeometryFromConfig();
{
if (isVisible() && g_emu_thread->shouldRenderToMain())
container->move(pos());
else
restoreDisplayWindowGeometryFromConfig();
}
if (!is_exclusive_fullscreen)
container->showFullScreen();

View File

@@ -110,6 +110,8 @@ public:
/// Rescans a single file. NOTE: Happens on UI thread.
void rescanFile(const std::string& path);
void openDebugger();
public Q_SLOTS:
void checkForUpdates(bool display_message, bool force_check);
void refreshGameList(bool invalidate_cache);
@@ -241,7 +243,6 @@ private:
void updateInputRecordingActions(bool started);
DebuggerWindow* getDebuggerWindow();
void openDebugger();
ControllerSettingsDialog* getControllerSettingsDialog();
void doControllerSettings(ControllerSettingsDialog::Category category = ControllerSettingsDialog::Category::Count);

View File

@@ -96,6 +96,7 @@ static bool s_nogui_mode = false;
static bool s_start_fullscreen_ui = false;
static bool s_start_fullscreen_ui_fullscreen = false;
static bool s_test_config_and_exit = false;
static bool s_boot_and_debug = false;
//////////////////////////////////////////////////////////////////////////
// CPU Thread
@@ -1621,6 +1622,7 @@ void QtHost::PrintCommandLineHelp(const std::string_view& progname)
std::fprintf(stderr, " -nofullscreen: Prevents fullscreen mode from triggering if enabled.\n");
std::fprintf(stderr, " -earlyconsolelog: Forces logging of early console messages to console.\n");
std::fprintf(stderr, " -testconfig: Initializes configuration and checks version, then exits.\n");
std::fprintf(stderr, " -debugger: Open debugger and break on entry point.\n");
#ifdef ENABLE_RAINTEGRATION
std::fprintf(stderr, " -raintegration: Use RAIntegration instead of built-in achievement support.\n");
#endif
@@ -1743,6 +1745,11 @@ bool QtHost::ParseCommandLineOptions(const QStringList& args, std::shared_ptr<VM
s_test_config_and_exit = true;
continue;
}
else if (CHECK_ARG(QStringLiteral("-debugger")))
{
s_boot_and_debug = true;
continue;
}
#ifdef ENABLE_RAINTEGRATION
else if (CHECK_ARG(QStringLiteral("-raintegration")))
{
@@ -1880,6 +1887,12 @@ int main(int argc, char* argv[])
if (s_start_fullscreen_ui)
g_emu_thread->startFullscreenUI(s_start_fullscreen_ui_fullscreen);
if (s_boot_and_debug)
{
DebugInterface::setPauseOnEntry(true);
main_window->openDebugger();
}
// Skip the update check if we're booting a game directly.
if (autoboot)
g_emu_thread->startVM(std::move(autoboot));

View File

@@ -595,7 +595,7 @@ namespace SettingWidgetBinder
/// Binds a widget's value to a setting, updating it when the value changes.
template <typename WidgetType>
static void BindWidgetToBoolSetting(
static inline void BindWidgetToBoolSetting(
SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, bool default_value)
{
using Accessor = SettingAccessor<WidgetType>;
@@ -636,7 +636,7 @@ namespace SettingWidgetBinder
}
template <typename WidgetType>
static void BindWidgetToIntSetting(
static inline void BindWidgetToIntSetting(
SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, int default_value, int option_offset = 0)
{
using Accessor = SettingAccessor<WidgetType>;
@@ -677,7 +677,7 @@ namespace SettingWidgetBinder
}
template <typename WidgetType>
static void BindWidgetToFloatSetting(
static inline void BindWidgetToFloatSetting(
SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, float default_value)
{
using Accessor = SettingAccessor<WidgetType>;
@@ -718,7 +718,7 @@ namespace SettingWidgetBinder
}
template <typename WidgetType>
static void BindWidgetToNormalizedSetting(
static inline void BindWidgetToNormalizedSetting(
SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, float range, float default_value)
{
using Accessor = SettingAccessor<WidgetType>;
@@ -759,7 +759,7 @@ namespace SettingWidgetBinder
}
template <typename WidgetType>
static void BindWidgetToStringSetting(
static inline void BindWidgetToStringSetting(
SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, std::string default_value = std::string())
{
using Accessor = SettingAccessor<WidgetType>;
@@ -804,7 +804,7 @@ namespace SettingWidgetBinder
}
template <typename WidgetType, typename DataType>
static void BindWidgetToEnumSetting(SettingsInterface* sif, WidgetType* widget, std::string section, std::string key,
static inline void BindWidgetToEnumSetting(SettingsInterface* sif, WidgetType* widget, std::string section, std::string key,
std::optional<DataType> (*from_string_function)(const char* str), const char* (*to_string_function)(DataType value),
DataType default_value)
{
@@ -866,7 +866,7 @@ namespace SettingWidgetBinder
}
template <typename WidgetType, typename DataType>
static void BindWidgetToEnumSetting(
static inline void BindWidgetToEnumSetting(
SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, const char** enum_names, DataType default_value)
{
using Accessor = SettingAccessor<WidgetType>;
@@ -928,7 +928,7 @@ namespace SettingWidgetBinder
}
template <typename WidgetType>
static void BindWidgetToEnumSetting(SettingsInterface* sif, WidgetType* widget, std::string section, std::string key,
static inline void BindWidgetToEnumSetting(SettingsInterface* sif, WidgetType* widget, std::string section, std::string key,
const char** enum_names, const char** enum_values, const char* default_value)
{
using Accessor = SettingAccessor<WidgetType>;
@@ -992,7 +992,7 @@ namespace SettingWidgetBinder
}
template <typename WidgetType>
static void BindWidgetToFolderSetting(SettingsInterface* sif, WidgetType* widget, QAbstractButton* browse_button,
static inline 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,
bool use_relative = true)
{
@@ -1068,7 +1068,7 @@ namespace SettingWidgetBinder
}
}
[[maybe_unused]] static void BindSliderToIntSetting(SettingsInterface* sif, QSlider* slider, QLabel* label, const QString& label_suffix,
static inline void BindSliderToIntSetting(SettingsInterface* sif, QSlider* slider, QLabel* label, const QString& label_suffix,
std::string section, std::string key, s32 default_value)
{
const s32 global_value = Host::GetBaseIntSettingValue(section.c_str(), key.c_str(), default_value);
@@ -1109,8 +1109,8 @@ namespace SettingWidgetBinder
});
slider->connect(slider, &QSlider::valueChanged, slider,
[sif, label, label_suffix, section = std::move(section), key = std::move(key),
orig_font = std::move(orig_font), bold_font = std::move(bold_font)](int value) {
[sif, label, label_suffix, section = std::move(section), key = std::move(key), orig_font = std::move(orig_font),
bold_font = std::move(bold_font)](int value) {
label->setText(QStringLiteral("%1%2").arg(value).arg(label_suffix));
if (label->font() != bold_font)

View File

@@ -42,10 +42,12 @@
ControllerBindingWidget::ControllerBindingWidget(QWidget* parent, ControllerSettingsDialog* dialog, u32 port)
: QWidget(parent)
, m_dialog(dialog)
, m_config_section(StringUtil::StdStringFromFormat("Pad%u", port + 1u))
, m_config_section(fmt::format("Pad{}", port + 1))
, m_port_number(port)
{
m_ui.setupUi(this);
m_ui.groupBox->setTitle(tr("Controller Port %1").arg(port + 1));
populateControllerTypes();
onTypeChanged();
@@ -113,10 +115,9 @@ void ControllerBindingWidget::onTypeChanged()
if (has_settings)
{
const QString settings_title(tr("%1 Settings").arg(qApp->translate("PAD", cinfo->display_name)));
const gsl::span<const SettingInfo> settings(cinfo->settings, cinfo->num_settings);
m_settings_widget = new ControllerCustomSettingsWidget(
settings, m_config_section, std::string(), settings_title, cinfo->name, getDialog(), m_ui.stackedWidget);
m_settings_widget =
new ControllerCustomSettingsWidget(settings, m_config_section, std::string(), cinfo->name, getDialog(), m_ui.stackedWidget);
m_ui.stackedWidget->addWidget(m_settings_widget);
}
@@ -458,8 +459,7 @@ void ControllerMacroEditWidget::updateBinds()
//////////////////////////////////////////////////////////////////////////
ControllerCustomSettingsWidget::ControllerCustomSettingsWidget(gsl::span<const SettingInfo> settings, std::string config_section,
std::string config_prefix, const QString& group_title, const char* translation_ctx, ControllerSettingsDialog* dialog,
QWidget* parent_widget)
std::string config_prefix, const char* translation_ctx, ControllerSettingsDialog* dialog, QWidget* parent_widget)
: QWidget(parent_widget)
, m_settings(settings)
, m_config_section(std::move(config_section))
@@ -469,22 +469,19 @@ ControllerCustomSettingsWidget::ControllerCustomSettingsWidget(gsl::span<const S
if (settings.empty())
return;
QGroupBox* gbox = new QGroupBox(group_title, this);
QGridLayout* gbox_layout = new QGridLayout(gbox);
createSettingWidgets(translation_ctx, gbox, gbox_layout);
QScrollArea* sarea = new QScrollArea(this);
QWidget* swidget = new QWidget(sarea);
sarea->setWidget(swidget);
sarea->setWidgetResizable(true);
sarea->setFrameShape(QFrame::StyledPanel);
sarea->setFrameShadow(QFrame::Sunken);
QGridLayout* swidget_layout = new QGridLayout(swidget);
createSettingWidgets(translation_ctx, swidget, swidget_layout);
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);
layout->addWidget(sarea);
}
ControllerCustomSettingsWidget::~ControllerCustomSettingsWidget() = default;
@@ -503,8 +500,6 @@ static std::tuple<QString, QString> getPrefixAndSuffixForIntFormat(const QString
return std::tie(prefix, suffix);
}
#if 0
// Unused until we handle multiplier below.
static std::tuple<QString, QString, int> getPrefixAndSuffixForFloatFormat(const QString& format)
{
QString prefix, suffix;
@@ -532,7 +527,6 @@ static std::tuple<QString, QString, int> getPrefixAndSuffixForFloatFormat(const
return std::tie(prefix, suffix, decimals);
}
#endif
void ControllerCustomSettingsWidget::createSettingWidgets(const char* translation_ctx, QWidget* widget_parent, QGridLayout* layout)
{
@@ -569,7 +563,8 @@ void ControllerCustomSettingsWidget::createSettingWidgets(const char* translatio
sb->setPrefix(prefix);
sb->setSuffix(suffix);
}
SettingWidgetBinder::BindWidgetToIntSetting(sif, sb, m_config_section, std::move(key_name), si.IntegerDefaultValue());
ControllerSettingWidgetBinder::BindWidgetToInputProfileInt(
sif, sb, m_config_section, std::move(key_name), si.IntegerDefaultValue());
layout->addWidget(new QLabel(qApp->translate(translation_ctx, si.display_name), widget_parent), current_row, 0);
layout->addWidget(sb, current_row, 1, 1, 3);
current_row++;
@@ -582,7 +577,7 @@ void ControllerCustomSettingsWidget::createSettingWidgets(const char* translatio
cb->setObjectName(QString::fromUtf8(si.name));
for (u32 i = 0; si.options[i] != nullptr; i++)
cb->addItem(qApp->translate(translation_ctx, si.options[i]));
SettingWidgetBinder::BindWidgetToIntSetting(
ControllerSettingWidgetBinder::BindWidgetToInputProfileInt(
sif, cb, m_config_section, std::move(key_name), si.IntegerDefaultValue(), si.IntegerMinValue());
layout->addWidget(new QLabel(qApp->translate(translation_ctx, si.display_name), widget_parent), current_row, 0);
layout->addWidget(cb, current_row, 1, 1, 3);
@@ -594,11 +589,10 @@ void ControllerCustomSettingsWidget::createSettingWidgets(const char* translatio
{
QDoubleSpinBox* sb = new QDoubleSpinBox(widget_parent);
sb->setObjectName(QString::fromUtf8(si.name));
sb->setMinimum(si.FloatMinValue());
sb->setMaximum(si.FloatMaxValue());
sb->setSingleStep(si.FloatStepValue());
#if 0
// We can't use this until we handle multiplier.
sb->setMinimum(si.FloatMinValue() * si.multiplier);
sb->setMaximum(si.FloatMaxValue() * si.multiplier);
sb->setSingleStep(si.FloatStepValue() * si.multiplier);
if (si.format)
{
const auto [prefix, suffix, decimals] = getPrefixAndSuffixForFloatFormat(QString::fromUtf8(si.format));
@@ -607,8 +601,9 @@ void ControllerCustomSettingsWidget::createSettingWidgets(const char* translatio
sb->setDecimals(decimals);
sb->setSuffix(suffix);
}
#endif
SettingWidgetBinder::BindWidgetToFloatSetting(sif, sb, m_config_section, std::move(key_name), si.FloatDefaultValue());
ControllerSettingWidgetBinder::BindWidgetToInputProfileFloat(
sif, sb, m_config_section, std::move(key_name), si.FloatDefaultValue(), si.multiplier);
layout->addWidget(new QLabel(qApp->translate(translation_ctx, si.display_name), widget_parent), current_row, 0);
layout->addWidget(sb, current_row, 1, 1, 3);
current_row++;
@@ -619,7 +614,8 @@ void ControllerCustomSettingsWidget::createSettingWidgets(const char* translatio
{
QLineEdit* le = new QLineEdit(widget_parent);
le->setObjectName(QString::fromUtf8(si.name));
SettingWidgetBinder::BindWidgetToStringSetting(sif, le, m_config_section, std::move(key_name), si.StringDefaultValue());
ControllerSettingWidgetBinder::BindWidgetToInputProfileString(
sif, le, m_config_section, std::move(key_name), si.StringDefaultValue());
layout->addWidget(new QLabel(qApp->translate(translation_ctx, si.display_name), widget_parent), current_row, 0);
layout->addWidget(le, current_row, 1, 1, 3);
current_row++;
@@ -641,7 +637,8 @@ void ControllerCustomSettingsWidget::createSettingWidgets(const char* translatio
for (u32 i = 0; si.options[i] != nullptr; i++)
cb->addItem(qApp->translate(translation_ctx, si.options[i]), QString::fromUtf8(si.options[i]));
}
SettingWidgetBinder::BindWidgetToStringSetting(sif, cb, m_config_section, std::move(key_name), si.StringDefaultValue());
ControllerSettingWidgetBinder::BindWidgetToInputProfileString(
sif, cb, m_config_section, std::move(key_name), si.StringDefaultValue());
layout->addWidget(new QLabel(qApp->translate(translation_ctx, si.display_name), widget_parent), current_row, 0);
layout->addWidget(cb, current_row, 1, 1, 3);
current_row++;
@@ -653,7 +650,8 @@ void ControllerCustomSettingsWidget::createSettingWidgets(const char* translatio
QLineEdit* le = new QLineEdit(widget_parent);
le->setObjectName(QString::fromUtf8(si.name));
QPushButton* browse_button = new QPushButton(tr("Browse..."), widget_parent);
SettingWidgetBinder::BindWidgetToStringSetting(sif, le, m_config_section, std::move(key_name), si.StringDefaultValue());
ControllerSettingWidgetBinder::BindWidgetToInputProfileString(
sif, le, m_config_section, std::move(key_name), si.StringDefaultValue());
connect(browse_button, &QPushButton::clicked, [this, le]() {
const QString path(QDir::toNativeSeparators(QFileDialog::getOpenFileName(this, tr("Select File"))));
if (!path.isEmpty())
@@ -677,6 +675,16 @@ void ControllerCustomSettingsWidget::createSettingWidgets(const char* translatio
layout->addItem(new QSpacerItem(1, 10, QSizePolicy::Minimum, QSizePolicy::Fixed), current_row++, 0, 1, 4);
}
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, current_row++, 0, 1, 4);
layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Expanding), current_row++, 0, 1, 4);
}
void ControllerCustomSettingsWidget::restoreDefaults()
@@ -715,7 +723,7 @@ void ControllerCustomSettingsWidget::restoreDefaults()
{
QDoubleSpinBox* widget = findChild<QDoubleSpinBox*>(QString::fromStdString(si.name));
if (widget)
widget->setValue(si.FloatDefaultValue());
widget->setValue(si.FloatDefaultValue() * si.multiplier);
}
break;
@@ -851,10 +859,12 @@ ControllerBindingWidget_Base* ControllerBindingWidget_DualShock2::createInstance
USBDeviceWidget::USBDeviceWidget(QWidget* parent, ControllerSettingsDialog* dialog, u32 port)
: QWidget(parent)
, m_dialog(dialog)
, m_config_section(StringUtil::StdStringFromFormat("USB%u", port + 1u))
, m_config_section(fmt::format("USB{}", port + 1))
, m_port_number(port)
{
m_ui.setupUi(this);
m_ui.groupBox->setTitle(tr("USB Port %1").arg(port + 1));
populateDeviceTypes();
populatePages();
@@ -926,9 +936,8 @@ void USBDeviceWidget::populatePages()
if (!settings.empty())
{
const QString settings_title(tr("Device Settings"));
m_settings_widget = new ControllerCustomSettingsWidget(
settings, m_config_section, m_device_type + "_", settings_title, m_device_type.c_str(), m_dialog, m_ui.stackedWidget);
settings, m_config_section, m_device_type + "_", m_device_type.c_str(), m_dialog, m_ui.stackedWidget);
m_ui.stackedWidget->addWidget(m_settings_widget);
}

View File

@@ -139,7 +139,7 @@ class ControllerCustomSettingsWidget : public QWidget
public:
ControllerCustomSettingsWidget(gsl::span<const SettingInfo> settings, std::string config_section, std::string config_prefix,
const QString& group_title, const char* translation_ctx, ControllerSettingsDialog* dialog, QWidget* parent_widget);
const char* translation_ctx, ControllerSettingsDialog* dialog, QWidget* parent_widget);
~ControllerCustomSettingsWidget();
private Q_SLOTS:

View File

@@ -38,7 +38,8 @@ namespace ControllerSettingWidgetBinder
{
/// Interface specific method of BindWidgetToBoolSetting().
template <typename WidgetType>
static void BindWidgetToInputProfileBool(SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, bool default_value)
static inline void BindWidgetToInputProfileBool(
SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, bool default_value)
{
using Accessor = SettingWidgetBinder::SettingAccessor<WidgetType>;
@@ -68,19 +69,53 @@ namespace ControllerSettingWidgetBinder
}
}
/// Interface specific method of BindWidgetToIntSetting().
template <typename WidgetType>
static inline void BindWidgetToInputProfileInt(
SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, s32 default_value, s32 option_offset = 0)
{
using Accessor = SettingWidgetBinder::SettingAccessor<WidgetType>;
if (sif)
{
const s32 value = sif->GetIntValue(section.c_str(), key.c_str(), default_value);
Accessor::setIntValue(widget, value - option_offset);
Accessor::connectValueChanged(widget, [sif, widget, section = std::move(section), key = std::move(key), option_offset]() {
const float new_value = Accessor::getIntValue(widget);
sif->SetIntValue(section.c_str(), key.c_str(), new_value + option_offset);
sif->Save();
g_emu_thread->reloadGameSettings();
});
}
else
{
const s32 value = Host::GetBaseIntSettingValue(section.c_str(), key.c_str(), default_value);
Accessor::setIntValue(widget, value - option_offset);
Accessor::connectValueChanged(widget, [widget, section = std::move(section), key = std::move(key), option_offset]() {
const s32 new_value = Accessor::getIntValue(widget);
Host::SetBaseIntSettingValue(section.c_str(), key.c_str(), new_value + option_offset);
Host::CommitBaseSettingChanges();
g_emu_thread->applySettings();
});
}
}
/// Interface specific method of BindWidgetToFloatSetting().
template <typename WidgetType>
static void BindWidgetToInputProfileFloat(SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, float default_value)
static inline void BindWidgetToInputProfileFloat(
SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, float default_value, float multiplier = 1.0f)
{
using Accessor = SettingWidgetBinder::SettingAccessor<WidgetType>;
if (sif)
{
const float value = sif->GetFloatValue(section.c_str(), key.c_str(), default_value);
Accessor::setFloatValue(widget, value);
Accessor::setFloatValue(widget, value * multiplier);
Accessor::connectValueChanged(widget, [sif, widget, section = std::move(section), key = std::move(key)]() {
const float new_value = Accessor::getFloatValue(widget);
Accessor::connectValueChanged(widget, [sif, widget, section = std::move(section), key = std::move(key), multiplier]() {
const float new_value = Accessor::getFloatValue(widget) / multiplier;
sif->SetFloatValue(section.c_str(), key.c_str(), new_value);
sif->Save();
g_emu_thread->reloadGameSettings();
@@ -89,10 +124,10 @@ namespace ControllerSettingWidgetBinder
else
{
const float value = Host::GetBaseFloatSettingValue(section.c_str(), key.c_str(), default_value);
Accessor::setFloatValue(widget, value);
Accessor::setFloatValue(widget, value * multiplier);
Accessor::connectValueChanged(widget, [widget, section = std::move(section), key = std::move(key)]() {
const float new_value = Accessor::getFloatValue(widget);
Accessor::connectValueChanged(widget, [widget, section = std::move(section), key = std::move(key), multiplier]() {
const float new_value = Accessor::getFloatValue(widget) / multiplier;
Host::SetBaseFloatSettingValue(section.c_str(), key.c_str(), new_value);
Host::CommitBaseSettingChanges();
g_emu_thread->applySettings();
@@ -102,12 +137,11 @@ namespace ControllerSettingWidgetBinder
/// Interface specific method of BindWidgetToNormalizedSetting().
template <typename WidgetType>
static void BindWidgetToInputProfileNormalized(
static inline void BindWidgetToInputProfileNormalized(
SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, float range, float default_value)
{
using Accessor = SettingWidgetBinder::SettingAccessor<WidgetType>;
if (sif)
{
const float value = sif->GetFloatValue(section.c_str(), key.c_str(), default_value);
@@ -136,7 +170,7 @@ namespace ControllerSettingWidgetBinder
/// Interface specific method of BindWidgetToStringSetting().
template <typename WidgetType>
static void BindWidgetToInputProfileString(
static inline void BindWidgetToInputProfileString(
SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, std::string default_value = std::string())
{
using Accessor = SettingWidgetBinder::SettingAccessor<WidgetType>;
@@ -160,7 +194,8 @@ namespace ControllerSettingWidgetBinder
}
else
{
const QString value(QString::fromStdString(Host::GetBaseStringSettingValue(section.c_str(), key.c_str(), default_value.c_str())));
const QString value(
QString::fromStdString(Host::GetBaseStringSettingValue(section.c_str(), key.c_str(), default_value.c_str())));
Accessor::setStringValue(widget, value);

View File

@@ -260,9 +260,13 @@ DEV9SettingsWidget::DEV9SettingsWidget(SettingsDialog* dialog, QWidget* parent)
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.hddEnabled, "DEV9/Hdd", "HddEnable", false);
connect(m_ui.hddFile, &QLineEdit::editingFinished, this, &DEV9SettingsWidget::onHddFileEdit);
SettingWidgetBinder::BindWidgetToStringSetting(sif, m_ui.hddFile, "DEV9/Hdd", "HddFile", "DEV9hdd.raw");
if (m_dialog->isPerGameSettings())
{
m_ui.hddFile->setText(QString::fromUtf8(m_dialog->getStringValue("DEV9/Hdd", "HddFile", "").value().c_str()));
m_ui.hddFile->setPlaceholderText(QString::fromUtf8(Host::GetBaseStringSettingValue("DEV9/Hdd", "HddFile", "DEV9hdd.raw")));
}
else
m_ui.hddFile->setText(QString::fromUtf8(m_dialog->getStringValue("DEV9/Hdd", "HddFile", "DEV9hdd.raw").value().c_str()));
connect(m_ui.hddBrowseFile, &QPushButton::clicked, this, &DEV9SettingsWidget::onHddBrowseFileClicked);
//TODO: need a getUintValue for if 48bit support occurs
@@ -270,18 +274,27 @@ DEV9SettingsWidget::DEV9SettingsWidget(SettingsDialog* dialog, QWidget* parent)
if (m_dialog->isPerGameSettings())
{
std::optional<int> sizeOpt = std::nullopt;
if (size > 0)
sizeOpt = size;
const int sizeGlobal = (u64)Host::GetBaseIntSettingValue("DEV9/Hdd", "HddSizeSectors", 0) * 512 / (1024 * 1024 * 1024);
m_ui.hddSizeSpinBox->setMinimum(39);
m_ui.hddSizeSpinBox->setSpecialValueText(tr("Global [%1]").arg(sizeGlobal));
SettingWidgetBinder::SettingAccessor<QSpinBox>::makeNullableInt(m_ui.hddSizeSpinBox, sizeGlobal);
SettingWidgetBinder::SettingAccessor<QSpinBox>::setNullableIntValue(m_ui.hddSizeSpinBox, sizeOpt);
m_ui.hddSizeSlider->setValue(sizeOpt.value_or(sizeGlobal));
m_ui.hddSizeSlider->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_ui.hddSizeSlider, &QSlider::customContextMenuRequested, this, &DEV9SettingsWidget::onHddSizeSliderContext);
}
else
{
m_ui.hddSizeSlider->setValue(size);
SettingWidgetBinder::SettingAccessor<QSpinBox>::setIntValue(m_ui.hddSizeSpinBox, size);
}
// clang-format off
m_ui.hddSizeSlider ->setValue(size);
m_ui.hddSizeSpinBox->setValue(size);
connect(m_ui.hddSizeSlider, QOverload<int>::of(&QSlider ::valueChanged), this, &DEV9SettingsWidget::onHddSizeSlide);
connect(m_ui.hddSizeSpinBox, QOverload<int>::of(&QSpinBox::valueChanged), this, &DEV9SettingsWidget::onHddSizeSpin );
// clang-format on
connect(m_ui.hddSizeSlider, QOverload<int>::of(&QSlider::valueChanged), this, &DEV9SettingsWidget::onHddSizeSlide);
SettingWidgetBinder::SettingAccessor<QSpinBox>::connectValueChanged(m_ui.hddSizeSpinBox, [&]() { onHddSizeAccessorSpin(); });
connect(m_ui.hddCreate, &QPushButton::clicked, this, &DEV9SettingsWidget::onHddCreateClicked);
}
@@ -682,7 +695,7 @@ void DEV9SettingsWidget::onEthHostEdit(QStandardItem* item)
void DEV9SettingsWidget::onHddEnabledChanged(int state)
{
const bool enabled = state == Qt::CheckState::PartiallyChecked ? m_dialog->getEffectiveBoolValue("DEV9/Hdd", "HddEnable", false) : state;
const bool enabled = state == Qt::CheckState::PartiallyChecked ? Host::GetBaseBoolSettingValue("DEV9/Hdd", "HddEnable", false) : state;
m_ui.hddFile->setEnabled(enabled);
m_ui.hddFileLabel->setEnabled(enabled);
@@ -699,8 +712,8 @@ void DEV9SettingsWidget::onHddBrowseFileClicked()
{
QString path =
QDir::toNativeSeparators(QFileDialog::getSaveFileName(QtUtils::GetRootWidget(this), tr("HDD Image File"),
!m_ui.hddFile->text().isEmpty() ? m_ui.hddFile->text() : "DEV9hdd.raw", tr("HDD (*.raw)"), nullptr,
QFileDialog::DontConfirmOverwrite));
!m_ui.hddFile->text().isEmpty() ? m_ui.hddFile->text() : (!m_ui.hddFile->placeholderText().isEmpty() ? m_ui.hddFile->placeholderText() : "DEV9hdd.raw"),
tr("HDD (*.raw)"), nullptr, QFileDialog::DontConfirmOverwrite));
if (path.isEmpty())
return;
@@ -711,10 +724,16 @@ void DEV9SettingsWidget::onHddBrowseFileClicked()
void DEV9SettingsWidget::onHddFileEdit()
{
//Check if file exists, if so set HddSize to correct value
// Check if file exists, if so set HddSize to correct value.
// Also save the hddPath setting
std::string hddPath(m_ui.hddFile->text().toStdString());
if (hddPath.empty())
{
m_dialog->setStringSettingValue("DEV9/Hdd", "HddFile", std::nullopt);
return;
}
else
m_dialog->setStringSettingValue("DEV9/Hdd", "HddFile", hddPath.c_str());
if (!Path::IsAbsolute(hddPath))
hddPath = Path::Combine(EmuFolders::Settings, hddPath);
@@ -739,22 +758,49 @@ void DEV9SettingsWidget::onHddFileEdit()
void DEV9SettingsWidget::onHddSizeSlide(int i)
{
// We have to call onHddSizeAccessorSpin() ourself, as the value could still be considered null when the valueChanged signal is fired
QSignalBlocker sb(m_ui.hddSizeSpinBox);
m_ui.hddSizeSpinBox->setValue(i);
m_dialog->setIntSettingValue("DEV9/Hdd", "HddSizeSectors", (int)((s64)i * 1024 * 1024 * 1024 / 512));
SettingWidgetBinder::SettingAccessor<QSpinBox>::setNullableIntValue(m_ui.hddSizeSpinBox, i);
onHddSizeAccessorSpin();
}
void DEV9SettingsWidget::onHddSizeSpin(int i)
void DEV9SettingsWidget::onHddSizeSliderContext(const QPoint& pt)
{
QSignalBlocker sb(m_ui.hddSizeSlider);
m_ui.hddSizeSlider->setValue(i);
QMenu menu(m_ui.hddSizeSlider);
connect(menu.addAction(qApp->translate("SettingWidgetBinder", "Reset")), &QAction::triggered, this, &DEV9SettingsWidget::onHddSizeSliderReset);
menu.exec(m_ui.hddSizeSlider->mapToGlobal(pt));
}
//TODO: need a setUintSettingValue for if 48bit support occurs
if (i == 39)
m_dialog->setIntSettingValue("DEV9/Hdd", "HddSizeSectors", std::nullopt);
void DEV9SettingsWidget::onHddSizeSliderReset([[maybe_unused]] bool checked)
{
// We have to call onHddSizeAccessorSpin() ourself, as the value could still be considered non-null when the valueChanged signal is fired
QSignalBlocker sb(m_ui.hddSizeSpinBox);
SettingWidgetBinder::SettingAccessor<QSpinBox>::setNullableIntValue(m_ui.hddSizeSpinBox, std::nullopt);
onHddSizeAccessorSpin();
}
void DEV9SettingsWidget::onHddSizeAccessorSpin()
{
//TODO: need a getUintValue for if 48bit support occurs
QSignalBlocker sb(m_ui.hddSizeSlider);
if (m_dialog->isPerGameSettings())
{
std::optional<int> new_value = SettingWidgetBinder::SettingAccessor<QSpinBox>::getNullableIntValue(m_ui.hddSizeSpinBox);
const int sizeGlobal = (u64)Host::GetBaseIntSettingValue("DEV9/Hdd", "HddSizeSectors", 0) * 512 / (1024 * 1024 * 1024);
m_ui.hddSizeSlider->setValue(new_value.value_or(sizeGlobal));
if (new_value.has_value())
m_dialog->setIntSettingValue("DEV9/Hdd", "HddSizeSectors", new_value.value() * (1024 * 1024 * 1024 / 512));
else
m_dialog->setIntSettingValue("DEV9/Hdd", "HddSizeSectors", std::nullopt);
}
else
m_dialog->setIntSettingValue("DEV9/Hdd", "HddSizeSectors", i * (1024 * 1024 * 1024 / 512));
{
const int new_value = SettingWidgetBinder::SettingAccessor<QSpinBox>::getIntValue(m_ui.hddSizeSpinBox);
m_ui.hddSizeSlider->setValue(new_value);
m_dialog->setIntSettingValue("DEV9/Hdd", "HddSizeSectors", new_value * (1024 * 1024 * 1024 / 512));
}
}
void DEV9SettingsWidget::onHddCreateClicked()
@@ -774,7 +820,7 @@ void DEV9SettingsWidget::onHddCreateClicked()
if (!Path::IsAbsolute(hddPath))
hddPath = Path::Combine(EmuFolders::Settings, hddPath);
if (!FileSystem::FileExists(hddPath.c_str()))
if (FileSystem::FileExists(hddPath.c_str()))
{
//GHC uses UTF8 on all platforms
QMessageBox::StandardButton selection =

View File

@@ -49,7 +49,11 @@ private Q_SLOTS:
void onHddBrowseFileClicked();
void onHddFileEdit();
void onHddSizeSlide(int i);
void onHddSizeSpin(int i);
// Per game only.
void onHddSizeSliderContext(const QPoint& pt);
void onHddSizeSliderReset(bool checked = false);
//
void onHddSizeAccessorSpin();
void onHddCreateClicked();
public:

View File

@@ -346,6 +346,11 @@
<string>In-Game</string>
</property>
</item>
<item>
<property name="text">
<string>Playable</string>
</property>
</item>
<item>
<property name="text">
<string>Perfect</string>

View File

@@ -17,6 +17,7 @@
#include "QtHost.h"
#include "QtUtils.h"
#include "Settings/ControllerSettingWidgetBinder.h"
#include "Settings/InputBindingDialog.h"
#include "Settings/InputBindingWidget.h"
#include <QtCore/QTimer>
@@ -24,6 +25,8 @@
#include <QtGui/QMouseEvent>
#include <QtGui/QWheelEvent>
#include "fmt/format.h"
// _BitScanForward()
#include "pcsx2/GS/GSIntrin.h"
@@ -45,6 +48,26 @@ InputBindingDialog::InputBindingDialog(SettingsInterface* sif, InputBindingInfo:
connect(m_ui.clearBindings, &QPushButton::clicked, this, &InputBindingDialog::onClearBindingsButtonClicked);
connect(m_ui.buttonBox, &QDialogButtonBox::rejected, [this]() { done(0); });
updateList();
// Only show the sensitivity controls for binds where it's applicable.
if (bind_type == InputBindingInfo::Type::Button || bind_type == InputBindingInfo::Type::Axis ||
bind_type == InputBindingInfo::Type::HalfAxis)
{
ControllerSettingWidgetBinder::BindWidgetToInputProfileNormalized(
sif, m_ui.sensitivity, m_section_name, fmt::format("{}Scale", m_key_name), 100.0f, 1.0f);
ControllerSettingWidgetBinder::BindWidgetToInputProfileNormalized(
sif, m_ui.deadzone, m_section_name, fmt::format("{}Deadzone", m_key_name), 100.0f, 0.0f);
connect(m_ui.sensitivity, &QSlider::valueChanged, this, &InputBindingDialog::onSensitivityChanged);
connect(m_ui.deadzone, &QSlider::valueChanged, this, &InputBindingDialog::onDeadzoneChanged);
onSensitivityChanged(m_ui.sensitivity->value());
onDeadzoneChanged(m_ui.deadzone->value());
}
else
{
m_ui.verticalLayout->removeWidget(m_ui.sensitivityWidget);
}
}
InputBindingDialog::~InputBindingDialog()
@@ -318,6 +341,16 @@ void InputBindingDialog::inputManagerHookCallback(InputBindingKey key, float val
}
}
void InputBindingDialog::onSensitivityChanged(int value)
{
m_ui.sensitivityValue->setText(tr("%1%").arg(value));
}
void InputBindingDialog::onDeadzoneChanged(int value)
{
m_ui.deadzoneValue->setText(tr("%1%").arg(value));
}
void InputBindingDialog::hookInputManager()
{
InputManager::SetHook([this](InputBindingKey key, float value) {

View File

@@ -41,6 +41,9 @@ protected Q_SLOTS:
void onInputListenTimerTimeout();
void inputManagerHookCallback(InputBindingKey key, float value);
void onSensitivityChanged(int value);
void onDeadzoneChanged(int value);
protected:
enum : u32
{

View File

@@ -10,7 +10,7 @@
<x>0</x>
<y>0</y>
<width>533</width>
<height>283</height>
<height>266</height>
</rect>
</property>
<property name="windowTitle">
@@ -30,6 +30,93 @@
<item>
<widget class="QListWidget" name="bindingList"/>
</item>
<item>
<widget class="QWidget" name="sensitivityWidget" native="true">
<layout class="QGridLayout" name="sensitivityLayout">
<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="QLabel" name="sensitivityLabel">
<property name="text">
<string>Sensitivity:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSlider" name="sensitivity">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>200</number>
</property>
<property name="value">
<number>100</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TicksBelow</enum>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="sensitivityValue">
<property name="text">
<string>100%</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="deadzoneLabel">
<property name="text">
<string>Deadzone:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSlider" name="deadzone">
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>1</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TicksBelow</enum>
</property>
<property name="tickInterval">
<number>5</number>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLabel" name="deadzoneValue">
<property name="text">
<string>100%</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLabel" name="status">
<property name="text">

View File

@@ -191,7 +191,7 @@ void SettingsDialog::setupUi(const GameList::Entry* game)
m_ui.settingsContainer->setCurrentIndex(0);
m_ui.helpText->setText(m_category_help_text[0]);
connect(m_ui.settingsCategory, &QListWidget::currentRowChanged, this, &SettingsDialog::onCategoryCurrentRowChanged);
connect(m_ui.closeButton, &QPushButton::clicked, this, &SettingsDialog::accept);
connect(m_ui.closeButton, &QPushButton::clicked, this, &SettingsDialog::close);
connect(m_ui.restoreDefaultsButton, &QPushButton::clicked, this, &SettingsDialog::onRestoreDefaultsClicked);
}

View File

@@ -0,0 +1,115 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "CDVD/CDVDdiscReader.h"
#ifdef __APPLE__
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/storage/IOMedia.h>
#include <IOKit/storage/IOCDMedia.h>
#include <IOKit/storage/IODVDMedia.h>
#include <IOKit/IOBSD.h>
#include <IOKit/IOKitLib.h>
#endif
#include <fcntl.h>
#include <unistd.h>
#ifdef __APPLE__
std::vector<std::string> GetDriveListFromClasses(CFMutableDictionaryRef classes)
{
io_iterator_t iterator = IO_OBJECT_NULL;
kern_return_t result;
std::vector<std::string> drives;
CFDictionarySetValue(classes, CFSTR(kIOMediaEjectableKey), kCFBooleanTrue);
result = IOServiceGetMatchingServices(kIOMasterPortDefault, classes, &iterator);
if (result != KERN_SUCCESS)
return drives;
while (io_object_t media = IOIteratorNext(iterator))
{
CFTypeRef path_cfstr = IORegistryEntryCreateCFProperty(media, CFSTR(kIOBSDNameKey), kCFAllocatorDefault, 0);
if (path_cfstr)
{
char path[PATH_MAX] = {0};
strlcpy(path, "/dev/r", PATH_MAX);
size_t path_prefix_len = strnlen(path, PATH_MAX);
result = CFStringGetCString((CFStringRef)path_cfstr, path + path_prefix_len, PATH_MAX - path_prefix_len, kCFStringEncodingUTF8);
if (result)
{
drives.emplace_back(path);
}
CFRelease(path_cfstr);
}
IOObjectRelease(media);
}
IOObjectRelease(iterator);
return drives;
}
#endif
std::vector<std::string> GetOpticalDriveList()
{
#ifdef __APPLE__
std::vector<std::string> drives;
if (CFMutableDictionaryRef cd_classes = IOServiceMatching(kIOCDMediaClass))
{
std::vector<std::string> cd = GetDriveListFromClasses(cd_classes);
drives.insert(drives.end(), cd.begin(), cd.end());
}
if (CFMutableDictionaryRef dvd_classes = IOServiceMatching(kIODVDMediaClass))
{
std::vector<std::string> dvd = GetDriveListFromClasses(dvd_classes);
drives.insert(drives.end(), dvd.begin(), dvd.end());
}
return drives;
#else
return {};
#endif
}
void GetValidDrive(std::string& drive)
{
if (!drive.empty())
{
#ifdef __APPLE__
int fd = open(drive.c_str(), O_RDONLY | O_NONBLOCK);
if (fd != -1)
{
close(fd);
}
else
{
drive.clear();
}
#else
drive.clear();
#endif
}
if (drive.empty())
{
auto drives = GetOpticalDriveList();
if (!drives.empty())
drive = drives.front();
}
if (!drive.empty())
DevCon.WriteLn("CDVD: Opening drive '%s'...", drive.c_str());
}

View File

@@ -0,0 +1,263 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "CDVD/CDVDdiscReader.h"
#include "CDVD/CDVD.h"
#ifdef __APPLE__
#include <IOKit/storage/IOCDMediaBSDClient.h>
#include <IOKit/storage/IODVDMediaBSDClient.h>
#endif
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <cerrno>
#include <cstring>
IOCtlSrc::IOCtlSrc(decltype(m_filename) filename)
: m_filename(filename)
{
if (!Reopen())
throw std::runtime_error(" * CDVD: Error opening source.\n");
}
IOCtlSrc::~IOCtlSrc()
{
if (m_device != -1)
{
SetSpindleSpeed(true);
close(m_device);
}
}
bool IOCtlSrc::Reopen()
{
if (m_device != -1)
close(m_device);
// O_NONBLOCK allows a valid file descriptor to be returned even if the
// drive is empty. Probably does other things too.
m_device = open(m_filename.c_str(), O_RDONLY | O_NONBLOCK);
if (m_device == -1)
return false;
// DVD detection MUST be first on Linux - The TOC ioctls work for both
// CDs and DVDs.
if (ReadDVDInfo() || ReadCDInfo())
SetSpindleSpeed(false);
return true;
}
void IOCtlSrc::SetSpindleSpeed(bool restore_defaults) const
{
u16 speed = restore_defaults ? 0xFFFF : m_media_type >= 0 ? 5540 :
3600;
int ioctl_code = m_media_type >= 0 ? DKIOCDVDSETSPEED : DKIOCCDSETSPEED;
if (ioctl(m_device, ioctl_code, &speed) == -1)
{
DevCon.Warning("CDVD: Failed to set spindle speed: %s", strerror(errno));
}
else if (!restore_defaults)
{
DevCon.WriteLn("CDVD: Spindle speed set to %d", speed);
}
}
u32 IOCtlSrc::GetSectorCount() const
{
return m_sectors;
}
u32 IOCtlSrc::GetLayerBreakAddress() const
{
return m_layer_break;
}
s32 IOCtlSrc::GetMediaType() const
{
return m_media_type;
}
const std::vector<toc_entry>& IOCtlSrc::ReadTOC() const
{
return m_toc;
}
bool IOCtlSrc::ReadSectors2048(u32 sector, u32 count, u8* buffer) const
{
const ssize_t bytes_to_read = 2048 * count;
ssize_t bytes_read = pread(m_device, buffer, bytes_to_read, sector * 2048ULL);
if (bytes_read == bytes_to_read)
return true;
if (bytes_read == -1)
DevCon.Warning("CDVD: read sectors %u-%u failed: %s",
sector, sector + count - 1, strerror(errno));
else
DevCon.Warning("CDVD: read sectors %u-%u: %zd bytes read, %zd bytes expected",
sector, sector + count - 1, bytes_read, bytes_to_read);
return false;
}
bool IOCtlSrc::ReadSectors2352(u32 sector, u32 count, u8* buffer) const
{
#ifdef __APPLE__
dk_cd_read_t desc;
memset(&desc, 0, sizeof(dk_cd_read_t));
desc.sectorArea = kCDSectorAreaSync | kCDSectorAreaHeader | kCDSectorAreaSubHeader | kCDSectorAreaUser | kCDSectorAreaAuxiliary;
desc.sectorType = kCDSectorTypeUnknown;
for (u32 i = 0; i < count; ++i)
{
desc.offset = (sector + i) * 2352ULL;
desc.buffer = buffer + i * 2352;
desc.bufferLength = 2352;
if (ioctl(m_device, DKIOCCDREAD, &desc) == -1)
{
DevCon.Warning("CDVD: DKIOCCDREAD sector %u failed: %s",
sector + i, strerror(errno));
return false;
}
}
return true;
#else
return false;
#endif
}
bool IOCtlSrc::ReadDVDInfo()
{
#ifdef __APPLE__
dk_dvd_read_structure_t dvdrs;
memset(&dvdrs, 0, sizeof(dk_dvd_read_structure_t));
dvdrs.format = kDVDStructureFormatPhysicalFormatInfo;
dvdrs.layer = 0;
DVDPhysicalFormatInfo layer0;
dvdrs.buffer = &layer0;
dvdrs.bufferLength = sizeof(DVDPhysicalFormatInfo);
int ret = ioctl(m_device, DKIOCDVDREADSTRUCTURE, &dvdrs);
if (ret == -1)
{
return false;
}
u32 start_sector = *(u32*)layer0.startingPhysicalSectorNumberOfDataArea;
u32 end_sector = *(u32*)layer0.endPhysicalSectorNumberOfDataArea;
if (layer0.numberOfLayers == 0)
{
// Single layer
m_media_type = 0;
m_layer_break = 0;
m_sectors = end_sector - start_sector + 1;
}
else if (layer0.trackPath == 0)
{
// Dual layer, Parallel Track Path
DVDPhysicalFormatInfo layer1;
dvdrs.layer = 1;
dvdrs.buffer = &layer1;
dvdrs.bufferLength = sizeof(DVDPhysicalFormatInfo);
ret = ioctl(m_device, DKIOCDVDREADSTRUCTURE, &dvdrs);
if (ret == -1)
return false;
u32 layer1_start_sector = *(u32*)layer1.startingPhysicalSectorNumberOfDataArea;
u32 layer1_end_sector = *(u32*)layer1.endPhysicalSectorNumberOfDataArea;
m_media_type = 1;
m_layer_break = end_sector - start_sector;
m_sectors = end_sector - start_sector + 1 + layer1_end_sector - layer1_start_sector + 1;
}
else
{
// Dual layer, Opposite Track Path
u32 end_sector_layer0 = *(u32*)layer0.endSectorNumberInLayerZero;
m_media_type = 2;
m_layer_break = end_sector_layer0 - start_sector;
m_sectors = end_sector_layer0 - start_sector + 1 + end_sector - (~end_sector_layer0 & 0xFFFFFFU) + 1;
}
return true;
#else
return false;
#endif
}
bool IOCtlSrc::ReadCDInfo()
{
#ifdef __APPLE__
u8* buffer = (u8*)malloc(2048);
dk_cd_read_toc_t cdrt;
memset(&cdrt, 0, sizeof(dk_cd_read_toc_t));
cdrt.format = kCDTOCFormatTOC;
cdrt.formatAsTime = 1;
cdrt.address.track = 0;
cdrt.buffer = buffer;
cdrt.bufferLength = 2048;
memset(buffer, 0, 2048);
if (ioctl(m_device, DKIOCCDREADTOC, &cdrt) == -1)
{
DevCon.Warning("CDVD: DKIOCCDREADTOC failed: %s\n", strerror(errno));
return false;
}
CDTOC* toc = (CDTOC*)buffer;
u32 desc_count = CDTOCGetDescriptorCount(toc);
for (u32 i = 0; i < desc_count; ++i)
{
CDTOCDescriptor desc = toc->descriptors[i];
if (desc.point < 0xa0 && desc.adr == 1)
{
u32 lba = CDConvertMSFToLBA(desc.p);
m_toc.push_back({lba, desc.point, desc.adr, desc.control});
}
else if (desc.point == 0xa2) // lead out, use to get total sector count
{
m_sectors = CDConvertMSFToLBA(desc.p);
}
}
m_media_type = -1;
free(buffer);
return true;
#else
return false;
#endif
}
bool IOCtlSrc::DiscReady()
{
#ifdef __APPLE__
if (m_device == -1)
return false;
if (!m_sectors)
{
Reopen();
}
return !!m_sectors;
#else
return false;
#endif
}

View File

@@ -16,18 +16,14 @@
#include "PrecompiledHeader.h"
#include "CDVD/CDVDdiscReader.h"
#ifdef __linux__
#include <libudev.h>
#include <linux/cdrom.h>
#endif
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
std::vector<std::string> GetOpticalDriveList()
{
#ifdef __linux__
udev* udev_context = udev_new();
if (!udev_context)
return {};
@@ -56,16 +52,12 @@ std::vector<std::string> GetOpticalDriveList()
udev_unref(udev_context);
return drives;
#else
return {};
#endif
}
void GetValidDrive(std::string& drive)
{
if (!drive.empty())
{
#ifdef __linux__
int fd = open(drive.c_str(), O_RDONLY | O_NONBLOCK);
if (fd != -1)
{
@@ -77,9 +69,6 @@ void GetValidDrive(std::string& drive)
{
drive.clear();
}
#else
drive.clear();
#endif
}
if (drive.empty())
{

View File

@@ -17,10 +17,7 @@
#include "CDVD/CDVDdiscReader.h"
#include "CDVD/CDVD.h"
#ifdef __linux__
#include <linux/cdrom.h>
#endif
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
@@ -108,7 +105,6 @@ bool IOCtlSrc::ReadSectors2048(u32 sector, u32 count, u8* buffer) const
bool IOCtlSrc::ReadSectors2352(u32 sector, u32 count, u8* buffer) const
{
#ifdef __linux__
union
{
cdrom_msf msf;
@@ -130,14 +126,10 @@ bool IOCtlSrc::ReadSectors2352(u32 sector, u32 count, u8* buffer) const
}
return true;
#else
return false;
#endif
}
bool IOCtlSrc::ReadDVDInfo()
{
#ifdef __linux__
dvd_struct dvdrs;
dvdrs.type = DVD_STRUCT_PHYSICAL;
dvdrs.physical.layer_num = 0;
@@ -180,14 +172,10 @@ bool IOCtlSrc::ReadDVDInfo()
}
return true;
#else
return false;
#endif
}
bool IOCtlSrc::ReadCDInfo()
{
#ifdef __linux__
cdrom_tochdr header;
if (ioctl(m_device, CDROMREADTOCHDR, &header) == -1)
@@ -214,14 +202,10 @@ bool IOCtlSrc::ReadCDInfo()
m_media_type = -1;
return true;
#else
return false;
#endif
}
bool IOCtlSrc::DiscReady()
{
#ifdef __linux__
if (m_device == -1)
return false;
@@ -239,7 +223,4 @@ bool IOCtlSrc::DiscReady()
}
return !!m_sectors;
#else
return false;
#endif
}

View File

@@ -329,6 +329,7 @@ set(pcsx2DEV9Sources
DEV9/InternalServers/DNS_Logger.cpp
DEV9/InternalServers/DNS_Server.cpp
DEV9/PacketReader/ARP/ARP_Packet.cpp
DEV9/PacketReader/ARP/ARP_PacketEditor.cpp
DEV9/PacketReader/IP/ICMP/ICMP_Packet.cpp
DEV9/PacketReader/IP/TCP/TCP_Options.cpp
DEV9/PacketReader/IP/TCP/TCP_Packet.cpp
@@ -340,6 +341,7 @@ set(pcsx2DEV9Sources
DEV9/PacketReader/IP/IP_Options.cpp
DEV9/PacketReader/IP/IP_Packet.cpp
DEV9/PacketReader/EthernetFrame.cpp
DEV9/PacketReader/EthernetFrameEditor.cpp
DEV9/Sessions/BaseSession.cpp
DEV9/Sessions/ICMP_Session/ICMP_Session.cpp
DEV9/Sessions/TCP_Session/TCP_Session.cpp
@@ -366,6 +368,7 @@ set(pcsx2DEV9Headers
DEV9/InternalServers/DNS_Server.h
DEV9/net.h
DEV9/PacketReader/ARP/ARP_Packet.h
DEV9/PacketReader/ARP/ARP_PacketEditor.h
DEV9/PacketReader/IP/ICMP/ICMP_Packet.h
DEV9/PacketReader/IP/TCP/TCP_Options.h
DEV9/PacketReader/IP/TCP/TCP_Packet.h
@@ -380,6 +383,7 @@ set(pcsx2DEV9Headers
DEV9/PacketReader/IP/IP_Packet.h
DEV9/PacketReader/IP/IP_Payload.h
DEV9/PacketReader/EthernetFrame.h
DEV9/PacketReader/EthernetFrameEditor.h
DEV9/PacketReader/MAC_Address.h
DEV9/PacketReader/NetLib.h
DEV9/PacketReader/Payload.h
@@ -912,14 +916,14 @@ set(pcsx2LinuxSources
)
set(pcsx2OSXSources
CDVD/Linux/DriveUtility.cpp
CDVD/Linux/IOCtlSrc.cpp
CDVD/Darwin/DriveUtility.cpp
CDVD/Darwin/IOCtlSrc.cpp
Darwin/DarwinFlatFileReader.cpp
)
set(pcsx2FreeBSDSources
CDVD/Linux/DriveUtility.cpp
CDVD/Linux/IOCtlSrc.cpp
CDVD/Darwin/DriveUtility.cpp
CDVD/Darwin/IOCtlSrc.cpp
Darwin/DarwinFlatFileReader.cpp
)

View File

@@ -445,6 +445,10 @@ u32 UpdateVSyncRate()
if (vSyncInfo.Framerate != frames_per_second || vSyncInfo.VideoMode != gsVideoMode)
{
// NBA Jam 2004 PAL will fail to display 3D on the menu if this value isn't correct on reset.
if (video_mode_initialized && vSyncInfo.VideoMode != gsVideoMode)
CSRreg.FIELD = 1;
vSyncInfo.VideoMode = gsVideoMode;
vSyncInfoCalc(&vSyncInfo, frames_per_second, total_scanlines);
@@ -457,7 +461,9 @@ u32 UpdateVSyncRate()
hsyncCounter.CycleT = vSyncInfo.hRender; // Amount of cycles before the counter will be updated
vsyncCounter.CycleT = vSyncInfo.Render; // Amount of cycles before the counter will be updated
hsyncCounter.sCycle = cpuRegs.cycle;
vsyncCounter.sCycle = cpuRegs.cycle;
vsyncCounter.Mode = MODE_VRENDER;
cpuRcntSet();
}

View File

@@ -62,8 +62,6 @@ struct EECNT_MODE
u32 OverflowReached:1;
};
// fixme: Cycle and sCycleT members are unused.
// But they can't be removed without making a new savestate version.
struct Counter
{
u32 count;

View File

@@ -0,0 +1,79 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2023 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "ARP_PacketEditor.h"
#ifdef _WIN32
#include "winsock.h"
#else
#include <arpa/inet.h>
#endif
namespace PacketReader::ARP
{
ARP_PacketEditor::ARP_PacketEditor(PayloadPtr* pkt)
: basePkt{pkt}
{
}
u16 ARP_PacketEditor::GetHardwareType()
{
return ntohs(*(u16*)&basePkt->data[0]);
}
u16 ARP_PacketEditor::GetProtocol()
{
return ntohs(*(u16*)&basePkt->data[2]);
}
u8 ARP_PacketEditor::GetHardwareAddressLength()
{
return basePkt->data[4];
}
u8 ARP_PacketEditor::GetProtocolAddressLength()
{
return basePkt->data[5];
}
u16 ARP_PacketEditor::GetOp()
{
return ntohs(*(u16*)&basePkt->data[6]);
}
u8* ARP_PacketEditor::SenderHardwareAddress()
{
return &basePkt->data[8];
}
u8* ARP_PacketEditor::SenderProtocolAddress()
{
int offset = 8 + GetHardwareAddressLength();
return &basePkt->data[offset];
}
u8* ARP_PacketEditor::TargetHardwareAddress()
{
int offset = 8 + GetHardwareAddressLength() + GetProtocolAddressLength();
return &basePkt->data[offset];
}
u8* ARP_PacketEditor::TargetProtocolAddress()
{
int offset = 8 + 2 * GetHardwareAddressLength() + GetProtocolAddressLength();
return &basePkt->data[offset];
}
} // namespace PacketReader::ARP

View File

@@ -0,0 +1,42 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2023 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "DEV9/PacketReader/Payload.h"
namespace PacketReader::ARP
{
class ARP_PacketEditor
{
private:
PayloadPtr* basePkt;
public:
ARP_PacketEditor(PayloadPtr* pkt);
u16 GetHardwareType();
u16 GetProtocol();
u8 GetHardwareAddressLength();
u8 GetProtocolAddressLength();
u16 GetOp();
u8* SenderHardwareAddress();
u8* SenderProtocolAddress();
u8* TargetHardwareAddress();
u8* TargetProtocolAddress();
};
} // namespace PacketReader::ARP

View File

@@ -0,0 +1,66 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2023 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "EthernetFrameEditor.h"
#ifdef _WIN32
#include "winsock.h"
#else
#include <arpa/inet.h>
#endif
namespace PacketReader
{
EthernetFrameEditor::EthernetFrameEditor(NetPacket* pkt)
: basePkt{pkt}
{
headerLength = 14; //(6+6+2)
//Note: we don't have to worry about the Ethernet Frame CRC as it is not included in the packet
//Note: We don't support tagged frames
payload = std::make_unique<PayloadPtr>((u8*)&basePkt->buffer[14], pkt->size - headerLength);
}
MAC_Address EthernetFrameEditor::GetDestinationMAC()
{
return *(MAC_Address*)&basePkt->buffer[0];
}
void EthernetFrameEditor::SetDestinationMAC(MAC_Address value)
{
*(MAC_Address*)&basePkt->buffer[0] = value;
}
MAC_Address EthernetFrameEditor::GetSourceMAC()
{
return *(MAC_Address*)&basePkt->buffer[6];
}
void EthernetFrameEditor::SetSourceMAC(MAC_Address value)
{
*(MAC_Address*)&basePkt->buffer[6] = value;
}
u16 EthernetFrameEditor::GetProtocol()
{
return ntohs(*(u16*)&basePkt->buffer[12]);
}
PayloadPtr* EthernetFrameEditor::GetPayload()
{
return payload.get();
}
} // namespace PacketReader

View File

@@ -0,0 +1,45 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2023 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "DEV9/net.h"
#include "MAC_Address.h"
#include "Payload.h"
namespace PacketReader
{
class EthernetFrameEditor
{
public:
int headerLength = 14;
//Length
private:
NetPacket* basePkt;
std::unique_ptr<PayloadPtr> payload;
public:
EthernetFrameEditor(NetPacket* pkt);
MAC_Address GetDestinationMAC();
void SetDestinationMAC(MAC_Address value);
MAC_Address GetSourceMAC();
void SetSourceMAC(MAC_Address value);
u16 GetProtocol();
PayloadPtr* GetPayload();
};
} // namespace PacketReader

View File

@@ -23,18 +23,16 @@
#include "common/StringUtil.h"
#include <WinSock2.h>
#include <iphlpapi.h>
#elif defined(__POSIX__)
#include <sys/types.h>
#include <ifaddrs.h>
#endif
#include <stdio.h>
#include <stdarg.h>
#include "pcap_io.h"
#include "DEV9.h"
#include "AdapterUtils.h"
#include "net.h"
#include "DEV9/PacketReader/MAC_Address.h"
#include "PacketReader/EthernetFrame.h"
#include "PacketReader/EthernetFrameEditor.h"
#include "PacketReader/ARP/ARP_PacketEditor.h"
#ifndef PCAP_NETMASK_UNKNOWN
#define PCAP_NETMASK_UNKNOWN 0xffffffff
#endif
@@ -43,203 +41,9 @@
#define PCAPPREFIX "\\Device\\NPF_"
#endif
pcap_t* adhandle;
pcap_dumper_t* dump_pcap = nullptr;
char errbuf[PCAP_ERRBUF_SIZE];
int pcap_io_running = 0;
bool pcap_io_switched;
bool pcap_io_blocking;
extern u8 eeprom[];
char namebuff[256];
ip_address ps2_ip;
mac_address ps2_mac;
mac_address host_mac;
int pcap_io_init(const std::string& adapter, bool switched, mac_address virtual_mac)
{
struct bpf_program fp;
char filter[1024] = "ether broadcast or ether dst ";
int dlt;
char* dlt_name;
Console.WriteLn("DEV9: Opening adapter '%s'...", adapter.c_str());
pcap_io_switched = switched;
#ifdef _WIN32
std::string pcapAdapter = PCAPPREFIX + adapter;
#else
std::string pcapAdapter = adapter;
#endif
/* Open the adapter */
if ((adhandle = pcap_open_live(pcapAdapter.c_str(), // name of the device
65536, // portion of the packet to capture.
// 65536 grants that the whole packet will be captured on all the MACs.
switched ? 1 : 0,
1, // read timeout
errbuf // error buffer
)) == NULL)
{
Console.Error("DEV9: %s", errbuf);
Console.Error("DEV9: Unable to open the adapter. %s is not supported by pcap", adapter.c_str());
return -1;
}
if (switched)
{
char virtual_mac_str[18];
sprintf(virtual_mac_str, "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x", virtual_mac.bytes[0], virtual_mac.bytes[1], virtual_mac.bytes[2], virtual_mac.bytes[3], virtual_mac.bytes[4], virtual_mac.bytes[5]);
strcat(filter, virtual_mac_str);
// fprintf(stderr, "Trying pcap filter: %s\n", filter);
if (pcap_compile(adhandle, &fp, filter, 1, PCAP_NETMASK_UNKNOWN) == -1)
{
Console.Error("DEV9: Error calling pcap_compile: %s", pcap_geterr(adhandle));
return -1;
}
if (pcap_setfilter(adhandle, &fp) == -1)
{
Console.Error("DEV9: Error setting filter: %s", pcap_geterr(adhandle));
return -1;
}
}
if (pcap_setnonblock(adhandle, 1, errbuf) == -1)
{
Console.Error("DEV9: Error setting non-blocking: %s", pcap_geterr(adhandle));
Console.Error("DEV9: Continuing in blocking mode");
pcap_io_blocking = true;
}
else
pcap_io_blocking = false;
dlt = pcap_datalink(adhandle);
dlt_name = (char*)pcap_datalink_val_to_name(dlt);
Console.Error("DEV9: Device uses DLT %d: %s", dlt, dlt_name);
switch (dlt)
{
case DLT_EN10MB:
//case DLT_IEEE802_11:
break;
default:
Console.Error("ERROR: Unsupported DataLink Type (%d): %s", dlt, dlt_name);
pcap_close(adhandle);
return -1;
}
#ifdef DEBUG
const std::string plfile(s_strLogPath + "/pkt_log.pcap");
dump_pcap = pcap_dump_open(adhandle, plfile.c_str());
#endif
pcap_io_running = 1;
Console.WriteLn("DEV9: Adapter Ok.");
return 0;
}
#ifdef _WIN32
int gettimeofday(struct timeval* tv, void* tz)
{
unsigned __int64 ns100; /*time since 1 Jan 1601 in 100ns units */
GetSystemTimeAsFileTime((LPFILETIME)&ns100);
tv->tv_usec = (long)((ns100 / 10L) % 1000000L);
tv->tv_sec = (long)((ns100 - 116444736000000000L) / 10000000L);
return (0);
}
#endif
int pcap_io_send(void* packet, int plen)
{
if (pcap_io_running <= 0)
return -1;
if (!pcap_io_switched)
{
if (((ethernet_header*)packet)->protocol == 0x0008) //IP
{
ps2_ip = ((ip_header*)((u8*)packet + sizeof(ethernet_header)))->src;
}
if (((ethernet_header*)packet)->protocol == 0x0608) //ARP
{
ps2_ip = ((arp_packet*)((u8*)packet + sizeof(ethernet_header)))->p_src;
((arp_packet*)((u8*)packet + sizeof(ethernet_header)))->h_src = host_mac;
}
((ethernet_header*)packet)->src = host_mac;
}
if (dump_pcap)
{
static struct pcap_pkthdr ph;
gettimeofday(&ph.ts, NULL);
ph.caplen = plen;
ph.len = plen;
pcap_dump((u_char*)dump_pcap, &ph, (u_char*)packet);
}
return pcap_sendpacket(adhandle, (u_char*)packet, plen);
}
int pcap_io_recv(void* packet, int max_len)
{
static struct pcap_pkthdr* header;
static const u_char* pkt_data1;
if (pcap_io_running <= 0)
return -1;
if ((pcap_next_ex(adhandle, &header, &pkt_data1)) > 0)
{
if ((int)header->len > max_len)
return -1;
memcpy(packet, pkt_data1, header->len);
if (!pcap_io_switched)
{
{
if (((ethernet_header*)packet)->protocol == 0x0008)
{
ip_header* iph = ((ip_header*)((u8*)packet + sizeof(ethernet_header)));
if (ip_compare(iph->dst, ps2_ip) == 0)
{
((ethernet_header*)packet)->dst = ps2_mac;
}
}
if (((ethernet_header*)packet)->protocol == 0x0608)
{
arp_packet* aph = ((arp_packet*)((u8*)packet + sizeof(ethernet_header)));
if (ip_compare(aph->p_dst, ps2_ip) == 0)
{
((ethernet_header*)packet)->dst = ps2_mac;
((arp_packet*)((u8*)packet + sizeof(ethernet_header)))->h_dst = ps2_mac;
}
}
}
}
if (dump_pcap)
pcap_dump((u_char*)dump_pcap, header, (u_char*)packet);
return header->len;
}
return -1;
}
void pcap_io_close()
{
if (dump_pcap)
pcap_dump_close(dump_pcap);
if (adhandle)
pcap_close(adhandle);
pcap_io_running = 0;
}
using namespace PacketReader;
using namespace PacketReader::ARP;
using namespace PacketReader::IP;
PCAPAdapter::PCAPAdapter()
: NetAdapter()
@@ -251,44 +55,62 @@ PCAPAdapter::PCAPAdapter()
return;
#endif
AdapterUtils::Adapter adapter;
AdapterUtils::AdapterBuffer buffer;
if (AdapterUtils::GetAdapter(EmuConfig.DEV9.EthDevice, &adapter, &buffer))
{
std::optional<PacketReader::MAC_Address> adMAC = AdapterUtils::GetAdapterMAC(&adapter);
if (adMAC.has_value())
{
mac_address hostMAC = *(mac_address*)&adMAC.value();
mac_address newMAC;
memcpy(&newMAC, &ps2MAC, 6);
#ifdef _WIN32
std::string pcapAdapter = PCAPPREFIX + EmuConfig.DEV9.EthDevice;
#else
std::string pcapAdapter = EmuConfig.DEV9.EthDevice;
#endif
//Lets take the hosts last 2 bytes to make it unique on Xlink
newMAC.bytes[5] = hostMAC.bytes[4];
newMAC.bytes[4] = hostMAC.bytes[5];
switched = EmuConfig.DEV9.EthApi == Pcsx2Config::DEV9Options::NetApi::PCAP_Switched;
SetMACAddress((PacketReader::MAC_Address*)&newMAC);
host_mac = hostMAC;
ps2_mac = newMAC; //Needed outside of this class
}
else
{
Console.Error("DEV9: Failed to get MAC address for adapter");
return;
}
}
else
{
Console.Error("DEV9: Failed to get adapter information");
return;
}
if (pcap_io_init(EmuConfig.DEV9.EthDevice, EmuConfig.DEV9.EthApi == Pcsx2Config::DEV9Options::NetApi::PCAP_Switched, ps2_mac) == -1)
if (!InitPCAP(pcapAdapter, switched))
{
Console.Error("DEV9: Can't open Device '%s'", EmuConfig.DEV9.EthDevice.c_str());
return;
}
InitInternalServer(&adapter);
AdapterUtils::Adapter adapter;
AdapterUtils::AdapterBuffer buffer;
std::optional<MAC_Address> adMAC = std::nullopt;
const bool foundAdapter = AdapterUtils::GetAdapter(EmuConfig.DEV9.EthDevice, &adapter, &buffer);
if (foundAdapter)
adMAC = AdapterUtils::GetAdapterMAC(&adapter);
else
Console.Error("DEV9: Failed to get adapter information");
if (adMAC.has_value())
{
hostMAC = adMAC.value();
MAC_Address newMAC = ps2MAC;
//Lets take the hosts last 2 bytes to make it unique on Xlink
newMAC.bytes[5] = hostMAC.bytes[4];
newMAC.bytes[4] = hostMAC.bytes[5];
SetMACAddress(&newMAC);
}
else if (switched)
Console.Error("DEV9: Failed to get MAC address for adapter, proceeding with hardcoded MAC address");
else
{
Console.Error("DEV9: Failed to get MAC address for adapter");
pcap_close(hpcap);
hpcap = nullptr;
return;
}
if (switched && !SetMACSwitchedFilter(ps2MAC))
{
pcap_close(hpcap);
hpcap = nullptr;
Console.Error("DEV9: Can't open Device '%s'", EmuConfig.DEV9.EthDevice.c_str());
return;
}
if (foundAdapter)
InitInternalServer(&adapter);
else
InitInternalServer(nullptr);
}
AdapterOptions PCAPAdapter::GetAdapterOptions()
{
@@ -296,43 +118,69 @@ AdapterOptions PCAPAdapter::GetAdapterOptions()
}
bool PCAPAdapter::blocks()
{
pxAssert(pcap_io_running);
return pcap_io_blocking;
pxAssert(hpcap);
return blocking;
}
bool PCAPAdapter::isInitialised()
{
return !!pcap_io_running;
return hpcap != nullptr;
}
//gets a packet.rv :true success
bool PCAPAdapter::recv(NetPacket* pkt)
{
if (!pcap_io_blocking && NetAdapter::recv(pkt))
pxAssert(hpcap);
if (!blocking && NetAdapter::recv(pkt))
return true;
int size = pcap_io_recv(pkt->buffer, sizeof(pkt->buffer));
if (size > 0 && VerifyPkt(pkt, size))
pcap_pkthdr* header;
const u_char* pkt_data;
// pcap bridged will pick up packets not intended for us, returning false on those packets will incur a 1ms wait.
// This delays getting packets we need, so instead loop untill a valid packet, or no packet, is returned from pcap_next_ex.
while (pcap_next_ex(hpcap, &header, &pkt_data) > 0)
{
InspectRecv(pkt);
return true;
if (header->len > sizeof(pkt->buffer))
{
Console.Error("DEV9: Dropped jumbo frame of size: %u", header->len);
continue;
}
pxAssert(header->len == header->caplen);
memcpy(pkt->buffer, pkt_data, header->len);
pkt->size = (int)header->len;
if (!switched)
SetMACBridgedRecv(pkt);
if (VerifyPkt(pkt, header->len))
{
InspectRecv(pkt);
return true;
}
// continue.
}
else
return false;
return false;
}
//sends the packet .rv :true success
bool PCAPAdapter::send(NetPacket* pkt)
{
pxAssert(hpcap);
InspectSend(pkt);
if (NetAdapter::send(pkt))
return true;
if (pcap_io_send(pkt->buffer, pkt->size))
{
// TODO: loopback broadcast packets to host pc in switched mode.
if (!switched)
SetMACBridgedSend(pkt);
if (pcap_sendpacket(hpcap, (u_char*)pkt->buffer, pkt->size))
return false;
}
else
{
return true;
}
}
void PCAPAdapter::reloadSettings()
@@ -347,7 +195,11 @@ void PCAPAdapter::reloadSettings()
PCAPAdapter::~PCAPAdapter()
{
pcap_io_close();
if (hpcap)
{
pcap_close(hpcap);
hpcap = nullptr;
}
}
std::vector<AdapterEntry> PCAPAdapter::GetAdapters()
@@ -359,6 +211,7 @@ std::vector<AdapterEntry> PCAPAdapter::GetAdapters()
return nic;
#endif
char errbuf[PCAP_ERRBUF_SIZE];
pcap_if_t* alldevs;
pcap_if_t* d;
@@ -414,3 +267,117 @@ std::vector<AdapterEntry> PCAPAdapter::GetAdapters()
return nic;
}
// Opens device for capture and sets non-blocking.
bool PCAPAdapter::InitPCAP(const std::string& adapter, bool promiscuous)
{
char errbuf[PCAP_ERRBUF_SIZE];
Console.WriteLn("DEV9: Opening adapter '%s'...", adapter.c_str());
// Open the adapter.
if ((hpcap = pcap_open_live(adapter.c_str(), // Name of the device.
65536, // portion of the packet to capture.
// 65536 grants that the whole packet will be captured on all the MACs.
promiscuous ? 1 : 0,
1, // Read timeout.
errbuf // Error buffer.
)) == nullptr)
{
Console.Error("DEV9: %s", errbuf);
Console.Error("DEV9: Unable to open the adapter. %s is not supported by pcap", adapter.c_str());
return false;
}
if (pcap_setnonblock(hpcap, 1, errbuf) == -1)
{
Console.Error("DEV9: Error setting non-blocking: %s", pcap_geterr(hpcap));
Console.Error("DEV9: Continuing in blocking mode");
blocking = true;
}
else
blocking = false;
// Validate.
const int dlt = pcap_datalink(hpcap);
const char* dlt_name = pcap_datalink_val_to_name(dlt);
Console.Error("DEV9: Device uses DLT %d: %s", dlt, dlt_name);
switch (dlt)
{
case DLT_EN10MB:
//case DLT_IEEE802_11:
break;
default:
Console.Error("ERROR: Unsupported DataLink Type (%d): %s", dlt, dlt_name);
pcap_close(hpcap);
hpcap = nullptr;
return false;
}
Console.WriteLn("DEV9: Adapter Ok.");
return true;
}
bool PCAPAdapter::SetMACSwitchedFilter(MAC_Address mac)
{
bpf_program fp;
char filter[1024] = "ether broadcast or ether dst ";
char virtual_mac_str[18];
sprintf(virtual_mac_str, "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x", mac.bytes[0], mac.bytes[1], mac.bytes[2], mac.bytes[3], mac.bytes[4], mac.bytes[5]);
strcat(filter, virtual_mac_str);
if (pcap_compile(hpcap, &fp, filter, 1, PCAP_NETMASK_UNKNOWN) == -1)
{
Console.Error("DEV9: Error calling pcap_compile: %s", pcap_geterr(hpcap));
return false;
}
int setFilterRet;
if ((setFilterRet = pcap_setfilter(hpcap, &fp)) == -1)
Console.Error("DEV9: Error setting filter: %s", pcap_geterr(hpcap));
pcap_freecode(&fp);
return setFilterRet != -1;
}
void PCAPAdapter::SetMACBridgedRecv(NetPacket* pkt)
{
EthernetFrameEditor frame(pkt);
if (frame.GetProtocol() == (u16)EtherType::IPv4) // IP
{
// Compare DEST IP in IP with the PS2's IP, if they match, change DEST MAC to ps2MAC.
PayloadPtr* payload = frame.GetPayload();
IP_Packet ippkt(payload->data, payload->GetLength());
if (ippkt.destinationIP == ps2IP)
frame.SetDestinationMAC(ps2MAC);
}
if (frame.GetProtocol() == (u16)EtherType::ARP) // ARP
{
// Compare DEST IP in ARP with the PS2's IP, if they match, DEST MAC to ps2MAC on both ARP and ETH Packet headers.
ARP_PacketEditor arpPkt(frame.GetPayload());
if (*(IP_Address*)arpPkt.TargetProtocolAddress() == ps2IP)
{
frame.SetDestinationMAC(ps2MAC);
*(MAC_Address*)arpPkt.TargetHardwareAddress() = ps2MAC;
}
}
}
void PCAPAdapter::SetMACBridgedSend(NetPacket* pkt)
{
EthernetFrameEditor frame(pkt);
if (frame.GetProtocol() == (u16)EtherType::IPv4) // IP
{
PayloadPtr* payload = frame.GetPayload();
IP_Packet ippkt(payload->data, payload->GetLength());
ps2IP = ippkt.sourceIP;
}
if (frame.GetProtocol() == (u16)EtherType::ARP) // ARP
{
ARP_PacketEditor arpPkt(frame.GetPayload());
ps2IP = *(IP_Address*)arpPkt.SenderProtocolAddress();
*(MAC_Address*)arpPkt.SenderHardwareAddress() = hostMAC;
}
frame.SetSourceMAC(hostMAC);
}

View File

@@ -16,163 +16,24 @@
#pragma once
#include "pcap.h"
#include "net.h"
#ifdef __cplusplus
extern "C" {
#endif
//#ifndef _WIN32
#pragma pack(push, 1)
typedef struct _ip_address
{
u_char bytes[4];
} ip_address;
typedef struct _mac_address
{
u_char bytes[6];
} mac_address;
typedef struct _ethernet_header
{
mac_address dst;
mac_address src;
u_short protocol;
} ethernet_header;
typedef struct _arp_packet
{
u_short hw_type;
u_short protocol;
u_char h_addr_len;
u_char p_addr_len;
u_short operation;
mac_address h_src;
ip_address p_src;
mac_address h_dst;
ip_address p_dst;
} arp_packet;
typedef struct _ip_header
{
u_char ver_hlen; /* version << 4 | header length >> 2 */
u_char type; /* type of service */
u_short len; /* total length */
u_short id; /* identification */
u_short offset; /* fragment offset field */
u_char ttl; /* time to live */
u_char proto; /* protocol */
u_short hdr_csum; /* checksum */
ip_address src; /* source and dest address */
ip_address dst;
} ip_header;
/* Internet Control Message Protocol Constants and Packet Format */
/* ic_type field */
#define ICT_ECHORP 0 /* Echo reply */
#define ICT_DESTUR 3 /* Destination unreachable */
#define ICT_SRCQ 4 /* Source quench */
#define ICT_REDIRECT 5 /* Redirect message type */
#define ICT_ECHORQ 8 /* Echo request */
#define ICT_TIMEX 11 /* Time exceeded */
#define ICT_PARAMP 12 /* Parameter Problem */
#define ICT_TIMERQ 13 /* Timestamp request */
#define ICT_TIMERP 14 /* Timestamp reply */
#define ICT_INFORQ 15 /* Information request */
#define ICT_INFORP 16 /* Information reply */
#define ICT_MASKRQ 17 /* Mask request */
#define ICT_MASKRP 18 /* Mask reply */
/* ic_code field */
#define ICC_NETUR 0 /* dest unreachable, net unreachable */
#define ICC_HOSTUR 1 /* dest unreachable, host unreachable */
#define ICC_PROTOUR 2 /* dest unreachable, proto unreachable */
#define ICC_PORTUR 3 /* dest unreachable, port unreachable */
#define ICC_FNADF 4 /* dest unr, frag needed & don't frag */
#define ICC_SRCRT 5 /* dest unreachable, src route failed */
#define ICC_NETRD 0 /* redirect: net */
#define ICC_HOSTRD 1 /* redirect: host */
#define IC_TOSNRD 2 /* redirect: type of service, net */
#define IC_TOSHRD 3 /* redirect: type of service, host */
#define ICC_TIMEX 0 /* time exceeded, ttl */
#define ICC_FTIMEX 1 /* time exceeded, frag */
#define IC_HLEN 8 /* octets */
#define IC_PADLEN 3 /* pad length (octets) */
#define IC_RDTTL 300 /* ttl for redirect routes */
/* ICMP packet format (following the IP header) */
typedef struct _icmp_header
{ /* ICMP packet */
char type; /* type of message (ICT_* above)*/
char code; /* code (ICC_* above) */
short csum; /* checksum of ICMP header+data */
union
{
struct
{
int ic1_id : 16; /* echo type, a message id */
int ic1_seq : 16; /* echo type, a seq. number */
} ic1;
ip_address ic2_gw; /* for redirect, gateway */
struct
{
char ic3_ptr; /* pointer, for ICT_PARAMP */
char ic3_pad[IC_PADLEN];
} ic3;
int ic4_mbz; /* must be zero */
} icu;
} icmp_header;
/*typedef struct _udp_header {
u16 src_port;
u16 dst_port;
u16 len;
u16 csum;
} udp_header;*/
typedef struct _full_arp_packet
{
ethernet_header header;
arp_packet arp;
} full_arp_packet;
#pragma pack(pop)
#define ARP_REQUEST 0x0100 //values are big-endian
#define mac_compare(a, b) (memcmp(&(a), &(b), 6))
#define ip_compare(a, b) (memcmp(&(a), &(b), 4))
//#endif
/*
int pcap_io_init(char *adapter);
int pcap_io_send(void* packet, int plen);
int pcap_io_recv(void* packet, int max_len);
void pcap_io_close();
int pcap_io_get_dev_num();
char* pcap_io_get_dev_desc(int num);
char* pcap_io_get_dev_name(int num);
*/
#include "PacketReader/MAC_Address.h"
#ifdef _WIN32
bool load_pcap();
void unload_pcap();
#endif
#ifdef __cplusplus
}
#endif
class PCAPAdapter : public NetAdapter
{
private:
pcap_t* hpcap = nullptr;
bool switched;
bool blocking;
PacketReader::IP::IP_Address ps2IP{};
PacketReader::MAC_Address hostMAC;
public:
PCAPAdapter();
virtual bool blocks();
@@ -185,4 +46,12 @@ public:
virtual ~PCAPAdapter();
static std::vector<AdapterEntry> GetAdapters();
static AdapterOptions GetAdapterOptions();
private:
bool InitPCAP(const std::string& adapter, bool promiscuous);
void InitPCAPDumper();
bool SetMACSwitchedFilter(PacketReader::MAC_Address mac);
void SetMACBridgedRecv(NetPacket* pkt);
void SetMACBridgedSend(NetPacket* pkt);
};

View File

@@ -15,29 +15,70 @@
#include "PrecompiledHeader.h"
#include "BiosDebugData.h"
#include "IopMem.h"
#include "Memory.h"
std::vector<EEThread> getEEThreads()
{
std::vector<EEThread> threads;
if (CurrentBiosInformation.threadListAddr <= 0)
std::vector<std::unique_ptr<BiosThread>> getEEThreads()
{
std::vector<std::unique_ptr<BiosThread>> threads;
if (CurrentBiosInformation.eeThreadListAddr <= 0)
return threads;
const u32 start = CurrentBiosInformation.threadListAddr & 0x3fffff;
const u32 start = CurrentBiosInformation.eeThreadListAddr & 0x3fffff;
for (int tid = 0; tid < 256; tid++)
{
EEThread thread;
EEInternalThread* internal = static_cast<EEInternalThread*>(PSM(start + tid * sizeof(EEInternalThread)));
if (internal->status != THS_BAD)
if (internal->status != (int)ThreadStatus::THS_BAD)
{
thread.tid = tid;
thread.data = *internal;
threads.push_back(thread);
auto thread = std::make_unique<EEThread>(tid, *internal);
threads.push_back(std::move(thread));
}
}
return threads;
}
std::vector<std::unique_ptr<BiosThread>> getIOPThreads()
{
std::vector<std::unique_ptr<BiosThread>> threads;
if (CurrentBiosInformation.iopThreadListAddr <= 0)
return threads;
u32 item = iopMemRead32(CurrentBiosInformation.iopThreadListAddr);
while (item != 0)
{
IOPInternalThread data{};
u16 tag = iopMemRead16(item + 0x8);
if (tag != 0x7f01)
{
// something went wrong
return {};
}
data.stackTop = iopMemRead32(item + 0x3c);
data.status = iopMemRead8(item + 0xc);
data.tid = iopMemRead16(item + 0xa);
data.entrypoint = iopMemRead32(item + 0x38);
data.waitstate = iopMemRead16(item + 0xe);
data.initPriority = iopMemRead16(item + 0x2e);
data.SavedSP = iopMemRead32(item + 0x10);
data.PC = iopMemRead32(data.SavedSP + 0x8c);
auto thread = std::make_unique<IOPThread>(data);
threads.push_back(std::move(thread));
item = iopMemRead32(item + 0x24);
}
return threads;
}

View File

@@ -15,9 +15,24 @@
#pragma once
#include "common/Pcsx2Types.h"
#include <memory>
#include <vector>
#include "ps2/BiosTools.h"
struct EEInternalThread { // internal struct
enum class ThreadStatus
{
THS_BAD = 0x00,
THS_RUN = 0x01,
THS_READY = 0x02,
THS_WAIT = 0x04,
THS_SUSPEND = 0x08,
THS_WAIT_SUSPEND = 0x0C,
THS_DORMANT = 0x10,
};
struct EEInternalThread
{ // internal struct
u32 prev;
u32 next;
int status;
@@ -40,26 +55,140 @@ struct EEInternalThread { // internal struct
u32 heap_base;
};
enum {
THS_BAD = 0x00,
THS_RUN = 0x01,
THS_READY = 0x02,
THS_WAIT = 0x04,
THS_SUSPEND = 0x08,
THS_WAIT_SUSPEND = 0x0C,
THS_DORMANT = 0x10,
};
enum {
WAIT_NONE = 0,
WAIT_WAKEUP_REQ = 1,
WAIT_SEMA = 2,
};
struct EEThread
// Not the full struct, just what we care about
struct IOPInternalThread
{
int tid;
u32 tid;
u32 PC;
u32 stackTop;
u32 SavedSP;
u32 status;
u32 entrypoint;
u32 waitstate;
u32 initPriority;
};
enum class IOPWaitStatus
{
TSW_SLEEP = 1,
TSW_DELAY = 2,
TSW_SEMA = 3,
TSW_EVENTFLAG = 4,
TSW_MBX = 5,
TSW_VPL = 6,
TSW_FPL = 7,
};
enum class EEWaitStatus
{
WAIT_NONE = 0,
WAIT_WAKEUP_REQ = 1,
WAIT_SEMA = 2,
};
enum class WaitState
{
NONE,
WAKEUP_REQ,
SEMA,
SLEEP,
DELAY,
EVENTFLAG,
MBOX,
VPOOL,
FIXPOOL,
};
class BiosThread
{
public:
virtual ~BiosThread() = default;
[[nodiscard]] virtual u32 TID() const = 0;
[[nodiscard]] virtual u32 PC() const = 0;
[[nodiscard]] virtual ThreadStatus Status() const = 0;
[[nodiscard]] virtual WaitState Wait() const = 0;
[[nodiscard]] virtual u32 EntryPoint() const = 0;
[[nodiscard]] virtual u32 StackTop() const = 0;
[[nodiscard]] virtual u32 Priority() const = 0;
};
class EEThread : public BiosThread
{
public:
EEThread(int tid, EEInternalThread th)
: tid(tid)
, data(th)
{
}
~EEThread() override = default;
[[nodiscard]] u32 TID() const override { return tid; };
[[nodiscard]] u32 PC() const override { return data.entry; };
[[nodiscard]] ThreadStatus Status() const override { return static_cast<ThreadStatus>(data.status); };
[[nodiscard]] WaitState Wait() const override
{
auto wait = static_cast<EEWaitStatus>(data.waitType);
switch (wait)
{
case EEWaitStatus::WAIT_NONE:
return WaitState::NONE;
case EEWaitStatus::WAIT_WAKEUP_REQ:
return WaitState::WAKEUP_REQ;
case EEWaitStatus::WAIT_SEMA:
return WaitState::SEMA;
}
return WaitState::NONE;
};
[[nodiscard]] u32 EntryPoint() const override { return data.entry_init; };
[[nodiscard]] u32 StackTop() const override { return data.stack; };
[[nodiscard]] u32 Priority() const override { return data.currentPriority; };
private:
u32 tid;
EEInternalThread data;
};
std::vector<EEThread> getEEThreads();
class IOPThread : public BiosThread
{
public:
IOPThread(IOPInternalThread th)
: data(th)
{
}
~IOPThread() override = default;
[[nodiscard]] u32 TID() const override { return data.tid; };
[[nodiscard]] u32 PC() const override { return data.PC; };
[[nodiscard]] ThreadStatus Status() const override { return static_cast<ThreadStatus>(data.status); };
[[nodiscard]] WaitState Wait() const override
{
auto wait = static_cast<IOPWaitStatus>(data.waitstate);
switch (wait)
{
case IOPWaitStatus::TSW_DELAY:
return WaitState::DELAY;
case IOPWaitStatus::TSW_EVENTFLAG:
return WaitState::EVENTFLAG;
case IOPWaitStatus::TSW_SLEEP:
return WaitState::SLEEP;
case IOPWaitStatus::TSW_SEMA:
return WaitState::SEMA;
case IOPWaitStatus::TSW_MBX:
return WaitState::MBOX;
case IOPWaitStatus::TSW_VPL:
return WaitState::VPOOL;
case IOPWaitStatus::TSW_FPL:
return WaitState::FIXPOOL;
}
return WaitState::NONE;
};
[[nodiscard]] u32 EntryPoint() const override { return data.entrypoint; };
[[nodiscard]] u32 StackTop() const override { return data.stackTop; };
[[nodiscard]] u32 Priority() const override { return data.initPriority; };
private:
IOPInternalThread data;
};
std::vector<std::unique_ptr<BiosThread>> getIOPThreads();
std::vector<std::unique_ptr<BiosThread>> getEEThreads();

View File

@@ -728,6 +728,12 @@ SymbolMap& R5900DebugInterface::GetSymbolMap() const
return R5900SymbolMap;
}
std::vector<std::unique_ptr<BiosThread>> R5900DebugInterface::GetThreadList() const
{
return getEEThreads();
}
//
// R3000DebugInterface
//
@@ -1000,3 +1006,8 @@ SymbolMap& R3000DebugInterface::GetSymbolMap() const
{
return R3000SymbolMap;
}
std::vector<std::unique_ptr<BiosThread>> R3000DebugInterface::GetThreadList() const
{
return getIOPThreads();
}

View File

@@ -14,6 +14,7 @@
*/
#pragma once
#include "DebugTools/BiosDebugData.h"
#include "MemoryTypes.h"
#include "ExpressionParser.h"
#include "SymbolMap.h"
@@ -84,6 +85,7 @@ public:
virtual u32 getCycles() = 0;
virtual BreakPointCpu getCpuType() = 0;
[[nodiscard]] virtual SymbolMap& GetSymbolMap() const = 0;
[[nodiscard]] virtual std::vector<std::unique_ptr<BiosThread>> GetThreadList() const = 0;
bool initExpression(const char* exp, PostfixExpression& dest);
bool parseExpression(PostfixExpression& exp, u64& dest);
@@ -130,6 +132,7 @@ public:
void setPc(u32 newPc) override;
void setRegister(int cat, int num, u128 newValue) override;
[[nodiscard]] SymbolMap& GetSymbolMap() const override;
[[nodiscard]] std::vector<std::unique_ptr<BiosThread>> GetThreadList() const override;
std::string disasm(u32 address, bool simplify) override;
bool isValidAddress(u32 address) override;
@@ -168,6 +171,7 @@ public:
void setPc(u32 newPc) override;
void setRegister(int cat, int num, u128 newValue) override;
[[nodiscard]] SymbolMap& GetSymbolMap() const override;
[[nodiscard]] std::vector<std::unique_ptr<BiosThread>> GetThreadList() const override;
std::string disasm(u32 address, bool simplify) override;
bool isValidAddress(u32 address) override;

View File

@@ -105,6 +105,7 @@ namespace InputManager
static bool ParseBindingAndGetSource(const std::string_view& binding, InputBindingKey* key, InputSource** source);
static bool IsAxisHandler(const InputEventHandler& handler);
static float ApplySingleBindingScale(float sensitivity, float deadzone, float value);
static void AddHotkeyBindings(SettingsInterface& si);
static void AddPadBindings(SettingsInterface& si, u32 pad, const char* default_type);
@@ -585,6 +586,12 @@ std::string InputManager::GetPointerDeviceName(u32 pointer_index)
// Binding Enumeration
// ------------------------------------------------------------------------
float InputManager::ApplySingleBindingScale(float scale, float deadzone, float value)
{
const float svalue = std::clamp(value * scale, 0.0f, 1.0f);
return (deadzone > 0.0f && svalue < deadzone) ? 0.0f : svalue;
}
std::vector<const HotkeyInfo*> InputManager::GetHotkeyList()
{
std::vector<const HotkeyInfo*> ret;
@@ -613,7 +620,7 @@ void InputManager::AddHotkeyBindings(SettingsInterface& si)
void InputManager::AddPadBindings(SettingsInterface& si, u32 pad_index, const char* default_type)
{
const std::string section(StringUtil::StdStringFromFormat("Pad%u", pad_index + 1));
const std::string section(fmt::format("Pad{}", pad_index + 1));
const std::string type(si.GetStringValue(section.c_str(), "Type", default_type));
if (type.empty() || type == "None")
return;
@@ -636,9 +643,12 @@ void InputManager::AddPadBindings(SettingsInterface& si, u32 pad_index, const ch
if (!bindings.empty())
{
// we use axes for all pad bindings to simplify things, and because they are pressure sensitive
AddBindings(bindings, InputAxisEventHandler{[pad_index, bind_index = bi.bind_index](float value) {
PAD::SetControllerState(pad_index, bind_index, value);
}});
const float sensitivity = si.GetFloatValue(section.c_str(), fmt::format("{}Scale", bi.name).c_str(), 1.0f);
const float deadzone = si.GetFloatValue(section.c_str(), fmt::format("{}Deadzone", bi.name).c_str(), 0.0f);
AddBindings(
bindings, InputAxisEventHandler{[pad_index, bind_index = bi.bind_index, sensitivity, deadzone](float value) {
PAD::SetControllerState(pad_index, bind_index, ApplySingleBindingScale(sensitivity, deadzone, value));
}});
}
}
break;
@@ -717,8 +727,11 @@ void InputManager::AddUSBBindings(SettingsInterface& si, u32 port)
const std::vector<std::string> bindings(si.GetStringList(section.c_str(), bind_name.c_str()));
if (!bindings.empty())
{
AddBindings(bindings, InputAxisEventHandler{[port, bind_index = bi.bind_index](
float value) { USB::SetDeviceBindValue(port, bind_index, value); }});
const float sensitivity = si.GetFloatValue(section.c_str(), fmt::format("{}Scale", bi.name).c_str(), 1.0f);
const float deadzone = si.GetFloatValue(section.c_str(), fmt::format("{}Deadzone", bi.name).c_str(), 0.0f);
AddBindings(bindings, InputAxisEventHandler{[port, bind_index = bi.bind_index, sensitivity, deadzone](float value) {
USB::SetDeviceBindValue(port, bind_index, ApplySingleBindingScale(sensitivity, deadzone, value));
}});
}
}
break;

View File

@@ -32,19 +32,15 @@ void gsSetVideoMode(GS_VideoMode mode)
{
gsVideoMode = mode;
UpdateVSyncRate();
CSRreg.FIELD = 1;
}
// Make sure framelimiter options are in sync with GS capabilities.
void gsReset()
{
GetMTGS().ResetGS(true);
UpdateVSyncRate();
gsVideoMode = GS_VideoMode::Uninitialized;
memzero(g_RealGSMem);
CSRreg.Reset();
GSIMR.reset();
UpdateVSyncRate();
}
void gsUpdateFrequency(Pcsx2Config& config)
@@ -85,11 +81,14 @@ static __fi void gsCSRwrite( const tGS_CSR& csr )
//Console.Warning( "csr.RESET" );
//gifUnit.Reset(true); // Don't think gif should be reset...
gifUnit.gsSIGNAL.queued = false;
GetMTGS().SendSimplePacket(GS_RINGTYPE_RESET, 0, 0, 0);
const u32 field = CSRreg.FIELD;
CSRreg.Reset();
gifUnit.gsFINISH.gsFINISHFired = true;
// Privilage registers also reset.
memzero(g_RealGSMem);
GSIMR.reset();
CSRreg.FIELD = field;
CSRreg.Reset();
gsVideoMode = GS_VideoMode::Uninitialized;
UpdateVSyncRate();
GetMTGS().SendSimplePacket(GS_RINGTYPE_RESET, 0, 0, 0);
}
if(csr.FLUSH)

View File

@@ -153,7 +153,6 @@ union tGS_CSR
void Reset()
{
_u64 = 0;
FIFO = CSR_FIFO_EMPTY;
REV = 0x1B; // GS Revision
ID = 0x55; // GS ID

View File

@@ -149,6 +149,15 @@ bool GSClut::InvalidateRange(u32 start_block, u32 end_block, bool is_draw)
GIFRegTEX0 next_cbp;
next_cbp.U64 = m_write.next_tex0;
// Handle wrapping writes. Star Wars Battlefront 2 does this.
if ((end_block & 0xFFE0) < (start_block & 0xFFE0))
{
if ((next_cbp.CBP + 3U) <= end_block)
next_cbp.CBP += 0x4000;
end_block += 0x4000;
}
if ((next_cbp.CBP + 3U) >= start_block && end_block >= next_cbp.CBP)
{
m_write.dirty |= is_draw ? 2 : 1;

View File

@@ -130,7 +130,7 @@ GIFRegTEX0 GSDrawingContext::GetSizeFixedTEX0(const GSVector4& st, bool linear,
res.TW = tw > 10 ? 0 : tw;
res.TH = th > 10 ? 0 : th;
if (GSConfig.Renderer == GSRendererType::SW && (TEX0.TW != res.TW || TEX0.TH != res.TH))
if (TEX0.TW != res.TW || TEX0.TH != res.TH)
{
GL_DBG("FixedTEX0 %05x %d %d tw %d=>%d th %d=>%d st (%.0f,%.0f,%.0f,%.0f) uvmax %d,%d wm %d,%d (%d,%d,%d,%d)",
(int)TEX0.TBP0, (int)TEX0.TBW, (int)TEX0.PSM,
@@ -142,50 +142,3 @@ GIFRegTEX0 GSDrawingContext::GetSizeFixedTEX0(const GSVector4& st, bool linear,
return res;
}
void GSDrawingContext::ComputeFixedTEX0(const GSVector4& st)
{
// It is quite complex to handle rescaling so this function is less stricter than GetSizeFixedTEX0,
// therefore we remove the reduce optimization and we don't handle bilinear filtering which might create wrong interpolation at the border.
int tw = TEX0.TW;
int th = TEX0.TH;
int wms = (int)CLAMP.WMS;
int wmt = (int)CLAMP.WMT;
int minu = (int)CLAMP.MINU;
int minv = (int)CLAMP.MINV;
int maxu = (int)CLAMP.MAXU;
int maxv = (int)CLAMP.MAXV;
if (wms != CLAMP_REGION_CLAMP)
tw = tw > 10 ? 0 : tw;
if (wmt != CLAMP_REGION_CLAMP)
th = th > 10 ? 0 : th;
GSVector4i uv = GSVector4i(st.floor().xyzw(st.ceil()));
uv.x = findmax(uv.x, uv.z, (1 << tw) - 1, wms, minu, maxu);
uv.y = findmax(uv.y, uv.w, (1 << th) - 1, wmt, minv, maxv);
if (wms == CLAMP_REGION_CLAMP || wms == CLAMP_REGION_REPEAT)
tw = extend(uv.x, tw);
if (wmt == CLAMP_REGION_CLAMP || wmt == CLAMP_REGION_REPEAT)
th = extend(uv.y, th);
tw = std::clamp<int>(tw, 0, 10);
th = std::clamp<int>(th, 0, 10);
if ((tw != (int)TEX0.TW) || (th != (int)TEX0.TH))
{
m_fixed_tex0 = true;
TEX0.TW = tw;
TEX0.TH = th;
GL_DBG("FixedTEX0 TW %d=>%d, TH %d=>%d wm %d,%d",
(int)stack.TEX0.TW, (int)TEX0.TW, (int)stack.TEX0.TH, (int)TEX0.TH,
(int)CLAMP.WMS, (int)CLAMP.WMT);
}
}

View File

@@ -69,12 +69,8 @@ public:
GIFRegZBUF ZBUF;
} stack;
bool m_fixed_tex0;
GSDrawingContext()
{
m_fixed_tex0 = false;
memset(&offset, 0, sizeof(offset));
Reset();
@@ -140,8 +136,6 @@ public:
}
GIFRegTEX0 GetSizeFixedTEX0(const GSVector4& st, bool linear, bool mipmap = false) const;
void ComputeFixedTEX0(const GSVector4& st);
bool HasFixedTEX0() const { return m_fixed_tex0; }
// Save & Restore before/after draw allow to correct/optimize current register for current draw
// Note: we could avoid the restore part if all renderer code is updated to use a local copy instead
@@ -159,9 +153,6 @@ public:
stack.FBA = FBA;
stack.FRAME = FRAME;
stack.ZBUF = ZBUF;
// This function is called before the draw so take opportunity to reset m_fixed_tex0
m_fixed_tex0 = false;
}
void RestoreReg()

View File

@@ -823,6 +823,7 @@ union
REG_END2
__forceinline bool IsRepeating() const
{
// This is actually "does the texture span more than one page".
if (TBW < 2)
{
if (PSM == PSM_PSMT8)

View File

@@ -56,11 +56,13 @@ GSState::GSState()
memset(&m_v, 0, sizeof(m_v));
memset(&m_vertex, 0, sizeof(m_vertex));
memset(&m_index, 0, sizeof(m_index));
memset(m_mem.m_vm8, 0, m_mem.m_vmsize);
m_v.RGBAQ.Q = 1.0f;
GrowVertexBuffer();
m_draw_transfers.clear();
m_sssize = 0;
m_sssize += sizeof(m_version);
@@ -140,8 +142,6 @@ void GSState::Reset(bool hardware_reset)
Flush(GSFlushReason::RESET);
// FIXME: bios logo not shown cut in half after reset, missing graphics in GoW after first FMV
if (hardware_reset)
memset(m_mem.m_vm8, 0, m_mem.m_vmsize);
memset(&m_path, 0, sizeof(m_path));
memset(&m_v, 0, sizeof(m_v));
@@ -155,7 +155,7 @@ void GSState::Reset(bool hardware_reset)
m_env.UpdateDIMX();
for (size_t i = 0; i < 2; i++)
for (u32 i = 0; i < 2; i++)
{
m_env.CTXT[i].UpdateScissor();
@@ -563,6 +563,16 @@ int GSState::GetFramebufferHeight()
return frame_memory_height;
}
int GSState::GetFramebufferBitDepth()
{
if (IsEnabled(0))
return GSLocalMemory::m_psm[m_regs->DISP[0].DISPFB.PSM].bpp;
else if (IsEnabled(1))
return GSLocalMemory::m_psm[m_regs->DISP[1].DISPFB.PSM].bpp;
return 32;
}
int GSState::GetFramebufferWidth()
{
const GSVector4i disp1_rect = GetFrameRect(0, true);
@@ -673,14 +683,14 @@ void GSState::DumpVertices(const std::string& filename)
file << std::endl << std::endl;
const size_t count = m_index.tail;
const u32 count = m_index.tail;
GSVertex* buffer = &m_vertex.buff[0];
const char* DEL = ", ";
file << "VERTEX COORDS (XYZ)" << std::endl;
file << std::fixed << std::setprecision(4);
for (size_t i = 0; i < count; ++i)
for (u32 i = 0; i < count; ++i)
{
file << "\t" << "v" << i << ": ";
GSVertex v = buffer[m_index.buff[i]];
@@ -698,7 +708,7 @@ void GSState::DumpVertices(const std::string& filename)
file << "VERTEX COLOR (RGBA)" << std::endl;
file << std::fixed << std::setprecision(6);
for (size_t i = 0; i < count; ++i)
for (u32 i = 0; i < count; ++i)
{
file << "\t" << "v" << i << ": ";
GSVertex v = buffer[m_index.buff[i]];
@@ -716,7 +726,7 @@ void GSState::DumpVertices(const std::string& filename)
const std::string qualifier = use_uv ? "UV" : "STQ";
file << "TEXTURE COORDS (" << qualifier << ")" << std::endl;;
for (size_t i = 0; i < count; ++i)
for (u32 i = 0; i < count; ++i)
{
file << "\t" << "v" << i << ": ";
const GSVertex v = buffer[m_index.buff[i]];
@@ -1062,7 +1072,7 @@ void GSState::ApplyTEX0(GIFRegTEX0& TEX0)
if (TEX0.CBP != m_mem.m_clut.GetCLUTCBP())
{
m_mem.m_clut.ClearDrawInvalidity();
CLUTAutoFlush();
CLUTAutoFlush(PRIM->PRIM);
}
Flush(GSFlushReason::CLUTCHANGE);
}
@@ -1689,7 +1699,6 @@ inline void GSState::CopyEnv(GSDrawingEnvironment* dest, GSDrawingEnvironment* s
{
memcpy(dest, src, 88);
memcpy(&dest->CTXT[ctx], &src->CTXT[ctx], 96);
dest->CTXT[ctx].m_fixed_tex0 = src->CTXT[ctx].m_fixed_tex0;
}
void GSState::Flush(GSFlushReason reason)
@@ -1844,10 +1853,10 @@ void GSState::FlushPrim()
GSVertex buff[2];
s_n++;
const size_t head = m_vertex.head;
const size_t tail = m_vertex.tail;
const size_t next = m_vertex.next;
size_t unused = 0;
const u32 head = m_vertex.head;
const u32 tail = m_vertex.tail;
const u32 next = m_vertex.next;
u32 unused = 0;
if (tail > head)
{
@@ -1864,7 +1873,7 @@ void GSState::FlushPrim()
break;
case GS_TRIANGLELIST:
case GS_TRIANGLESTRIP:
unused = std::min<size_t>(tail - head, 2);
unused = std::min<u32>(tail - head, 2);
memcpy(buff, &m_vertex.buff[tail - unused], sizeof(GSVertex) * 2);
break;
case GS_TRIANGLEFAN:
@@ -1932,7 +1941,7 @@ void GSState::FlushPrim()
// Jak 3 shadows get spikey (with autoflush) if you don't.
if (PRIM->PRIM == GS_TRIANGLEFAN)
{
for (size_t i = 0; i < unused; i++)
for (u32 i = 0; i < unused; i++)
{
GSVector4i* RESTRICT vert_ptr = (GSVector4i*)&m_vertex.buff[i];
GSVector4i v = vert_ptr[1];
@@ -1984,8 +1993,6 @@ void GSState::Write(const u8* mem, int len)
if (!m_tr.Update(w, h, psm.trbpp, len))
return;
GIFRegTEX0& prev_tex0 = m_prev_env.CTXT[m_prev_env.PRIM.CTXT].TEX0;
const u32 write_start_bp = m_mem.m_psm[blit.DPSM].info.bn(m_env.TRXPOS.DSAX, m_env.TRXPOS.DSAY, blit.DBP, blit.DBW); // (m_mem.*psm.pa)(static_cast<int>(m_env.TRXPOS.DSAX), static_cast<int>(m_env.TRXPOS.DSAY), blit.DBP, blit.DBW) >> 6;
@@ -2000,6 +2007,16 @@ void GSState::Write(const u8* mem, int len)
// Invalid the CLUT if it crosses paths.
m_mem.m_clut.InvalidateRange(write_start_bp, write_end_bp);
if (GSConfig.PreloadFrameWithGSData)
{
// Store the transfer for preloading new RT's.
if (m_draw_transfers.size() == 0 || (m_draw_transfers.size() > 0 && blit.DBP != m_draw_transfers.back().blit.DBP))
{
GSUploadQueue new_transfer = { blit, s_n };
m_draw_transfers.push_back(new_transfer);
}
}
GL_CACHE("Write! ... => 0x%x W:%d F:%s (DIR %d%d), dPos(%d %d) size(%d %d)",
blit.DBP, blit.DBW, psm_str(blit.DPSM),
m_env.TRXPOS.DIRX, m_env.TRXPOS.DIRY,
@@ -2146,6 +2163,16 @@ void GSState::Move()
{
Flush(GSFlushReason::LOCALTOLOCALMOVE);
}
if (GSConfig.PreloadFrameWithGSData)
{
// Store the transfer for preloading new RT's.
if (m_draw_transfers.size() == 0 || (m_draw_transfers.size() > 0 && dbp != m_draw_transfers.back().blit.DBP))
{
GSUploadQueue new_transfer = { m_env.BITBLTBUF, s_n };
m_draw_transfers.push_back(new_transfer);
}
}
// Invalid the CLUT if it crosses paths.
m_mem.m_clut.InvalidateRange(write_start_bp, write_end_bp);
@@ -2733,7 +2760,7 @@ int GSState::Defrost(const freezeData* fd)
m_env.UpdateDIMX();
for (size_t i = 0; i < 2; i++)
for (u32 i = 0; i < 2; i++)
{
m_env.CTXT[i].UpdateScissor();
@@ -2802,7 +2829,7 @@ void GSState::UpdateVertexKick()
void GSState::GrowVertexBuffer()
{
const size_t maxcount = std::max<size_t>(m_vertex.maxcount * 3 / 2, 10000);
const u32 maxcount = std::max<u32>(m_vertex.maxcount * 3 / 2, 10000);
GSVertex* vertex = (GSVertex*)_aligned_malloc(sizeof(GSVertex) * maxcount, 32);
// Worst case index list is a list of points with vs expansion, 6 indices per point
@@ -2810,8 +2837,8 @@ void GSState::GrowVertexBuffer()
if (vertex == NULL || index == NULL)
{
const size_t vert_byte_count = sizeof(GSVertex) * maxcount;
const size_t idx_byte_count = sizeof(u32) * maxcount * 3;
const u32 vert_byte_count = sizeof(GSVertex) * maxcount;
const u32 idx_byte_count = sizeof(u32) * maxcount * 3;
Console.Error("GS: failed to allocate %zu bytes for verticles and %zu for indices.",
vert_byte_count, idx_byte_count);
@@ -2849,12 +2876,12 @@ GSState::PRIM_OVERLAP GSState::PrimitiveOverlap()
return PRIM_OVERLAP_UNKNOW; // maybe, maybe not
// Check intersection of sprite primitive only
const size_t count = m_vertex.next;
const u32 count = m_vertex.next;
PRIM_OVERLAP overlap = PRIM_OVERLAP_NO;
const GSVertex* v = m_vertex.buff;
m_drawlist.clear();
size_t i = 0;
u32 i = 0;
while (i < count)
{
// In order to speed up comparison a bounding-box is accumulated. It removes a
@@ -2868,7 +2895,7 @@ GSState::PRIM_OVERLAP GSState::PrimitiveOverlap()
GSVector4i all = GSVector4i(v[i].m[1]).upl16(GSVector4i(v[i + 1].m[1])).upl16().xzyw();
all = all.xyxy().blend(all.zwzw(), all > all.zwxy());
size_t j = i + 2;
u32 j = i + 2;
while (j < count)
{
GSVector4i sprite = GSVector4i(v[j].m[1]).upl16(GSVector4i(v[j + 1].m[1])).upl16().xzyw();
@@ -2906,12 +2933,12 @@ GSState::PRIM_OVERLAP GSState::PrimitiveOverlap()
// Some safe-guard will be added in the outer-loop to avoid corruption with a limited perf impact
if (v[1].XYZ.Y < v[0].XYZ.Y) {
// First vertex is Top-Left
for (size_t i = 0; i < count; i += 2) {
for (u32 i = 0; i < count; i += 2) {
if (v[i + 1].XYZ.Y > v[i].XYZ.Y) {
return PRIM_OVERLAP_UNKNOW;
}
GSVector4i vi(v[i].XYZ.X, v[i + 1].XYZ.Y, v[i + 1].XYZ.X, v[i].XYZ.Y);
for (size_t j = i + 2; j < count; j += 2) {
for (u32 j = i + 2; j < count; j += 2) {
GSVector4i vj(v[j].XYZ.X, v[j + 1].XYZ.Y, v[j + 1].XYZ.X, v[j].XYZ.Y);
GSVector4i inter = vi.rintersect(vj);
if (!inter.rempty()) {
@@ -2922,12 +2949,12 @@ GSState::PRIM_OVERLAP GSState::PrimitiveOverlap()
}
else {
// First vertex is Bottom-Left
for (size_t i = 0; i < count; i += 2) {
for (u32 i = 0; i < count; i += 2) {
if (v[i + 1].XYZ.Y < v[i].XYZ.Y) {
return PRIM_OVERLAP_UNKNOW;
}
GSVector4i vi(v[i].XYZ.X, v[i].XYZ.Y, v[i + 1].XYZ.X, v[i + 1].XYZ.Y);
for (size_t j = i + 2; j < count; j += 2) {
for (u32 j = i + 2; j < count; j += 2) {
GSVector4i vj(v[j].XYZ.X, v[j].XYZ.Y, v[j + 1].XYZ.X, v[j + 1].XYZ.Y);
GSVector4i inter = vi.rintersect(vj);
if (!inter.rempty()) {
@@ -2960,14 +2987,14 @@ __forceinline bool GSState::IsAutoFlushDraw()
return false;
}
__forceinline void GSState::CLUTAutoFlush()
__forceinline void GSState::CLUTAutoFlush(u32 prim)
{
if (m_mem.m_clut.IsInvalid() & 2)
return;
size_t n = 1;
u32 n = 1;
switch (PRIM->PRIM)
switch (prim)
{
case GS_POINTLIST:
n = 1;
@@ -3000,7 +3027,7 @@ __forceinline void GSState::CLUTAutoFlush()
// If it's a point, then we only have one coord, so the address for start and end will be the same, which is bad for the following check.
u32 endbp = startbp;
// otherwise calculate the end.
if (PRIM->PRIM != GS_POINTLIST || (m_index.tail > 1))
if (prim != GS_POINTLIST || (m_index.tail > 1))
endbp = psm.info.bn(temp_draw_rect.z - 1, temp_draw_rect.w - 1, m_context->FRAME.Block(), m_context->FRAME.FBW);
m_mem.m_clut.InvalidateRange(startbp, endbp, true);
@@ -3008,6 +3035,7 @@ __forceinline void GSState::CLUTAutoFlush()
}
}
template<u32 prim, bool index_swap>
__forceinline void GSState::HandleAutoFlush()
{
// Kind of a cheat, making the assumption that 2 consecutive fan/strip triangles won't overlap each other (*should* be safe)
@@ -3020,13 +3048,14 @@ __forceinline void GSState::HandleAutoFlush()
if (IsAutoFlushDraw())
{
int n = 1;
size_t buff[3];
const size_t head = m_vertex.head;
const size_t tail = m_vertex.tail;
u32 buff[3];
const u32 head = m_vertex.head;
const u32 tail = m_vertex.tail;
switch (PRIM->PRIM)
switch (prim)
{
case GS_POINTLIST:
buff[0] = tail - 1;
n = 1;
break;
case GS_LINELIST:
@@ -3099,7 +3128,7 @@ __forceinline void GSState::HandleAutoFlush()
}
// Get the last texture position from the last draw.
const GSVertex* v = &m_vertex.buff[m_index.buff[m_index.tail - 1]];
const GSVertex* v = &m_vertex.buff[m_index.buff[m_index.tail - (index_swap ? n : 1)]];
if (PRIM->FST)
{
@@ -3203,40 +3232,43 @@ __forceinline void GSState::HandleAutoFlush()
}
}
template <u32 prim, bool auto_flush, bool index_swap>
__forceinline void GSState::VertexKick(u32 skip)
static constexpr u32 NumIndicesForPrim(u32 prim)
{
size_t n = 0;
switch (prim)
{
case GS_POINTLIST:
case GS_INVALID:
n = 1;
break;
return 1;
case GS_LINELIST:
case GS_SPRITE:
case GS_LINESTRIP:
n = 2;
break;
return 2;
case GS_TRIANGLELIST:
case GS_TRIANGLESTRIP:
case GS_TRIANGLEFAN:
n = 3;
break;
return 3;
default:
return 0;
}
}
template <u32 prim, bool auto_flush, bool index_swap>
__forceinline void GSState::VertexKick(u32 skip)
{
constexpr u32 n = NumIndicesForPrim(prim);
static_assert(n > 0);
ASSERT(m_vertex.tail < m_vertex.maxcount + 3);
if (auto_flush && skip == 0 && m_index.tail > 0 && ((m_vertex.tail + 1) - m_vertex.head) >= n)
{
HandleAutoFlush();
HandleAutoFlush<prim, index_swap>();
}
size_t head = m_vertex.head;
size_t tail = m_vertex.tail;
size_t next = m_vertex.next;
size_t xy_tail = m_vertex.xy_tail;
u32 head = m_vertex.head;
u32 tail = m_vertex.tail;
u32 next = m_vertex.next;
u32 xy_tail = m_vertex.xy_tail;
// callers should write XYZUVF to m_v.m[1] in one piece to have this load store-forwarded, either by the cpu or the compiler when this function is inlined
@@ -3255,7 +3287,7 @@ __forceinline void GSState::VertexKick(u32 skip)
m_vertex.tail = ++tail;
m_vertex.xy_tail = ++xy_tail;
const size_t m = tail - head;
const u32 m = tail - head;
if (m < n)
return;
@@ -3437,41 +3469,61 @@ __forceinline void GSState::VertexKick(u32 skip)
break;
case GS_INVALID:
m_vertex.tail = head;
break;
return;
default:
__assume(0);
}
GSVector4i draw_coord;
const GSVector2i offset = GSVector2i(m_context->XYOFFSET.OFX, m_context->XYOFFSET.OFY);
for (size_t i = 0; i < n; i++)
{
const GSVertex* v = &m_vertex.buff[m_index.buff[(m_index.tail - n) + i]];
draw_coord.x = (static_cast<int>(v->XYZ.X) - offset.x) >> 4;
draw_coord.y = (static_cast<int>(v->XYZ.Y) - offset.y) >> 4;
const GSVector4i voffset(GSVector4i::loadl(&m_context->XYOFFSET));
if (m_vertex.tail == n && i == 0)
auto get_vertex = [&](u32 i) {
GSVector4i v(GSVector4i::loadl(&m_vertex.buff[m_index.buff[(m_index.tail - n) + (i)]].XYZ));
v = v.upl16(); // 16->32
v = v.sub32(voffset); // -= (OFX, OFY)
v = v.sra32(4); // >> 4
return v;
};
const GSVector4i xy0(get_vertex(0));
GSVector4i min, max;
if (m_vertex.tail == n)
{
temp_draw_rect.x = draw_coord.x;
temp_draw_rect.y = draw_coord.y;
temp_draw_rect = temp_draw_rect.xyxy();
min = xy0;
max = xy0;
}
else
{
temp_draw_rect.x = std::min(draw_coord.x, temp_draw_rect.x);
temp_draw_rect.y = std::min(draw_coord.y, temp_draw_rect.y);
temp_draw_rect.z = std::max(draw_coord.x, temp_draw_rect.z);
temp_draw_rect.w = std::max(draw_coord.y, temp_draw_rect.w);
min = temp_draw_rect.min_i32(xy0);
max = temp_draw_rect.zwzw().max_i32(xy0);
}
if constexpr (n > 1)
{
const GSVector4i xy1(get_vertex(1));
min = min.min_i32(xy1);
max = max.max_i32(xy1);
if constexpr (n > 2)
{
const GSVector4i xy2(get_vertex(2));
min = min.min_i32(xy2);
max = max.max_i32(xy2);
}
}
const GSVector4i scissor = GSVector4i(m_context->scissor.in);
temp_draw_rect = min.upl64(max).rintersect(scissor);
}
const GSVector4i scissor = GSVector4i(m_context->scissor.in);
temp_draw_rect.rintersect(scissor);
CLUTAutoFlush(prim);
CLUTAutoFlush();
if (GSLocalMemory::m_psm[m_prev_env.CTXT[m_prev_env.PRIM.CTXT].FRAME.PSM].bpp == 16 && GSLocalMemory::m_psm[m_prev_env.CTXT[m_prev_env.PRIM.CTXT].TEX0.PSM].bpp == 16
&& m_prev_env.CTXT[m_prev_env.PRIM.CTXT].FRAME.FBMSK == 0x3FFF && m_prev_env.CTXT[m_prev_env.PRIM.CTXT].ALPHA.IsOpaque())
{
DevCon.Warning("Shuffle flush %d", s_n+1);
Flush(AUTOFLUSH);
}
}
/// Checks if region repeat is used (applying it does something to at least one of the values in min...max)
@@ -3539,8 +3591,11 @@ GSState::TextureMinMaxResult GSState::GetTextureMinMax(const GIFRegTEX0& TEX0, c
const int minu = (int)CLAMP.MINU;
const int minv = (int)CLAMP.MINV;
const int maxu = (int)CLAMP.MAXU;
const int maxv = (int)CLAMP.MAXV;
// For the FixedTEX0 case, in hardware, we handle this in the texture cache. Don't OR the bits in here, otherwise
// we'll end up with an invalid rectangle, we want the passed-in rectangle to be relative to the normalized size.
const int maxu = (wms != CLAMP_REGION_REPEAT || (int)CLAMP.MAXU < w) ? (int)CLAMP.MAXU : 0;
const int maxv = (wmt != CLAMP_REGION_REPEAT || (int)CLAMP.MAXV < h) ? (int)CLAMP.MAXV : 0;
GSVector4i vr = tr;

View File

@@ -151,15 +151,15 @@ protected:
struct
{
GSVertex* buff;
size_t head, tail, next, maxcount; // head: first vertex, tail: last vertex + 1, next: last indexed + 1
size_t xy_tail;
u32 head, tail, next, maxcount; // head: first vertex, tail: last vertex + 1, next: last indexed + 1
u32 xy_tail;
u64 xy[4];
} m_vertex;
struct
{
u32* buff;
size_t tail;
u32 tail;
} m_index;
void UpdateContext();
@@ -169,8 +169,9 @@ protected:
void GrowVertexBuffer();
bool IsAutoFlushDraw();
template<u32 prim, bool index_swap>
void HandleAutoFlush();
void CLUTAutoFlush();
void CLUTAutoFlush(u32 prim);
template <u32 prim, bool auto_flush, bool index_swap>
void VertexKick(u32 skip);
@@ -206,6 +207,12 @@ protected:
bool IsCoverageAlpha();
public:
struct GSUploadQueue
{
GIFRegBITBLTBUF blit;
int draw;
};
GIFPath m_path[4];
GIFRegPRIM* PRIM;
GSPrivRegSet* m_regs;
@@ -222,6 +229,8 @@ public:
bool m_mipmap;
u32 m_dirty_gs_regs;
int m_backed_up_ctx;
std::vector<GSUploadQueue> m_draw_transfers;
bool m_force_preload;
static int s_n;
@@ -321,6 +330,7 @@ public:
int GetFramebufferHeight();
int GetFramebufferWidth();
int GetFramebufferBitDepth();
int GetDisplayHMagnification();
GSVector4i GetDisplayRect(int i = -1);
GSVector4i GetFrameMagnifiedRect(int i = -1);

View File

@@ -187,14 +187,6 @@ std::unique_ptr<GSDownloadTexture> GSDevice::CreateDownloadTexture(u32 width, u3
return {};
}
void GSDevice::EndScene()
{
m_vertex.start += m_vertex.count;
m_vertex.count = 0;
m_index.start += m_index.count;
m_index.count = 0;
}
void GSDevice::Recycle(GSTexture* t)
{
if (!t)

View File

@@ -309,6 +309,8 @@ struct alignas(16) GSHWDrawConfig
u32 tcc : 1;
u32 wms : 2;
u32 wmt : 2;
u32 adjs : 1;
u32 adjt : 1;
u32 ltf : 1;
// Shuffle and fbmask effect
u32 shuffle : 1;
@@ -352,10 +354,10 @@ struct alignas(16) GSHWDrawConfig
u32 automatic_lod : 1;
u32 manual_lod : 1;
u32 point_sampler : 1;
u32 invalid_tex0 : 1; // Lupin the 3rd
// Scan mask
u32 scanmsk : 2;
u32 swap_ga : 1;
};
struct
@@ -554,11 +556,11 @@ struct alignas(16) GSHWDrawConfig
GSVector4 FogColor_AREF;
GSVector4 WH;
GSVector4 TA_MaxDepth_Af;
GSVector4i MskFix;
GSVector4i FbMask;
GSVector4 HalfTexel;
GSVector4 MinMax;
GSVector4 STRange;
GSVector4i ChannelShuffle;
GSVector2 TCOffsetHack;
GSVector2 STScale;
@@ -744,11 +746,11 @@ protected:
struct
{
size_t stride, start, count, limit;
u32 start, count;
} m_vertex = {};
struct
{
size_t start, count, limit;
u32 start, count;
} m_index = {};
unsigned int m_frame = 0; // for ageing the pool
bool m_rbswapped = false;
@@ -799,9 +801,6 @@ public:
virtual void ResetAPIState();
virtual void RestoreAPIState();
virtual void BeginScene() {}
virtual void EndScene();
virtual void ClearRenderTarget(GSTexture* t, const GSVector4& c) {}
virtual void ClearRenderTarget(GSTexture* t, u32 c) {}
virtual void InvalidateRenderTarget(GSTexture* t) {}

View File

@@ -15,6 +15,7 @@
#include "PrecompiledHeader.h"
#include "GSDirtyRect.h"
#include <vector>
GSDirtyRect::GSDirtyRect() :
r(GSVector4i::zero()),
@@ -30,7 +31,7 @@ GSDirtyRect::GSDirtyRect(GSVector4i& r, u32 psm, u32 bw) :
{
}
GSVector4i GSDirtyRect::GetDirtyRect(GIFRegTEX0& TEX0) const
GSVector4i GSDirtyRect::GetDirtyRect(GIFRegTEX0& TEX0)
{
GSVector4i _r;
@@ -53,9 +54,7 @@ GSVector4i GSDirtyRect::GetDirtyRect(GIFRegTEX0& TEX0) const
return _r;
}
//
GSVector4i GSDirtyRectList::GetDirtyRect(GIFRegTEX0& TEX0, const GSVector2i& size) const
GSVector4i GSDirtyRectList::GetTotalRect(GIFRegTEX0& TEX0, const GSVector2i& size)
{
if (!empty())
{
@@ -74,9 +73,31 @@ GSVector4i GSDirtyRectList::GetDirtyRect(GIFRegTEX0& TEX0, const GSVector2i& siz
return GSVector4i::zero();
}
GSVector4i GSDirtyRectList::GetDirtyRect(GIFRegTEX0& TEX0, const GSVector2i& size, bool clear)
{
if (!empty())
{
const std::vector<GSDirtyRect>::iterator &it = begin();
const GSVector4i r = it[0].GetDirtyRect(TEX0);
if (clear)
erase(it);
GSVector2i bs = GSLocalMemory::m_psm[TEX0.PSM].bs;
return r.ralign<Align_Outside>(bs).rintersect(GSVector4i(0, 0, size.x, size.y));
}
return GSVector4i::zero();
}
GSVector4i GSDirtyRectList::GetDirtyRectAndClear(GIFRegTEX0& TEX0, const GSVector2i& size)
{
GSVector4i r = GetDirtyRect(TEX0, size);
clear();
const GSVector4i r = GetDirtyRect(TEX0, size, true);
return r;
}
void GSDirtyRectList::ClearDirty()
{
clear();
}

View File

@@ -26,13 +26,15 @@ public:
GSDirtyRect();
GSDirtyRect(GSVector4i& r, u32 psm, u32 bw);
GSVector4i GetDirtyRect(GIFRegTEX0& TEX0) const;
GSVector4i GetDirtyRect(GIFRegTEX0& TEX0);
};
class GSDirtyRectList : public std::vector<GSDirtyRect>
{
public:
GSDirtyRectList() {}
GSVector4i GetDirtyRect(GIFRegTEX0& TEX0, const GSVector2i& size) const;
GSVector4i GetTotalRect(GIFRegTEX0& TEX0, const GSVector2i& size);
GSVector4i GetDirtyRect(GIFRegTEX0& TEX0, const GSVector2i& size, bool clear = false);
GSVector4i GetDirtyRectAndClear(GIFRegTEX0& TEX0, const GSVector2i& size);
void ClearDirty();
};

View File

@@ -17,6 +17,7 @@
#include "GSRenderer.h"
#include "GS/GSCapture.h"
#include "GS/GSGL.h"
#include "GSDumpReplayer.h"
#include "Host.h"
#include "HostDisplay.h"
#include "PerformanceMetrics.h"
@@ -554,15 +555,18 @@ static void CompressAndWriteScreenshot(std::string filename, u32 width, u32 heig
image.SetPixels(width, height, std::move(pixels));
std::string key(fmt::format("GSScreenshot_{}", filename));
Host::AddIconOSDMessage(key, ICON_FA_CAMERA, fmt::format("Saving screenshot to '{}'.", Path::GetFileName(filename)), 60.0f);
if(!GSDumpReplayer::IsRunner())
Host::AddIconOSDMessage(key, ICON_FA_CAMERA, fmt::format("Saving screenshot to '{}'.", Path::GetFileName(filename)), 60.0f);
// maybe std::async would be better here.. but it's definitely worth threading, large screenshots take a while to compress.
std::unique_lock lock(s_screenshot_threads_mutex);
s_screenshot_threads.emplace_back([key = std::move(key), filename = std::move(filename), image = std::move(image), quality = GSConfig.ScreenshotQuality]() {
if (image.SaveToFile(filename.c_str(), quality))
{
Host::AddIconOSDMessage(std::move(key), ICON_FA_CAMERA,
fmt::format("Saved screenshot to '{}'.", Path::GetFileName(filename)), Host::OSD_INFO_DURATION);
if(!GSDumpReplayer::IsRunner())
Host::AddIconOSDMessage(std::move(key), ICON_FA_CAMERA,
fmt::format("Saved screenshot to '{}'.", Path::GetFileName(filename)), Host::OSD_INFO_DURATION);
}
else
{

View File

@@ -29,6 +29,7 @@
#include <sstream>
#include <VersionHelpers.h>
#include <d3dcompiler.h>
#include <dxgidebug.h>
static bool SupportsTextureFormat(ID3D11Device* dev, DXGI_FORMAT format)
{
@@ -90,6 +91,8 @@ bool GSDevice11::Create()
m_dev = static_cast<ID3D11Device*>(g_host_display->GetDevice());
m_ctx = static_cast<ID3D11DeviceContext*>(g_host_display->GetContext());
if (GSConfig.UseDebugDevice)
m_annotation = m_ctx.try_query<ID3DUserDefinedAnnotation>();
level = m_dev->GetFeatureLevel();
const bool support_feature_level_11_0 = (level >= D3D_FEATURE_LEVEL_11_0);
@@ -269,6 +272,27 @@ bool GSDevice11::Create()
if (!m_shadeboost.ps)
return false;
// Vertex/Index Buffer
bd = {};
bd.ByteWidth = VERTEX_BUFFER_SIZE;
bd.Usage = D3D11_USAGE_DYNAMIC;
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
if (FAILED(m_dev->CreateBuffer(&bd, nullptr, m_vb.put())))
{
Console.Error("Failed to create vertex buffer.");
return false;
}
bd.ByteWidth = INDEX_BUFFER_SIZE;
bd.BindFlags = D3D11_BIND_INDEX_BUFFER;
if (FAILED(m_dev->CreateBuffer(&bd, nullptr, m_ib.put())))
{
Console.Error("Failed to create index buffer.");
return false;
}
m_ctx->IASetIndexBuffer(m_ib.get(), DXGI_FORMAT_R32_UINT, 0);
//
memset(&rd, 0, sizeof(rd));
@@ -281,7 +305,7 @@ bool GSDevice11::Create()
rd.SlopeScaledDepthBias = 0;
rd.DepthClipEnable = false; // ???
rd.ScissorEnable = true;
rd.MultisampleEnable = true;
rd.MultisampleEnable = false;
rd.AntialiasedLineEnable = false;
m_dev->CreateRasterizerState(&rd, m_rs.put());
@@ -366,10 +390,9 @@ void GSDevice11::ResetAPIState()
void GSDevice11::RestoreAPIState()
{
const UINT vb_stride = static_cast<UINT>(m_state.vb_stride);
const UINT vb_offset = 0;
m_ctx->IASetVertexBuffers(0, 1, &m_state.vb, &vb_stride, &vb_offset);
m_ctx->IASetIndexBuffer(m_state.ib, DXGI_FORMAT_R32_UINT, 0);
m_ctx->IASetVertexBuffers(0, 1, m_vb.addressof(), &m_state.vb_stride, &vb_offset);
m_ctx->IASetIndexBuffer(m_ib.get(), DXGI_FORMAT_R32_UINT, 0);
m_ctx->IASetInputLayout(m_state.layout);
m_ctx->IASetPrimitiveTopology(m_state.topology);
m_ctx->VSSetShader(m_state.vs, nullptr, 0);
@@ -451,6 +474,40 @@ void GSDevice11::ClearStencil(GSTexture* t, u8 c)
m_ctx->ClearDepthStencilView(*(GSTexture11*)t, D3D11_CLEAR_STENCIL, 0, c);
}
void GSDevice11::PushDebugGroup(const char* fmt, ...)
{
if (!m_annotation)
return;
std::va_list ap;
va_start(ap, fmt);
std::string str(StringUtil::StdStringFromFormatV(fmt, ap));
va_end(ap);
m_annotation->BeginEvent(StringUtil::UTF8StringToWideString(str).c_str());
}
void GSDevice11::PopDebugGroup()
{
if (!m_annotation)
return;
m_annotation->EndEvent();
}
void GSDevice11::InsertDebugMessage(DebugMessageCategory category, const char* fmt, ...)
{
if (!m_annotation)
return;
std::va_list ap;
va_start(ap, fmt);
std::string str(StringUtil::StdStringFromFormatV(fmt, ap));
va_end(ap);
m_annotation->SetMarker(StringUtil::UTF8StringToWideString(str).c_str());
}
GSTexture* GSDevice11::CreateSurface(GSTexture::Type type, int width, int height, int levels, GSTexture::Format format)
{
D3D11_TEXTURE2D_DESC desc = {};
@@ -579,8 +636,6 @@ void GSDevice11::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture*
const bool draw_in_depth = dTex && dTex->IsDepthStencil();
BeginScene();
GSVector2i ds;
if (dTex)
{
@@ -649,8 +704,6 @@ void GSDevice11::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture*
//
EndScene();
PSSetShaderResources(nullptr, nullptr);
}
@@ -658,8 +711,6 @@ void GSDevice11::PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture*
{
ASSERT(sTex);
BeginScene();
GSVector2i ds;
if (dTex)
{
@@ -726,8 +777,6 @@ void GSDevice11::PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture*
//
EndScene();
PSSetShaderResources(nullptr, nullptr);
}
@@ -908,8 +957,6 @@ void GSDevice11::SetupDATE(GSTexture* rt, GSTexture* ds, const GSVertexPT1* vert
{
// sfex3 (after the capcom logo), vf4 (first menu fading in), ffxii shadows, rumble roses shadows, persona4 shadows
BeginScene();
ClearStencil(ds, 0);
// om
@@ -942,156 +989,69 @@ void GSDevice11::SetupDATE(GSTexture* rt, GSTexture* ds, const GSVertexPT1* vert
DrawPrimitive();
//
EndScene();
}
void GSDevice11::IASetVertexBuffer(const void* vertex, size_t stride, size_t count)
bool GSDevice11::IASetVertexBuffer(const void* vertex, u32 stride, u32 count)
{
void* ptr = nullptr;
if (IAMapVertexBuffer(&ptr, stride, count))
{
GSVector4i::storent(ptr, vertex, count * stride);
IAUnmapVertexBuffer();
}
}
bool GSDevice11::IAMapVertexBuffer(void** vertex, size_t stride, size_t count)
{
ASSERT(m_vertex.count == 0);
if (count * stride > m_vertex.limit * m_vertex.stride)
{
m_vb.reset();
m_vertex.start = 0;
m_vertex.limit = std::max<int>(count * 3 / 2, 11000);
}
if (m_vb == nullptr)
{
D3D11_BUFFER_DESC bd;
memset(&bd, 0, sizeof(bd));
bd.Usage = D3D11_USAGE_DYNAMIC;
bd.ByteWidth = m_vertex.limit * stride;
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
const HRESULT hr = m_dev->CreateBuffer(&bd, nullptr, m_vb.put());
if (FAILED(hr))
return false;
}
const u32 size = stride * count;
if (size > VERTEX_BUFFER_SIZE)
return false;
D3D11_MAP type = D3D11_MAP_WRITE_NO_OVERWRITE;
if (m_vertex.start + count > m_vertex.limit || stride != m_vertex.stride)
m_vertex.start = (m_vb_pos + (stride - 1)) / stride;
m_vb_pos = (m_vertex.start * stride) + size;
if (m_vb_pos > VERTEX_BUFFER_SIZE)
{
m_vertex.start = 0;
m_vb_pos = size;
type = D3D11_MAP_WRITE_DISCARD;
}
D3D11_MAPPED_SUBRESOURCE m;
if (FAILED(m_ctx->Map(m_vb.get(), 0, type, 0, &m)))
{
return false;
GSVector4i::storent(static_cast<u8*>(m.pData) + m_vertex.start * stride, vertex, count * stride);
m_ctx->Unmap(m_vb.get(), 0);
if (m_state.vb_stride != stride)
{
m_state.vb_stride = stride;
const UINT vb_offset = 0;
m_ctx->IASetVertexBuffers(0, 1, m_vb.addressof(), &stride, &vb_offset);
}
*vertex = (u8*)m.pData + m_vertex.start * stride;
m_vertex.count = count;
m_vertex.stride = stride;
return true;
}
void GSDevice11::IAUnmapVertexBuffer()
bool GSDevice11::IASetIndexBuffer(const void* index, u32 count)
{
m_ctx->Unmap(m_vb.get(), 0);
IASetVertexBuffer(m_vb.get(), m_vertex.stride);
}
void GSDevice11::IASetVertexBuffer(ID3D11Buffer* vb, size_t stride)
{
if (m_state.vb != vb || m_state.vb_stride != stride)
{
m_state.vb = vb;
m_state.vb_stride = stride;
const u32 stride2 = stride;
const u32 offset = 0;
m_ctx->IASetVertexBuffers(0, 1, &vb, &stride2, &offset);
}
}
void GSDevice11::IASetIndexBuffer(const void* index, size_t count)
{
ASSERT(m_index.count == 0);
if (count > m_index.limit)
{
m_ib.reset();
m_index.start = 0;
m_index.limit = std::max<int>(count * 3 / 2, 11000);
}
if (m_ib == nullptr)
{
D3D11_BUFFER_DESC bd;
memset(&bd, 0, sizeof(bd));
bd.Usage = D3D11_USAGE_DYNAMIC;
bd.ByteWidth = m_index.limit * sizeof(u32);
bd.BindFlags = D3D11_BIND_INDEX_BUFFER;
bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
HRESULT hr = m_dev->CreateBuffer(&bd, nullptr, m_ib.put());
if (FAILED(hr))
return;
}
if (count > (INDEX_BUFFER_SIZE / sizeof(u32)))
return false;
D3D11_MAP type = D3D11_MAP_WRITE_NO_OVERWRITE;
if (m_index.start + count > m_index.limit)
m_index.start = m_ib_pos;
m_ib_pos += count;
if (m_ib_pos > (INDEX_BUFFER_SIZE / sizeof(u32)))
{
m_index.start = 0;
m_ib_pos = count;
type = D3D11_MAP_WRITE_DISCARD;
}
D3D11_MAPPED_SUBRESOURCE m;
if (FAILED(m_ctx->Map(m_ib.get(), 0, type, 0, &m)))
return false;
if (SUCCEEDED(m_ctx->Map(m_ib.get(), 0, type, 0, &m)))
{
memcpy((u8*)m.pData + m_index.start * sizeof(u32), index, count * sizeof(u32));
m_ctx->Unmap(m_ib.get(), 0);
}
std::memcpy((u8*)m.pData + m_index.start * sizeof(u32), index, count * sizeof(u32));
m_ctx->Unmap(m_ib.get(), 0);
m_index.count = count;
IASetIndexBuffer(m_ib.get());
}
void GSDevice11::IASetIndexBuffer(ID3D11Buffer* ib)
{
if (m_state.ib != ib)
{
m_state.ib = ib;
m_ctx->IASetIndexBuffer(ib, DXGI_FORMAT_R32_UINT, 0);
}
return true;
}
void GSDevice11::IASetInputLayout(ID3D11InputLayout* layout)
@@ -1370,17 +1330,15 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config)
// 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::HDR_INIT, false);
g_perfmon.Put(GSPerfMon::TextureCopies, 1);
}
BeginScene();
void* ptr = nullptr;
if (IAMapVertexBuffer(&ptr, sizeof(*config.verts), config.nverts))
if (!IASetVertexBuffer(config.verts, sizeof(*config.verts), config.nverts) ||
!IASetIndexBuffer(config.indices, config.nindices))
{
GSVector4i::storent(ptr, config.verts, config.nverts * sizeof(*config.verts));
IAUnmapVertexBuffer();
Console.Error("Failed to upload vertices/indices (%u/%u)", config.nverts, config.nindices);
return;
}
IASetIndexBuffer(config.indices, config.nindices);
D3D11_PRIMITIVE_TOPOLOGY topology = D3D11_PRIMITIVE_TOPOLOGY_UNDEFINED;
switch (config.topology)
{
@@ -1489,8 +1447,6 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config)
}
}
EndScene();
if (rt_copy)
Recycle(rt_copy);
if (ds_copy)
@@ -1504,6 +1460,7 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config)
const GSVector4 dRect(config.drawarea);
const GSVector4 sRect = dRect / GSVector4(size.x, size.y).xyxy();
StretchRect(hdr_rt, sRect, config.rt, dRect, ShaderConvert::HDR_RESOLVE, false);
g_perfmon.Put(GSPerfMon::TextureCopies, 1);
Recycle(hdr_rt);
}
}

View File

@@ -22,6 +22,7 @@
#include <unordered_map>
#include <wil/com.h>
#include <dxgi1_3.h>
#include <d3d11_1.h>
struct GSVertexShader11
{
@@ -109,8 +110,13 @@ public:
};
private:
static constexpr u32 MAX_TEXTURES = 4;
static constexpr u32 MAX_SAMPLERS = 1;
enum : u32
{
MAX_TEXTURES = 4,
MAX_SAMPLERS = 1,
VERTEX_BUFFER_SIZE = 32 * 1024 * 1024,
INDEX_BUFFER_SIZE = 16 * 1024 * 1024,
};
int m_d3d_texsize;
@@ -130,15 +136,15 @@ private:
wil::com_ptr_nothrow<ID3D11Device> m_dev;
wil::com_ptr_nothrow<ID3D11DeviceContext> m_ctx;
wil::com_ptr_nothrow<ID3DUserDefinedAnnotation> m_annotation;
wil::com_ptr_nothrow<IDXGISwapChain1> m_swapchain;
wil::com_ptr_nothrow<ID3D11Buffer> m_vb;
wil::com_ptr_nothrow<ID3D11Buffer> m_ib;
u32 m_vb_pos = 0; // bytes
u32 m_ib_pos = 0; // indices/sizeof(u32)
struct
{
ID3D11Buffer* vb;
size_t vb_stride;
ID3D11Buffer* ib;
ID3D11InputLayout* layout;
D3D11_PRIMITIVE_TOPOLOGY topology;
ID3D11VertexShader* vs;
@@ -151,6 +157,7 @@ private:
std::array<ID3D11SamplerState*, MAX_SAMPLERS> ps_ss;
GSVector2i viewport;
GSVector4i scissor;
u32 vb_stride;
ID3D11DepthStencilState* dss;
u8 sref;
ID3D11BlendState* bs;
@@ -257,6 +264,10 @@ public:
void ClearDepth(GSTexture* t) override;
void ClearStencil(GSTexture* t, u8 c) override;
void PushDebugGroup(const char* fmt, ...) override;
void PopDebugGroup() override;
void InsertDebugMessage(DebugMessageCategory category, const char* fmt, ...) override;
void CloneTexture(GSTexture* src, GSTexture** dest, const GSVector4i& rect);
void CopyRect(GSTexture* sTex, GSTexture* dTex, const GSVector4i& r, u32 destX, u32 destY) override;
@@ -270,12 +281,8 @@ public:
void SetupDATE(GSTexture* rt, GSTexture* ds, const GSVertexPT1* vertices, bool datm);
void IASetVertexBuffer(const void* vertex, size_t stride, size_t count);
bool IAMapVertexBuffer(void** vertex, size_t stride, size_t count);
void IAUnmapVertexBuffer();
void IASetVertexBuffer(ID3D11Buffer* vb, size_t stride);
void IASetIndexBuffer(const void* index, size_t count);
void IASetIndexBuffer(ID3D11Buffer* ib);
bool IASetVertexBuffer(const void* vertex, u32 stride, u32 count);
bool IASetIndexBuffer(const void* index, u32 count);
void IASetInputLayout(ID3D11InputLayout* layout);
void IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY topology);

View File

@@ -142,6 +142,8 @@ void GSDevice11::SetupPS(const PSSelector& sel, const GSHWDrawConfig::PSConstant
sm.AddMacro("PS_FST", sel.fst);
sm.AddMacro("PS_WMS", sel.wms);
sm.AddMacro("PS_WMT", sel.wmt);
sm.AddMacro("PS_ADJS", sel.adjs);
sm.AddMacro("PS_ADJT", sel.adjt);
sm.AddMacro("PS_AEM_FMT", sel.aem_fmt);
sm.AddMacro("PS_AEM", sel.aem);
sm.AddMacro("PS_TFX", sel.tfx);
@@ -158,13 +160,13 @@ void GSDevice11::SetupPS(const PSSelector& sel, const GSHWDrawConfig::PSConstant
sm.AddMacro("PS_POINT_SAMPLER", sel.point_sampler);
sm.AddMacro("PS_SHUFFLE", sel.shuffle);
sm.AddMacro("PS_READ_BA", sel.read_ba);
sm.AddMacro("PS_SWAP_GA", sel.swap_ga);
sm.AddMacro("PS_CHANNEL_FETCH", sel.channel);
sm.AddMacro("PS_TALES_OF_ABYSS_HLE", sel.tales_of_abyss_hle);
sm.AddMacro("PS_URBAN_CHAOS_HLE", sel.urban_chaos_hle);
sm.AddMacro("PS_DFMT", sel.dfmt);
sm.AddMacro("PS_DEPTH_FMT", sel.depth_fmt);
sm.AddMacro("PS_PAL_FMT", sel.pal_fmt);
sm.AddMacro("PS_INVALID_TEX0", sel.invalid_tex0);
sm.AddMacro("PS_HDR", sel.hdr);
sm.AddMacro("PS_COLCLIP", sel.colclip);
sm.AddMacro("PS_BLEND_A", sel.blend_a);

View File

@@ -842,8 +842,6 @@ void GSDevice12::IASetVertexBuffer(const void* vertex, size_t stride, size_t cou
}
m_vertex.start = m_vertex_stream_buffer.GetCurrentOffset() / stride;
m_vertex.limit = count;
m_vertex.stride = stride;
m_vertex.count = count;
SetVertexBuffer(m_vertex_stream_buffer.GetGPUPointer(), m_vertex_stream_buffer.GetSize(), stride);
@@ -851,32 +849,6 @@ void GSDevice12::IASetVertexBuffer(const void* vertex, size_t stride, size_t cou
m_vertex_stream_buffer.CommitMemory(size);
}
bool GSDevice12::IAMapVertexBuffer(void** vertex, size_t stride, size_t count)
{
const u32 size = static_cast<u32>(stride) * static_cast<u32>(count);
if (!m_vertex_stream_buffer.ReserveMemory(size, static_cast<u32>(stride)))
{
ExecuteCommandListAndRestartRenderPass(false, "Mapping bytes to vertex buffer");
if (!m_vertex_stream_buffer.ReserveMemory(size, static_cast<u32>(stride)))
pxFailRel("Failed to reserve space for vertices");
}
m_vertex.start = m_vertex_stream_buffer.GetCurrentOffset() / stride;
m_vertex.limit = m_vertex_stream_buffer.GetCurrentSpace() / stride;
m_vertex.stride = stride;
m_vertex.count = count;
SetVertexBuffer(m_vertex_stream_buffer.GetGPUPointer(), m_vertex_stream_buffer.GetSize(), stride);
*vertex = m_vertex_stream_buffer.GetCurrentHostPointer();
return true;
}
void GSDevice12::IAUnmapVertexBuffer()
{
const u32 size = static_cast<u32>(m_vertex.stride) * static_cast<u32>(m_vertex.count);
m_vertex_stream_buffer.CommitMemory(size);
}
void GSDevice12::IASetIndexBuffer(const void* index, size_t count)
{
const u32 size = sizeof(u32) * static_cast<u32>(count);
@@ -888,7 +860,6 @@ void GSDevice12::IASetIndexBuffer(const void* index, size_t count)
}
m_index.start = m_index_stream_buffer.GetCurrentOffset() / sizeof(u32);
m_index.limit = count;
m_index.count = count;
SetIndexBuffer(m_index_stream_buffer.GetGPUPointer(), m_index_stream_buffer.GetSize(), DXGI_FORMAT_R32_UINT);
@@ -1512,6 +1483,8 @@ const ID3DBlob* GSDevice12::GetTFXPixelShader(const GSHWDrawConfig::PSSelector&
sm.AddMacro("PS_FST", sel.fst);
sm.AddMacro("PS_WMS", sel.wms);
sm.AddMacro("PS_WMT", sel.wmt);
sm.AddMacro("PS_ADJS", sel.adjs);
sm.AddMacro("PS_ADJT", sel.adjt);
sm.AddMacro("PS_AEM_FMT", sel.aem_fmt);
sm.AddMacro("PS_AEM", sel.aem);
sm.AddMacro("PS_TFX", sel.tfx);
@@ -1534,7 +1507,6 @@ const ID3DBlob* GSDevice12::GetTFXPixelShader(const GSHWDrawConfig::PSSelector&
sm.AddMacro("PS_DFMT", sel.dfmt);
sm.AddMacro("PS_DEPTH_FMT", sel.depth_fmt);
sm.AddMacro("PS_PAL_FMT", sel.pal_fmt);
sm.AddMacro("PS_INVALID_TEX0", sel.invalid_tex0);
sm.AddMacro("PS_HDR", sel.hdr);
sm.AddMacro("PS_COLCLIP", sel.colclip);
sm.AddMacro("PS_BLEND_A", sel.blend_a);
@@ -2657,8 +2629,6 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
if (date_image)
Recycle(date_image);
EndScene();
// now blit the hdr texture back to the original target
if (hdr_rt)
{

View File

@@ -264,8 +264,6 @@ public:
GSTexture12* SetupPrimitiveTrackingDATE(GSHWDrawConfig& config, PipelineSelector& pipe);
void IASetVertexBuffer(const void* vertex, size_t stride, size_t count);
bool IAMapVertexBuffer(void** vertex, size_t stride, size_t count);
void IAUnmapVertexBuffer();
void IASetIndexBuffer(const void* index, size_t count);
void PSSetShaderResource(int i, GSTexture* sr, bool check_state);

View File

@@ -838,7 +838,7 @@ bool GSHwHack::GSC_BlueTongueGames(GSRendererHW& r, const GSFrameInfo& fi, int&
bool GSHwHack::OI_PointListPalette(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t)
{
const size_t n_vertices = r.m_vertex.next;
const u32 n_vertices = r.m_vertex.next;
const int w = r.m_r.width();
const int h = r.m_r.height();
const bool is_copy = !r.PRIM->ABE || (

View File

@@ -204,14 +204,27 @@ void GSRendererHW::VSync(u32 field, bool registers_written)
{
m_tc->RemoveAll();
m_reset = false;
m_force_preload = true;
}
else
{
m_force_preload = false;
std::vector<GSState::GSUploadQueue>::iterator iter;
for (iter = m_draw_transfers.begin(); iter != m_draw_transfers.end(); ) {
if ((s_n - iter->draw) > 5)
iter = m_draw_transfers.erase(iter);
else
iter++;
}
}
if (GSConfig.LoadTextureReplacements)
GSTextureReplacements::ProcessAsyncLoadedTextures();
m_tc->IncAge();
GSRenderer::VSync(field, registers_written);
m_tc->IncAge();
if (m_tc->GetHashCacheMemoryUsage() > 1024 * 1024 * 1024)
{
@@ -320,7 +333,7 @@ void GSRendererHW::Lines2Sprites()
if (m_vertex.next >= 2)
{
const size_t count = m_vertex.next;
const u32 count = m_vertex.next;
int i = static_cast<int>(count) * 2 - 4;
GSVertex* s = &m_vertex.buff[count - 2];
@@ -390,7 +403,7 @@ void GSRendererHW::Lines2Sprites()
template <GSHWDrawConfig::VSExpand Expand>
void GSRendererHW::ExpandIndices()
{
size_t process_count = (m_index.tail + 3) / 4 * 4;
u32 process_count = (m_index.tail + 3) / 4 * 4;
if (Expand == GSHWDrawConfig::VSExpand::Point)
{
// Make sure we have space for writing off the end slightly
@@ -450,22 +463,47 @@ void GSRendererHW::ExpandIndices()
}
// Fix the vertex position/tex_coordinate from 16 bits color to 32 bits color
void GSRendererHW::ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba)
void GSRendererHW::ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba, bool& swap_ga)
{
const size_t count = m_vertex.next;
const u32 count = m_vertex.next;
GSVertex* v = &m_vertex.buff[0];
const GIFRegXYOFFSET& o = m_context->XYOFFSET;
int first_vertex = (v[1].XYZ.X > v[0].XYZ.X) ? 0 : 1;
// vertex position is 8 to 16 pixels, therefore it is the 16-31 bits of the colors
const int pos = (v[0].XYZ.X - o.OFX) & 0xFF;
const int pos = (v[first_vertex].XYZ.X - o.OFX) & 0xFF;
DevCon.Warning("Draw %d Pos %d(%d)",s_n, pos, pos >> 4);
write_ba = (pos > 112 && pos < 136);
// Read texture is 8 to 16 pixels (same as above)
const float tw = static_cast<float>(1u << m_context->TEX0.TW);
int tex_pos = (PRIM->FST) ? v[0].U : static_cast<int>(tw * v[0].ST.S);
// Read texture is 8 to 16 pixels (same as above)
int tex_pos = (PRIM->FST) ? v[first_vertex].U : static_cast<int>(tw * v[first_vertex].ST.S);
tex_pos &= 0xFF;
read_ba = (tex_pos > 112 && tex_pos < 144);
int coord_width = std::abs(v[first_vertex].XYZ.X - v[1 - first_vertex].XYZ.X) >> 4;
int tex_width = m_vt.m_max.t.x - m_vt.m_min.t.x;
// Probably Green/Alpha swap
if (tex_width > 14)
{
/*v[1 - first_vertex].XYZ.X = ((v[1 - first_vertex].XYZ.X - o.OFX) * 2) + o.OFX;
v[first_vertex].XYZ.X = ((v[first_vertex].XYZ.X - o.OFX) * 2) + o.OFX;
if (m_vt.m_max.p.y > 512.0f)
{
DevCon.Warning("Changing width from %d to %d", v[1 - first_vertex].XYZ.X, v[1 - first_vertex].XYZ.X + (tex_width << 4));
v[1 - first_vertex].XYZ.X -= tex_width << 4;
}*/
DevCon.Warning("Width %d Max y %f", tex_width, m_vt.m_max.p.y);
//read_ba = true;
//write_ba = false;
swap_ga = true;
return;
}
else
swap_ga = false;
bool half_bottom = false;
switch (GSConfig.UserHacks_HalfBottomOverride)
{
@@ -503,7 +541,7 @@ void GSRendererHW::ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba)
int maxvert = 0;
int minvert = 4096;
for (size_t i = 0; i < count; i ++)
for (u32 i = 0; i < count; i ++)
{
int YCord = 0;
@@ -526,17 +564,18 @@ void GSRendererHW::ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba)
{
GL_INS("First vertex is P: %d => %d T: %d => %d", v[0].XYZ.X, v[1].XYZ.X, v[0].U, v[1].U);
for (size_t i = 0; i < count; i += 2)
for (u32 i = 0; i < count; i += 2)
{
first_vertex = (v[i+1].XYZ.X > v[i].XYZ.X) ? 0 : 1;
if (write_ba)
v[i].XYZ.X -= 128u;
v[i+first_vertex].XYZ.X -= 128u;
else
v[i+1].XYZ.X += 128u;
v[i+(1-first_vertex)].XYZ.X += 128u;
if (read_ba)
v[i].U -= 128u;
v[i + first_vertex].U -= 128u;
else
v[i+1].U += 128u;
v[i+(1 - first_vertex)].U += 128u;
if (!half_bottom)
{
@@ -544,13 +583,13 @@ void GSRendererHW::ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba)
const int tex_offset = v[i].V & 0xF;
const GSVector4i offset(o.OFY, tex_offset, o.OFY, tex_offset);
GSVector4i tmp(v[i].XYZ.Y, v[i].V, v[i + 1].XYZ.Y, v[i + 1].V);
GSVector4i tmp(v[i + first_vertex].XYZ.Y, v[i +first_vertex].V, v[i + (1 - first_vertex)].XYZ.Y, v[i + (1 - first_vertex)].V);
tmp = GSVector4i(tmp - offset).srl32(1) + offset;
v[i].XYZ.Y = static_cast<u16>(tmp.x);
v[i].V = static_cast<u16>(tmp.y);
v[i + 1].XYZ.Y = static_cast<u16>(tmp.z);
v[i + 1].V = static_cast<u16>(tmp.w);
v[i + first_vertex].XYZ.Y = static_cast<u16>(tmp.x);
v[i + first_vertex].V = static_cast<u16>(tmp.y);
v[i + (1 - first_vertex)].XYZ.Y = static_cast<u16>(tmp.z);
v[i + (1 - first_vertex)].V = static_cast<u16>(tmp.w);
}
}
}
@@ -559,24 +598,26 @@ void GSRendererHW::ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba)
const float offset_8pix = 8.0f / tw;
GL_INS("First vertex is P: %d => %d T: %f => %f (offset %f)", v[0].XYZ.X, v[1].XYZ.X, v[0].ST.S, v[1].ST.S, offset_8pix);
for (size_t i = 0; i < count; i += 2)
for (u32 i = 0; i < count; i += 2)
{
first_vertex = (v[i + 1].XYZ.X > v[i].XYZ.X) ? 0 : 1;
if (write_ba)
v[i].XYZ.X -= 128u;
v[i+ first_vertex].XYZ.X -= 128u;
else
v[i+1].XYZ.X += 128u;
v[i+(1- first_vertex)].XYZ.X += 128u;
if (read_ba)
v[i].ST.S -= offset_8pix;
v[i + first_vertex].ST.S -= offset_8pix;
else
v[i+1].ST.S += offset_8pix;
v[i + (1- first_vertex)].ST.S += offset_8pix;
if (!half_bottom)
{
// Height is too big (2x).
const GSVector4i offset(o.OFY, o.OFY);
GSVector4i tmp(v[i].XYZ.Y, v[i + 1].XYZ.Y);
GSVector4i tmp(v[i+ first_vertex].XYZ.Y, v[i + (1- first_vertex)].XYZ.Y);
tmp = GSVector4i(tmp - offset).srl32(1) + offset;
//fprintf(stderr, "Before %d, After %d\n", v[i + 1].XYZ.Y, tmp.y);
@@ -696,7 +737,7 @@ GSVector4i GSRendererHW::ComputeBoundingBox(const GSVector2& rtscale, const GSVe
void GSRendererHW::MergeSprite(GSTextureCache::Source* tex)
{
// Upscaling hack to avoid various line/grid issues
if (GSConfig.UserHacks_MergePPSprite && tex && tex->m_target && (m_vt.m_primclass == GS_SPRITE_CLASS))
if (GSConfig.UserHacks_MergePPSprite && CanUpscale() && tex && tex->m_target && (m_vt.m_primclass == GS_SPRITE_CLASS))
{
if (PRIM->FST && GSLocalMemory::m_psm[tex->m_TEX0.PSM].fmt < 2 && ((m_vt.m_eq.value & 0xCFFFF) == 0xCFFFF))
{
@@ -709,7 +750,7 @@ void GSRendererHW::MergeSprite(GSTextureCache::Source* tex)
// SSE optimization: shuffle m[1] to have (4*32 bits) X, Y, U, V
const int first_dpX = v[1].XYZ.X - v[0].XYZ.X;
const int first_dpU = v[1].U - v[0].U;
for (size_t i = 0; i < m_vertex.next; i += 2)
for (u32 i = 0; i < m_vertex.next; i += 2)
{
const int dpX = v[i + 1].XYZ.X - v[i].XYZ.X;
const int dpU = v[i + 1].U - v[i].U;
@@ -1112,10 +1153,10 @@ void GSRendererHW::RoundSpriteOffset()
#if defined(DEBUG_V) || defined(DEBUG_U)
bool debug = linear;
#endif
const size_t count = m_vertex.next;
const u32 count = m_vertex.next;
GSVertex* v = &m_vertex.buff[0];
for (size_t i = 0; i < count; i += 2)
for (u32 i = 0; i < count; i += 2)
{
// Performance note: if it had any impact on perf, someone would port it to SSE (AKA GSVector)
@@ -1266,10 +1307,6 @@ void GSRendererHW::Draw()
return;
}
// Fix TEX0 size
if (PRIM->TME && !IsMipMapActive())
m_context->ComputeFixedTEX0(m_vt.m_min.t.xyxy(m_vt.m_max.t));
// skip alpha test if possible
// Note: do it first so we know if frame/depth writes are masked
@@ -1362,7 +1399,7 @@ void GSRendererHW::Draw()
}
// SW CLUT Render enable.
bool preload = GSConfig.PreloadFrameWithGSData;
bool preload = GSConfig.PreloadFrameWithGSData | m_force_preload;
if (GSConfig.UserHacks_CPUCLUTRender > 0 || GSConfig.UserHacks_GPUTargetCLUTMode != GSGPUTargetCLUTMode::Disabled)
{
const CLUTDrawTestResult result = (GSConfig.UserHacks_CPUCLUTRender == 2) ? PossibleCLUTDrawAggressive() : PossibleCLUTDraw();
@@ -1383,7 +1420,7 @@ void GSRendererHW::Draw()
!IsOpaque()) // Blending enabled
{
GL_INS("Forcing preload due to partial/blended CLUT draw");
preload = true;
preload = m_force_preload = true;
}
}
}
@@ -1422,6 +1459,61 @@ void GSRendererHW::Draw()
m_texture_shuffle = false;
m_tex_is_fb = false;
// The rectangle of the draw
m_r = GSVector4i(m_vt.m_min.p.xyxy(m_vt.m_max.p)).rintersect(GSVector4i(context->scissor.in));
if (!GSConfig.UserHacks_DisableSafeFeatures)
{
if (IsConstantDirectWriteMemClear(true))
{
// Likely doing a huge single page width clear, which never goes well. (Superman)
// Burnout 3 does a 32x1024 double width clear on its reflection targets.
const bool clear_height_valid = (m_r.w >= 1024);
if (clear_height_valid && context->FRAME.FBW == 1)
{
u32 width = ceil(static_cast<float>(m_r.w) / GetFramebufferHeight()) * 64;
// Framebuffer is likely to be read as 16bit later, so we will need to double the width if the write is 32bit.
const bool double_width = GSLocalMemory::m_psm[context->FRAME.PSM].bpp == 32 && GetFramebufferBitDepth() == 16;
m_r.x = 0;
m_r.y = 0;
m_r.w = GetFramebufferHeight();
m_r.z = std::max((width * (double_width ? 2 : 1)), static_cast<u32>(GetFramebufferWidth()));
context->FRAME.FBW = (m_r.z + 63) / 64;
m_context->scissor.in.z = context->FRAME.FBW * 64;
GSVertex* s = &m_vertex.buff[0];
s[0].XYZ.X = static_cast<u16>(m_context->XYOFFSET.OFX + 0);
s[1].XYZ.X = static_cast<u16>(m_context->XYOFFSET.OFX + 16384);
s[0].XYZ.Y = static_cast<u16>(m_context->XYOFFSET.OFY + 0);
s[1].XYZ.Y = static_cast<u16>(m_context->XYOFFSET.OFY + 16384);
m_vertex.head = m_vertex.tail = m_vertex.next = 2;
m_index.tail = 2;
}
// Superman does a clear to white, not black, on its depth buffer.
// Since we don't preload depth, OI_GsMemClear() won't work here, since we invalidate the target later
// on. So, instead, let the draw go through with the expanded rectangle, and copy color->depth.
const bool is_zero_clear = (((GSLocalMemory::m_psm[m_context->FRAME.PSM].fmt == 0) ?
m_vertex.buff[1].RGBAQ.U32[0] :
(m_vertex.buff[1].RGBAQ.U32[0] & ~0xFF000000)) == 0) && m_context->FRAME.FBMSK == 0 && IsBlendedOrOpaque();
if (is_zero_clear && OI_GsMemClear() && clear_height_valid)
{
m_tc->InvalidateVideoMem(context->offset.fb, m_r, false, true);
m_tc->InvalidateVideoMemType(GSTextureCache::RenderTarget, context->FRAME.Block());
if (m_context->ZBUF.ZMSK == 0)
{
m_tc->InvalidateVideoMem(context->offset.zb, m_r, false, false);
m_tc->InvalidateVideoMemType(GSTextureCache::DepthStencil, context->ZBUF.Block());
}
return;
}
}
}
TextureMinMaxResult tmm;
// Disable texture mapping if the blend is black and using alpha from vertex.
if (PRIM->TME && !(PRIM->ABE && m_context->ALPHA.IsBlack() && !m_context->TEX0.TCC))
{
@@ -1513,19 +1605,58 @@ void GSRendererHW::Draw()
m_context->offset.tex = m_mem.GetOffset(TEX0.TBP0, TEX0.TBW, TEX0.PSM);
TextureMinMaxResult tmm = GetTextureMinMax(TEX0, MIP_CLAMP, m_vt.IsLinear());
tmm = GetTextureMinMax(TEX0, MIP_CLAMP, m_vt.IsLinear());
m_src = tex_psm.depth ? m_tc->LookupDepthSource(TEX0, env.TEXA, tmm.coverage) :
m_tc->LookupSource(TEX0, env.TEXA, tmm.coverage, (GSConfig.HWMipmap >= HWMipmapLevel::Basic ||
m_src = tex_psm.depth ? m_tc->LookupDepthSource(TEX0, env.TEXA, MIP_CLAMP, tmm.coverage) :
m_tc->LookupSource(TEX0, env.TEXA, MIP_CLAMP, tmm.coverage, (GSConfig.HWMipmap >= HWMipmapLevel::Basic ||
GSConfig.TriFilter == TriFiltering::Forced) ? &hash_lod_range : nullptr);
}
GSVector2i unscaled_target_size;
const GSVector2i t_size = GetTargetSize(&unscaled_target_size);
// Ensure draw rect is clamped to framebuffer size. Necessary for updating valid area.
m_r = m_r.rintersect(GSVector4i(0, 0, unscaled_target_size.x, unscaled_target_size.y));
TEX0.TBP0 = context->FRAME.Block();
TEX0.TBW = context->FRAME.FBW;
TEX0.PSM = context->FRAME.PSM;
GSTextureCache::Target* rt = nullptr;
if (!no_rt)
rt = m_tc->LookupTarget(TEX0, t_size, GSTextureCache::RenderTarget, true, fm, false, unscaled_target_size.x, unscaled_target_size.y, preload);
TEX0.TBP0 = context->ZBUF.Block();
TEX0.TBW = context->FRAME.FBW;
TEX0.PSM = context->ZBUF.PSM;
GSTextureCache::Target* ds = nullptr;
if (!no_ds)
ds = m_tc->LookupTarget(TEX0, t_size, GSTextureCache::DepthStencil, context->DepthWrite(), 0, false, 0, 0, preload);
if (PRIM->TME)
{
GIFRegCLAMP MIP_CLAMP = context->CLAMP;
bool copy_16bit_to_target_shuffle = false;
if (rt)
{
// copy of a 16bit source in to this target, make sure it's opaque and not bilinear to reduce false positives.
copy_16bit_to_target_shuffle = context->TEX0.TBP0 != context->FRAME.Block() && rt->m_32_bits_fmt == true && IsOpaque() && !(context->TEX1.MMIN & 1);
}
// Hypothesis: texture shuffle is used as a postprocessing effect so texture will be an old target.
// Initially code also tested the RT but it gives too much false-positive
//
// Both input and output are 16 bits and texture was initially 32 bits!
m_texture_shuffle = (GSLocalMemory::m_psm[context->FRAME.PSM].bpp == 16) && (tex_psm.bpp == 16)
&& draw_sprite_tex && m_src->m_32_bits_fmt;
&& draw_sprite_tex && (m_src->m_32_bits_fmt || copy_16bit_to_target_shuffle);
if (copy_16bit_to_target_shuffle && m_texture_shuffle)
DevCon.Warning("here");
// Okami mustn't call this code
if (m_texture_shuffle && m_vertex.next < 3 && PRIM->FST && ((m_context->FRAME.FBMSK & fm_mask) == 0))
{
@@ -1562,7 +1693,6 @@ void GSRendererHW::Draw()
{
m_channel_shuffle = false;
}
#if 0
// FIXME: We currently crop off the rightmost and bottommost pixel when upscaling clamps,
// until the issue is properly solved we should keep this disabled as it breaks many games when upscaling.
@@ -1585,6 +1715,7 @@ void GSRendererHW::Draw()
const int tw = 1 << TEX0.TW;
const int th = 1 << TEX0.TH;
const bool is_shuffle = m_channel_shuffle || m_texture_shuffle;
// If m_src is from a target that isn't the same size as the texture, texture sample edge modes won't work quite the same way
// If the game actually tries to access stuff outside of the rendered target, it was going to get garbage anyways so whatever
// But the game could issue reads that wrap to valid areas, so move wrapping to the shader if wrapping is used
@@ -1629,7 +1760,7 @@ void GSRendererHW::Draw()
for (int layer = m_lod.x + 1; layer <= m_lod.y; layer++)
{
const GIFRegTEX0& MIP_TEX0 = GetTex0Layer(layer);
const GIFRegTEX0 MIP_TEX0(GetTex0Layer(layer));
m_context->offset.tex = m_mem.GetOffset(MIP_TEX0.TBP0, MIP_TEX0.TBW, MIP_TEX0.PSM);
@@ -1652,68 +1783,7 @@ void GSRendererHW::Draw()
m_vt.m_max.t = tmax;
}
}
// The rectangle of the draw
m_r = GSVector4i(m_vt.m_min.p.xyxy(m_vt.m_max.p)).rintersect(GSVector4i(context->scissor.in));
if (!GSConfig.UserHacks_DisableSafeFeatures)
{
if (IsConstantDirectWriteMemClear())
{
// Likely doing a huge single page width clear, which never goes well. (Superman)
// Burnout 3 does a 32x1024 double width clear on its reflection targets.
const bool clear_height_valid = (m_r.w >= 1024);
if (clear_height_valid && context->FRAME.FBW == 1)
{
m_r.w = GetFramebufferHeight();
m_r.z = GetFramebufferWidth();
context->FRAME.FBW = (m_r.z + 63) / 64;
}
// Superman does a clear to white, not black, on its depth buffer.
// Since we don't preload depth, OI_GsMemClear() won't work here, since we invalidate the target later
// on. So, instead, let the draw go through with the expanded rectangle, and copy color->depth.
const bool is_zero_clear = (((GSLocalMemory::m_psm[m_context->FRAME.PSM].fmt == 0) ?
m_vertex.buff[1].RGBAQ.U32[0] :
(m_vertex.buff[1].RGBAQ.U32[0] & ~0xFF000000)) == 0);
if (is_zero_clear && OI_GsMemClear() && clear_height_valid)
{
m_tc->InvalidateVideoMem(context->offset.fb, m_r, false, true);
m_tc->InvalidateVideoMemType(GSTextureCache::RenderTarget, context->FRAME.Block());
if (m_context->ZBUF.ZMSK == 0)
{
m_tc->InvalidateVideoMem(context->offset.zb, m_r, false, false);
m_tc->InvalidateVideoMemType(GSTextureCache::DepthStencil, context->ZBUF.Block());
}
return;
}
}
}
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;
if (!no_rt)
rt = m_tc->LookupTarget(TEX0, t_size, GSTextureCache::RenderTarget, true, fm, false, 0, 0, preload);
TEX0.TBP0 = context->ZBUF.Block();
TEX0.TBW = context->FRAME.FBW;
TEX0.PSM = context->ZBUF.PSM;
GSTextureCache::Target* ds = nullptr;
if (!no_ds)
ds = m_tc->LookupTarget(TEX0, t_size, GSTextureCache::DepthStencil, context->DepthWrite(), 0, false, 0, 0, preload);
if (rt)
{
// Be sure texture shuffle detection is properly propagated
@@ -1803,7 +1873,7 @@ void GSRendererHW::Draw()
if (!GSConfig.UserHacks_DisableSafeFeatures)
{
if (IsConstantDirectWriteMemClear())
if (IsConstantDirectWriteMemClear(false) && IsBlendedOrOpaque())
OI_DoubleHalfClear(rt, ds);
}
@@ -1812,7 +1882,7 @@ void GSRendererHW::Draw()
// Note: second hack corrects only the texture coordinate
if (CanUpscale() && (m_vt.m_primclass == GS_SPRITE_CLASS))
{
const size_t count = m_vertex.next;
const u32 count = m_vertex.next;
GSVertex* v = &m_vertex.buff[0];
// Hack to avoid vertical black line in various games (ace combat/tekken)
@@ -1829,7 +1899,7 @@ void GSRendererHW::Draw()
// Normaly vertex are aligned on full pixels and texture in half
// pixels. Let's extend the coverage of an half-pixel to avoid
// hole after upscaling
for (size_t i = 0; i < count; i += 2)
for (u32 i = 0; i < count; i += 2)
{
v[i + 1].XYZ.X += 8;
// I really don't know if it is a good idea. Neither what to do for !PRIM->FST
@@ -1940,7 +2010,7 @@ bool GSRendererHW::VerifyIndices()
[[fallthrough]];
case GS_POINT_CLASS:
// Expect indices to be flat increasing
for (size_t i = 0; i < m_index.tail; i++)
for (u32 i = 0; i < m_index.tail; i++)
{
if (m_index.buff[i] != i)
return false;
@@ -1953,7 +2023,7 @@ bool GSRendererHW::VerifyIndices()
// VS expand relies on this!
if (g_gs_device->Features().provoking_vertex_last)
{
for (size_t i = 0; i < m_index.tail; i += 2)
for (u32 i = 0; i < m_index.tail; i += 2)
{
if (m_index.buff[i] + 1 != m_index.buff[i + 1])
return false;
@@ -1961,7 +2031,7 @@ bool GSRendererHW::VerifyIndices()
}
else
{
for (size_t i = 0; i < m_index.tail; i += 2)
for (u32 i = 0; i < m_index.tail; i += 2)
{
if (m_index.buff[i] != m_index.buff[i + 1] + 1)
return false;
@@ -1984,7 +2054,7 @@ void GSRendererHW::SetupIA(const float& sx, const float& sy)
if (GSConfig.UserHacks_WildHack && !m_isPackedUV_HackFlag && PRIM->TME && PRIM->FST)
{
for (unsigned int i = 0; i < m_vertex.next; i++)
for (u32 i = 0; i < m_vertex.next; i++)
m_vertex.buff[i].UV &= 0x3FEF3FEF;
}
const bool unscale_pt_ln = !GSConfig.UserHacks_DisableSafeFeatures && (GetUpscaleMultiplier() != 1.0f);
@@ -2191,8 +2261,9 @@ void GSRendererHW::EmulateTextureShuffleAndFbmask()
bool write_ba;
bool read_ba;
bool swap_ga;
ConvertSpriteTextureShuffle(write_ba, read_ba);
ConvertSpriteTextureShuffle(write_ba, read_ba, swap_ga);
// If date is enabled you need to test the green channel instead of the
// alpha channel. Only enable this code in DATE mode to reduce the number
@@ -2212,37 +2283,52 @@ void GSRendererHW::EmulateTextureShuffleAndFbmask()
const GSVector2i rb_ga_mask = GSVector2i(fbmask & 0xFF, (fbmask >> 8) & 0xFF);
m_conf.colormask.wrgba = 0;
// 2 Select the new mask
if (rb_ga_mask.r != 0xFF)
if (swap_ga)
{
if (write_ba)
{
GL_INS("Color shuffle %s => B", read_ba ? "B" : "R");
m_conf.colormask.wb = 1;
}
else
{
GL_INS("Color shuffle %s => R", read_ba ? "B" : "R");
m_conf.colormask.wr = 1;
}
if (rb_ga_mask.r)
m_conf.ps.fbmask = 1;
m_conf.ps.swap_ga = true;
m_conf.ps.fbmask = 0;
GL_CACHE("I hate myself");
m_conf.colormask.wg = 1;
m_conf.colormask.wa = 1;
m_skip = 1;
DevCon.Warning("Kill me");
m_conf.ps.tex_is_fb = true;
return;
}
if (rb_ga_mask.g != 0xFF)
else
{
if (write_ba)
// 2 Select the new mask
if (rb_ga_mask.r != 0xFF)
{
GL_INS("Color shuffle %s => A", read_ba ? "A" : "G");
m_conf.colormask.wa = 1;
if (write_ba)
{
DevCon.Warning("Color shuffle %s => B", read_ba ? "B" : "R");
m_conf.colormask.wb = 1;
}
else
{
DevCon.Warning("Color shuffle %s => R", read_ba ? "B" : "R");
m_conf.colormask.wr = 1;
}
if (rb_ga_mask.r)
m_conf.ps.fbmask = 1;
}
else
if (rb_ga_mask.g != 0xFF)
{
GL_INS("Color shuffle %s => G", read_ba ? "A" : "G");
m_conf.colormask.wg = 1;
if (write_ba)
{
DevCon.Warning("Color shuffle %s => A", read_ba ? "A" : "G");
m_conf.colormask.wa = 1;
}
else
{
DevCon.Warning("Color shuffle %s => G", read_ba ? "A" : "G");
m_conf.colormask.wg = 1;
}
if (rb_ga_mask.g)
m_conf.ps.fbmask = 1;
}
if (rb_ga_mask.g)
m_conf.ps.fbmask = 1;
}
if (m_conf.ps.fbmask && enable_fbmask_emulation)
@@ -2255,12 +2341,12 @@ void GSRendererHW::EmulateTextureShuffleAndFbmask()
// No blending so hit unsafe path.
if (!PRIM->ABE || !features.texture_barrier)
{
GL_INS("FBMASK Unsafe SW emulated fb_mask:%x on tex shuffle", fbmask);
DevCon.Warning("FBMASK Unsafe SW emulated fb_mask:%x on tex shuffle", fbmask);
m_conf.require_one_barrier = true;
}
else
{
GL_INS("FBMASK SW emulated fb_mask:%x on tex shuffle", fbmask);
DevCon.Warning("FBMASK SW emulated fb_mask:%x on tex shuffle", fbmask);
m_conf.require_full_barrier = true;
}
}
@@ -2312,14 +2398,14 @@ void GSRendererHW::EmulateTextureShuffleAndFbmask()
// No blending so hit unsafe path.
if (!PRIM->ABE || !(~ff_fbmask & ~zero_fbmask & 0x7) || !g_gs_device->Features().texture_barrier)
{
GL_INS("FBMASK Unsafe SW emulated fb_mask:%x on %d bits format", m_context->FRAME.FBMSK,
DevCon.Warning("FBMASK Unsafe SW emulated fb_mask:%x on %d bits format", m_context->FRAME.FBMSK,
(m_conf.ps.dfmt == 2) ? 16 : 32);
m_conf.require_one_barrier = true;
}
else
{
// The safe and accurate path (but slow)
GL_INS("FBMASK SW emulated fb_mask:%x on %d bits format", m_context->FRAME.FBMSK,
DevCon.Warning("FBMASK SW emulated fb_mask:%x on %d bits format", m_context->FRAME.FBMSK,
(m_conf.ps.dfmt == 2) ? 16 : 32);
m_conf.require_full_barrier = true;
}
@@ -3076,6 +3162,26 @@ void GSRendererHW::EmulateBlending(bool& DATE_PRIMID, bool& DATE_BARRIER, bool&
}
}
__ri static constexpr bool IsRedundantClamp(u8 clamp, u32 clamp_min, u32 clamp_max, u32 tsize)
{
// Don't shader sample when the clamp/repeat is configured to the texture size.
// That way trilinear etc still works.
const u32 textent = (1u << tsize) - 1u;
if (clamp == CLAMP_REGION_CLAMP)
return (clamp_min == 0 && clamp_max == textent);
else if (clamp == CLAMP_REGION_REPEAT)
return (clamp_max == 0 && clamp_min == textent);
else
return false;
}
__ri static constexpr u8 EffectiveClamp(u8 clamp, bool has_region)
{
// When we have extracted the region in the texture, we can use the hardware sampler for repeat/clamp.
// (weird flip here because clamp/repeat is inverted for region vs non-region).
return (clamp >= CLAMP_REGION_CLAMP && has_region) ? (clamp ^ 3) : clamp;
}
void GSRendererHW::EmulateTextureSampler(const GSTextureCache::Source* tex)
{
// Warning fetch the texture PSM format rather than the context format. The latter could have been corrected in the texture cache for depth.
@@ -3083,9 +3189,20 @@ void GSRendererHW::EmulateTextureSampler(const GSTextureCache::Source* tex)
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[tex->m_TEX0.PSM];
const GSLocalMemory::psm_t& cpsm = psm.pal > 0 ? GSLocalMemory::m_psm[m_context->TEX0.CPSM] : psm;
const u8 wms = m_context->CLAMP.WMS;
const u8 wmt = m_context->CLAMP.WMT;
// Redundant clamp tests are restricted to local memory/1x sources only, if we're from a target,
// we keep the shader clamp. See #5851 on github, and the note in Draw().
[[maybe_unused]] static constexpr const char* clamp_modes[] = { "REPEAT", "CLAMP", "REGION_CLAMP", "REGION_REPEAT" };
const bool redundant_wms = !tex->m_target && IsRedundantClamp(m_context->CLAMP.WMS, m_context->CLAMP.MINU,
m_context->CLAMP.MAXU, tex->m_TEX0.TW);
const bool redundant_wmt = !tex->m_target && IsRedundantClamp(m_context->CLAMP.WMT, m_context->CLAMP.MINV,
m_context->CLAMP.MAXV, tex->m_TEX0.TH);
const u8 wms = EffectiveClamp(m_context->CLAMP.WMS, tex->m_region.HasX() || redundant_wms);
const u8 wmt = EffectiveClamp(m_context->CLAMP.WMT, tex->m_region.HasY() || redundant_wmt);
const bool complex_wms_wmt = !!((wms | wmt) & 2);
GL_CACHE("WMS: %s [%s%s] WMT: %s [%s%s] Complex: %d MINU: %d MINV: %d MINV: %d MAXV: %d",
clamp_modes[m_context->CLAMP.WMS], redundant_wms ? "redundant," : "", clamp_modes[wms],
clamp_modes[m_context->CLAMP.WMT], redundant_wmt ? "redundant," : "", clamp_modes[wmt],
complex_wms_wmt, m_context->CLAMP.MINU, m_context->CLAMP.MINV, m_context->CLAMP.MAXU, m_context->CLAMP.MAXV);
const bool need_mipmap = IsMipMapDraw();
const bool shader_emulated_sampler = tex->m_palette || cpsm.fmt != 0 || complex_wms_wmt || psm.depth;
@@ -3261,14 +3378,38 @@ void GSRendererHW::EmulateTextureSampler(const GSTextureCache::Source* tex)
const GSVector4 st_scale = WH.zwzw() / GSVector4(w, h).xyxy();
m_conf.cb_ps.STScale = GSVector2(st_scale.x, st_scale.y);
if (tex->m_region.HasX())
{
m_conf.cb_ps.STRange.x = static_cast<float>(tex->m_region.GetMinX()) / static_cast<float>(miptw);
m_conf.cb_ps.STRange.z = static_cast<float>(miptw) / static_cast<float>(tex->m_region.GetWidth());
m_conf.ps.adjs = 1;
}
if (tex->m_region.HasY())
{
m_conf.cb_ps.STRange.y = static_cast<float>(tex->m_region.GetMinY()) / static_cast<float>(mipth);
m_conf.cb_ps.STRange.w = static_cast<float>(mipth) / static_cast<float>(tex->m_region.GetHeight());
m_conf.ps.adjt = 1;
}
m_conf.ps.fst = !!PRIM->FST;
m_conf.cb_ps.WH = WH;
m_conf.cb_ps.HalfTexel = GSVector4(-0.5f, 0.5f).xxyy() / WH.zwzw();
if (complex_wms_wmt)
{
m_conf.cb_ps.MskFix = GSVector4i(m_context->CLAMP.MINU, m_context->CLAMP.MINV, m_context->CLAMP.MAXU, m_context->CLAMP.MAXV);;
m_conf.cb_ps.MinMax = GSVector4(m_conf.cb_ps.MskFix) / WH.xyxy();
const GSVector4i clamp(m_context->CLAMP.MINU, m_context->CLAMP.MINV, m_context->CLAMP.MAXU, m_context->CLAMP.MAXV);
const GSVector4 region_repeat(GSVector4::cast(clamp));
const GSVector4 region_clamp(GSVector4(clamp) / WH.xyxy());
if (wms >= CLAMP_REGION_CLAMP)
{
m_conf.cb_ps.MinMax.x = (wms == CLAMP_REGION_CLAMP && !m_conf.ps.depth_fmt) ? region_clamp.x : region_repeat.x;
m_conf.cb_ps.MinMax.z = (wms == CLAMP_REGION_CLAMP && !m_conf.ps.depth_fmt) ? region_clamp.z : region_repeat.z;
}
if (wmt >= CLAMP_REGION_CLAMP)
{
m_conf.cb_ps.MinMax.y = (wmt == CLAMP_REGION_CLAMP && !m_conf.ps.depth_fmt) ? region_clamp.y : region_repeat.y;
m_conf.cb_ps.MinMax.w = (wmt == CLAMP_REGION_CLAMP && !m_conf.ps.depth_fmt) ? region_clamp.w : region_repeat.w;
}
}
else if (trilinear_manual)
{
@@ -3289,18 +3430,6 @@ void GSRendererHW::EmulateTextureSampler(const GSTextureCache::Source* tex)
m_conf.cb_ps.TCOffsetHack = GSVector2(tc_oh_ts.z, tc_oh_ts.w);
m_conf.cb_vs.texture_scale = GSVector2(tc_oh_ts.x, tc_oh_ts.y);
// Must be done after all coordinates math
if (m_context->HasFixedTEX0() && !PRIM->FST)
{
m_conf.ps.invalid_tex0 = 1;
// Use invalid size to denormalize ST coordinate
m_conf.cb_ps.WH.x = static_cast<float>(1 << m_context->stack.TEX0.TW);
m_conf.cb_ps.WH.y = static_cast<float>(1 << m_context->stack.TEX0.TH);
// We can't handle m_target with invalid_tex0 atm due to upscaling
ASSERT(!tex->m_target);
}
// Only enable clamping in CLAMP mode. REGION_CLAMP will be done manually in the shader
m_conf.sampler.tau = (wms != CLAMP_CLAMP);
m_conf.sampler.tav = (wmt != CLAMP_CLAMP);
@@ -3997,11 +4126,19 @@ GSRendererHW::CLUTDrawTestResult GSRendererHW::PossibleCLUTDraw()
// If we have GPU CLUT enabled, don't do a CPU draw when it would result in a download.
if (GSConfig.UserHacks_GPUTargetCLUTMode != GSGPUTargetCLUTMode::Disabled)
{
std::vector<GSState::GSUploadQueue>::iterator iter;
for (iter = m_draw_transfers.begin(); iter != m_draw_transfers.end(); ) {
if (iter->blit.DBP == m_context->TEX0.TBP0 && GSUtil::HasSharedBits(iter->blit.DPSM, m_context->TEX0.PSM))
return CLUTDrawTestResult::CLUTDrawOnCPU;
iter++;
}
GSTextureCache::Target* tgt = m_tc->GetExactTarget(m_context->TEX0.TBP0, m_context->TEX0.TBW, m_context->TEX0.PSM);
if (tgt)
{
bool is_dirty = false;
for (const GSDirtyRect& rc : tgt->m_dirty)
for (GSDirtyRect& rc : tgt->m_dirty)
{
if (!rc.GetDirtyRect(m_context->TEX0).rintersect(r).rempty())
{
@@ -4016,6 +4153,16 @@ GSRendererHW::CLUTDrawTestResult GSRendererHW::PossibleCLUTDraw()
}
}
}
else
{
std::vector<GSState::GSUploadQueue>::iterator iter;
for (iter = m_draw_transfers.begin(); iter != m_draw_transfers.end(); ) {
if (iter->blit.DBP == m_context->TEX0.TBP0 && GSUtil::HasSharedBits(iter->blit.DPSM, m_context->TEX0.PSM))
return CLUTDrawTestResult::CLUTDrawOnCPU;
iter++;
}
}
GIFRegBITBLTBUF BITBLTBUF = {};
BITBLTBUF.SBP = m_context->TEX0.TBP0;
@@ -4212,7 +4359,6 @@ bool GSRendererHW::OI_GsMemClear()
const GSOffset& off = m_context->offset.fb;
GSVector4i r = GSVector4i(m_vt.m_min.p.xyxy(m_vt.m_max.p)).rintersect(GSVector4i(m_context->scissor.in));
if (r.width() == 32 && ZisFrame)
r.z += 32;
// Limit the hack to a single full buffer clear. Some games might use severals column to clear a screen
@@ -4229,7 +4375,6 @@ bool GSRendererHW::OI_GsMemClear()
vert_color &= ~0xFF000000;
const u32 color = (format == 0) ? vert_color : (vert_color & ~0xFF000000);
// FIXME: loop can likely be optimized with AVX/SSE. Pixels aren't
// linear but the value will be done for all pixels of a block.
// FIXME: maybe we could limit the write to the top and bottom row page.
@@ -4276,6 +4421,7 @@ bool GSRendererHW::OI_GsMemClear()
}
#endif
}
return true;
}
return false;
@@ -4339,17 +4485,20 @@ bool GSRendererHW::OI_BlitFMV(GSTextureCache::Target* _rt, GSTextureCache::Sourc
// Nothing to see keep going
return true;
}
bool GSRendererHW::IsBlendedOrOpaque()
{
return (!PRIM->ABE || IsOpaque() || m_context->ALPHA.IsCdOutput());
}
bool GSRendererHW::IsConstantDirectWriteMemClear()
bool GSRendererHW::IsConstantDirectWriteMemClear(bool include_zero)
{
// Constant Direct Write without texture/test/blending (aka a GS mem clear)
if ((m_vt.m_primclass == GS_SPRITE_CLASS) && !PRIM->TME // Direct write
&& (!PRIM->ABE || IsOpaque() || m_context->ALPHA.IsCdOutput()) // No transparency
&& (m_context->FRAME.FBMSK == 0) // no color mask
&& (m_context->FRAME.FBMSK == 0 || (include_zero && m_vt.m_max.c.eq(GSVector4i::zero()))) // no color mask
&& !(m_env.SCANMSK.MSK & 2)
&& !m_context->TEST.ATE // no alpha test
&& (!m_context->TEST.ZTE || m_context->TEST.ZTST == ZTST_ALWAYS) // no depth test
&& (m_vt.m_eq.rgba == 0xFFFF) // constant color write
&& (m_vt.m_eq.rgba == 0xFFFF || m_vertex.next == 2) // constant color write
&& m_r.x == 0 && m_r.y == 0) // Likely full buffer write
return true;

View File

@@ -65,7 +65,8 @@ private:
float alpha1(int L, int X0, int X1);
void SwSpriteRender();
bool CanUseSwSpriteRender();
bool IsConstantDirectWriteMemClear();
bool IsConstantDirectWriteMemClear(bool include_zero);
bool IsBlendedOrOpaque();
enum class CLUTDrawTestResult
{
@@ -139,7 +140,7 @@ public:
void Lines2Sprites();
bool VerifyIndices();
template <GSHWDrawConfig::VSExpand Expand> void ExpandIndices();
void ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba);
void ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba, bool& swap_ga);
GSVector4 RealignTargetTextureCoordinate(const GSTextureCache::Source* tex);
GSVector4i ComputeBoundingBox(const GSVector2& rtscale, const GSVector2i& rtsize);
void MergeSprite(GSTextureCache::Source* tex);

File diff suppressed because it is too large Load Diff

View File

@@ -44,6 +44,44 @@ public:
return valid && overlap;
}
struct SourceRegion
{
u64 bits;
bool HasX() const { return static_cast<u32>(bits) != 0; }
bool HasY() const { return static_cast<u32>(bits >> 32) != 0; }
bool HasEither() const { return (bits != 0); }
void SetX(u32 min, u32 max) { bits |= (min | (max << 16)); }
void SetY(u32 min, u32 max) { bits |= ((static_cast<u64>(min) << 32) | (static_cast<u64>(max) << 48)); }
u32 GetMinX() const { return static_cast<u32>(bits) & 0xFFFFu; }
u32 GetMaxX() const { return static_cast<u32>(bits >> 16) & 0xFFFFu; }
u32 GetMinY() const { return static_cast<u32>(bits >> 32) & 0xFFFFu; }
u32 GetMaxY() const { return static_cast<u32>(bits >> 48); }
u32 GetWidth() const { return (GetMaxX() - GetMinX()); }
u32 GetHeight() const { return (GetMaxY() - GetMinY()); }
/// Returns true if the area of the region exceeds the TW/TH size (i.e. "fixed tex0").
bool IsFixedTEX0(int tw, int th) const;
bool IsFixedTEX0W(int tw) const;
bool IsFixedTEX0H(int th) const;
/// Returns the rectangle relative to the texture base pointer that the region occupies.
GSVector4i GetRect(int tw, int th) const;
/// When TW/TH is less than the extents covered by the region ("fixed tex0"), returns the offset
/// which should be applied to any coordinates to relocate them to the actual region.
GSVector4i GetOffset(int tw, int th) const;
/// Reduces the range of texels relative to the specified mipmap level.
SourceRegion AdjustForMipmap(u32 level) const;
/// Adjusts the texture base pointer and block width relative to the region.
void AdjustTEX0(GIFRegTEX0* TEX0) const;
};
using HashType = u64;
struct HashCacheKey
@@ -51,10 +89,11 @@ public:
HashType TEX0Hash, CLUTHash;
GIFRegTEX0 TEX0;
GIFRegTEXA TEXA;
SourceRegion region;
HashCacheKey();
static HashCacheKey Create(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const u32* clut, const GSVector2i* lod);
static HashCacheKey Create(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const u32* clut, const GSVector2i* lod, SourceRegion region);
HashCacheKey WithRemovedCLUTHash() const;
void RemoveCLUTHash();
@@ -148,7 +187,7 @@ public:
{
GSVector4i* rect;
u32 count;
} m_write;
} m_write = {};
void PreloadLevel(int level);
@@ -161,6 +200,7 @@ public:
GSTexture* m_palette;
GSVector4i m_valid_rect;
GSVector2i m_lod;
SourceRegion m_region = {};
u8 m_valid_hashes = 0;
u8 m_complete_layers = 0;
bool m_target;
@@ -178,11 +218,13 @@ public:
GSOffset::PageLooper m_pages;
public:
Source(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, bool dummy_container = false);
Source(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA);
virtual ~Source();
__fi bool CanPreload() const { return CanPreloadTextureSize(m_TEX0.TW, m_TEX0.TH); }
void SetPages();
void Update(const GSVector4i& rect, int layer = 0);
void UpdateLayer(const GIFRegTEX0& TEX0, const GSVector4i& rect, int layer = 0);
@@ -198,6 +240,7 @@ public:
GSVector4i m_valid;
const bool m_depth_supported;
bool m_dirty_alpha;
bool m_is_frame;
public:
Target(const GIFRegTEX0& TEX0, const bool depth_supported, const int type);
@@ -321,7 +364,7 @@ protected:
std::unique_ptr<GSDownloadTexture> m_uint16_download_texture;
std::unique_ptr<GSDownloadTexture> m_uint32_download_texture;
Source* CreateSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, Target* t, bool half_right, int x_offset, int y_offset, const GSVector2i* lod, const GSVector4i* src_range, GSTexture* gpu_clut);
Source* CreateSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, Target* t, bool half_right, int x_offset, int y_offset, const GSVector2i* lod, const GSVector4i* src_range, GSTexture* gpu_clut, SourceRegion region);
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
@@ -331,10 +374,10 @@ protected:
/// Resizes the download texture if needed.
bool PrepareDownloadTexture(u32 width, u32 height, GSTexture::Format format, std::unique_ptr<GSDownloadTexture>* tex);
HashCacheEntry* LookupHashCache(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, bool& paltex, const u32* clut, const GSVector2i* lod);
HashCacheEntry* LookupHashCache(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, bool& paltex, const u32* clut, const GSVector2i* lod, SourceRegion region);
static void PreloadTexture(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, GSLocalMemory& mem, bool paltex, GSTexture* tex, u32 level);
static HashType HashTexture(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA);
static void PreloadTexture(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, SourceRegion region, GSLocalMemory& mem, bool paltex, GSTexture* tex, u32 level);
static HashType HashTexture(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, SourceRegion region);
// TODO: virtual void Write(Source* s, const GSVector4i& r) = 0;
// TODO: virtual void Write(Target* t, const GSVector4i& r) = 0;
@@ -357,8 +400,8 @@ public:
GSTexture* LookupPaletteSource(u32 CBP, u32 CPSM, u32 CBW, GSVector2i& offset, const GSVector2i& size);
Source* LookupSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GSVector4i& r, const GSVector2i* lod);
Source* LookupDepthSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GSVector4i& r, bool palette = false);
Source* LookupSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GIFRegCLAMP& CLAMP, const GSVector4i& r, const GSVector2i* lod);
Source* LookupDepthSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GIFRegCLAMP& CLAMP, 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_w = 0, const int real_h = 0, bool preload = GSConfig.PreloadFrameWithGSData);
Target* LookupDisplayTarget(const GIFRegTEX0& TEX0, const GSVector2i& size, const int real_w, const int real_h);

View File

@@ -42,6 +42,8 @@
// this is a #define instead of a variable to avoid warnings from non-literal format strings
#define TEXTURE_FILENAME_FORMAT_STRING "%" PRIx64 "-%08x"
#define TEXTURE_FILENAME_CLUT_FORMAT_STRING "%" PRIx64 "-%" PRIx64 "-%08x"
#define TEXTURE_FILENAME_REGION_FORMAT_STRING "%" PRIx64 "-r%" PRIx64 "-" "-%08x"
#define TEXTURE_FILENAME_REGION_CLUT_FORMAT_STRING "%" PRIx64 "-%" PRIx64 "-r%" PRIx64 "-%08x"
#define TEXTURE_REPLACEMENT_SUBDIRECTORY_NAME "replacements"
#define TEXTURE_DUMP_SUBDIRECTORY_NAME "dumps"
@@ -51,6 +53,7 @@ namespace
{
u64 TEX0Hash;
u64 CLUTHash;
GSTextureCache::SourceRegion region;
union
{
@@ -68,9 +71,10 @@ namespace
};
u32 miplevel;
__fi u32 Width() const { return (1u << TEX0_TW); }
__fi u32 Height() const { return (1u << TEX0_TH); }
__fi u32 Width() const { return (region.HasX() ? region.GetWidth() : (1u << TEX0_TW)); }
__fi u32 Height() const { return (region.HasY() ? region.GetWidth() : (1u << TEX0_TH)); }
__fi bool HasPalette() const { return (GSLocalMemory::m_psm[TEX0_PSM].pal > 0); }
__fi bool HasRegion() const { return region.HasEither(); }
__fi GSVector2 ReplacementScale(const GSTextureReplacements::ReplacementTexture& rtex) const
{
@@ -79,14 +83,27 @@ namespace
__fi GSVector2 ReplacementScale(u32 rwidth, u32 rheight) const
{
return GSVector2(static_cast<float>(rwidth) / static_cast<float>(Width()), static_cast<float>(rheight) / static_cast<float>(Height()));
return GSVector2(static_cast<float>(rwidth) / static_cast<float>(Width()),
static_cast<float>(rheight) / static_cast<float>(Height()));
}
__fi bool operator==(const TextureName& rhs) const { return std::tie(TEX0Hash, CLUTHash, bits) == std::tie(rhs.TEX0Hash, rhs.CLUTHash, rhs.bits); }
__fi bool operator!=(const TextureName& rhs) const { return std::tie(TEX0Hash, CLUTHash, bits) != std::tie(rhs.TEX0Hash, rhs.CLUTHash, rhs.bits); }
__fi bool operator<(const TextureName& rhs) const { return std::tie(TEX0Hash, CLUTHash, bits) < std::tie(rhs.TEX0Hash, rhs.CLUTHash, rhs.bits); }
__fi bool operator==(const TextureName& rhs) const
{
return std::tie(TEX0Hash, CLUTHash, region.bits, bits) ==
std::tie(rhs.TEX0Hash, rhs.CLUTHash, region.bits, rhs.bits);
}
__fi bool operator!=(const TextureName& rhs) const
{
return std::tie(TEX0Hash, CLUTHash, region.bits, bits) !=
std::tie(rhs.TEX0Hash, rhs.CLUTHash, region.bits, rhs.bits);
}
__fi bool operator<(const TextureName& rhs) const
{
return std::tie(TEX0Hash, CLUTHash, region.bits, bits) <
std::tie(rhs.TEX0Hash, rhs.CLUTHash, region.bits, rhs.bits);
}
};
static_assert(sizeof(TextureName) == 24, "ReplacementTextureName is expected size");
static_assert(sizeof(TextureName) == 32, "ReplacementTextureName is expected size");
} // namespace
namespace std
@@ -97,7 +114,7 @@ namespace std
std::size_t operator()(const TextureName& val) const
{
std::size_t h = 0;
HashCombine(h, val.TEX0Hash, val.CLUTHash, val.bits, val.miplevel);
HashCombine(h, val.TEX0Hash, val.CLUTHash, val.region.bits, val.bits, val.miplevel);
return h;
}
};
@@ -169,6 +186,7 @@ TextureName GSTextureReplacements::CreateTextureName(const GSTextureCache::HashC
name.TEX0Hash = hash.TEX0Hash;
name.CLUTHash = name.HasPalette() ? hash.CLUTHash : 0;
name.miplevel = miplevel;
name.region = hash.region;
return name;
}
@@ -184,6 +202,7 @@ GSTextureCache::HashCacheKey GSTextureReplacements::HashCacheKeyFromTextureName(
key.TEXA.TA1 = tn.TEXA_TA1;
key.TEX0Hash = tn.TEX0Hash;
key.CLUTHash = tn.HasPalette() ? tn.CLUTHash : 0;
key.region = tn.region;
return key;
}
@@ -192,15 +211,38 @@ std::optional<TextureName> GSTextureReplacements::ParseReplacementName(const std
TextureName ret;
ret.miplevel = 0;
// TODO(Stenzek): Make this better.
char extension_dot;
if (std::sscanf(filename.c_str(), TEXTURE_FILENAME_CLUT_FORMAT_STRING "%c", &ret.TEX0Hash, &ret.CLUTHash, &ret.bits, &extension_dot) != 4 || extension_dot != '.')
if (std::sscanf(filename.c_str(), TEXTURE_FILENAME_REGION_CLUT_FORMAT_STRING "%c", &ret.TEX0Hash, &ret.CLUTHash,
&ret.region.bits, &ret.bits, &extension_dot) == 5 &&
extension_dot == '.')
{
if (std::sscanf(filename.c_str(), TEXTURE_FILENAME_FORMAT_STRING "%c", &ret.TEX0Hash, &ret.bits, &extension_dot) != 3 || extension_dot != '.')
return std::nullopt;
return ret;
}
return ret;
if (std::sscanf(filename.c_str(), TEXTURE_FILENAME_REGION_FORMAT_STRING "%c", &ret.TEX0Hash, &ret.region.bits,
&ret.bits, &extension_dot) == 4 &&
extension_dot == '.')
{
return ret;
}
ret.region.bits = 0;
if (std::sscanf(filename.c_str(), TEXTURE_FILENAME_CLUT_FORMAT_STRING "%c", &ret.TEX0Hash, &ret.CLUTHash, &ret.bits,
&extension_dot) == 4 &&
extension_dot == '.')
{
return ret;
}
if (std::sscanf(filename.c_str(), TEXTURE_FILENAME_FORMAT_STRING "%c", &ret.TEX0Hash, &ret.bits, &extension_dot) ==
3 &&
extension_dot == '.')
{
return ret;
}
return std::nullopt;
}
std::string GSTextureReplacements::GetGameTextureDirectory()
@@ -229,23 +271,45 @@ std::string GSTextureReplacements::GetDumpFilename(const TextureName& name, u32
const std::string game_subdir(Path::Combine(game_dir, TEXTURE_DUMP_SUBDIRECTORY_NAME));
if (name.HasPalette())
std::string filename;
if (name.HasRegion())
{
const std::string filename(
(level > 0) ?
StringUtil::StdStringFromFormat(TEXTURE_FILENAME_CLUT_FORMAT_STRING "-mip%u.png", name.TEX0Hash, name.CLUTHash, name.bits, level) :
StringUtil::StdStringFromFormat(TEXTURE_FILENAME_CLUT_FORMAT_STRING ".png", name.TEX0Hash, name.CLUTHash, name.bits));
ret = Path::Combine(game_subdir, filename);
if (name.HasPalette())
{
filename = (level > 0) ?
StringUtil::StdStringFromFormat(TEXTURE_FILENAME_REGION_CLUT_FORMAT_STRING "-mip%u.png",
name.TEX0Hash, name.CLUTHash, name.region.bits, name.bits, level) :
StringUtil::StdStringFromFormat(TEXTURE_FILENAME_REGION_CLUT_FORMAT_STRING ".png",
name.TEX0Hash, name.CLUTHash, name.region.bits, name.bits);
}
else
{
filename = (level > 0) ? StringUtil::StdStringFromFormat(
TEXTURE_FILENAME_FORMAT_STRING "-mip%u.png", name.TEX0Hash, name.bits, level) :
StringUtil::StdStringFromFormat(
TEXTURE_FILENAME_FORMAT_STRING ".png", name.TEX0Hash, name.bits);
}
}
else
{
const std::string filename(
(level > 0) ?
StringUtil::StdStringFromFormat(TEXTURE_FILENAME_FORMAT_STRING "-mip%u.png", name.TEX0Hash, name.bits, level) :
StringUtil::StdStringFromFormat(TEXTURE_FILENAME_FORMAT_STRING ".png", name.TEX0Hash, name.bits));
ret = Path::Combine(game_subdir, filename);
if (name.HasPalette())
{
filename = (level > 0) ? StringUtil::StdStringFromFormat(TEXTURE_FILENAME_CLUT_FORMAT_STRING "-mip%u.png",
name.TEX0Hash, name.CLUTHash, name.bits, level) :
StringUtil::StdStringFromFormat(TEXTURE_FILENAME_CLUT_FORMAT_STRING ".png",
name.TEX0Hash, name.CLUTHash, name.bits);
}
else
{
filename = (level > 0) ? StringUtil::StdStringFromFormat(
TEXTURE_FILENAME_FORMAT_STRING "-mip%u.png", name.TEX0Hash, name.bits, level) :
StringUtil::StdStringFromFormat(
TEXTURE_FILENAME_FORMAT_STRING ".png", name.TEX0Hash, name.bits);
}
}
ret = Path::Combine(game_subdir, filename);
return ret;
}
@@ -569,7 +633,8 @@ void GSTextureReplacements::ProcessAsyncLoadedTextures()
s_async_loaded_textures.clear();
}
void GSTextureReplacements::DumpTexture(const GSTextureCache::HashCacheKey& hash, const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, GSLocalMemory& mem, u32 level)
void GSTextureReplacements::DumpTexture(const GSTextureCache::HashCacheKey& hash, const GIFRegTEX0& TEX0,
const GIFRegTEXA& TEXA, GSTextureCache::SourceRegion region, GSLocalMemory& mem, u32 level)
{
// check if it's been dumped or replaced already
const TextureName name(CreateTextureName(hash, level));
@@ -589,12 +654,12 @@ void GSTextureReplacements::DumpTexture(const GSTextureCache::HashCacheKey& hash
// compute width/height
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[TEX0.PSM];
const GSVector2i& bs = psm.bs;
const int tw = 1 << TEX0.TW;
const int th = 1 << TEX0.TH;
const GSVector4i rect(0, 0, tw, th);
const int tw = region.HasX() ? region.GetWidth() : (1 << TEX0.TW);
const int th = region.HasY() ? region.GetHeight() : (1 << TEX0.TH);
const GSVector4i rect(region.GetRect(tw, th));
const GSVector4i block_rect(rect.ralign<Align_Outside>(bs));
const int read_width = std::max(tw, psm.bs.x);
const int read_height = std::max(th, psm.bs.y);
const int read_width = block_rect.width();
const int read_height = block_rect.height();
const u32 pitch = static_cast<u32>(read_width) * sizeof(u32);
// use per-texture buffer so we can compress the texture asynchronously and not block the GS thread
@@ -603,8 +668,9 @@ void GSTextureReplacements::DumpTexture(const GSTextureCache::HashCacheKey& hash
psm.rtx(mem, mem.GetOffset(TEX0.TBP0, TEX0.TBW, TEX0.PSM), block_rect, buffer.GetPtr(), pitch, TEXA);
// okay, now we can actually dump it
QueueWorkerThreadItem([filename = std::move(filename), tw, th, pitch, buffer = std::move(buffer)]() {
if (!SavePNGImage(filename.c_str(), tw, th, buffer.GetPtr(), pitch))
const u32 buffer_offset = ((rect.top - block_rect.top) * pitch) + ((rect.left - block_rect.left) * sizeof(u32));
QueueWorkerThreadItem([filename = std::move(filename), tw, th, pitch, buffer = std::move(buffer), buffer_offset]() {
if (!SavePNGImage(filename.c_str(), tw, th, buffer.GetPtr() + buffer_offset, pitch))
Console.Error("Failed to dump texture to '%s'.", filename.c_str());
});
}

View File

@@ -52,7 +52,8 @@ namespace GSTextureReplacements
GSTexture* CreateReplacementTexture(const ReplacementTexture& rtex, const GSVector2& scale, bool mipmap);
void ProcessAsyncLoadedTextures();
void DumpTexture(const GSTextureCache::HashCacheKey& hash, const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, GSLocalMemory& mem, u32 level);
void DumpTexture(const GSTextureCache::HashCacheKey& hash, const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA,
GSTextureCache::SourceRegion region, GSLocalMemory& mem, u32 level);
void ClearDumpedTextureList();
/// Loader will take a filename and interpret the format (e.g. DDS, PNG, etc).

View File

@@ -1127,8 +1127,6 @@ void GSDeviceMTL::CopyRect(GSTexture* sTex, GSTexture* dTex, const GSVector4i& r
void GSDeviceMTL::DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, id<MTLRenderPipelineState> pipeline, bool linear, LoadAction load_action, void* frag_uniform, size_t frag_uniform_len)
{
BeginScene();
FlushClears(sTex);
GSTextureMTL* sT = static_cast<GSTextureMTL*>(sTex);
@@ -1164,8 +1162,6 @@ void GSDeviceMTL::DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTextu
MRESetSampler(linear ? SamplerSelector::Linear() : SamplerSelector::Point());
DrawStretchRect(sRect, dRect, ds);
EndScene();
}
void GSDeviceMTL::DrawStretchRect(const GSVector4& sRect, const GSVector4& dRect, const GSVector2i& ds)
@@ -1378,6 +1374,8 @@ void GSDeviceMTL::MRESetHWPipelineState(GSHWDrawConfig::VSSelector vssel, GSHWDr
setFnConstantB(m_fn_constants, pssel.tcc, GSMTLConstantIndex_PS_TCC);
setFnConstantI(m_fn_constants, pssel.wms, GSMTLConstantIndex_PS_WMS);
setFnConstantI(m_fn_constants, pssel.wmt, GSMTLConstantIndex_PS_WMT);
setFnConstantB(m_fn_constants, pssel.adjs, GSMTLConstantIndex_PS_ADJS);
setFnConstantB(m_fn_constants, pssel.adjt, GSMTLConstantIndex_PS_ADJT);
setFnConstantB(m_fn_constants, pssel.ltf, GSMTLConstantIndex_PS_LTF);
setFnConstantB(m_fn_constants, pssel.shuffle, GSMTLConstantIndex_PS_SHUFFLE);
setFnConstantB(m_fn_constants, pssel.read_ba, GSMTLConstantIndex_PS_READ_BA);
@@ -1407,7 +1405,6 @@ void GSDeviceMTL::MRESetHWPipelineState(GSHWDrawConfig::VSSelector vssel, GSHWDr
setFnConstantB(m_fn_constants, pssel.automatic_lod, GSMTLConstantIndex_PS_AUTOMATIC_LOD);
setFnConstantB(m_fn_constants, pssel.manual_lod, GSMTLConstantIndex_PS_MANUAL_LOD);
setFnConstantB(m_fn_constants, pssel.point_sampler, GSMTLConstantIndex_PS_POINT_SAMPLER);
setFnConstantB(m_fn_constants, pssel.invalid_tex0, GSMTLConstantIndex_PS_INVALID_TEX0);
setFnConstantI(m_fn_constants, pssel.scanmsk, GSMTLConstantIndex_PS_SCANMSK);
auto newps = LoadShader(@"ps_main");
ps = newps;
@@ -1598,10 +1595,10 @@ static_assert(offsetof(GSHWDrawConfig::PSConstantBuffer, WH) == of
static_assert(offsetof(GSHWDrawConfig::PSConstantBuffer, TA_MaxDepth_Af.x) == offsetof(GSMTLMainPSUniform, ta));
static_assert(offsetof(GSHWDrawConfig::PSConstantBuffer, TA_MaxDepth_Af.z) == offsetof(GSMTLMainPSUniform, max_depth));
static_assert(offsetof(GSHWDrawConfig::PSConstantBuffer, TA_MaxDepth_Af.w) == offsetof(GSMTLMainPSUniform, alpha_fix));
static_assert(offsetof(GSHWDrawConfig::PSConstantBuffer, MskFix) == offsetof(GSMTLMainPSUniform, uv_msk_fix));
static_assert(offsetof(GSHWDrawConfig::PSConstantBuffer, FbMask) == offsetof(GSMTLMainPSUniform, fbmask));
static_assert(offsetof(GSHWDrawConfig::PSConstantBuffer, HalfTexel) == offsetof(GSMTLMainPSUniform, half_texel));
static_assert(offsetof(GSHWDrawConfig::PSConstantBuffer, MinMax) == offsetof(GSMTLMainPSUniform, uv_min_max));
static_assert(offsetof(GSHWDrawConfig::PSConstantBuffer, STRange) == offsetof(GSMTLMainPSUniform, st_range));
static_assert(offsetof(GSHWDrawConfig::PSConstantBuffer, ChannelShuffle) == offsetof(GSMTLMainPSUniform, channel_shuffle));
static_assert(offsetof(GSHWDrawConfig::PSConstantBuffer, TCOffsetHack) == offsetof(GSMTLMainPSUniform, tc_offset));
static_assert(offsetof(GSHWDrawConfig::PSConstantBuffer, STScale) == offsetof(GSMTLMainPSUniform, st_scale));
@@ -1609,7 +1606,6 @@ static_assert(offsetof(GSHWDrawConfig::PSConstantBuffer, DitherMatrix) == of
void GSDeviceMTL::SetupDestinationAlpha(GSTexture* rt, GSTexture* ds, const GSVector4i& r, bool datm)
{
BeginScene();
FlushClears(rt);
BeginRenderPass(@"Destination Alpha Setup", nullptr, MTLLoadActionDontCare, nullptr, MTLLoadActionDontCare, ds, MTLLoadActionDontCare);
[m_current_render.encoder setStencilReferenceValue:1];
@@ -1617,7 +1613,6 @@ void GSDeviceMTL::SetupDestinationAlpha(GSTexture* rt, GSTexture* ds, const GSVe
RenderCopy(nullptr, m_stencil_clear_pipeline, r);
MRESetDSS(m_dss_stencil_write);
RenderCopy(rt, m_datm_pipeline[datm], r);
EndScene();
}
static id<MTLTexture> getTexture(GSTexture* tex)
@@ -1696,7 +1691,6 @@ void GSDeviceMTL::RenderHW(GSHWDrawConfig& config)
break;
}
BeginScene();
GSTexture* hdr_rt = nullptr;
if (config.ps.hdr)
{

View File

@@ -108,11 +108,15 @@ struct GSMTLMainPSUniform
vector_float2 ta;
float max_depth;
float alpha_fix;
vector_uint4 uv_msk_fix;
vector_uint4 fbmask;
vector_float4 half_texel;
vector_float4 uv_min_max;
union
{
vector_float4 uv_min_max;
vector_uint4 uv_msk_fix;
};
vector_float4 st_range;
struct
{
unsigned int blue_mask;
@@ -166,6 +170,8 @@ enum GSMTLFnConstants
GSMTLConstantIndex_PS_TCC,
GSMTLConstantIndex_PS_WMS,
GSMTLConstantIndex_PS_WMT,
GSMTLConstantIndex_PS_ADJS,
GSMTLConstantIndex_PS_ADJT,
GSMTLConstantIndex_PS_LTF,
GSMTLConstantIndex_PS_SHUFFLE,
GSMTLConstantIndex_PS_READ_BA,
@@ -194,6 +200,5 @@ enum GSMTLFnConstants
GSMTLConstantIndex_PS_AUTOMATIC_LOD,
GSMTLConstantIndex_PS_MANUAL_LOD,
GSMTLConstantIndex_PS_POINT_SAMPLER,
GSMTLConstantIndex_PS_INVALID_TEX0,
GSMTLConstantIndex_PS_SCANMSK,
};

View File

@@ -37,6 +37,8 @@ constant uint PS_TFX [[function_constant(GSMTLConstantIndex_PS_TF
constant bool PS_TCC [[function_constant(GSMTLConstantIndex_PS_TCC)]];
constant uint PS_WMS [[function_constant(GSMTLConstantIndex_PS_WMS)]];
constant uint PS_WMT [[function_constant(GSMTLConstantIndex_PS_WMT)]];
constant bool PS_ADJS [[function_constant(GSMTLConstantIndex_PS_ADJS)]];
constant bool PS_ADJT [[function_constant(GSMTLConstantIndex_PS_ADJT)]];
constant bool PS_LTF [[function_constant(GSMTLConstantIndex_PS_LTF)]];
constant bool PS_SHUFFLE [[function_constant(GSMTLConstantIndex_PS_SHUFFLE)]];
constant bool PS_READ_BA [[function_constant(GSMTLConstantIndex_PS_READ_BA)]];
@@ -65,7 +67,6 @@ constant bool PS_TEX_IS_FB [[function_constant(GSMTLConstantIndex_PS_TE
constant bool PS_AUTOMATIC_LOD [[function_constant(GSMTLConstantIndex_PS_AUTOMATIC_LOD)]];
constant bool PS_MANUAL_LOD [[function_constant(GSMTLConstantIndex_PS_MANUAL_LOD)]];
constant bool PS_POINT_SAMPLER [[function_constant(GSMTLConstantIndex_PS_POINT_SAMPLER)]];
constant bool PS_INVALID_TEX0 [[function_constant(GSMTLConstantIndex_PS_INVALID_TEX0)]];
constant uint PS_SCANMSK [[function_constant(GSMTLConstantIndex_PS_SCANMSK)]];
constant GSMTLExpandType VS_EXPAND_TYPE = static_cast<GSMTLExpandType>(VS_EXPAND_TYPE_RAW);
@@ -321,7 +322,21 @@ struct PSMain
// As of 2018 this issue is still present.
uv = (trunc(uv * cb.wh.zw) + 0.5) / cb.wh.zw;
}
uv *= cb.st_scale;
if (!PS_ADJS && !PS_ADJT)
{
uv *= cb.st_scale;
}
else
{
if (PS_ADJS)
uv.x = (uv.x - cb.st_range.x) * cb.st_range.z;
else
uv.x = uv.x * cb.st_scale.x;
if (PS_ADJT)
uv.y = (uv.y - cb.st_range.y) * cb.st_range.w;
else
uv.y = uv.y * cb.st_scale.y;
}
if (PS_AUTOMATIC_LOD)
{
@@ -360,7 +375,7 @@ struct PSMain
float4 clamp_wrap_uv(float4 uv)
{
float4 uv_out = uv;
float4 tex_size = PS_INVALID_TEX0 ? cb.wh.zwzw : cb.wh.xyxy;
float4 tex_size = cb.wh.xyxy;
if (PS_WMS == PS_WMT)
{
@@ -724,12 +739,7 @@ struct PSMain
float4 ps_color()
{
float2 st, st_int;
if (!FST && PS_INVALID_TEX0)
{
st = (in.t.xy * cb.wh.xy) / (in.t.w * cb.wh.zw);
st_int = (in.ti.zw * cb.wh.xy) / (in.t.w * cb.wh.zw);
}
else if (!FST)
if (!FST)
{
st = in.t.xy / in.t.w;
st_int = in.ti.zw / in.t.w;

View File

@@ -1029,6 +1029,8 @@ std::string GSDeviceOGL::GetPSSource(const PSSelector& sel)
std::string macro = fmt::format("#define PS_FST {}\n", sel.fst)
+ fmt::format("#define PS_WMS {}\n", sel.wms)
+ fmt::format("#define PS_WMT {}\n", sel.wmt)
+ fmt::format("#define PS_ADJS {}\n", sel.adjs)
+ fmt::format("#define PS_ADJT {}\n", sel.adjt)
+ fmt::format("#define PS_AEM_FMT {}\n", sel.aem_fmt)
+ fmt::format("#define PS_PAL_FMT {}\n", sel.pal_fmt)
+ fmt::format("#define PS_DFMT {}\n", sel.dfmt)
@@ -1037,7 +1039,6 @@ std::string GSDeviceOGL::GetPSSource(const PSSelector& sel)
+ fmt::format("#define PS_URBAN_CHAOS_HLE {}\n", sel.urban_chaos_hle)
+ fmt::format("#define PS_TALES_OF_ABYSS_HLE {}\n", sel.tales_of_abyss_hle)
+ fmt::format("#define PS_TEX_IS_FB {}\n", sel.tex_is_fb)
+ fmt::format("#define PS_INVALID_TEX0 {}\n", sel.invalid_tex0)
+ fmt::format("#define PS_AEM {}\n", sel.aem)
+ fmt::format("#define PS_TFX {}\n", sel.tfx)
+ fmt::format("#define PS_TCC {}\n", sel.tcc)
@@ -1093,7 +1094,6 @@ void GSDeviceOGL::BlitRect(GSTexture* sTex, const GSVector4i& r, const GSVector2
const GSVector4 float_r(r);
BeginScene();
m_convert.ps[static_cast<int>(ShaderConvert::COPY)].Bind();
OMSetDepthStencilState(m_convert.dss);
OMSetBlendState();
@@ -1101,7 +1101,6 @@ void GSDeviceOGL::BlitRect(GSTexture* sTex, const GSVector4i& r, const GSVector2
PSSetShaderResource(0, sTex);
PSSetSamplerState(linear ? m_convert.ln : m_convert.pt);
DrawStretchRect(float_r / (GSVector4(sTex->GetSize()).xyxy()), float_r, dsize);
EndScene();
glEnable(GL_SCISSOR_TEST);
}
@@ -1190,8 +1189,6 @@ void GSDeviceOGL::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture
// Init
// ************************************
BeginScene();
GL_PUSH("StretchRect from %d to %d", sTex->GetID(), dTex->GetID());
if (draw_in_depth)
OMSetRenderTargets(NULL, dTex);
@@ -1223,20 +1220,12 @@ void GSDeviceOGL::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture
// Draw
// ************************************
DrawStretchRect(sRect, dRect, dTex->GetSize());
// ************************************
// End
// ************************************
EndScene();
}
void GSDeviceOGL::PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, PresentShader shader, float shaderTime, bool linear)
{
ASSERT(sTex);
BeginScene();
const GSVector2i ds(dTex ? dTex->GetSize() : GSVector2i(g_host_display->GetWindowWidth(), g_host_display->GetWindowHeight()));
DisplayConstantBuffer cb;
cb.SetSource(sRect, sTex->GetSize());
@@ -1269,14 +1258,10 @@ void GSDeviceOGL::PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture
// Only flipping the backbuffer is transparent (I hope)...
const GSVector4 flip_sr(sRect.xwzy());
DrawStretchRect(flip_sr, dRect, ds);
EndScene();
}
void GSDeviceOGL::UpdateCLUTTexture(GSTexture* sTex, u32 offsetX, u32 offsetY, GSTexture* dTex, u32 dOffset, u32 dSize)
{
BeginScene();
const ShaderConvert shader = (dSize == 16) ? ShaderConvert::CLUT_4 : ShaderConvert::CLUT_8;
GL::Program& prog = m_convert.ps[static_cast<int>(shader)];
prog.Bind();
@@ -1293,8 +1278,6 @@ void GSDeviceOGL::UpdateCLUTTexture(GSTexture* sTex, u32 offsetX, u32 offsetY, G
const GSVector4 dRect(0, 0, dSize, 1);
DrawStretchRect(GSVector4::zero(), dRect, dTex->GetSize());
EndScene();
}
void GSDeviceOGL::DrawStretchRect(const GSVector4& sRect, const GSVector4& dRect, const GSVector2i& ds)
@@ -1458,8 +1441,6 @@ void GSDeviceOGL::SetupDATE(GSTexture* rt, GSTexture* ds, const GSVertexPT1* ver
// sfex3 (after the capcom logo), vf4 (first menu fading in), ffxii shadows, rumble roses shadows, persona4 shadows
BeginScene();
ClearStencil(ds, 0);
m_convert.ps[static_cast<int>(datm ? ShaderConvert::DATM_1 : ShaderConvert::DATM_0)].Bind();
@@ -1490,8 +1471,6 @@ void GSDeviceOGL::SetupDATE(GSTexture* rt, GSTexture* ds, const GSVertexPT1* ver
{
glEnable(GL_BLEND);
}
EndScene();
}
void GSDeviceOGL::IASetVertexBuffer(const void* vertices, size_t count)
@@ -1894,8 +1873,6 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
CopyRect(config.rt, draw_rt_clone, config.drawarea, config.drawarea.left, config.drawarea.top);
}
BeginScene();
IASetVertexBuffer(config.verts, config.nverts);
IASetIndexBuffer(config.indices, config.nindices);
GLenum topology = 0;
@@ -2047,10 +2024,6 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
if (draw_rt_clone)
Recycle(draw_rt_clone);
EndScene();
// Warning: EndScene must be called before StretchRect otherwise
// vertices will be overwritten. Trust me you don't want to do that.
if (hdr_rt)
{
GSVector2i size = config.rt->GetSize();

View File

@@ -21,10 +21,17 @@
#include "GS/GSPerfMon.h"
#include "GS/GSPng.h"
#include "GS/GSGL.h"
#include "common/Align.h"
#include "common/AlignedMalloc.h"
#include "common/StringUtil.h"
static constexpr u32 TEXTURE_UPLOAD_ALIGNMENT = 256;
// Looking across a range of GPUs, the optimal copy alignment for Vulkan drivers seems
// to be between 1 (AMD/NV) and 64 (Intel). So, we'll go with 64 here.
static constexpr u32 TEXTURE_UPLOAD_ALIGNMENT = 64;
// The pitch alignment must be less or equal to the upload alignment.
// We need 32 here for AVX2, so 64 is also fine.
static constexpr u32 TEXTURE_UPLOAD_PITCH_ALIGNMENT = 64;
GSTextureOGL::GSTextureOGL(Type type, int width, int height, int levels, Format format)
{
@@ -214,8 +221,8 @@ bool GSTextureOGL::Update(const GSVector4i& r, const void* data, int pitch, int
m_clean = false;
u32 row_byte = r.width() << m_int_shift;
u32 map_size = r.height() * row_byte;
const u32 preferred_pitch = Common::AlignUpPow2(r.width() << m_int_shift, TEXTURE_UPLOAD_PITCH_ALIGNMENT);
const u32 map_size = r.height() * preferred_pitch;
#if 0
if (r.height() == 1) {
@@ -250,13 +257,18 @@ bool GSTextureOGL::Update(const GSVector4i& r, const void* data, int pitch, int
GL::StreamBuffer* const sb = GSDeviceOGL::GetTextureUploadBuffer();
const auto map = sb->Map(TEXTURE_UPLOAD_ALIGNMENT, map_size);
StringUtil::StrideMemCpy(map.pointer, row_byte, data, pitch, row_byte, r.height());
StringUtil::StrideMemCpy(map.pointer, preferred_pitch, data, pitch, r.width() << m_int_shift, r.height());
sb->Unmap(map_size);
sb->Bind();
const u32 row_length = CalcUploadRowLengthFromPitch(preferred_pitch);
glPixelStorei(GL_UNPACK_ROW_LENGTH, row_length);
glTextureSubImage2D(m_texture_id, layer, r.x, r.y, r.width(), r.height(), m_int_format, m_int_type,
reinterpret_cast<void*>(static_cast<uintptr_t>(map.buffer_offset)));
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
sb->Unbind();
}
@@ -275,13 +287,13 @@ bool GSTextureOGL::Map(GSMap& m, const GSVector4i* _r, int layer)
ASSERT(r.width() != 0);
ASSERT(r.height() != 0);
u32 row_byte = r.width() << m_int_shift;
m.pitch = row_byte;
const u32 pitch = Common::AlignUpPow2(r.width() << m_int_shift, TEXTURE_UPLOAD_PITCH_ALIGNMENT);
m.pitch = pitch;
if (m_type == Type::Texture || m_type == Type::RenderTarget)
{
const u32 map_size = r.height() * row_byte;
if (GLLoader::buggy_pbo || map_size > GSDeviceOGL::GetTextureUploadBuffer()->GetChunkSize())
const u32 upload_size = CalcUploadSize(r.height(), pitch);
if (GLLoader::buggy_pbo || upload_size > GSDeviceOGL::GetTextureUploadBuffer()->GetChunkSize())
return false;
GL_PUSH_("Upload Texture %d", m_texture_id); // POP is in Unmap
@@ -289,7 +301,7 @@ bool GSTextureOGL::Map(GSMap& m, const GSVector4i* _r, int layer)
m_clean = false;
const auto map = GSDeviceOGL::GetTextureUploadBuffer()->Map(TEXTURE_UPLOAD_ALIGNMENT, map_size);
const auto map = GSDeviceOGL::GetTextureUploadBuffer()->Map(TEXTURE_UPLOAD_ALIGNMENT, upload_size);
m.bits = static_cast<u8*>(map.pointer);
// Save the area for the unmap
@@ -310,14 +322,20 @@ void GSTextureOGL::Unmap()
{
if (m_type == Type::Texture || m_type == Type::RenderTarget)
{
const u32 map_size = (m_r_w << m_int_shift) * m_r_h;
const u32 pitch = Common::AlignUpPow2(m_r_w << m_int_shift, TEXTURE_UPLOAD_PITCH_ALIGNMENT);
const u32 upload_size = pitch * m_r_h;
GL::StreamBuffer* sb = GSDeviceOGL::GetTextureUploadBuffer();
sb->Unmap(map_size);
sb->Unmap(upload_size);
sb->Bind();
const u32 row_length = CalcUploadRowLengthFromPitch(pitch);
glPixelStorei(GL_UNPACK_ROW_LENGTH, row_length);
glTextureSubImage2D(m_texture_id, m_layer, m_r_x, m_r_y, m_r_w, m_r_h, m_int_format, m_int_type,
reinterpret_cast<void*>(static_cast<uintptr_t>(m_map_offset)));
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
sb->Unbind();
m_needs_mipmaps_generated = true;
@@ -494,11 +512,13 @@ void GSDownloadTextureOGL::CopyFromTexture(
pxAssert((drc.left == 0 && drc.top == 0) || !use_transfer_pitch);
u32 copy_offset, copy_size, copy_rows;
m_current_pitch =
GetTransferPitch(use_transfer_pitch ? static_cast<u32>(drc.width()) : m_width, GSTexture::GetCompressedBytesPerBlock(m_format));
m_current_pitch = GetTransferPitch(use_transfer_pitch ? static_cast<u32>(drc.width()) : m_width, TEXTURE_UPLOAD_PITCH_ALIGNMENT);
GetTransferSize(drc, &copy_offset, &copy_size, &copy_rows);
g_perfmon.Put(GSPerfMon::Readbacks, 1);
glBindFramebuffer(GL_READ_FRAMEBUFFER, GSDeviceOGL::GetInstance()->GetFBORead());
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, glTex->GetID(), 0);
glPixelStorei(GL_PACK_ALIGNMENT, 1u << glTex->GetIntShift());
glPixelStorei(GL_PACK_ROW_LENGTH, GSTexture::CalcUploadRowLengthFromPitch(m_format, m_current_pitch));
@@ -508,10 +528,7 @@ void GSDownloadTextureOGL::CopyFromTexture(
glBindBuffer(GL_PIXEL_PACK_BUFFER, m_buffer_id);
}
glBindFramebuffer(GL_READ_FRAMEBUFFER, GSDeviceOGL::GetInstance()->GetFBORead());
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, glTex->GetID(), 0);
glReadPixels(drc.left, drc.top, drc.width(), drc.height(), glTex->GetIntFormat(), glTex->GetIntType(), m_cpu_buffer);
glReadPixels(src.left, src.top, src.width(), src.height(), glTex->GetIntFormat(), glTex->GetIntType(), m_cpu_buffer + copy_offset);
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
@@ -522,7 +539,7 @@ void GSDownloadTextureOGL::CopyFromTexture(
}
else
{
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
// Create a sync object so we know when the GPU is done copying.
if (m_sync)

View File

@@ -119,6 +119,7 @@ void GSRendererSW::VSync(u32 field, bool registers_written)
m_tc->IncAge();
m_draw_transfers.clear();
// if((m_perfmon.GetFrame() & 255) == 0) m_rl->PrintStats();
}
@@ -244,7 +245,7 @@ void GSVertexSW::InitStatic()
MULTI_ISA_UNSHARED_START
template <u32 primclass, u32 tme, u32 fst, u32 q_div>
void ConvertVertexBuffer(const GSDrawingContext* RESTRICT ctx, GSVertexSW* RESTRICT dst, const GSVertex* RESTRICT src, size_t count)
void ConvertVertexBuffer(const GSDrawingContext* RESTRICT ctx, GSVertexSW* RESTRICT dst, const GSVertex* RESTRICT src, u32 count)
{
// FIXME q_div wasn't added to AVX2 code path.

View File

@@ -246,7 +246,7 @@ struct alignas(32) GSVertexSW
#endif
}
typedef void (*ConvertVertexBufferPtr)(const GSDrawingContext* RESTRICT ctx, GSVertexSW* RESTRICT dst, const GSVertex* RESTRICT src, size_t count);
typedef void (*ConvertVertexBufferPtr)(const GSDrawingContext* RESTRICT ctx, GSVertexSW* RESTRICT dst, const GSVertex* RESTRICT src, u32 count);
static ConvertVertexBufferPtr s_cvb[4][2][2][2];

View File

@@ -251,6 +251,9 @@ bool GSDeviceVK::CheckFeatures()
if (!m_features.texture_barrier)
Console.Warning("Texture buffers are disabled. This may break some graphical effects.");
if (!g_vulkan_context->GetOptionalExtensions().vk_ext_line_rasterization)
Console.WriteLn("VK_EXT_line_rasterization or the BRESENHAM mode is not supported, this may cause rendering inaccuracies.");
// Test for D32S8 support.
{
VkFormatProperties props = {};
@@ -882,8 +885,6 @@ void GSDeviceVK::IASetVertexBuffer(const void* vertex, size_t stride, size_t cou
}
m_vertex.start = m_vertex_stream_buffer.GetCurrentOffset() / stride;
m_vertex.limit = count;
m_vertex.stride = stride;
m_vertex.count = count;
SetVertexBuffer(m_vertex_stream_buffer.GetBuffer(), 0);
@@ -891,32 +892,6 @@ void GSDeviceVK::IASetVertexBuffer(const void* vertex, size_t stride, size_t cou
m_vertex_stream_buffer.CommitMemory(size);
}
bool GSDeviceVK::IAMapVertexBuffer(void** vertex, size_t stride, size_t count)
{
const u32 size = static_cast<u32>(stride) * static_cast<u32>(count);
if (!m_vertex_stream_buffer.ReserveMemory(size, static_cast<u32>(stride)))
{
ExecuteCommandBufferAndRestartRenderPass(false, "Mapping bytes to vertex buffer");
if (!m_vertex_stream_buffer.ReserveMemory(size, static_cast<u32>(stride)))
pxFailRel("Failed to reserve space for vertices");
}
m_vertex.start = m_vertex_stream_buffer.GetCurrentOffset() / stride;
m_vertex.limit = m_vertex_stream_buffer.GetCurrentSpace() / stride;
m_vertex.stride = stride;
m_vertex.count = count;
SetVertexBuffer(m_vertex_stream_buffer.GetBuffer(), 0);
*vertex = m_vertex_stream_buffer.GetCurrentHostPointer();
return true;
}
void GSDeviceVK::IAUnmapVertexBuffer()
{
const u32 size = static_cast<u32>(m_vertex.stride) * static_cast<u32>(m_vertex.count);
m_vertex_stream_buffer.CommitMemory(size);
}
void GSDeviceVK::IASetIndexBuffer(const void* index, size_t count)
{
const u32 size = sizeof(u32) * static_cast<u32>(count);
@@ -928,7 +903,6 @@ void GSDeviceVK::IASetIndexBuffer(const void* index, size_t count)
}
m_index.start = m_index_stream_buffer.GetCurrentOffset() / sizeof(u32);
m_index.limit = count;
m_index.count = count;
SetIndexBuffer(m_index_stream_buffer.GetBuffer(), 0, VK_INDEX_TYPE_UINT32);
@@ -1977,6 +1951,8 @@ VkShaderModule GSDeviceVK::GetTFXFragmentShader(const GSHWDrawConfig::PSSelector
AddMacro(ss, "PS_FST", sel.fst);
AddMacro(ss, "PS_WMS", sel.wms);
AddMacro(ss, "PS_WMT", sel.wmt);
AddMacro(ss, "PS_ADJS", sel.adjs);
AddMacro(ss, "PS_ADJT", sel.adjt);
AddMacro(ss, "PS_AEM_FMT", sel.aem_fmt);
AddMacro(ss, "PS_PAL_FMT", sel.pal_fmt);
AddMacro(ss, "PS_DFMT", sel.dfmt);
@@ -1984,7 +1960,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_INVALID_TEX0", sel.invalid_tex0);
AddMacro(ss, "PS_AEM", sel.aem);
AddMacro(ss, "PS_TFX", sel.tfx);
AddMacro(ss, "PS_TCC", sel.tcc);
@@ -2021,6 +1996,7 @@ VkShaderModule GSDeviceVK::GetTFXFragmentShader(const GSHWDrawConfig::PSSelector
AddMacro(ss, "PS_NO_COLOR1", sel.no_color1);
AddMacro(ss, "PS_NO_ABLEND", sel.no_ablend);
AddMacro(ss, "PS_ONLY_ALPHA", sel.only_alpha);
AddMacro(ss, "PS_SWAP_GA", sel.swap_ga);
ss << m_tfx_source;
VkShaderModule mod = g_vulkan_shader_cache->GetFragmentShader(ss.str());
@@ -2075,6 +2051,8 @@ VkPipeline GSDeviceVK::CreateTFXPipeline(const PipelineSelector& p)
gpb.SetRasterizationState(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_CLOCKWISE);
if (p.line_width)
gpb.SetLineWidth(static_cast<float>(GSConfig.UpscaleMultiplier));
if (p.topology == static_cast<u8>(GSHWDrawConfig::Topology::Line) && g_vulkan_context->GetOptionalExtensions().vk_ext_line_rasterization)
gpb.SetLineRasterizationMode(VK_LINE_RASTERIZATION_MODE_BRESENHAM_EXT);
gpb.SetDynamicViewportAndScissorState();
gpb.AddDynamicState(VK_DYNAMIC_STATE_BLEND_CONSTANTS);
@@ -3177,8 +3155,6 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config)
if (date_image)
Recycle(date_image);
EndScene();
// now blit the hdr texture back to the original target
if (hdr_rt)
{

View File

@@ -247,8 +247,6 @@ public:
GSTextureVK* SetupPrimitiveTrackingDATE(GSHWDrawConfig& config);
void IASetVertexBuffer(const void* vertex, size_t stride, size_t count);
bool IAMapVertexBuffer(void** vertex, size_t stride, size_t count);
void IAUnmapVertexBuffer();
void IASetIndexBuffer(const void* index, size_t count);
void PSSetShaderResource(int i, GSTexture* sr, bool check_state);

View File

@@ -58,6 +58,7 @@ static bool s_dump_running = false;
static bool s_needs_state_loaded = false;
static u64 s_frame_ticks = 0;
static u64 s_next_frame_time = 0;
static bool s_is_dump_runner = false;
R5900cpu GSDumpReplayerCpu = {
GSDumpReplayerCpuReserve,
@@ -77,11 +78,26 @@ bool GSDumpReplayer::IsReplayingDump()
return static_cast<bool>(s_dump_file);
}
bool GSDumpReplayer::IsRunner()
{
return s_is_dump_runner;
}
void GSDumpReplayer::SetIsDumpRunner(bool is_runner)
{
s_is_dump_runner = is_runner;
}
void GSDumpReplayer::SetLoopCount(s32 loop_count)
{
s_dump_loop_count = loop_count - 1;
}
int GSDumpReplayer::GetLoopCount()
{
return s_dump_loop_count;
}
bool GSDumpReplayer::Initialize(const char* filename)
{
Common::Timer timer;

View File

@@ -25,6 +25,9 @@ bool IsReplayingDump();
/// If set, playback will repeat once it reaches the last frame.
void SetLoopCount(s32 loop_count = 0);
int GetLoopCount();
bool IsRunner();
void SetIsDumpRunner(bool is_runner);
bool Initialize(const char* filename);
void Reset();

View File

@@ -85,6 +85,7 @@ bool Gif_HandlerAD(u8* pMem)
{ // FINISH
GUNIT_WARN("GIF Handler - FINISH");
CSRreg.FINISH = true;
gifUnit.gsFINISH.gsFINISHFired = false;
}
else if (reg == GIF_A_D_REG_LABEL)
{ // LABEL

View File

@@ -317,6 +317,8 @@ void ipuSoftReset()
memzero(g_BP);
coded_block_pattern = 0;
g_ipu_thresh[0] = 0;
g_ipu_thresh[1] = 0;
ipuRegs.ctrl.reset();
ipuRegs.top = 0;

View File

@@ -20,8 +20,12 @@
#include "R5900.h" // for g_GameStarted
#include "IopBios.h"
#include "IopMem.h"
#include "iR3000A.h"
#include "ps2/BiosTools.h"
#include "DebugTools/SymbolMap.h"
#include <ctype.h>
#include <fmt/format.h>
#include <string.h>
#include <sys/stat.h>
#include "common/FileSystem.h"
@@ -983,6 +987,37 @@ namespace R3000A
namespace loadcore
{
// Gets the thread list ptr from thbase
u32 GetThreadList(u32 a0reg, u32 version)
{
// Function 3 returns the main thread manager struct
u32 function = iopMemRead32(a0reg + 0x20);
// read the lui
u32 thstruct = (iopMemRead32(function) & 0xFFFF) << 16;
thstruct |= iopMemRead32(function + 4) & 0xFFFF;
u32 list = thstruct + 0x42c;
if (version > 0x101)
list = thstruct + 0x430;
return list;
}
int RegisterLibraryEntries_HLE()
{
const std::string modname = iopMemReadString(a0 + 12);
if (modname == "thbase")
{
const u32 version = iopMemRead32(a0 + 8);
CurrentBiosInformation.iopThreadListAddr = GetThreadList(a0, version);
}
return 0;
}
void RegisterLibraryEntries_DEBUG()
{
const std::string modname = iopMemReadString(a0 + 12);
@@ -1093,6 +1128,11 @@ namespace R3000A
EXPORT_H( 14, Kprintf)
END_MODULE
// For grabbing the thread list from thbase
MODULE(loadcore)
EXPORT_H( 6, RegisterLibraryEntries)
END_MODULE
// Special case with ioman and iomanX
// They are mostly compatible excluding stat structures
if(libname == "ioman" || libname == "iomanx")

View File

@@ -1919,10 +1919,13 @@ void FolderMemoryCard::DeleteFromIndex(const std::string& filePath, const std::s
if (yaml.has_value() && !yaml.value().empty())
{
ryml::NodeRef index = yaml.value().rootref();
index.remove_child(c4::csubstr(entry.data(), entry.length()));
// Write out the changes
SaveYAMLToFile(indexName.c_str(), index);
if (index.has_child(c4::csubstr(entry.data(), entry.length())))
{
index.remove_child(c4::csubstr(entry.data(), entry.length()));
// Write out the changes
SaveYAMLToFile(indexName.c_str(), index);
}
}
}

View File

@@ -55,6 +55,29 @@ void KeyStatus::Init()
void KeyStatus::Set(u32 pad, u32 index, float value)
{
// Since we reordered the buttons for better UI, we need to remap them here.
static constexpr std::array<u8, MAX_KEYS> bitmask_mapping = {{
12, // PAD_UP
13, // PAD_RIGHT
14, // PAD_DOWN
15, // PAD_LEFT
4, // PAD_TRIANGLE
5, // PAD_CIRCLE
6, // PAD_CROSS
7, // PAD_SQUARE
8, // PAD_SELECT
11, // PAD_START
2, // PAD_L1
0, // PAD_L2
3, // PAD_R1
1, // PAD_R2
9, // PAD_L3
10, // PAD_R3
16, // PAD_ANALOG
17, // PAD_PRESSURE
// remainder are analogs and not used here
}};
if (IsAnalogKey(index))
{
m_button_pressure[pad][index] = static_cast<u8>(std::clamp(value * m_axis_scale[pad][1] * 255.0f, 0.0f, 255.0f));
@@ -95,8 +118,8 @@ void KeyStatus::Set(u32 pad, u32 index, float value)
}
else
{
pos_x = m_analog[pad].invert_lx ? MERGE_F(pad, PAD_R_LEFT, PAD_R_RIGHT) : MERGE_F(pad, PAD_R_RIGHT, PAD_R_LEFT);
pos_y = m_analog[pad].invert_ly ? MERGE_F(pad, PAD_R_UP, PAD_R_DOWN) : MERGE_F(pad, PAD_R_DOWN, PAD_R_UP);
pos_x = m_analog[pad].invert_rx ? MERGE_F(pad, PAD_R_LEFT, PAD_R_RIGHT) : MERGE_F(pad, PAD_R_RIGHT, PAD_R_LEFT);
pos_y = m_analog[pad].invert_ry ? MERGE_F(pad, PAD_R_UP, PAD_R_DOWN) : MERGE_F(pad, PAD_R_DOWN, PAD_R_UP);
}
// No point checking if we're at dead center (usually keyboard with no buttons pressed).
@@ -123,38 +146,24 @@ void KeyStatus::Set(u32 pad, u32 index, float value)
}
#undef MERGE_F
}
}
else if (IsTriggerKey(index))
{
const float s_value = std::clamp(value * m_trigger_scale[pad][1], 0.0f, 1.0f);
const float dz_value = (m_trigger_scale[pad][0] > 0.0f && s_value < m_trigger_scale[pad][0]) ? 0.0f : s_value;
m_button_pressure[pad][index] = static_cast<u8>(dz_value * 255.0f);
if (dz_value > 0.0f)
m_button[pad] &= ~(1u << bitmask_mapping[index]);
else
m_button[pad] |= (1u << bitmask_mapping[index]);
}
else
{
// Don't affect L2/R2, since they are analog on most pads.
const float pmod = ((m_button[pad] & (1u << PAD_PRESSURE)) == 0 && !IsTriggerKey(index)) ? m_pressure_modifier[pad] : 1.0f;
const float pmod = ((m_button[pad] & (1u << PAD_PRESSURE)) == 0) ? m_pressure_modifier[pad] : 1.0f;
const float dz_value = (value < m_button_deadzone[pad]) ? 0.0f : value;
m_button_pressure[pad][index] = static_cast<u8>(std::clamp(dz_value * pmod * 255.0f, 0.0f, 255.0f));
// Since we reordered the buttons for better UI, we need to remap them here.
static constexpr std::array<u8, MAX_KEYS> bitmask_mapping = {{
12, // PAD_UP
13, // PAD_RIGHT
14, // PAD_DOWN
15, // PAD_LEFT
4, // PAD_TRIANGLE
5, // PAD_CIRCLE
6, // PAD_CROSS
7, // PAD_SQUARE
8, // PAD_SELECT
11, // PAD_START
2, // PAD_L1
0, // PAD_L2
3, // PAD_R1
1, // PAD_R2
9, // PAD_L3
10, // PAD_R3
16, // PAD_ANALOG
17, // PAD_PRESSURE
// remainder are analogs and not used here
}};
if (dz_value > 0.0f)
m_button[pad] &= ~(1u << bitmask_mapping[index]);
else
@@ -170,7 +179,8 @@ void KeyStatus::Set(u32 pad, u32 index, float value)
continue;
// We add 0.5 here so that the round trip between 255->127->255 when applying works as expected.
m_button_pressure[pad][i] = static_cast<u8>(std::clamp((static_cast<float>(m_button_pressure[pad][i]) + 0.5f) * adjust_pmod, 0.0f, 255.0f));
const float add = (m_button_pressure[pad][i] != 0) ? 0.5f : 0.0f;
m_button_pressure[pad][i] = static_cast<u8>(std::clamp((static_cast<float>(m_button_pressure[pad][i]) + add) * adjust_pmod, 0.0f, 255.0f));
}
}
}

View File

@@ -40,6 +40,7 @@ namespace PAD
u8 m_button_pressure[NUM_CONTROLLER_PORTS][MAX_KEYS];
PADAnalog m_analog[NUM_CONTROLLER_PORTS];
float m_axis_scale[NUM_CONTROLLER_PORTS][2];
float m_trigger_scale[NUM_CONTROLLER_PORTS][2];
float m_vibration_scale[NUM_CONTROLLER_PORTS][2];
float m_pressure_modifier[NUM_CONTROLLER_PORTS];
float m_button_deadzone[NUM_CONTROLLER_PORTS];
@@ -66,6 +67,11 @@ namespace PAD
m_axis_scale[pad][0] = deadzone;
m_axis_scale[pad][1] = scale;
}
__fi void SetTriggerScale(u32 pad, float deadzone, float scale)
{
m_trigger_scale[pad][0] = deadzone;
m_trigger_scale[pad][1] = scale;
}
__fi float GetVibrationScale(u32 pad, u32 motor) const { return m_vibration_scale[pad][motor]; }
__fi void SetVibrationScale(u32 pad, u32 motor, float scale) { m_vibration_scale[pad][motor] = scale; }
__fi float GetPressureModifier(u32 pad) const { return m_pressure_modifier[pad]; }

View File

@@ -217,8 +217,11 @@ void PAD::LoadConfig(const SettingsInterface& si)
const float axis_deadzone = si.GetFloatValue(section.c_str(), "Deadzone", DEFAULT_STICK_DEADZONE);
const float axis_scale = si.GetFloatValue(section.c_str(), "AxisScale", DEFAULT_STICK_SCALE);
const float trigger_deadzone = si.GetFloatValue(section.c_str(), "TriggerDeadzone", DEFAULT_TRIGGER_DEADZONE);
const float trigger_scale = si.GetFloatValue(section.c_str(), "TriggerScale", DEFAULT_TRIGGER_SCALE);
const float button_deadzone = si.GetFloatValue(section.c_str(), "ButtonDeadzone", DEFAULT_BUTTON_DEADZONE);
g_key_status.SetAxisScale(i, axis_deadzone, axis_scale);
g_key_status.SetTriggerScale(i, trigger_deadzone, trigger_scale);
g_key_status.SetButtonDeadzone(i, button_deadzone);
if (ci->vibration_caps != VibrationCapabilities::NoVibration)
@@ -412,9 +415,15 @@ static const SettingInfo s_dualshock2_settings[] = {
"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", "%.0f%%", nullptr, nullptr, 100.0f},
{SettingInfo::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 "
"Sets the analog stick axis scaling factor. A value between 130% and 140% is recommended when using recent "
"controllers, e.g. DualShock 4, Xbox One Controller.",
"1.33", "0.01", "2.00", "0.01", "%.0f%%", nullptr, nullptr, 100.0f},
{SettingInfo::Type::Float, "TriggerDeadzone", "Trigger 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", "%.0f%%", nullptr, nullptr, 100.0f},
{SettingInfo::Type::Float, "TriggerScale", "Trigger Sensitivity",
"Sets the trigger scaling factor.",
"1.00", "0.01", "2.00", "0.01", "%.0f%%", nullptr, nullptr, 100.0f},
{SettingInfo::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", "%.0f%%", nullptr, nullptr, 100.0f},
@@ -504,7 +513,12 @@ void PAD::ClearPortBindings(SettingsInterface& si, u32 port)
return;
for (u32 i = 0; i < info->num_bindings; i++)
si.DeleteValue(section.c_str(), info->bindings[i].name);
{
const InputBindingInfo& bi = info->bindings[i];
si.DeleteValue(section.c_str(), bi.name);
si.DeleteValue(section.c_str(), fmt::format("{}Scale", bi.name).c_str());
si.DeleteValue(section.c_str(), fmt::format("{}Deadzone", bi.name).c_str());
}
}
void PAD::CopyConfiguration(SettingsInterface* dest_si, const SettingsInterface& src_si,
@@ -545,6 +559,8 @@ void PAD::CopyConfiguration(SettingsInterface* dest_si, const SettingsInterface&
{
const InputBindingInfo& bi = info->bindings[i];
dest_si->CopyStringListValue(src_si, section.c_str(), bi.name);
dest_si->CopyFloatValue(src_si, section.c_str(), fmt::format("{}Sensitivity", bi.name).c_str());
dest_si->CopyFloatValue(src_si, section.c_str(), fmt::format("{}Deadzone", bi.name).c_str());
}
for (u32 i = 0; i < NUM_MACRO_BUTTONS_PER_CONTROLLER; i++)
@@ -557,14 +573,6 @@ void PAD::CopyConfiguration(SettingsInterface* dest_si, const SettingsInterface&
if (copy_pad_config)
{
dest_si->CopyFloatValue(src_si, section.c_str(), "AxisScale");
if (info->vibration_caps != VibrationCapabilities::NoVibration)
{
dest_si->CopyFloatValue(src_si, section.c_str(), "LargeMotorScale");
dest_si->CopyFloatValue(src_si, section.c_str(), "SmallMotorScale");
}
for (u32 i = 0; i < info->num_settings; i++)
{
const SettingInfo& csi = info->settings[i];
@@ -601,8 +609,8 @@ void PAD::CopyConfiguration(SettingsInterface* dest_si, const SettingsInterface&
}
static u32 TryMapGenericMapping(SettingsInterface& si, const std::string& section,
const InputManager::GenericInputBindingMapping& mapping, GenericInputBinding generic_name,
const char* bind_name)
const InputManager::GenericInputBindingMapping& mapping, InputBindingInfo::Type bind_type,
GenericInputBinding generic_name, const char* bind_name)
{
// find the mapping it corresponds to
const std::string* found_mapping = nullptr;
@@ -615,6 +623,14 @@ static u32 TryMapGenericMapping(SettingsInterface& si, const std::string& sectio
}
}
// Remove previously-set binding scales.
if (bind_type == InputBindingInfo::Type::Button || bind_type == InputBindingInfo::Type::Axis ||
bind_type == InputBindingInfo::Type::HalfAxis)
{
si.DeleteValue(section.c_str(), fmt::format("{}Scale", bind_name).c_str());
si.DeleteValue(section.c_str(), fmt::format("{}Deadzone", bind_name).c_str());
}
if (found_mapping)
{
Console.WriteLn("(MapController) Map %s/%s to '%s'", section.c_str(), bind_name, found_mapping->c_str());
@@ -645,17 +661,17 @@ bool PAD::MapController(SettingsInterface& si, u32 controller,
if (bi.generic_mapping == GenericInputBinding::Unknown)
continue;
num_mappings += TryMapGenericMapping(si, section, mapping, bi.generic_mapping, bi.name);
num_mappings += TryMapGenericMapping(si, section, mapping, bi.bind_type, bi.generic_mapping, bi.name);
}
if (info->vibration_caps == VibrationCapabilities::LargeSmallMotors)
{
num_mappings += TryMapGenericMapping(si, section, mapping, GenericInputBinding::SmallMotor, "SmallMotor");
num_mappings += TryMapGenericMapping(si, section, mapping, GenericInputBinding::LargeMotor, "LargeMotor");
num_mappings += TryMapGenericMapping(si, section, mapping, InputBindingInfo::Type::Motor, GenericInputBinding::SmallMotor, "SmallMotor");
num_mappings += TryMapGenericMapping(si, section, mapping, InputBindingInfo::Type::Motor, GenericInputBinding::LargeMotor, "LargeMotor");
}
else if (info->vibration_caps == VibrationCapabilities::SingleMotor)
{
if (TryMapGenericMapping(si, section, mapping, GenericInputBinding::LargeMotor, "Motor") == 0)
num_mappings += TryMapGenericMapping(si, section, mapping, GenericInputBinding::SmallMotor, "Motor");
if (TryMapGenericMapping(si, section, mapping, InputBindingInfo::Type::Motor, GenericInputBinding::LargeMotor, "Motor") == 0)
num_mappings += TryMapGenericMapping(si, section, mapping, InputBindingInfo::Type::Motor, GenericInputBinding::SmallMotor, "Motor");
else
num_mappings++;
}

View File

@@ -76,6 +76,8 @@ namespace PAD
/// Default stick deadzone/sensitivity.
static constexpr float DEFAULT_STICK_DEADZONE = 0.0f;
static constexpr float DEFAULT_STICK_SCALE = 1.33f;
static constexpr float DEFAULT_TRIGGER_DEADZONE = 0.0f;
static constexpr float DEFAULT_TRIGGER_SCALE = 1.0f;
static constexpr float DEFAULT_MOTOR_SCALE = 1.0f;
static constexpr float DEFAULT_PRESSURE_MODIFIER = 0.5f;
static constexpr float DEFAULT_BUTTON_DEADZONE = 0.0f;

View File

@@ -519,7 +519,7 @@ void DSRLV(){ if (!_Rd_) return; cpuRegs.GPR.r[_Rd_].UD[0] = (u64)(cpuRegs.GPR.r
// exceptions, since the lower bits of the address are used to determine the portions
// of the address/register operations.
[[ noreturn ]] __noinline static void RaiseAddressError(u32 addr, bool store)
__noinline static void RaiseAddressError(u32 addr, bool store)
{
const std::string message(fmt::format("Address Error, addr=0x{:x} [{}]", addr, store ? "store" : "load"));
@@ -1012,7 +1012,7 @@ void SYSCALL()
case Syscall::StartThread:
case Syscall::ChangeThreadPriority:
{
if (CurrentBiosInformation.threadListAddr == 0)
if (CurrentBiosInformation.eeThreadListAddr == 0)
{
u32 offset = 0x0;
// Suprisingly not that slow :)
@@ -1030,16 +1030,16 @@ void SYSCALL()
// We've found the instruction pattern!
// We (well, I) know that the thread address is always 0x8001 + the immediate of the 6th instruction from here
const u32 op = memRead32(0x80000000 + offset + (sizeof(u32) * 6));
CurrentBiosInformation.threadListAddr = 0x80010000 + static_cast<u16>(op) - 8; // Subtract 8 because the address here is offset by 8.
DevCon.WriteLn("BIOS: Successfully found the instruction pattern. Assuming the thread list is here: %0x", CurrentBiosInformation.threadListAddr);
CurrentBiosInformation.eeThreadListAddr = 0x80010000 + static_cast<u16>(op) - 8; // Subtract 8 because the address here is offset by 8.
DevCon.WriteLn("BIOS: Successfully found the instruction pattern. Assuming the thread list is here: %0x", CurrentBiosInformation.eeThreadListAddr);
break;
}
offset += 4;
}
if (!CurrentBiosInformation.threadListAddr)
if (!CurrentBiosInformation.eeThreadListAddr)
{
// We couldn't find the address
CurrentBiosInformation.threadListAddr = -1;
CurrentBiosInformation.eeThreadListAddr = -1;
// If you're here because a user has reported this message, this means that the instruction pattern is not present on their bios, or it is aligned weirdly.
Console.Warning("BIOS Warning: Unable to get a thread list offset. The debugger thread and stack frame views will not be functional.");
}

View File

@@ -15,4 +15,4 @@
/// Version number for GS and other shaders. Increment whenever any of the contents of the
/// shaders change, to invalidate the cache.
static constexpr u32 SHADER_CACHE_VERSION = 11;
static constexpr u32 SHADER_CACHE_VERSION = 12;

View File

@@ -71,11 +71,13 @@ namespace usb_lightgun
{"SLUS-20485", 90.25f, 92.5f, 390, 132, 640, 240}, // Dino Stalker (U)
{"SLUS-20389", 89.25f, 93.5f, 422, 141, 640, 240}, // Endgame (U)
{"SLES-50936", 112.0f, 100.0f, 320, 120, 512, 256}, // Endgame (E) (Guncon2 needs to be connected to USB port 2)
{"SLPM-65139", 90.0f, 91.5f, 320, 120, 640, 240}, // Gun Survivor 3: Dino Crisis (J)
{"SLES-52620", 89.5f, 112.3f, 390, 147, 640, 256}, // Guncom 2 (E)
{"SLES-51289", 84.5f, 89.0f, 456, 164, 640, 256}, // Gunfighter 2 - Jesse James (E)
{"SLPS-25165", 90.25f, 98.0f, 390, 138, 640, 240}, // Gunvari Collection (J) (480i)
// {"SLPS-25165", 86.75f, 96.0f, 454, 164, 640, 256}, // Gunvari Collection (J) (480p)
{"SCES-50889", 90.25f, 94.5f, 390, 169, 640, 256}, // Ninja Assault (E)
{"SLPS-20218", 90.0f, 92.0f, 320, 134, 640, 240}, // Ninja Assault (J)
{"SLUS-20492", 90.25f, 92.5f, 390, 132, 640, 240}, // Ninja Assault (U)
{"SLES-50650", 84.75f, 96.0f, 454, 164, 640, 240}, // Resident Evil Survivor 2 (E)
{"SLES-51448", 90.25f, 95.0f, 420, 132, 640, 240}, // Resident Evil - Dead Aim (E)
@@ -89,13 +91,14 @@ namespace usb_lightgun
{"SLUS-20927", 90.25f, 99.0f, 390, 153, 640, 240}, // Time Crisis - Crisis Zone (U) (480i)
// {"SLUS-20927", 94.5f, 104.75f, 423, 407, 768, 768}, // Time Crisis - Crisis Zone (U) (480p)
{"SCES-50411", 89.8f, 99.9f, 421, 138, 640, 256}, // Vampire Night (E)
{"SLPS-25077", 90.0f, 97.5f, 422, 118, 640, 240}, // Vampire Night (J)
{"SLUS-20221", 89.8f, 102.5f, 422, 124, 640, 228}, // Vampire Night (U)
{"SLES-51229", 110.15f, 100.0f, 433, 159, 512, 256}, // Virtua Cop - Elite Edition (E,J) (480i)
// {"SLES-51229", 85.75f, 92.0f, 456, 164, 640, 256}, // Virtua Cop - Elite Edition (E,J) (480p)
};
static constexpr s32 DEFAULT_SCREEN_WIDTH = 640;
static constexpr s32 DEFAULT_SCREEN_HEIGHT = 480;
static constexpr s32 DEFAULT_SCREEN_HEIGHT = 240;
static constexpr float DEFAULT_CENTER_X = 320.0f;
static constexpr float DEFAULT_CENTER_Y = 120.0f;
static constexpr float DEFAULT_SCALE_X = 100.0f;
@@ -359,7 +362,7 @@ namespace usb_lightgun
GSTranslateWindowToDisplayCoordinates(abs_pos.first, abs_pos.second, &pointer_x, &pointer_y);
s16 pos_x, pos_y;
if (pointer_x < 0.0f || pointer_y < 0.0f || (button_state & BID_SHOOT_OFFSCREEN))
if (pointer_x < 0.0f || pointer_y < 0.0f)
{
// off-screen
pos_x = 0;

View File

@@ -729,9 +729,8 @@ namespace usb_msd
int index, int length, uint8_t* data)
{
MSDState* s = USB_CONTAINER_OF(dev, MSDState, dev);
int ret = 0;
const int ret = usb_desc_handle_control(dev, p, request, value, index, length, data);
ret = usb_desc_handle_control(dev, p, request, value, index, length, data);
if (ret >= 0)
{
return;
@@ -743,7 +742,6 @@ namespace usb_msd
case ClassInterfaceOutRequest | MassStorageReset:
/* Reset state ready for the next CBW. */
s->f.mode = USB_MSDM_CBW;
ret = 0;
break;
case ClassInterfaceRequest | GetMaxLun:
data[0] = 0;

View File

@@ -209,7 +209,7 @@ void VMManager::SetState(VMState state)
else
Host::OnVMResumed();
}
else if (state == VMState::Stopping)
else if (state == VMState::Stopping && old_state == VMState::Running)
{
// If stopping, break execution as soon as possible.
Cpu->ExitExecution();

View File

@@ -166,6 +166,8 @@
<ClCompile Include="DEV9\InternalServers\DNS_Logger.cpp" />
<ClCompile Include="DEV9\InternalServers\DNS_Server.cpp" />
<ClCompile Include="DEV9\PacketReader\ARP\ARP_Packet.cpp" />
<ClCompile Include="DEV9\PacketReader\ARP\ARP_PacketEditor.cpp" />
<ClCompile Include="DEV9\PacketReader\EthernetFrameEditor.cpp" />
<ClCompile Include="DEV9\PacketReader\EthernetFrame.cpp" />
<ClCompile Include="DEV9\PacketReader\IP\ICMP\ICMP_Packet.cpp" />
<ClCompile Include="DEV9\PacketReader\IP\TCP\TCP_Options.cpp" />
@@ -510,6 +512,8 @@
<ClInclude Include="DEV9\InternalServers\DNS_Server.h" />
<ClInclude Include="DEV9\net.h" />
<ClInclude Include="DEV9\PacketReader\ARP\ARP_Packet.h" />
<ClInclude Include="DEV9\PacketReader\ARP\ARP_PacketEditor.h" />
<ClInclude Include="DEV9\PacketReader\EthernetFrameEditor.h" />
<ClInclude Include="DEV9\PacketReader\EthernetFrame.h" />
<ClInclude Include="DEV9\PacketReader\MAC_Address.h" />
<ClInclude Include="DEV9\PacketReader\IP\ICMP\ICMP_Packet.h" />

Some files were not shown because too many files have changed in this diff Show More