mirror of
https://github.com/PCSX2/pcsx2.git
synced 2026-07-31 03:08:40 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39fb64cdcd | ||
|
|
d531a7f1af | ||
|
|
bb7ff414ae | ||
|
|
215f112521 | ||
|
|
644766d965 | ||
|
|
1e1a555d3b | ||
|
|
c64ae2684d | ||
|
|
50ed04436d | ||
|
|
2fb9beca52 | ||
|
|
cd4c1e920e | ||
|
|
e846ac367a | ||
|
|
ef31c733ee | ||
|
|
724aa657f3 | ||
|
|
0284c35f4c | ||
|
|
6ce33de287 | ||
|
|
6ccfa011d4 | ||
|
|
c9078af45e | ||
|
|
6745428d0c | ||
|
|
925e874ada | ||
|
|
03f0f2f803 | ||
|
|
857360d6b2 | ||
|
|
2dd76c3f12 | ||
|
|
666de3a874 | ||
|
|
e0e9b64db6 | ||
|
|
2f521348c6 | ||
|
|
8ac2949a1f | ||
|
|
d0d5d991ce | ||
|
|
1fc2d7de3c | ||
|
|
2598e8d9b9 | ||
|
|
01f65e98e6 | ||
|
|
c5330cf166 | ||
|
|
b78796d0c1 | ||
|
|
da1e9db2c0 | ||
|
|
6beb6aa05b | ||
|
|
6ea7777a3a | ||
|
|
f97191e241 | ||
|
|
51420dade4 | ||
|
|
cc55c01197 | ||
|
|
86ce464ee3 | ||
|
|
0fa52a75ad | ||
|
|
c91e7dc3b0 | ||
|
|
88034b176c | ||
|
|
efeaff488c | ||
|
|
5df30f5bdd | ||
|
|
cf179c42b8 | ||
|
|
a615f8bf17 | ||
|
|
b38964e814 | ||
|
|
013c9eec58 | ||
|
|
ddbd6eddf7 | ||
|
|
982fd42683 | ||
|
|
90e28e7957 | ||
|
|
f4201f3947 | ||
|
|
7844b40243 | ||
|
|
d0839a3d55 | ||
|
|
876fd9ba9e | ||
|
|
e12717c108 | ||
|
|
af0b17bb7a | ||
|
|
6f595b7d87 | ||
|
|
59cbdc79f5 | ||
|
|
50ff3649b1 | ||
|
|
9ca9db8770 | ||
|
|
fa70f0e764 | ||
|
|
3eb629f133 | ||
|
|
c9aba6bbe1 | ||
|
|
0a26adae76 | ||
|
|
f0798f6510 | ||
|
|
245b03e208 | ||
|
|
4e31e5fdc2 | ||
|
|
750a74206c | ||
|
|
7e64dc2576 | ||
|
|
8a08e2fd97 | ||
|
|
d0a933cda8 | ||
|
|
d00845f56b | ||
|
|
3350e5ebb1 | ||
|
|
aeb4445cad | ||
|
|
73abae8cb9 | ||
|
|
7ecc7b76ba | ||
|
|
71d0bbbc25 | ||
|
|
26e691ba93 | ||
|
|
c7352d9e10 | ||
|
|
7b8f9a54ec | ||
|
|
28980af858 | ||
|
|
80dce398e0 | ||
|
|
06db8eec48 | ||
|
|
9c720efe46 | ||
|
|
cbf91a8d19 | ||
|
|
f99414708d | ||
|
|
9549a6b16a | ||
|
|
3206094545 | ||
|
|
5cfae80701 | ||
|
|
b4d140c6bb | ||
|
|
c65eb3c3ee | ||
|
|
eec0984dbe | ||
|
|
9b1163f959 | ||
|
|
5b5edc506d | ||
|
|
b30b4375e7 | ||
|
|
1b0c03d892 | ||
|
|
bca49184e7 | ||
|
|
26d6c33163 | ||
|
|
9420615317 | ||
|
|
7198c6b8c6 | ||
|
|
9513864851 | ||
|
|
f9b8aa1862 | ||
|
|
34371c070c | ||
|
|
d6099dd263 | ||
|
|
a1bc39141e | ||
|
|
13ed41d077 | ||
|
|
8a0a8f718f | ||
|
|
7d08a54ad9 | ||
|
|
b9b47e3ec7 | ||
|
|
16989f2122 | ||
|
|
892d3a370d | ||
|
|
6af7ca9867 | ||
|
|
c3c354f794 | ||
|
|
43572a1560 | ||
|
|
79daed63ee | ||
|
|
21d3ad86d4 | ||
|
|
31ebe842e8 | ||
|
|
88487de72f | ||
|
|
616da8c99d | ||
|
|
ec8712cceb | ||
|
|
3d6923b2a1 | ||
|
|
f04337becf | ||
|
|
9a5dd4c19d | ||
|
|
643e0b1039 | ||
|
|
330061a6e7 | ||
|
|
0a292715cf | ||
|
|
52f034a513 | ||
|
|
25e05388ba | ||
|
|
7ad9a1af03 | ||
|
|
9346c69343 | ||
|
|
4a3f0ccf96 | ||
|
|
52a1396e29 | ||
|
|
f556dd2584 | ||
|
|
aea5c09825 | ||
|
|
8c3c9a1219 | ||
|
|
a4f1f383a7 | ||
|
|
ee73c5c1b5 | ||
|
|
c2904a4633 | ||
|
|
6faa2249f9 | ||
|
|
119c3acfe7 | ||
|
|
138a2683f2 | ||
|
|
463637fa10 | ||
|
|
d5aab926bf | ||
|
|
37ba04b770 | ||
|
|
3d0b7dee71 | ||
|
|
a287c2caac | ||
|
|
1b673d9dd0 | ||
|
|
d10621c201 | ||
|
|
e1d6dfc324 | ||
|
|
130ea2a7ca | ||
|
|
c8d53253d2 | ||
|
|
5c67438925 | ||
|
|
1012dba8d7 | ||
|
|
e8d43f53d9 | ||
|
|
a0e8ce4b13 | ||
|
|
0e35b3edcb | ||
|
|
25bb5851ec | ||
|
|
80b0bc0869 | ||
|
|
c05743b7b9 | ||
|
|
7c2a1f0f37 | ||
|
|
0b8b9e75d1 | ||
|
|
d65c343e91 | ||
|
|
cd8e7cc947 | ||
|
|
0df5cf2e91 | ||
|
|
412275e40d | ||
|
|
4c7ad66bd7 | ||
|
|
8ac21357c3 | ||
|
|
2fd88b901b | ||
|
|
5697759d9e | ||
|
|
ee0042c768 | ||
|
|
a6212f1388 | ||
|
|
7bfea60b35 | ||
|
|
beee740dc8 | ||
|
|
e31387b8bc | ||
|
|
3586a12c46 | ||
|
|
bf21254b13 | ||
|
|
dc9f61e70a | ||
|
|
3028998a43 | ||
|
|
42511ce8d8 | ||
|
|
c245d2134f | ||
|
|
f01884537d | ||
|
|
b48fb0d4da | ||
|
|
7a6470a19d | ||
|
|
bfd8fc771a | ||
|
|
f96ca4ac1f | ||
|
|
937bfce68e | ||
|
|
5869d35d85 | ||
|
|
8d3325e6cd | ||
|
|
4badb5b975 | ||
|
|
7e4ff233ec | ||
|
|
0e3397239d | ||
|
|
08bae3da2e | ||
|
|
4b49c8bd6e | ||
|
|
c1bd1fcbd4 | ||
|
|
1c3379f082 | ||
|
|
86c97a8ba3 | ||
|
|
8e6c18d3f4 | ||
|
|
f1e80c466d | ||
|
|
9a542bcb20 | ||
|
|
290c8ec420 | ||
|
|
517ccd5e40 |
@@ -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':
|
||||
|
||||
@@ -36,10 +36,10 @@ _Note: Recommended GPU is based on 3x Internal, ~1080p resolution requirements.
|
||||
|
||||
### Technical Notes
|
||||
|
||||
- You need the [Visual C++ 2019 x64 Redistributables](https://support.microsoft.com/en-us/help/2977003/) to run PCSX2.
|
||||
- You need the [Visual C++ 2019 x64 Redistributables](https://support.microsoft.com/en-us/help/2977003/) to run PCSX2 on Windows.
|
||||
- Windows XP and Direct3D9 support was dropped after stable release 1.4.0.
|
||||
- Windows 7, Windows 8.0, and Windows 8.1 support was dropped after stable release 1.6.0.
|
||||
- 32-bit and wxwidgets support was dropped after stable release 1.6.0, with the wxwidgets code being removed completely on 25th December 2022.
|
||||
- 32-bit and wxWidgets support was dropped after stable release 1.6.0, with the wxWidgets code being removed completely on 25th December 2022.
|
||||
- Make sure to update your operating system and drivers to ensure you have the best experience possible. Having a newer GPU is also recommended so you have the latest supported drivers.
|
||||
- Because of copyright issues, and the complexity of trying to work around it, you need a BIOS dump extracted from a legitimately-owned PS2 console to use the emulator. For more information about the BIOS and how to get it from your console, visit [this page](pcsx2/Docs/PCSX2_FAQ.md#question-13-where-do-i-get-a-ps2-bios).
|
||||
- PCSX2 uses two CPU cores for emulation by default. A third core can be used via the MTVU speed hack, which is compatible with most games. This can be a significant speedup on CPUs with 3+ cores, but it may be a slowdown on GS-limited games (or on CPUs with fewer than 2 cores). Software renderers will then additionally use however many rendering threads it is set to and will need higher core counts to run efficiently.
|
||||
|
||||
+712
-329
File diff suppressed because it is too large
Load Diff
@@ -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,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,
|
||||
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,
|
||||
@@ -849,6 +850,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
|
||||
030000000d0f00003801000008010000,Hori PC Engine Mini Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b9,platform:Mac OS X,
|
||||
030000000d0f00009200000000010000,Hori Pokken Tournament DX Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
030000000d0f0000aa00000072050000,Hori Real Arcade Pro,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X,
|
||||
030000000d0f00000002000015010000,Hori Switch Split Pad Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
030000000d0f00006e00000000010000,Horipad 4 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
030000000d0f00006600000000010000,Horipad 4 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
030000000d0f00006600000000000000,Horipad FPS Plus 4,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:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
@@ -991,6 +993,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
|
||||
030000005e040000130b000011050000,Xbox One Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
|
||||
030000005e040000200b000011050000,Xbox One Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
|
||||
030000005e040000200b000013050000,Xbox One Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
|
||||
030000005e040000200b000015050000,Xbox One Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
|
||||
030000005e040000d102000000000000,Xbox One Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
|
||||
030000005e040000dd02000000000000,Xbox One Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
|
||||
030000005e040000e002000000000000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Mac OS X,
|
||||
@@ -1175,6 +1178,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
|
||||
030000000d0f00006a00000011010000,Hori Real Arcade Pro 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
|
||||
030000000d0f00006b00000011010000,Hori Real Arcade Pro 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
|
||||
030000000d0f00001600000000010000,Hori Real Arcade Pro EXSE,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b2,y:b3,platform:Linux,
|
||||
030000000d0f00008501000015010000,Hori Switch Split Pad Pro,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
030000000d0f00006e00000011010000,Horipad 4 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
|
||||
030000000d0f00006600000011010000,Horipad 4 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
|
||||
030000000d0f0000ee00000011010000,Horipad Mini 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
|
||||
@@ -1270,6 +1274,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
|
||||
030000005e0400008902000021010000,Microsoft Xbox pad v2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux,
|
||||
03000000030000000300000002000000,Miroof,a:b1,b:b0,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Linux,
|
||||
050000004d4f435554452d3035335800,Mocute 053X,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
|
||||
05000000e80400006e0400001b010000,Mocute 053X M59,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
|
||||
050000004d4f435554452d3035305800,Mocute 054X,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
|
||||
05000000d6200000e589000001000000,Moga 2,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Linux,
|
||||
05000000d6200000ad0d000001000000,Moga Pro,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Linux,
|
||||
@@ -1546,6 +1551,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
|
||||
050000005e040000130b000009050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
|
||||
050000005e040000130b000013050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
|
||||
060000005e040000120b00000b050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
030000005e040000120b00000f050000,Xbox Series Controller,a:b0,b:b1,y:b3,x:b2,start:b7,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:b12,dpleft:b14,dpdown:b13,dpright:b15,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Linux,
|
||||
030000005e040000120b000007050000,Xbox Series X Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
050000005e040000130b000007050000,Xbox Series X Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
|
||||
050000005e040000130b000011050000,Xbox Series X Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
|
||||
|
||||
@@ -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_READ16_SRC 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
|
||||
@@ -872,26 +878,37 @@ PS_OUTPUT ps_main(PS_INPUT input)
|
||||
{
|
||||
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_READ16_SRC)
|
||||
{
|
||||
C.rb = (float2)float((denorm_c.r >> 3) | (((denorm_c.g >> 3) & 0x7u) << 5));
|
||||
if (denorm_c.a & 0x80u)
|
||||
C.ga = (float2)(float((denorm_c.a & 0x7Fu) | (denorm_TA.y & 0x80u)));
|
||||
C.ga = (float2)float((denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.y & 0x80u));
|
||||
else
|
||||
C.ga = (float2)(float((denorm_c.a & 0x7Fu) | (denorm_TA.x & 0x80u)));
|
||||
C.ga = (float2)float((denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.x & 0x80u));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (denorm_c.g & 0x80u)
|
||||
C.ga = (float2)(float((denorm_c.g & 0x7Fu) | (denorm_TA.y & 0x80u)));
|
||||
// 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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -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
|
||||
@@ -866,7 +871,13 @@ void ps_main()
|
||||
#if PS_SHUFFLE
|
||||
uvec4 denorm_c = uvec4(C);
|
||||
uvec2 denorm_TA = uvec2(vec2(TA.xy) * 255.0f + 0.5f);
|
||||
|
||||
#if PS_READ16_SRC
|
||||
C.rb = vec2(float((denorm_c.r >> 3) | (((denorm_c.g >> 3) & 0x7u) << 5)));
|
||||
if (bool(denorm_c.a & 0x80u))
|
||||
C.ga = vec2(float((denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.y & 0x80u)));
|
||||
else
|
||||
C.ga = vec2(float((denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.x & 0x80u)));
|
||||
#else
|
||||
// Write RB part. Mask will take care of the correct destination
|
||||
#if PS_READ_BA
|
||||
C.rb = C.bb;
|
||||
@@ -902,9 +913,10 @@ void ps_main()
|
||||
// float sel = step(128.0f, c.g);
|
||||
// vec2 c_shuffle = vec2((denorm_c.gg & 0x7Fu) | (denorm_TA & 0x80u));
|
||||
// c.ga = mix(c_shuffle.xx, c_shuffle.yy, sel);
|
||||
#endif
|
||||
#endif // PS_READ_BA
|
||||
|
||||
#endif
|
||||
#endif // READ16_SRC
|
||||
#endif // PS_SHUFFLE
|
||||
|
||||
// Must be done before alpha correction
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -326,13 +328,13 @@ void main()
|
||||
#define PS_POINT_SAMPLER 0
|
||||
#define PS_SHUFFLE 0
|
||||
#define PS_READ_BA 0
|
||||
#define PS_READ16_SRC 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
|
||||
@@ -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
|
||||
@@ -1171,24 +1176,31 @@ void main()
|
||||
#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;
|
||||
#else
|
||||
C.rb = C.rr;
|
||||
#endif
|
||||
|
||||
#if PS_READ_BA
|
||||
#if PS_READ16_SRC
|
||||
C.rb = vec2(float((denorm_c.r >> 3) | (((denorm_c.g >> 3) & 0x7u) << 5)));
|
||||
if ((denorm_c.a & 0x80u) != 0u)
|
||||
C.ga = vec2(float((denorm_c.a & 0x7Fu) | (denorm_TA.y & 0x80u)));
|
||||
C.ga = vec2(float((denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.y & 0x80u)));
|
||||
else
|
||||
C.ga = vec2(float((denorm_c.a & 0x7Fu) | (denorm_TA.x & 0x80u)));
|
||||
C.ga = vec2(float((denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (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
|
||||
|
||||
|
||||
@@ -10,8 +10,9 @@ function(detectOperatingSystem)
|
||||
if(WIN32)
|
||||
set(Windows TRUE PARENT_SCOPE)
|
||||
elseif(UNIX AND APPLE)
|
||||
# No easy way to filter out iOS.
|
||||
message(WARNING "OS X/iOS isn't supported, the build will most likely fail")
|
||||
if(IOS)
|
||||
message(WARNING "iOS isn't supported, the build will most likely fail")
|
||||
endif()
|
||||
set(MacOSX TRUE PARENT_SCOPE)
|
||||
elseif(UNIX)
|
||||
if(CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
|
||||
@@ -445,7 +445,7 @@ ID3D12GraphicsCommandList4* Context::GetInitCommandList()
|
||||
return res.command_lists[0].get();
|
||||
}
|
||||
|
||||
void Context::ExecuteCommandList(WaitType wait_for_completion)
|
||||
bool Context::ExecuteCommandList(WaitType wait_for_completion)
|
||||
{
|
||||
CommandListResources& res = m_command_lists[m_current_command_list];
|
||||
HRESULT hr;
|
||||
@@ -463,12 +463,21 @@ void Context::ExecuteCommandList(WaitType wait_for_completion)
|
||||
if (res.init_command_list_used)
|
||||
{
|
||||
hr = res.command_lists[0]->Close();
|
||||
pxAssertRel(SUCCEEDED(hr), "Close init command list");
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Console.Error("Closing init command list failed with HRESULT %08X", hr);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Close and queue command list.
|
||||
hr = res.command_lists[1]->Close();
|
||||
pxAssertRel(SUCCEEDED(hr), "Close command list");
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Console.Error("Closing main command list failed with HRESULT %08X", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (res.init_command_list_used)
|
||||
{
|
||||
const std::array<ID3D12CommandList*, 2> execute_lists{res.command_lists[0].get(), res.command_lists[1].get()};
|
||||
@@ -487,6 +496,8 @@ void Context::ExecuteCommandList(WaitType wait_for_completion)
|
||||
MoveToNextCommandList();
|
||||
if (wait_for_completion != WaitType::None)
|
||||
WaitForFence(res.ready_fence_value, wait_for_completion == WaitType::Spin);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Context::InvalidateSamplerGroups()
|
||||
|
||||
@@ -130,7 +130,7 @@ namespace D3D12
|
||||
};
|
||||
|
||||
/// Executes the current command list.
|
||||
void ExecuteCommandList(WaitType wait_for_completion);
|
||||
bool ExecuteCommandList(WaitType wait_for_completion);
|
||||
|
||||
/// Waits for a specific fence.
|
||||
void WaitForFence(u64 fence, bool spin);
|
||||
|
||||
@@ -119,7 +119,7 @@ namespace GL
|
||||
if (!context)
|
||||
return nullptr;
|
||||
|
||||
Console.WriteLn("Created a %s context", context->IsGLES() ? "OpenGL ES" : "OpenGL");
|
||||
Console.WriteLn("Created an %s context", context->IsGLES() ? "OpenGL ES" : "OpenGL");
|
||||
|
||||
// NOTE: Not thread-safe. But this is okay, since we're not going to be creating more than one context at a time.
|
||||
static Context* context_being_created;
|
||||
|
||||
@@ -100,6 +100,8 @@ static void SysPageFaultSignalFilter(int signal, siginfo_t* siginfo, void* ctx)
|
||||
|
||||
#if defined(__APPLE__) && defined(__x86_64__)
|
||||
void* const exception_pc = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext->__ss.__rip);
|
||||
#elif defined(__FreeBSD__) && defined(__x86_64__)
|
||||
void* const exception_pc = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext.mc_rip);
|
||||
#elif defined(__x86_64__)
|
||||
void* const exception_pc = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext.gregs[REG_RIP]);
|
||||
#else
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
+45
-13
@@ -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",
|
||||
@@ -1106,9 +1122,13 @@ namespace Vulkan
|
||||
void Context::WaitForCommandBufferCompletion(u32 index)
|
||||
{
|
||||
// Wait for this command buffer to be completed.
|
||||
VkResult res = vkWaitForFences(m_device, 1, &m_frame_resources[index].fence, VK_TRUE, UINT64_MAX);
|
||||
const VkResult res = vkWaitForFences(m_device, 1, &m_frame_resources[index].fence, VK_TRUE, UINT64_MAX);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkWaitForFences failed: ");
|
||||
m_last_submit_failed.store(true, std::memory_order_release);
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean up any resources for command buffers between the last known completed buffer and this
|
||||
// now-completed command buffer. If we use >2 buffers, this may be more than one buffer.
|
||||
@@ -1250,11 +1270,12 @@ namespace Vulkan
|
||||
submit_info.pSignalSemaphores = &m_spin_resources[index].semaphore;
|
||||
}
|
||||
|
||||
VkResult res = vkQueueSubmit(m_graphics_queue, 1, &submit_info, resources.fence);
|
||||
const VkResult res = vkQueueSubmit(m_graphics_queue, 1, &submit_info, resources.fence);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkQueueSubmit failed: ");
|
||||
pxFailRel("Failed to submit command buffer.");
|
||||
m_last_submit_failed.store(true, std::memory_order_release);
|
||||
return;
|
||||
}
|
||||
|
||||
if (spin_cycles != 0)
|
||||
@@ -1270,14 +1291,14 @@ namespace Vulkan
|
||||
|
||||
present_swap_chain->ReleaseCurrentImage();
|
||||
|
||||
VkResult res = vkQueuePresentKHR(m_present_queue, &present_info);
|
||||
const VkResult res = vkQueuePresentKHR(m_present_queue, &present_info);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
// VK_ERROR_OUT_OF_DATE_KHR is not fatal, just means we need to recreate our swap chain.
|
||||
if (res != VK_ERROR_OUT_OF_DATE_KHR && res != VK_SUBOPTIMAL_KHR)
|
||||
LOG_VULKAN_ERROR(res, "vkQueuePresentKHR failed: ");
|
||||
|
||||
m_last_present_failed.store(true);
|
||||
m_last_present_failed.store(true, std::memory_order_release);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1444,6 +1465,9 @@ namespace Vulkan
|
||||
|
||||
void Context::ExecuteCommandBuffer(WaitType wait_for_completion)
|
||||
{
|
||||
if (m_last_submit_failed.load(std::memory_order_acquire))
|
||||
return;
|
||||
|
||||
// If we're waiting for completion, don't bother waking the worker thread.
|
||||
const u32 current_frame = m_current_frame;
|
||||
SubmitCommandBuffer();
|
||||
@@ -1465,9 +1489,12 @@ namespace Vulkan
|
||||
|
||||
bool Context::CheckLastPresentFail()
|
||||
{
|
||||
bool res = m_last_present_failed;
|
||||
m_last_present_failed = false;
|
||||
return res;
|
||||
return m_last_present_failed.exchange(false, std::memory_order_acq_rel);
|
||||
}
|
||||
|
||||
bool Context::CheckLastSubmitFail()
|
||||
{
|
||||
return m_last_submit_failed.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
void Context::DeferBufferDestruction(VkBuffer object)
|
||||
@@ -1580,7 +1607,7 @@ namespace Vulkan
|
||||
VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT,
|
||||
DebugMessengerCallback, nullptr};
|
||||
|
||||
VkResult res =
|
||||
const VkResult res =
|
||||
vkCreateDebugUtilsMessengerEXT(m_instance, &messenger_info, nullptr, &m_debug_messenger_callback);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
@@ -1672,7 +1699,7 @@ namespace Vulkan
|
||||
subpass_dependency_ptr};
|
||||
|
||||
VkRenderPass pass;
|
||||
VkResult res = vkCreateRenderPass(m_device, &pass_info, nullptr, &pass);
|
||||
const VkResult res = vkCreateRenderPass(m_device, &pass_info, nullptr, &pass);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkCreateRenderPass failed: ");
|
||||
@@ -1878,9 +1905,14 @@ void main()
|
||||
SpinResources& resources = m_spin_resources[index];
|
||||
if (!resources.in_progress)
|
||||
return;
|
||||
VkResult res = vkWaitForFences(m_device, 1, &resources.fence, VK_TRUE, UINT64_MAX);
|
||||
|
||||
const VkResult res = vkWaitForFences(m_device, 1, &resources.fence, VK_TRUE, UINT64_MAX);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkWaitForFences failed: ");
|
||||
m_last_submit_failed.store(true, std::memory_order_release);
|
||||
return;
|
||||
}
|
||||
SpinCommandCompleted(index);
|
||||
}
|
||||
|
||||
@@ -1890,7 +1922,7 @@ void main()
|
||||
resources.in_progress = false;
|
||||
const u32 timestamp_base = (index + NUM_COMMAND_BUFFERS) * 2;
|
||||
std::array<u64, 2> timestamps;
|
||||
VkResult res = vkGetQueryPoolResults(m_device, m_timestamp_query_pool, timestamp_base, static_cast<u32>(timestamps.size()),
|
||||
const VkResult res = vkGetQueryPoolResults(m_device, m_timestamp_query_pool, timestamp_base, static_cast<u32>(timestamps.size()),
|
||||
sizeof(timestamps), timestamps.data(), sizeof(u64), VK_QUERY_RESULT_64_BIT);
|
||||
if (res == VK_SUCCESS)
|
||||
{
|
||||
@@ -1998,7 +2030,7 @@ void main()
|
||||
constexpr u64 MAX_MAX_DEVIATION = 100000; // 100µs
|
||||
for (int i = 0; i < 4; i++) // 4 tries to get under MAX_MAX_DEVIATION
|
||||
{
|
||||
VkResult res = vkGetCalibratedTimestampsEXT(m_device, std::size(infos), infos, timestamps, &maxDeviation);
|
||||
const VkResult res = vkGetCalibratedTimestampsEXT(m_device, std::size(infos), infos, timestamps, &maxDeviation);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkGetCalibratedTimestampsEXT failed: ");
|
||||
|
||||
@@ -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;
|
||||
@@ -208,6 +209,7 @@ namespace Vulkan
|
||||
|
||||
// Was the last present submitted to the queue a failure? If so, we must recreate our swapchain.
|
||||
bool CheckLastPresentFail();
|
||||
bool CheckLastSubmitFail();
|
||||
|
||||
// Schedule a vulkan resource for destruction later on. This will occur when the command buffer
|
||||
// is next re-used, and the GPU has finished working with the specified resource.
|
||||
@@ -372,6 +374,7 @@ namespace Vulkan
|
||||
|
||||
StreamBuffer m_texture_upload_buffer;
|
||||
|
||||
std::atomic_bool m_last_submit_failed{false};
|
||||
std::atomic_bool m_last_present_failed{false};
|
||||
std::atomic_bool m_present_done{true};
|
||||
std::mutex m_present_mutex;
|
||||
|
||||
@@ -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};
|
||||
|
||||
+50
-11
@@ -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);
|
||||
@@ -271,21 +280,26 @@ void Host::ReleaseHostDisplay(bool clear_state)
|
||||
g_host_display.reset();
|
||||
}
|
||||
|
||||
bool Host::BeginPresentFrame(bool frame_skip)
|
||||
HostDisplay::PresentResult Host::BeginPresentFrame(bool frame_skip)
|
||||
{
|
||||
// when we wrap around, don't race other files
|
||||
GSJoinSnapshotThreads();
|
||||
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);
|
||||
// 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;
|
||||
const HostDisplay::PresentResult result = g_host_display->BeginPresent(frame_skip);
|
||||
if (result != HostDisplay::PresentResult::OK)
|
||||
{
|
||||
// don't render imgui
|
||||
ImGuiManager::SkipFrame();
|
||||
}
|
||||
|
||||
// don't render imgui
|
||||
ImGuiManager::NewFrame();
|
||||
return false;
|
||||
return result;
|
||||
}
|
||||
|
||||
void Host::EndPresentFrame()
|
||||
@@ -519,6 +533,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 +664,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 +689,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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -58,6 +58,7 @@ target_sources(pcsx2-qt PRIVATE
|
||||
Settings/ControllerGlobalSettingsWidget.ui
|
||||
Settings/ControllerMacroEditWidget.ui
|
||||
Settings/ControllerMacroWidget.ui
|
||||
Settings/ControllerMouseSettingsDialog.ui
|
||||
Settings/ControllerSettingsDialog.cpp
|
||||
Settings/ControllerSettingsDialog.h
|
||||
Settings/ControllerSettingsDialog.ui
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -488,7 +488,7 @@ void GameListWidget::resizeTableViewColumnsToFit()
|
||||
80, // last played
|
||||
80, // size
|
||||
60, // region
|
||||
100 // compatibility
|
||||
120 // compatibility
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+38
-23
@@ -983,27 +983,28 @@ void MainWindow::saveStateToConfig()
|
||||
if (!isVisible())
|
||||
return;
|
||||
|
||||
bool changed = false;
|
||||
|
||||
const QByteArray geometry(saveGeometry());
|
||||
const QByteArray geometry_b64(geometry.toBase64());
|
||||
const std::string old_geometry_b64(Host::GetBaseStringSettingValue("UI", "MainWindowGeometry"));
|
||||
if (old_geometry_b64 != geometry_b64.constData())
|
||||
{
|
||||
const QByteArray geometry = saveGeometry();
|
||||
const QByteArray geometry_b64 = geometry.toBase64();
|
||||
const std::string old_geometry_b64 = Host::GetBaseStringSettingValue("UI", "MainWindowGeometry");
|
||||
if (old_geometry_b64 != geometry_b64.constData())
|
||||
{
|
||||
Host::SetBaseStringSettingValue("UI", "MainWindowGeometry", geometry_b64.constData());
|
||||
Host::CommitBaseSettingChanges();
|
||||
}
|
||||
Host::SetBaseStringSettingValue("UI", "MainWindowGeometry", geometry_b64.constData());
|
||||
changed = true;
|
||||
}
|
||||
|
||||
const QByteArray state(saveState());
|
||||
const QByteArray state_b64(state.toBase64());
|
||||
const std::string old_state_b64(Host::GetBaseStringSettingValue("UI", "MainWindowState"));
|
||||
if (old_state_b64 != state_b64.constData())
|
||||
{
|
||||
const QByteArray state = saveState();
|
||||
const QByteArray state_b64 = state.toBase64();
|
||||
const std::string old_state_b64 = Host::GetBaseStringSettingValue("UI", "MainWindowState");
|
||||
if (old_state_b64 != state_b64.constData())
|
||||
{
|
||||
Host::SetBaseStringSettingValue("UI", "MainWindowState", state_b64.constData());
|
||||
Host::CommitBaseSettingChanges();
|
||||
}
|
||||
Host::SetBaseStringSettingValue("UI", "MainWindowState", state_b64.constData());
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed)
|
||||
Host::CommitBaseSettingChanges();
|
||||
}
|
||||
|
||||
void MainWindow::restoreStateFromConfig()
|
||||
@@ -1363,6 +1364,7 @@ void MainWindow::onGameListRefreshProgress(const QString& status, int current, i
|
||||
|
||||
void MainWindow::onGameListRefreshComplete()
|
||||
{
|
||||
m_ui.statusBar->clearMessage();
|
||||
clearProgressBar();
|
||||
}
|
||||
|
||||
@@ -1463,7 +1465,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 +1933,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 +1977,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();
|
||||
saveStateToConfig();
|
||||
QMainWindow::closeEvent(event);
|
||||
return;
|
||||
}
|
||||
|
||||
saveStateToConfig();
|
||||
m_is_closing = true;
|
||||
// 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();
|
||||
|
||||
QMainWindow::closeEvent(event);
|
||||
// Exit cancelled?
|
||||
if (!requestShutdown(true, true, EmuConfig.SaveStateOnShutdown))
|
||||
return;
|
||||
|
||||
// Application will be exited in VM stopped handler.
|
||||
m_is_closing = true;
|
||||
}
|
||||
|
||||
static QString getFilenameFromMimeData(const QMimeData* md)
|
||||
@@ -2249,7 +2259,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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
+18
-5
@@ -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
|
||||
@@ -966,17 +967,17 @@ void Host::ReleaseHostDisplay(bool clear_state)
|
||||
g_emu_thread->releaseHostDisplay(clear_state);
|
||||
}
|
||||
|
||||
bool Host::BeginPresentFrame(bool frame_skip)
|
||||
HostDisplay::PresentResult Host::BeginPresentFrame(bool frame_skip)
|
||||
{
|
||||
if (!g_host_display->BeginPresent(frame_skip))
|
||||
const HostDisplay::PresentResult result = g_host_display->BeginPresent(frame_skip);
|
||||
if (result != HostDisplay::PresentResult::OK)
|
||||
{
|
||||
// if we're skipping a frame, we need to reset imgui's state, since
|
||||
// we won't be calling EndPresentFrame().
|
||||
ImGuiManager::NewFrame();
|
||||
return false;
|
||||
ImGuiManager::SkipFrame();
|
||||
}
|
||||
|
||||
return true;
|
||||
return result;
|
||||
}
|
||||
|
||||
void Host::EndPresentFrame()
|
||||
@@ -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));
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -72,7 +72,7 @@ AdvancedSettingsWidget::AdvancedSettingsWidget(SettingsDialog* dialog, QWidget*
|
||||
dialog->registerWidgetHelp(m_ui.eeClampMode, tr("Clamping Mode"), tr("Normal (Default)"), tr(""));
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.eeRecompiler, tr("Enable Recompiler"), tr("Checked"),
|
||||
tr("Performs just - in - time binary translation of 64 - bit MIPS - IV machine code to x86."));
|
||||
tr("Performs just-in-time binary translation of 64-bit MIPS-IV machine code to x86."));
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.eeWaitLoopDetection, tr("Wait Loop Detection"), tr("Checked"),
|
||||
tr("Moderate speedup for some games, with no known side effects."));
|
||||
@@ -86,7 +86,7 @@ AdvancedSettingsWidget::AdvancedSettingsWidget(SettingsDialog* dialog, QWidget*
|
||||
tr("Uses backpatching to avoid register flushing on every memory access."));
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.pauseOnTLBMiss, tr("Pause On TLB Miss"), tr("Unchecked"),
|
||||
tr("Pauses the virtual machine when a TLB miss occurs, instead of ignoring it and continuing. Note the the VM will pause after the "
|
||||
tr("Pauses the virtual machine when a TLB miss occurs, instead of ignoring it and continuing. Note that the VM will pause after the "
|
||||
"end of the block, not on the instruction which caused the exception. Refer to the console to see the address where the invalid "
|
||||
"access occurred."));
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -45,10 +45,10 @@ ControllerGlobalSettingsWidget::ControllerGlobalSettingsWidget(QWidget* parent,
|
||||
#endif
|
||||
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableMouseMapping, "UI", "EnableMouseMapping", false);
|
||||
connect(m_ui.mouseSettings, &QToolButton::clicked, this, &ControllerGlobalSettingsWidget::mouseSettingsClicked);
|
||||
|
||||
ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(sif, m_ui.multitapPort1, "Pad", "MultitapPort1", false);
|
||||
ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(sif, m_ui.multitapPort2, "Pad", "MultitapPort2", false);
|
||||
ControllerSettingWidgetBinder::BindWidgetToInputProfileFloat(sif, m_ui.pointerXScale, "Pad", "PointerXScale", 8.0f);
|
||||
ControllerSettingWidgetBinder::BindWidgetToInputProfileFloat(sif, m_ui.pointerYScale, "Pad", "PointerYScale", 8.0f);
|
||||
|
||||
#ifdef _WIN32
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableXInputSource, "InputSources", "XInput", false);
|
||||
@@ -81,13 +81,6 @@ ControllerGlobalSettingsWidget::ControllerGlobalSettingsWidget(QWidget* parent,
|
||||
for (QCheckBox* cb : {m_ui.multitapPort1, m_ui.multitapPort2})
|
||||
connect(cb, &QCheckBox::stateChanged, this, [this]() { emit bindingSetupChanged(); });
|
||||
|
||||
connect(m_ui.pointerXScale, &QSlider::valueChanged, this,
|
||||
[this](int value) { m_ui.pointerXScaleLabel->setText(QStringLiteral("%1").arg(value)); });
|
||||
connect(m_ui.pointerYScale, &QSlider::valueChanged, this,
|
||||
[this](int value) { m_ui.pointerYScaleLabel->setText(QStringLiteral("%1").arg(value)); });
|
||||
m_ui.pointerXScaleLabel->setText(QStringLiteral("%1").arg(m_ui.pointerXScale->value()));
|
||||
m_ui.pointerYScaleLabel->setText(QStringLiteral("%1").arg(m_ui.pointerYScale->value()));
|
||||
|
||||
updateSDLOptionsEnabled();
|
||||
}
|
||||
|
||||
@@ -128,6 +121,12 @@ void ControllerGlobalSettingsWidget::ledSettingsClicked()
|
||||
dialog.exec();
|
||||
}
|
||||
|
||||
void ControllerGlobalSettingsWidget::mouseSettingsClicked()
|
||||
{
|
||||
ControllerMouseSettingsDialog dialog(this, m_dialog);
|
||||
dialog.exec();
|
||||
}
|
||||
|
||||
ControllerLEDSettingsDialog::ControllerLEDSettingsDialog(QWidget* parent, ControllerSettingsDialog* dialog)
|
||||
: QDialog(parent)
|
||||
, m_dialog(dialog)
|
||||
@@ -156,3 +155,35 @@ void ControllerLEDSettingsDialog::linkButton(ColorPickerButton* button, u32 play
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
ControllerMouseSettingsDialog::ControllerMouseSettingsDialog(QWidget* parent, ControllerSettingsDialog* dialog)
|
||||
: QDialog(parent)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
||||
SettingsInterface* sif = dialog->getProfileSettingsInterface();
|
||||
|
||||
m_ui.icon->setPixmap(QIcon::fromTheme(QStringLiteral("mouse-line")).pixmap(32, 32));
|
||||
|
||||
ControllerSettingWidgetBinder::BindWidgetToInputProfileFloat(sif, m_ui.pointerXSpeedSlider, "Pad", "PointerXSpeed", 40.0f);
|
||||
ControllerSettingWidgetBinder::BindWidgetToInputProfileFloat(sif, m_ui.pointerYSpeedSlider, "Pad", "PointerYSpeed", 40.0f);
|
||||
ControllerSettingWidgetBinder::BindWidgetToInputProfileFloat(sif, m_ui.pointerXDeadZoneSlider, "Pad", "PointerXDeadZone", 20.0f);
|
||||
ControllerSettingWidgetBinder::BindWidgetToInputProfileFloat(sif, m_ui.pointerYDeadZoneSlider, "Pad", "PointerYDeadZone", 20.0f);
|
||||
ControllerSettingWidgetBinder::BindWidgetToInputProfileFloat(sif, m_ui.pointerInertiaSlider, "Pad", "PointerInertia", 10.0f);
|
||||
|
||||
connect(m_ui.pointerXSpeedSlider, &QSlider::valueChanged, this, [this](int value) { m_ui.pointerXSpeedVal->setText(QStringLiteral("%1").arg(value)); });
|
||||
connect(m_ui.pointerYSpeedSlider, &QSlider::valueChanged, this, [this](int value) { m_ui.pointerYSpeedVal->setText(QStringLiteral("%1").arg(value)); });
|
||||
connect(m_ui.pointerXDeadZoneSlider, &QSlider::valueChanged, this, [this](int value) { m_ui.pointerXDeadZoneVal->setText(QStringLiteral("%1").arg(value)); });
|
||||
connect(m_ui.pointerYDeadZoneSlider, &QSlider::valueChanged, this, [this](int value) { m_ui.pointerYDeadZoneVal->setText(QStringLiteral("%1").arg(value)); });
|
||||
connect(m_ui.pointerInertiaSlider, &QSlider::valueChanged, this, [this](int value) { m_ui.pointerInertiaVal->setText(QStringLiteral("%1").arg(value)); });
|
||||
|
||||
m_ui.pointerXSpeedVal->setText(QStringLiteral("%1").arg(m_ui.pointerXSpeedSlider->value()));
|
||||
m_ui.pointerYSpeedVal->setText(QStringLiteral("%1").arg(m_ui.pointerYSpeedSlider->value()));
|
||||
m_ui.pointerXDeadZoneVal->setText(QStringLiteral("%1").arg(m_ui.pointerXDeadZoneSlider->value()));
|
||||
m_ui.pointerYDeadZoneVal->setText(QStringLiteral("%1").arg(m_ui.pointerYDeadZoneSlider->value()));
|
||||
m_ui.pointerInertiaVal->setText(QStringLiteral("%1").arg(m_ui.pointerInertiaSlider->value()));
|
||||
|
||||
connect(m_ui.buttonBox->button(QDialogButtonBox::Close), &QPushButton::clicked, this, &QDialog::accept);
|
||||
}
|
||||
|
||||
ControllerMouseSettingsDialog::~ControllerMouseSettingsDialog() = default;
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
#include "ui_ControllerGlobalSettingsWidget.h"
|
||||
#include "ui_ControllerLEDSettingsDialog.h"
|
||||
#include "ui_ControllerMouseSettingsDialog.h"
|
||||
|
||||
class ControllerSettingsDialog;
|
||||
|
||||
@@ -44,6 +45,7 @@ Q_SIGNALS:
|
||||
private Q_SLOTS:
|
||||
void updateSDLOptionsEnabled();
|
||||
void ledSettingsClicked();
|
||||
void mouseSettingsClicked();
|
||||
|
||||
private:
|
||||
Ui::ControllerGlobalSettingsWidget m_ui;
|
||||
@@ -64,3 +66,15 @@ private:
|
||||
Ui::ControllerLEDSettingsDialog m_ui;
|
||||
ControllerSettingsDialog* m_dialog;
|
||||
};
|
||||
|
||||
class ControllerMouseSettingsDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ControllerMouseSettingsDialog(QWidget* parent, ControllerSettingsDialog* dialog);
|
||||
~ControllerMouseSettingsDialog();
|
||||
|
||||
private:
|
||||
Ui::ControllerMouseSettingsDialog m_ui;
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>902</width>
|
||||
<height>677</height>
|
||||
<height>583</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -26,6 +26,32 @@
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="5" column="0">
|
||||
<widget class="QGroupBox" name="profileSettings">
|
||||
<property name="title">
|
||||
<string>Profile Settings</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_5">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>When this option is enabled, hotkeys can be set in this input profile, and will be used instead of the global hotkeys. By default, hotkeys are always shared between all profiles.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="useProfileHotkeyBindings">
|
||||
<property name="text">
|
||||
<string>Use Per-Profile Hotkeys</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QGroupBox" name="sdlGroup">
|
||||
<property name="title">
|
||||
@@ -65,8 +91,7 @@
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="lightbulb-line">
|
||||
<normaloff>.</normaloff>.
|
||||
</iconset>
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -75,6 +100,19 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>45</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QGroupBox" name="xinputGroup">
|
||||
<property name="title">
|
||||
@@ -101,71 +139,6 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QGroupBox" name="dinputGroup">
|
||||
<property name="title">
|
||||
<string>DInput Source</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_6">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
<string>The DInput source provides support for legacy controllers which do not support XInput. Accessing these controllers via SDL instead is recommended, but DirectInput can be used if they are not compatible with SDL.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="enableDInputSource">
|
||||
<property name="text">
|
||||
<string>Enable DInput Input Source</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>45</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QGroupBox" name="profileSettings">
|
||||
<property name="title">
|
||||
<string>Profile Settings</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_5">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>When this option is enabled, hotkeys can be set in this input profile, and will be used instead of the global hotkeys. By default, hotkeys are always shared between all profiles.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="useProfileHotkeyBindings">
|
||||
<property name="text">
|
||||
<string>Use Per-Profile Hotkeys</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QGroupBox" name="multitapGroup">
|
||||
<property name="title">
|
||||
@@ -199,146 +172,6 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QGroupBox" name="mouseGroup">
|
||||
<property name="title">
|
||||
<string>Mouse/Pointer Source</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Using raw input improves precision when you bind controller sticks to the mouse pointer. Also enables multiple mice to be used.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Horizontal Sensitivity:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QSlider" name="pointerXScale">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>30</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="pointerXScaleLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>10</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Vertical Sensitivity:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QSlider" name="pointerYScale">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>30</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="pointerYScaleLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>10</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="enableMouseMapping">
|
||||
<property name="text">
|
||||
<string>Enable Mouse Mapping</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" rowspan="7">
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
@@ -364,6 +197,72 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QGroupBox" name="dinputGroup">
|
||||
<property name="title">
|
||||
<string>DInput Source</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_6">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
<string>The DInput source provides support for legacy controllers which do not support XInput. Accessing these controllers via SDL instead is recommended, but DirectInput can be used if they are not compatible with SDL.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="enableDInputSource">
|
||||
<property name="text">
|
||||
<string>Enable DInput Input Source</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QGroupBox" name="mouseGroup">
|
||||
<property name="title">
|
||||
<string>Mouse/Pointer Source</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>PCSX2 allows you to use your mouse to simulate analog stick movement.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3" stretch="1,0">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="enableMouseMapping">
|
||||
<property name="text">
|
||||
<string>Enable Mouse Mapping</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="mouseSettings">
|
||||
<property name="toolTip">
|
||||
<string>Controller LED Settings</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Settings...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
|
||||
@@ -0,0 +1,417 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ControllerMouseSettingsDialog</class>
|
||||
<widget class="QDialog" name="ControllerMouseSettingsDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>654</width>
|
||||
<height>169</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Mouse Mapping Settings</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="5" column="1">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Close</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<layout class="QHBoxLayout" name="pointerYSpeed">
|
||||
<item>
|
||||
<widget class="QLabel" name="pointerYSpeedLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>70</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Y Speed</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="pointerYSpeedSlider">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="pointerYSpeedVal">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>30</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>10</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<layout class="QHBoxLayout" name="pointerXSpeed">
|
||||
<item>
|
||||
<widget class="QLabel" name="pointerXSpeedLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>70</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>X Speed</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="pointerXSpeedSlider">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="pointerXSpeedVal">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>30</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>10</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="bottomMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="icon">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><span style=" font-weight:700;">Mouse Mapping Settings</span><br/>These settings fine-tune the behavior when mapping a mouse to the emulated controller.</p></body></html></string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="2">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<layout class="QHBoxLayout" name="pointerInertia">
|
||||
<item>
|
||||
<widget class="QLabel" name="pointerInertiaLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>70</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Inertia</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="pointerInertiaSlider">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="pointerInertiaVal">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>30</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>10</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<layout class="QHBoxLayout" name="pointerXDeadZone">
|
||||
<item>
|
||||
<widget class="QLabel" name="pointerXDeadZoneLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>70</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>X Dead Zone</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="pointerXDeadZoneSlider">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="pointerXDeadZoneVal">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>30</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>10</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<layout class="QHBoxLayout" name="pointerYDeadZone">
|
||||
<item>
|
||||
<widget class="QLabel" name="pointerYDeadZoneLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>70</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Y Dead Zone</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="pointerYDeadZoneSlider">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="pointerYDeadZoneVal">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>30</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>10</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../resources/resources.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -120,8 +120,8 @@ EmulationSettingsWidget::EmulationSettingsWidget(SettingsDialog* dialog, QWidget
|
||||
tr("Allows games and homebrew to access files / folders directly on the host computer."));
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.optimalFramePacing, tr("Optimal Frame Pacing"), tr("Unchecked"),
|
||||
tr("Sets the vsync queue size to 0, making every frame be completed and presented by the GS before input is polled, and the next frame begins. "
|
||||
"Using this setting can reduce input lag, at the cost of measurably higher CPU and GPU requirements."));
|
||||
tr("Sets the VSync queue size to 0, making every frame be completed and presented by the GS before input is polled and the next frame begins. "
|
||||
"Using this setting can reduce input lag at the cost of measurably higher CPU and GPU requirements."));
|
||||
dialog->registerWidgetHelp(m_ui.maxFrameLatency, tr("Maximum Frame Latency"), tr("2 Frames"),
|
||||
tr("Sets the maximum number of frames that can be queued up to the GS, before the CPU thread will wait for one of them to complete before continuing. "
|
||||
"Higher values can assist with smoothing out irregular frame times, but add additional input lag."));
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -372,7 +372,8 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget*
|
||||
dialog->registerWidgetHelp(m_ui.PCRTCOverscan, tr("Show Overscan"), tr("Unchecked"),
|
||||
tr("Enables the option to show the overscan area on games which draw more than the safe area of the screen."));
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.fmvAspectRatio, tr("FMV Aspect Ratio"), tr("Off (Default)"), tr("Overrides the FMV aspect ratio."));
|
||||
dialog->registerWidgetHelp(m_ui.fmvAspectRatio, tr("FMV Aspect Ratio"), tr("Off (Default)"),
|
||||
tr("Overrides the full-motion video (FMV) aspect ratio."));
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.PCRTCAntiBlur, tr("Anti-Blur"), tr("Checked"),
|
||||
tr("Enables internal Anti-Blur hacks. Less accurate to PS2 rendering but will make a lot of games look less blurry."));
|
||||
@@ -402,24 +403,32 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget*
|
||||
tr("Selects the quality at which screenshots will be compressed. Higher values preserve more detail for JPEG, and reduce file "
|
||||
"size for PNG."));
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.stretchY, tr("Stretch Height"), tr("100%"), tr(""));
|
||||
dialog->registerWidgetHelp(m_ui.stretchY, tr("Stretch Height"), tr("100%"),
|
||||
tr("Stretches (> 100%) or squashes (< 100%) the vertical component of the display"));
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.fullscreenModes, tr("Fullscreen Mode"), tr("Borderless Fullscreen"),
|
||||
tr("Chooses the fullscreen resolution and frequency."));
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.cropLeft, tr("Left"), tr("0px"), tr(""));
|
||||
dialog->registerWidgetHelp(m_ui.cropLeft, tr("Left"), tr("0px"),
|
||||
tr("Changes number of pixels cropped from the left side of the display"));
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.cropTop, tr("Top"), tr("0px"), tr(""));
|
||||
dialog->registerWidgetHelp(m_ui.cropTop, tr("Top"), tr("0px"),
|
||||
tr("Changes number of pixels cropped from the top of the display"));
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.cropRight, tr("Right"), tr("0px"), tr(""));
|
||||
dialog->registerWidgetHelp(m_ui.cropRight, tr("Right"), tr("0px"),
|
||||
tr("Changes number of pixels cropped from the right side of the display"));
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.cropBottom, tr("Bottom"), tr("0px"), tr(""));
|
||||
dialog->registerWidgetHelp(m_ui.cropBottom, tr("Bottom"), tr("0px"),
|
||||
tr("Changes number of pixels cropped from the bottom of the display"));
|
||||
}
|
||||
|
||||
// Rendering tab
|
||||
{
|
||||
// Hardware
|
||||
dialog->registerWidgetHelp(m_ui.upscaleMultiplier, tr("Internal Resolution"), tr("Native (PS2) (Default)"), tr(""));
|
||||
dialog->registerWidgetHelp(m_ui.upscaleMultiplier, tr("Internal Resolution"), tr("Native (PS2) (Default)"),
|
||||
tr("Control the resolution at which games are rendered. High resolutions can impact performance on"
|
||||
"older or lower-end GPUs.<br>Non-native resolution may cause minor graphical issues in some games.<br>"
|
||||
"FMV resolution will remain unchanged, as the video files are pre-rendered."));
|
||||
|
||||
dialog->registerWidgetHelp(
|
||||
m_ui.mipmapping, tr("Mipmapping"), tr("Automatic (Default)"), tr("Control the accuracy level of the mipmapping emulation."));
|
||||
@@ -491,7 +500,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget*
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.cpuSpriteRenderBW, tr("CPU Sprite Renderer Size"), tr("0 (Disabled)"), tr(""));
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.cpuCLUTRender, tr("Software Clut Render"), tr("0 (Disabled)"), tr(""));
|
||||
dialog->registerWidgetHelp(m_ui.cpuCLUTRender, tr("Software CLUT Render"), tr("0 (Disabled)"), tr(""));
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.skipDrawStart, tr("Skipdraw Range Start"), tr("0"),
|
||||
tr("Completely skips drawing surfaces from the surface in the left box up to the surface specified in the box on the right."));
|
||||
@@ -592,13 +601,14 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget*
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.shadeBoostSaturation, tr("Saturation"), tr("50"), tr(""));
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.tvShader, tr("TV Shader"), tr("None (Default)"), tr(""));
|
||||
dialog->registerWidgetHelp(m_ui.tvShader, tr("TV Shader"), tr("None (Default)"),
|
||||
tr("Applies a shader which replicates the visual effects of different styles of television set."));
|
||||
}
|
||||
|
||||
// OSD tab
|
||||
{
|
||||
dialog->registerWidgetHelp(
|
||||
m_ui.osdScale, tr("OSD Scale"), tr("100%"), tr("Scales the size of the onscreen OSD from 100% to 500%."));
|
||||
m_ui.osdScale, tr("OSD Scale"), tr("100%"), tr("Scales the size of the onscreen OSD from 50% to 500%."));
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.osdShowMessages, tr("Show OSD Messages"), tr("Checked"),
|
||||
tr("Shows on-screen-display messages when events occur such as save states being "
|
||||
@@ -654,7 +664,8 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget*
|
||||
tr("Allows the GPU instead of just the CPU to transform lines into sprites. "
|
||||
"This reduces CPU load and bandwidth requirement, but it is heavier on the GPU."));
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.gsDumpCompression, tr("GS Dump Compression"), tr("Zstandard (zst)"), tr(""));
|
||||
dialog->registerWidgetHelp(m_ui.gsDumpCompression, tr("GS Dump Compression"), tr("Zstandard (zst)"),
|
||||
tr("Change the compression algorithm used when creating a GS dump."));
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.useBlitSwapChain, tr("Use Blit Swap Chain"), tr("Unchecked"),
|
||||
tr("Uses a blit presentation model instead of flipping when using the Direct3D 11 "
|
||||
|
||||
@@ -1536,7 +1536,7 @@
|
||||
<string>%</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>100</number>
|
||||
<number>50</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>500</number>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -129,7 +129,7 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsDialog* dialog, QWidget
|
||||
m_ui.confirmShutdown, tr("Confirm Shutdown"), tr("Checked"),
|
||||
tr("Determines whether a prompt will be displayed to confirm shutting down the virtual machine "
|
||||
"when the hotkey is pressed."));
|
||||
dialog->registerWidgetHelp(m_ui.saveStateOnShutdown, tr("Save State On Shutdown"), tr("Checked"),
|
||||
dialog->registerWidgetHelp(m_ui.saveStateOnShutdown, tr("Save State On Shutdown"), tr("Unchecked"),
|
||||
tr("Automatically saves the emulator state when powering down or exiting. You can then "
|
||||
"resume directly from where you left off next time."));
|
||||
dialog->registerWidgetHelp(m_ui.pauseOnStart, tr("Pause On Start"), tr("Unchecked"),
|
||||
|
||||
@@ -58,7 +58,7 @@ MemoryCardConvertDialog::MemoryCardConvertDialog(QWidget* parent, QString select
|
||||
SetType(MemoryCardType::File, MemoryCardFileType::PS2_8MB, "A standard, 8 MB memory card. Most compatible, but smallest capacity.");
|
||||
break;
|
||||
case 16:
|
||||
SetType(MemoryCardType::File, MemoryCardFileType::PS2_16MB, "2x larger as a standard memory card. May have some compatibility issues.");
|
||||
SetType(MemoryCardType::File, MemoryCardFileType::PS2_16MB, "2x larger than a standard memory card. May have some compatibility issues.");
|
||||
break;
|
||||
case 32:
|
||||
SetType(MemoryCardType::File, MemoryCardFileType::PS2_32MB, "4x larger than a standard memory card. Likely to have compatibility issues.");
|
||||
|
||||
@@ -115,7 +115,7 @@ void SettingsDialog::setupUi(const GameList::Entry* game)
|
||||
{
|
||||
addWidget(m_game_fix_settings_widget = new GameFixSettingsWidget(this, m_ui.settingsContainer), tr("Game Fix"),
|
||||
QStringLiteral("close-line"),
|
||||
tr("<strong>Game Fix Settings</strong><hr>Gamefixes can work around incorrect emulation in some titles<br>however they can "
|
||||
tr("<strong>Game Fix Settings</strong><hr>Gamefixes can work around incorrect emulation in some titles.<br>However, they can "
|
||||
"also cause problems in games if used incorrectly.<br>It is best to leave them all disabled unless advised otherwise."));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -423,6 +423,9 @@
|
||||
<QtUi Include="Settings\ControllerLEDSettingsDialog.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<QtUi Include="Settings\ControllerMouseSettingsDialog.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<None Include="Settings\USBBindingWidget_DrivingForce.ui" />
|
||||
<None Include="Settings\USBBindingWidget_GTForce.ui" />
|
||||
<QtUi Include="Settings\USBDeviceWidget.ui">
|
||||
@@ -439,4 +442,4 @@
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<Import Project="$(SolutionDir)common\vsprops\QtCompile.targets" />
|
||||
<ImportGroup Label="ExtensionTargets" />
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -583,6 +583,9 @@
|
||||
<QtUi Include="Settings\ControllerLEDSettingsDialog.ui">
|
||||
<Filter>Settings</Filter>
|
||||
</QtUi>
|
||||
<QtUi Include="Settings\ControllerMouseSettingsDialog.ui">
|
||||
<Filter>Settings</Filter>
|
||||
</QtUi>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Settings\FolderSettingsWidget.ui">
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M11.141 4c-1.582 0-2.387.169-3.128.565a3.453 3.453 0 0 0-1.448 1.448C6.169 6.753 6 7.559 6 9.14v5.718c0 1.582.169 2.387.565 3.128.337.63.818 1.111 1.448 1.448.74.396 1.546.565 3.128.565h1.718c1.582 0 2.387-.169 3.128-.565a3.453 3.453 0 0 0 1.448-1.448c.396-.74.565-1.546.565-3.128V9.14c0-1.582-.169-2.387-.565-3.128a3.453 3.453 0 0 0-1.448-1.448C15.247 4.169 14.441 4 12.86 4H11.14zm0-2h1.718c2.014 0 3.094.278 4.072.801a5.452 5.452 0 0 1 2.268 2.268c.523.978.801 2.058.801 4.072v5.718c0 2.014-.278 3.094-.801 4.072a5.452 5.452 0 0 1-2.268 2.268c-.978.523-2.058.801-4.072.801H11.14c-2.014 0-3.094-.278-4.072-.801a5.452 5.452 0 0 1-2.268-2.268C4.278 17.953 4 16.873 4 14.859V9.14c0-2.014.278-3.094.801-4.072A5.452 5.452 0 0 1 7.07 2.801C8.047 2.278 9.127 2 11.141 2zM11 6h2v5h-2V6z"/></svg>
|
||||
|
After Width: | Height: | Size: 918 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M11.141 4c-1.582 0-2.387.169-3.128.565a3.453 3.453 0 0 0-1.448 1.448C6.169 6.753 6 7.559 6 9.14v5.718c0 1.582.169 2.387.565 3.128.337.63.818 1.111 1.448 1.448.74.396 1.546.565 3.128.565h1.718c1.582 0 2.387-.169 3.128-.565a3.453 3.453 0 0 0 1.448-1.448c.396-.74.565-1.546.565-3.128V9.14c0-1.582-.169-2.387-.565-3.128a3.453 3.453 0 0 0-1.448-1.448C15.247 4.169 14.441 4 12.86 4H11.14zm0-2h1.718c2.014 0 3.094.278 4.072.801a5.452 5.452 0 0 1 2.268 2.268c.523.978.801 2.058.801 4.072v5.718c0 2.014-.278 3.094-.801 4.072a5.452 5.452 0 0 1-2.268 2.268c-.978.523-2.058.801-4.072.801H11.14c-2.014 0-3.094-.278-4.072-.801a5.452 5.452 0 0 1-2.268-2.268C4.278 17.953 4 16.873 4 14.859V9.14c0-2.014.278-3.094.801-4.072A5.452 5.452 0 0 1 7.07 2.801C8.047 2.278 9.127 2 11.141 2zM11 6h2v5h-2V6z" fill="#ffffff"/></svg>
|
||||
|
After Width: | Height: | Size: 933 B |
@@ -40,6 +40,7 @@
|
||||
<file>icons/black/svg/lightbulb-line.svg</file>
|
||||
<file>icons/black/svg/list-check.svg</file>
|
||||
<file>icons/black/svg/login-box-line.svg</file>
|
||||
<file>icons/black/svg/mouse-line.svg</file>
|
||||
<file>icons/black/svg/pause-line.svg</file>
|
||||
<file>icons/black/svg/play-line.svg</file>
|
||||
<file>icons/black/svg/price-tag-3-line.svg</file>
|
||||
@@ -100,6 +101,7 @@
|
||||
<file>icons/white/svg/lightbulb-line.svg</file>
|
||||
<file>icons/white/svg/list-check.svg</file>
|
||||
<file>icons/white/svg/login-box-line.svg</file>
|
||||
<file>icons/white/svg/mouse-line.svg</file>
|
||||
<file>icons/white/svg/pause-line.svg</file>
|
||||
<file>icons/white/svg/play-line.svg</file>
|
||||
<file>icons/white/svg/price-tag-3-line.svg</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());
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
/* 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
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
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);
|
||||
}
|
||||
#else
|
||||
// FIXME: FreeBSD equivalent for DKIOCDVDSETSPEED DKIOCCDSETSPEED.
|
||||
DevCon.Warning("CDVD: Setting spindle speed not supported!");
|
||||
#endif
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -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())
|
||||
{
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
+15
-8
@@ -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();
|
||||
}
|
||||
|
||||
@@ -537,12 +543,16 @@ static __fi void DoFMVSwitch()
|
||||
RendererSwitched = false;
|
||||
}
|
||||
|
||||
// Convenience function to update UI thread and set patches.
|
||||
static __fi void frameLimitUpdateCore()
|
||||
// Convenience function to update UI thread and set patches.
|
||||
static __fi void VSyncUpdateCore()
|
||||
{
|
||||
DoFMVSwitch();
|
||||
|
||||
VMManager::Internal::VSyncOnCPUThread();
|
||||
}
|
||||
|
||||
static __fi void VSyncCheckExit()
|
||||
{
|
||||
if (VMManager::Internal::IsExecutionInterrupted())
|
||||
Cpu->ExitExecution();
|
||||
}
|
||||
@@ -553,10 +563,7 @@ static __fi void frameLimit()
|
||||
{
|
||||
// Framelimiter off in settings? Framelimiter go brrr.
|
||||
if (EmuConfig.GS.LimitScalar == 0.0f || s_use_vsync_for_timing)
|
||||
{
|
||||
frameLimitUpdateCore();
|
||||
return;
|
||||
}
|
||||
|
||||
const u64 uExpectedEnd = m_iStart + m_iTicks; // Compute when we would expect this frame to end, assuming everything goes perfectly perfect.
|
||||
const u64 iEnd = GetCPUTicks(); // The current tick we actually stopped on.
|
||||
@@ -567,7 +574,6 @@ static __fi void frameLimit()
|
||||
{
|
||||
// ... Fudge the next frame start over a bit. Prevents fast forward zoomies.
|
||||
m_iStart += (sDeltaTime / m_iTicks) * m_iTicks;
|
||||
frameLimitUpdateCore();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -591,16 +597,17 @@ static __fi void frameLimit()
|
||||
|
||||
// Finally, set our next frame start to when this one ends
|
||||
m_iStart = uExpectedEnd;
|
||||
frameLimitUpdateCore();
|
||||
}
|
||||
|
||||
static __fi void VSyncStart(u32 sCycle)
|
||||
{
|
||||
// Update vibration at the end of a frame.
|
||||
VSyncUpdateCore();
|
||||
PAD::Update();
|
||||
|
||||
frameLimit(); // limit FPS
|
||||
gsPostVsyncStart(); // MUST be after framelimit; doing so before causes funk with frame times!
|
||||
VSyncCheckExit();
|
||||
|
||||
if(EmuConfig.Trace.Enabled && EmuConfig.Trace.EE.m_EnableAll)
|
||||
SysTrace.EE.Counters.Write( " ================ EE COUNTER VSYNC START (frame: %d) ================", g_FrameCount );
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
+216
-249
@@ -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);
|
||||
}
|
||||
|
||||
+18
-149
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -169,8 +169,8 @@ The clamp modes are also numerically based.
|
||||
* alignSprite [`0` or `1`] {Off or On} Default: Off (`0`)
|
||||
* mergeSprite [`0` or `1`] {Off or On} Default: Off (`0`)
|
||||
* wildArmsHack [`0` or `1`] {Off or On} Default: Off (`0`)
|
||||
* skipDrawStart [Value between `0` to `100000`] {0-100000} Default: Off (`0`)
|
||||
* skipDrawEnd [Value between `0` to `100000`] {0-100000} Default: Off (`0`)
|
||||
* skipDrawStart [Value between `0` to `10000`] {0-10000} Default: Off (`0`)
|
||||
* skipDrawEnd [Value between `0` to `10000`] {0-10000} Default: Off (`0`)
|
||||
* halfPixelOffset [`0` or `1` or `2` or `3`] {Off, Normal Vertex, Special Texture or Special Texture Aggressive} Default: Off (`0`)
|
||||
* roundSprite [`0` or `1` or `2`] {Off, Half or Full} Default: Off (`0`)
|
||||
|
||||
|
||||
@@ -160,7 +160,7 @@ void CommonHost::SetDataDirectory()
|
||||
EmuFolders::DataRoot = Path::Combine(StringUtil::WideStringToUTF8String(documents_directory), "PCSX2");
|
||||
CoTaskMemFree(documents_directory);
|
||||
}
|
||||
#elif defined(__linux__)
|
||||
#elif defined(__linux__) || defined(__FreeBSD__)
|
||||
// Use $XDG_CONFIG_HOME/PCSX2 if it exists.
|
||||
const char* xdg_config_home = getenv("XDG_CONFIG_HOME");
|
||||
if (xdg_config_home && Path::IsAbsolute(xdg_config_home))
|
||||
|
||||
@@ -641,13 +641,10 @@ bool D3D11HostDisplay::UpdateImGuiFontTexture()
|
||||
return true;
|
||||
}
|
||||
|
||||
bool D3D11HostDisplay::BeginPresent(bool frame_skip)
|
||||
HostDisplay::PresentResult D3D11HostDisplay::BeginPresent(bool frame_skip)
|
||||
{
|
||||
if (frame_skip || !m_swap_chain)
|
||||
{
|
||||
ImGui::EndFrame();
|
||||
return false;
|
||||
}
|
||||
return PresentResult::FrameSkipped;
|
||||
|
||||
// When using vsync, the time here seems to include the time for the buffer to become available.
|
||||
// This blows our our GPU usage number considerably, so read the timestamp before the final blit
|
||||
@@ -664,7 +661,7 @@ bool D3D11HostDisplay::BeginPresent(bool frame_skip)
|
||||
const CD3D11_RECT scissor(0, 0, m_window_info.surface_width, m_window_info.surface_height);
|
||||
m_context->RSSetViewports(1, &vp);
|
||||
m_context->RSSetScissorRects(1, &scissor);
|
||||
return true;
|
||||
return PresentResult::OK;
|
||||
}
|
||||
|
||||
void D3D11HostDisplay::EndPresent()
|
||||
|
||||
@@ -65,7 +65,7 @@ public:
|
||||
|
||||
void SetVSync(VsyncMode mode) override;
|
||||
|
||||
bool BeginPresent(bool frame_skip) override;
|
||||
PresentResult BeginPresent(bool frame_skip) override;
|
||||
void EndPresent() override;
|
||||
|
||||
bool SetGPUTimingEnabled(bool enabled) override;
|
||||
|
||||
@@ -554,13 +554,13 @@ bool D3D12HostDisplay::UpdateImGuiFontTexture()
|
||||
return ImGui_ImplDX12_CreateFontsTexture();
|
||||
}
|
||||
|
||||
bool D3D12HostDisplay::BeginPresent(bool frame_skip)
|
||||
HostDisplay::PresentResult D3D12HostDisplay::BeginPresent(bool frame_skip)
|
||||
{
|
||||
if (m_device_lost)
|
||||
return HostDisplay::PresentResult::DeviceLost;
|
||||
|
||||
if (frame_skip || !m_swap_chain)
|
||||
{
|
||||
ImGui::EndFrame();
|
||||
return false;
|
||||
}
|
||||
return PresentResult::FrameSkipped;
|
||||
|
||||
static constexpr std::array<float, 4> clear_color = {};
|
||||
D3D12::Texture& swap_chain_buf = m_swap_chain_buffers[m_current_swap_chain_buffer];
|
||||
@@ -574,7 +574,7 @@ bool D3D12HostDisplay::BeginPresent(bool frame_skip)
|
||||
const D3D12_RECT scissor{0, 0, static_cast<LONG>(m_window_info.surface_width), static_cast<LONG>(m_window_info.surface_height)};
|
||||
cmdlist->RSSetViewports(1, &vp);
|
||||
cmdlist->RSSetScissorRects(1, &scissor);
|
||||
return true;
|
||||
return PresentResult::OK;
|
||||
}
|
||||
|
||||
void D3D12HostDisplay::EndPresent()
|
||||
@@ -586,7 +586,11 @@ void D3D12HostDisplay::EndPresent()
|
||||
m_current_swap_chain_buffer = ((m_current_swap_chain_buffer + 1) % static_cast<u32>(m_swap_chain_buffers.size()));
|
||||
|
||||
swap_chain_buf.TransitionToState(g_d3d12_context->GetCommandList(), D3D12_RESOURCE_STATE_PRESENT);
|
||||
g_d3d12_context->ExecuteCommandList(D3D12::Context::WaitType::None);
|
||||
if (!g_d3d12_context->ExecuteCommandList(D3D12::Context::WaitType::None))
|
||||
{
|
||||
m_device_lost = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const bool vsync = static_cast<UINT>(m_vsync_mode != VsyncMode::Off);
|
||||
if (!vsync && m_using_allow_tearing)
|
||||
|
||||
@@ -71,7 +71,7 @@ public:
|
||||
|
||||
void SetVSync(VsyncMode mode) override;
|
||||
|
||||
bool BeginPresent(bool frame_skip) override;
|
||||
PresentResult BeginPresent(bool frame_skip) override;
|
||||
void EndPresent() override;
|
||||
|
||||
bool SetGPUTimingEnabled(bool enabled) override;
|
||||
@@ -97,4 +97,5 @@ protected:
|
||||
|
||||
bool m_allow_tearing_supported = false;
|
||||
bool m_using_allow_tearing = false;
|
||||
bool m_device_lost = false;
|
||||
};
|
||||
|
||||
@@ -90,7 +90,7 @@ bool ImGuiManager::Initialize()
|
||||
return false;
|
||||
}
|
||||
|
||||
s_global_scale = std::max(1.0f, g_host_display->GetWindowScale() * (EmuConfig.GS.OsdScale / 100.0f));
|
||||
s_global_scale = std::max(0.5f, g_host_display->GetWindowScale() * (EmuConfig.GS.OsdScale / 100.0f));
|
||||
|
||||
ImGui::CreateContext();
|
||||
|
||||
@@ -185,7 +185,7 @@ void ImGuiManager::WindowResized()
|
||||
void ImGuiManager::UpdateScale()
|
||||
{
|
||||
const float window_scale = g_host_display ? g_host_display->GetWindowScale() : 1.0f;
|
||||
const float scale = std::max(window_scale * (EmuConfig.GS.OsdScale / 100.0f), 1.0f);
|
||||
const float scale = std::max(window_scale * (EmuConfig.GS.OsdScale / 100.0f), 0.5f);
|
||||
|
||||
if (scale == s_global_scale && (!HasFullscreenFonts() || !ImGuiFullscreen::UpdateLayoutScale()))
|
||||
return;
|
||||
@@ -229,6 +229,12 @@ void ImGuiManager::NewFrame()
|
||||
}
|
||||
}
|
||||
|
||||
void ImGuiManager::SkipFrame()
|
||||
{
|
||||
ImGui::EndFrame();
|
||||
NewFrame();
|
||||
}
|
||||
|
||||
void ImGuiManager::SetStyle()
|
||||
{
|
||||
ImGuiStyle& style = ImGui::GetStyle();
|
||||
|
||||
@@ -40,6 +40,9 @@ namespace ImGuiManager
|
||||
/// Call at the beginning of the frame to set up ImGui state.
|
||||
void NewFrame();
|
||||
|
||||
/// Call when skipping rendering a frame, to update internal state.
|
||||
void SkipFrame();
|
||||
|
||||
/// Renders any on-screen display elements.
|
||||
void RenderOSD();
|
||||
|
||||
|
||||
@@ -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);
|
||||
@@ -159,7 +160,11 @@ struct PointerAxisState
|
||||
static std::array<std::array<float, static_cast<u8>(InputPointerAxis::Count)>, InputManager::MAX_POINTER_DEVICES> s_host_pointer_positions;
|
||||
static std::array<std::array<PointerAxisState, static_cast<u8>(InputPointerAxis::Count)>, InputManager::MAX_POINTER_DEVICES>
|
||||
s_pointer_state;
|
||||
static std::array<float, static_cast<u8>(InputPointerAxis::Count)> s_pointer_axis_scale;
|
||||
static std::array<float, 2> s_pointer_axis_speed;
|
||||
static std::array<float, 2> s_pointer_axis_dead_zone;
|
||||
static std::array<float, 2> s_pointer_axis_range;
|
||||
static std::array<float, 2> s_pointer_pos = {0.0f, 0.0f};
|
||||
static float s_pointer_inertia = 0.0f;
|
||||
|
||||
using PointerMoveCallback = std::function<void(InputBindingKey key, float value)>;
|
||||
using KeyboardEventCallback = std::function<void(InputBindingKey key, float value)>;
|
||||
@@ -585,6 +590,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 +624,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 +647,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 +731,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;
|
||||
@@ -937,15 +954,34 @@ void InputManager::GenerateRelativeMouseEvents()
|
||||
{
|
||||
for (u32 axis = 0; axis < static_cast<u32>(static_cast<u8>(InputPointerAxis::Count)); axis++)
|
||||
{
|
||||
const InputBindingKey key(MakePointerAxisKey(device, static_cast<InputPointerAxis>(axis)));
|
||||
|
||||
PointerAxisState& state = s_pointer_state[device][axis];
|
||||
const float delta = static_cast<float>(state.delta.exchange(0, std::memory_order_acquire)) / 65536.0f;
|
||||
const float unclamped_value = delta * s_pointer_axis_scale[axis];
|
||||
float value = 0.0f;
|
||||
|
||||
const InputBindingKey key(MakePointerAxisKey(device, static_cast<InputPointerAxis>(axis)));
|
||||
if (axis >= static_cast<u32>(InputPointerAxis::WheelX) && ImGuiManager::ProcessPointerAxisEvent(key, unclamped_value))
|
||||
continue;
|
||||
if (axis <= static_cast<u32>(InputPointerAxis::Y))
|
||||
{
|
||||
s_pointer_pos[axis] += delta * s_pointer_axis_speed[axis];
|
||||
value = std::clamp(s_pointer_pos[axis], -1.0f, 1.0f);
|
||||
s_pointer_pos[axis] -= value;
|
||||
s_pointer_pos[axis] *= s_pointer_inertia;
|
||||
|
||||
value *= s_pointer_axis_range[axis];
|
||||
if (value > 0.0f)
|
||||
value += s_pointer_axis_dead_zone[axis];
|
||||
else if (value < 0.0f)
|
||||
value -= s_pointer_axis_dead_zone[axis];
|
||||
}
|
||||
else
|
||||
{
|
||||
// ImGui can consume mouse wheel events when the mouse is over a UI element.
|
||||
if (delta != 0.0f && ImGuiManager::ProcessPointerAxisEvent(key, delta))
|
||||
continue;
|
||||
|
||||
value = std::clamp(delta, -1.0f, 1.0f);
|
||||
}
|
||||
|
||||
const float value = std::clamp(unclamped_value, -1.0f, 1.0f);
|
||||
if (value != state.last_value)
|
||||
{
|
||||
state.last_value = value;
|
||||
@@ -1190,14 +1226,18 @@ void InputManager::ReloadBindings(SettingsInterface& si, SettingsInterface& bind
|
||||
for (u32 pad = 0; pad < PAD::NUM_CONTROLLER_PORTS; pad++)
|
||||
AddPadBindings(binding_si, pad, PAD::GetDefaultPadType(pad));
|
||||
|
||||
for (u32 axis = 0; axis < static_cast<u32>(InputPointerAxis::Count); axis++)
|
||||
constexpr float ui_ctrl_range = 100.0f;
|
||||
constexpr float pointer_sensitivity = 0.05f;
|
||||
for (u32 axis = 0; axis <= static_cast<u32>(InputPointerAxis::Y); axis++)
|
||||
{
|
||||
// From lilypad: 1 mouse pixel = 1/8th way down.
|
||||
const float default_scale = (axis <= static_cast<u32>(InputPointerAxis::Y)) ? 8.0f : 1.0f;
|
||||
s_pointer_axis_scale[axis] =
|
||||
1.0f /
|
||||
std::max(si.GetFloatValue("Pad", fmt::format("Pointer{}Scale", s_pointer_axis_names[axis]).c_str(), default_scale), 1.0f);
|
||||
s_pointer_axis_speed[axis] = si.GetFloatValue("Pad", fmt::format("Pointer{}Speed", s_pointer_axis_names[axis]).c_str(), 40.0f) /
|
||||
ui_ctrl_range * pointer_sensitivity;
|
||||
s_pointer_axis_dead_zone[axis] = std::min(
|
||||
si.GetFloatValue("Pad", fmt::format("Pointer{}DeadZone", s_pointer_axis_names[axis]).c_str(), 20.0f) / ui_ctrl_range, 1.0f);
|
||||
s_pointer_axis_range[axis] = 1.0f - s_pointer_axis_dead_zone[axis];
|
||||
}
|
||||
s_pointer_inertia = si.GetFloatValue("Pad", "PointerInertia", 10.0f) / ui_ctrl_range;
|
||||
s_pointer_pos = {};
|
||||
|
||||
for (u32 port = 0; port < USB::NUM_PORTS; port++)
|
||||
AddUSBBindings(binding_si, port);
|
||||
|
||||
@@ -81,7 +81,7 @@ public:
|
||||
std::unique_ptr<HostDisplayTexture> CreateTexture(u32 width, u32 height, const void* data, u32 data_stride, bool dynamic = false) override;
|
||||
void UpdateTexture(id<MTLTexture> texture, u32 x, u32 y, u32 width, u32 height, const void* data, u32 data_stride);
|
||||
void UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* data, u32 data_stride) override;
|
||||
bool BeginPresent(bool frame_skip) override;
|
||||
PresentResult BeginPresent(bool frame_skip) override;
|
||||
void EndPresent() override;
|
||||
void SetVSync(VsyncMode mode) override;
|
||||
|
||||
|
||||
@@ -265,7 +265,7 @@ void MetalHostDisplay::UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y,
|
||||
|
||||
static bool s_capture_next = false;
|
||||
|
||||
bool MetalHostDisplay::BeginPresent(bool frame_skip)
|
||||
HostDisplay::PresentResult MetalHostDisplay::BeginPresent(bool frame_skip)
|
||||
{ @autoreleasepool {
|
||||
GSDeviceMTL* dev = static_cast<GSDeviceMTL*>(g_gs_device.get());
|
||||
if (dev && m_capture_start_frame && dev->FrameNo() == m_capture_start_frame)
|
||||
@@ -273,7 +273,7 @@ bool MetalHostDisplay::BeginPresent(bool frame_skip)
|
||||
if (frame_skip || m_window_info.type == WindowInfo::Type::Surfaceless || !g_gs_device)
|
||||
{
|
||||
ImGui::EndFrame();
|
||||
return false;
|
||||
return PresentResult::FrameSkipped;
|
||||
}
|
||||
id<MTLCommandBuffer> buf = dev->GetRenderCmdBuf();
|
||||
m_current_drawable = MRCRetain([m_layer nextDrawable]);
|
||||
@@ -284,13 +284,13 @@ bool MetalHostDisplay::BeginPresent(bool frame_skip)
|
||||
[buf popDebugGroup];
|
||||
dev->FlushEncoders();
|
||||
ImGui::EndFrame();
|
||||
return false;
|
||||
return PresentResult::FrameSkipped;
|
||||
}
|
||||
[m_pass_desc colorAttachments][0].texture = [m_current_drawable texture];
|
||||
id<MTLRenderCommandEncoder> enc = [buf renderCommandEncoderWithDescriptor:m_pass_desc];
|
||||
[enc setLabel:@"Present"];
|
||||
dev->m_current_render.encoder = MRCRetain(enc);
|
||||
return true;
|
||||
return PresentResult::OK;
|
||||
}}
|
||||
|
||||
void MetalHostDisplay::EndPresent()
|
||||
|
||||
@@ -335,13 +335,10 @@ bool OpenGLHostDisplay::UpdateImGuiFontTexture()
|
||||
return ImGui_ImplOpenGL3_CreateFontsTexture();
|
||||
}
|
||||
|
||||
bool OpenGLHostDisplay::BeginPresent(bool frame_skip)
|
||||
HostDisplay::PresentResult OpenGLHostDisplay::BeginPresent(bool frame_skip)
|
||||
{
|
||||
if (frame_skip || m_window_info.type == WindowInfo::Type::Surfaceless)
|
||||
{
|
||||
ImGui::EndFrame();
|
||||
return false;
|
||||
}
|
||||
return PresentResult::FrameSkipped;
|
||||
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
@@ -350,7 +347,7 @@ bool OpenGLHostDisplay::BeginPresent(bool frame_skip)
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glViewport(0, 0, m_window_info.surface_width, m_window_info.surface_height);
|
||||
|
||||
return true;
|
||||
return PresentResult::OK;
|
||||
}
|
||||
|
||||
void OpenGLHostDisplay::EndPresent()
|
||||
|
||||
@@ -58,7 +58,7 @@ public:
|
||||
|
||||
void SetVSync(VsyncMode mode) override;
|
||||
|
||||
bool BeginPresent(bool frame_skip) override;
|
||||
PresentResult BeginPresent(bool frame_skip) override;
|
||||
void EndPresent() override;
|
||||
|
||||
bool SetGPUTimingEnabled(bool enabled) override;
|
||||
|
||||
@@ -339,17 +339,18 @@ bool VulkanHostDisplay::DoneCurrent()
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VulkanHostDisplay::BeginPresent(bool frame_skip)
|
||||
HostDisplay::PresentResult VulkanHostDisplay::BeginPresent(bool frame_skip)
|
||||
{
|
||||
if (frame_skip || !m_swap_chain)
|
||||
{
|
||||
ImGui::EndFrame();
|
||||
return false;
|
||||
}
|
||||
return PresentResult::FrameSkipped;
|
||||
|
||||
// Previous frame needs to be presented before we can acquire the swap chain.
|
||||
g_vulkan_context->WaitForPresentComplete();
|
||||
|
||||
// Check if the device was lost.
|
||||
if (g_vulkan_context->CheckLastSubmitFail())
|
||||
return PresentResult::DeviceLost;
|
||||
|
||||
VkResult res = m_swap_chain->AcquireNextImage();
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
@@ -367,7 +368,7 @@ bool VulkanHostDisplay::BeginPresent(bool frame_skip)
|
||||
{
|
||||
Console.Error("Failed to recreate surface after loss");
|
||||
g_vulkan_context->ExecuteCommandBuffer(Vulkan::Context::WaitType::None);
|
||||
return false;
|
||||
return PresentResult::FrameSkipped;
|
||||
}
|
||||
|
||||
res = m_swap_chain->AcquireNextImage();
|
||||
@@ -380,7 +381,7 @@ bool VulkanHostDisplay::BeginPresent(bool frame_skip)
|
||||
// Still submit the command buffer, otherwise we'll end up with several frames waiting.
|
||||
LOG_VULKAN_ERROR(res, "vkAcquireNextImageKHR() failed: ");
|
||||
g_vulkan_context->ExecuteCommandBuffer(Vulkan::Context::WaitType::None);
|
||||
return false;
|
||||
return PresentResult::FrameSkipped;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -401,7 +402,7 @@ bool VulkanHostDisplay::BeginPresent(bool frame_skip)
|
||||
const VkRect2D scissor{{0, 0}, {static_cast<u32>(swap_chain_texture.GetWidth()), static_cast<u32>(swap_chain_texture.GetHeight())}};
|
||||
vkCmdSetViewport(g_vulkan_context->GetCurrentCommandBuffer(), 0, 1, &vp);
|
||||
vkCmdSetScissor(g_vulkan_context->GetCurrentCommandBuffer(), 0, 1, &scissor);
|
||||
return true;
|
||||
return PresentResult::OK;
|
||||
}
|
||||
|
||||
void VulkanHostDisplay::EndPresent()
|
||||
|
||||
@@ -47,7 +47,7 @@ public:
|
||||
|
||||
void SetVSync(VsyncMode mode) override;
|
||||
|
||||
bool BeginPresent(bool frame_skip) override;
|
||||
PresentResult BeginPresent(bool frame_skip) override;
|
||||
void EndPresent() override;
|
||||
|
||||
bool SetGPUTimingEnabled(bool enabled) override;
|
||||
|
||||
+9
-10
@@ -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)
|
||||
|
||||
@@ -153,7 +153,6 @@ union tGS_CSR
|
||||
|
||||
void Reset()
|
||||
{
|
||||
_u64 = 0;
|
||||
FIFO = CSR_FIFO_EMPTY;
|
||||
REV = 0x1B; // GS Revision
|
||||
ID = 0x55; // GS ID
|
||||
|
||||
+54
-35
@@ -209,15 +209,15 @@ static bool DoGSOpen(GSRendererType renderer, u8* basemem)
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
if (!g_gs_device->Create())
|
||||
{
|
||||
if (!g_gs_device->Create())
|
||||
{
|
||||
g_gs_device->Destroy();
|
||||
g_gs_device.reset();
|
||||
return false;
|
||||
}
|
||||
g_gs_device->Destroy();
|
||||
g_gs_device.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!g_gs_renderer)
|
||||
{
|
||||
if (renderer == GSRendererType::Null)
|
||||
{
|
||||
g_gs_renderer = std::make_unique<GSRendererNull>();
|
||||
@@ -230,55 +230,68 @@ static bool DoGSOpen(GSRendererType renderer, u8* basemem)
|
||||
{
|
||||
g_gs_renderer = std::unique_ptr<GSRenderer>(MULTI_ISA_SELECT(makeGSRendererSW)(GSConfig.SWExtraThreads));
|
||||
}
|
||||
|
||||
g_gs_renderer->SetRegsMem(basemem);
|
||||
g_gs_renderer->ResetPCRTC();
|
||||
}
|
||||
catch (std::exception& ex)
|
||||
else
|
||||
{
|
||||
Host::ReportFormattedErrorAsync("GS", "GS error: Exception caught in GSopen: %s", ex.what());
|
||||
g_gs_renderer.reset();
|
||||
g_gs_device->Destroy();
|
||||
g_gs_device.reset();
|
||||
return false;
|
||||
Console.Warning("(DoGSOpen) Using existing renderer.");
|
||||
}
|
||||
|
||||
GSConfig.OsdShowGPU = EmuConfig.GS.OsdShowGPU && g_host_display->SetGPUTimingEnabled(true);
|
||||
|
||||
g_gs_renderer->SetRegsMem(basemem);
|
||||
g_perfmon.Reset();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GSreopen(bool recreate_display, const Pcsx2Config::GSOptions& old_config)
|
||||
bool GSreopen(bool recreate_display, bool recreate_renderer, const Pcsx2Config::GSOptions& old_config)
|
||||
{
|
||||
Console.WriteLn("Reopening GS with %s display", recreate_display ? "new" : "existing");
|
||||
|
||||
g_gs_renderer->Flush(GSState::GSFlushReason::GSREOPEN);
|
||||
if (recreate_renderer)
|
||||
g_gs_renderer->Flush(GSState::GSFlushReason::GSREOPEN);
|
||||
|
||||
freezeData fd = {};
|
||||
if (g_gs_renderer->Freeze(&fd, true) != 0)
|
||||
std::unique_ptr<u8[]> fd_data;
|
||||
if (recreate_renderer)
|
||||
{
|
||||
Console.Error("(GSreopen) Failed to get GS freeze size");
|
||||
return false;
|
||||
}
|
||||
if (g_gs_renderer->Freeze(&fd, true) != 0)
|
||||
{
|
||||
Console.Error("(GSreopen) Failed to get GS freeze size");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<u8[]> fd_data = std::make_unique<u8[]>(fd.size);
|
||||
fd.data = fd_data.get();
|
||||
if (g_gs_renderer->Freeze(&fd, false) != 0)
|
||||
fd_data = std::make_unique<u8[]>(fd.size);
|
||||
fd.data = fd_data.get();
|
||||
if (g_gs_renderer->Freeze(&fd, false) != 0)
|
||||
{
|
||||
Console.Error("(GSreopen) Failed to freeze GS");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Error("(GSreopen) Failed to freeze GS");
|
||||
return false;
|
||||
// Make sure nothing is left over.
|
||||
g_gs_renderer->PurgePool();
|
||||
g_gs_renderer->PurgeTextureCache();
|
||||
}
|
||||
|
||||
if (recreate_display)
|
||||
{
|
||||
g_gs_device->ResetAPIState();
|
||||
if (Host::BeginPresentFrame(true))
|
||||
if (Host::BeginPresentFrame(false) == HostDisplay::PresentResult::OK)
|
||||
Host::EndPresentFrame();
|
||||
}
|
||||
|
||||
u8* basemem = g_gs_renderer->GetRegsMem();
|
||||
const u32 gamecrc = g_gs_renderer->GetGameCRC();
|
||||
g_gs_renderer->Destroy();
|
||||
g_gs_renderer.reset();
|
||||
if (recreate_renderer)
|
||||
{
|
||||
g_gs_renderer->Destroy();
|
||||
g_gs_renderer.reset();
|
||||
}
|
||||
|
||||
g_gs_device->Destroy();
|
||||
g_gs_device.reset();
|
||||
|
||||
@@ -327,13 +340,17 @@ bool GSreopen(bool recreate_display, const Pcsx2Config::GSOptions& old_config)
|
||||
}
|
||||
}
|
||||
|
||||
if (g_gs_renderer->Defrost(&fd) != 0)
|
||||
if (recreate_renderer)
|
||||
{
|
||||
Console.Error("(GSreopen) Failed to defrost");
|
||||
return false;
|
||||
if (g_gs_renderer->Defrost(&fd) != 0)
|
||||
{
|
||||
Console.Error("(GSreopen) Failed to defrost");
|
||||
return false;
|
||||
}
|
||||
|
||||
g_gs_renderer->SetGameCRC(gamecrc);
|
||||
}
|
||||
|
||||
g_gs_renderer->SetGameCRC(gamecrc);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -698,7 +715,7 @@ void GSUpdateConfig(const Pcsx2Config::GSOptions& new_config)
|
||||
GSConfig.DisableShaderCache != old_config.DisableShaderCache ||
|
||||
GSConfig.DisableThreadedPresentation != old_config.DisableThreadedPresentation
|
||||
);
|
||||
if (!GSreopen(do_full_restart, old_config))
|
||||
if (!GSreopen(do_full_restart, true, old_config))
|
||||
pxFailRel("Failed to do full GS reopen");
|
||||
return;
|
||||
}
|
||||
@@ -709,7 +726,7 @@ void GSUpdateConfig(const Pcsx2Config::GSOptions& new_config)
|
||||
GSConfig.SWExtraThreads != old_config.SWExtraThreads ||
|
||||
GSConfig.SWExtraThreadsHeight != old_config.SWExtraThreadsHeight)
|
||||
{
|
||||
if (!GSreopen(false, old_config))
|
||||
if (!GSreopen(false, true, old_config))
|
||||
pxFailRel("Failed to do quick GS reopen");
|
||||
|
||||
return;
|
||||
@@ -783,11 +800,13 @@ void GSSwitchRenderer(GSRendererType new_renderer)
|
||||
if (existing_api == RenderAPI::OpenGLES)
|
||||
existing_api = RenderAPI::OpenGL;
|
||||
|
||||
g_gs_renderer->PurgeTextureCache();
|
||||
|
||||
const bool is_software_switch = (new_renderer == GSRendererType::SW || GSConfig.Renderer == GSRendererType::SW);
|
||||
const bool recreate_display = (!is_software_switch && existing_api != GetAPIForRenderer(new_renderer));
|
||||
const Pcsx2Config::GSOptions old_config(GSConfig);
|
||||
GSConfig.Renderer = new_renderer;
|
||||
if (!GSreopen(recreate_display, old_config))
|
||||
if (!GSreopen(recreate_display, true, old_config))
|
||||
pxFailRel("Failed to reopen GS for renderer switch.");
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ s16 GSLookupBeforeDrawFunctionId(const std::string_view& name);
|
||||
int GSinit();
|
||||
void GSshutdown();
|
||||
bool GSopen(const Pcsx2Config::GSOptions& config, GSRendererType renderer, u8* basemem);
|
||||
bool GSreopen(bool recreate_display, const Pcsx2Config::GSOptions& old_config);
|
||||
bool GSreopen(bool recreate_display, bool recreate_renderer, const Pcsx2Config::GSOptions& old_config);
|
||||
void GSreset(bool hardware_reset);
|
||||
void GSclose();
|
||||
void GSgifSoftReset(u32 mask);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -24,15 +24,6 @@ const CRC::Game CRC::m_games[] =
|
||||
{
|
||||
// Note: IDs 0x7ACF7E03, 0x7D4EA48F, 0x37C53760 - shouldn't be added as it's from the multiloaders when packing games.
|
||||
{0x00000000, NoTitle /* NoRegion */},
|
||||
{0x9AAC5309, FFX2 /* EU */},
|
||||
{0x9AAC530C, FFX2 /* FR */},
|
||||
{0x9AAC530A, FFX2 /* ES */},
|
||||
{0x9AAC530D, FFX2 /* DE */},
|
||||
{0x9AAC530B, FFX2 /* IT */},
|
||||
{0x48FE0C71, FFX2 /* US */},
|
||||
{0x8A6D7F14, FFX2 /* JP */},
|
||||
{0xE1FD9A2D, FFX2 /* JP */}, // int.
|
||||
{0x11624CD6, FFX2 /* KO */},
|
||||
{0x08C1ED4D, HauntingGround /* EU */},
|
||||
{0x2CD5794C, HauntingGround /* EU */},
|
||||
{0x867BB945, HauntingGround /* JP */},
|
||||
|
||||
@@ -23,7 +23,6 @@ public:
|
||||
enum Title : u32
|
||||
{
|
||||
NoTitle,
|
||||
FFX2,
|
||||
GetawayGames,
|
||||
HauntingGround,
|
||||
ICO,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -481,7 +481,7 @@ public:
|
||||
static psm_t m_psm[64];
|
||||
static readImage m_readImageX;
|
||||
|
||||
static const int m_vmsize = 1024 * 1024 * 4;
|
||||
static constexpr int m_vmsize = 1024 * 1024 * 4;
|
||||
|
||||
u8* m_vm8;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
+199
-395
@@ -26,6 +26,7 @@
|
||||
#include <iomanip> // Dump Verticles
|
||||
|
||||
int GSState::s_n = 0;
|
||||
int GSState::s_transfer_n = 0;
|
||||
|
||||
static __fi bool IsAutoFlushEnabled()
|
||||
{
|
||||
@@ -37,14 +38,61 @@ static __fi bool IsFirstProvokingVertex()
|
||||
return (GSConfig.Renderer != GSRendererType::SW && !g_gs_device->Features().provoking_vertex_last);
|
||||
}
|
||||
|
||||
constexpr int GSState::GetSaveStateSize()
|
||||
{
|
||||
int size = 0;
|
||||
|
||||
size += sizeof(STATE_VERSION);
|
||||
size += sizeof(m_env.PRIM);
|
||||
size += sizeof(m_env.PRMODECONT);
|
||||
size += sizeof(m_env.TEXCLUT);
|
||||
size += sizeof(m_env.SCANMSK);
|
||||
size += sizeof(m_env.TEXA);
|
||||
size += sizeof(m_env.FOGCOL);
|
||||
size += sizeof(m_env.DIMX);
|
||||
size += sizeof(m_env.DTHE);
|
||||
size += sizeof(m_env.COLCLAMP);
|
||||
size += sizeof(m_env.PABE);
|
||||
size += sizeof(m_env.BITBLTBUF);
|
||||
size += sizeof(m_env.TRXDIR);
|
||||
size += sizeof(m_env.TRXPOS);
|
||||
size += sizeof(m_env.TRXREG);
|
||||
size += sizeof(m_env.TRXREG); // obsolete
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
size += sizeof(m_env.CTXT[i].XYOFFSET);
|
||||
size += sizeof(m_env.CTXT[i].TEX0);
|
||||
size += sizeof(m_env.CTXT[i].TEX1);
|
||||
size += sizeof(m_env.CTXT[i].CLAMP);
|
||||
size += sizeof(m_env.CTXT[i].MIPTBP1);
|
||||
size += sizeof(m_env.CTXT[i].MIPTBP2);
|
||||
size += sizeof(m_env.CTXT[i].SCISSOR);
|
||||
size += sizeof(m_env.CTXT[i].ALPHA);
|
||||
size += sizeof(m_env.CTXT[i].TEST);
|
||||
size += sizeof(m_env.CTXT[i].FBA);
|
||||
size += sizeof(m_env.CTXT[i].FRAME);
|
||||
size += sizeof(m_env.CTXT[i].ZBUF);
|
||||
}
|
||||
|
||||
size += sizeof(m_v.RGBAQ);
|
||||
size += sizeof(m_v.ST);
|
||||
size += sizeof(m_v.UV);
|
||||
size += sizeof(m_v.FOG);
|
||||
size += sizeof(m_v.XYZ);
|
||||
size += sizeof(GIFReg); // obsolete
|
||||
|
||||
size += sizeof(m_tr.x);
|
||||
size += sizeof(m_tr.y);
|
||||
size += GSLocalMemory::m_vmsize;
|
||||
size += (sizeof(GIFPath::tag) + sizeof(GIFPath::reg)) * 4 /* std::size(GSState::m_path) */; // std::size won't work without an instance.
|
||||
size += sizeof(m_q);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
GSState::GSState()
|
||||
: m_version(STATE_VERSION)
|
||||
, m_q(1.0f)
|
||||
, m_scanmask_used(0)
|
||||
, tex_flushed(true)
|
||||
, m_vt(this, IsFirstProvokingVertex())
|
||||
, m_regs(NULL)
|
||||
, m_crc(0)
|
||||
: m_vt(this, IsFirstProvokingVertex())
|
||||
{
|
||||
// m_nativeres seems to be a hack. Unfortunately it impacts draw call number which make debug painful in the replayer.
|
||||
// Let's keep it disabled to ease debug.
|
||||
@@ -56,58 +104,12 @@ 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_sssize = 0;
|
||||
|
||||
m_sssize += sizeof(m_version);
|
||||
m_sssize += sizeof(m_env.PRIM);
|
||||
m_sssize += sizeof(m_env.PRMODECONT);
|
||||
m_sssize += sizeof(m_env.TEXCLUT);
|
||||
m_sssize += sizeof(m_env.SCANMSK);
|
||||
m_sssize += sizeof(m_env.TEXA);
|
||||
m_sssize += sizeof(m_env.FOGCOL);
|
||||
m_sssize += sizeof(m_env.DIMX);
|
||||
m_sssize += sizeof(m_env.DTHE);
|
||||
m_sssize += sizeof(m_env.COLCLAMP);
|
||||
m_sssize += sizeof(m_env.PABE);
|
||||
m_sssize += sizeof(m_env.BITBLTBUF);
|
||||
m_sssize += sizeof(m_env.TRXDIR);
|
||||
m_sssize += sizeof(m_env.TRXPOS);
|
||||
m_sssize += sizeof(m_env.TRXREG);
|
||||
m_sssize += sizeof(m_env.TRXREG); // obsolete
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
m_sssize += sizeof(m_env.CTXT[i].XYOFFSET);
|
||||
m_sssize += sizeof(m_env.CTXT[i].TEX0);
|
||||
m_sssize += sizeof(m_env.CTXT[i].TEX1);
|
||||
m_sssize += sizeof(m_env.CTXT[i].CLAMP);
|
||||
m_sssize += sizeof(m_env.CTXT[i].MIPTBP1);
|
||||
m_sssize += sizeof(m_env.CTXT[i].MIPTBP2);
|
||||
m_sssize += sizeof(m_env.CTXT[i].SCISSOR);
|
||||
m_sssize += sizeof(m_env.CTXT[i].ALPHA);
|
||||
m_sssize += sizeof(m_env.CTXT[i].TEST);
|
||||
m_sssize += sizeof(m_env.CTXT[i].FBA);
|
||||
m_sssize += sizeof(m_env.CTXT[i].FRAME);
|
||||
m_sssize += sizeof(m_env.CTXT[i].ZBUF);
|
||||
}
|
||||
|
||||
m_sssize += sizeof(m_v.RGBAQ);
|
||||
m_sssize += sizeof(m_v.ST);
|
||||
m_sssize += sizeof(m_v.UV);
|
||||
m_sssize += sizeof(m_v.FOG);
|
||||
m_sssize += sizeof(m_v.XYZ);
|
||||
m_sssize += sizeof(GIFReg); // obsolete
|
||||
|
||||
m_sssize += sizeof(m_tr.x);
|
||||
m_sssize += sizeof(m_tr.y);
|
||||
m_sssize += m_mem.m_vmsize;
|
||||
m_sssize += (sizeof(m_path[0].tag) + sizeof(m_path[0].reg)) * std::size(m_path);
|
||||
m_sssize += sizeof(m_q);
|
||||
m_draw_transfers.clear();
|
||||
|
||||
PRIM = &m_env.PRIM;
|
||||
//CSR->rREV = 0x20;
|
||||
@@ -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();
|
||||
|
||||
@@ -285,6 +285,14 @@ void GSState::ResetHandlers()
|
||||
m_fpGIFRegHandlers[GIF_A_D_REG_LABEL] = &GSState::GIFRegHandlerNull;
|
||||
}
|
||||
|
||||
void GSState::ResetPCRTC()
|
||||
{
|
||||
PCRTCDisplays.SetVideoMode(GetVideoMode());
|
||||
PCRTCDisplays.EnableDisplays(m_regs->PMODE, m_regs->SMODE2, isReallyInterlaced());
|
||||
PCRTCDisplays.SetRects(0, m_regs->DISP[0].DISPLAY, m_regs->DISP[0].DISPFB);
|
||||
PCRTCDisplays.SetRects(1, m_regs->DISP[1].DISPLAY, m_regs->DISP[1].DISPFB);
|
||||
}
|
||||
|
||||
void GSState::UpdateSettings(const Pcsx2Config::GSOptions& old_config)
|
||||
{
|
||||
m_mipmap = GSConfig.Mipmap;
|
||||
@@ -340,254 +348,6 @@ GSVideoMode GSState::GetVideoMode()
|
||||
__assume(0); // unreachable
|
||||
}
|
||||
|
||||
bool GSState::IsAnalogue()
|
||||
{
|
||||
return GetVideoMode() == GSVideoMode::NTSC || GetVideoMode() == GSVideoMode::PAL || GetVideoMode() == GSVideoMode::HDTV_1080I;
|
||||
}
|
||||
|
||||
GSVector4i GSState::GetFrameMagnifiedRect(int i)
|
||||
{
|
||||
GSVector4i rectangle = { 0, 0, 0, 0 };
|
||||
|
||||
if (!IsEnabled(i))
|
||||
return rectangle;
|
||||
|
||||
const int videomode = static_cast<int>(GetVideoMode()) - 1;
|
||||
const auto& DISP = m_regs->DISP[i].DISPLAY;
|
||||
const bool ignore_offset = !GSConfig.PCRTCOffsets;
|
||||
|
||||
const u32 DW = DISP.DW + 1;
|
||||
const u32 DH = DISP.DH + 1;
|
||||
;
|
||||
// The number of sub pixels to draw are given in DH and DW, the MAGH/V relates to the size of the original square in the FB
|
||||
// but the size it's drawn to uses the default size of the display mode (for PAL/NTSC this is a MAGH of 3)
|
||||
// so for example a game will have a DW of 2559 and a MAGH of 4 to make it 512 (from the FB), but because it's drawing 2560 subpixels
|
||||
// it will cover the entire 640 wide of the screen (2560 / (3+1)).
|
||||
int width;
|
||||
int height;
|
||||
if (ignore_offset)
|
||||
{
|
||||
width = (DW / (DISP.MAGH + 1));
|
||||
height = (DH / (DISP.MAGV + 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
width = (DW / (VideoModeDividers[videomode].x + 1));
|
||||
height = (DH / (VideoModeDividers[videomode].y + 1));
|
||||
}
|
||||
|
||||
int res_multi = 1;
|
||||
|
||||
if (isinterlaced() && m_regs->SMODE2.FFMD && height > 1)
|
||||
res_multi = 2;
|
||||
|
||||
// Set up the display rectangle based on the values obtained from DISPLAY registers
|
||||
rectangle.right = width;
|
||||
rectangle.bottom = height / res_multi;
|
||||
|
||||
return rectangle;
|
||||
}
|
||||
|
||||
int GSState::GetDisplayHMagnification()
|
||||
{
|
||||
// Pick one of the DISPLAY's and hope that they are both the same. Favour DISPLAY[1]
|
||||
for (int i = 1; i >= 0; i--)
|
||||
{
|
||||
if (IsEnabled(i))
|
||||
return m_regs->DISP[i].DISPLAY.MAGH + 1;
|
||||
}
|
||||
|
||||
// If neither DISPLAY is enabled, fallback to resolution offset (should never happen)
|
||||
const int videomode = static_cast<int>(GetVideoMode()) - 1;
|
||||
return VideoModeDividers[videomode].x + 1;
|
||||
}
|
||||
|
||||
GSVector4i GSState::GetDisplayRect(int i)
|
||||
{
|
||||
GSVector4i rectangle = { 0, 0, 0, 0 };
|
||||
|
||||
if (i == -1)
|
||||
{
|
||||
return GetDisplayRect(0).runion(GetDisplayRect(1));
|
||||
}
|
||||
|
||||
if (!IsEnabled(i))
|
||||
return rectangle;
|
||||
|
||||
const auto& DISP = m_regs->DISP[i].DISPLAY;
|
||||
|
||||
const u32 DW = DISP.DW + 1;
|
||||
const u32 DH = DISP.DH + 1;
|
||||
const u32 MAGH = DISP.MAGH + 1;
|
||||
const u32 MAGV = DISP.MAGV + 1;
|
||||
|
||||
const GSVector2i magnification(MAGH, MAGV);
|
||||
|
||||
const int width = DW / magnification.x;
|
||||
const int height = DH / magnification.y;
|
||||
|
||||
const GSVector2i offsets = GetResolutionOffset(i);
|
||||
|
||||
// Set up the display rectangle based on the values obtained from DISPLAY registers
|
||||
rectangle.left = offsets.x;
|
||||
rectangle.top = offsets.y;
|
||||
|
||||
rectangle.right = rectangle.left + width;
|
||||
rectangle.bottom = rectangle.top + height;
|
||||
|
||||
return rectangle;
|
||||
}
|
||||
|
||||
GSVector2i GSState::GetResolutionOffset(int i)
|
||||
{
|
||||
GSVector2i offset = { 0, 0 };
|
||||
|
||||
if (!IsEnabled(i))
|
||||
return offset;
|
||||
|
||||
const int videomode = static_cast<int>(GetVideoMode()) - 1;
|
||||
const auto& DISP = m_regs->DISP[i].DISPLAY;
|
||||
|
||||
const auto& SMODE2 = m_regs->SMODE2;
|
||||
const int res_multi = (SMODE2.INT + 1);
|
||||
const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode];
|
||||
|
||||
offset.x = (static_cast<int>(DISP.DX) - offsets.z) / (VideoModeDividers[videomode].x + 1);
|
||||
offset.y = (static_cast<int>(DISP.DY) - (offsets.w * ((IsAnalogue() && res_multi) ? res_multi : 1))) / (VideoModeDividers[videomode].y + 1);
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
GSVector2i GSState::GetResolution()
|
||||
{
|
||||
const int videomode = static_cast<int>(GetVideoMode()) - 1;
|
||||
const bool ignore_offset = !GSConfig.PCRTCOffsets;
|
||||
|
||||
const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode];
|
||||
|
||||
GSVector2i resolution(offsets.x, offsets.y);
|
||||
|
||||
// The resolution of the framebuffer is double when in FRAME mode and interlaced.
|
||||
// Also we need a special check because no-interlace patches like to render in the original height, but in non-interlaced mode
|
||||
// which means it would normally go off the bottom of the screen. Advantages of emulation, i guess... Limited to Ignore Offsets + Deinterlacing = Off.
|
||||
if ((isinterlaced() && !m_regs->SMODE2.FFMD) || (GSConfig.InterlaceMode == GSInterlaceMode::Off && !GSConfig.PCRTCOffsets && !isinterlaced()))
|
||||
resolution.y *= 2;
|
||||
|
||||
if (ignore_offset)
|
||||
{
|
||||
// Ideally we'd just cut the width at the resolution, but of course we have to hack the hack...
|
||||
// Some games (Mortal Kombat Armageddon) render the image at 834 pixels then shrink it to 624 pixels
|
||||
// which does fit, but when we ignore offsets we go on framebuffer size and some other games
|
||||
// such as Johnny Mosleys Mad Trix and Transformers render too much but design it to go off the screen.
|
||||
int magnified_width = (VideoModeDividers[videomode].z + 1) / GetDisplayHMagnification();
|
||||
|
||||
// When viewing overscan allow up to the overscan size
|
||||
if (GSConfig.PCRTCOverscan)
|
||||
magnified_width = std::max(magnified_width, offsets.x);
|
||||
|
||||
GSVector4i total_rect = GetDisplayRect(0).runion(GetDisplayRect(1));
|
||||
total_rect.z = total_rect.z - total_rect.x;
|
||||
total_rect.w = total_rect.w - total_rect.y;
|
||||
total_rect.z = std::min(total_rect.z, magnified_width);
|
||||
total_rect.w = std::min(total_rect.w, resolution.y);
|
||||
resolution.x = total_rect.z;
|
||||
resolution.y = total_rect.w;
|
||||
}
|
||||
|
||||
return resolution;
|
||||
}
|
||||
|
||||
GSVector4i GSState::GetFrameRect(int i, bool ignore_off)
|
||||
{
|
||||
// If no specific context is requested then pass the merged rectangle as return value
|
||||
if (i == -1)
|
||||
return GetFrameRect(0, ignore_off).runion(GetFrameRect(1, ignore_off));
|
||||
|
||||
GSVector4i rectangle = { 0, 0, 0, 0 };
|
||||
|
||||
if (!IsEnabled(i))
|
||||
return rectangle;
|
||||
|
||||
const auto& DISP = m_regs->DISP[i].DISPLAY;
|
||||
|
||||
const u32 DW = DISP.DW + 1;
|
||||
const u32 DH = DISP.DH + 1;
|
||||
const GSVector2i magnification(DISP.MAGH+1, DISP.MAGV + 1);
|
||||
|
||||
const u32 DBX = m_regs->DISP[i].DISPFB.DBX;
|
||||
int DBY = m_regs->DISP[i].DISPFB.DBY;
|
||||
|
||||
|
||||
const int w = DW / magnification.x;
|
||||
const int h = DH / magnification.y;
|
||||
|
||||
// If the combined height overflows 2048, it's likely adding a bit of extra data before the picture for offsetting the interlace
|
||||
// only game known to do this is NASCAR '08
|
||||
if (!ignore_off && (DBY + h) >= 2048)
|
||||
DBY = DBY - 2048;
|
||||
|
||||
rectangle.left = (ignore_off) ? 0 : DBX;
|
||||
rectangle.top = (ignore_off) ? 0 : DBY;
|
||||
|
||||
rectangle.right = rectangle.left + w;
|
||||
rectangle.bottom = rectangle.top + h;
|
||||
|
||||
if (isinterlaced() && m_regs->SMODE2.FFMD && h > 1)
|
||||
{
|
||||
rectangle.bottom += 1;
|
||||
rectangle.bottom >>= 1;
|
||||
}
|
||||
|
||||
return rectangle;
|
||||
}
|
||||
|
||||
int GSState::GetFramebufferHeight()
|
||||
{
|
||||
// Framebuffer height is 11 bits max
|
||||
constexpr int height_limit = (1 << 11);
|
||||
|
||||
const GSVector4i disp1_rect = GetFrameRect(0, true);
|
||||
const GSVector4i disp2_rect = GetFrameRect(1, true);
|
||||
const GSVector4i combined = disp1_rect.runion(disp2_rect);
|
||||
|
||||
// DBY isn't an offset to the frame memory but rather an offset to read output circuit inside
|
||||
// the frame memory, hence the top offset should also be calculated for the total height of the
|
||||
// frame memory. Also we need to wrap the value only when we're dealing with values with range of the
|
||||
// frame memory (offset + read output circuit height, IOW bottom of merged_output)
|
||||
const int max_height = std::max(disp1_rect.height(), disp2_rect.height());
|
||||
const int frame_memory_height = std::max(max_height, combined.bottom % height_limit);
|
||||
|
||||
if (frame_memory_height > 1024)
|
||||
GL_PERF("Massive framebuffer height detected! (height:%d)", frame_memory_height);
|
||||
|
||||
return frame_memory_height;
|
||||
}
|
||||
|
||||
int GSState::GetFramebufferWidth()
|
||||
{
|
||||
const GSVector4i disp1_rect = GetFrameRect(0, true);
|
||||
const GSVector4i disp2_rect = GetFrameRect(1, true);
|
||||
|
||||
const int max_width = std::max(disp1_rect.width(), disp2_rect.width());
|
||||
|
||||
return max_width;
|
||||
}
|
||||
|
||||
bool GSState::IsEnabled(int i)
|
||||
{
|
||||
ASSERT(i >= 0 && i < 2);
|
||||
|
||||
const auto& DISP = m_regs->DISP[i].DISPLAY;
|
||||
|
||||
const bool disp1_enabled = m_regs->PMODE.EN1;
|
||||
const bool disp2_enabled = m_regs->PMODE.EN2;
|
||||
|
||||
if ((i == 0 && disp1_enabled) || (i == 1 && disp2_enabled))
|
||||
return DISP.DW && DISP.DH;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
float GSState::GetTvRefreshRate()
|
||||
{
|
||||
const GSVideoMode videomode = GetVideoMode();
|
||||
@@ -673,14 +433,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 +458,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 +476,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 +822,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 +1449,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)
|
||||
@@ -1762,6 +1521,7 @@ void GSState::FlushWrite()
|
||||
m_tr.start += len;
|
||||
|
||||
g_perfmon.Put(GSPerfMon::Swizzle, len);
|
||||
s_transfer_n++;
|
||||
}
|
||||
|
||||
// This function decides if the context has changed in a way which warrants flushing the draw.
|
||||
@@ -1844,10 +1604,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 +1624,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 +1692,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 +1744,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 +1758,20 @@ 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);
|
||||
|
||||
GSVector4i r;
|
||||
|
||||
r.left = m_env.TRXPOS.DSAX;
|
||||
r.top = m_env.TRXPOS.DSAY;
|
||||
r.right = r.left + m_env.TRXREG.RRW;
|
||||
r.bottom = r.top + m_env.TRXREG.RRH;
|
||||
|
||||
// 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, r, 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,
|
||||
@@ -2008,12 +1780,6 @@ void GSState::Write(const u8* mem, int len)
|
||||
if (m_tr.end == 0 && len >= m_tr.total)
|
||||
{
|
||||
// received all data in one piece, no need to buffer it
|
||||
GSVector4i r;
|
||||
|
||||
r.left = m_env.TRXPOS.DSAX;
|
||||
r.top = m_env.TRXPOS.DSAY;
|
||||
r.right = r.left + m_env.TRXREG.RRW;
|
||||
r.bottom = r.top + m_env.TRXREG.RRH;
|
||||
ExpandTarget(m_env.BITBLTBUF, r);
|
||||
InvalidateVideoMem(blit, r, true);
|
||||
|
||||
@@ -2022,6 +1788,7 @@ void GSState::Write(const u8* mem, int len)
|
||||
m_tr.start = m_tr.end = m_tr.total;
|
||||
|
||||
g_perfmon.Put(GSPerfMon::Swizzle, len);
|
||||
s_transfer_n++;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2088,6 +1855,7 @@ void GSState::Move()
|
||||
{
|
||||
// ffxii uses this to move the top/bottom of the scrolling menus offscreen and then blends them back over the text to create a shading effect
|
||||
// guitar hero copies the far end of the board to do a similar blend too
|
||||
s_transfer_n++;
|
||||
|
||||
int sx = m_env.TRXPOS.SSAX;
|
||||
int sy = m_env.TRXPOS.SSAY;
|
||||
@@ -2146,6 +1914,21 @@ void GSState::Move()
|
||||
{
|
||||
Flush(GSFlushReason::LOCALTOLOCALMOVE);
|
||||
}
|
||||
|
||||
// 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))
|
||||
{
|
||||
GSVector4i r;
|
||||
|
||||
r.left = m_env.TRXPOS.DSAX;
|
||||
r.top = m_env.TRXPOS.DSAY;
|
||||
r.right = r.left + m_env.TRXREG.RRW;
|
||||
r.bottom = r.top + m_env.TRXREG.RRH;
|
||||
|
||||
GSUploadQueue new_transfer = { m_env.BITBLTBUF, r, 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);
|
||||
|
||||
@@ -2553,18 +2336,19 @@ int GSState::Freeze(freezeData* fd, bool sizeonly)
|
||||
{
|
||||
if (sizeonly)
|
||||
{
|
||||
fd->size = m_sssize;
|
||||
fd->size = GetSaveStateSize();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!fd->data || fd->size < m_sssize)
|
||||
if (!fd->data || fd->size < GetSaveStateSize())
|
||||
return -1;
|
||||
|
||||
Flush(GSFlushReason::SAVESTATE);
|
||||
|
||||
u8* data = fd->data;
|
||||
const u32 version = STATE_VERSION;
|
||||
|
||||
WriteState(data, &m_version);
|
||||
WriteState(data, &version);
|
||||
WriteState(data, &m_env.PRIM);
|
||||
WriteState(data, &m_env.PRMODECONT);
|
||||
WriteState(data, &m_env.TEXCLUT);
|
||||
@@ -2632,7 +2416,7 @@ int GSState::Defrost(const freezeData* fd)
|
||||
if (!fd || !fd->data || fd->size == 0)
|
||||
return -1;
|
||||
|
||||
if (fd->size < m_sssize)
|
||||
if (fd->size < GetSaveStateSize())
|
||||
return -1;
|
||||
|
||||
u8* data = fd->data;
|
||||
@@ -2641,7 +2425,7 @@ int GSState::Defrost(const freezeData* fd)
|
||||
|
||||
ReadState(&version, data);
|
||||
|
||||
if (version > m_version)
|
||||
if (version > STATE_VERSION)
|
||||
{
|
||||
Console.Error("GS: Savestate version is incompatible. Load aborted.");
|
||||
return -1;
|
||||
@@ -2733,7 +2517,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();
|
||||
|
||||
@@ -2748,6 +2532,8 @@ int GSState::Defrost(const freezeData* fd)
|
||||
|
||||
g_perfmon.SetFrame(5000);
|
||||
|
||||
ResetPCRTC();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -2802,7 +2588,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 +2596,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 +2635,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 +2654,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 +2692,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 +2708,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 +2746,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 +2786,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 +2794,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 +2807,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 +2887,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)
|
||||
{
|
||||
@@ -3135,7 +2923,7 @@ __forceinline void GSState::HandleAutoFlush()
|
||||
|
||||
const GSVector2i offset = GSVector2i(m_context->XYOFFSET.OFX, m_context->XYOFFSET.OFY);
|
||||
const GSVector4i scissor = GSVector4i(m_context->scissor.in);
|
||||
GSVector4i old_tex_rect = GSVector4i(0, 0, 0, 0);
|
||||
GSVector4i old_tex_rect = GSVector4i::zero();
|
||||
int current_draw_end = m_index.tail;
|
||||
|
||||
while (current_draw_end >= n)
|
||||
@@ -3203,40 +2991,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 +3046,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 +3228,54 @@ __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();
|
||||
CLUTAutoFlush(prim);
|
||||
}
|
||||
|
||||
/// Checks if region repeat is used (applying it does something to at least one of the values in min...max)
|
||||
@@ -3539,8 +3343,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;
|
||||
|
||||
@@ -4050,9 +3857,6 @@ GIFRegTEX0 GSState::GetTex0Layer(u32 lod)
|
||||
|
||||
GSState::GSTransferBuffer::GSTransferBuffer()
|
||||
{
|
||||
x = y = 0;
|
||||
start = end = total = 0;
|
||||
|
||||
constexpr size_t alloc_size = 1024 * 1024 * 4;
|
||||
buff = reinterpret_cast<u8*>(_aligned_malloc(alloc_size, 32));
|
||||
}
|
||||
|
||||
+623
-82
@@ -35,13 +35,15 @@ public:
|
||||
GSState();
|
||||
virtual ~GSState();
|
||||
|
||||
static constexpr int GetSaveStateSize();
|
||||
|
||||
private:
|
||||
// RESTRICT prevents multiple loads of the same part of the register when accessing its bitfields (the compiler is happy to know that memory writes in-between will not go there)
|
||||
|
||||
typedef void (GSState::*GIFPackedRegHandler)(const GIFPackedReg* RESTRICT r);
|
||||
|
||||
GIFPackedRegHandler m_fpGIFPackedRegHandlers[16];
|
||||
GIFPackedRegHandler m_fpGIFPackedRegHandlerXYZ[8][4];
|
||||
GIFPackedRegHandler m_fpGIFPackedRegHandlers[16] = {};
|
||||
GIFPackedRegHandler m_fpGIFPackedRegHandlerXYZ[8][4] = {};
|
||||
|
||||
void CheckFlushes();
|
||||
|
||||
@@ -58,14 +60,14 @@ private:
|
||||
|
||||
typedef void (GSState::*GIFRegHandler)(const GIFReg* RESTRICT r);
|
||||
|
||||
GIFRegHandler m_fpGIFRegHandlers[256];
|
||||
GIFRegHandler m_fpGIFRegHandlerXYZ[8][4];
|
||||
GIFRegHandler m_fpGIFRegHandlers[256] = {};
|
||||
GIFRegHandler m_fpGIFRegHandlerXYZ[8][4] = {};
|
||||
|
||||
typedef void (GSState::*GIFPackedRegHandlerC)(const GIFPackedReg* RESTRICT r, u32 size);
|
||||
|
||||
GIFPackedRegHandlerC m_fpGIFPackedRegHandlersC[2];
|
||||
GIFPackedRegHandlerC m_fpGIFPackedRegHandlerSTQRGBAXYZF2[8];
|
||||
GIFPackedRegHandlerC m_fpGIFPackedRegHandlerSTQRGBAXYZ2[8];
|
||||
GIFPackedRegHandlerC m_fpGIFPackedRegHandlersC[2] = {};
|
||||
GIFPackedRegHandlerC m_fpGIFPackedRegHandlerSTQRGBAXYZF2[8] = {};
|
||||
GIFPackedRegHandlerC m_fpGIFPackedRegHandlerSTQRGBAXYZ2[8] = {};
|
||||
|
||||
template<u32 prim, bool auto_flush, bool index_swap> void GIFPackedRegHandlerSTQRGBAXYZF2(const GIFPackedReg* RESTRICT r, u32 size);
|
||||
template<u32 prim, bool auto_flush, bool index_swap> void GIFPackedRegHandlerSTQRGBAXYZ2(const GIFPackedReg* RESTRICT r, u32 size);
|
||||
@@ -117,15 +119,12 @@ private:
|
||||
template<bool auto_flush, bool index_swap>
|
||||
void SetPrimHandlers();
|
||||
|
||||
u32 m_version;
|
||||
int m_sssize;
|
||||
|
||||
struct GSTransferBuffer
|
||||
{
|
||||
int x, y;
|
||||
int start, end, total;
|
||||
u8* buff;
|
||||
GIFRegBITBLTBUF m_blit;
|
||||
int x = 0, y = 0;
|
||||
int start = 0, end = 0, total = 0;
|
||||
u8* buff = nullptr;
|
||||
GIFRegBITBLTBUF m_blit = {};
|
||||
|
||||
GSTransferBuffer();
|
||||
virtual ~GSTransferBuffer();
|
||||
@@ -139,28 +138,28 @@ private:
|
||||
void CalcAlphaMinMax();
|
||||
|
||||
protected:
|
||||
GSVertex m_v;
|
||||
float m_q;
|
||||
GSVector4i m_scissor;
|
||||
GSVector4i m_ofxy;
|
||||
GSVertex m_v = {};
|
||||
float m_q = 1.0f;
|
||||
GSVector4i m_scissor = {};
|
||||
GSVector4i m_ofxy = {};
|
||||
|
||||
u8 m_scanmask_used;
|
||||
bool tex_flushed;
|
||||
bool m_isPackedUV_HackFlag;
|
||||
u8 m_scanmask_used = 0;
|
||||
bool tex_flushed = true;
|
||||
bool m_isPackedUV_HackFlag = false;
|
||||
|
||||
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;
|
||||
} m_vertex = {};
|
||||
|
||||
struct
|
||||
{
|
||||
u32* buff;
|
||||
size_t tail;
|
||||
} m_index;
|
||||
u32 tail;
|
||||
} m_index = {};
|
||||
|
||||
void UpdateContext();
|
||||
void UpdateScissor();
|
||||
@@ -169,8 +168,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,24 +206,34 @@ protected:
|
||||
bool IsCoverageAlpha();
|
||||
|
||||
public:
|
||||
GIFPath m_path[4];
|
||||
GIFRegPRIM* PRIM;
|
||||
GSPrivRegSet* m_regs;
|
||||
struct GSUploadQueue
|
||||
{
|
||||
GIFRegBITBLTBUF blit;
|
||||
GSVector4i rect;
|
||||
int draw;
|
||||
};
|
||||
|
||||
GIFPath m_path[4] = {};
|
||||
GIFRegPRIM* PRIM = nullptr;
|
||||
GSPrivRegSet* m_regs = nullptr;
|
||||
GSLocalMemory m_mem;
|
||||
GSDrawingEnvironment m_env;
|
||||
GSDrawingEnvironment m_backup_env;
|
||||
GSDrawingEnvironment m_prev_env;
|
||||
GSVector4i temp_draw_rect;
|
||||
GSDrawingContext* m_context;
|
||||
u32 m_crc;
|
||||
CRC::Game m_game;
|
||||
GSDrawingEnvironment m_env = {};
|
||||
GSDrawingEnvironment m_backup_env = {};
|
||||
GSDrawingEnvironment m_prev_env = {};
|
||||
GSVector4i temp_draw_rect = {};
|
||||
GSDrawingContext* m_context = nullptr;
|
||||
u32 m_crc = 0;
|
||||
CRC::Game m_game = {};
|
||||
std::unique_ptr<GSDumpBase> m_dump;
|
||||
bool m_nativeres;
|
||||
bool m_mipmap;
|
||||
u32 m_dirty_gs_regs;
|
||||
int m_backed_up_ctx;
|
||||
bool m_nativeres = false;
|
||||
bool m_mipmap = false;
|
||||
u8 m_force_preload = 0;
|
||||
u32 m_dirty_gs_regs = 0;
|
||||
int m_backed_up_ctx = 0;
|
||||
std::vector<GSUploadQueue> m_draw_transfers;
|
||||
|
||||
static int s_n;
|
||||
static int s_transfer_n;
|
||||
|
||||
static constexpr u32 STATE_VERSION = 8;
|
||||
|
||||
@@ -269,7 +279,7 @@ public:
|
||||
GSREOPEN = 1 << 13,
|
||||
};
|
||||
|
||||
GSFlushReason m_state_flush_reason;
|
||||
GSFlushReason m_state_flush_reason = UNKNOWN;
|
||||
|
||||
enum PRIM_OVERLAP
|
||||
{
|
||||
@@ -278,61 +288,592 @@ public:
|
||||
PRIM_OVERLAP_NO
|
||||
};
|
||||
|
||||
PRIM_OVERLAP m_prim_overlap;
|
||||
PRIM_OVERLAP m_prim_overlap = PRIM_OVERLAP_UNKNOW;
|
||||
std::vector<size_t> m_drawlist;
|
||||
|
||||
// The horizontal offset values (under z) for PAL and NTSC have been tweaked
|
||||
// they should be apparently 632 and 652 respectively, but that causes a thick black line on the left
|
||||
// these values leave a small black line on the right in a bunch of games, but it's not so bad.
|
||||
// The only conclusion I can come to is there is horizontal overscan expected so there would normally
|
||||
// be black borders either side anyway, or both sides slightly covered.
|
||||
const GSVector4i VideoModeOffsets[6] = {
|
||||
GSVector4i(640, 224, 642, 25),
|
||||
GSVector4i(640, 256, 676, 36),
|
||||
GSVector4i(640, 480, 276, 34),
|
||||
GSVector4i(720, 480, 232, 35),
|
||||
GSVector4i(1280, 720, 302, 24),
|
||||
GSVector4i(1920, 540, 238, 40)
|
||||
};
|
||||
struct GSPCRTCRegs
|
||||
{
|
||||
// The horizontal offset values (under z) for PAL and NTSC have been tweaked
|
||||
// they should be apparently 632 and 652 respectively, but that causes a thick black line on the left
|
||||
// these values leave a small black line on the right in a bunch of games, but it's not so bad.
|
||||
// The only conclusion I can come to is there is horizontal overscan expected so there would normally
|
||||
// be black borders either side anyway, or both sides slightly covered.
|
||||
static inline constexpr GSVector4i VideoModeOffsets[6] = {
|
||||
GSVector4i::cxpr(640, 224, 642, 25),
|
||||
GSVector4i::cxpr(640, 256, 676, 36),
|
||||
GSVector4i::cxpr(640, 480, 276, 34),
|
||||
GSVector4i::cxpr(720, 480, 232, 35),
|
||||
GSVector4i::cxpr(1280, 720, 302, 24),
|
||||
GSVector4i::cxpr(1920, 540, 238, 40)
|
||||
};
|
||||
|
||||
const GSVector4i VideoModeOffsetsOverscan[6] = {
|
||||
GSVector4i(711, 243, 498, 12),
|
||||
GSVector4i(702, 288, 532, 18),
|
||||
GSVector4i(640, 480, 276, 34),
|
||||
GSVector4i(720, 480, 232, 35),
|
||||
GSVector4i(1280, 720, 302, 24),
|
||||
GSVector4i(1920, 540, 238, 40)
|
||||
};
|
||||
static inline constexpr GSVector4i VideoModeOffsetsOverscan[6] = {
|
||||
GSVector4i::cxpr(711, 243, 498, 12),
|
||||
GSVector4i::cxpr(702, 288, 532, 18),
|
||||
GSVector4i::cxpr(640, 480, 276, 34),
|
||||
GSVector4i::cxpr(720, 480, 232, 35),
|
||||
GSVector4i::cxpr(1280, 720, 302, 24),
|
||||
GSVector4i::cxpr(1920, 540, 238, 40)
|
||||
};
|
||||
|
||||
const GSVector4i VideoModeDividers[6] = {
|
||||
GSVector4i(3, 0, 2559, 239),
|
||||
GSVector4i(3, 0, 2559, 287),
|
||||
GSVector4i(1, 0, 1279, 479),
|
||||
GSVector4i(1, 0, 1439, 479),
|
||||
GSVector4i(0, 0, 1279, 719),
|
||||
GSVector4i(0, 0, 1919, 1079)
|
||||
};
|
||||
static inline constexpr GSVector4i VideoModeDividers[6] = {
|
||||
GSVector4i::cxpr(3, 0, 2559, 239),
|
||||
GSVector4i::cxpr(3, 0, 2559, 287),
|
||||
GSVector4i::cxpr(1, 0, 1279, 479),
|
||||
GSVector4i::cxpr(1, 0, 1439, 479),
|
||||
GSVector4i::cxpr(0, 0, 1279, 719),
|
||||
GSVector4i::cxpr(0, 0, 1919, 1079)
|
||||
};
|
||||
|
||||
struct PCRTCDisplay
|
||||
{
|
||||
bool enabled;
|
||||
int FBP;
|
||||
int FBW;
|
||||
int PSM;
|
||||
GSRegDISPFB prevFramebufferReg;
|
||||
GSVector2i prevDisplayOffset;
|
||||
GSVector2i displayOffset;
|
||||
GSVector4i displayRect;
|
||||
GSVector2i magnification;
|
||||
GSVector2i prevFramebufferOffsets;
|
||||
GSVector2i framebufferOffsets;
|
||||
GSVector4i framebufferRect;
|
||||
|
||||
int Block()
|
||||
{
|
||||
return FBP << 5;
|
||||
}
|
||||
};
|
||||
|
||||
int videomode = 0;
|
||||
int interlaced = 0;
|
||||
int FFMD = 0;
|
||||
bool PCRTCSameSrc = false;
|
||||
bool toggling_field = false;
|
||||
PCRTCDisplay PCRTCDisplays[2] = {};
|
||||
|
||||
bool IsAnalogue()
|
||||
{
|
||||
const GSVideoMode video = static_cast<GSVideoMode>(videomode + 1);
|
||||
return video == GSVideoMode::NTSC || video == GSVideoMode::PAL || video == GSVideoMode::HDTV_1080I;
|
||||
}
|
||||
|
||||
// Calculates which display is closest to matching zero offsets in either direction.
|
||||
GSVector2i NearestToZeroOffset()
|
||||
{
|
||||
GSVector2i returnValue = { 1, 1 };
|
||||
|
||||
if (!PCRTCDisplays[0].enabled && !PCRTCDisplays[1].enabled)
|
||||
return returnValue;
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (!PCRTCDisplays[i].enabled)
|
||||
{
|
||||
returnValue.x = 1 - i;
|
||||
returnValue.y = 1 - i;
|
||||
return returnValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (abs(PCRTCDisplays[0].displayOffset.x - VideoModeOffsets[videomode].z) <
|
||||
abs(PCRTCDisplays[1].displayOffset.x - VideoModeOffsets[videomode].z))
|
||||
returnValue.x = 0;
|
||||
|
||||
// When interlaced, the vertical base offset is doubled
|
||||
const int verticalOffset = VideoModeOffsets[videomode].w * (1 << interlaced);
|
||||
|
||||
if (abs(PCRTCDisplays[0].displayOffset.y - verticalOffset) <
|
||||
abs(PCRTCDisplays[1].displayOffset.y - verticalOffset))
|
||||
returnValue.y = 0;
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
void SetVideoMode(GSVideoMode videoModeIn)
|
||||
{
|
||||
videomode = static_cast<int>(videoModeIn) - 1;
|
||||
}
|
||||
|
||||
// Enable each of the displays.
|
||||
void EnableDisplays(GSRegPMODE pmode, GSRegSMODE2 smode2, bool smodetoggle)
|
||||
{
|
||||
PCRTCDisplays[0].enabled = pmode.EN1;
|
||||
PCRTCDisplays[1].enabled = pmode.EN2;
|
||||
|
||||
interlaced = smode2.INT && IsAnalogue();
|
||||
FFMD = smode2.FFMD;
|
||||
toggling_field = smodetoggle && IsAnalogue();
|
||||
}
|
||||
|
||||
void CheckSameSource()
|
||||
{
|
||||
if (PCRTCDisplays[0].enabled != PCRTCDisplays[1].enabled || (PCRTCDisplays[0].enabled | PCRTCDisplays[1].enabled) == false)
|
||||
{
|
||||
PCRTCSameSrc = false;
|
||||
return;
|
||||
}
|
||||
|
||||
PCRTCSameSrc = PCRTCDisplays[0].FBP == PCRTCDisplays[1].FBP &&
|
||||
PCRTCDisplays[0].FBW == PCRTCDisplays[1].FBW &&
|
||||
GSUtil::HasCompatibleBits(PCRTCDisplays[0].PSM, PCRTCDisplays[1].PSM);
|
||||
}
|
||||
|
||||
bool FrameWrap()
|
||||
{
|
||||
const GSVector4i combined_rect = GSVector4i(PCRTCDisplays[0].framebufferRect.runion(PCRTCDisplays[1].framebufferRect));
|
||||
return combined_rect.w >= 2048 || combined_rect.z >= 2048;
|
||||
}
|
||||
|
||||
// If the start point of both frames match, we can do a single read
|
||||
bool FrameRectMatch()
|
||||
{
|
||||
return PCRTCSameSrc;
|
||||
}
|
||||
|
||||
GSVector2i GetResolution()
|
||||
{
|
||||
GSVector2i resolution;
|
||||
|
||||
const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode];
|
||||
const bool is_full_height = interlaced || (toggling_field && GSConfig.InterlaceMode != GSInterlaceMode::Off) || GSConfig.InterlaceMode == GSInterlaceMode::Off;
|
||||
|
||||
if (!GSConfig.PCRTCOffsets)
|
||||
{
|
||||
if (PCRTCDisplays[0].enabled && PCRTCDisplays[1].enabled)
|
||||
{
|
||||
const GSVector4i combined_size = PCRTCDisplays[0].displayRect.runion(PCRTCDisplays[1].displayRect);
|
||||
resolution = { combined_size.width(), combined_size.height() };
|
||||
}
|
||||
else if (PCRTCDisplays[0].enabled)
|
||||
{
|
||||
resolution = { PCRTCDisplays[0].displayRect.width(), PCRTCDisplays[0].displayRect.height() };
|
||||
}
|
||||
else
|
||||
{
|
||||
resolution = { PCRTCDisplays[1].displayRect.width(), PCRTCDisplays[1].displayRect.height() };
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const int shift = is_full_height ? 1 : 0;
|
||||
resolution = { offsets.x, offsets.y << shift };
|
||||
}
|
||||
|
||||
resolution.x = std::min(resolution.x, offsets.x);
|
||||
resolution.y = std::min(resolution.y, is_full_height ? offsets.y << 1 : offsets.y);
|
||||
|
||||
return resolution;
|
||||
}
|
||||
|
||||
GSVector4i GetFramebufferRect(int display)
|
||||
{
|
||||
if (display == -1)
|
||||
{
|
||||
return GSVector4i(PCRTCDisplays[0].framebufferRect.runion(PCRTCDisplays[1].framebufferRect));
|
||||
}
|
||||
else
|
||||
{
|
||||
return PCRTCDisplays[display].framebufferRect;
|
||||
}
|
||||
}
|
||||
|
||||
int GetFramebufferBitDepth()
|
||||
{
|
||||
if (PCRTCDisplays[0].enabled)
|
||||
return GSLocalMemory::m_psm[PCRTCDisplays[0].PSM].bpp;
|
||||
else if (PCRTCDisplays[1].enabled)
|
||||
return GSLocalMemory::m_psm[PCRTCDisplays[1].PSM].bpp;
|
||||
|
||||
return 32;
|
||||
}
|
||||
|
||||
GSVector2i GetFramebufferSize(int display)
|
||||
{
|
||||
int max_height = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode].y : VideoModeOffsetsOverscan[videomode].y;
|
||||
|
||||
if (!(FFMD && interlaced))
|
||||
{
|
||||
max_height *= 2;
|
||||
}
|
||||
|
||||
if (display == -1)
|
||||
{
|
||||
GSVector4i combined_rect = PCRTCDisplays[0].framebufferRect.runion(PCRTCDisplays[1].framebufferRect);
|
||||
|
||||
if (combined_rect.z >= 2048)
|
||||
{
|
||||
const int high_x = (PCRTCDisplays[0].framebufferRect.x > PCRTCDisplays[1].framebufferRect.x) ? PCRTCDisplays[0].framebufferRect.x : PCRTCDisplays[1].framebufferRect.x;
|
||||
combined_rect.z -= GSConfig.UseHardwareRenderer() ? 2048 : high_x;
|
||||
combined_rect.x = 0;
|
||||
}
|
||||
|
||||
if (combined_rect.w >= 2048)
|
||||
{
|
||||
const int high_y = (PCRTCDisplays[0].framebufferRect.y > PCRTCDisplays[1].framebufferRect.y) ? PCRTCDisplays[0].framebufferRect.y : PCRTCDisplays[1].framebufferRect.y;
|
||||
combined_rect.w -= GSConfig.UseHardwareRenderer() ? 2048 : high_y;
|
||||
combined_rect.y = 0;
|
||||
}
|
||||
|
||||
// Cap the framebuffer read to the maximum display height, otherwise the hardware renderer gets messy.
|
||||
const int min_mag = std::max(1, std::min(PCRTCDisplays[0].magnification.y, PCRTCDisplays[1].magnification.y));
|
||||
int offset = PCRTCDisplays[0].displayRect.runion(PCRTCDisplays[1].displayRect).y;
|
||||
|
||||
if (FFMD && interlaced)
|
||||
{
|
||||
offset = (offset - 1) / 2;
|
||||
}
|
||||
|
||||
// Hardware mode needs a wider framebuffer as it can't offset the read.
|
||||
if (GSConfig.UseHardwareRenderer())
|
||||
{
|
||||
combined_rect.z += std::max(PCRTCDisplays[0].framebufferOffsets.x, PCRTCDisplays[1].framebufferOffsets.x);
|
||||
combined_rect.w += std::max(PCRTCDisplays[0].framebufferOffsets.y, PCRTCDisplays[1].framebufferOffsets.y);
|
||||
}
|
||||
offset = (max_height / min_mag) - offset;
|
||||
combined_rect.w = std::min(combined_rect.w, offset);
|
||||
return GSVector2i(combined_rect.z, combined_rect.w);
|
||||
}
|
||||
else
|
||||
{
|
||||
GSVector4i out_rect = PCRTCDisplays[display].framebufferRect;
|
||||
|
||||
if (out_rect.z >= 2048)
|
||||
{
|
||||
out_rect.z -= GSConfig.UseHardwareRenderer() ? 2048 : out_rect.x;
|
||||
out_rect.x = 0;
|
||||
}
|
||||
|
||||
if (out_rect.w >= 2048)
|
||||
{
|
||||
out_rect.w -= GSConfig.UseHardwareRenderer() ? 2048 : out_rect.y;
|
||||
out_rect.y = 0;
|
||||
}
|
||||
|
||||
// Cap the framebuffer read to the maximum display height, otherwise the hardware renderer gets messy.
|
||||
const int min_mag = std::max(1, PCRTCDisplays[display].magnification.y);
|
||||
int offset = PCRTCDisplays[display].displayRect.y;
|
||||
|
||||
if (FFMD && interlaced)
|
||||
{
|
||||
offset = (offset - 1) / 2;
|
||||
}
|
||||
|
||||
offset = (max_height / min_mag) - offset;
|
||||
out_rect.w = std::min(out_rect.w, offset);
|
||||
|
||||
// Hardware mode needs a wider framebuffer as it can't offset the read.
|
||||
if (GSConfig.UseHardwareRenderer())
|
||||
{
|
||||
out_rect.z += PCRTCDisplays[display].framebufferOffsets.x;
|
||||
out_rect.w += PCRTCDisplays[display].framebufferOffsets.y;
|
||||
}
|
||||
return GSVector2i(out_rect.z, out_rect.w);
|
||||
}
|
||||
}
|
||||
|
||||
// Sets up the rectangles for both the framebuffer read and the displays for the merge circuit.
|
||||
void SetRects(int display, GSRegDISPLAY displayReg, GSRegDISPFB framebufferReg)
|
||||
{
|
||||
// Save framebuffer information first, while we're here.
|
||||
PCRTCDisplays[display].FBP = framebufferReg.FBP;
|
||||
PCRTCDisplays[display].FBW = framebufferReg.FBW;
|
||||
PCRTCDisplays[display].PSM = framebufferReg.PSM;
|
||||
PCRTCDisplays[display].prevFramebufferReg = framebufferReg;
|
||||
// Probably not really enabled but will cause a mess.
|
||||
// Q-Ball Billiards enables both circuits but doesn't set one of them up.
|
||||
if (PCRTCDisplays[display].FBW == 0 && displayReg.DW == 0 && displayReg.DH == 0 && displayReg.MAGH == 0)
|
||||
{
|
||||
PCRTCDisplays[display].enabled = false;
|
||||
return;
|
||||
}
|
||||
PCRTCDisplays[display].magnification = GSVector2i(displayReg.MAGH + 1, displayReg.MAGV + 1);
|
||||
const u32 DW = displayReg.DW + 1;
|
||||
const u32 DH = displayReg.DH + 1;
|
||||
|
||||
const int renderWidth = DW / PCRTCDisplays[display].magnification.x;
|
||||
const int renderHeight = DH / PCRTCDisplays[display].magnification.y;
|
||||
|
||||
u32 finalDisplayWidth = renderWidth;
|
||||
u32 finalDisplayHeight = renderHeight;
|
||||
// When using screen offsets the screen gets squashed/resized in to the actual screen size.
|
||||
if (GSConfig.PCRTCOffsets)
|
||||
{
|
||||
finalDisplayWidth = DW / (VideoModeDividers[videomode].x + 1);
|
||||
finalDisplayHeight = DH / (VideoModeDividers[videomode].y + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
finalDisplayWidth = std::min(finalDisplayWidth ,DW / (VideoModeDividers[videomode].x + 1));
|
||||
finalDisplayHeight = std::min(finalDisplayHeight, DH / (VideoModeDividers[videomode].y + 1));
|
||||
}
|
||||
|
||||
// Framebuffer size and offsets.
|
||||
PCRTCDisplays[display].prevFramebufferOffsets = PCRTCDisplays[display].framebufferOffsets;
|
||||
PCRTCDisplays[display].framebufferRect.x = 0;
|
||||
PCRTCDisplays[display].framebufferRect.y = 0;
|
||||
PCRTCDisplays[display].framebufferRect.z = renderWidth;
|
||||
|
||||
if(FFMD && interlaced) // Round up the height as if it's an odd value, this will cause havok with the merge circuit.
|
||||
PCRTCDisplays[display].framebufferRect.w = (renderHeight + 1) >> (FFMD * interlaced); // Half height read if FFMD + INT enabled.
|
||||
else
|
||||
PCRTCDisplays[display].framebufferRect.w = renderHeight;
|
||||
PCRTCDisplays[display].framebufferOffsets.x = framebufferReg.DBX;
|
||||
PCRTCDisplays[display].framebufferOffsets.y = framebufferReg.DBY;
|
||||
|
||||
const bool is_interlaced_resolution = interlaced || (toggling_field && GSConfig.InterlaceMode != GSInterlaceMode::Off);
|
||||
|
||||
// If the interlace flag isn't set, but it's still interlacing, the height is likely reported wrong.
|
||||
// Q-Ball Billiards.
|
||||
if (is_interlaced_resolution && !interlaced)
|
||||
finalDisplayHeight *= 2;
|
||||
|
||||
// Display size and offsets.
|
||||
PCRTCDisplays[display].displayRect.x = 0;
|
||||
PCRTCDisplays[display].displayRect.y = 0;
|
||||
PCRTCDisplays[display].displayRect.z = finalDisplayWidth;
|
||||
PCRTCDisplays[display].displayRect.w = finalDisplayHeight;
|
||||
PCRTCDisplays[display].prevDisplayOffset = PCRTCDisplays[display].displayOffset;
|
||||
PCRTCDisplays[display].displayOffset.x = displayReg.DX;
|
||||
PCRTCDisplays[display].displayOffset.y = displayReg.DY;
|
||||
}
|
||||
|
||||
// Calculate framebuffer read offsets, should be considered if only one circuit is enabled, or difference is more than 1 line.
|
||||
// Only considered if "Anti-blur" is enabled.
|
||||
void CalculateFramebufferOffset()
|
||||
{
|
||||
if (GSConfig.PCRTCAntiBlur && PCRTCSameSrc)
|
||||
{
|
||||
if (abs(PCRTCDisplays[1].framebufferOffsets.y - PCRTCDisplays[0].framebufferOffsets.y) == 1
|
||||
&& PCRTCDisplays[0].displayRect.y == PCRTCDisplays[1].displayRect.y)
|
||||
{
|
||||
if (PCRTCDisplays[1].framebufferOffsets.y < PCRTCDisplays[0].framebufferOffsets.y)
|
||||
PCRTCDisplays[0].framebufferOffsets.y = PCRTCDisplays[1].framebufferOffsets.y;
|
||||
else
|
||||
PCRTCDisplays[1].framebufferOffsets.y = PCRTCDisplays[0].framebufferOffsets.y;
|
||||
}
|
||||
if (abs(PCRTCDisplays[1].framebufferOffsets.x - PCRTCDisplays[0].framebufferOffsets.x) == 1
|
||||
&& PCRTCDisplays[0].displayRect.x == PCRTCDisplays[1].displayRect.x)
|
||||
{
|
||||
if (PCRTCDisplays[1].framebufferOffsets.x < PCRTCDisplays[0].framebufferOffsets.x)
|
||||
PCRTCDisplays[0].framebufferOffsets.x = PCRTCDisplays[1].framebufferOffsets.x;
|
||||
else
|
||||
PCRTCDisplays[1].framebufferOffsets.x = PCRTCDisplays[0].framebufferOffsets.x;
|
||||
}
|
||||
}
|
||||
PCRTCDisplays[0].framebufferRect.x += PCRTCDisplays[0].framebufferOffsets.x;
|
||||
PCRTCDisplays[0].framebufferRect.z += PCRTCDisplays[0].framebufferOffsets.x;
|
||||
PCRTCDisplays[0].framebufferRect.y += PCRTCDisplays[0].framebufferOffsets.y;
|
||||
PCRTCDisplays[0].framebufferRect.w += PCRTCDisplays[0].framebufferOffsets.y;
|
||||
|
||||
PCRTCDisplays[1].framebufferRect.x += PCRTCDisplays[1].framebufferOffsets.x;
|
||||
PCRTCDisplays[1].framebufferRect.z += PCRTCDisplays[1].framebufferOffsets.x;
|
||||
PCRTCDisplays[1].framebufferRect.y += PCRTCDisplays[1].framebufferOffsets.y;
|
||||
PCRTCDisplays[1].framebufferRect.w += PCRTCDisplays[1].framebufferOffsets.y;
|
||||
}
|
||||
|
||||
// Used in software mode to align the buffer when reading. Offset is accounted for (block aligned) by GetOutput.
|
||||
void RemoveFramebufferOffset(int display)
|
||||
{
|
||||
if (display >= 0)
|
||||
{
|
||||
// Hardware needs nothing but handling for wrapped framebuffers.
|
||||
if (GSConfig.UseHardwareRenderer())
|
||||
{
|
||||
if (PCRTCDisplays[display].framebufferRect.z >= 2048)
|
||||
{
|
||||
PCRTCDisplays[display].framebufferRect.x = 0;
|
||||
PCRTCDisplays[display].framebufferRect.z -= 2048;
|
||||
}
|
||||
if (PCRTCDisplays[display].framebufferRect.w >= 2048)
|
||||
{
|
||||
PCRTCDisplays[display].framebufferRect.y = 0;
|
||||
PCRTCDisplays[display].framebufferRect.w -= 2048;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[PCRTCDisplays[display].PSM];
|
||||
|
||||
// Software mode - See note below.
|
||||
GSVector4i r = PCRTCDisplays[display].framebufferRect;
|
||||
r = r.ralign<Align_Outside>(psm.bs);
|
||||
|
||||
PCRTCDisplays[display].framebufferRect.z -= r.x;
|
||||
PCRTCDisplays[display].framebufferRect.w -= r.y;
|
||||
PCRTCDisplays[display].framebufferRect.x -= r.x;
|
||||
PCRTCDisplays[display].framebufferRect.y -= r.y;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Software Mode Note:
|
||||
// This code is to read the framebuffer nicely block aligned in software, then leave the remaining offset in to the block.
|
||||
// In hardware mode this doesn't happen, it reads the whole framebuffer, so we need to keep the offset.
|
||||
if (!GSConfig.UseHardwareRenderer())
|
||||
{
|
||||
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[PCRTCDisplays[1].PSM];
|
||||
|
||||
GSVector4i r = PCRTCDisplays[0].framebufferRect.runion(PCRTCDisplays[1].framebufferRect);
|
||||
r = r.ralign<Align_Outside>(psm.bs);
|
||||
|
||||
PCRTCDisplays[0].framebufferRect.x -= r.x;
|
||||
PCRTCDisplays[0].framebufferRect.y -= r.y;
|
||||
PCRTCDisplays[0].framebufferRect.z -= r.x;
|
||||
PCRTCDisplays[0].framebufferRect.w -= r.y;
|
||||
PCRTCDisplays[1].framebufferRect.x -= r.x;
|
||||
PCRTCDisplays[1].framebufferRect.y -= r.y;
|
||||
PCRTCDisplays[1].framebufferRect.z -= r.x;
|
||||
PCRTCDisplays[1].framebufferRect.w -= r.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the two displays are offset from each other, move them to the correct offsets.
|
||||
// If using screen offsets, calculate the positions here.
|
||||
void CalculateDisplayOffset(bool scanmask)
|
||||
{
|
||||
const bool both_enabled = PCRTCDisplays[0].enabled && PCRTCDisplays[1].enabled;
|
||||
// Offsets are generally ignored, the "hacky" way of doing the displays, but direct to framebuffers.
|
||||
if (!GSConfig.PCRTCOffsets)
|
||||
{
|
||||
const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode];
|
||||
int int_off[2] = { 0, 0 };
|
||||
GSVector2i zeroDisplay = NearestToZeroOffset();
|
||||
GSVector2i baseOffset = PCRTCDisplays[zeroDisplay.y].displayOffset;
|
||||
|
||||
if (both_enabled)
|
||||
{
|
||||
int blurOffset = abs(PCRTCDisplays[1].displayOffset.y - PCRTCDisplays[0].displayOffset.y);
|
||||
if (GSConfig.PCRTCAntiBlur && !scanmask && blurOffset < 4)
|
||||
{
|
||||
if (PCRTCDisplays[1].displayOffset.y > PCRTCDisplays[0].displayOffset.y)
|
||||
PCRTCDisplays[1].displayOffset.y -= blurOffset;
|
||||
else
|
||||
PCRTCDisplays[0].displayOffset.y -= blurOffset;
|
||||
}
|
||||
}
|
||||
|
||||
// If there's a single pixel offset, account for it else it can throw interlacing out.
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (!PCRTCDisplays[i].enabled)
|
||||
continue;
|
||||
|
||||
// Should this be MAGV/H in the DISPLAY register rather than the "default" magnification?
|
||||
const int offset = (PCRTCDisplays[i].displayOffset.y - (offsets.w * (interlaced + 1))) / (VideoModeDividers[videomode].y + 1);
|
||||
|
||||
if (offset > 4)
|
||||
continue;
|
||||
|
||||
int_off[i] = offset & 1;
|
||||
if (offset < 0)
|
||||
int_off[i] = -int_off[i];
|
||||
|
||||
PCRTCDisplays[i].displayRect.y += int_off[i];
|
||||
PCRTCDisplays[i].displayRect.w += int_off[i];
|
||||
}
|
||||
|
||||
// Handle difference in offset between the two displays, used in games like DmC and Time Crisis 2 (for split screen).
|
||||
// Offset is not screen based, but relative to each other.
|
||||
if (both_enabled)
|
||||
{
|
||||
GSVector2i offset;
|
||||
|
||||
offset.x = (PCRTCDisplays[1 - zeroDisplay.x].displayOffset.x - PCRTCDisplays[zeroDisplay.x].displayOffset.x) / (VideoModeDividers[videomode].x + 1);
|
||||
offset.y = (PCRTCDisplays[1 - zeroDisplay.y].displayOffset.y - PCRTCDisplays[zeroDisplay.y].displayOffset.y) / (VideoModeDividers[videomode].y + 1);
|
||||
|
||||
if (offset.x >= 4 || !GSConfig.PCRTCAntiBlur)
|
||||
{
|
||||
PCRTCDisplays[1 - zeroDisplay.x].displayRect.x += offset.x;
|
||||
PCRTCDisplays[1 - zeroDisplay.x].displayRect.z += offset.x;
|
||||
}
|
||||
if (offset.y >= 4 || !GSConfig.PCRTCAntiBlur)
|
||||
{
|
||||
PCRTCDisplays[1 - zeroDisplay.y].displayRect.y += offset.y - int_off[1 - zeroDisplay.y];
|
||||
PCRTCDisplays[1 - zeroDisplay.y].displayRect.w += offset.y - int_off[1 - zeroDisplay.y];
|
||||
}
|
||||
|
||||
baseOffset = PCRTCDisplays[zeroDisplay.y].displayOffset;
|
||||
}
|
||||
|
||||
// Handle any large vertical offset from the zero position on the screen.
|
||||
// Example: Hokuto no Ken, does a rougly -14 offset to bring the screen up.
|
||||
// Ignore the lowest bit, we've already accounted for this
|
||||
int vOffset = ((static_cast<int>(baseOffset.y) - (offsets.w * (interlaced + 1))) / (VideoModeDividers[videomode].y + 1));
|
||||
|
||||
if(vOffset <= 4 && vOffset != 0)
|
||||
{
|
||||
PCRTCDisplays[0].displayRect.y += vOffset - int_off[0];
|
||||
PCRTCDisplays[0].displayRect.w += vOffset - int_off[0];
|
||||
PCRTCDisplays[1].displayRect.y += vOffset - int_off[1];
|
||||
PCRTCDisplays[1].displayRect.w += vOffset - int_off[1];
|
||||
}
|
||||
}
|
||||
else // We're using screen offsets, so just calculate the entire offset.
|
||||
{
|
||||
const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode];
|
||||
GSVector2i offset = { 0, 0 };
|
||||
GSVector2i zeroDisplay = NearestToZeroOffset();
|
||||
|
||||
if (both_enabled)
|
||||
{
|
||||
int blurOffset = abs(PCRTCDisplays[1].displayOffset.y - PCRTCDisplays[0].displayOffset.y);
|
||||
if (GSConfig.PCRTCAntiBlur && !scanmask && blurOffset < 4)
|
||||
{
|
||||
if (PCRTCDisplays[1].displayOffset.y > PCRTCDisplays[0].displayOffset.y)
|
||||
PCRTCDisplays[1].displayOffset.y -= blurOffset;
|
||||
else
|
||||
PCRTCDisplays[0].displayOffset.y -= blurOffset;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
// Should this be MAGV/H in the DISPLAY register rather than the "default" magnification?
|
||||
offset.x = (static_cast<int>(PCRTCDisplays[i].displayOffset.x) - offsets.z) / (VideoModeDividers[videomode].x + 1);
|
||||
offset.y = (static_cast<int>(PCRTCDisplays[i].displayOffset.y) - (offsets.w * (interlaced + 1))) / (VideoModeDividers[videomode].y + 1);
|
||||
|
||||
PCRTCDisplays[i].displayRect.x += offset.x;
|
||||
PCRTCDisplays[i].displayRect.z += offset.x;
|
||||
PCRTCDisplays[i].displayRect.y += offset.y;
|
||||
PCRTCDisplays[i].displayRect.w += offset.y;
|
||||
}
|
||||
|
||||
if (both_enabled)
|
||||
{
|
||||
GSVector2i offset;
|
||||
|
||||
offset.x = (PCRTCDisplays[1 - zeroDisplay.x].displayRect.x - PCRTCDisplays[zeroDisplay.x].displayRect.x);
|
||||
offset.y = (PCRTCDisplays[1 - zeroDisplay.y].displayRect.y - PCRTCDisplays[zeroDisplay.y].displayRect.y);
|
||||
|
||||
if (offset.x > 0 && offset.x < 4 && GSConfig.PCRTCAntiBlur)
|
||||
{
|
||||
PCRTCDisplays[1 - zeroDisplay.x].displayRect.x -= offset.x;
|
||||
PCRTCDisplays[1 - zeroDisplay.x].displayRect.z -= offset.x;
|
||||
}
|
||||
if (offset.y > 0 && offset.y < 4 && GSConfig.PCRTCAntiBlur)
|
||||
{
|
||||
PCRTCDisplays[1 - zeroDisplay.y].displayRect.y -= offset.y;
|
||||
PCRTCDisplays[1 - zeroDisplay.y].displayRect.w -= offset.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} PCRTCDisplays;
|
||||
|
||||
public:
|
||||
/// Returns the appropriate directory for draw dumping.
|
||||
static std::string GetDrawDumpPath(const char* format, ...);
|
||||
|
||||
void ResetHandlers();
|
||||
void ResetPCRTC();
|
||||
|
||||
int GetFramebufferHeight();
|
||||
int GetFramebufferWidth();
|
||||
int GetDisplayHMagnification();
|
||||
GSVector4i GetDisplayRect(int i = -1);
|
||||
GSVector4i GetFrameMagnifiedRect(int i = -1);
|
||||
GSVector2i GetResolutionOffset(int i = -1);
|
||||
GSVector2i GetResolution();
|
||||
GSVector4i GetFrameRect(int i = -1, bool ignore_off = false);
|
||||
GSVideoMode GetVideoMode();
|
||||
|
||||
bool IsEnabled(int i);
|
||||
bool isinterlaced();
|
||||
bool isReallyInterlaced();
|
||||
bool IsAnalogue();
|
||||
|
||||
float GetTvRefreshRate();
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -307,7 +299,7 @@ void GSDevice::Merge(GSTexture* sTex[3], GSVector4* sRect, GSVector4* dRect, con
|
||||
}
|
||||
}
|
||||
|
||||
DoMerge(tex, sRect, m_merge, dRect, PMODE, EXTBUF, c);
|
||||
DoMerge(tex, sRect, m_merge, dRect, PMODE, EXTBUF, c, GSConfig.PCRTCOffsets);
|
||||
|
||||
for (size_t i = 0; i < std::size(tex); i++)
|
||||
{
|
||||
@@ -364,6 +356,7 @@ void GSDevice::Interlace(const GSVector2i& ds, int field, int mode, float yoffse
|
||||
break;
|
||||
default:
|
||||
m_current = m_merge;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user