mirror of
https://github.com/PCSX2/pcsx2.git
synced 2026-01-31 01:15:24 +01:00
Compare commits
110 Commits
v1.7.3979
...
gs_wrchack
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2202a4e33 | ||
|
|
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 |
5
.github/labeler.yml
vendored
5
.github/labeler.yml
vendored
@@ -32,6 +32,8 @@
|
||||
- '**/GameIndex.*'
|
||||
'Installer | Package':
|
||||
- 'build.sh'
|
||||
'Translations':
|
||||
- 'pcsx2-qt/Translations/*'
|
||||
|
||||
# Tools / Features
|
||||
'Debugger':
|
||||
@@ -45,6 +47,9 @@
|
||||
'TAS Functionality':
|
||||
- 'pcsx2/Recording/*'
|
||||
- 'pcsx2/Recording/**/*'
|
||||
'RetroAchievements':
|
||||
- 'pcsx2/Frontend/Achievements.*'
|
||||
- 'pcsx2/Achievements.*'
|
||||
|
||||
# Emulation Components
|
||||
'Counters':
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -543,6 +543,7 @@
|
||||
030000009b2800001e00000000000000,Raphnet Vectrex Adapter,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a1,lefty:a2,x:b2,y:b3,platform:Windows,
|
||||
030000009b2800002b00000000000000,Raphnet Wii Classic Adapter,a:b1,b:b4,back:b2,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,guide:b10,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a3,righty:a4,start:b3,x:b0,y:b5,platform:Windows,
|
||||
030000009b2800002c00000000000000,Raphnet Wii Classic Adapter,a:b1,b:b4,back:b2,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,guide:b10,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a3,righty:a4,start:b3,x:b0,y:b5,platform:Windows,
|
||||
030000009b2800008000000000000000,Raphnet Wii Classic Adapter,a:b1,b:b4,x:b0,y:b5,back:b2,guide:b10,start:b3,leftshoulder:b6,rightshoulder:b7,dpup:b12,dpleft:b14,dpdown:b13,dpright:b15,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:b8,righttrigger:b9,platform:Windows,
|
||||
03000000321500000003000000000000,Razer Hydra,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
|
||||
03000000321500000204000000000000,Razer Panthera PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000321500000104000000000000,Razer Panthera PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
#define PS_FST 0
|
||||
#define PS_WMS 0
|
||||
#define PS_WMT 0
|
||||
#define PS_ADJS 0
|
||||
#define PS_ADJT 0
|
||||
#define PS_AEM_FMT FMT_32
|
||||
#define PS_AEM 0
|
||||
#define PS_TFX 0
|
||||
@@ -36,13 +38,13 @@
|
||||
#define PS_POINT_SAMPLER 0
|
||||
#define PS_SHUFFLE 0
|
||||
#define PS_READ_BA 0
|
||||
#define PS_SWAP_GA 0
|
||||
#define PS_DFMT 0
|
||||
#define PS_DEPTH_FMT 0
|
||||
#define PS_PAL_FMT 0
|
||||
#define PS_CHANNEL_FETCH 0
|
||||
#define PS_TALES_OF_ABYSS_HLE 0
|
||||
#define PS_URBAN_CHAOS_HLE 0
|
||||
#define PS_INVALID_TEX0 0
|
||||
#define PS_SCALE_FACTOR 1.0
|
||||
#define PS_HDR 0
|
||||
#define PS_COLCLIP 0
|
||||
@@ -158,10 +160,10 @@ cbuffer cb1
|
||||
float2 TA;
|
||||
float MaxDepthPS;
|
||||
float Af;
|
||||
uint4 MskFix;
|
||||
uint4 FbMask;
|
||||
float4 HalfTexel;
|
||||
float4 MinMax;
|
||||
float4 STRange;
|
||||
int4 ChannelShuffle;
|
||||
float2 TC_OffsetHack;
|
||||
float2 STScale;
|
||||
@@ -183,7 +185,20 @@ float4 sample_c(float2 uv, float uv_w)
|
||||
// As of 2018 this issue is still present.
|
||||
uv = (trunc(uv * WH.zw) + float2(0.5, 0.5)) / WH.zw;
|
||||
}
|
||||
#if !PS_ADJS && !PS_ADJT
|
||||
uv *= STScale;
|
||||
#else
|
||||
#if PS_ADJS
|
||||
uv.x = (uv.x - STRange.x) * STRange.z;
|
||||
#else
|
||||
uv.x = uv.x * STScale.x;
|
||||
#endif
|
||||
#if PS_ADJT
|
||||
uv.y = (uv.y - STRange.y) * STRange.w;
|
||||
#else
|
||||
uv.y = uv.y * STScale.y;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if PS_AUTOMATIC_LOD == 1
|
||||
return Texture.Sample(TextureSampler, uv);
|
||||
@@ -218,12 +233,7 @@ float4 sample_p_norm(float u)
|
||||
|
||||
float4 clamp_wrap_uv(float4 uv)
|
||||
{
|
||||
float4 tex_size;
|
||||
|
||||
if (PS_INVALID_TEX0 == 1)
|
||||
tex_size = WH.zwzw;
|
||||
else
|
||||
tex_size = WH.xyxy;
|
||||
float4 tex_size = WH.xyxy;
|
||||
|
||||
if(PS_WMS == PS_WMT)
|
||||
{
|
||||
@@ -238,7 +248,7 @@ float4 clamp_wrap_uv(float4 uv)
|
||||
// textures. Fixes Xenosaga's hair issue.
|
||||
uv = frac(uv);
|
||||
#endif
|
||||
uv = (float4)(((uint4)(uv * tex_size) & MskFix.xyxy) | MskFix.zwzw) / tex_size;
|
||||
uv = (float4)(((uint4)(uv * tex_size) & asuint(MinMax.xyxy)) | asuint(MinMax.zwzw)) / tex_size;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -252,7 +262,7 @@ float4 clamp_wrap_uv(float4 uv)
|
||||
#if PS_FST == 0
|
||||
uv.xz = frac(uv.xz);
|
||||
#endif
|
||||
uv.xz = (float2)(((uint2)(uv.xz * tex_size.xx) & MskFix.xx) | MskFix.zz) / tex_size.xx;
|
||||
uv.xz = (float2)(((uint2)(uv.xz * tex_size.xx) & asuint(MinMax.xx)) | asuint(MinMax.zz)) / tex_size.xx;
|
||||
}
|
||||
if(PS_WMT == 2)
|
||||
{
|
||||
@@ -263,7 +273,7 @@ float4 clamp_wrap_uv(float4 uv)
|
||||
#if PS_FST == 0
|
||||
uv.yw = frac(uv.yw);
|
||||
#endif
|
||||
uv.yw = (float2)(((uint2)(uv.yw * tex_size.yy) & MskFix.yy) | MskFix.ww) / tex_size.yy;
|
||||
uv.yw = (float2)(((uint2)(uv.yw * tex_size.yy) & asuint(MinMax.yy)) | asuint(MinMax.ww)) / tex_size.yy;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,7 +363,7 @@ float4 fetch_c(int2 uv)
|
||||
|
||||
int2 clamp_wrap_uv_depth(int2 uv)
|
||||
{
|
||||
int4 mask = (int4)MskFix << 4;
|
||||
int4 mask = asint(MinMax) << 4;
|
||||
if (PS_WMS == PS_WMT)
|
||||
{
|
||||
if (PS_WMS == 2)
|
||||
@@ -676,11 +686,7 @@ float4 fog(float4 c, float f)
|
||||
|
||||
float4 ps_color(PS_INPUT input)
|
||||
{
|
||||
#if PS_FST == 0 && PS_INVALID_TEX0 == 1
|
||||
// Re-normalize coordinate from invalid GS to corrected texture size
|
||||
float2 st = (input.t.xy * WH.xy) / (input.t.w * WH.zw);
|
||||
float2 st_int = (input.ti.zw * WH.xy) / (input.t.w * WH.zw);
|
||||
#elif PS_FST == 0
|
||||
#if PS_FST == 0
|
||||
float2 st = input.t.xy / input.t.w;
|
||||
float2 st_int = input.ti.zw / input.t.w;
|
||||
#else
|
||||
@@ -870,28 +876,37 @@ PS_OUTPUT ps_main(PS_INPUT input)
|
||||
|
||||
if (PS_SHUFFLE)
|
||||
{
|
||||
uint4 denorm_c = uint4(C);
|
||||
uint2 denorm_TA = uint2(float2(TA.xy) * 255.0f + 0.5f);
|
||||
|
||||
// Mask will take care of the correct destination
|
||||
if (PS_READ_BA)
|
||||
C.rb = C.bb;
|
||||
else
|
||||
C.rb = C.rr;
|
||||
|
||||
if (PS_READ_BA)
|
||||
if(PS_SWAP_GA)
|
||||
{
|
||||
if (denorm_c.a & 0x80u)
|
||||
C.ga = (float2)(float((denorm_c.a & 0x7Fu) | (denorm_TA.y & 0x80u)));
|
||||
else
|
||||
C.ga = (float2)(float((denorm_c.a & 0x7Fu) | (denorm_TA.x & 0x80u)));
|
||||
float4 RT = trunc(RtTexture.Load(int3(input.p.xy, 0)) * 255.0f + 0.1f);
|
||||
C.a = C.g;
|
||||
C.g = C.a;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (denorm_c.g & 0x80u)
|
||||
C.ga = (float2)(float((denorm_c.g & 0x7Fu) | (denorm_TA.y & 0x80u)));
|
||||
uint4 denorm_c = uint4(C);
|
||||
uint2 denorm_TA = uint2(float2(TA.xy) * 255.0f + 0.5f);
|
||||
|
||||
// Mask will take care of the correct destination
|
||||
if (PS_READ_BA)
|
||||
C.rb = C.bb;
|
||||
else
|
||||
C.ga = (float2)(float((denorm_c.g & 0x7Fu) | (denorm_TA.x & 0x80u)));
|
||||
C.rb = C.rr;
|
||||
|
||||
if (PS_READ_BA)
|
||||
{
|
||||
if (denorm_c.a & 0x80u)
|
||||
C.ga = (float2)(float((denorm_c.a & 0x7Fu) | (denorm_TA.y & 0x80u)));
|
||||
else
|
||||
C.ga = (float2)(float((denorm_c.a & 0x7Fu) | (denorm_TA.x & 0x80u)));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (denorm_c.g & 0x80u)
|
||||
C.ga = (float2)(float((denorm_c.g & 0x7Fu) | (denorm_TA.y & 0x80u)));
|
||||
else
|
||||
C.ga = (float2)(float((denorm_c.g & 0x7Fu) | (denorm_TA.x & 0x80u)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -312,6 +312,8 @@ void main()
|
||||
#define PS_FST 0
|
||||
#define PS_WMS 0
|
||||
#define PS_WMT 0
|
||||
#define PS_ADJS 0
|
||||
#define PS_ADJT 0
|
||||
#define PS_FMT FMT_32
|
||||
#define PS_AEM 0
|
||||
#define PS_TFX 0
|
||||
@@ -332,7 +334,6 @@ void main()
|
||||
#define PS_CHANNEL_FETCH 0
|
||||
#define PS_TALES_OF_ABYSS_HLE 0
|
||||
#define PS_URBAN_CHAOS_HLE 0
|
||||
#define PS_INVALID_TEX0 0
|
||||
#define PS_SCALE_FACTOR 1.0
|
||||
#define PS_HDR 0
|
||||
#define PS_COLCLIP 0
|
||||
@@ -346,6 +347,7 @@ void main()
|
||||
#define PS_ZCLAMP 0
|
||||
#define PS_FEEDBACK_LOOP 0
|
||||
#define PS_TEX_IS_FB 0
|
||||
#define PS_SWAP_GA 0
|
||||
#endif
|
||||
|
||||
#define SW_BLEND (PS_BLEND_A || PS_BLEND_B || PS_BLEND_D)
|
||||
@@ -361,10 +363,10 @@ layout(std140, set = 0, binding = 1) uniform cb1
|
||||
vec2 TA;
|
||||
float MaxDepthPS;
|
||||
float Af;
|
||||
uvec4 MskFix;
|
||||
uvec4 FbMask;
|
||||
vec4 HalfTexel;
|
||||
vec4 MinMax;
|
||||
vec4 STRange;
|
||||
ivec4 ChannelShuffle;
|
||||
vec2 TC_OffsetHack;
|
||||
vec2 STScale;
|
||||
@@ -420,7 +422,20 @@ vec4 sample_c(vec2 uv)
|
||||
// As of 2018 this issue is still present.
|
||||
uv = (trunc(uv * WH.zw) + vec2(0.5, 0.5)) / WH.zw;
|
||||
#endif
|
||||
#if !PS_ADJS && !PS_ADJT
|
||||
uv *= STScale;
|
||||
#else
|
||||
#if PS_ADJS
|
||||
uv.x = (uv.x - STRange.x) * STRange.z;
|
||||
#else
|
||||
uv.x = uv.x * STScale.x;
|
||||
#endif
|
||||
#if PS_ADJT
|
||||
uv.y = (uv.y - STRange.y) * STRange.w;
|
||||
#else
|
||||
uv.y = uv.y * STScale.y;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if PS_AUTOMATIC_LOD == 1
|
||||
return texture(Texture, uv);
|
||||
@@ -455,13 +470,7 @@ vec4 sample_p_norm(float u)
|
||||
|
||||
vec4 clamp_wrap_uv(vec4 uv)
|
||||
{
|
||||
vec4 tex_size;
|
||||
|
||||
#if PS_INVALID_TEX0
|
||||
tex_size = WH.zwzw;
|
||||
#else
|
||||
tex_size = WH.xyxy;
|
||||
#endif
|
||||
vec4 tex_size = WH.xyxy;
|
||||
|
||||
#if PS_WMS == PS_WMT
|
||||
{
|
||||
@@ -476,7 +485,7 @@ vec4 clamp_wrap_uv(vec4 uv)
|
||||
// textures. Fixes Xenosaga's hair issue.
|
||||
uv = fract(uv);
|
||||
#endif
|
||||
uv = vec4((uvec4(uv * tex_size) & MskFix.xyxy) | MskFix.zwzw) / tex_size;
|
||||
uv = vec4((uvec4(uv * tex_size) & floatBitsToUint(MinMax.xyxy)) | floatBitsToUint(MinMax.zwzw)) / tex_size;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -491,7 +500,7 @@ vec4 clamp_wrap_uv(vec4 uv)
|
||||
#if PS_FST == 0
|
||||
uv.xz = fract(uv.xz);
|
||||
#endif
|
||||
uv.xz = vec2((uvec2(uv.xz * tex_size.xx) & MskFix.xx) | MskFix.zz) / tex_size.xx;
|
||||
uv.xz = vec2((uvec2(uv.xz * tex_size.xx) & floatBitsToUint(MinMax.xx)) | floatBitsToUint(MinMax.zz)) / tex_size.xx;
|
||||
}
|
||||
#endif
|
||||
#if PS_WMT == 2
|
||||
@@ -503,7 +512,7 @@ vec4 clamp_wrap_uv(vec4 uv)
|
||||
#if PS_FST == 0
|
||||
uv.yw = fract(uv.yw);
|
||||
#endif
|
||||
uv.yw = vec2((uvec2(uv.yw * tex_size.yy) & MskFix.yy) | MskFix.ww) / tex_size.yy;
|
||||
uv.yw = vec2((uvec2(uv.yw * tex_size.yy) & floatBitsToUint(MinMax.yy)) | floatBitsToUint(MinMax.ww)) / tex_size.yy;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -590,7 +599,7 @@ vec4 fetch_c(ivec2 uv)
|
||||
|
||||
ivec2 clamp_wrap_uv_depth(ivec2 uv)
|
||||
{
|
||||
ivec4 mask = ivec4(MskFix << 4);
|
||||
ivec4 mask = floatBitsToInt(MinMax) << 4;
|
||||
#if (PS_WMS == PS_WMT)
|
||||
{
|
||||
#if (PS_WMS == 2)
|
||||
@@ -907,11 +916,7 @@ vec4 fog(vec4 c, float f)
|
||||
|
||||
vec4 ps_color()
|
||||
{
|
||||
#if PS_FST == 0 && PS_INVALID_TEX0 == 1
|
||||
// Re-normalize coordinate from invalid GS to corrected texture size
|
||||
vec2 st = (vsIn.t.xy * WH.xy) / (vsIn.t.w * WH.zw);
|
||||
vec2 st_int = (vsIn.ti.zw * WH.xy) / (vsIn.t.w * WH.zw);
|
||||
#elif PS_FST == 0
|
||||
#if PS_FST == 0
|
||||
vec2 st = vsIn.t.xy / vsIn.t.w;
|
||||
vec2 st_int = vsIn.ti.zw / vsIn.t.w;
|
||||
#else
|
||||
@@ -1169,26 +1174,32 @@ void main()
|
||||
vec4 C = ps_color();
|
||||
|
||||
#if PS_SHUFFLE
|
||||
uvec4 denorm_c = uvec4(C);
|
||||
uvec2 denorm_TA = uvec2(vec2(TA.xy) * 255.0f + 0.5f);
|
||||
|
||||
// Mask will take care of the correct destination
|
||||
#if PS_READ_BA
|
||||
C.rb = C.bb;
|
||||
#if PS_SWAP_GA
|
||||
vec4 RT = trunc(subpassLoad(RtSampler) * 255.0f + 0.1f);
|
||||
C.a = RT.g;
|
||||
C.g = RT.a;
|
||||
#else
|
||||
C.rb = C.rr;
|
||||
#endif
|
||||
uvec4 denorm_c = uvec4(C);
|
||||
uvec2 denorm_TA = uvec2(vec2(TA.xy) * 255.0f + 0.5f);
|
||||
|
||||
#if PS_READ_BA
|
||||
if ((denorm_c.a & 0x80u) != 0u)
|
||||
C.ga = vec2(float((denorm_c.a & 0x7Fu) | (denorm_TA.y & 0x80u)));
|
||||
else
|
||||
C.ga = vec2(float((denorm_c.a & 0x7Fu) | (denorm_TA.x & 0x80u)));
|
||||
#else
|
||||
if ((denorm_c.g & 0x80u) != 0u)
|
||||
C.ga = vec2(float((denorm_c.g & 0x7Fu) | (denorm_TA.y & 0x80u)));
|
||||
else
|
||||
C.ga = vec2(float((denorm_c.g & 0x7Fu) | (denorm_TA.x & 0x80u)));
|
||||
// Mask will take care of the correct destination
|
||||
#if PS_READ_BA
|
||||
C.rb = C.bb;
|
||||
#else
|
||||
C.rb = C.rr;
|
||||
#endif
|
||||
|
||||
#if PS_READ_BA
|
||||
if ((denorm_c.a & 0x80u) != 0u)
|
||||
C.ga = vec2(float((denorm_c.a & 0x7Fu) | (denorm_TA.y & 0x80u)));
|
||||
else
|
||||
C.ga = vec2(float((denorm_c.a & 0x7Fu) | (denorm_TA.x & 0x80u)));
|
||||
#else
|
||||
if ((denorm_c.g & 0x80u) != 0u)
|
||||
C.ga = vec2(float((denorm_c.g & 0x7Fu) | (denorm_TA.y & 0x80u)));
|
||||
else
|
||||
C.ga = vec2(float((denorm_c.g & 0x7Fu) | (denorm_TA.x & 0x80u)));
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
#include "pcsx2/HostDisplay.h"
|
||||
#include "pcsx2/HostSettings.h"
|
||||
#include "pcsx2/INISettingsInterface.h"
|
||||
#include "pcsx2/PAD/Host/PAD.h"
|
||||
#include "pcsx2/PerformanceMetrics.h"
|
||||
#include "pcsx2/VMManager.h"
|
||||
|
||||
@@ -79,6 +80,7 @@ static std::optional<bool> s_use_window;
|
||||
|
||||
// Owned by the GS thread.
|
||||
static u32 s_dump_frame_number = 0;
|
||||
static u32 s_loop_number = s_loop_count;
|
||||
|
||||
bool GSRunner::InitializeConfig()
|
||||
{
|
||||
@@ -102,6 +104,13 @@ bool GSRunner::InitializeConfig()
|
||||
// we don't need any sound output
|
||||
si.SetStringValue("SPU2/Output", "OutputModule", "nullout");
|
||||
|
||||
// none of the bindings are going to resolve to anything
|
||||
PAD::ClearPortBindings(si, 0);
|
||||
si.ClearSection("Hotkeys");
|
||||
|
||||
// make sure any gamesettings inis in your tree don't get loaded
|
||||
si.SetBoolValue("EmuCore", "EnablePerGameSettings", false);
|
||||
|
||||
// force logging
|
||||
si.SetBoolValue("Logging", "EnableSystemConsole", true);
|
||||
si.SetBoolValue("Logging", "EnableTimestamps", true);
|
||||
@@ -273,13 +282,15 @@ void Host::ReleaseHostDisplay(bool clear_state)
|
||||
|
||||
bool Host::BeginPresentFrame(bool frame_skip)
|
||||
{
|
||||
// when we wrap around, don't race other files
|
||||
GSJoinSnapshotThreads();
|
||||
|
||||
// queue dumping of this frame
|
||||
std::string dump_path(fmt::format("{}_frame{}.png", s_output_prefix, s_dump_frame_number));
|
||||
GSQueueSnapshot(dump_path);
|
||||
if (s_loop_number == 0)
|
||||
{
|
||||
// when we wrap around, don't race other files
|
||||
GSJoinSnapshotThreads();
|
||||
|
||||
// queue dumping of this frame
|
||||
std::string dump_path(fmt::format("{}_frame{}.png", s_output_prefix, s_dump_frame_number));
|
||||
GSQueueSnapshot(dump_path);
|
||||
}
|
||||
if (g_host_display->BeginPresent(frame_skip))
|
||||
return true;
|
||||
|
||||
@@ -519,6 +530,29 @@ static bool ParseCommandLineArgs(int argc, char* argv[], VMBootParameters& param
|
||||
s_settings_interface.SetIntValue("EmuCore/GS", "Renderer", static_cast<int>(type));
|
||||
continue;
|
||||
}
|
||||
else if (CHECK_ARG_PARAM("-renderhacks"))
|
||||
{
|
||||
std::string str(argv[++i]);
|
||||
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "UserHacks", true);
|
||||
|
||||
if(str.find("af") != std::string::npos)
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "UserHacks_AutoFlush", true);
|
||||
if (str.find("cpufb") != std::string::npos)
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "UserHacks_CPU_FB_Conversion", true);
|
||||
if (str.find("dds") != std::string::npos)
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "UserHacks_DisableDepthSupport", true);
|
||||
if (str.find("dpi") != std::string::npos)
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "UserHacks_DisablePartialInvalidation", true);
|
||||
if (str.find("dsf") != std::string::npos)
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "UserHacks_DisableSafeFeatures", true);
|
||||
if (str.find("tinrt") != std::string::npos)
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "UserHacks_TextureInsideRt", true);
|
||||
if (str.find("plf") != std::string::npos)
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "preload_frame_with_gs_data", true);
|
||||
|
||||
continue;
|
||||
}
|
||||
else if (CHECK_ARG_PARAM("-logfile"))
|
||||
{
|
||||
const char* logfile = argv[++i];
|
||||
@@ -627,6 +661,7 @@ int main(int argc, char* argv[])
|
||||
|
||||
// apply new settings (e.g. pick up renderer change)
|
||||
VMManager::ApplySettings();
|
||||
GSDumpReplayer::SetIsDumpRunner(true);
|
||||
|
||||
if (VMManager::Initialize(params))
|
||||
{
|
||||
@@ -651,6 +686,7 @@ void Host::CPUThreadVSync()
|
||||
{
|
||||
// update GS thread copy of frame number
|
||||
GetMTGS().RunOnGSThread([frame_number = GSDumpReplayer::GetFrameNumber()]() { s_dump_frame_number = frame_number; });
|
||||
GetMTGS().RunOnGSThread([loop_number = GSDumpReplayer::GetLoopCount()]() { s_loop_number = loop_number; });
|
||||
|
||||
// process any window messages (but we shouldn't really have any)
|
||||
GSRunner::PumpPlatformMessages();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1363,6 +1363,7 @@ void MainWindow::onGameListRefreshProgress(const QString& status, int current, i
|
||||
|
||||
void MainWindow::onGameListRefreshComplete()
|
||||
{
|
||||
m_ui.statusBar->clearMessage();
|
||||
clearProgressBar();
|
||||
}
|
||||
|
||||
@@ -1463,7 +1464,6 @@ void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point)
|
||||
|
||||
if (m_ui.menuDebug->menuAction()->isVisible())
|
||||
{
|
||||
// TODO: Hook this up once it's implemented.
|
||||
action = menu.addAction(tr("Boot and Debug"));
|
||||
connect(action, &QAction::triggered, [this, entry]() { DebugInterface::setPauseOnEntry(true); startGameListEntry(entry); getDebuggerWindow()->show(); });
|
||||
}
|
||||
@@ -1932,6 +1932,7 @@ void MainWindow::onVMStopped()
|
||||
// If we're closing or in batch mode, quit the whole application now.
|
||||
if (m_is_closing || QtHost::InBatchMode())
|
||||
{
|
||||
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 1);
|
||||
QCoreApplication::quit();
|
||||
return;
|
||||
}
|
||||
@@ -1975,16 +1976,24 @@ void MainWindow::showEvent(QShowEvent* event)
|
||||
|
||||
void MainWindow::closeEvent(QCloseEvent* event)
|
||||
{
|
||||
if (!requestShutdown(true, true, EmuConfig.SaveStateOnShutdown))
|
||||
// If there's no VM, we can just exit as normal.
|
||||
if (!s_vm_valid)
|
||||
{
|
||||
event->ignore();
|
||||
QMainWindow::closeEvent(event);
|
||||
return;
|
||||
}
|
||||
|
||||
// But if there is, we have to cancel the action, regardless of whether we ended exiting
|
||||
// or not. The window still needs to be visible while GS is shutting down.
|
||||
event->ignore();
|
||||
|
||||
// Exit cancelled?
|
||||
if (!requestShutdown(true, true, EmuConfig.SaveStateOnShutdown))
|
||||
return;
|
||||
|
||||
// Application will be exited in VM stopped handler.
|
||||
saveStateToConfig();
|
||||
m_is_closing = true;
|
||||
|
||||
QMainWindow::closeEvent(event);
|
||||
}
|
||||
|
||||
static QString getFilenameFromMimeData(const QMimeData* md)
|
||||
@@ -2249,7 +2258,12 @@ void MainWindow::createDisplayWidget(bool fullscreen, bool render_to_main, bool
|
||||
// Don't risk doing this on Wayland, it really doesn't like window state changes,
|
||||
// and positioning has no effect anyway.
|
||||
if (!s_use_central_widget)
|
||||
restoreDisplayWindowGeometryFromConfig();
|
||||
{
|
||||
if (isVisible() && g_emu_thread->shouldRenderToMain())
|
||||
container->move(pos());
|
||||
else
|
||||
restoreDisplayWindowGeometryFromConfig();
|
||||
}
|
||||
|
||||
if (!is_exclusive_fullscreen)
|
||||
container->showFullScreen();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -96,6 +96,7 @@ static bool s_nogui_mode = false;
|
||||
static bool s_start_fullscreen_ui = false;
|
||||
static bool s_start_fullscreen_ui_fullscreen = false;
|
||||
static bool s_test_config_and_exit = false;
|
||||
static bool s_boot_and_debug = false;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// CPU Thread
|
||||
@@ -1621,6 +1622,7 @@ void QtHost::PrintCommandLineHelp(const std::string_view& progname)
|
||||
std::fprintf(stderr, " -nofullscreen: Prevents fullscreen mode from triggering if enabled.\n");
|
||||
std::fprintf(stderr, " -earlyconsolelog: Forces logging of early console messages to console.\n");
|
||||
std::fprintf(stderr, " -testconfig: Initializes configuration and checks version, then exits.\n");
|
||||
std::fprintf(stderr, " -debugger: Open debugger and break on entry point.\n");
|
||||
#ifdef ENABLE_RAINTEGRATION
|
||||
std::fprintf(stderr, " -raintegration: Use RAIntegration instead of built-in achievement support.\n");
|
||||
#endif
|
||||
@@ -1743,6 +1745,11 @@ bool QtHost::ParseCommandLineOptions(const QStringList& args, std::shared_ptr<VM
|
||||
s_test_config_and_exit = true;
|
||||
continue;
|
||||
}
|
||||
else if (CHECK_ARG(QStringLiteral("-debugger")))
|
||||
{
|
||||
s_boot_and_debug = true;
|
||||
continue;
|
||||
}
|
||||
#ifdef ENABLE_RAINTEGRATION
|
||||
else if (CHECK_ARG(QStringLiteral("-raintegration")))
|
||||
{
|
||||
@@ -1880,6 +1887,12 @@ int main(int argc, char* argv[])
|
||||
if (s_start_fullscreen_ui)
|
||||
g_emu_thread->startFullscreenUI(s_start_fullscreen_ui_fullscreen);
|
||||
|
||||
if (s_boot_and_debug)
|
||||
{
|
||||
DebugInterface::setPauseOnEntry(true);
|
||||
main_window->openDebugger();
|
||||
}
|
||||
|
||||
// Skip the update check if we're booting a game directly.
|
||||
if (autoboot)
|
||||
g_emu_thread->startVM(std::move(autoboot));
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
115
pcsx2/CDVD/Darwin/DriveUtility.cpp
Normal file
115
pcsx2/CDVD/Darwin/DriveUtility.cpp
Normal 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());
|
||||
}
|
||||
263
pcsx2/CDVD/Darwin/IOCtlSrc.cpp
Normal file
263
pcsx2/CDVD/Darwin/IOCtlSrc.cpp
Normal file
@@ -0,0 +1,263 @@
|
||||
/* PCSX2 - PS2 Emulator for PCs
|
||||
* Copyright (C) 2002-2022 PCSX2 Dev Team
|
||||
*
|
||||
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with PCSX2.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "PrecompiledHeader.h"
|
||||
#include "CDVD/CDVDdiscReader.h"
|
||||
#include "CDVD/CDVD.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <IOKit/storage/IOCDMediaBSDClient.h>
|
||||
#include <IOKit/storage/IODVDMediaBSDClient.h>
|
||||
#endif
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
|
||||
IOCtlSrc::IOCtlSrc(decltype(m_filename) filename)
|
||||
: m_filename(filename)
|
||||
{
|
||||
if (!Reopen())
|
||||
throw std::runtime_error(" * CDVD: Error opening source.\n");
|
||||
}
|
||||
|
||||
IOCtlSrc::~IOCtlSrc()
|
||||
{
|
||||
if (m_device != -1)
|
||||
{
|
||||
SetSpindleSpeed(true);
|
||||
close(m_device);
|
||||
}
|
||||
}
|
||||
|
||||
bool IOCtlSrc::Reopen()
|
||||
{
|
||||
if (m_device != -1)
|
||||
close(m_device);
|
||||
|
||||
// O_NONBLOCK allows a valid file descriptor to be returned even if the
|
||||
// drive is empty. Probably does other things too.
|
||||
m_device = open(m_filename.c_str(), O_RDONLY | O_NONBLOCK);
|
||||
if (m_device == -1)
|
||||
return false;
|
||||
|
||||
// DVD detection MUST be first on Linux - The TOC ioctls work for both
|
||||
// CDs and DVDs.
|
||||
if (ReadDVDInfo() || ReadCDInfo())
|
||||
SetSpindleSpeed(false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void IOCtlSrc::SetSpindleSpeed(bool restore_defaults) const
|
||||
{
|
||||
u16 speed = restore_defaults ? 0xFFFF : m_media_type >= 0 ? 5540 :
|
||||
3600;
|
||||
int ioctl_code = m_media_type >= 0 ? DKIOCDVDSETSPEED : DKIOCCDSETSPEED;
|
||||
if (ioctl(m_device, ioctl_code, &speed) == -1)
|
||||
{
|
||||
DevCon.Warning("CDVD: Failed to set spindle speed: %s", strerror(errno));
|
||||
}
|
||||
else if (!restore_defaults)
|
||||
{
|
||||
DevCon.WriteLn("CDVD: Spindle speed set to %d", speed);
|
||||
}
|
||||
}
|
||||
|
||||
u32 IOCtlSrc::GetSectorCount() const
|
||||
{
|
||||
return m_sectors;
|
||||
}
|
||||
|
||||
u32 IOCtlSrc::GetLayerBreakAddress() const
|
||||
{
|
||||
return m_layer_break;
|
||||
}
|
||||
|
||||
s32 IOCtlSrc::GetMediaType() const
|
||||
{
|
||||
return m_media_type;
|
||||
}
|
||||
|
||||
const std::vector<toc_entry>& IOCtlSrc::ReadTOC() const
|
||||
{
|
||||
return m_toc;
|
||||
}
|
||||
|
||||
bool IOCtlSrc::ReadSectors2048(u32 sector, u32 count, u8* buffer) const
|
||||
{
|
||||
const ssize_t bytes_to_read = 2048 * count;
|
||||
ssize_t bytes_read = pread(m_device, buffer, bytes_to_read, sector * 2048ULL);
|
||||
if (bytes_read == bytes_to_read)
|
||||
return true;
|
||||
|
||||
if (bytes_read == -1)
|
||||
DevCon.Warning("CDVD: read sectors %u-%u failed: %s",
|
||||
sector, sector + count - 1, strerror(errno));
|
||||
else
|
||||
DevCon.Warning("CDVD: read sectors %u-%u: %zd bytes read, %zd bytes expected",
|
||||
sector, sector + count - 1, bytes_read, bytes_to_read);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IOCtlSrc::ReadSectors2352(u32 sector, u32 count, u8* buffer) const
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
dk_cd_read_t desc;
|
||||
memset(&desc, 0, sizeof(dk_cd_read_t));
|
||||
desc.sectorArea = kCDSectorAreaSync | kCDSectorAreaHeader | kCDSectorAreaSubHeader | kCDSectorAreaUser | kCDSectorAreaAuxiliary;
|
||||
desc.sectorType = kCDSectorTypeUnknown;
|
||||
for (u32 i = 0; i < count; ++i)
|
||||
{
|
||||
desc.offset = (sector + i) * 2352ULL;
|
||||
desc.buffer = buffer + i * 2352;
|
||||
desc.bufferLength = 2352;
|
||||
if (ioctl(m_device, DKIOCCDREAD, &desc) == -1)
|
||||
{
|
||||
DevCon.Warning("CDVD: DKIOCCDREAD sector %u failed: %s",
|
||||
sector + i, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool IOCtlSrc::ReadDVDInfo()
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
dk_dvd_read_structure_t dvdrs;
|
||||
memset(&dvdrs, 0, sizeof(dk_dvd_read_structure_t));
|
||||
dvdrs.format = kDVDStructureFormatPhysicalFormatInfo;
|
||||
dvdrs.layer = 0;
|
||||
|
||||
DVDPhysicalFormatInfo layer0;
|
||||
dvdrs.buffer = &layer0;
|
||||
dvdrs.bufferLength = sizeof(DVDPhysicalFormatInfo);
|
||||
|
||||
int ret = ioctl(m_device, DKIOCDVDREADSTRUCTURE, &dvdrs);
|
||||
if (ret == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
u32 start_sector = *(u32*)layer0.startingPhysicalSectorNumberOfDataArea;
|
||||
u32 end_sector = *(u32*)layer0.endPhysicalSectorNumberOfDataArea;
|
||||
if (layer0.numberOfLayers == 0)
|
||||
{
|
||||
// Single layer
|
||||
m_media_type = 0;
|
||||
m_layer_break = 0;
|
||||
m_sectors = end_sector - start_sector + 1;
|
||||
}
|
||||
else if (layer0.trackPath == 0)
|
||||
{
|
||||
// Dual layer, Parallel Track Path
|
||||
DVDPhysicalFormatInfo layer1;
|
||||
dvdrs.layer = 1;
|
||||
dvdrs.buffer = &layer1;
|
||||
dvdrs.bufferLength = sizeof(DVDPhysicalFormatInfo);
|
||||
ret = ioctl(m_device, DKIOCDVDREADSTRUCTURE, &dvdrs);
|
||||
if (ret == -1)
|
||||
return false;
|
||||
u32 layer1_start_sector = *(u32*)layer1.startingPhysicalSectorNumberOfDataArea;
|
||||
u32 layer1_end_sector = *(u32*)layer1.endPhysicalSectorNumberOfDataArea;
|
||||
|
||||
m_media_type = 1;
|
||||
m_layer_break = end_sector - start_sector;
|
||||
m_sectors = end_sector - start_sector + 1 + layer1_end_sector - layer1_start_sector + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Dual layer, Opposite Track Path
|
||||
u32 end_sector_layer0 = *(u32*)layer0.endSectorNumberInLayerZero;
|
||||
m_media_type = 2;
|
||||
m_layer_break = end_sector_layer0 - start_sector;
|
||||
m_sectors = end_sector_layer0 - start_sector + 1 + end_sector - (~end_sector_layer0 & 0xFFFFFFU) + 1;
|
||||
}
|
||||
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool IOCtlSrc::ReadCDInfo()
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
u8* buffer = (u8*)malloc(2048);
|
||||
dk_cd_read_toc_t cdrt;
|
||||
memset(&cdrt, 0, sizeof(dk_cd_read_toc_t));
|
||||
cdrt.format = kCDTOCFormatTOC;
|
||||
cdrt.formatAsTime = 1;
|
||||
cdrt.address.track = 0;
|
||||
cdrt.buffer = buffer;
|
||||
cdrt.bufferLength = 2048;
|
||||
memset(buffer, 0, 2048);
|
||||
|
||||
if (ioctl(m_device, DKIOCCDREADTOC, &cdrt) == -1)
|
||||
{
|
||||
DevCon.Warning("CDVD: DKIOCCDREADTOC failed: %s\n", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
CDTOC* toc = (CDTOC*)buffer;
|
||||
|
||||
u32 desc_count = CDTOCGetDescriptorCount(toc);
|
||||
|
||||
for (u32 i = 0; i < desc_count; ++i)
|
||||
{
|
||||
CDTOCDescriptor desc = toc->descriptors[i];
|
||||
if (desc.point < 0xa0 && desc.adr == 1)
|
||||
{
|
||||
u32 lba = CDConvertMSFToLBA(desc.p);
|
||||
m_toc.push_back({lba, desc.point, desc.adr, desc.control});
|
||||
}
|
||||
else if (desc.point == 0xa2) // lead out, use to get total sector count
|
||||
{
|
||||
m_sectors = CDConvertMSFToLBA(desc.p);
|
||||
}
|
||||
}
|
||||
|
||||
m_media_type = -1;
|
||||
|
||||
free(buffer);
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool IOCtlSrc::DiscReady()
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
if (m_device == -1)
|
||||
return false;
|
||||
|
||||
if (!m_sectors)
|
||||
{
|
||||
Reopen();
|
||||
}
|
||||
|
||||
return !!m_sectors;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
79
pcsx2/DEV9/PacketReader/ARP/ARP_PacketEditor.cpp
Normal file
79
pcsx2/DEV9/PacketReader/ARP/ARP_PacketEditor.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
/* PCSX2 - PS2 Emulator for PCs
|
||||
* Copyright (C) 2002-2023 PCSX2 Dev Team
|
||||
*
|
||||
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with PCSX2.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "PrecompiledHeader.h"
|
||||
|
||||
#include "ARP_PacketEditor.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "winsock.h"
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
|
||||
namespace PacketReader::ARP
|
||||
{
|
||||
ARP_PacketEditor::ARP_PacketEditor(PayloadPtr* pkt)
|
||||
: basePkt{pkt}
|
||||
{
|
||||
}
|
||||
|
||||
u16 ARP_PacketEditor::GetHardwareType()
|
||||
{
|
||||
return ntohs(*(u16*)&basePkt->data[0]);
|
||||
}
|
||||
|
||||
u16 ARP_PacketEditor::GetProtocol()
|
||||
{
|
||||
return ntohs(*(u16*)&basePkt->data[2]);
|
||||
}
|
||||
|
||||
u8 ARP_PacketEditor::GetHardwareAddressLength()
|
||||
{
|
||||
return basePkt->data[4];
|
||||
}
|
||||
u8 ARP_PacketEditor::GetProtocolAddressLength()
|
||||
{
|
||||
return basePkt->data[5];
|
||||
}
|
||||
|
||||
u16 ARP_PacketEditor::GetOp()
|
||||
{
|
||||
return ntohs(*(u16*)&basePkt->data[6]);
|
||||
}
|
||||
|
||||
u8* ARP_PacketEditor::SenderHardwareAddress()
|
||||
{
|
||||
return &basePkt->data[8];
|
||||
}
|
||||
|
||||
u8* ARP_PacketEditor::SenderProtocolAddress()
|
||||
{
|
||||
int offset = 8 + GetHardwareAddressLength();
|
||||
return &basePkt->data[offset];
|
||||
}
|
||||
|
||||
u8* ARP_PacketEditor::TargetHardwareAddress()
|
||||
{
|
||||
int offset = 8 + GetHardwareAddressLength() + GetProtocolAddressLength();
|
||||
return &basePkt->data[offset];
|
||||
}
|
||||
|
||||
u8* ARP_PacketEditor::TargetProtocolAddress()
|
||||
{
|
||||
int offset = 8 + 2 * GetHardwareAddressLength() + GetProtocolAddressLength();
|
||||
return &basePkt->data[offset];
|
||||
}
|
||||
} // namespace PacketReader::ARP
|
||||
42
pcsx2/DEV9/PacketReader/ARP/ARP_PacketEditor.h
Normal file
42
pcsx2/DEV9/PacketReader/ARP/ARP_PacketEditor.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/* PCSX2 - PS2 Emulator for PCs
|
||||
* Copyright (C) 2002-2023 PCSX2 Dev Team
|
||||
*
|
||||
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with PCSX2.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DEV9/PacketReader/Payload.h"
|
||||
|
||||
namespace PacketReader::ARP
|
||||
{
|
||||
class ARP_PacketEditor
|
||||
{
|
||||
private:
|
||||
PayloadPtr* basePkt;
|
||||
|
||||
public:
|
||||
|
||||
ARP_PacketEditor(PayloadPtr* pkt);
|
||||
|
||||
u16 GetHardwareType();
|
||||
u16 GetProtocol();
|
||||
u8 GetHardwareAddressLength();
|
||||
u8 GetProtocolAddressLength();
|
||||
u16 GetOp();
|
||||
|
||||
u8* SenderHardwareAddress();
|
||||
u8* SenderProtocolAddress();
|
||||
u8* TargetHardwareAddress();
|
||||
u8* TargetProtocolAddress();
|
||||
};
|
||||
} // namespace PacketReader::ARP
|
||||
66
pcsx2/DEV9/PacketReader/EthernetFrameEditor.cpp
Normal file
66
pcsx2/DEV9/PacketReader/EthernetFrameEditor.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
/* PCSX2 - PS2 Emulator for PCs
|
||||
* Copyright (C) 2002-2023 PCSX2 Dev Team
|
||||
*
|
||||
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with PCSX2.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "PrecompiledHeader.h"
|
||||
|
||||
#include "EthernetFrameEditor.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "winsock.h"
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
|
||||
namespace PacketReader
|
||||
{
|
||||
EthernetFrameEditor::EthernetFrameEditor(NetPacket* pkt)
|
||||
: basePkt{pkt}
|
||||
{
|
||||
headerLength = 14; //(6+6+2)
|
||||
|
||||
//Note: we don't have to worry about the Ethernet Frame CRC as it is not included in the packet
|
||||
//Note: We don't support tagged frames
|
||||
|
||||
payload = std::make_unique<PayloadPtr>((u8*)&basePkt->buffer[14], pkt->size - headerLength);
|
||||
}
|
||||
|
||||
MAC_Address EthernetFrameEditor::GetDestinationMAC()
|
||||
{
|
||||
return *(MAC_Address*)&basePkt->buffer[0];
|
||||
}
|
||||
void EthernetFrameEditor::SetDestinationMAC(MAC_Address value)
|
||||
{
|
||||
*(MAC_Address*)&basePkt->buffer[0] = value;
|
||||
}
|
||||
|
||||
MAC_Address EthernetFrameEditor::GetSourceMAC()
|
||||
{
|
||||
return *(MAC_Address*)&basePkt->buffer[6];
|
||||
}
|
||||
void EthernetFrameEditor::SetSourceMAC(MAC_Address value)
|
||||
{
|
||||
*(MAC_Address*)&basePkt->buffer[6] = value;
|
||||
}
|
||||
|
||||
u16 EthernetFrameEditor::GetProtocol()
|
||||
{
|
||||
return ntohs(*(u16*)&basePkt->buffer[12]);
|
||||
}
|
||||
|
||||
PayloadPtr* EthernetFrameEditor::GetPayload()
|
||||
{
|
||||
return payload.get();
|
||||
}
|
||||
} // namespace PacketReader
|
||||
45
pcsx2/DEV9/PacketReader/EthernetFrameEditor.h
Normal file
45
pcsx2/DEV9/PacketReader/EthernetFrameEditor.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/* PCSX2 - PS2 Emulator for PCs
|
||||
* Copyright (C) 2002-2023 PCSX2 Dev Team
|
||||
*
|
||||
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with PCSX2.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DEV9/net.h"
|
||||
#include "MAC_Address.h"
|
||||
#include "Payload.h"
|
||||
|
||||
namespace PacketReader
|
||||
{
|
||||
class EthernetFrameEditor
|
||||
{
|
||||
public:
|
||||
int headerLength = 14;
|
||||
//Length
|
||||
private:
|
||||
NetPacket* basePkt;
|
||||
std::unique_ptr<PayloadPtr> payload;
|
||||
|
||||
public:
|
||||
EthernetFrameEditor(NetPacket* pkt);
|
||||
|
||||
MAC_Address GetDestinationMAC();
|
||||
void SetDestinationMAC(MAC_Address value);
|
||||
MAC_Address GetSourceMAC();
|
||||
void SetSourceMAC(MAC_Address value);
|
||||
|
||||
u16 GetProtocol();
|
||||
|
||||
PayloadPtr* GetPayload();
|
||||
};
|
||||
} // namespace PacketReader
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -105,6 +105,7 @@ namespace InputManager
|
||||
static bool ParseBindingAndGetSource(const std::string_view& binding, InputBindingKey* key, InputSource** source);
|
||||
|
||||
static bool IsAxisHandler(const InputEventHandler& handler);
|
||||
static float ApplySingleBindingScale(float sensitivity, float deadzone, float value);
|
||||
|
||||
static void AddHotkeyBindings(SettingsInterface& si);
|
||||
static void AddPadBindings(SettingsInterface& si, u32 pad, const char* default_type);
|
||||
@@ -585,6 +586,12 @@ std::string InputManager::GetPointerDeviceName(u32 pointer_index)
|
||||
// Binding Enumeration
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
float InputManager::ApplySingleBindingScale(float scale, float deadzone, float value)
|
||||
{
|
||||
const float svalue = std::clamp(value * scale, 0.0f, 1.0f);
|
||||
return (deadzone > 0.0f && svalue < deadzone) ? 0.0f : svalue;
|
||||
}
|
||||
|
||||
std::vector<const HotkeyInfo*> InputManager::GetHotkeyList()
|
||||
{
|
||||
std::vector<const HotkeyInfo*> ret;
|
||||
@@ -613,7 +620,7 @@ void InputManager::AddHotkeyBindings(SettingsInterface& si)
|
||||
|
||||
void InputManager::AddPadBindings(SettingsInterface& si, u32 pad_index, const char* default_type)
|
||||
{
|
||||
const std::string section(StringUtil::StdStringFromFormat("Pad%u", pad_index + 1));
|
||||
const std::string section(fmt::format("Pad{}", pad_index + 1));
|
||||
const std::string type(si.GetStringValue(section.c_str(), "Type", default_type));
|
||||
if (type.empty() || type == "None")
|
||||
return;
|
||||
@@ -636,9 +643,12 @@ void InputManager::AddPadBindings(SettingsInterface& si, u32 pad_index, const ch
|
||||
if (!bindings.empty())
|
||||
{
|
||||
// we use axes for all pad bindings to simplify things, and because they are pressure sensitive
|
||||
AddBindings(bindings, InputAxisEventHandler{[pad_index, bind_index = bi.bind_index](float value) {
|
||||
PAD::SetControllerState(pad_index, bind_index, value);
|
||||
}});
|
||||
const float sensitivity = si.GetFloatValue(section.c_str(), fmt::format("{}Scale", bi.name).c_str(), 1.0f);
|
||||
const float deadzone = si.GetFloatValue(section.c_str(), fmt::format("{}Deadzone", bi.name).c_str(), 0.0f);
|
||||
AddBindings(
|
||||
bindings, InputAxisEventHandler{[pad_index, bind_index = bi.bind_index, sensitivity, deadzone](float value) {
|
||||
PAD::SetControllerState(pad_index, bind_index, ApplySingleBindingScale(sensitivity, deadzone, value));
|
||||
}});
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -717,8 +727,11 @@ void InputManager::AddUSBBindings(SettingsInterface& si, u32 port)
|
||||
const std::vector<std::string> bindings(si.GetStringList(section.c_str(), bind_name.c_str()));
|
||||
if (!bindings.empty())
|
||||
{
|
||||
AddBindings(bindings, InputAxisEventHandler{[port, bind_index = bi.bind_index](
|
||||
float value) { USB::SetDeviceBindValue(port, bind_index, value); }});
|
||||
const float sensitivity = si.GetFloatValue(section.c_str(), fmt::format("{}Scale", bi.name).c_str(), 1.0f);
|
||||
const float deadzone = si.GetFloatValue(section.c_str(), fmt::format("{}Deadzone", bi.name).c_str(), 0.0f);
|
||||
AddBindings(bindings, InputAxisEventHandler{[port, bind_index = bi.bind_index, sensitivity, deadzone](float value) {
|
||||
USB::SetDeviceBindValue(port, bind_index, ApplySingleBindingScale(sensitivity, deadzone, value));
|
||||
}});
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
19
pcsx2/GS.cpp
19
pcsx2/GS.cpp
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -56,11 +56,13 @@ GSState::GSState()
|
||||
memset(&m_v, 0, sizeof(m_v));
|
||||
memset(&m_vertex, 0, sizeof(m_vertex));
|
||||
memset(&m_index, 0, sizeof(m_index));
|
||||
|
||||
memset(m_mem.m_vm8, 0, m_mem.m_vmsize);
|
||||
m_v.RGBAQ.Q = 1.0f;
|
||||
|
||||
GrowVertexBuffer();
|
||||
|
||||
m_draw_transfers.clear();
|
||||
|
||||
m_sssize = 0;
|
||||
|
||||
m_sssize += sizeof(m_version);
|
||||
@@ -140,8 +142,6 @@ void GSState::Reset(bool hardware_reset)
|
||||
Flush(GSFlushReason::RESET);
|
||||
|
||||
// FIXME: bios logo not shown cut in half after reset, missing graphics in GoW after first FMV
|
||||
if (hardware_reset)
|
||||
memset(m_mem.m_vm8, 0, m_mem.m_vmsize);
|
||||
memset(&m_path, 0, sizeof(m_path));
|
||||
memset(&m_v, 0, sizeof(m_v));
|
||||
|
||||
@@ -155,7 +155,7 @@ void GSState::Reset(bool hardware_reset)
|
||||
|
||||
m_env.UpdateDIMX();
|
||||
|
||||
for (size_t i = 0; i < 2; i++)
|
||||
for (u32 i = 0; i < 2; i++)
|
||||
{
|
||||
m_env.CTXT[i].UpdateScissor();
|
||||
|
||||
@@ -563,6 +563,16 @@ int GSState::GetFramebufferHeight()
|
||||
return frame_memory_height;
|
||||
}
|
||||
|
||||
int GSState::GetFramebufferBitDepth()
|
||||
{
|
||||
if (IsEnabled(0))
|
||||
return GSLocalMemory::m_psm[m_regs->DISP[0].DISPFB.PSM].bpp;
|
||||
else if (IsEnabled(1))
|
||||
return GSLocalMemory::m_psm[m_regs->DISP[1].DISPFB.PSM].bpp;
|
||||
|
||||
return 32;
|
||||
}
|
||||
|
||||
int GSState::GetFramebufferWidth()
|
||||
{
|
||||
const GSVector4i disp1_rect = GetFrameRect(0, true);
|
||||
@@ -673,14 +683,14 @@ void GSState::DumpVertices(const std::string& filename)
|
||||
|
||||
file << std::endl << std::endl;
|
||||
|
||||
const size_t count = m_index.tail;
|
||||
const u32 count = m_index.tail;
|
||||
GSVertex* buffer = &m_vertex.buff[0];
|
||||
|
||||
const char* DEL = ", ";
|
||||
|
||||
file << "VERTEX COORDS (XYZ)" << std::endl;
|
||||
file << std::fixed << std::setprecision(4);
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
for (u32 i = 0; i < count; ++i)
|
||||
{
|
||||
file << "\t" << "v" << i << ": ";
|
||||
GSVertex v = buffer[m_index.buff[i]];
|
||||
@@ -698,7 +708,7 @@ void GSState::DumpVertices(const std::string& filename)
|
||||
|
||||
file << "VERTEX COLOR (RGBA)" << std::endl;
|
||||
file << std::fixed << std::setprecision(6);
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
for (u32 i = 0; i < count; ++i)
|
||||
{
|
||||
file << "\t" << "v" << i << ": ";
|
||||
GSVertex v = buffer[m_index.buff[i]];
|
||||
@@ -716,7 +726,7 @@ void GSState::DumpVertices(const std::string& filename)
|
||||
const std::string qualifier = use_uv ? "UV" : "STQ";
|
||||
|
||||
file << "TEXTURE COORDS (" << qualifier << ")" << std::endl;;
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
for (u32 i = 0; i < count; ++i)
|
||||
{
|
||||
file << "\t" << "v" << i << ": ";
|
||||
const GSVertex v = buffer[m_index.buff[i]];
|
||||
@@ -1062,7 +1072,7 @@ void GSState::ApplyTEX0(GIFRegTEX0& TEX0)
|
||||
if (TEX0.CBP != m_mem.m_clut.GetCLUTCBP())
|
||||
{
|
||||
m_mem.m_clut.ClearDrawInvalidity();
|
||||
CLUTAutoFlush();
|
||||
CLUTAutoFlush(PRIM->PRIM);
|
||||
}
|
||||
Flush(GSFlushReason::CLUTCHANGE);
|
||||
}
|
||||
@@ -1689,7 +1699,6 @@ inline void GSState::CopyEnv(GSDrawingEnvironment* dest, GSDrawingEnvironment* s
|
||||
{
|
||||
memcpy(dest, src, 88);
|
||||
memcpy(&dest->CTXT[ctx], &src->CTXT[ctx], 96);
|
||||
dest->CTXT[ctx].m_fixed_tex0 = src->CTXT[ctx].m_fixed_tex0;
|
||||
}
|
||||
|
||||
void GSState::Flush(GSFlushReason reason)
|
||||
@@ -1844,10 +1853,10 @@ void GSState::FlushPrim()
|
||||
GSVertex buff[2];
|
||||
s_n++;
|
||||
|
||||
const size_t head = m_vertex.head;
|
||||
const size_t tail = m_vertex.tail;
|
||||
const size_t next = m_vertex.next;
|
||||
size_t unused = 0;
|
||||
const u32 head = m_vertex.head;
|
||||
const u32 tail = m_vertex.tail;
|
||||
const u32 next = m_vertex.next;
|
||||
u32 unused = 0;
|
||||
|
||||
if (tail > head)
|
||||
{
|
||||
@@ -1864,7 +1873,7 @@ void GSState::FlushPrim()
|
||||
break;
|
||||
case GS_TRIANGLELIST:
|
||||
case GS_TRIANGLESTRIP:
|
||||
unused = std::min<size_t>(tail - head, 2);
|
||||
unused = std::min<u32>(tail - head, 2);
|
||||
memcpy(buff, &m_vertex.buff[tail - unused], sizeof(GSVertex) * 2);
|
||||
break;
|
||||
case GS_TRIANGLEFAN:
|
||||
@@ -1932,7 +1941,7 @@ void GSState::FlushPrim()
|
||||
// Jak 3 shadows get spikey (with autoflush) if you don't.
|
||||
if (PRIM->PRIM == GS_TRIANGLEFAN)
|
||||
{
|
||||
for (size_t i = 0; i < unused; i++)
|
||||
for (u32 i = 0; i < unused; i++)
|
||||
{
|
||||
GSVector4i* RESTRICT vert_ptr = (GSVector4i*)&m_vertex.buff[i];
|
||||
GSVector4i v = vert_ptr[1];
|
||||
@@ -1984,8 +1993,6 @@ void GSState::Write(const u8* mem, int len)
|
||||
if (!m_tr.Update(w, h, psm.trbpp, len))
|
||||
return;
|
||||
|
||||
|
||||
|
||||
GIFRegTEX0& prev_tex0 = m_prev_env.CTXT[m_prev_env.PRIM.CTXT].TEX0;
|
||||
|
||||
const u32 write_start_bp = m_mem.m_psm[blit.DPSM].info.bn(m_env.TRXPOS.DSAX, m_env.TRXPOS.DSAY, blit.DBP, blit.DBW); // (m_mem.*psm.pa)(static_cast<int>(m_env.TRXPOS.DSAX), static_cast<int>(m_env.TRXPOS.DSAY), blit.DBP, blit.DBW) >> 6;
|
||||
@@ -2000,6 +2007,16 @@ void GSState::Write(const u8* mem, int len)
|
||||
// Invalid the CLUT if it crosses paths.
|
||||
m_mem.m_clut.InvalidateRange(write_start_bp, write_end_bp);
|
||||
|
||||
if (GSConfig.PreloadFrameWithGSData)
|
||||
{
|
||||
// Store the transfer for preloading new RT's.
|
||||
if (m_draw_transfers.size() == 0 || (m_draw_transfers.size() > 0 && blit.DBP != m_draw_transfers.back().blit.DBP))
|
||||
{
|
||||
GSUploadQueue new_transfer = { blit, s_n };
|
||||
m_draw_transfers.push_back(new_transfer);
|
||||
}
|
||||
}
|
||||
|
||||
GL_CACHE("Write! ... => 0x%x W:%d F:%s (DIR %d%d), dPos(%d %d) size(%d %d)",
|
||||
blit.DBP, blit.DBW, psm_str(blit.DPSM),
|
||||
m_env.TRXPOS.DIRX, m_env.TRXPOS.DIRY,
|
||||
@@ -2146,6 +2163,16 @@ void GSState::Move()
|
||||
{
|
||||
Flush(GSFlushReason::LOCALTOLOCALMOVE);
|
||||
}
|
||||
|
||||
if (GSConfig.PreloadFrameWithGSData)
|
||||
{
|
||||
// Store the transfer for preloading new RT's.
|
||||
if (m_draw_transfers.size() == 0 || (m_draw_transfers.size() > 0 && dbp != m_draw_transfers.back().blit.DBP))
|
||||
{
|
||||
GSUploadQueue new_transfer = { m_env.BITBLTBUF, s_n };
|
||||
m_draw_transfers.push_back(new_transfer);
|
||||
}
|
||||
}
|
||||
// Invalid the CLUT if it crosses paths.
|
||||
m_mem.m_clut.InvalidateRange(write_start_bp, write_end_bp);
|
||||
|
||||
@@ -2733,7 +2760,7 @@ int GSState::Defrost(const freezeData* fd)
|
||||
|
||||
m_env.UpdateDIMX();
|
||||
|
||||
for (size_t i = 0; i < 2; i++)
|
||||
for (u32 i = 0; i < 2; i++)
|
||||
{
|
||||
m_env.CTXT[i].UpdateScissor();
|
||||
|
||||
@@ -2802,7 +2829,7 @@ void GSState::UpdateVertexKick()
|
||||
|
||||
void GSState::GrowVertexBuffer()
|
||||
{
|
||||
const size_t maxcount = std::max<size_t>(m_vertex.maxcount * 3 / 2, 10000);
|
||||
const u32 maxcount = std::max<u32>(m_vertex.maxcount * 3 / 2, 10000);
|
||||
|
||||
GSVertex* vertex = (GSVertex*)_aligned_malloc(sizeof(GSVertex) * maxcount, 32);
|
||||
// Worst case index list is a list of points with vs expansion, 6 indices per point
|
||||
@@ -2810,8 +2837,8 @@ void GSState::GrowVertexBuffer()
|
||||
|
||||
if (vertex == NULL || index == NULL)
|
||||
{
|
||||
const size_t vert_byte_count = sizeof(GSVertex) * maxcount;
|
||||
const size_t idx_byte_count = sizeof(u32) * maxcount * 3;
|
||||
const u32 vert_byte_count = sizeof(GSVertex) * maxcount;
|
||||
const u32 idx_byte_count = sizeof(u32) * maxcount * 3;
|
||||
|
||||
Console.Error("GS: failed to allocate %zu bytes for verticles and %zu for indices.",
|
||||
vert_byte_count, idx_byte_count);
|
||||
@@ -2849,12 +2876,12 @@ GSState::PRIM_OVERLAP GSState::PrimitiveOverlap()
|
||||
return PRIM_OVERLAP_UNKNOW; // maybe, maybe not
|
||||
|
||||
// Check intersection of sprite primitive only
|
||||
const size_t count = m_vertex.next;
|
||||
const u32 count = m_vertex.next;
|
||||
PRIM_OVERLAP overlap = PRIM_OVERLAP_NO;
|
||||
const GSVertex* v = m_vertex.buff;
|
||||
|
||||
m_drawlist.clear();
|
||||
size_t i = 0;
|
||||
u32 i = 0;
|
||||
while (i < count)
|
||||
{
|
||||
// In order to speed up comparison a bounding-box is accumulated. It removes a
|
||||
@@ -2868,7 +2895,7 @@ GSState::PRIM_OVERLAP GSState::PrimitiveOverlap()
|
||||
GSVector4i all = GSVector4i(v[i].m[1]).upl16(GSVector4i(v[i + 1].m[1])).upl16().xzyw();
|
||||
all = all.xyxy().blend(all.zwzw(), all > all.zwxy());
|
||||
|
||||
size_t j = i + 2;
|
||||
u32 j = i + 2;
|
||||
while (j < count)
|
||||
{
|
||||
GSVector4i sprite = GSVector4i(v[j].m[1]).upl16(GSVector4i(v[j + 1].m[1])).upl16().xzyw();
|
||||
@@ -2906,12 +2933,12 @@ GSState::PRIM_OVERLAP GSState::PrimitiveOverlap()
|
||||
// Some safe-guard will be added in the outer-loop to avoid corruption with a limited perf impact
|
||||
if (v[1].XYZ.Y < v[0].XYZ.Y) {
|
||||
// First vertex is Top-Left
|
||||
for (size_t i = 0; i < count; i += 2) {
|
||||
for (u32 i = 0; i < count; i += 2) {
|
||||
if (v[i + 1].XYZ.Y > v[i].XYZ.Y) {
|
||||
return PRIM_OVERLAP_UNKNOW;
|
||||
}
|
||||
GSVector4i vi(v[i].XYZ.X, v[i + 1].XYZ.Y, v[i + 1].XYZ.X, v[i].XYZ.Y);
|
||||
for (size_t j = i + 2; j < count; j += 2) {
|
||||
for (u32 j = i + 2; j < count; j += 2) {
|
||||
GSVector4i vj(v[j].XYZ.X, v[j + 1].XYZ.Y, v[j + 1].XYZ.X, v[j].XYZ.Y);
|
||||
GSVector4i inter = vi.rintersect(vj);
|
||||
if (!inter.rempty()) {
|
||||
@@ -2922,12 +2949,12 @@ GSState::PRIM_OVERLAP GSState::PrimitiveOverlap()
|
||||
}
|
||||
else {
|
||||
// First vertex is Bottom-Left
|
||||
for (size_t i = 0; i < count; i += 2) {
|
||||
for (u32 i = 0; i < count; i += 2) {
|
||||
if (v[i + 1].XYZ.Y < v[i].XYZ.Y) {
|
||||
return PRIM_OVERLAP_UNKNOW;
|
||||
}
|
||||
GSVector4i vi(v[i].XYZ.X, v[i].XYZ.Y, v[i + 1].XYZ.X, v[i + 1].XYZ.Y);
|
||||
for (size_t j = i + 2; j < count; j += 2) {
|
||||
for (u32 j = i + 2; j < count; j += 2) {
|
||||
GSVector4i vj(v[j].XYZ.X, v[j].XYZ.Y, v[j + 1].XYZ.X, v[j + 1].XYZ.Y);
|
||||
GSVector4i inter = vi.rintersect(vj);
|
||||
if (!inter.rempty()) {
|
||||
@@ -2960,14 +2987,14 @@ __forceinline bool GSState::IsAutoFlushDraw()
|
||||
return false;
|
||||
}
|
||||
|
||||
__forceinline void GSState::CLUTAutoFlush()
|
||||
__forceinline void GSState::CLUTAutoFlush(u32 prim)
|
||||
{
|
||||
if (m_mem.m_clut.IsInvalid() & 2)
|
||||
return;
|
||||
|
||||
size_t n = 1;
|
||||
u32 n = 1;
|
||||
|
||||
switch (PRIM->PRIM)
|
||||
switch (prim)
|
||||
{
|
||||
case GS_POINTLIST:
|
||||
n = 1;
|
||||
@@ -3000,7 +3027,7 @@ __forceinline void GSState::CLUTAutoFlush()
|
||||
// If it's a point, then we only have one coord, so the address for start and end will be the same, which is bad for the following check.
|
||||
u32 endbp = startbp;
|
||||
// otherwise calculate the end.
|
||||
if (PRIM->PRIM != GS_POINTLIST || (m_index.tail > 1))
|
||||
if (prim != GS_POINTLIST || (m_index.tail > 1))
|
||||
endbp = psm.info.bn(temp_draw_rect.z - 1, temp_draw_rect.w - 1, m_context->FRAME.Block(), m_context->FRAME.FBW);
|
||||
|
||||
m_mem.m_clut.InvalidateRange(startbp, endbp, true);
|
||||
@@ -3008,6 +3035,7 @@ __forceinline void GSState::CLUTAutoFlush()
|
||||
}
|
||||
}
|
||||
|
||||
template<u32 prim, bool index_swap>
|
||||
__forceinline void GSState::HandleAutoFlush()
|
||||
{
|
||||
// Kind of a cheat, making the assumption that 2 consecutive fan/strip triangles won't overlap each other (*should* be safe)
|
||||
@@ -3020,13 +3048,14 @@ __forceinline void GSState::HandleAutoFlush()
|
||||
if (IsAutoFlushDraw())
|
||||
{
|
||||
int n = 1;
|
||||
size_t buff[3];
|
||||
const size_t head = m_vertex.head;
|
||||
const size_t tail = m_vertex.tail;
|
||||
u32 buff[3];
|
||||
const u32 head = m_vertex.head;
|
||||
const u32 tail = m_vertex.tail;
|
||||
|
||||
switch (PRIM->PRIM)
|
||||
switch (prim)
|
||||
{
|
||||
case GS_POINTLIST:
|
||||
buff[0] = tail - 1;
|
||||
n = 1;
|
||||
break;
|
||||
case GS_LINELIST:
|
||||
@@ -3099,7 +3128,7 @@ __forceinline void GSState::HandleAutoFlush()
|
||||
}
|
||||
|
||||
// Get the last texture position from the last draw.
|
||||
const GSVertex* v = &m_vertex.buff[m_index.buff[m_index.tail - 1]];
|
||||
const GSVertex* v = &m_vertex.buff[m_index.buff[m_index.tail - (index_swap ? n : 1)]];
|
||||
|
||||
if (PRIM->FST)
|
||||
{
|
||||
@@ -3203,40 +3232,43 @@ __forceinline void GSState::HandleAutoFlush()
|
||||
}
|
||||
}
|
||||
|
||||
template <u32 prim, bool auto_flush, bool index_swap>
|
||||
__forceinline void GSState::VertexKick(u32 skip)
|
||||
static constexpr u32 NumIndicesForPrim(u32 prim)
|
||||
{
|
||||
size_t n = 0;
|
||||
|
||||
switch (prim)
|
||||
{
|
||||
case GS_POINTLIST:
|
||||
case GS_INVALID:
|
||||
n = 1;
|
||||
break;
|
||||
return 1;
|
||||
case GS_LINELIST:
|
||||
case GS_SPRITE:
|
||||
case GS_LINESTRIP:
|
||||
n = 2;
|
||||
break;
|
||||
return 2;
|
||||
case GS_TRIANGLELIST:
|
||||
case GS_TRIANGLESTRIP:
|
||||
case GS_TRIANGLEFAN:
|
||||
n = 3;
|
||||
break;
|
||||
return 3;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
template <u32 prim, bool auto_flush, bool index_swap>
|
||||
__forceinline void GSState::VertexKick(u32 skip)
|
||||
{
|
||||
constexpr u32 n = NumIndicesForPrim(prim);
|
||||
static_assert(n > 0);
|
||||
|
||||
ASSERT(m_vertex.tail < m_vertex.maxcount + 3);
|
||||
|
||||
if (auto_flush && skip == 0 && m_index.tail > 0 && ((m_vertex.tail + 1) - m_vertex.head) >= n)
|
||||
{
|
||||
HandleAutoFlush();
|
||||
HandleAutoFlush<prim, index_swap>();
|
||||
}
|
||||
|
||||
size_t head = m_vertex.head;
|
||||
size_t tail = m_vertex.tail;
|
||||
size_t next = m_vertex.next;
|
||||
size_t xy_tail = m_vertex.xy_tail;
|
||||
u32 head = m_vertex.head;
|
||||
u32 tail = m_vertex.tail;
|
||||
u32 next = m_vertex.next;
|
||||
u32 xy_tail = m_vertex.xy_tail;
|
||||
|
||||
// callers should write XYZUVF to m_v.m[1] in one piece to have this load store-forwarded, either by the cpu or the compiler when this function is inlined
|
||||
|
||||
@@ -3255,7 +3287,7 @@ __forceinline void GSState::VertexKick(u32 skip)
|
||||
m_vertex.tail = ++tail;
|
||||
m_vertex.xy_tail = ++xy_tail;
|
||||
|
||||
const size_t m = tail - head;
|
||||
const u32 m = tail - head;
|
||||
|
||||
if (m < n)
|
||||
return;
|
||||
@@ -3437,41 +3469,61 @@ __forceinline void GSState::VertexKick(u32 skip)
|
||||
break;
|
||||
case GS_INVALID:
|
||||
m_vertex.tail = head;
|
||||
break;
|
||||
return;
|
||||
default:
|
||||
__assume(0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
GSVector4i draw_coord;
|
||||
const GSVector2i offset = GSVector2i(m_context->XYOFFSET.OFX, m_context->XYOFFSET.OFY);
|
||||
|
||||
for (size_t i = 0; i < n; i++)
|
||||
{
|
||||
const GSVertex* v = &m_vertex.buff[m_index.buff[(m_index.tail - n) + i]];
|
||||
draw_coord.x = (static_cast<int>(v->XYZ.X) - offset.x) >> 4;
|
||||
draw_coord.y = (static_cast<int>(v->XYZ.Y) - offset.y) >> 4;
|
||||
const GSVector4i voffset(GSVector4i::loadl(&m_context->XYOFFSET));
|
||||
|
||||
if (m_vertex.tail == n && i == 0)
|
||||
auto get_vertex = [&](u32 i) {
|
||||
GSVector4i v(GSVector4i::loadl(&m_vertex.buff[m_index.buff[(m_index.tail - n) + (i)]].XYZ));
|
||||
v = v.upl16(); // 16->32
|
||||
v = v.sub32(voffset); // -= (OFX, OFY)
|
||||
v = v.sra32(4); // >> 4
|
||||
return v;
|
||||
};
|
||||
|
||||
const GSVector4i xy0(get_vertex(0));
|
||||
GSVector4i min, max;
|
||||
if (m_vertex.tail == n)
|
||||
{
|
||||
temp_draw_rect.x = draw_coord.x;
|
||||
temp_draw_rect.y = draw_coord.y;
|
||||
temp_draw_rect = temp_draw_rect.xyxy();
|
||||
min = xy0;
|
||||
max = xy0;
|
||||
}
|
||||
else
|
||||
{
|
||||
temp_draw_rect.x = std::min(draw_coord.x, temp_draw_rect.x);
|
||||
temp_draw_rect.y = std::min(draw_coord.y, temp_draw_rect.y);
|
||||
temp_draw_rect.z = std::max(draw_coord.x, temp_draw_rect.z);
|
||||
temp_draw_rect.w = std::max(draw_coord.y, temp_draw_rect.w);
|
||||
min = temp_draw_rect.min_i32(xy0);
|
||||
max = temp_draw_rect.zwzw().max_i32(xy0);
|
||||
}
|
||||
|
||||
if constexpr (n > 1)
|
||||
{
|
||||
const GSVector4i xy1(get_vertex(1));
|
||||
min = min.min_i32(xy1);
|
||||
max = max.max_i32(xy1);
|
||||
|
||||
if constexpr (n > 2)
|
||||
{
|
||||
const GSVector4i xy2(get_vertex(2));
|
||||
min = min.min_i32(xy2);
|
||||
max = max.max_i32(xy2);
|
||||
}
|
||||
}
|
||||
|
||||
const GSVector4i scissor = GSVector4i(m_context->scissor.in);
|
||||
temp_draw_rect = min.upl64(max).rintersect(scissor);
|
||||
}
|
||||
|
||||
const GSVector4i scissor = GSVector4i(m_context->scissor.in);
|
||||
temp_draw_rect.rintersect(scissor);
|
||||
CLUTAutoFlush(prim);
|
||||
|
||||
CLUTAutoFlush();
|
||||
if (GSLocalMemory::m_psm[m_prev_env.CTXT[m_prev_env.PRIM.CTXT].FRAME.PSM].bpp == 16 && GSLocalMemory::m_psm[m_prev_env.CTXT[m_prev_env.PRIM.CTXT].TEX0.PSM].bpp == 16
|
||||
&& m_prev_env.CTXT[m_prev_env.PRIM.CTXT].FRAME.FBMSK == 0x3FFF && m_prev_env.CTXT[m_prev_env.PRIM.CTXT].ALPHA.IsOpaque())
|
||||
{
|
||||
DevCon.Warning("Shuffle flush %d", s_n+1);
|
||||
Flush(AUTOFLUSH);
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if region repeat is used (applying it does something to at least one of the values in min...max)
|
||||
@@ -3539,8 +3591,11 @@ GSState::TextureMinMaxResult GSState::GetTextureMinMax(const GIFRegTEX0& TEX0, c
|
||||
|
||||
const int minu = (int)CLAMP.MINU;
|
||||
const int minv = (int)CLAMP.MINV;
|
||||
const int maxu = (int)CLAMP.MAXU;
|
||||
const int maxv = (int)CLAMP.MAXV;
|
||||
|
||||
// For the FixedTEX0 case, in hardware, we handle this in the texture cache. Don't OR the bits in here, otherwise
|
||||
// we'll end up with an invalid rectangle, we want the passed-in rectangle to be relative to the normalized size.
|
||||
const int maxu = (wms != CLAMP_REGION_REPEAT || (int)CLAMP.MAXU < w) ? (int)CLAMP.MAXU : 0;
|
||||
const int maxv = (wmt != CLAMP_REGION_REPEAT || (int)CLAMP.MAXV < h) ? (int)CLAMP.MAXV : 0;
|
||||
|
||||
GSVector4i vr = tr;
|
||||
|
||||
|
||||
@@ -151,15 +151,15 @@ protected:
|
||||
struct
|
||||
{
|
||||
GSVertex* buff;
|
||||
size_t head, tail, next, maxcount; // head: first vertex, tail: last vertex + 1, next: last indexed + 1
|
||||
size_t xy_tail;
|
||||
u32 head, tail, next, maxcount; // head: first vertex, tail: last vertex + 1, next: last indexed + 1
|
||||
u32 xy_tail;
|
||||
u64 xy[4];
|
||||
} m_vertex;
|
||||
|
||||
struct
|
||||
{
|
||||
u32* buff;
|
||||
size_t tail;
|
||||
u32 tail;
|
||||
} m_index;
|
||||
|
||||
void UpdateContext();
|
||||
@@ -169,8 +169,9 @@ protected:
|
||||
|
||||
void GrowVertexBuffer();
|
||||
bool IsAutoFlushDraw();
|
||||
template<u32 prim, bool index_swap>
|
||||
void HandleAutoFlush();
|
||||
void CLUTAutoFlush();
|
||||
void CLUTAutoFlush(u32 prim);
|
||||
|
||||
template <u32 prim, bool auto_flush, bool index_swap>
|
||||
void VertexKick(u32 skip);
|
||||
@@ -206,6 +207,12 @@ protected:
|
||||
bool IsCoverageAlpha();
|
||||
|
||||
public:
|
||||
struct GSUploadQueue
|
||||
{
|
||||
GIFRegBITBLTBUF blit;
|
||||
int draw;
|
||||
};
|
||||
|
||||
GIFPath m_path[4];
|
||||
GIFRegPRIM* PRIM;
|
||||
GSPrivRegSet* m_regs;
|
||||
@@ -222,6 +229,8 @@ public:
|
||||
bool m_mipmap;
|
||||
u32 m_dirty_gs_regs;
|
||||
int m_backed_up_ctx;
|
||||
std::vector<GSUploadQueue> m_draw_transfers;
|
||||
bool m_force_preload;
|
||||
|
||||
static int s_n;
|
||||
|
||||
@@ -321,6 +330,7 @@ public:
|
||||
|
||||
int GetFramebufferHeight();
|
||||
int GetFramebufferWidth();
|
||||
int GetFramebufferBitDepth();
|
||||
int GetDisplayHMagnification();
|
||||
GSVector4i GetDisplayRect(int i = -1);
|
||||
GSVector4i GetFrameMagnifiedRect(int i = -1);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -309,6 +309,8 @@ struct alignas(16) GSHWDrawConfig
|
||||
u32 tcc : 1;
|
||||
u32 wms : 2;
|
||||
u32 wmt : 2;
|
||||
u32 adjs : 1;
|
||||
u32 adjt : 1;
|
||||
u32 ltf : 1;
|
||||
// Shuffle and fbmask effect
|
||||
u32 shuffle : 1;
|
||||
@@ -352,10 +354,10 @@ struct alignas(16) GSHWDrawConfig
|
||||
u32 automatic_lod : 1;
|
||||
u32 manual_lod : 1;
|
||||
u32 point_sampler : 1;
|
||||
u32 invalid_tex0 : 1; // Lupin the 3rd
|
||||
|
||||
// Scan mask
|
||||
u32 scanmsk : 2;
|
||||
u32 swap_ga : 1;
|
||||
};
|
||||
|
||||
struct
|
||||
@@ -554,11 +556,11 @@ struct alignas(16) GSHWDrawConfig
|
||||
GSVector4 FogColor_AREF;
|
||||
GSVector4 WH;
|
||||
GSVector4 TA_MaxDepth_Af;
|
||||
GSVector4i MskFix;
|
||||
GSVector4i FbMask;
|
||||
|
||||
GSVector4 HalfTexel;
|
||||
GSVector4 MinMax;
|
||||
GSVector4 STRange;
|
||||
GSVector4i ChannelShuffle;
|
||||
GSVector2 TCOffsetHack;
|
||||
GSVector2 STScale;
|
||||
@@ -744,11 +746,11 @@ protected:
|
||||
|
||||
struct
|
||||
{
|
||||
size_t stride, start, count, limit;
|
||||
u32 start, count;
|
||||
} m_vertex = {};
|
||||
struct
|
||||
{
|
||||
size_t start, count, limit;
|
||||
u32 start, count;
|
||||
} m_index = {};
|
||||
unsigned int m_frame = 0; // for ageing the pool
|
||||
bool m_rbswapped = false;
|
||||
@@ -799,9 +801,6 @@ public:
|
||||
virtual void ResetAPIState();
|
||||
virtual void RestoreAPIState();
|
||||
|
||||
virtual void BeginScene() {}
|
||||
virtual void EndScene();
|
||||
|
||||
virtual void ClearRenderTarget(GSTexture* t, const GSVector4& c) {}
|
||||
virtual void ClearRenderTarget(GSTexture* t, u32 c) {}
|
||||
virtual void InvalidateRenderTarget(GSTexture* t) {}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
#include "PrecompiledHeader.h"
|
||||
#include "GSDirtyRect.h"
|
||||
#include <vector>
|
||||
|
||||
GSDirtyRect::GSDirtyRect() :
|
||||
r(GSVector4i::zero()),
|
||||
@@ -30,7 +31,7 @@ GSDirtyRect::GSDirtyRect(GSVector4i& r, u32 psm, u32 bw) :
|
||||
{
|
||||
}
|
||||
|
||||
GSVector4i GSDirtyRect::GetDirtyRect(GIFRegTEX0& TEX0) const
|
||||
GSVector4i GSDirtyRect::GetDirtyRect(GIFRegTEX0& TEX0)
|
||||
{
|
||||
GSVector4i _r;
|
||||
|
||||
@@ -53,9 +54,7 @@ GSVector4i GSDirtyRect::GetDirtyRect(GIFRegTEX0& TEX0) const
|
||||
return _r;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
GSVector4i GSDirtyRectList::GetDirtyRect(GIFRegTEX0& TEX0, const GSVector2i& size) const
|
||||
GSVector4i GSDirtyRectList::GetTotalRect(GIFRegTEX0& TEX0, const GSVector2i& size)
|
||||
{
|
||||
if (!empty())
|
||||
{
|
||||
@@ -74,9 +73,31 @@ GSVector4i GSDirtyRectList::GetDirtyRect(GIFRegTEX0& TEX0, const GSVector2i& siz
|
||||
return GSVector4i::zero();
|
||||
}
|
||||
|
||||
GSVector4i GSDirtyRectList::GetDirtyRect(GIFRegTEX0& TEX0, const GSVector2i& size, bool clear)
|
||||
{
|
||||
if (!empty())
|
||||
{
|
||||
const std::vector<GSDirtyRect>::iterator &it = begin();
|
||||
const GSVector4i r = it[0].GetDirtyRect(TEX0);
|
||||
|
||||
if (clear)
|
||||
erase(it);
|
||||
|
||||
GSVector2i bs = GSLocalMemory::m_psm[TEX0.PSM].bs;
|
||||
|
||||
return r.ralign<Align_Outside>(bs).rintersect(GSVector4i(0, 0, size.x, size.y));
|
||||
}
|
||||
|
||||
return GSVector4i::zero();
|
||||
}
|
||||
|
||||
GSVector4i GSDirtyRectList::GetDirtyRectAndClear(GIFRegTEX0& TEX0, const GSVector2i& size)
|
||||
{
|
||||
GSVector4i r = GetDirtyRect(TEX0, size);
|
||||
clear();
|
||||
const GSVector4i r = GetDirtyRect(TEX0, size, true);
|
||||
return r;
|
||||
}
|
||||
|
||||
void GSDirtyRectList::ClearDirty()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
@@ -26,13 +26,15 @@ public:
|
||||
|
||||
GSDirtyRect();
|
||||
GSDirtyRect(GSVector4i& r, u32 psm, u32 bw);
|
||||
GSVector4i GetDirtyRect(GIFRegTEX0& TEX0) const;
|
||||
GSVector4i GetDirtyRect(GIFRegTEX0& TEX0);
|
||||
};
|
||||
|
||||
class GSDirtyRectList : public std::vector<GSDirtyRect>
|
||||
{
|
||||
public:
|
||||
GSDirtyRectList() {}
|
||||
GSVector4i GetDirtyRect(GIFRegTEX0& TEX0, const GSVector2i& size) const;
|
||||
GSVector4i GetTotalRect(GIFRegTEX0& TEX0, const GSVector2i& size);
|
||||
GSVector4i GetDirtyRect(GIFRegTEX0& TEX0, const GSVector2i& size, bool clear = false);
|
||||
GSVector4i GetDirtyRectAndClear(GIFRegTEX0& TEX0, const GSVector2i& size);
|
||||
void ClearDirty();
|
||||
};
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "GSRenderer.h"
|
||||
#include "GS/GSCapture.h"
|
||||
#include "GS/GSGL.h"
|
||||
#include "GSDumpReplayer.h"
|
||||
#include "Host.h"
|
||||
#include "HostDisplay.h"
|
||||
#include "PerformanceMetrics.h"
|
||||
@@ -554,15 +555,18 @@ static void CompressAndWriteScreenshot(std::string filename, u32 width, u32 heig
|
||||
image.SetPixels(width, height, std::move(pixels));
|
||||
|
||||
std::string key(fmt::format("GSScreenshot_{}", filename));
|
||||
Host::AddIconOSDMessage(key, ICON_FA_CAMERA, fmt::format("Saving screenshot to '{}'.", Path::GetFileName(filename)), 60.0f);
|
||||
|
||||
if(!GSDumpReplayer::IsRunner())
|
||||
Host::AddIconOSDMessage(key, ICON_FA_CAMERA, fmt::format("Saving screenshot to '{}'.", Path::GetFileName(filename)), 60.0f);
|
||||
|
||||
// maybe std::async would be better here.. but it's definitely worth threading, large screenshots take a while to compress.
|
||||
std::unique_lock lock(s_screenshot_threads_mutex);
|
||||
s_screenshot_threads.emplace_back([key = std::move(key), filename = std::move(filename), image = std::move(image), quality = GSConfig.ScreenshotQuality]() {
|
||||
if (image.SaveToFile(filename.c_str(), quality))
|
||||
{
|
||||
Host::AddIconOSDMessage(std::move(key), ICON_FA_CAMERA,
|
||||
fmt::format("Saved screenshot to '{}'.", Path::GetFileName(filename)), Host::OSD_INFO_DURATION);
|
||||
if(!GSDumpReplayer::IsRunner())
|
||||
Host::AddIconOSDMessage(std::move(key), ICON_FA_CAMERA,
|
||||
fmt::format("Saved screenshot to '{}'.", Path::GetFileName(filename)), Host::OSD_INFO_DURATION);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <sstream>
|
||||
#include <VersionHelpers.h>
|
||||
#include <d3dcompiler.h>
|
||||
#include <dxgidebug.h>
|
||||
|
||||
static bool SupportsTextureFormat(ID3D11Device* dev, DXGI_FORMAT format)
|
||||
{
|
||||
@@ -90,6 +91,8 @@ bool GSDevice11::Create()
|
||||
|
||||
m_dev = static_cast<ID3D11Device*>(g_host_display->GetDevice());
|
||||
m_ctx = static_cast<ID3D11DeviceContext*>(g_host_display->GetContext());
|
||||
if (GSConfig.UseDebugDevice)
|
||||
m_annotation = m_ctx.try_query<ID3DUserDefinedAnnotation>();
|
||||
level = m_dev->GetFeatureLevel();
|
||||
const bool support_feature_level_11_0 = (level >= D3D_FEATURE_LEVEL_11_0);
|
||||
|
||||
@@ -269,6 +272,27 @@ bool GSDevice11::Create()
|
||||
if (!m_shadeboost.ps)
|
||||
return false;
|
||||
|
||||
// Vertex/Index Buffer
|
||||
bd = {};
|
||||
bd.ByteWidth = VERTEX_BUFFER_SIZE;
|
||||
bd.Usage = D3D11_USAGE_DYNAMIC;
|
||||
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
|
||||
bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
|
||||
if (FAILED(m_dev->CreateBuffer(&bd, nullptr, m_vb.put())))
|
||||
{
|
||||
Console.Error("Failed to create vertex buffer.");
|
||||
return false;
|
||||
}
|
||||
|
||||
bd.ByteWidth = INDEX_BUFFER_SIZE;
|
||||
bd.BindFlags = D3D11_BIND_INDEX_BUFFER;
|
||||
if (FAILED(m_dev->CreateBuffer(&bd, nullptr, m_ib.put())))
|
||||
{
|
||||
Console.Error("Failed to create index buffer.");
|
||||
return false;
|
||||
}
|
||||
m_ctx->IASetIndexBuffer(m_ib.get(), DXGI_FORMAT_R32_UINT, 0);
|
||||
|
||||
//
|
||||
|
||||
memset(&rd, 0, sizeof(rd));
|
||||
@@ -281,7 +305,7 @@ bool GSDevice11::Create()
|
||||
rd.SlopeScaledDepthBias = 0;
|
||||
rd.DepthClipEnable = false; // ???
|
||||
rd.ScissorEnable = true;
|
||||
rd.MultisampleEnable = true;
|
||||
rd.MultisampleEnable = false;
|
||||
rd.AntialiasedLineEnable = false;
|
||||
|
||||
m_dev->CreateRasterizerState(&rd, m_rs.put());
|
||||
@@ -366,10 +390,9 @@ void GSDevice11::ResetAPIState()
|
||||
|
||||
void GSDevice11::RestoreAPIState()
|
||||
{
|
||||
const UINT vb_stride = static_cast<UINT>(m_state.vb_stride);
|
||||
const UINT vb_offset = 0;
|
||||
m_ctx->IASetVertexBuffers(0, 1, &m_state.vb, &vb_stride, &vb_offset);
|
||||
m_ctx->IASetIndexBuffer(m_state.ib, DXGI_FORMAT_R32_UINT, 0);
|
||||
m_ctx->IASetVertexBuffers(0, 1, m_vb.addressof(), &m_state.vb_stride, &vb_offset);
|
||||
m_ctx->IASetIndexBuffer(m_ib.get(), DXGI_FORMAT_R32_UINT, 0);
|
||||
m_ctx->IASetInputLayout(m_state.layout);
|
||||
m_ctx->IASetPrimitiveTopology(m_state.topology);
|
||||
m_ctx->VSSetShader(m_state.vs, nullptr, 0);
|
||||
@@ -451,6 +474,40 @@ void GSDevice11::ClearStencil(GSTexture* t, u8 c)
|
||||
m_ctx->ClearDepthStencilView(*(GSTexture11*)t, D3D11_CLEAR_STENCIL, 0, c);
|
||||
}
|
||||
|
||||
void GSDevice11::PushDebugGroup(const char* fmt, ...)
|
||||
{
|
||||
if (!m_annotation)
|
||||
return;
|
||||
|
||||
std::va_list ap;
|
||||
va_start(ap, fmt);
|
||||
std::string str(StringUtil::StdStringFromFormatV(fmt, ap));
|
||||
va_end(ap);
|
||||
|
||||
m_annotation->BeginEvent(StringUtil::UTF8StringToWideString(str).c_str());
|
||||
}
|
||||
|
||||
void GSDevice11::PopDebugGroup()
|
||||
{
|
||||
if (!m_annotation)
|
||||
return;
|
||||
|
||||
m_annotation->EndEvent();
|
||||
}
|
||||
|
||||
void GSDevice11::InsertDebugMessage(DebugMessageCategory category, const char* fmt, ...)
|
||||
{
|
||||
if (!m_annotation)
|
||||
return;
|
||||
|
||||
std::va_list ap;
|
||||
va_start(ap, fmt);
|
||||
std::string str(StringUtil::StdStringFromFormatV(fmt, ap));
|
||||
va_end(ap);
|
||||
|
||||
m_annotation->SetMarker(StringUtil::UTF8StringToWideString(str).c_str());
|
||||
}
|
||||
|
||||
GSTexture* GSDevice11::CreateSurface(GSTexture::Type type, int width, int height, int levels, GSTexture::Format format)
|
||||
{
|
||||
D3D11_TEXTURE2D_DESC desc = {};
|
||||
@@ -579,8 +636,6 @@ void GSDevice11::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture*
|
||||
|
||||
const bool draw_in_depth = dTex && dTex->IsDepthStencil();
|
||||
|
||||
BeginScene();
|
||||
|
||||
GSVector2i ds;
|
||||
if (dTex)
|
||||
{
|
||||
@@ -649,8 +704,6 @@ void GSDevice11::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture*
|
||||
|
||||
//
|
||||
|
||||
EndScene();
|
||||
|
||||
PSSetShaderResources(nullptr, nullptr);
|
||||
}
|
||||
|
||||
@@ -658,8 +711,6 @@ void GSDevice11::PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture*
|
||||
{
|
||||
ASSERT(sTex);
|
||||
|
||||
BeginScene();
|
||||
|
||||
GSVector2i ds;
|
||||
if (dTex)
|
||||
{
|
||||
@@ -726,8 +777,6 @@ void GSDevice11::PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture*
|
||||
|
||||
//
|
||||
|
||||
EndScene();
|
||||
|
||||
PSSetShaderResources(nullptr, nullptr);
|
||||
}
|
||||
|
||||
@@ -908,8 +957,6 @@ void GSDevice11::SetupDATE(GSTexture* rt, GSTexture* ds, const GSVertexPT1* vert
|
||||
{
|
||||
// sfex3 (after the capcom logo), vf4 (first menu fading in), ffxii shadows, rumble roses shadows, persona4 shadows
|
||||
|
||||
BeginScene();
|
||||
|
||||
ClearStencil(ds, 0);
|
||||
|
||||
// om
|
||||
@@ -942,156 +989,69 @@ void GSDevice11::SetupDATE(GSTexture* rt, GSTexture* ds, const GSVertexPT1* vert
|
||||
DrawPrimitive();
|
||||
|
||||
//
|
||||
|
||||
EndScene();
|
||||
}
|
||||
|
||||
void GSDevice11::IASetVertexBuffer(const void* vertex, size_t stride, size_t count)
|
||||
bool GSDevice11::IASetVertexBuffer(const void* vertex, u32 stride, u32 count)
|
||||
{
|
||||
void* ptr = nullptr;
|
||||
|
||||
if (IAMapVertexBuffer(&ptr, stride, count))
|
||||
{
|
||||
GSVector4i::storent(ptr, vertex, count * stride);
|
||||
|
||||
IAUnmapVertexBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
bool GSDevice11::IAMapVertexBuffer(void** vertex, size_t stride, size_t count)
|
||||
{
|
||||
ASSERT(m_vertex.count == 0);
|
||||
|
||||
if (count * stride > m_vertex.limit * m_vertex.stride)
|
||||
{
|
||||
m_vb.reset();
|
||||
|
||||
m_vertex.start = 0;
|
||||
m_vertex.limit = std::max<int>(count * 3 / 2, 11000);
|
||||
}
|
||||
|
||||
if (m_vb == nullptr)
|
||||
{
|
||||
D3D11_BUFFER_DESC bd;
|
||||
|
||||
memset(&bd, 0, sizeof(bd));
|
||||
|
||||
bd.Usage = D3D11_USAGE_DYNAMIC;
|
||||
bd.ByteWidth = m_vertex.limit * stride;
|
||||
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
|
||||
bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
|
||||
|
||||
const HRESULT hr = m_dev->CreateBuffer(&bd, nullptr, m_vb.put());
|
||||
|
||||
if (FAILED(hr))
|
||||
return false;
|
||||
}
|
||||
const u32 size = stride * count;
|
||||
if (size > VERTEX_BUFFER_SIZE)
|
||||
return false;
|
||||
|
||||
D3D11_MAP type = D3D11_MAP_WRITE_NO_OVERWRITE;
|
||||
|
||||
if (m_vertex.start + count > m_vertex.limit || stride != m_vertex.stride)
|
||||
m_vertex.start = (m_vb_pos + (stride - 1)) / stride;
|
||||
m_vb_pos = (m_vertex.start * stride) + size;
|
||||
if (m_vb_pos > VERTEX_BUFFER_SIZE)
|
||||
{
|
||||
m_vertex.start = 0;
|
||||
|
||||
m_vb_pos = size;
|
||||
type = D3D11_MAP_WRITE_DISCARD;
|
||||
}
|
||||
|
||||
D3D11_MAPPED_SUBRESOURCE m;
|
||||
|
||||
if (FAILED(m_ctx->Map(m_vb.get(), 0, type, 0, &m)))
|
||||
{
|
||||
return false;
|
||||
|
||||
GSVector4i::storent(static_cast<u8*>(m.pData) + m_vertex.start * stride, vertex, count * stride);
|
||||
|
||||
m_ctx->Unmap(m_vb.get(), 0);
|
||||
|
||||
if (m_state.vb_stride != stride)
|
||||
{
|
||||
m_state.vb_stride = stride;
|
||||
const UINT vb_offset = 0;
|
||||
m_ctx->IASetVertexBuffers(0, 1, m_vb.addressof(), &stride, &vb_offset);
|
||||
}
|
||||
|
||||
*vertex = (u8*)m.pData + m_vertex.start * stride;
|
||||
|
||||
m_vertex.count = count;
|
||||
m_vertex.stride = stride;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GSDevice11::IAUnmapVertexBuffer()
|
||||
bool GSDevice11::IASetIndexBuffer(const void* index, u32 count)
|
||||
{
|
||||
m_ctx->Unmap(m_vb.get(), 0);
|
||||
|
||||
IASetVertexBuffer(m_vb.get(), m_vertex.stride);
|
||||
}
|
||||
|
||||
void GSDevice11::IASetVertexBuffer(ID3D11Buffer* vb, size_t stride)
|
||||
{
|
||||
if (m_state.vb != vb || m_state.vb_stride != stride)
|
||||
{
|
||||
m_state.vb = vb;
|
||||
m_state.vb_stride = stride;
|
||||
|
||||
const u32 stride2 = stride;
|
||||
const u32 offset = 0;
|
||||
|
||||
m_ctx->IASetVertexBuffers(0, 1, &vb, &stride2, &offset);
|
||||
}
|
||||
}
|
||||
|
||||
void GSDevice11::IASetIndexBuffer(const void* index, size_t count)
|
||||
{
|
||||
ASSERT(m_index.count == 0);
|
||||
|
||||
if (count > m_index.limit)
|
||||
{
|
||||
m_ib.reset();
|
||||
|
||||
m_index.start = 0;
|
||||
m_index.limit = std::max<int>(count * 3 / 2, 11000);
|
||||
}
|
||||
|
||||
if (m_ib == nullptr)
|
||||
{
|
||||
D3D11_BUFFER_DESC bd;
|
||||
|
||||
memset(&bd, 0, sizeof(bd));
|
||||
|
||||
bd.Usage = D3D11_USAGE_DYNAMIC;
|
||||
bd.ByteWidth = m_index.limit * sizeof(u32);
|
||||
bd.BindFlags = D3D11_BIND_INDEX_BUFFER;
|
||||
bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
|
||||
|
||||
HRESULT hr = m_dev->CreateBuffer(&bd, nullptr, m_ib.put());
|
||||
|
||||
if (FAILED(hr))
|
||||
return;
|
||||
}
|
||||
if (count > (INDEX_BUFFER_SIZE / sizeof(u32)))
|
||||
return false;
|
||||
|
||||
D3D11_MAP type = D3D11_MAP_WRITE_NO_OVERWRITE;
|
||||
|
||||
if (m_index.start + count > m_index.limit)
|
||||
m_index.start = m_ib_pos;
|
||||
m_ib_pos += count;
|
||||
|
||||
if (m_ib_pos > (INDEX_BUFFER_SIZE / sizeof(u32)))
|
||||
{
|
||||
m_index.start = 0;
|
||||
|
||||
m_ib_pos = count;
|
||||
type = D3D11_MAP_WRITE_DISCARD;
|
||||
}
|
||||
|
||||
D3D11_MAPPED_SUBRESOURCE m;
|
||||
if (FAILED(m_ctx->Map(m_ib.get(), 0, type, 0, &m)))
|
||||
return false;
|
||||
|
||||
if (SUCCEEDED(m_ctx->Map(m_ib.get(), 0, type, 0, &m)))
|
||||
{
|
||||
memcpy((u8*)m.pData + m_index.start * sizeof(u32), index, count * sizeof(u32));
|
||||
|
||||
m_ctx->Unmap(m_ib.get(), 0);
|
||||
}
|
||||
|
||||
std::memcpy((u8*)m.pData + m_index.start * sizeof(u32), index, count * sizeof(u32));
|
||||
m_ctx->Unmap(m_ib.get(), 0);
|
||||
m_index.count = count;
|
||||
|
||||
IASetIndexBuffer(m_ib.get());
|
||||
}
|
||||
|
||||
void GSDevice11::IASetIndexBuffer(ID3D11Buffer* ib)
|
||||
{
|
||||
if (m_state.ib != ib)
|
||||
{
|
||||
m_state.ib = ib;
|
||||
|
||||
m_ctx->IASetIndexBuffer(ib, DXGI_FORMAT_R32_UINT, 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void GSDevice11::IASetInputLayout(ID3D11InputLayout* layout)
|
||||
@@ -1370,17 +1330,15 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config)
|
||||
// Warning: StretchRect must be called before BeginScene otherwise
|
||||
// vertices will be overwritten. Trust me you don't want to do that.
|
||||
StretchRect(config.rt, sRect, hdr_rt, dRect, ShaderConvert::HDR_INIT, false);
|
||||
g_perfmon.Put(GSPerfMon::TextureCopies, 1);
|
||||
}
|
||||
|
||||
BeginScene();
|
||||
|
||||
void* ptr = nullptr;
|
||||
if (IAMapVertexBuffer(&ptr, sizeof(*config.verts), config.nverts))
|
||||
if (!IASetVertexBuffer(config.verts, sizeof(*config.verts), config.nverts) ||
|
||||
!IASetIndexBuffer(config.indices, config.nindices))
|
||||
{
|
||||
GSVector4i::storent(ptr, config.verts, config.nverts * sizeof(*config.verts));
|
||||
IAUnmapVertexBuffer();
|
||||
Console.Error("Failed to upload vertices/indices (%u/%u)", config.nverts, config.nindices);
|
||||
return;
|
||||
}
|
||||
IASetIndexBuffer(config.indices, config.nindices);
|
||||
D3D11_PRIMITIVE_TOPOLOGY topology = D3D11_PRIMITIVE_TOPOLOGY_UNDEFINED;
|
||||
switch (config.topology)
|
||||
{
|
||||
@@ -1489,8 +1447,6 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config)
|
||||
}
|
||||
}
|
||||
|
||||
EndScene();
|
||||
|
||||
if (rt_copy)
|
||||
Recycle(rt_copy);
|
||||
if (ds_copy)
|
||||
@@ -1504,6 +1460,7 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config)
|
||||
const GSVector4 dRect(config.drawarea);
|
||||
const GSVector4 sRect = dRect / GSVector4(size.x, size.y).xyxy();
|
||||
StretchRect(hdr_rt, sRect, config.rt, dRect, ShaderConvert::HDR_RESOLVE, false);
|
||||
g_perfmon.Put(GSPerfMon::TextureCopies, 1);
|
||||
Recycle(hdr_rt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <unordered_map>
|
||||
#include <wil/com.h>
|
||||
#include <dxgi1_3.h>
|
||||
#include <d3d11_1.h>
|
||||
|
||||
struct GSVertexShader11
|
||||
{
|
||||
@@ -109,8 +110,13 @@ public:
|
||||
};
|
||||
|
||||
private:
|
||||
static constexpr u32 MAX_TEXTURES = 4;
|
||||
static constexpr u32 MAX_SAMPLERS = 1;
|
||||
enum : u32
|
||||
{
|
||||
MAX_TEXTURES = 4,
|
||||
MAX_SAMPLERS = 1,
|
||||
VERTEX_BUFFER_SIZE = 32 * 1024 * 1024,
|
||||
INDEX_BUFFER_SIZE = 16 * 1024 * 1024,
|
||||
};
|
||||
|
||||
int m_d3d_texsize;
|
||||
|
||||
@@ -130,15 +136,15 @@ private:
|
||||
|
||||
wil::com_ptr_nothrow<ID3D11Device> m_dev;
|
||||
wil::com_ptr_nothrow<ID3D11DeviceContext> m_ctx;
|
||||
wil::com_ptr_nothrow<ID3DUserDefinedAnnotation> m_annotation;
|
||||
wil::com_ptr_nothrow<IDXGISwapChain1> m_swapchain;
|
||||
wil::com_ptr_nothrow<ID3D11Buffer> m_vb;
|
||||
wil::com_ptr_nothrow<ID3D11Buffer> m_ib;
|
||||
u32 m_vb_pos = 0; // bytes
|
||||
u32 m_ib_pos = 0; // indices/sizeof(u32)
|
||||
|
||||
struct
|
||||
{
|
||||
ID3D11Buffer* vb;
|
||||
size_t vb_stride;
|
||||
ID3D11Buffer* ib;
|
||||
ID3D11InputLayout* layout;
|
||||
D3D11_PRIMITIVE_TOPOLOGY topology;
|
||||
ID3D11VertexShader* vs;
|
||||
@@ -151,6 +157,7 @@ private:
|
||||
std::array<ID3D11SamplerState*, MAX_SAMPLERS> ps_ss;
|
||||
GSVector2i viewport;
|
||||
GSVector4i scissor;
|
||||
u32 vb_stride;
|
||||
ID3D11DepthStencilState* dss;
|
||||
u8 sref;
|
||||
ID3D11BlendState* bs;
|
||||
@@ -257,6 +264,10 @@ public:
|
||||
void ClearDepth(GSTexture* t) override;
|
||||
void ClearStencil(GSTexture* t, u8 c) override;
|
||||
|
||||
void PushDebugGroup(const char* fmt, ...) override;
|
||||
void PopDebugGroup() override;
|
||||
void InsertDebugMessage(DebugMessageCategory category, const char* fmt, ...) override;
|
||||
|
||||
void CloneTexture(GSTexture* src, GSTexture** dest, const GSVector4i& rect);
|
||||
|
||||
void CopyRect(GSTexture* sTex, GSTexture* dTex, const GSVector4i& r, u32 destX, u32 destY) override;
|
||||
@@ -270,12 +281,8 @@ public:
|
||||
|
||||
void SetupDATE(GSTexture* rt, GSTexture* ds, const GSVertexPT1* vertices, bool datm);
|
||||
|
||||
void IASetVertexBuffer(const void* vertex, size_t stride, size_t count);
|
||||
bool IAMapVertexBuffer(void** vertex, size_t stride, size_t count);
|
||||
void IAUnmapVertexBuffer();
|
||||
void IASetVertexBuffer(ID3D11Buffer* vb, size_t stride);
|
||||
void IASetIndexBuffer(const void* index, size_t count);
|
||||
void IASetIndexBuffer(ID3D11Buffer* ib);
|
||||
bool IASetVertexBuffer(const void* vertex, u32 stride, u32 count);
|
||||
bool IASetIndexBuffer(const void* index, u32 count);
|
||||
void IASetInputLayout(ID3D11InputLayout* layout);
|
||||
void IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY topology);
|
||||
|
||||
|
||||
@@ -142,6 +142,8 @@ void GSDevice11::SetupPS(const PSSelector& sel, const GSHWDrawConfig::PSConstant
|
||||
sm.AddMacro("PS_FST", sel.fst);
|
||||
sm.AddMacro("PS_WMS", sel.wms);
|
||||
sm.AddMacro("PS_WMT", sel.wmt);
|
||||
sm.AddMacro("PS_ADJS", sel.adjs);
|
||||
sm.AddMacro("PS_ADJT", sel.adjt);
|
||||
sm.AddMacro("PS_AEM_FMT", sel.aem_fmt);
|
||||
sm.AddMacro("PS_AEM", sel.aem);
|
||||
sm.AddMacro("PS_TFX", sel.tfx);
|
||||
@@ -158,13 +160,13 @@ void GSDevice11::SetupPS(const PSSelector& sel, const GSHWDrawConfig::PSConstant
|
||||
sm.AddMacro("PS_POINT_SAMPLER", sel.point_sampler);
|
||||
sm.AddMacro("PS_SHUFFLE", sel.shuffle);
|
||||
sm.AddMacro("PS_READ_BA", sel.read_ba);
|
||||
sm.AddMacro("PS_SWAP_GA", sel.swap_ga);
|
||||
sm.AddMacro("PS_CHANNEL_FETCH", sel.channel);
|
||||
sm.AddMacro("PS_TALES_OF_ABYSS_HLE", sel.tales_of_abyss_hle);
|
||||
sm.AddMacro("PS_URBAN_CHAOS_HLE", sel.urban_chaos_hle);
|
||||
sm.AddMacro("PS_DFMT", sel.dfmt);
|
||||
sm.AddMacro("PS_DEPTH_FMT", sel.depth_fmt);
|
||||
sm.AddMacro("PS_PAL_FMT", sel.pal_fmt);
|
||||
sm.AddMacro("PS_INVALID_TEX0", sel.invalid_tex0);
|
||||
sm.AddMacro("PS_HDR", sel.hdr);
|
||||
sm.AddMacro("PS_COLCLIP", sel.colclip);
|
||||
sm.AddMacro("PS_BLEND_A", sel.blend_a);
|
||||
|
||||
@@ -842,8 +842,6 @@ void GSDevice12::IASetVertexBuffer(const void* vertex, size_t stride, size_t cou
|
||||
}
|
||||
|
||||
m_vertex.start = m_vertex_stream_buffer.GetCurrentOffset() / stride;
|
||||
m_vertex.limit = count;
|
||||
m_vertex.stride = stride;
|
||||
m_vertex.count = count;
|
||||
SetVertexBuffer(m_vertex_stream_buffer.GetGPUPointer(), m_vertex_stream_buffer.GetSize(), stride);
|
||||
|
||||
@@ -851,32 +849,6 @@ void GSDevice12::IASetVertexBuffer(const void* vertex, size_t stride, size_t cou
|
||||
m_vertex_stream_buffer.CommitMemory(size);
|
||||
}
|
||||
|
||||
bool GSDevice12::IAMapVertexBuffer(void** vertex, size_t stride, size_t count)
|
||||
{
|
||||
const u32 size = static_cast<u32>(stride) * static_cast<u32>(count);
|
||||
if (!m_vertex_stream_buffer.ReserveMemory(size, static_cast<u32>(stride)))
|
||||
{
|
||||
ExecuteCommandListAndRestartRenderPass(false, "Mapping bytes to vertex buffer");
|
||||
if (!m_vertex_stream_buffer.ReserveMemory(size, static_cast<u32>(stride)))
|
||||
pxFailRel("Failed to reserve space for vertices");
|
||||
}
|
||||
|
||||
m_vertex.start = m_vertex_stream_buffer.GetCurrentOffset() / stride;
|
||||
m_vertex.limit = m_vertex_stream_buffer.GetCurrentSpace() / stride;
|
||||
m_vertex.stride = stride;
|
||||
m_vertex.count = count;
|
||||
SetVertexBuffer(m_vertex_stream_buffer.GetGPUPointer(), m_vertex_stream_buffer.GetSize(), stride);
|
||||
|
||||
*vertex = m_vertex_stream_buffer.GetCurrentHostPointer();
|
||||
return true;
|
||||
}
|
||||
|
||||
void GSDevice12::IAUnmapVertexBuffer()
|
||||
{
|
||||
const u32 size = static_cast<u32>(m_vertex.stride) * static_cast<u32>(m_vertex.count);
|
||||
m_vertex_stream_buffer.CommitMemory(size);
|
||||
}
|
||||
|
||||
void GSDevice12::IASetIndexBuffer(const void* index, size_t count)
|
||||
{
|
||||
const u32 size = sizeof(u32) * static_cast<u32>(count);
|
||||
@@ -888,7 +860,6 @@ void GSDevice12::IASetIndexBuffer(const void* index, size_t count)
|
||||
}
|
||||
|
||||
m_index.start = m_index_stream_buffer.GetCurrentOffset() / sizeof(u32);
|
||||
m_index.limit = count;
|
||||
m_index.count = count;
|
||||
SetIndexBuffer(m_index_stream_buffer.GetGPUPointer(), m_index_stream_buffer.GetSize(), DXGI_FORMAT_R32_UINT);
|
||||
|
||||
@@ -1512,6 +1483,8 @@ const ID3DBlob* GSDevice12::GetTFXPixelShader(const GSHWDrawConfig::PSSelector&
|
||||
sm.AddMacro("PS_FST", sel.fst);
|
||||
sm.AddMacro("PS_WMS", sel.wms);
|
||||
sm.AddMacro("PS_WMT", sel.wmt);
|
||||
sm.AddMacro("PS_ADJS", sel.adjs);
|
||||
sm.AddMacro("PS_ADJT", sel.adjt);
|
||||
sm.AddMacro("PS_AEM_FMT", sel.aem_fmt);
|
||||
sm.AddMacro("PS_AEM", sel.aem);
|
||||
sm.AddMacro("PS_TFX", sel.tfx);
|
||||
@@ -1534,7 +1507,6 @@ const ID3DBlob* GSDevice12::GetTFXPixelShader(const GSHWDrawConfig::PSSelector&
|
||||
sm.AddMacro("PS_DFMT", sel.dfmt);
|
||||
sm.AddMacro("PS_DEPTH_FMT", sel.depth_fmt);
|
||||
sm.AddMacro("PS_PAL_FMT", sel.pal_fmt);
|
||||
sm.AddMacro("PS_INVALID_TEX0", sel.invalid_tex0);
|
||||
sm.AddMacro("PS_HDR", sel.hdr);
|
||||
sm.AddMacro("PS_COLCLIP", sel.colclip);
|
||||
sm.AddMacro("PS_BLEND_A", sel.blend_a);
|
||||
@@ -2657,8 +2629,6 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
|
||||
if (date_image)
|
||||
Recycle(date_image);
|
||||
|
||||
EndScene();
|
||||
|
||||
// now blit the hdr texture back to the original target
|
||||
if (hdr_rt)
|
||||
{
|
||||
|
||||
@@ -264,8 +264,6 @@ public:
|
||||
GSTexture12* SetupPrimitiveTrackingDATE(GSHWDrawConfig& config, PipelineSelector& pipe);
|
||||
|
||||
void IASetVertexBuffer(const void* vertex, size_t stride, size_t count);
|
||||
bool IAMapVertexBuffer(void** vertex, size_t stride, size_t count);
|
||||
void IAUnmapVertexBuffer();
|
||||
void IASetIndexBuffer(const void* index, size_t count);
|
||||
|
||||
void PSSetShaderResource(int i, GSTexture* sr, bool check_state);
|
||||
|
||||
@@ -838,7 +838,7 @@ bool GSHwHack::GSC_BlueTongueGames(GSRendererHW& r, const GSFrameInfo& fi, int&
|
||||
|
||||
bool GSHwHack::OI_PointListPalette(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t)
|
||||
{
|
||||
const size_t n_vertices = r.m_vertex.next;
|
||||
const u32 n_vertices = r.m_vertex.next;
|
||||
const int w = r.m_r.width();
|
||||
const int h = r.m_r.height();
|
||||
const bool is_copy = !r.PRIM->ABE || (
|
||||
|
||||
@@ -204,14 +204,27 @@ void GSRendererHW::VSync(u32 field, bool registers_written)
|
||||
{
|
||||
m_tc->RemoveAll();
|
||||
m_reset = false;
|
||||
m_force_preload = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_force_preload = false;
|
||||
std::vector<GSState::GSUploadQueue>::iterator iter;
|
||||
for (iter = m_draw_transfers.begin(); iter != m_draw_transfers.end(); ) {
|
||||
if ((s_n - iter->draw) > 5)
|
||||
iter = m_draw_transfers.erase(iter);
|
||||
else
|
||||
iter++;
|
||||
}
|
||||
}
|
||||
|
||||
if (GSConfig.LoadTextureReplacements)
|
||||
GSTextureReplacements::ProcessAsyncLoadedTextures();
|
||||
|
||||
m_tc->IncAge();
|
||||
|
||||
GSRenderer::VSync(field, registers_written);
|
||||
|
||||
m_tc->IncAge();
|
||||
|
||||
if (m_tc->GetHashCacheMemoryUsage() > 1024 * 1024 * 1024)
|
||||
{
|
||||
@@ -320,7 +333,7 @@ void GSRendererHW::Lines2Sprites()
|
||||
|
||||
if (m_vertex.next >= 2)
|
||||
{
|
||||
const size_t count = m_vertex.next;
|
||||
const u32 count = m_vertex.next;
|
||||
|
||||
int i = static_cast<int>(count) * 2 - 4;
|
||||
GSVertex* s = &m_vertex.buff[count - 2];
|
||||
@@ -390,7 +403,7 @@ void GSRendererHW::Lines2Sprites()
|
||||
template <GSHWDrawConfig::VSExpand Expand>
|
||||
void GSRendererHW::ExpandIndices()
|
||||
{
|
||||
size_t process_count = (m_index.tail + 3) / 4 * 4;
|
||||
u32 process_count = (m_index.tail + 3) / 4 * 4;
|
||||
if (Expand == GSHWDrawConfig::VSExpand::Point)
|
||||
{
|
||||
// Make sure we have space for writing off the end slightly
|
||||
@@ -450,22 +463,47 @@ void GSRendererHW::ExpandIndices()
|
||||
}
|
||||
|
||||
// Fix the vertex position/tex_coordinate from 16 bits color to 32 bits color
|
||||
void GSRendererHW::ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba)
|
||||
void GSRendererHW::ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba, bool& swap_ga)
|
||||
{
|
||||
const size_t count = m_vertex.next;
|
||||
const u32 count = m_vertex.next;
|
||||
GSVertex* v = &m_vertex.buff[0];
|
||||
const GIFRegXYOFFSET& o = m_context->XYOFFSET;
|
||||
|
||||
int first_vertex = (v[1].XYZ.X > v[0].XYZ.X) ? 0 : 1;
|
||||
// vertex position is 8 to 16 pixels, therefore it is the 16-31 bits of the colors
|
||||
const int pos = (v[0].XYZ.X - o.OFX) & 0xFF;
|
||||
const int pos = (v[first_vertex].XYZ.X - o.OFX) & 0xFF;
|
||||
DevCon.Warning("Draw %d Pos %d(%d)",s_n, pos, pos >> 4);
|
||||
write_ba = (pos > 112 && pos < 136);
|
||||
|
||||
// Read texture is 8 to 16 pixels (same as above)
|
||||
const float tw = static_cast<float>(1u << m_context->TEX0.TW);
|
||||
int tex_pos = (PRIM->FST) ? v[0].U : static_cast<int>(tw * v[0].ST.S);
|
||||
|
||||
// Read texture is 8 to 16 pixels (same as above)
|
||||
int tex_pos = (PRIM->FST) ? v[first_vertex].U : static_cast<int>(tw * v[first_vertex].ST.S);
|
||||
tex_pos &= 0xFF;
|
||||
read_ba = (tex_pos > 112 && tex_pos < 144);
|
||||
|
||||
int coord_width = std::abs(v[first_vertex].XYZ.X - v[1 - first_vertex].XYZ.X) >> 4;
|
||||
|
||||
int tex_width = m_vt.m_max.t.x - m_vt.m_min.t.x;
|
||||
|
||||
// Probably Green/Alpha swap
|
||||
if (tex_width > 14)
|
||||
{
|
||||
/*v[1 - first_vertex].XYZ.X = ((v[1 - first_vertex].XYZ.X - o.OFX) * 2) + o.OFX;
|
||||
v[first_vertex].XYZ.X = ((v[first_vertex].XYZ.X - o.OFX) * 2) + o.OFX;
|
||||
if (m_vt.m_max.p.y > 512.0f)
|
||||
{
|
||||
DevCon.Warning("Changing width from %d to %d", v[1 - first_vertex].XYZ.X, v[1 - first_vertex].XYZ.X + (tex_width << 4));
|
||||
v[1 - first_vertex].XYZ.X -= tex_width << 4;
|
||||
}*/
|
||||
DevCon.Warning("Width %d Max y %f", tex_width, m_vt.m_max.p.y);
|
||||
//read_ba = true;
|
||||
//write_ba = false;
|
||||
swap_ga = true;
|
||||
return;
|
||||
}
|
||||
else
|
||||
swap_ga = false;
|
||||
|
||||
bool half_bottom = false;
|
||||
switch (GSConfig.UserHacks_HalfBottomOverride)
|
||||
{
|
||||
@@ -503,7 +541,7 @@ void GSRendererHW::ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba)
|
||||
|
||||
int maxvert = 0;
|
||||
int minvert = 4096;
|
||||
for (size_t i = 0; i < count; i ++)
|
||||
for (u32 i = 0; i < count; i ++)
|
||||
{
|
||||
int YCord = 0;
|
||||
|
||||
@@ -526,17 +564,18 @@ void GSRendererHW::ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba)
|
||||
{
|
||||
GL_INS("First vertex is P: %d => %d T: %d => %d", v[0].XYZ.X, v[1].XYZ.X, v[0].U, v[1].U);
|
||||
|
||||
for (size_t i = 0; i < count; i += 2)
|
||||
for (u32 i = 0; i < count; i += 2)
|
||||
{
|
||||
first_vertex = (v[i+1].XYZ.X > v[i].XYZ.X) ? 0 : 1;
|
||||
if (write_ba)
|
||||
v[i].XYZ.X -= 128u;
|
||||
v[i+first_vertex].XYZ.X -= 128u;
|
||||
else
|
||||
v[i+1].XYZ.X += 128u;
|
||||
v[i+(1-first_vertex)].XYZ.X += 128u;
|
||||
|
||||
if (read_ba)
|
||||
v[i].U -= 128u;
|
||||
v[i + first_vertex].U -= 128u;
|
||||
else
|
||||
v[i+1].U += 128u;
|
||||
v[i+(1 - first_vertex)].U += 128u;
|
||||
|
||||
if (!half_bottom)
|
||||
{
|
||||
@@ -544,13 +583,13 @@ void GSRendererHW::ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba)
|
||||
const int tex_offset = v[i].V & 0xF;
|
||||
const GSVector4i offset(o.OFY, tex_offset, o.OFY, tex_offset);
|
||||
|
||||
GSVector4i tmp(v[i].XYZ.Y, v[i].V, v[i + 1].XYZ.Y, v[i + 1].V);
|
||||
GSVector4i tmp(v[i + first_vertex].XYZ.Y, v[i +first_vertex].V, v[i + (1 - first_vertex)].XYZ.Y, v[i + (1 - first_vertex)].V);
|
||||
tmp = GSVector4i(tmp - offset).srl32(1) + offset;
|
||||
|
||||
v[i].XYZ.Y = static_cast<u16>(tmp.x);
|
||||
v[i].V = static_cast<u16>(tmp.y);
|
||||
v[i + 1].XYZ.Y = static_cast<u16>(tmp.z);
|
||||
v[i + 1].V = static_cast<u16>(tmp.w);
|
||||
v[i + first_vertex].XYZ.Y = static_cast<u16>(tmp.x);
|
||||
v[i + first_vertex].V = static_cast<u16>(tmp.y);
|
||||
v[i + (1 - first_vertex)].XYZ.Y = static_cast<u16>(tmp.z);
|
||||
v[i + (1 - first_vertex)].V = static_cast<u16>(tmp.w);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -559,24 +598,26 @@ void GSRendererHW::ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba)
|
||||
const float offset_8pix = 8.0f / tw;
|
||||
GL_INS("First vertex is P: %d => %d T: %f => %f (offset %f)", v[0].XYZ.X, v[1].XYZ.X, v[0].ST.S, v[1].ST.S, offset_8pix);
|
||||
|
||||
for (size_t i = 0; i < count; i += 2)
|
||||
for (u32 i = 0; i < count; i += 2)
|
||||
{
|
||||
first_vertex = (v[i + 1].XYZ.X > v[i].XYZ.X) ? 0 : 1;
|
||||
|
||||
if (write_ba)
|
||||
v[i].XYZ.X -= 128u;
|
||||
v[i+ first_vertex].XYZ.X -= 128u;
|
||||
else
|
||||
v[i+1].XYZ.X += 128u;
|
||||
v[i+(1- first_vertex)].XYZ.X += 128u;
|
||||
|
||||
if (read_ba)
|
||||
v[i].ST.S -= offset_8pix;
|
||||
v[i + first_vertex].ST.S -= offset_8pix;
|
||||
else
|
||||
v[i+1].ST.S += offset_8pix;
|
||||
v[i + (1- first_vertex)].ST.S += offset_8pix;
|
||||
|
||||
if (!half_bottom)
|
||||
{
|
||||
// Height is too big (2x).
|
||||
const GSVector4i offset(o.OFY, o.OFY);
|
||||
|
||||
GSVector4i tmp(v[i].XYZ.Y, v[i + 1].XYZ.Y);
|
||||
GSVector4i tmp(v[i+ first_vertex].XYZ.Y, v[i + (1- first_vertex)].XYZ.Y);
|
||||
tmp = GSVector4i(tmp - offset).srl32(1) + offset;
|
||||
|
||||
//fprintf(stderr, "Before %d, After %d\n", v[i + 1].XYZ.Y, tmp.y);
|
||||
@@ -696,7 +737,7 @@ GSVector4i GSRendererHW::ComputeBoundingBox(const GSVector2& rtscale, const GSVe
|
||||
void GSRendererHW::MergeSprite(GSTextureCache::Source* tex)
|
||||
{
|
||||
// Upscaling hack to avoid various line/grid issues
|
||||
if (GSConfig.UserHacks_MergePPSprite && tex && tex->m_target && (m_vt.m_primclass == GS_SPRITE_CLASS))
|
||||
if (GSConfig.UserHacks_MergePPSprite && CanUpscale() && tex && tex->m_target && (m_vt.m_primclass == GS_SPRITE_CLASS))
|
||||
{
|
||||
if (PRIM->FST && GSLocalMemory::m_psm[tex->m_TEX0.PSM].fmt < 2 && ((m_vt.m_eq.value & 0xCFFFF) == 0xCFFFF))
|
||||
{
|
||||
@@ -709,7 +750,7 @@ void GSRendererHW::MergeSprite(GSTextureCache::Source* tex)
|
||||
// SSE optimization: shuffle m[1] to have (4*32 bits) X, Y, U, V
|
||||
const int first_dpX = v[1].XYZ.X - v[0].XYZ.X;
|
||||
const int first_dpU = v[1].U - v[0].U;
|
||||
for (size_t i = 0; i < m_vertex.next; i += 2)
|
||||
for (u32 i = 0; i < m_vertex.next; i += 2)
|
||||
{
|
||||
const int dpX = v[i + 1].XYZ.X - v[i].XYZ.X;
|
||||
const int dpU = v[i + 1].U - v[i].U;
|
||||
@@ -1112,10 +1153,10 @@ void GSRendererHW::RoundSpriteOffset()
|
||||
#if defined(DEBUG_V) || defined(DEBUG_U)
|
||||
bool debug = linear;
|
||||
#endif
|
||||
const size_t count = m_vertex.next;
|
||||
const u32 count = m_vertex.next;
|
||||
GSVertex* v = &m_vertex.buff[0];
|
||||
|
||||
for (size_t i = 0; i < count; i += 2)
|
||||
for (u32 i = 0; i < count; i += 2)
|
||||
{
|
||||
// Performance note: if it had any impact on perf, someone would port it to SSE (AKA GSVector)
|
||||
|
||||
@@ -1266,10 +1307,6 @@ void GSRendererHW::Draw()
|
||||
return;
|
||||
}
|
||||
|
||||
// Fix TEX0 size
|
||||
if (PRIM->TME && !IsMipMapActive())
|
||||
m_context->ComputeFixedTEX0(m_vt.m_min.t.xyxy(m_vt.m_max.t));
|
||||
|
||||
// skip alpha test if possible
|
||||
// Note: do it first so we know if frame/depth writes are masked
|
||||
|
||||
@@ -1362,7 +1399,7 @@ void GSRendererHW::Draw()
|
||||
}
|
||||
|
||||
// SW CLUT Render enable.
|
||||
bool preload = GSConfig.PreloadFrameWithGSData;
|
||||
bool preload = GSConfig.PreloadFrameWithGSData | m_force_preload;
|
||||
if (GSConfig.UserHacks_CPUCLUTRender > 0 || GSConfig.UserHacks_GPUTargetCLUTMode != GSGPUTargetCLUTMode::Disabled)
|
||||
{
|
||||
const CLUTDrawTestResult result = (GSConfig.UserHacks_CPUCLUTRender == 2) ? PossibleCLUTDrawAggressive() : PossibleCLUTDraw();
|
||||
@@ -1383,7 +1420,7 @@ void GSRendererHW::Draw()
|
||||
!IsOpaque()) // Blending enabled
|
||||
{
|
||||
GL_INS("Forcing preload due to partial/blended CLUT draw");
|
||||
preload = true;
|
||||
preload = m_force_preload = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1422,6 +1459,61 @@ void GSRendererHW::Draw()
|
||||
m_texture_shuffle = false;
|
||||
m_tex_is_fb = false;
|
||||
|
||||
// The rectangle of the draw
|
||||
m_r = GSVector4i(m_vt.m_min.p.xyxy(m_vt.m_max.p)).rintersect(GSVector4i(context->scissor.in));
|
||||
|
||||
if (!GSConfig.UserHacks_DisableSafeFeatures)
|
||||
{
|
||||
if (IsConstantDirectWriteMemClear(true))
|
||||
{
|
||||
// Likely doing a huge single page width clear, which never goes well. (Superman)
|
||||
// Burnout 3 does a 32x1024 double width clear on its reflection targets.
|
||||
const bool clear_height_valid = (m_r.w >= 1024);
|
||||
if (clear_height_valid && context->FRAME.FBW == 1)
|
||||
{
|
||||
u32 width = ceil(static_cast<float>(m_r.w) / GetFramebufferHeight()) * 64;
|
||||
// Framebuffer is likely to be read as 16bit later, so we will need to double the width if the write is 32bit.
|
||||
const bool double_width = GSLocalMemory::m_psm[context->FRAME.PSM].bpp == 32 && GetFramebufferBitDepth() == 16;
|
||||
m_r.x = 0;
|
||||
m_r.y = 0;
|
||||
m_r.w = GetFramebufferHeight();
|
||||
m_r.z = std::max((width * (double_width ? 2 : 1)), static_cast<u32>(GetFramebufferWidth()));
|
||||
context->FRAME.FBW = (m_r.z + 63) / 64;
|
||||
m_context->scissor.in.z = context->FRAME.FBW * 64;
|
||||
|
||||
GSVertex* s = &m_vertex.buff[0];
|
||||
s[0].XYZ.X = static_cast<u16>(m_context->XYOFFSET.OFX + 0);
|
||||
s[1].XYZ.X = static_cast<u16>(m_context->XYOFFSET.OFX + 16384);
|
||||
s[0].XYZ.Y = static_cast<u16>(m_context->XYOFFSET.OFY + 0);
|
||||
s[1].XYZ.Y = static_cast<u16>(m_context->XYOFFSET.OFY + 16384);
|
||||
|
||||
m_vertex.head = m_vertex.tail = m_vertex.next = 2;
|
||||
m_index.tail = 2;
|
||||
}
|
||||
|
||||
// Superman does a clear to white, not black, on its depth buffer.
|
||||
// Since we don't preload depth, OI_GsMemClear() won't work here, since we invalidate the target later
|
||||
// on. So, instead, let the draw go through with the expanded rectangle, and copy color->depth.
|
||||
const bool is_zero_clear = (((GSLocalMemory::m_psm[m_context->FRAME.PSM].fmt == 0) ?
|
||||
m_vertex.buff[1].RGBAQ.U32[0] :
|
||||
(m_vertex.buff[1].RGBAQ.U32[0] & ~0xFF000000)) == 0) && m_context->FRAME.FBMSK == 0 && IsBlendedOrOpaque();
|
||||
|
||||
if (is_zero_clear && OI_GsMemClear() && clear_height_valid)
|
||||
{
|
||||
m_tc->InvalidateVideoMem(context->offset.fb, m_r, false, true);
|
||||
m_tc->InvalidateVideoMemType(GSTextureCache::RenderTarget, context->FRAME.Block());
|
||||
|
||||
if (m_context->ZBUF.ZMSK == 0)
|
||||
{
|
||||
m_tc->InvalidateVideoMem(context->offset.zb, m_r, false, false);
|
||||
m_tc->InvalidateVideoMemType(GSTextureCache::DepthStencil, context->ZBUF.Block());
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
TextureMinMaxResult tmm;
|
||||
// Disable texture mapping if the blend is black and using alpha from vertex.
|
||||
if (PRIM->TME && !(PRIM->ABE && m_context->ALPHA.IsBlack() && !m_context->TEX0.TCC))
|
||||
{
|
||||
@@ -1513,19 +1605,58 @@ void GSRendererHW::Draw()
|
||||
|
||||
m_context->offset.tex = m_mem.GetOffset(TEX0.TBP0, TEX0.TBW, TEX0.PSM);
|
||||
|
||||
TextureMinMaxResult tmm = GetTextureMinMax(TEX0, MIP_CLAMP, m_vt.IsLinear());
|
||||
tmm = GetTextureMinMax(TEX0, MIP_CLAMP, m_vt.IsLinear());
|
||||
|
||||
m_src = tex_psm.depth ? m_tc->LookupDepthSource(TEX0, env.TEXA, tmm.coverage) :
|
||||
m_tc->LookupSource(TEX0, env.TEXA, tmm.coverage, (GSConfig.HWMipmap >= HWMipmapLevel::Basic ||
|
||||
m_src = tex_psm.depth ? m_tc->LookupDepthSource(TEX0, env.TEXA, MIP_CLAMP, tmm.coverage) :
|
||||
m_tc->LookupSource(TEX0, env.TEXA, MIP_CLAMP, tmm.coverage, (GSConfig.HWMipmap >= HWMipmapLevel::Basic ||
|
||||
GSConfig.TriFilter == TriFiltering::Forced) ? &hash_lod_range : nullptr);
|
||||
|
||||
}
|
||||
|
||||
GSVector2i unscaled_target_size;
|
||||
const GSVector2i t_size = GetTargetSize(&unscaled_target_size);
|
||||
|
||||
// Ensure draw rect is clamped to framebuffer size. Necessary for updating valid area.
|
||||
m_r = m_r.rintersect(GSVector4i(0, 0, unscaled_target_size.x, unscaled_target_size.y));
|
||||
|
||||
TEX0.TBP0 = context->FRAME.Block();
|
||||
TEX0.TBW = context->FRAME.FBW;
|
||||
TEX0.PSM = context->FRAME.PSM;
|
||||
|
||||
GSTextureCache::Target* rt = nullptr;
|
||||
if (!no_rt)
|
||||
rt = m_tc->LookupTarget(TEX0, t_size, GSTextureCache::RenderTarget, true, fm, false, unscaled_target_size.x, unscaled_target_size.y, preload);
|
||||
|
||||
TEX0.TBP0 = context->ZBUF.Block();
|
||||
TEX0.TBW = context->FRAME.FBW;
|
||||
TEX0.PSM = context->ZBUF.PSM;
|
||||
|
||||
GSTextureCache::Target* ds = nullptr;
|
||||
if (!no_ds)
|
||||
ds = m_tc->LookupTarget(TEX0, t_size, GSTextureCache::DepthStencil, context->DepthWrite(), 0, false, 0, 0, preload);
|
||||
|
||||
if (PRIM->TME)
|
||||
{
|
||||
GIFRegCLAMP MIP_CLAMP = context->CLAMP;
|
||||
|
||||
bool copy_16bit_to_target_shuffle = false;
|
||||
|
||||
if (rt)
|
||||
{
|
||||
// copy of a 16bit source in to this target, make sure it's opaque and not bilinear to reduce false positives.
|
||||
copy_16bit_to_target_shuffle = context->TEX0.TBP0 != context->FRAME.Block() && rt->m_32_bits_fmt == true && IsOpaque() && !(context->TEX1.MMIN & 1);
|
||||
}
|
||||
|
||||
// Hypothesis: texture shuffle is used as a postprocessing effect so texture will be an old target.
|
||||
// Initially code also tested the RT but it gives too much false-positive
|
||||
//
|
||||
// Both input and output are 16 bits and texture was initially 32 bits!
|
||||
m_texture_shuffle = (GSLocalMemory::m_psm[context->FRAME.PSM].bpp == 16) && (tex_psm.bpp == 16)
|
||||
&& draw_sprite_tex && m_src->m_32_bits_fmt;
|
||||
&& draw_sprite_tex && (m_src->m_32_bits_fmt || copy_16bit_to_target_shuffle);
|
||||
|
||||
|
||||
if (copy_16bit_to_target_shuffle && m_texture_shuffle)
|
||||
DevCon.Warning("here");
|
||||
// Okami mustn't call this code
|
||||
if (m_texture_shuffle && m_vertex.next < 3 && PRIM->FST && ((m_context->FRAME.FBMSK & fm_mask) == 0))
|
||||
{
|
||||
@@ -1562,7 +1693,6 @@ void GSRendererHW::Draw()
|
||||
{
|
||||
m_channel_shuffle = false;
|
||||
}
|
||||
|
||||
#if 0
|
||||
// FIXME: We currently crop off the rightmost and bottommost pixel when upscaling clamps,
|
||||
// until the issue is properly solved we should keep this disabled as it breaks many games when upscaling.
|
||||
@@ -1585,6 +1715,7 @@ void GSRendererHW::Draw()
|
||||
const int tw = 1 << TEX0.TW;
|
||||
const int th = 1 << TEX0.TH;
|
||||
const bool is_shuffle = m_channel_shuffle || m_texture_shuffle;
|
||||
|
||||
// If m_src is from a target that isn't the same size as the texture, texture sample edge modes won't work quite the same way
|
||||
// If the game actually tries to access stuff outside of the rendered target, it was going to get garbage anyways so whatever
|
||||
// But the game could issue reads that wrap to valid areas, so move wrapping to the shader if wrapping is used
|
||||
@@ -1629,7 +1760,7 @@ void GSRendererHW::Draw()
|
||||
|
||||
for (int layer = m_lod.x + 1; layer <= m_lod.y; layer++)
|
||||
{
|
||||
const GIFRegTEX0& MIP_TEX0 = GetTex0Layer(layer);
|
||||
const GIFRegTEX0 MIP_TEX0(GetTex0Layer(layer));
|
||||
|
||||
m_context->offset.tex = m_mem.GetOffset(MIP_TEX0.TBP0, MIP_TEX0.TBW, MIP_TEX0.PSM);
|
||||
|
||||
@@ -1652,68 +1783,7 @@ void GSRendererHW::Draw()
|
||||
m_vt.m_max.t = tmax;
|
||||
}
|
||||
}
|
||||
|
||||
// The rectangle of the draw
|
||||
m_r = GSVector4i(m_vt.m_min.p.xyxy(m_vt.m_max.p)).rintersect(GSVector4i(context->scissor.in));
|
||||
|
||||
if (!GSConfig.UserHacks_DisableSafeFeatures)
|
||||
{
|
||||
if (IsConstantDirectWriteMemClear())
|
||||
{
|
||||
// Likely doing a huge single page width clear, which never goes well. (Superman)
|
||||
// Burnout 3 does a 32x1024 double width clear on its reflection targets.
|
||||
const bool clear_height_valid = (m_r.w >= 1024);
|
||||
if (clear_height_valid && context->FRAME.FBW == 1)
|
||||
{
|
||||
m_r.w = GetFramebufferHeight();
|
||||
m_r.z = GetFramebufferWidth();
|
||||
context->FRAME.FBW = (m_r.z + 63) / 64;
|
||||
}
|
||||
|
||||
// Superman does a clear to white, not black, on its depth buffer.
|
||||
// Since we don't preload depth, OI_GsMemClear() won't work here, since we invalidate the target later
|
||||
// on. So, instead, let the draw go through with the expanded rectangle, and copy color->depth.
|
||||
const bool is_zero_clear = (((GSLocalMemory::m_psm[m_context->FRAME.PSM].fmt == 0) ?
|
||||
m_vertex.buff[1].RGBAQ.U32[0] :
|
||||
(m_vertex.buff[1].RGBAQ.U32[0] & ~0xFF000000)) == 0);
|
||||
if (is_zero_clear && OI_GsMemClear() && clear_height_valid)
|
||||
{
|
||||
m_tc->InvalidateVideoMem(context->offset.fb, m_r, false, true);
|
||||
m_tc->InvalidateVideoMemType(GSTextureCache::RenderTarget, context->FRAME.Block());
|
||||
|
||||
if (m_context->ZBUF.ZMSK == 0)
|
||||
{
|
||||
m_tc->InvalidateVideoMem(context->offset.zb, m_r, false, false);
|
||||
m_tc->InvalidateVideoMemType(GSTextureCache::DepthStencil, context->ZBUF.Block());
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GSVector2i unscaled_size;
|
||||
const GSVector2i t_size = GetTargetSize(&unscaled_size);
|
||||
|
||||
// Ensure draw rect is clamped to framebuffer size. Necessary for updating valid area.
|
||||
m_r = m_r.rintersect(GSVector4i(0, 0, unscaled_size.x, unscaled_size.y));
|
||||
|
||||
TEX0.TBP0 = context->FRAME.Block();
|
||||
TEX0.TBW = context->FRAME.FBW;
|
||||
TEX0.PSM = context->FRAME.PSM;
|
||||
|
||||
GSTextureCache::Target* rt = nullptr;
|
||||
if (!no_rt)
|
||||
rt = m_tc->LookupTarget(TEX0, t_size, GSTextureCache::RenderTarget, true, fm, false, 0, 0, preload);
|
||||
|
||||
TEX0.TBP0 = context->ZBUF.Block();
|
||||
TEX0.TBW = context->FRAME.FBW;
|
||||
TEX0.PSM = context->ZBUF.PSM;
|
||||
|
||||
GSTextureCache::Target* ds = nullptr;
|
||||
if (!no_ds)
|
||||
ds = m_tc->LookupTarget(TEX0, t_size, GSTextureCache::DepthStencil, context->DepthWrite(), 0, false, 0, 0, preload);
|
||||
|
||||
|
||||
if (rt)
|
||||
{
|
||||
// Be sure texture shuffle detection is properly propagated
|
||||
@@ -1803,7 +1873,7 @@ void GSRendererHW::Draw()
|
||||
|
||||
if (!GSConfig.UserHacks_DisableSafeFeatures)
|
||||
{
|
||||
if (IsConstantDirectWriteMemClear())
|
||||
if (IsConstantDirectWriteMemClear(false) && IsBlendedOrOpaque())
|
||||
OI_DoubleHalfClear(rt, ds);
|
||||
}
|
||||
|
||||
@@ -1812,7 +1882,7 @@ void GSRendererHW::Draw()
|
||||
// Note: second hack corrects only the texture coordinate
|
||||
if (CanUpscale() && (m_vt.m_primclass == GS_SPRITE_CLASS))
|
||||
{
|
||||
const size_t count = m_vertex.next;
|
||||
const u32 count = m_vertex.next;
|
||||
GSVertex* v = &m_vertex.buff[0];
|
||||
|
||||
// Hack to avoid vertical black line in various games (ace combat/tekken)
|
||||
@@ -1829,7 +1899,7 @@ void GSRendererHW::Draw()
|
||||
// Normaly vertex are aligned on full pixels and texture in half
|
||||
// pixels. Let's extend the coverage of an half-pixel to avoid
|
||||
// hole after upscaling
|
||||
for (size_t i = 0; i < count; i += 2)
|
||||
for (u32 i = 0; i < count; i += 2)
|
||||
{
|
||||
v[i + 1].XYZ.X += 8;
|
||||
// I really don't know if it is a good idea. Neither what to do for !PRIM->FST
|
||||
@@ -1940,7 +2010,7 @@ bool GSRendererHW::VerifyIndices()
|
||||
[[fallthrough]];
|
||||
case GS_POINT_CLASS:
|
||||
// Expect indices to be flat increasing
|
||||
for (size_t i = 0; i < m_index.tail; i++)
|
||||
for (u32 i = 0; i < m_index.tail; i++)
|
||||
{
|
||||
if (m_index.buff[i] != i)
|
||||
return false;
|
||||
@@ -1953,7 +2023,7 @@ bool GSRendererHW::VerifyIndices()
|
||||
// VS expand relies on this!
|
||||
if (g_gs_device->Features().provoking_vertex_last)
|
||||
{
|
||||
for (size_t i = 0; i < m_index.tail; i += 2)
|
||||
for (u32 i = 0; i < m_index.tail; i += 2)
|
||||
{
|
||||
if (m_index.buff[i] + 1 != m_index.buff[i + 1])
|
||||
return false;
|
||||
@@ -1961,7 +2031,7 @@ bool GSRendererHW::VerifyIndices()
|
||||
}
|
||||
else
|
||||
{
|
||||
for (size_t i = 0; i < m_index.tail; i += 2)
|
||||
for (u32 i = 0; i < m_index.tail; i += 2)
|
||||
{
|
||||
if (m_index.buff[i] != m_index.buff[i + 1] + 1)
|
||||
return false;
|
||||
@@ -1984,7 +2054,7 @@ void GSRendererHW::SetupIA(const float& sx, const float& sy)
|
||||
|
||||
if (GSConfig.UserHacks_WildHack && !m_isPackedUV_HackFlag && PRIM->TME && PRIM->FST)
|
||||
{
|
||||
for (unsigned int i = 0; i < m_vertex.next; i++)
|
||||
for (u32 i = 0; i < m_vertex.next; i++)
|
||||
m_vertex.buff[i].UV &= 0x3FEF3FEF;
|
||||
}
|
||||
const bool unscale_pt_ln = !GSConfig.UserHacks_DisableSafeFeatures && (GetUpscaleMultiplier() != 1.0f);
|
||||
@@ -2191,8 +2261,9 @@ void GSRendererHW::EmulateTextureShuffleAndFbmask()
|
||||
|
||||
bool write_ba;
|
||||
bool read_ba;
|
||||
bool swap_ga;
|
||||
|
||||
ConvertSpriteTextureShuffle(write_ba, read_ba);
|
||||
ConvertSpriteTextureShuffle(write_ba, read_ba, swap_ga);
|
||||
|
||||
// If date is enabled you need to test the green channel instead of the
|
||||
// alpha channel. Only enable this code in DATE mode to reduce the number
|
||||
@@ -2212,37 +2283,52 @@ void GSRendererHW::EmulateTextureShuffleAndFbmask()
|
||||
const GSVector2i rb_ga_mask = GSVector2i(fbmask & 0xFF, (fbmask >> 8) & 0xFF);
|
||||
m_conf.colormask.wrgba = 0;
|
||||
|
||||
// 2 Select the new mask
|
||||
if (rb_ga_mask.r != 0xFF)
|
||||
if (swap_ga)
|
||||
{
|
||||
if (write_ba)
|
||||
{
|
||||
GL_INS("Color shuffle %s => B", read_ba ? "B" : "R");
|
||||
m_conf.colormask.wb = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
GL_INS("Color shuffle %s => R", read_ba ? "B" : "R");
|
||||
m_conf.colormask.wr = 1;
|
||||
}
|
||||
if (rb_ga_mask.r)
|
||||
m_conf.ps.fbmask = 1;
|
||||
m_conf.ps.swap_ga = true;
|
||||
m_conf.ps.fbmask = 0;
|
||||
GL_CACHE("I hate myself");
|
||||
m_conf.colormask.wg = 1;
|
||||
m_conf.colormask.wa = 1;
|
||||
m_skip = 1;
|
||||
DevCon.Warning("Kill me");
|
||||
m_conf.ps.tex_is_fb = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (rb_ga_mask.g != 0xFF)
|
||||
else
|
||||
{
|
||||
if (write_ba)
|
||||
// 2 Select the new mask
|
||||
if (rb_ga_mask.r != 0xFF)
|
||||
{
|
||||
GL_INS("Color shuffle %s => A", read_ba ? "A" : "G");
|
||||
m_conf.colormask.wa = 1;
|
||||
if (write_ba)
|
||||
{
|
||||
DevCon.Warning("Color shuffle %s => B", read_ba ? "B" : "R");
|
||||
m_conf.colormask.wb = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
DevCon.Warning("Color shuffle %s => R", read_ba ? "B" : "R");
|
||||
m_conf.colormask.wr = 1;
|
||||
}
|
||||
if (rb_ga_mask.r)
|
||||
m_conf.ps.fbmask = 1;
|
||||
}
|
||||
else
|
||||
|
||||
if (rb_ga_mask.g != 0xFF)
|
||||
{
|
||||
GL_INS("Color shuffle %s => G", read_ba ? "A" : "G");
|
||||
m_conf.colormask.wg = 1;
|
||||
if (write_ba)
|
||||
{
|
||||
DevCon.Warning("Color shuffle %s => A", read_ba ? "A" : "G");
|
||||
m_conf.colormask.wa = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
DevCon.Warning("Color shuffle %s => G", read_ba ? "A" : "G");
|
||||
m_conf.colormask.wg = 1;
|
||||
}
|
||||
if (rb_ga_mask.g)
|
||||
m_conf.ps.fbmask = 1;
|
||||
}
|
||||
if (rb_ga_mask.g)
|
||||
m_conf.ps.fbmask = 1;
|
||||
}
|
||||
|
||||
if (m_conf.ps.fbmask && enable_fbmask_emulation)
|
||||
@@ -2255,12 +2341,12 @@ void GSRendererHW::EmulateTextureShuffleAndFbmask()
|
||||
// No blending so hit unsafe path.
|
||||
if (!PRIM->ABE || !features.texture_barrier)
|
||||
{
|
||||
GL_INS("FBMASK Unsafe SW emulated fb_mask:%x on tex shuffle", fbmask);
|
||||
DevCon.Warning("FBMASK Unsafe SW emulated fb_mask:%x on tex shuffle", fbmask);
|
||||
m_conf.require_one_barrier = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
GL_INS("FBMASK SW emulated fb_mask:%x on tex shuffle", fbmask);
|
||||
DevCon.Warning("FBMASK SW emulated fb_mask:%x on tex shuffle", fbmask);
|
||||
m_conf.require_full_barrier = true;
|
||||
}
|
||||
}
|
||||
@@ -2312,14 +2398,14 @@ void GSRendererHW::EmulateTextureShuffleAndFbmask()
|
||||
// No blending so hit unsafe path.
|
||||
if (!PRIM->ABE || !(~ff_fbmask & ~zero_fbmask & 0x7) || !g_gs_device->Features().texture_barrier)
|
||||
{
|
||||
GL_INS("FBMASK Unsafe SW emulated fb_mask:%x on %d bits format", m_context->FRAME.FBMSK,
|
||||
DevCon.Warning("FBMASK Unsafe SW emulated fb_mask:%x on %d bits format", m_context->FRAME.FBMSK,
|
||||
(m_conf.ps.dfmt == 2) ? 16 : 32);
|
||||
m_conf.require_one_barrier = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The safe and accurate path (but slow)
|
||||
GL_INS("FBMASK SW emulated fb_mask:%x on %d bits format", m_context->FRAME.FBMSK,
|
||||
DevCon.Warning("FBMASK SW emulated fb_mask:%x on %d bits format", m_context->FRAME.FBMSK,
|
||||
(m_conf.ps.dfmt == 2) ? 16 : 32);
|
||||
m_conf.require_full_barrier = true;
|
||||
}
|
||||
@@ -3076,6 +3162,26 @@ void GSRendererHW::EmulateBlending(bool& DATE_PRIMID, bool& DATE_BARRIER, bool&
|
||||
}
|
||||
}
|
||||
|
||||
__ri static constexpr bool IsRedundantClamp(u8 clamp, u32 clamp_min, u32 clamp_max, u32 tsize)
|
||||
{
|
||||
// Don't shader sample when the clamp/repeat is configured to the texture size.
|
||||
// That way trilinear etc still works.
|
||||
const u32 textent = (1u << tsize) - 1u;
|
||||
if (clamp == CLAMP_REGION_CLAMP)
|
||||
return (clamp_min == 0 && clamp_max == textent);
|
||||
else if (clamp == CLAMP_REGION_REPEAT)
|
||||
return (clamp_max == 0 && clamp_min == textent);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
__ri static constexpr u8 EffectiveClamp(u8 clamp, bool has_region)
|
||||
{
|
||||
// When we have extracted the region in the texture, we can use the hardware sampler for repeat/clamp.
|
||||
// (weird flip here because clamp/repeat is inverted for region vs non-region).
|
||||
return (clamp >= CLAMP_REGION_CLAMP && has_region) ? (clamp ^ 3) : clamp;
|
||||
}
|
||||
|
||||
void GSRendererHW::EmulateTextureSampler(const GSTextureCache::Source* tex)
|
||||
{
|
||||
// Warning fetch the texture PSM format rather than the context format. The latter could have been corrected in the texture cache for depth.
|
||||
@@ -3083,9 +3189,20 @@ void GSRendererHW::EmulateTextureSampler(const GSTextureCache::Source* tex)
|
||||
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[tex->m_TEX0.PSM];
|
||||
const GSLocalMemory::psm_t& cpsm = psm.pal > 0 ? GSLocalMemory::m_psm[m_context->TEX0.CPSM] : psm;
|
||||
|
||||
const u8 wms = m_context->CLAMP.WMS;
|
||||
const u8 wmt = m_context->CLAMP.WMT;
|
||||
// Redundant clamp tests are restricted to local memory/1x sources only, if we're from a target,
|
||||
// we keep the shader clamp. See #5851 on github, and the note in Draw().
|
||||
[[maybe_unused]] static constexpr const char* clamp_modes[] = { "REPEAT", "CLAMP", "REGION_CLAMP", "REGION_REPEAT" };
|
||||
const bool redundant_wms = !tex->m_target && IsRedundantClamp(m_context->CLAMP.WMS, m_context->CLAMP.MINU,
|
||||
m_context->CLAMP.MAXU, tex->m_TEX0.TW);
|
||||
const bool redundant_wmt = !tex->m_target && IsRedundantClamp(m_context->CLAMP.WMT, m_context->CLAMP.MINV,
|
||||
m_context->CLAMP.MAXV, tex->m_TEX0.TH);
|
||||
const u8 wms = EffectiveClamp(m_context->CLAMP.WMS, tex->m_region.HasX() || redundant_wms);
|
||||
const u8 wmt = EffectiveClamp(m_context->CLAMP.WMT, tex->m_region.HasY() || redundant_wmt);
|
||||
const bool complex_wms_wmt = !!((wms | wmt) & 2);
|
||||
GL_CACHE("WMS: %s [%s%s] WMT: %s [%s%s] Complex: %d MINU: %d MINV: %d MINV: %d MAXV: %d",
|
||||
clamp_modes[m_context->CLAMP.WMS], redundant_wms ? "redundant," : "", clamp_modes[wms],
|
||||
clamp_modes[m_context->CLAMP.WMT], redundant_wmt ? "redundant," : "", clamp_modes[wmt],
|
||||
complex_wms_wmt, m_context->CLAMP.MINU, m_context->CLAMP.MINV, m_context->CLAMP.MAXU, m_context->CLAMP.MAXV);
|
||||
|
||||
const bool need_mipmap = IsMipMapDraw();
|
||||
const bool shader_emulated_sampler = tex->m_palette || cpsm.fmt != 0 || complex_wms_wmt || psm.depth;
|
||||
@@ -3261,14 +3378,38 @@ void GSRendererHW::EmulateTextureSampler(const GSTextureCache::Source* tex)
|
||||
const GSVector4 st_scale = WH.zwzw() / GSVector4(w, h).xyxy();
|
||||
m_conf.cb_ps.STScale = GSVector2(st_scale.x, st_scale.y);
|
||||
|
||||
if (tex->m_region.HasX())
|
||||
{
|
||||
m_conf.cb_ps.STRange.x = static_cast<float>(tex->m_region.GetMinX()) / static_cast<float>(miptw);
|
||||
m_conf.cb_ps.STRange.z = static_cast<float>(miptw) / static_cast<float>(tex->m_region.GetWidth());
|
||||
m_conf.ps.adjs = 1;
|
||||
}
|
||||
if (tex->m_region.HasY())
|
||||
{
|
||||
m_conf.cb_ps.STRange.y = static_cast<float>(tex->m_region.GetMinY()) / static_cast<float>(mipth);
|
||||
m_conf.cb_ps.STRange.w = static_cast<float>(mipth) / static_cast<float>(tex->m_region.GetHeight());
|
||||
m_conf.ps.adjt = 1;
|
||||
}
|
||||
|
||||
m_conf.ps.fst = !!PRIM->FST;
|
||||
|
||||
m_conf.cb_ps.WH = WH;
|
||||
m_conf.cb_ps.HalfTexel = GSVector4(-0.5f, 0.5f).xxyy() / WH.zwzw();
|
||||
if (complex_wms_wmt)
|
||||
{
|
||||
m_conf.cb_ps.MskFix = GSVector4i(m_context->CLAMP.MINU, m_context->CLAMP.MINV, m_context->CLAMP.MAXU, m_context->CLAMP.MAXV);;
|
||||
m_conf.cb_ps.MinMax = GSVector4(m_conf.cb_ps.MskFix) / WH.xyxy();
|
||||
const GSVector4i clamp(m_context->CLAMP.MINU, m_context->CLAMP.MINV, m_context->CLAMP.MAXU, m_context->CLAMP.MAXV);
|
||||
const GSVector4 region_repeat(GSVector4::cast(clamp));
|
||||
const GSVector4 region_clamp(GSVector4(clamp) / WH.xyxy());
|
||||
if (wms >= CLAMP_REGION_CLAMP)
|
||||
{
|
||||
m_conf.cb_ps.MinMax.x = (wms == CLAMP_REGION_CLAMP && !m_conf.ps.depth_fmt) ? region_clamp.x : region_repeat.x;
|
||||
m_conf.cb_ps.MinMax.z = (wms == CLAMP_REGION_CLAMP && !m_conf.ps.depth_fmt) ? region_clamp.z : region_repeat.z;
|
||||
}
|
||||
if (wmt >= CLAMP_REGION_CLAMP)
|
||||
{
|
||||
m_conf.cb_ps.MinMax.y = (wmt == CLAMP_REGION_CLAMP && !m_conf.ps.depth_fmt) ? region_clamp.y : region_repeat.y;
|
||||
m_conf.cb_ps.MinMax.w = (wmt == CLAMP_REGION_CLAMP && !m_conf.ps.depth_fmt) ? region_clamp.w : region_repeat.w;
|
||||
}
|
||||
}
|
||||
else if (trilinear_manual)
|
||||
{
|
||||
@@ -3289,18 +3430,6 @@ void GSRendererHW::EmulateTextureSampler(const GSTextureCache::Source* tex)
|
||||
m_conf.cb_ps.TCOffsetHack = GSVector2(tc_oh_ts.z, tc_oh_ts.w);
|
||||
m_conf.cb_vs.texture_scale = GSVector2(tc_oh_ts.x, tc_oh_ts.y);
|
||||
|
||||
// Must be done after all coordinates math
|
||||
if (m_context->HasFixedTEX0() && !PRIM->FST)
|
||||
{
|
||||
m_conf.ps.invalid_tex0 = 1;
|
||||
// Use invalid size to denormalize ST coordinate
|
||||
m_conf.cb_ps.WH.x = static_cast<float>(1 << m_context->stack.TEX0.TW);
|
||||
m_conf.cb_ps.WH.y = static_cast<float>(1 << m_context->stack.TEX0.TH);
|
||||
|
||||
// We can't handle m_target with invalid_tex0 atm due to upscaling
|
||||
ASSERT(!tex->m_target);
|
||||
}
|
||||
|
||||
// Only enable clamping in CLAMP mode. REGION_CLAMP will be done manually in the shader
|
||||
m_conf.sampler.tau = (wms != CLAMP_CLAMP);
|
||||
m_conf.sampler.tav = (wmt != CLAMP_CLAMP);
|
||||
@@ -3997,11 +4126,19 @@ GSRendererHW::CLUTDrawTestResult GSRendererHW::PossibleCLUTDraw()
|
||||
// If we have GPU CLUT enabled, don't do a CPU draw when it would result in a download.
|
||||
if (GSConfig.UserHacks_GPUTargetCLUTMode != GSGPUTargetCLUTMode::Disabled)
|
||||
{
|
||||
std::vector<GSState::GSUploadQueue>::iterator iter;
|
||||
for (iter = m_draw_transfers.begin(); iter != m_draw_transfers.end(); ) {
|
||||
if (iter->blit.DBP == m_context->TEX0.TBP0 && GSUtil::HasSharedBits(iter->blit.DPSM, m_context->TEX0.PSM))
|
||||
return CLUTDrawTestResult::CLUTDrawOnCPU;
|
||||
|
||||
iter++;
|
||||
}
|
||||
|
||||
GSTextureCache::Target* tgt = m_tc->GetExactTarget(m_context->TEX0.TBP0, m_context->TEX0.TBW, m_context->TEX0.PSM);
|
||||
if (tgt)
|
||||
{
|
||||
bool is_dirty = false;
|
||||
for (const GSDirtyRect& rc : tgt->m_dirty)
|
||||
for (GSDirtyRect& rc : tgt->m_dirty)
|
||||
{
|
||||
if (!rc.GetDirtyRect(m_context->TEX0).rintersect(r).rempty())
|
||||
{
|
||||
@@ -4016,6 +4153,16 @@ GSRendererHW::CLUTDrawTestResult GSRendererHW::PossibleCLUTDraw()
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<GSState::GSUploadQueue>::iterator iter;
|
||||
for (iter = m_draw_transfers.begin(); iter != m_draw_transfers.end(); ) {
|
||||
if (iter->blit.DBP == m_context->TEX0.TBP0 && GSUtil::HasSharedBits(iter->blit.DPSM, m_context->TEX0.PSM))
|
||||
return CLUTDrawTestResult::CLUTDrawOnCPU;
|
||||
|
||||
iter++;
|
||||
}
|
||||
}
|
||||
|
||||
GIFRegBITBLTBUF BITBLTBUF = {};
|
||||
BITBLTBUF.SBP = m_context->TEX0.TBP0;
|
||||
@@ -4212,7 +4359,6 @@ bool GSRendererHW::OI_GsMemClear()
|
||||
const GSOffset& off = m_context->offset.fb;
|
||||
GSVector4i r = GSVector4i(m_vt.m_min.p.xyxy(m_vt.m_max.p)).rintersect(GSVector4i(m_context->scissor.in));
|
||||
|
||||
|
||||
if (r.width() == 32 && ZisFrame)
|
||||
r.z += 32;
|
||||
// Limit the hack to a single full buffer clear. Some games might use severals column to clear a screen
|
||||
@@ -4229,7 +4375,6 @@ bool GSRendererHW::OI_GsMemClear()
|
||||
vert_color &= ~0xFF000000;
|
||||
|
||||
const u32 color = (format == 0) ? vert_color : (vert_color & ~0xFF000000);
|
||||
|
||||
// FIXME: loop can likely be optimized with AVX/SSE. Pixels aren't
|
||||
// linear but the value will be done for all pixels of a block.
|
||||
// FIXME: maybe we could limit the write to the top and bottom row page.
|
||||
@@ -4276,6 +4421,7 @@ bool GSRendererHW::OI_GsMemClear()
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -4339,17 +4485,20 @@ bool GSRendererHW::OI_BlitFMV(GSTextureCache::Target* _rt, GSTextureCache::Sourc
|
||||
// Nothing to see keep going
|
||||
return true;
|
||||
}
|
||||
bool GSRendererHW::IsBlendedOrOpaque()
|
||||
{
|
||||
return (!PRIM->ABE || IsOpaque() || m_context->ALPHA.IsCdOutput());
|
||||
}
|
||||
|
||||
bool GSRendererHW::IsConstantDirectWriteMemClear()
|
||||
bool GSRendererHW::IsConstantDirectWriteMemClear(bool include_zero)
|
||||
{
|
||||
// Constant Direct Write without texture/test/blending (aka a GS mem clear)
|
||||
if ((m_vt.m_primclass == GS_SPRITE_CLASS) && !PRIM->TME // Direct write
|
||||
&& (!PRIM->ABE || IsOpaque() || m_context->ALPHA.IsCdOutput()) // No transparency
|
||||
&& (m_context->FRAME.FBMSK == 0) // no color mask
|
||||
&& (m_context->FRAME.FBMSK == 0 || (include_zero && m_vt.m_max.c.eq(GSVector4i::zero()))) // no color mask
|
||||
&& !(m_env.SCANMSK.MSK & 2)
|
||||
&& !m_context->TEST.ATE // no alpha test
|
||||
&& (!m_context->TEST.ZTE || m_context->TEST.ZTST == ZTST_ALWAYS) // no depth test
|
||||
&& (m_vt.m_eq.rgba == 0xFFFF) // constant color write
|
||||
&& (m_vt.m_eq.rgba == 0xFFFF || m_vertex.next == 2) // constant color write
|
||||
&& m_r.x == 0 && m_r.y == 0) // Likely full buffer write
|
||||
return true;
|
||||
|
||||
|
||||
@@ -65,7 +65,8 @@ private:
|
||||
float alpha1(int L, int X0, int X1);
|
||||
void SwSpriteRender();
|
||||
bool CanUseSwSpriteRender();
|
||||
bool IsConstantDirectWriteMemClear();
|
||||
bool IsConstantDirectWriteMemClear(bool include_zero);
|
||||
bool IsBlendedOrOpaque();
|
||||
|
||||
enum class CLUTDrawTestResult
|
||||
{
|
||||
@@ -139,7 +140,7 @@ public:
|
||||
void Lines2Sprites();
|
||||
bool VerifyIndices();
|
||||
template <GSHWDrawConfig::VSExpand Expand> void ExpandIndices();
|
||||
void ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba);
|
||||
void ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba, bool& swap_ga);
|
||||
GSVector4 RealignTargetTextureCoordinate(const GSTextureCache::Source* tex);
|
||||
GSVector4i ComputeBoundingBox(const GSVector2& rtscale, const GSVector2i& rtsize);
|
||||
void MergeSprite(GSTextureCache::Source* tex);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -44,6 +44,44 @@ public:
|
||||
return valid && overlap;
|
||||
}
|
||||
|
||||
struct SourceRegion
|
||||
{
|
||||
u64 bits;
|
||||
|
||||
bool HasX() const { return static_cast<u32>(bits) != 0; }
|
||||
bool HasY() const { return static_cast<u32>(bits >> 32) != 0; }
|
||||
bool HasEither() const { return (bits != 0); }
|
||||
|
||||
void SetX(u32 min, u32 max) { bits |= (min | (max << 16)); }
|
||||
void SetY(u32 min, u32 max) { bits |= ((static_cast<u64>(min) << 32) | (static_cast<u64>(max) << 48)); }
|
||||
|
||||
u32 GetMinX() const { return static_cast<u32>(bits) & 0xFFFFu; }
|
||||
u32 GetMaxX() const { return static_cast<u32>(bits >> 16) & 0xFFFFu; }
|
||||
u32 GetMinY() const { return static_cast<u32>(bits >> 32) & 0xFFFFu; }
|
||||
u32 GetMaxY() const { return static_cast<u32>(bits >> 48); }
|
||||
|
||||
u32 GetWidth() const { return (GetMaxX() - GetMinX()); }
|
||||
u32 GetHeight() const { return (GetMaxY() - GetMinY()); }
|
||||
|
||||
/// Returns true if the area of the region exceeds the TW/TH size (i.e. "fixed tex0").
|
||||
bool IsFixedTEX0(int tw, int th) const;
|
||||
bool IsFixedTEX0W(int tw) const;
|
||||
bool IsFixedTEX0H(int th) const;
|
||||
|
||||
/// Returns the rectangle relative to the texture base pointer that the region occupies.
|
||||
GSVector4i GetRect(int tw, int th) const;
|
||||
|
||||
/// When TW/TH is less than the extents covered by the region ("fixed tex0"), returns the offset
|
||||
/// which should be applied to any coordinates to relocate them to the actual region.
|
||||
GSVector4i GetOffset(int tw, int th) const;
|
||||
|
||||
/// Reduces the range of texels relative to the specified mipmap level.
|
||||
SourceRegion AdjustForMipmap(u32 level) const;
|
||||
|
||||
/// Adjusts the texture base pointer and block width relative to the region.
|
||||
void AdjustTEX0(GIFRegTEX0* TEX0) const;
|
||||
};
|
||||
|
||||
using HashType = u64;
|
||||
|
||||
struct HashCacheKey
|
||||
@@ -51,10 +89,11 @@ public:
|
||||
HashType TEX0Hash, CLUTHash;
|
||||
GIFRegTEX0 TEX0;
|
||||
GIFRegTEXA TEXA;
|
||||
SourceRegion region;
|
||||
|
||||
HashCacheKey();
|
||||
|
||||
static HashCacheKey Create(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const u32* clut, const GSVector2i* lod);
|
||||
static HashCacheKey Create(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const u32* clut, const GSVector2i* lod, SourceRegion region);
|
||||
|
||||
HashCacheKey WithRemovedCLUTHash() const;
|
||||
void RemoveCLUTHash();
|
||||
@@ -148,7 +187,7 @@ public:
|
||||
{
|
||||
GSVector4i* rect;
|
||||
u32 count;
|
||||
} m_write;
|
||||
} m_write = {};
|
||||
|
||||
void PreloadLevel(int level);
|
||||
|
||||
@@ -161,6 +200,7 @@ public:
|
||||
GSTexture* m_palette;
|
||||
GSVector4i m_valid_rect;
|
||||
GSVector2i m_lod;
|
||||
SourceRegion m_region = {};
|
||||
u8 m_valid_hashes = 0;
|
||||
u8 m_complete_layers = 0;
|
||||
bool m_target;
|
||||
@@ -178,11 +218,13 @@ public:
|
||||
GSOffset::PageLooper m_pages;
|
||||
|
||||
public:
|
||||
Source(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, bool dummy_container = false);
|
||||
Source(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA);
|
||||
virtual ~Source();
|
||||
|
||||
__fi bool CanPreload() const { return CanPreloadTextureSize(m_TEX0.TW, m_TEX0.TH); }
|
||||
|
||||
void SetPages();
|
||||
|
||||
void Update(const GSVector4i& rect, int layer = 0);
|
||||
void UpdateLayer(const GIFRegTEX0& TEX0, const GSVector4i& rect, int layer = 0);
|
||||
|
||||
@@ -198,6 +240,7 @@ public:
|
||||
GSVector4i m_valid;
|
||||
const bool m_depth_supported;
|
||||
bool m_dirty_alpha;
|
||||
bool m_is_frame;
|
||||
|
||||
public:
|
||||
Target(const GIFRegTEX0& TEX0, const bool depth_supported, const int type);
|
||||
@@ -321,7 +364,7 @@ protected:
|
||||
std::unique_ptr<GSDownloadTexture> m_uint16_download_texture;
|
||||
std::unique_ptr<GSDownloadTexture> m_uint32_download_texture;
|
||||
|
||||
Source* CreateSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, Target* t, bool half_right, int x_offset, int y_offset, const GSVector2i* lod, const GSVector4i* src_range, GSTexture* gpu_clut);
|
||||
Source* CreateSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, Target* t, bool half_right, int x_offset, int y_offset, const GSVector2i* lod, const GSVector4i* src_range, GSTexture* gpu_clut, SourceRegion region);
|
||||
Target* CreateTarget(const GIFRegTEX0& TEX0, int w, int h, int type, const bool clear);
|
||||
|
||||
/// Expands a target when the block pointer for a display framebuffer is within another target, but the read offset
|
||||
@@ -331,10 +374,10 @@ protected:
|
||||
/// Resizes the download texture if needed.
|
||||
bool PrepareDownloadTexture(u32 width, u32 height, GSTexture::Format format, std::unique_ptr<GSDownloadTexture>* tex);
|
||||
|
||||
HashCacheEntry* LookupHashCache(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, bool& paltex, const u32* clut, const GSVector2i* lod);
|
||||
HashCacheEntry* LookupHashCache(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, bool& paltex, const u32* clut, const GSVector2i* lod, SourceRegion region);
|
||||
|
||||
static void PreloadTexture(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, GSLocalMemory& mem, bool paltex, GSTexture* tex, u32 level);
|
||||
static HashType HashTexture(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA);
|
||||
static void PreloadTexture(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, SourceRegion region, GSLocalMemory& mem, bool paltex, GSTexture* tex, u32 level);
|
||||
static HashType HashTexture(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, SourceRegion region);
|
||||
|
||||
// TODO: virtual void Write(Source* s, const GSVector4i& r) = 0;
|
||||
// TODO: virtual void Write(Target* t, const GSVector4i& r) = 0;
|
||||
@@ -357,8 +400,8 @@ public:
|
||||
|
||||
GSTexture* LookupPaletteSource(u32 CBP, u32 CPSM, u32 CBW, GSVector2i& offset, const GSVector2i& size);
|
||||
|
||||
Source* LookupSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GSVector4i& r, const GSVector2i* lod);
|
||||
Source* LookupDepthSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GSVector4i& r, bool palette = false);
|
||||
Source* LookupSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GIFRegCLAMP& CLAMP, const GSVector4i& r, const GSVector2i* lod);
|
||||
Source* LookupDepthSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GIFRegCLAMP& CLAMP, const GSVector4i& r, bool palette = false);
|
||||
|
||||
Target* LookupTarget(const GIFRegTEX0& TEX0, const GSVector2i& size, int type, bool used, u32 fbmask = 0, const bool is_frame = false, const int real_w = 0, const int real_h = 0, bool preload = GSConfig.PreloadFrameWithGSData);
|
||||
Target* LookupDisplayTarget(const GIFRegTEX0& TEX0, const GSVector2i& size, const int real_w, const int real_h);
|
||||
|
||||
@@ -42,6 +42,8 @@
|
||||
// this is a #define instead of a variable to avoid warnings from non-literal format strings
|
||||
#define TEXTURE_FILENAME_FORMAT_STRING "%" PRIx64 "-%08x"
|
||||
#define TEXTURE_FILENAME_CLUT_FORMAT_STRING "%" PRIx64 "-%" PRIx64 "-%08x"
|
||||
#define TEXTURE_FILENAME_REGION_FORMAT_STRING "%" PRIx64 "-r%" PRIx64 "-" "-%08x"
|
||||
#define TEXTURE_FILENAME_REGION_CLUT_FORMAT_STRING "%" PRIx64 "-%" PRIx64 "-r%" PRIx64 "-%08x"
|
||||
#define TEXTURE_REPLACEMENT_SUBDIRECTORY_NAME "replacements"
|
||||
#define TEXTURE_DUMP_SUBDIRECTORY_NAME "dumps"
|
||||
|
||||
@@ -51,6 +53,7 @@ namespace
|
||||
{
|
||||
u64 TEX0Hash;
|
||||
u64 CLUTHash;
|
||||
GSTextureCache::SourceRegion region;
|
||||
|
||||
union
|
||||
{
|
||||
@@ -68,9 +71,10 @@ namespace
|
||||
};
|
||||
u32 miplevel;
|
||||
|
||||
__fi u32 Width() const { return (1u << TEX0_TW); }
|
||||
__fi u32 Height() const { return (1u << TEX0_TH); }
|
||||
__fi u32 Width() const { return (region.HasX() ? region.GetWidth() : (1u << TEX0_TW)); }
|
||||
__fi u32 Height() const { return (region.HasY() ? region.GetWidth() : (1u << TEX0_TH)); }
|
||||
__fi bool HasPalette() const { return (GSLocalMemory::m_psm[TEX0_PSM].pal > 0); }
|
||||
__fi bool HasRegion() const { return region.HasEither(); }
|
||||
|
||||
__fi GSVector2 ReplacementScale(const GSTextureReplacements::ReplacementTexture& rtex) const
|
||||
{
|
||||
@@ -79,14 +83,27 @@ namespace
|
||||
|
||||
__fi GSVector2 ReplacementScale(u32 rwidth, u32 rheight) const
|
||||
{
|
||||
return GSVector2(static_cast<float>(rwidth) / static_cast<float>(Width()), static_cast<float>(rheight) / static_cast<float>(Height()));
|
||||
return GSVector2(static_cast<float>(rwidth) / static_cast<float>(Width()),
|
||||
static_cast<float>(rheight) / static_cast<float>(Height()));
|
||||
}
|
||||
|
||||
__fi bool operator==(const TextureName& rhs) const { return std::tie(TEX0Hash, CLUTHash, bits) == std::tie(rhs.TEX0Hash, rhs.CLUTHash, rhs.bits); }
|
||||
__fi bool operator!=(const TextureName& rhs) const { return std::tie(TEX0Hash, CLUTHash, bits) != std::tie(rhs.TEX0Hash, rhs.CLUTHash, rhs.bits); }
|
||||
__fi bool operator<(const TextureName& rhs) const { return std::tie(TEX0Hash, CLUTHash, bits) < std::tie(rhs.TEX0Hash, rhs.CLUTHash, rhs.bits); }
|
||||
__fi bool operator==(const TextureName& rhs) const
|
||||
{
|
||||
return std::tie(TEX0Hash, CLUTHash, region.bits, bits) ==
|
||||
std::tie(rhs.TEX0Hash, rhs.CLUTHash, region.bits, rhs.bits);
|
||||
}
|
||||
__fi bool operator!=(const TextureName& rhs) const
|
||||
{
|
||||
return std::tie(TEX0Hash, CLUTHash, region.bits, bits) !=
|
||||
std::tie(rhs.TEX0Hash, rhs.CLUTHash, region.bits, rhs.bits);
|
||||
}
|
||||
__fi bool operator<(const TextureName& rhs) const
|
||||
{
|
||||
return std::tie(TEX0Hash, CLUTHash, region.bits, bits) <
|
||||
std::tie(rhs.TEX0Hash, rhs.CLUTHash, region.bits, rhs.bits);
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(TextureName) == 24, "ReplacementTextureName is expected size");
|
||||
static_assert(sizeof(TextureName) == 32, "ReplacementTextureName is expected size");
|
||||
} // namespace
|
||||
|
||||
namespace std
|
||||
@@ -97,7 +114,7 @@ namespace std
|
||||
std::size_t operator()(const TextureName& val) const
|
||||
{
|
||||
std::size_t h = 0;
|
||||
HashCombine(h, val.TEX0Hash, val.CLUTHash, val.bits, val.miplevel);
|
||||
HashCombine(h, val.TEX0Hash, val.CLUTHash, val.region.bits, val.bits, val.miplevel);
|
||||
return h;
|
||||
}
|
||||
};
|
||||
@@ -169,6 +186,7 @@ TextureName GSTextureReplacements::CreateTextureName(const GSTextureCache::HashC
|
||||
name.TEX0Hash = hash.TEX0Hash;
|
||||
name.CLUTHash = name.HasPalette() ? hash.CLUTHash : 0;
|
||||
name.miplevel = miplevel;
|
||||
name.region = hash.region;
|
||||
return name;
|
||||
}
|
||||
|
||||
@@ -184,6 +202,7 @@ GSTextureCache::HashCacheKey GSTextureReplacements::HashCacheKeyFromTextureName(
|
||||
key.TEXA.TA1 = tn.TEXA_TA1;
|
||||
key.TEX0Hash = tn.TEX0Hash;
|
||||
key.CLUTHash = tn.HasPalette() ? tn.CLUTHash : 0;
|
||||
key.region = tn.region;
|
||||
return key;
|
||||
}
|
||||
|
||||
@@ -192,15 +211,38 @@ std::optional<TextureName> GSTextureReplacements::ParseReplacementName(const std
|
||||
TextureName ret;
|
||||
ret.miplevel = 0;
|
||||
|
||||
// TODO(Stenzek): Make this better.
|
||||
char extension_dot;
|
||||
if (std::sscanf(filename.c_str(), TEXTURE_FILENAME_CLUT_FORMAT_STRING "%c", &ret.TEX0Hash, &ret.CLUTHash, &ret.bits, &extension_dot) != 4 || extension_dot != '.')
|
||||
if (std::sscanf(filename.c_str(), TEXTURE_FILENAME_REGION_CLUT_FORMAT_STRING "%c", &ret.TEX0Hash, &ret.CLUTHash,
|
||||
&ret.region.bits, &ret.bits, &extension_dot) == 5 &&
|
||||
extension_dot == '.')
|
||||
{
|
||||
if (std::sscanf(filename.c_str(), TEXTURE_FILENAME_FORMAT_STRING "%c", &ret.TEX0Hash, &ret.bits, &extension_dot) != 3 || extension_dot != '.')
|
||||
return std::nullopt;
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
if (std::sscanf(filename.c_str(), TEXTURE_FILENAME_REGION_FORMAT_STRING "%c", &ret.TEX0Hash, &ret.region.bits,
|
||||
&ret.bits, &extension_dot) == 4 &&
|
||||
extension_dot == '.')
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret.region.bits = 0;
|
||||
|
||||
if (std::sscanf(filename.c_str(), TEXTURE_FILENAME_CLUT_FORMAT_STRING "%c", &ret.TEX0Hash, &ret.CLUTHash, &ret.bits,
|
||||
&extension_dot) == 4 &&
|
||||
extension_dot == '.')
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (std::sscanf(filename.c_str(), TEXTURE_FILENAME_FORMAT_STRING "%c", &ret.TEX0Hash, &ret.bits, &extension_dot) ==
|
||||
3 &&
|
||||
extension_dot == '.')
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::string GSTextureReplacements::GetGameTextureDirectory()
|
||||
@@ -229,23 +271,45 @@ std::string GSTextureReplacements::GetDumpFilename(const TextureName& name, u32
|
||||
|
||||
const std::string game_subdir(Path::Combine(game_dir, TEXTURE_DUMP_SUBDIRECTORY_NAME));
|
||||
|
||||
if (name.HasPalette())
|
||||
std::string filename;
|
||||
if (name.HasRegion())
|
||||
{
|
||||
const std::string filename(
|
||||
(level > 0) ?
|
||||
StringUtil::StdStringFromFormat(TEXTURE_FILENAME_CLUT_FORMAT_STRING "-mip%u.png", name.TEX0Hash, name.CLUTHash, name.bits, level) :
|
||||
StringUtil::StdStringFromFormat(TEXTURE_FILENAME_CLUT_FORMAT_STRING ".png", name.TEX0Hash, name.CLUTHash, name.bits));
|
||||
ret = Path::Combine(game_subdir, filename);
|
||||
if (name.HasPalette())
|
||||
{
|
||||
filename = (level > 0) ?
|
||||
StringUtil::StdStringFromFormat(TEXTURE_FILENAME_REGION_CLUT_FORMAT_STRING "-mip%u.png",
|
||||
name.TEX0Hash, name.CLUTHash, name.region.bits, name.bits, level) :
|
||||
StringUtil::StdStringFromFormat(TEXTURE_FILENAME_REGION_CLUT_FORMAT_STRING ".png",
|
||||
name.TEX0Hash, name.CLUTHash, name.region.bits, name.bits);
|
||||
}
|
||||
else
|
||||
{
|
||||
filename = (level > 0) ? StringUtil::StdStringFromFormat(
|
||||
TEXTURE_FILENAME_FORMAT_STRING "-mip%u.png", name.TEX0Hash, name.bits, level) :
|
||||
StringUtil::StdStringFromFormat(
|
||||
TEXTURE_FILENAME_FORMAT_STRING ".png", name.TEX0Hash, name.bits);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::string filename(
|
||||
(level > 0) ?
|
||||
StringUtil::StdStringFromFormat(TEXTURE_FILENAME_FORMAT_STRING "-mip%u.png", name.TEX0Hash, name.bits, level) :
|
||||
StringUtil::StdStringFromFormat(TEXTURE_FILENAME_FORMAT_STRING ".png", name.TEX0Hash, name.bits));
|
||||
ret = Path::Combine(game_subdir, filename);
|
||||
if (name.HasPalette())
|
||||
{
|
||||
filename = (level > 0) ? StringUtil::StdStringFromFormat(TEXTURE_FILENAME_CLUT_FORMAT_STRING "-mip%u.png",
|
||||
name.TEX0Hash, name.CLUTHash, name.bits, level) :
|
||||
StringUtil::StdStringFromFormat(TEXTURE_FILENAME_CLUT_FORMAT_STRING ".png",
|
||||
name.TEX0Hash, name.CLUTHash, name.bits);
|
||||
}
|
||||
else
|
||||
{
|
||||
filename = (level > 0) ? StringUtil::StdStringFromFormat(
|
||||
TEXTURE_FILENAME_FORMAT_STRING "-mip%u.png", name.TEX0Hash, name.bits, level) :
|
||||
StringUtil::StdStringFromFormat(
|
||||
TEXTURE_FILENAME_FORMAT_STRING ".png", name.TEX0Hash, name.bits);
|
||||
}
|
||||
}
|
||||
|
||||
ret = Path::Combine(game_subdir, filename);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -569,7 +633,8 @@ void GSTextureReplacements::ProcessAsyncLoadedTextures()
|
||||
s_async_loaded_textures.clear();
|
||||
}
|
||||
|
||||
void GSTextureReplacements::DumpTexture(const GSTextureCache::HashCacheKey& hash, const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, GSLocalMemory& mem, u32 level)
|
||||
void GSTextureReplacements::DumpTexture(const GSTextureCache::HashCacheKey& hash, const GIFRegTEX0& TEX0,
|
||||
const GIFRegTEXA& TEXA, GSTextureCache::SourceRegion region, GSLocalMemory& mem, u32 level)
|
||||
{
|
||||
// check if it's been dumped or replaced already
|
||||
const TextureName name(CreateTextureName(hash, level));
|
||||
@@ -589,12 +654,12 @@ void GSTextureReplacements::DumpTexture(const GSTextureCache::HashCacheKey& hash
|
||||
// compute width/height
|
||||
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[TEX0.PSM];
|
||||
const GSVector2i& bs = psm.bs;
|
||||
const int tw = 1 << TEX0.TW;
|
||||
const int th = 1 << TEX0.TH;
|
||||
const GSVector4i rect(0, 0, tw, th);
|
||||
const int tw = region.HasX() ? region.GetWidth() : (1 << TEX0.TW);
|
||||
const int th = region.HasY() ? region.GetHeight() : (1 << TEX0.TH);
|
||||
const GSVector4i rect(region.GetRect(tw, th));
|
||||
const GSVector4i block_rect(rect.ralign<Align_Outside>(bs));
|
||||
const int read_width = std::max(tw, psm.bs.x);
|
||||
const int read_height = std::max(th, psm.bs.y);
|
||||
const int read_width = block_rect.width();
|
||||
const int read_height = block_rect.height();
|
||||
const u32 pitch = static_cast<u32>(read_width) * sizeof(u32);
|
||||
|
||||
// use per-texture buffer so we can compress the texture asynchronously and not block the GS thread
|
||||
@@ -603,8 +668,9 @@ void GSTextureReplacements::DumpTexture(const GSTextureCache::HashCacheKey& hash
|
||||
psm.rtx(mem, mem.GetOffset(TEX0.TBP0, TEX0.TBW, TEX0.PSM), block_rect, buffer.GetPtr(), pitch, TEXA);
|
||||
|
||||
// okay, now we can actually dump it
|
||||
QueueWorkerThreadItem([filename = std::move(filename), tw, th, pitch, buffer = std::move(buffer)]() {
|
||||
if (!SavePNGImage(filename.c_str(), tw, th, buffer.GetPtr(), pitch))
|
||||
const u32 buffer_offset = ((rect.top - block_rect.top) * pitch) + ((rect.left - block_rect.left) * sizeof(u32));
|
||||
QueueWorkerThreadItem([filename = std::move(filename), tw, th, pitch, buffer = std::move(buffer), buffer_offset]() {
|
||||
if (!SavePNGImage(filename.c_str(), tw, th, buffer.GetPtr() + buffer_offset, pitch))
|
||||
Console.Error("Failed to dump texture to '%s'.", filename.c_str());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -52,7 +52,8 @@ namespace GSTextureReplacements
|
||||
GSTexture* CreateReplacementTexture(const ReplacementTexture& rtex, const GSVector2& scale, bool mipmap);
|
||||
void ProcessAsyncLoadedTextures();
|
||||
|
||||
void DumpTexture(const GSTextureCache::HashCacheKey& hash, const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, GSLocalMemory& mem, u32 level);
|
||||
void DumpTexture(const GSTextureCache::HashCacheKey& hash, const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA,
|
||||
GSTextureCache::SourceRegion region, GSLocalMemory& mem, u32 level);
|
||||
void ClearDumpedTextureList();
|
||||
|
||||
/// Loader will take a filename and interpret the format (e.g. DDS, PNG, etc).
|
||||
|
||||
@@ -1127,8 +1127,6 @@ void GSDeviceMTL::CopyRect(GSTexture* sTex, GSTexture* dTex, const GSVector4i& r
|
||||
|
||||
void GSDeviceMTL::DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, id<MTLRenderPipelineState> pipeline, bool linear, LoadAction load_action, void* frag_uniform, size_t frag_uniform_len)
|
||||
{
|
||||
BeginScene();
|
||||
|
||||
FlushClears(sTex);
|
||||
|
||||
GSTextureMTL* sT = static_cast<GSTextureMTL*>(sTex);
|
||||
@@ -1164,8 +1162,6 @@ void GSDeviceMTL::DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTextu
|
||||
MRESetSampler(linear ? SamplerSelector::Linear() : SamplerSelector::Point());
|
||||
|
||||
DrawStretchRect(sRect, dRect, ds);
|
||||
|
||||
EndScene();
|
||||
}
|
||||
|
||||
void GSDeviceMTL::DrawStretchRect(const GSVector4& sRect, const GSVector4& dRect, const GSVector2i& ds)
|
||||
@@ -1378,6 +1374,8 @@ void GSDeviceMTL::MRESetHWPipelineState(GSHWDrawConfig::VSSelector vssel, GSHWDr
|
||||
setFnConstantB(m_fn_constants, pssel.tcc, GSMTLConstantIndex_PS_TCC);
|
||||
setFnConstantI(m_fn_constants, pssel.wms, GSMTLConstantIndex_PS_WMS);
|
||||
setFnConstantI(m_fn_constants, pssel.wmt, GSMTLConstantIndex_PS_WMT);
|
||||
setFnConstantB(m_fn_constants, pssel.adjs, GSMTLConstantIndex_PS_ADJS);
|
||||
setFnConstantB(m_fn_constants, pssel.adjt, GSMTLConstantIndex_PS_ADJT);
|
||||
setFnConstantB(m_fn_constants, pssel.ltf, GSMTLConstantIndex_PS_LTF);
|
||||
setFnConstantB(m_fn_constants, pssel.shuffle, GSMTLConstantIndex_PS_SHUFFLE);
|
||||
setFnConstantB(m_fn_constants, pssel.read_ba, GSMTLConstantIndex_PS_READ_BA);
|
||||
@@ -1407,7 +1405,6 @@ void GSDeviceMTL::MRESetHWPipelineState(GSHWDrawConfig::VSSelector vssel, GSHWDr
|
||||
setFnConstantB(m_fn_constants, pssel.automatic_lod, GSMTLConstantIndex_PS_AUTOMATIC_LOD);
|
||||
setFnConstantB(m_fn_constants, pssel.manual_lod, GSMTLConstantIndex_PS_MANUAL_LOD);
|
||||
setFnConstantB(m_fn_constants, pssel.point_sampler, GSMTLConstantIndex_PS_POINT_SAMPLER);
|
||||
setFnConstantB(m_fn_constants, pssel.invalid_tex0, GSMTLConstantIndex_PS_INVALID_TEX0);
|
||||
setFnConstantI(m_fn_constants, pssel.scanmsk, GSMTLConstantIndex_PS_SCANMSK);
|
||||
auto newps = LoadShader(@"ps_main");
|
||||
ps = newps;
|
||||
@@ -1598,10 +1595,10 @@ static_assert(offsetof(GSHWDrawConfig::PSConstantBuffer, WH) == of
|
||||
static_assert(offsetof(GSHWDrawConfig::PSConstantBuffer, TA_MaxDepth_Af.x) == offsetof(GSMTLMainPSUniform, ta));
|
||||
static_assert(offsetof(GSHWDrawConfig::PSConstantBuffer, TA_MaxDepth_Af.z) == offsetof(GSMTLMainPSUniform, max_depth));
|
||||
static_assert(offsetof(GSHWDrawConfig::PSConstantBuffer, TA_MaxDepth_Af.w) == offsetof(GSMTLMainPSUniform, alpha_fix));
|
||||
static_assert(offsetof(GSHWDrawConfig::PSConstantBuffer, MskFix) == offsetof(GSMTLMainPSUniform, uv_msk_fix));
|
||||
static_assert(offsetof(GSHWDrawConfig::PSConstantBuffer, FbMask) == offsetof(GSMTLMainPSUniform, fbmask));
|
||||
static_assert(offsetof(GSHWDrawConfig::PSConstantBuffer, HalfTexel) == offsetof(GSMTLMainPSUniform, half_texel));
|
||||
static_assert(offsetof(GSHWDrawConfig::PSConstantBuffer, MinMax) == offsetof(GSMTLMainPSUniform, uv_min_max));
|
||||
static_assert(offsetof(GSHWDrawConfig::PSConstantBuffer, STRange) == offsetof(GSMTLMainPSUniform, st_range));
|
||||
static_assert(offsetof(GSHWDrawConfig::PSConstantBuffer, ChannelShuffle) == offsetof(GSMTLMainPSUniform, channel_shuffle));
|
||||
static_assert(offsetof(GSHWDrawConfig::PSConstantBuffer, TCOffsetHack) == offsetof(GSMTLMainPSUniform, tc_offset));
|
||||
static_assert(offsetof(GSHWDrawConfig::PSConstantBuffer, STScale) == offsetof(GSMTLMainPSUniform, st_scale));
|
||||
@@ -1609,7 +1606,6 @@ static_assert(offsetof(GSHWDrawConfig::PSConstantBuffer, DitherMatrix) == of
|
||||
|
||||
void GSDeviceMTL::SetupDestinationAlpha(GSTexture* rt, GSTexture* ds, const GSVector4i& r, bool datm)
|
||||
{
|
||||
BeginScene();
|
||||
FlushClears(rt);
|
||||
BeginRenderPass(@"Destination Alpha Setup", nullptr, MTLLoadActionDontCare, nullptr, MTLLoadActionDontCare, ds, MTLLoadActionDontCare);
|
||||
[m_current_render.encoder setStencilReferenceValue:1];
|
||||
@@ -1617,7 +1613,6 @@ void GSDeviceMTL::SetupDestinationAlpha(GSTexture* rt, GSTexture* ds, const GSVe
|
||||
RenderCopy(nullptr, m_stencil_clear_pipeline, r);
|
||||
MRESetDSS(m_dss_stencil_write);
|
||||
RenderCopy(rt, m_datm_pipeline[datm], r);
|
||||
EndScene();
|
||||
}
|
||||
|
||||
static id<MTLTexture> getTexture(GSTexture* tex)
|
||||
@@ -1696,7 +1691,6 @@ void GSDeviceMTL::RenderHW(GSHWDrawConfig& config)
|
||||
break;
|
||||
}
|
||||
|
||||
BeginScene();
|
||||
GSTexture* hdr_rt = nullptr;
|
||||
if (config.ps.hdr)
|
||||
{
|
||||
|
||||
@@ -108,11 +108,15 @@ struct GSMTLMainPSUniform
|
||||
vector_float2 ta;
|
||||
float max_depth;
|
||||
float alpha_fix;
|
||||
vector_uint4 uv_msk_fix;
|
||||
vector_uint4 fbmask;
|
||||
|
||||
vector_float4 half_texel;
|
||||
vector_float4 uv_min_max;
|
||||
union
|
||||
{
|
||||
vector_float4 uv_min_max;
|
||||
vector_uint4 uv_msk_fix;
|
||||
};
|
||||
vector_float4 st_range;
|
||||
struct
|
||||
{
|
||||
unsigned int blue_mask;
|
||||
@@ -166,6 +170,8 @@ enum GSMTLFnConstants
|
||||
GSMTLConstantIndex_PS_TCC,
|
||||
GSMTLConstantIndex_PS_WMS,
|
||||
GSMTLConstantIndex_PS_WMT,
|
||||
GSMTLConstantIndex_PS_ADJS,
|
||||
GSMTLConstantIndex_PS_ADJT,
|
||||
GSMTLConstantIndex_PS_LTF,
|
||||
GSMTLConstantIndex_PS_SHUFFLE,
|
||||
GSMTLConstantIndex_PS_READ_BA,
|
||||
@@ -194,6 +200,5 @@ enum GSMTLFnConstants
|
||||
GSMTLConstantIndex_PS_AUTOMATIC_LOD,
|
||||
GSMTLConstantIndex_PS_MANUAL_LOD,
|
||||
GSMTLConstantIndex_PS_POINT_SAMPLER,
|
||||
GSMTLConstantIndex_PS_INVALID_TEX0,
|
||||
GSMTLConstantIndex_PS_SCANMSK,
|
||||
};
|
||||
|
||||
@@ -37,6 +37,8 @@ constant uint PS_TFX [[function_constant(GSMTLConstantIndex_PS_TF
|
||||
constant bool PS_TCC [[function_constant(GSMTLConstantIndex_PS_TCC)]];
|
||||
constant uint PS_WMS [[function_constant(GSMTLConstantIndex_PS_WMS)]];
|
||||
constant uint PS_WMT [[function_constant(GSMTLConstantIndex_PS_WMT)]];
|
||||
constant bool PS_ADJS [[function_constant(GSMTLConstantIndex_PS_ADJS)]];
|
||||
constant bool PS_ADJT [[function_constant(GSMTLConstantIndex_PS_ADJT)]];
|
||||
constant bool PS_LTF [[function_constant(GSMTLConstantIndex_PS_LTF)]];
|
||||
constant bool PS_SHUFFLE [[function_constant(GSMTLConstantIndex_PS_SHUFFLE)]];
|
||||
constant bool PS_READ_BA [[function_constant(GSMTLConstantIndex_PS_READ_BA)]];
|
||||
@@ -65,7 +67,6 @@ constant bool PS_TEX_IS_FB [[function_constant(GSMTLConstantIndex_PS_TE
|
||||
constant bool PS_AUTOMATIC_LOD [[function_constant(GSMTLConstantIndex_PS_AUTOMATIC_LOD)]];
|
||||
constant bool PS_MANUAL_LOD [[function_constant(GSMTLConstantIndex_PS_MANUAL_LOD)]];
|
||||
constant bool PS_POINT_SAMPLER [[function_constant(GSMTLConstantIndex_PS_POINT_SAMPLER)]];
|
||||
constant bool PS_INVALID_TEX0 [[function_constant(GSMTLConstantIndex_PS_INVALID_TEX0)]];
|
||||
constant uint PS_SCANMSK [[function_constant(GSMTLConstantIndex_PS_SCANMSK)]];
|
||||
|
||||
constant GSMTLExpandType VS_EXPAND_TYPE = static_cast<GSMTLExpandType>(VS_EXPAND_TYPE_RAW);
|
||||
@@ -321,7 +322,21 @@ struct PSMain
|
||||
// As of 2018 this issue is still present.
|
||||
uv = (trunc(uv * cb.wh.zw) + 0.5) / cb.wh.zw;
|
||||
}
|
||||
uv *= cb.st_scale;
|
||||
if (!PS_ADJS && !PS_ADJT)
|
||||
{
|
||||
uv *= cb.st_scale;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (PS_ADJS)
|
||||
uv.x = (uv.x - cb.st_range.x) * cb.st_range.z;
|
||||
else
|
||||
uv.x = uv.x * cb.st_scale.x;
|
||||
if (PS_ADJT)
|
||||
uv.y = (uv.y - cb.st_range.y) * cb.st_range.w;
|
||||
else
|
||||
uv.y = uv.y * cb.st_scale.y;
|
||||
}
|
||||
|
||||
if (PS_AUTOMATIC_LOD)
|
||||
{
|
||||
@@ -360,7 +375,7 @@ struct PSMain
|
||||
float4 clamp_wrap_uv(float4 uv)
|
||||
{
|
||||
float4 uv_out = uv;
|
||||
float4 tex_size = PS_INVALID_TEX0 ? cb.wh.zwzw : cb.wh.xyxy;
|
||||
float4 tex_size = cb.wh.xyxy;
|
||||
|
||||
if (PS_WMS == PS_WMT)
|
||||
{
|
||||
@@ -724,12 +739,7 @@ struct PSMain
|
||||
float4 ps_color()
|
||||
{
|
||||
float2 st, st_int;
|
||||
if (!FST && PS_INVALID_TEX0)
|
||||
{
|
||||
st = (in.t.xy * cb.wh.xy) / (in.t.w * cb.wh.zw);
|
||||
st_int = (in.ti.zw * cb.wh.xy) / (in.t.w * cb.wh.zw);
|
||||
}
|
||||
else if (!FST)
|
||||
if (!FST)
|
||||
{
|
||||
st = in.t.xy / in.t.w;
|
||||
st_int = in.ti.zw / in.t.w;
|
||||
|
||||
@@ -1029,6 +1029,8 @@ std::string GSDeviceOGL::GetPSSource(const PSSelector& sel)
|
||||
std::string macro = fmt::format("#define PS_FST {}\n", sel.fst)
|
||||
+ fmt::format("#define PS_WMS {}\n", sel.wms)
|
||||
+ fmt::format("#define PS_WMT {}\n", sel.wmt)
|
||||
+ fmt::format("#define PS_ADJS {}\n", sel.adjs)
|
||||
+ fmt::format("#define PS_ADJT {}\n", sel.adjt)
|
||||
+ fmt::format("#define PS_AEM_FMT {}\n", sel.aem_fmt)
|
||||
+ fmt::format("#define PS_PAL_FMT {}\n", sel.pal_fmt)
|
||||
+ fmt::format("#define PS_DFMT {}\n", sel.dfmt)
|
||||
@@ -1037,7 +1039,6 @@ std::string GSDeviceOGL::GetPSSource(const PSSelector& sel)
|
||||
+ fmt::format("#define PS_URBAN_CHAOS_HLE {}\n", sel.urban_chaos_hle)
|
||||
+ fmt::format("#define PS_TALES_OF_ABYSS_HLE {}\n", sel.tales_of_abyss_hle)
|
||||
+ fmt::format("#define PS_TEX_IS_FB {}\n", sel.tex_is_fb)
|
||||
+ fmt::format("#define PS_INVALID_TEX0 {}\n", sel.invalid_tex0)
|
||||
+ fmt::format("#define PS_AEM {}\n", sel.aem)
|
||||
+ fmt::format("#define PS_TFX {}\n", sel.tfx)
|
||||
+ fmt::format("#define PS_TCC {}\n", sel.tcc)
|
||||
@@ -1093,7 +1094,6 @@ void GSDeviceOGL::BlitRect(GSTexture* sTex, const GSVector4i& r, const GSVector2
|
||||
|
||||
const GSVector4 float_r(r);
|
||||
|
||||
BeginScene();
|
||||
m_convert.ps[static_cast<int>(ShaderConvert::COPY)].Bind();
|
||||
OMSetDepthStencilState(m_convert.dss);
|
||||
OMSetBlendState();
|
||||
@@ -1101,7 +1101,6 @@ void GSDeviceOGL::BlitRect(GSTexture* sTex, const GSVector4i& r, const GSVector2
|
||||
PSSetShaderResource(0, sTex);
|
||||
PSSetSamplerState(linear ? m_convert.ln : m_convert.pt);
|
||||
DrawStretchRect(float_r / (GSVector4(sTex->GetSize()).xyxy()), float_r, dsize);
|
||||
EndScene();
|
||||
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
}
|
||||
@@ -1190,8 +1189,6 @@ void GSDeviceOGL::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture
|
||||
// Init
|
||||
// ************************************
|
||||
|
||||
BeginScene();
|
||||
|
||||
GL_PUSH("StretchRect from %d to %d", sTex->GetID(), dTex->GetID());
|
||||
if (draw_in_depth)
|
||||
OMSetRenderTargets(NULL, dTex);
|
||||
@@ -1223,20 +1220,12 @@ void GSDeviceOGL::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture
|
||||
// Draw
|
||||
// ************************************
|
||||
DrawStretchRect(sRect, dRect, dTex->GetSize());
|
||||
|
||||
// ************************************
|
||||
// End
|
||||
// ************************************
|
||||
|
||||
EndScene();
|
||||
}
|
||||
|
||||
void GSDeviceOGL::PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, PresentShader shader, float shaderTime, bool linear)
|
||||
{
|
||||
ASSERT(sTex);
|
||||
|
||||
BeginScene();
|
||||
|
||||
const GSVector2i ds(dTex ? dTex->GetSize() : GSVector2i(g_host_display->GetWindowWidth(), g_host_display->GetWindowHeight()));
|
||||
DisplayConstantBuffer cb;
|
||||
cb.SetSource(sRect, sTex->GetSize());
|
||||
@@ -1269,14 +1258,10 @@ void GSDeviceOGL::PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture
|
||||
// Only flipping the backbuffer is transparent (I hope)...
|
||||
const GSVector4 flip_sr(sRect.xwzy());
|
||||
DrawStretchRect(flip_sr, dRect, ds);
|
||||
|
||||
EndScene();
|
||||
}
|
||||
|
||||
void GSDeviceOGL::UpdateCLUTTexture(GSTexture* sTex, u32 offsetX, u32 offsetY, GSTexture* dTex, u32 dOffset, u32 dSize)
|
||||
{
|
||||
BeginScene();
|
||||
|
||||
const ShaderConvert shader = (dSize == 16) ? ShaderConvert::CLUT_4 : ShaderConvert::CLUT_8;
|
||||
GL::Program& prog = m_convert.ps[static_cast<int>(shader)];
|
||||
prog.Bind();
|
||||
@@ -1293,8 +1278,6 @@ void GSDeviceOGL::UpdateCLUTTexture(GSTexture* sTex, u32 offsetX, u32 offsetY, G
|
||||
|
||||
const GSVector4 dRect(0, 0, dSize, 1);
|
||||
DrawStretchRect(GSVector4::zero(), dRect, dTex->GetSize());
|
||||
|
||||
EndScene();
|
||||
}
|
||||
|
||||
void GSDeviceOGL::DrawStretchRect(const GSVector4& sRect, const GSVector4& dRect, const GSVector2i& ds)
|
||||
@@ -1458,8 +1441,6 @@ void GSDeviceOGL::SetupDATE(GSTexture* rt, GSTexture* ds, const GSVertexPT1* ver
|
||||
|
||||
// sfex3 (after the capcom logo), vf4 (first menu fading in), ffxii shadows, rumble roses shadows, persona4 shadows
|
||||
|
||||
BeginScene();
|
||||
|
||||
ClearStencil(ds, 0);
|
||||
|
||||
m_convert.ps[static_cast<int>(datm ? ShaderConvert::DATM_1 : ShaderConvert::DATM_0)].Bind();
|
||||
@@ -1490,8 +1471,6 @@ void GSDeviceOGL::SetupDATE(GSTexture* rt, GSTexture* ds, const GSVertexPT1* ver
|
||||
{
|
||||
glEnable(GL_BLEND);
|
||||
}
|
||||
|
||||
EndScene();
|
||||
}
|
||||
|
||||
void GSDeviceOGL::IASetVertexBuffer(const void* vertices, size_t count)
|
||||
@@ -1894,8 +1873,6 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
|
||||
CopyRect(config.rt, draw_rt_clone, config.drawarea, config.drawarea.left, config.drawarea.top);
|
||||
}
|
||||
|
||||
BeginScene();
|
||||
|
||||
IASetVertexBuffer(config.verts, config.nverts);
|
||||
IASetIndexBuffer(config.indices, config.nindices);
|
||||
GLenum topology = 0;
|
||||
@@ -2047,10 +2024,6 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
|
||||
if (draw_rt_clone)
|
||||
Recycle(draw_rt_clone);
|
||||
|
||||
EndScene();
|
||||
|
||||
// Warning: EndScene must be called before StretchRect otherwise
|
||||
// vertices will be overwritten. Trust me you don't want to do that.
|
||||
if (hdr_rt)
|
||||
{
|
||||
GSVector2i size = config.rt->GetSize();
|
||||
|
||||
@@ -21,10 +21,17 @@
|
||||
#include "GS/GSPerfMon.h"
|
||||
#include "GS/GSPng.h"
|
||||
#include "GS/GSGL.h"
|
||||
#include "common/Align.h"
|
||||
#include "common/AlignedMalloc.h"
|
||||
#include "common/StringUtil.h"
|
||||
|
||||
static constexpr u32 TEXTURE_UPLOAD_ALIGNMENT = 256;
|
||||
// Looking across a range of GPUs, the optimal copy alignment for Vulkan drivers seems
|
||||
// to be between 1 (AMD/NV) and 64 (Intel). So, we'll go with 64 here.
|
||||
static constexpr u32 TEXTURE_UPLOAD_ALIGNMENT = 64;
|
||||
|
||||
// The pitch alignment must be less or equal to the upload alignment.
|
||||
// We need 32 here for AVX2, so 64 is also fine.
|
||||
static constexpr u32 TEXTURE_UPLOAD_PITCH_ALIGNMENT = 64;
|
||||
|
||||
GSTextureOGL::GSTextureOGL(Type type, int width, int height, int levels, Format format)
|
||||
{
|
||||
@@ -214,8 +221,8 @@ bool GSTextureOGL::Update(const GSVector4i& r, const void* data, int pitch, int
|
||||
|
||||
m_clean = false;
|
||||
|
||||
u32 row_byte = r.width() << m_int_shift;
|
||||
u32 map_size = r.height() * row_byte;
|
||||
const u32 preferred_pitch = Common::AlignUpPow2(r.width() << m_int_shift, TEXTURE_UPLOAD_PITCH_ALIGNMENT);
|
||||
const u32 map_size = r.height() * preferred_pitch;
|
||||
|
||||
#if 0
|
||||
if (r.height() == 1) {
|
||||
@@ -250,13 +257,18 @@ bool GSTextureOGL::Update(const GSVector4i& r, const void* data, int pitch, int
|
||||
GL::StreamBuffer* const sb = GSDeviceOGL::GetTextureUploadBuffer();
|
||||
|
||||
const auto map = sb->Map(TEXTURE_UPLOAD_ALIGNMENT, map_size);
|
||||
StringUtil::StrideMemCpy(map.pointer, row_byte, data, pitch, row_byte, r.height());
|
||||
StringUtil::StrideMemCpy(map.pointer, preferred_pitch, data, pitch, r.width() << m_int_shift, r.height());
|
||||
sb->Unmap(map_size);
|
||||
sb->Bind();
|
||||
|
||||
const u32 row_length = CalcUploadRowLengthFromPitch(preferred_pitch);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, row_length);
|
||||
|
||||
glTextureSubImage2D(m_texture_id, layer, r.x, r.y, r.width(), r.height(), m_int_format, m_int_type,
|
||||
reinterpret_cast<void*>(static_cast<uintptr_t>(map.buffer_offset)));
|
||||
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
|
||||
sb->Unbind();
|
||||
}
|
||||
|
||||
@@ -275,13 +287,13 @@ bool GSTextureOGL::Map(GSMap& m, const GSVector4i* _r, int layer)
|
||||
ASSERT(r.width() != 0);
|
||||
ASSERT(r.height() != 0);
|
||||
|
||||
u32 row_byte = r.width() << m_int_shift;
|
||||
m.pitch = row_byte;
|
||||
const u32 pitch = Common::AlignUpPow2(r.width() << m_int_shift, TEXTURE_UPLOAD_PITCH_ALIGNMENT);
|
||||
m.pitch = pitch;
|
||||
|
||||
if (m_type == Type::Texture || m_type == Type::RenderTarget)
|
||||
{
|
||||
const u32 map_size = r.height() * row_byte;
|
||||
if (GLLoader::buggy_pbo || map_size > GSDeviceOGL::GetTextureUploadBuffer()->GetChunkSize())
|
||||
const u32 upload_size = CalcUploadSize(r.height(), pitch);
|
||||
if (GLLoader::buggy_pbo || upload_size > GSDeviceOGL::GetTextureUploadBuffer()->GetChunkSize())
|
||||
return false;
|
||||
|
||||
GL_PUSH_("Upload Texture %d", m_texture_id); // POP is in Unmap
|
||||
@@ -289,7 +301,7 @@ bool GSTextureOGL::Map(GSMap& m, const GSVector4i* _r, int layer)
|
||||
|
||||
m_clean = false;
|
||||
|
||||
const auto map = GSDeviceOGL::GetTextureUploadBuffer()->Map(TEXTURE_UPLOAD_ALIGNMENT, map_size);
|
||||
const auto map = GSDeviceOGL::GetTextureUploadBuffer()->Map(TEXTURE_UPLOAD_ALIGNMENT, upload_size);
|
||||
m.bits = static_cast<u8*>(map.pointer);
|
||||
|
||||
// Save the area for the unmap
|
||||
@@ -310,14 +322,20 @@ void GSTextureOGL::Unmap()
|
||||
{
|
||||
if (m_type == Type::Texture || m_type == Type::RenderTarget)
|
||||
{
|
||||
const u32 map_size = (m_r_w << m_int_shift) * m_r_h;
|
||||
const u32 pitch = Common::AlignUpPow2(m_r_w << m_int_shift, TEXTURE_UPLOAD_PITCH_ALIGNMENT);
|
||||
const u32 upload_size = pitch * m_r_h;
|
||||
GL::StreamBuffer* sb = GSDeviceOGL::GetTextureUploadBuffer();
|
||||
sb->Unmap(map_size);
|
||||
sb->Unmap(upload_size);
|
||||
sb->Bind();
|
||||
|
||||
const u32 row_length = CalcUploadRowLengthFromPitch(pitch);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, row_length);
|
||||
|
||||
glTextureSubImage2D(m_texture_id, m_layer, m_r_x, m_r_y, m_r_w, m_r_h, m_int_format, m_int_type,
|
||||
reinterpret_cast<void*>(static_cast<uintptr_t>(m_map_offset)));
|
||||
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
|
||||
sb->Unbind();
|
||||
|
||||
m_needs_mipmaps_generated = true;
|
||||
@@ -494,11 +512,13 @@ void GSDownloadTextureOGL::CopyFromTexture(
|
||||
pxAssert((drc.left == 0 && drc.top == 0) || !use_transfer_pitch);
|
||||
|
||||
u32 copy_offset, copy_size, copy_rows;
|
||||
m_current_pitch =
|
||||
GetTransferPitch(use_transfer_pitch ? static_cast<u32>(drc.width()) : m_width, GSTexture::GetCompressedBytesPerBlock(m_format));
|
||||
m_current_pitch = GetTransferPitch(use_transfer_pitch ? static_cast<u32>(drc.width()) : m_width, TEXTURE_UPLOAD_PITCH_ALIGNMENT);
|
||||
GetTransferSize(drc, ©_offset, ©_size, ©_rows);
|
||||
g_perfmon.Put(GSPerfMon::Readbacks, 1);
|
||||
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, GSDeviceOGL::GetInstance()->GetFBORead());
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, glTex->GetID(), 0);
|
||||
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 1u << glTex->GetIntShift());
|
||||
glPixelStorei(GL_PACK_ROW_LENGTH, GSTexture::CalcUploadRowLengthFromPitch(m_format, m_current_pitch));
|
||||
|
||||
@@ -508,10 +528,7 @@ void GSDownloadTextureOGL::CopyFromTexture(
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, m_buffer_id);
|
||||
}
|
||||
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, GSDeviceOGL::GetInstance()->GetFBORead());
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, glTex->GetID(), 0);
|
||||
|
||||
glReadPixels(drc.left, drc.top, drc.width(), drc.height(), glTex->GetIntFormat(), glTex->GetIntType(), m_cpu_buffer);
|
||||
glReadPixels(src.left, src.top, src.width(), src.height(), glTex->GetIntFormat(), glTex->GetIntType(), m_cpu_buffer + copy_offset);
|
||||
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
||||
|
||||
@@ -522,7 +539,7 @@ void GSDownloadTextureOGL::CopyFromTexture(
|
||||
}
|
||||
else
|
||||
{
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
|
||||
// Create a sync object so we know when the GPU is done copying.
|
||||
if (m_sync)
|
||||
|
||||
@@ -119,6 +119,7 @@ void GSRendererSW::VSync(u32 field, bool registers_written)
|
||||
|
||||
m_tc->IncAge();
|
||||
|
||||
m_draw_transfers.clear();
|
||||
// if((m_perfmon.GetFrame() & 255) == 0) m_rl->PrintStats();
|
||||
}
|
||||
|
||||
@@ -244,7 +245,7 @@ void GSVertexSW::InitStatic()
|
||||
MULTI_ISA_UNSHARED_START
|
||||
|
||||
template <u32 primclass, u32 tme, u32 fst, u32 q_div>
|
||||
void ConvertVertexBuffer(const GSDrawingContext* RESTRICT ctx, GSVertexSW* RESTRICT dst, const GSVertex* RESTRICT src, size_t count)
|
||||
void ConvertVertexBuffer(const GSDrawingContext* RESTRICT ctx, GSVertexSW* RESTRICT dst, const GSVertex* RESTRICT src, u32 count)
|
||||
{
|
||||
// FIXME q_div wasn't added to AVX2 code path.
|
||||
|
||||
|
||||
@@ -246,7 +246,7 @@ struct alignas(32) GSVertexSW
|
||||
#endif
|
||||
}
|
||||
|
||||
typedef void (*ConvertVertexBufferPtr)(const GSDrawingContext* RESTRICT ctx, GSVertexSW* RESTRICT dst, const GSVertex* RESTRICT src, size_t count);
|
||||
typedef void (*ConvertVertexBufferPtr)(const GSDrawingContext* RESTRICT ctx, GSVertexSW* RESTRICT dst, const GSVertex* RESTRICT src, u32 count);
|
||||
|
||||
static ConvertVertexBufferPtr s_cvb[4][2][2][2];
|
||||
|
||||
|
||||
@@ -251,6 +251,9 @@ bool GSDeviceVK::CheckFeatures()
|
||||
if (!m_features.texture_barrier)
|
||||
Console.Warning("Texture buffers are disabled. This may break some graphical effects.");
|
||||
|
||||
if (!g_vulkan_context->GetOptionalExtensions().vk_ext_line_rasterization)
|
||||
Console.WriteLn("VK_EXT_line_rasterization or the BRESENHAM mode is not supported, this may cause rendering inaccuracies.");
|
||||
|
||||
// Test for D32S8 support.
|
||||
{
|
||||
VkFormatProperties props = {};
|
||||
@@ -882,8 +885,6 @@ void GSDeviceVK::IASetVertexBuffer(const void* vertex, size_t stride, size_t cou
|
||||
}
|
||||
|
||||
m_vertex.start = m_vertex_stream_buffer.GetCurrentOffset() / stride;
|
||||
m_vertex.limit = count;
|
||||
m_vertex.stride = stride;
|
||||
m_vertex.count = count;
|
||||
SetVertexBuffer(m_vertex_stream_buffer.GetBuffer(), 0);
|
||||
|
||||
@@ -891,32 +892,6 @@ void GSDeviceVK::IASetVertexBuffer(const void* vertex, size_t stride, size_t cou
|
||||
m_vertex_stream_buffer.CommitMemory(size);
|
||||
}
|
||||
|
||||
bool GSDeviceVK::IAMapVertexBuffer(void** vertex, size_t stride, size_t count)
|
||||
{
|
||||
const u32 size = static_cast<u32>(stride) * static_cast<u32>(count);
|
||||
if (!m_vertex_stream_buffer.ReserveMemory(size, static_cast<u32>(stride)))
|
||||
{
|
||||
ExecuteCommandBufferAndRestartRenderPass(false, "Mapping bytes to vertex buffer");
|
||||
if (!m_vertex_stream_buffer.ReserveMemory(size, static_cast<u32>(stride)))
|
||||
pxFailRel("Failed to reserve space for vertices");
|
||||
}
|
||||
|
||||
m_vertex.start = m_vertex_stream_buffer.GetCurrentOffset() / stride;
|
||||
m_vertex.limit = m_vertex_stream_buffer.GetCurrentSpace() / stride;
|
||||
m_vertex.stride = stride;
|
||||
m_vertex.count = count;
|
||||
SetVertexBuffer(m_vertex_stream_buffer.GetBuffer(), 0);
|
||||
|
||||
*vertex = m_vertex_stream_buffer.GetCurrentHostPointer();
|
||||
return true;
|
||||
}
|
||||
|
||||
void GSDeviceVK::IAUnmapVertexBuffer()
|
||||
{
|
||||
const u32 size = static_cast<u32>(m_vertex.stride) * static_cast<u32>(m_vertex.count);
|
||||
m_vertex_stream_buffer.CommitMemory(size);
|
||||
}
|
||||
|
||||
void GSDeviceVK::IASetIndexBuffer(const void* index, size_t count)
|
||||
{
|
||||
const u32 size = sizeof(u32) * static_cast<u32>(count);
|
||||
@@ -928,7 +903,6 @@ void GSDeviceVK::IASetIndexBuffer(const void* index, size_t count)
|
||||
}
|
||||
|
||||
m_index.start = m_index_stream_buffer.GetCurrentOffset() / sizeof(u32);
|
||||
m_index.limit = count;
|
||||
m_index.count = count;
|
||||
SetIndexBuffer(m_index_stream_buffer.GetBuffer(), 0, VK_INDEX_TYPE_UINT32);
|
||||
|
||||
@@ -1977,6 +1951,8 @@ VkShaderModule GSDeviceVK::GetTFXFragmentShader(const GSHWDrawConfig::PSSelector
|
||||
AddMacro(ss, "PS_FST", sel.fst);
|
||||
AddMacro(ss, "PS_WMS", sel.wms);
|
||||
AddMacro(ss, "PS_WMT", sel.wmt);
|
||||
AddMacro(ss, "PS_ADJS", sel.adjs);
|
||||
AddMacro(ss, "PS_ADJT", sel.adjt);
|
||||
AddMacro(ss, "PS_AEM_FMT", sel.aem_fmt);
|
||||
AddMacro(ss, "PS_PAL_FMT", sel.pal_fmt);
|
||||
AddMacro(ss, "PS_DFMT", sel.dfmt);
|
||||
@@ -1984,7 +1960,6 @@ VkShaderModule GSDeviceVK::GetTFXFragmentShader(const GSHWDrawConfig::PSSelector
|
||||
AddMacro(ss, "PS_CHANNEL_FETCH", sel.channel);
|
||||
AddMacro(ss, "PS_URBAN_CHAOS_HLE", sel.urban_chaos_hle);
|
||||
AddMacro(ss, "PS_TALES_OF_ABYSS_HLE", sel.tales_of_abyss_hle);
|
||||
AddMacro(ss, "PS_INVALID_TEX0", sel.invalid_tex0);
|
||||
AddMacro(ss, "PS_AEM", sel.aem);
|
||||
AddMacro(ss, "PS_TFX", sel.tfx);
|
||||
AddMacro(ss, "PS_TCC", sel.tcc);
|
||||
@@ -2021,6 +1996,7 @@ VkShaderModule GSDeviceVK::GetTFXFragmentShader(const GSHWDrawConfig::PSSelector
|
||||
AddMacro(ss, "PS_NO_COLOR1", sel.no_color1);
|
||||
AddMacro(ss, "PS_NO_ABLEND", sel.no_ablend);
|
||||
AddMacro(ss, "PS_ONLY_ALPHA", sel.only_alpha);
|
||||
AddMacro(ss, "PS_SWAP_GA", sel.swap_ga);
|
||||
ss << m_tfx_source;
|
||||
|
||||
VkShaderModule mod = g_vulkan_shader_cache->GetFragmentShader(ss.str());
|
||||
@@ -2075,6 +2051,8 @@ VkPipeline GSDeviceVK::CreateTFXPipeline(const PipelineSelector& p)
|
||||
gpb.SetRasterizationState(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_CLOCKWISE);
|
||||
if (p.line_width)
|
||||
gpb.SetLineWidth(static_cast<float>(GSConfig.UpscaleMultiplier));
|
||||
if (p.topology == static_cast<u8>(GSHWDrawConfig::Topology::Line) && g_vulkan_context->GetOptionalExtensions().vk_ext_line_rasterization)
|
||||
gpb.SetLineRasterizationMode(VK_LINE_RASTERIZATION_MODE_BRESENHAM_EXT);
|
||||
gpb.SetDynamicViewportAndScissorState();
|
||||
gpb.AddDynamicState(VK_DYNAMIC_STATE_BLEND_CONSTANTS);
|
||||
|
||||
@@ -3177,8 +3155,6 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config)
|
||||
if (date_image)
|
||||
Recycle(date_image);
|
||||
|
||||
EndScene();
|
||||
|
||||
// now blit the hdr texture back to the original target
|
||||
if (hdr_rt)
|
||||
{
|
||||
|
||||
@@ -247,8 +247,6 @@ public:
|
||||
GSTextureVK* SetupPrimitiveTrackingDATE(GSHWDrawConfig& config);
|
||||
|
||||
void IASetVertexBuffer(const void* vertex, size_t stride, size_t count);
|
||||
bool IAMapVertexBuffer(void** vertex, size_t stride, size_t count);
|
||||
void IAUnmapVertexBuffer();
|
||||
void IASetIndexBuffer(const void* index, size_t count);
|
||||
|
||||
void PSSetShaderResource(int i, GSTexture* sr, bool check_state);
|
||||
|
||||
@@ -58,6 +58,7 @@ static bool s_dump_running = false;
|
||||
static bool s_needs_state_loaded = false;
|
||||
static u64 s_frame_ticks = 0;
|
||||
static u64 s_next_frame_time = 0;
|
||||
static bool s_is_dump_runner = false;
|
||||
|
||||
R5900cpu GSDumpReplayerCpu = {
|
||||
GSDumpReplayerCpuReserve,
|
||||
@@ -77,11 +78,26 @@ bool GSDumpReplayer::IsReplayingDump()
|
||||
return static_cast<bool>(s_dump_file);
|
||||
}
|
||||
|
||||
bool GSDumpReplayer::IsRunner()
|
||||
{
|
||||
return s_is_dump_runner;
|
||||
}
|
||||
|
||||
void GSDumpReplayer::SetIsDumpRunner(bool is_runner)
|
||||
{
|
||||
s_is_dump_runner = is_runner;
|
||||
}
|
||||
|
||||
void GSDumpReplayer::SetLoopCount(s32 loop_count)
|
||||
{
|
||||
s_dump_loop_count = loop_count - 1;
|
||||
}
|
||||
|
||||
int GSDumpReplayer::GetLoopCount()
|
||||
{
|
||||
return s_dump_loop_count;
|
||||
}
|
||||
|
||||
bool GSDumpReplayer::Initialize(const char* filename)
|
||||
{
|
||||
Common::Timer timer;
|
||||
|
||||
@@ -25,6 +25,9 @@ bool IsReplayingDump();
|
||||
|
||||
/// If set, playback will repeat once it reaches the last frame.
|
||||
void SetLoopCount(s32 loop_count = 0);
|
||||
int GetLoopCount();
|
||||
bool IsRunner();
|
||||
void SetIsDumpRunner(bool is_runner);
|
||||
|
||||
bool Initialize(const char* filename);
|
||||
void Reset();
|
||||
|
||||
@@ -85,6 +85,7 @@ bool Gif_HandlerAD(u8* pMem)
|
||||
{ // FINISH
|
||||
GUNIT_WARN("GIF Handler - FINISH");
|
||||
CSRreg.FINISH = true;
|
||||
gifUnit.gsFINISH.gsFINISHFired = false;
|
||||
}
|
||||
else if (reg == GIF_A_D_REG_LABEL)
|
||||
{ // LABEL
|
||||
|
||||
@@ -317,6 +317,8 @@ void ipuSoftReset()
|
||||
memzero(g_BP);
|
||||
|
||||
coded_block_pattern = 0;
|
||||
g_ipu_thresh[0] = 0;
|
||||
g_ipu_thresh[1] = 0;
|
||||
|
||||
ipuRegs.ctrl.reset();
|
||||
ipuRegs.top = 0;
|
||||
|
||||
@@ -20,8 +20,12 @@
|
||||
#include "R5900.h" // for g_GameStarted
|
||||
#include "IopBios.h"
|
||||
#include "IopMem.h"
|
||||
#include "iR3000A.h"
|
||||
#include "ps2/BiosTools.h"
|
||||
#include "DebugTools/SymbolMap.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <fmt/format.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include "common/FileSystem.h"
|
||||
@@ -983,6 +987,37 @@ namespace R3000A
|
||||
|
||||
namespace loadcore
|
||||
{
|
||||
|
||||
// Gets the thread list ptr from thbase
|
||||
u32 GetThreadList(u32 a0reg, u32 version)
|
||||
{
|
||||
// Function 3 returns the main thread manager struct
|
||||
u32 function = iopMemRead32(a0reg + 0x20);
|
||||
|
||||
// read the lui
|
||||
u32 thstruct = (iopMemRead32(function) & 0xFFFF) << 16;
|
||||
thstruct |= iopMemRead32(function + 4) & 0xFFFF;
|
||||
|
||||
u32 list = thstruct + 0x42c;
|
||||
|
||||
if (version > 0x101)
|
||||
list = thstruct + 0x430;
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
int RegisterLibraryEntries_HLE()
|
||||
{
|
||||
const std::string modname = iopMemReadString(a0 + 12);
|
||||
if (modname == "thbase")
|
||||
{
|
||||
const u32 version = iopMemRead32(a0 + 8);
|
||||
CurrentBiosInformation.iopThreadListAddr = GetThreadList(a0, version);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void RegisterLibraryEntries_DEBUG()
|
||||
{
|
||||
const std::string modname = iopMemReadString(a0 + 12);
|
||||
@@ -1093,6 +1128,11 @@ namespace R3000A
|
||||
EXPORT_H( 14, Kprintf)
|
||||
END_MODULE
|
||||
|
||||
// For grabbing the thread list from thbase
|
||||
MODULE(loadcore)
|
||||
EXPORT_H( 6, RegisterLibraryEntries)
|
||||
END_MODULE
|
||||
|
||||
// Special case with ioman and iomanX
|
||||
// They are mostly compatible excluding stat structures
|
||||
if(libname == "ioman" || libname == "iomanx")
|
||||
|
||||
@@ -1919,10 +1919,13 @@ void FolderMemoryCard::DeleteFromIndex(const std::string& filePath, const std::s
|
||||
if (yaml.has_value() && !yaml.value().empty())
|
||||
{
|
||||
ryml::NodeRef index = yaml.value().rootref();
|
||||
index.remove_child(c4::csubstr(entry.data(), entry.length()));
|
||||
|
||||
// Write out the changes
|
||||
SaveYAMLToFile(indexName.c_str(), index);
|
||||
|
||||
if (index.has_child(c4::csubstr(entry.data(), entry.length())))
|
||||
{
|
||||
index.remove_child(c4::csubstr(entry.data(), entry.length()));
|
||||
// Write out the changes
|
||||
SaveYAMLToFile(indexName.c_str(), index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +55,29 @@ void KeyStatus::Init()
|
||||
|
||||
void KeyStatus::Set(u32 pad, u32 index, float value)
|
||||
{
|
||||
// Since we reordered the buttons for better UI, we need to remap them here.
|
||||
static constexpr std::array<u8, MAX_KEYS> bitmask_mapping = {{
|
||||
12, // PAD_UP
|
||||
13, // PAD_RIGHT
|
||||
14, // PAD_DOWN
|
||||
15, // PAD_LEFT
|
||||
4, // PAD_TRIANGLE
|
||||
5, // PAD_CIRCLE
|
||||
6, // PAD_CROSS
|
||||
7, // PAD_SQUARE
|
||||
8, // PAD_SELECT
|
||||
11, // PAD_START
|
||||
2, // PAD_L1
|
||||
0, // PAD_L2
|
||||
3, // PAD_R1
|
||||
1, // PAD_R2
|
||||
9, // PAD_L3
|
||||
10, // PAD_R3
|
||||
16, // PAD_ANALOG
|
||||
17, // PAD_PRESSURE
|
||||
// remainder are analogs and not used here
|
||||
}};
|
||||
|
||||
if (IsAnalogKey(index))
|
||||
{
|
||||
m_button_pressure[pad][index] = static_cast<u8>(std::clamp(value * m_axis_scale[pad][1] * 255.0f, 0.0f, 255.0f));
|
||||
@@ -95,8 +118,8 @@ void KeyStatus::Set(u32 pad, u32 index, float value)
|
||||
}
|
||||
else
|
||||
{
|
||||
pos_x = m_analog[pad].invert_lx ? MERGE_F(pad, PAD_R_LEFT, PAD_R_RIGHT) : MERGE_F(pad, PAD_R_RIGHT, PAD_R_LEFT);
|
||||
pos_y = m_analog[pad].invert_ly ? MERGE_F(pad, PAD_R_UP, PAD_R_DOWN) : MERGE_F(pad, PAD_R_DOWN, PAD_R_UP);
|
||||
pos_x = m_analog[pad].invert_rx ? MERGE_F(pad, PAD_R_LEFT, PAD_R_RIGHT) : MERGE_F(pad, PAD_R_RIGHT, PAD_R_LEFT);
|
||||
pos_y = m_analog[pad].invert_ry ? MERGE_F(pad, PAD_R_UP, PAD_R_DOWN) : MERGE_F(pad, PAD_R_DOWN, PAD_R_UP);
|
||||
}
|
||||
|
||||
// No point checking if we're at dead center (usually keyboard with no buttons pressed).
|
||||
@@ -123,38 +146,24 @@ void KeyStatus::Set(u32 pad, u32 index, float value)
|
||||
}
|
||||
#undef MERGE_F
|
||||
}
|
||||
|
||||
}
|
||||
else if (IsTriggerKey(index))
|
||||
{
|
||||
const float s_value = std::clamp(value * m_trigger_scale[pad][1], 0.0f, 1.0f);
|
||||
const float dz_value = (m_trigger_scale[pad][0] > 0.0f && s_value < m_trigger_scale[pad][0]) ? 0.0f : s_value;
|
||||
m_button_pressure[pad][index] = static_cast<u8>(dz_value * 255.0f);
|
||||
if (dz_value > 0.0f)
|
||||
m_button[pad] &= ~(1u << bitmask_mapping[index]);
|
||||
else
|
||||
m_button[pad] |= (1u << bitmask_mapping[index]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Don't affect L2/R2, since they are analog on most pads.
|
||||
const float pmod = ((m_button[pad] & (1u << PAD_PRESSURE)) == 0 && !IsTriggerKey(index)) ? m_pressure_modifier[pad] : 1.0f;
|
||||
const float pmod = ((m_button[pad] & (1u << PAD_PRESSURE)) == 0) ? m_pressure_modifier[pad] : 1.0f;
|
||||
const float dz_value = (value < m_button_deadzone[pad]) ? 0.0f : value;
|
||||
m_button_pressure[pad][index] = static_cast<u8>(std::clamp(dz_value * pmod * 255.0f, 0.0f, 255.0f));
|
||||
|
||||
// Since we reordered the buttons for better UI, we need to remap them here.
|
||||
static constexpr std::array<u8, MAX_KEYS> bitmask_mapping = {{
|
||||
12, // PAD_UP
|
||||
13, // PAD_RIGHT
|
||||
14, // PAD_DOWN
|
||||
15, // PAD_LEFT
|
||||
4, // PAD_TRIANGLE
|
||||
5, // PAD_CIRCLE
|
||||
6, // PAD_CROSS
|
||||
7, // PAD_SQUARE
|
||||
8, // PAD_SELECT
|
||||
11, // PAD_START
|
||||
2, // PAD_L1
|
||||
0, // PAD_L2
|
||||
3, // PAD_R1
|
||||
1, // PAD_R2
|
||||
9, // PAD_L3
|
||||
10, // PAD_R3
|
||||
16, // PAD_ANALOG
|
||||
17, // PAD_PRESSURE
|
||||
// remainder are analogs and not used here
|
||||
}};
|
||||
|
||||
if (dz_value > 0.0f)
|
||||
m_button[pad] &= ~(1u << bitmask_mapping[index]);
|
||||
else
|
||||
@@ -170,7 +179,8 @@ void KeyStatus::Set(u32 pad, u32 index, float value)
|
||||
continue;
|
||||
|
||||
// We add 0.5 here so that the round trip between 255->127->255 when applying works as expected.
|
||||
m_button_pressure[pad][i] = static_cast<u8>(std::clamp((static_cast<float>(m_button_pressure[pad][i]) + 0.5f) * adjust_pmod, 0.0f, 255.0f));
|
||||
const float add = (m_button_pressure[pad][i] != 0) ? 0.5f : 0.0f;
|
||||
m_button_pressure[pad][i] = static_cast<u8>(std::clamp((static_cast<float>(m_button_pressure[pad][i]) + add) * adjust_pmod, 0.0f, 255.0f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ namespace PAD
|
||||
u8 m_button_pressure[NUM_CONTROLLER_PORTS][MAX_KEYS];
|
||||
PADAnalog m_analog[NUM_CONTROLLER_PORTS];
|
||||
float m_axis_scale[NUM_CONTROLLER_PORTS][2];
|
||||
float m_trigger_scale[NUM_CONTROLLER_PORTS][2];
|
||||
float m_vibration_scale[NUM_CONTROLLER_PORTS][2];
|
||||
float m_pressure_modifier[NUM_CONTROLLER_PORTS];
|
||||
float m_button_deadzone[NUM_CONTROLLER_PORTS];
|
||||
@@ -66,6 +67,11 @@ namespace PAD
|
||||
m_axis_scale[pad][0] = deadzone;
|
||||
m_axis_scale[pad][1] = scale;
|
||||
}
|
||||
__fi void SetTriggerScale(u32 pad, float deadzone, float scale)
|
||||
{
|
||||
m_trigger_scale[pad][0] = deadzone;
|
||||
m_trigger_scale[pad][1] = scale;
|
||||
}
|
||||
__fi float GetVibrationScale(u32 pad, u32 motor) const { return m_vibration_scale[pad][motor]; }
|
||||
__fi void SetVibrationScale(u32 pad, u32 motor, float scale) { m_vibration_scale[pad][motor] = scale; }
|
||||
__fi float GetPressureModifier(u32 pad) const { return m_pressure_modifier[pad]; }
|
||||
|
||||
@@ -217,8 +217,11 @@ void PAD::LoadConfig(const SettingsInterface& si)
|
||||
|
||||
const float axis_deadzone = si.GetFloatValue(section.c_str(), "Deadzone", DEFAULT_STICK_DEADZONE);
|
||||
const float axis_scale = si.GetFloatValue(section.c_str(), "AxisScale", DEFAULT_STICK_SCALE);
|
||||
const float trigger_deadzone = si.GetFloatValue(section.c_str(), "TriggerDeadzone", DEFAULT_TRIGGER_DEADZONE);
|
||||
const float trigger_scale = si.GetFloatValue(section.c_str(), "TriggerScale", DEFAULT_TRIGGER_SCALE);
|
||||
const float button_deadzone = si.GetFloatValue(section.c_str(), "ButtonDeadzone", DEFAULT_BUTTON_DEADZONE);
|
||||
g_key_status.SetAxisScale(i, axis_deadzone, axis_scale);
|
||||
g_key_status.SetTriggerScale(i, trigger_deadzone, trigger_scale);
|
||||
g_key_status.SetButtonDeadzone(i, button_deadzone);
|
||||
|
||||
if (ci->vibration_caps != VibrationCapabilities::NoVibration)
|
||||
@@ -412,9 +415,15 @@ static const SettingInfo s_dualshock2_settings[] = {
|
||||
"Sets the analog stick deadzone, i.e. the fraction of the stick movement which will be ignored.",
|
||||
"0.00", "0.00", "1.00", "0.01", "%.0f%%", nullptr, nullptr, 100.0f},
|
||||
{SettingInfo::Type::Float, "AxisScale", "Analog Sensitivity",
|
||||
"Sets the analog stick axis scaling factor. A value between 1.30 and 1.40 is recommended when using recent "
|
||||
"Sets the analog stick axis scaling factor. A value between 130% and 140% is recommended when using recent "
|
||||
"controllers, e.g. DualShock 4, Xbox One Controller.",
|
||||
"1.33", "0.01", "2.00", "0.01", "%.0f%%", nullptr, nullptr, 100.0f},
|
||||
{SettingInfo::Type::Float, "TriggerDeadzone", "Trigger Deadzone",
|
||||
"Sets the analog stick deadzone, i.e. the fraction of the stick movement which will be ignored.",
|
||||
"0.00", "0.00", "1.00", "0.01", "%.0f%%", nullptr, nullptr, 100.0f},
|
||||
{SettingInfo::Type::Float, "TriggerScale", "Trigger Sensitivity",
|
||||
"Sets the trigger scaling factor.",
|
||||
"1.00", "0.01", "2.00", "0.01", "%.0f%%", nullptr, nullptr, 100.0f},
|
||||
{SettingInfo::Type::Float, "LargeMotorScale", "Large Motor Vibration Scale",
|
||||
"Increases or decreases the intensity of low frequency vibration sent by the game.",
|
||||
"1.00", "0.00", "2.00", "0.01", "%.0f%%", nullptr, nullptr, 100.0f},
|
||||
@@ -504,7 +513,12 @@ void PAD::ClearPortBindings(SettingsInterface& si, u32 port)
|
||||
return;
|
||||
|
||||
for (u32 i = 0; i < info->num_bindings; i++)
|
||||
si.DeleteValue(section.c_str(), info->bindings[i].name);
|
||||
{
|
||||
const InputBindingInfo& bi = info->bindings[i];
|
||||
si.DeleteValue(section.c_str(), bi.name);
|
||||
si.DeleteValue(section.c_str(), fmt::format("{}Scale", bi.name).c_str());
|
||||
si.DeleteValue(section.c_str(), fmt::format("{}Deadzone", bi.name).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void PAD::CopyConfiguration(SettingsInterface* dest_si, const SettingsInterface& src_si,
|
||||
@@ -545,6 +559,8 @@ void PAD::CopyConfiguration(SettingsInterface* dest_si, const SettingsInterface&
|
||||
{
|
||||
const InputBindingInfo& bi = info->bindings[i];
|
||||
dest_si->CopyStringListValue(src_si, section.c_str(), bi.name);
|
||||
dest_si->CopyFloatValue(src_si, section.c_str(), fmt::format("{}Sensitivity", bi.name).c_str());
|
||||
dest_si->CopyFloatValue(src_si, section.c_str(), fmt::format("{}Deadzone", bi.name).c_str());
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < NUM_MACRO_BUTTONS_PER_CONTROLLER; i++)
|
||||
@@ -557,14 +573,6 @@ void PAD::CopyConfiguration(SettingsInterface* dest_si, const SettingsInterface&
|
||||
|
||||
if (copy_pad_config)
|
||||
{
|
||||
dest_si->CopyFloatValue(src_si, section.c_str(), "AxisScale");
|
||||
|
||||
if (info->vibration_caps != VibrationCapabilities::NoVibration)
|
||||
{
|
||||
dest_si->CopyFloatValue(src_si, section.c_str(), "LargeMotorScale");
|
||||
dest_si->CopyFloatValue(src_si, section.c_str(), "SmallMotorScale");
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < info->num_settings; i++)
|
||||
{
|
||||
const SettingInfo& csi = info->settings[i];
|
||||
@@ -601,8 +609,8 @@ void PAD::CopyConfiguration(SettingsInterface* dest_si, const SettingsInterface&
|
||||
}
|
||||
|
||||
static u32 TryMapGenericMapping(SettingsInterface& si, const std::string& section,
|
||||
const InputManager::GenericInputBindingMapping& mapping, GenericInputBinding generic_name,
|
||||
const char* bind_name)
|
||||
const InputManager::GenericInputBindingMapping& mapping, InputBindingInfo::Type bind_type,
|
||||
GenericInputBinding generic_name, const char* bind_name)
|
||||
{
|
||||
// find the mapping it corresponds to
|
||||
const std::string* found_mapping = nullptr;
|
||||
@@ -615,6 +623,14 @@ static u32 TryMapGenericMapping(SettingsInterface& si, const std::string& sectio
|
||||
}
|
||||
}
|
||||
|
||||
// Remove previously-set binding scales.
|
||||
if (bind_type == InputBindingInfo::Type::Button || bind_type == InputBindingInfo::Type::Axis ||
|
||||
bind_type == InputBindingInfo::Type::HalfAxis)
|
||||
{
|
||||
si.DeleteValue(section.c_str(), fmt::format("{}Scale", bind_name).c_str());
|
||||
si.DeleteValue(section.c_str(), fmt::format("{}Deadzone", bind_name).c_str());
|
||||
}
|
||||
|
||||
if (found_mapping)
|
||||
{
|
||||
Console.WriteLn("(MapController) Map %s/%s to '%s'", section.c_str(), bind_name, found_mapping->c_str());
|
||||
@@ -645,17 +661,17 @@ bool PAD::MapController(SettingsInterface& si, u32 controller,
|
||||
if (bi.generic_mapping == GenericInputBinding::Unknown)
|
||||
continue;
|
||||
|
||||
num_mappings += TryMapGenericMapping(si, section, mapping, bi.generic_mapping, bi.name);
|
||||
num_mappings += TryMapGenericMapping(si, section, mapping, bi.bind_type, bi.generic_mapping, bi.name);
|
||||
}
|
||||
if (info->vibration_caps == VibrationCapabilities::LargeSmallMotors)
|
||||
{
|
||||
num_mappings += TryMapGenericMapping(si, section, mapping, GenericInputBinding::SmallMotor, "SmallMotor");
|
||||
num_mappings += TryMapGenericMapping(si, section, mapping, GenericInputBinding::LargeMotor, "LargeMotor");
|
||||
num_mappings += TryMapGenericMapping(si, section, mapping, InputBindingInfo::Type::Motor, GenericInputBinding::SmallMotor, "SmallMotor");
|
||||
num_mappings += TryMapGenericMapping(si, section, mapping, InputBindingInfo::Type::Motor, GenericInputBinding::LargeMotor, "LargeMotor");
|
||||
}
|
||||
else if (info->vibration_caps == VibrationCapabilities::SingleMotor)
|
||||
{
|
||||
if (TryMapGenericMapping(si, section, mapping, GenericInputBinding::LargeMotor, "Motor") == 0)
|
||||
num_mappings += TryMapGenericMapping(si, section, mapping, GenericInputBinding::SmallMotor, "Motor");
|
||||
if (TryMapGenericMapping(si, section, mapping, InputBindingInfo::Type::Motor, GenericInputBinding::LargeMotor, "Motor") == 0)
|
||||
num_mappings += TryMapGenericMapping(si, section, mapping, InputBindingInfo::Type::Motor, GenericInputBinding::SmallMotor, "Motor");
|
||||
else
|
||||
num_mappings++;
|
||||
}
|
||||
|
||||
@@ -76,6 +76,8 @@ namespace PAD
|
||||
/// Default stick deadzone/sensitivity.
|
||||
static constexpr float DEFAULT_STICK_DEADZONE = 0.0f;
|
||||
static constexpr float DEFAULT_STICK_SCALE = 1.33f;
|
||||
static constexpr float DEFAULT_TRIGGER_DEADZONE = 0.0f;
|
||||
static constexpr float DEFAULT_TRIGGER_SCALE = 1.0f;
|
||||
static constexpr float DEFAULT_MOTOR_SCALE = 1.0f;
|
||||
static constexpr float DEFAULT_PRESSURE_MODIFIER = 0.5f;
|
||||
static constexpr float DEFAULT_BUTTON_DEADZONE = 0.0f;
|
||||
|
||||
@@ -519,7 +519,7 @@ void DSRLV(){ if (!_Rd_) return; cpuRegs.GPR.r[_Rd_].UD[0] = (u64)(cpuRegs.GPR.r
|
||||
// exceptions, since the lower bits of the address are used to determine the portions
|
||||
// of the address/register operations.
|
||||
|
||||
[[ noreturn ]] __noinline static void RaiseAddressError(u32 addr, bool store)
|
||||
__noinline static void RaiseAddressError(u32 addr, bool store)
|
||||
{
|
||||
const std::string message(fmt::format("Address Error, addr=0x{:x} [{}]", addr, store ? "store" : "load"));
|
||||
|
||||
@@ -1012,7 +1012,7 @@ void SYSCALL()
|
||||
case Syscall::StartThread:
|
||||
case Syscall::ChangeThreadPriority:
|
||||
{
|
||||
if (CurrentBiosInformation.threadListAddr == 0)
|
||||
if (CurrentBiosInformation.eeThreadListAddr == 0)
|
||||
{
|
||||
u32 offset = 0x0;
|
||||
// Suprisingly not that slow :)
|
||||
@@ -1030,16 +1030,16 @@ void SYSCALL()
|
||||
// We've found the instruction pattern!
|
||||
// We (well, I) know that the thread address is always 0x8001 + the immediate of the 6th instruction from here
|
||||
const u32 op = memRead32(0x80000000 + offset + (sizeof(u32) * 6));
|
||||
CurrentBiosInformation.threadListAddr = 0x80010000 + static_cast<u16>(op) - 8; // Subtract 8 because the address here is offset by 8.
|
||||
DevCon.WriteLn("BIOS: Successfully found the instruction pattern. Assuming the thread list is here: %0x", CurrentBiosInformation.threadListAddr);
|
||||
CurrentBiosInformation.eeThreadListAddr = 0x80010000 + static_cast<u16>(op) - 8; // Subtract 8 because the address here is offset by 8.
|
||||
DevCon.WriteLn("BIOS: Successfully found the instruction pattern. Assuming the thread list is here: %0x", CurrentBiosInformation.eeThreadListAddr);
|
||||
break;
|
||||
}
|
||||
offset += 4;
|
||||
}
|
||||
if (!CurrentBiosInformation.threadListAddr)
|
||||
if (!CurrentBiosInformation.eeThreadListAddr)
|
||||
{
|
||||
// We couldn't find the address
|
||||
CurrentBiosInformation.threadListAddr = -1;
|
||||
CurrentBiosInformation.eeThreadListAddr = -1;
|
||||
// If you're here because a user has reported this message, this means that the instruction pattern is not present on their bios, or it is aligned weirdly.
|
||||
Console.Warning("BIOS Warning: Unable to get a thread list offset. The debugger thread and stack frame views will not be functional.");
|
||||
}
|
||||
|
||||
@@ -15,4 +15,4 @@
|
||||
|
||||
/// Version number for GS and other shaders. Increment whenever any of the contents of the
|
||||
/// shaders change, to invalidate the cache.
|
||||
static constexpr u32 SHADER_CACHE_VERSION = 11;
|
||||
static constexpr u32 SHADER_CACHE_VERSION = 12;
|
||||
|
||||
@@ -71,11 +71,13 @@ namespace usb_lightgun
|
||||
{"SLUS-20485", 90.25f, 92.5f, 390, 132, 640, 240}, // Dino Stalker (U)
|
||||
{"SLUS-20389", 89.25f, 93.5f, 422, 141, 640, 240}, // Endgame (U)
|
||||
{"SLES-50936", 112.0f, 100.0f, 320, 120, 512, 256}, // Endgame (E) (Guncon2 needs to be connected to USB port 2)
|
||||
{"SLPM-65139", 90.0f, 91.5f, 320, 120, 640, 240}, // Gun Survivor 3: Dino Crisis (J)
|
||||
{"SLES-52620", 89.5f, 112.3f, 390, 147, 640, 256}, // Guncom 2 (E)
|
||||
{"SLES-51289", 84.5f, 89.0f, 456, 164, 640, 256}, // Gunfighter 2 - Jesse James (E)
|
||||
{"SLPS-25165", 90.25f, 98.0f, 390, 138, 640, 240}, // Gunvari Collection (J) (480i)
|
||||
// {"SLPS-25165", 86.75f, 96.0f, 454, 164, 640, 256}, // Gunvari Collection (J) (480p)
|
||||
{"SCES-50889", 90.25f, 94.5f, 390, 169, 640, 256}, // Ninja Assault (E)
|
||||
{"SLPS-20218", 90.0f, 92.0f, 320, 134, 640, 240}, // Ninja Assault (J)
|
||||
{"SLUS-20492", 90.25f, 92.5f, 390, 132, 640, 240}, // Ninja Assault (U)
|
||||
{"SLES-50650", 84.75f, 96.0f, 454, 164, 640, 240}, // Resident Evil Survivor 2 (E)
|
||||
{"SLES-51448", 90.25f, 95.0f, 420, 132, 640, 240}, // Resident Evil - Dead Aim (E)
|
||||
@@ -89,13 +91,14 @@ namespace usb_lightgun
|
||||
{"SLUS-20927", 90.25f, 99.0f, 390, 153, 640, 240}, // Time Crisis - Crisis Zone (U) (480i)
|
||||
// {"SLUS-20927", 94.5f, 104.75f, 423, 407, 768, 768}, // Time Crisis - Crisis Zone (U) (480p)
|
||||
{"SCES-50411", 89.8f, 99.9f, 421, 138, 640, 256}, // Vampire Night (E)
|
||||
{"SLPS-25077", 90.0f, 97.5f, 422, 118, 640, 240}, // Vampire Night (J)
|
||||
{"SLUS-20221", 89.8f, 102.5f, 422, 124, 640, 228}, // Vampire Night (U)
|
||||
{"SLES-51229", 110.15f, 100.0f, 433, 159, 512, 256}, // Virtua Cop - Elite Edition (E,J) (480i)
|
||||
// {"SLES-51229", 85.75f, 92.0f, 456, 164, 640, 256}, // Virtua Cop - Elite Edition (E,J) (480p)
|
||||
};
|
||||
|
||||
static constexpr s32 DEFAULT_SCREEN_WIDTH = 640;
|
||||
static constexpr s32 DEFAULT_SCREEN_HEIGHT = 480;
|
||||
static constexpr s32 DEFAULT_SCREEN_HEIGHT = 240;
|
||||
static constexpr float DEFAULT_CENTER_X = 320.0f;
|
||||
static constexpr float DEFAULT_CENTER_Y = 120.0f;
|
||||
static constexpr float DEFAULT_SCALE_X = 100.0f;
|
||||
@@ -359,7 +362,7 @@ namespace usb_lightgun
|
||||
GSTranslateWindowToDisplayCoordinates(abs_pos.first, abs_pos.second, &pointer_x, &pointer_y);
|
||||
|
||||
s16 pos_x, pos_y;
|
||||
if (pointer_x < 0.0f || pointer_y < 0.0f || (button_state & BID_SHOOT_OFFSCREEN))
|
||||
if (pointer_x < 0.0f || pointer_y < 0.0f)
|
||||
{
|
||||
// off-screen
|
||||
pos_x = 0;
|
||||
|
||||
@@ -729,9 +729,8 @@ namespace usb_msd
|
||||
int index, int length, uint8_t* data)
|
||||
{
|
||||
MSDState* s = USB_CONTAINER_OF(dev, MSDState, dev);
|
||||
int ret = 0;
|
||||
const int ret = usb_desc_handle_control(dev, p, request, value, index, length, data);
|
||||
|
||||
ret = usb_desc_handle_control(dev, p, request, value, index, length, data);
|
||||
if (ret >= 0)
|
||||
{
|
||||
return;
|
||||
@@ -743,7 +742,6 @@ namespace usb_msd
|
||||
case ClassInterfaceOutRequest | MassStorageReset:
|
||||
/* Reset state ready for the next CBW. */
|
||||
s->f.mode = USB_MSDM_CBW;
|
||||
ret = 0;
|
||||
break;
|
||||
case ClassInterfaceRequest | GetMaxLun:
|
||||
data[0] = 0;
|
||||
|
||||
@@ -209,7 +209,7 @@ void VMManager::SetState(VMState state)
|
||||
else
|
||||
Host::OnVMResumed();
|
||||
}
|
||||
else if (state == VMState::Stopping)
|
||||
else if (state == VMState::Stopping && old_state == VMState::Running)
|
||||
{
|
||||
// If stopping, break execution as soon as possible.
|
||||
Cpu->ExitExecution();
|
||||
|
||||
@@ -166,6 +166,8 @@
|
||||
<ClCompile Include="DEV9\InternalServers\DNS_Logger.cpp" />
|
||||
<ClCompile Include="DEV9\InternalServers\DNS_Server.cpp" />
|
||||
<ClCompile Include="DEV9\PacketReader\ARP\ARP_Packet.cpp" />
|
||||
<ClCompile Include="DEV9\PacketReader\ARP\ARP_PacketEditor.cpp" />
|
||||
<ClCompile Include="DEV9\PacketReader\EthernetFrameEditor.cpp" />
|
||||
<ClCompile Include="DEV9\PacketReader\EthernetFrame.cpp" />
|
||||
<ClCompile Include="DEV9\PacketReader\IP\ICMP\ICMP_Packet.cpp" />
|
||||
<ClCompile Include="DEV9\PacketReader\IP\TCP\TCP_Options.cpp" />
|
||||
@@ -510,6 +512,8 @@
|
||||
<ClInclude Include="DEV9\InternalServers\DNS_Server.h" />
|
||||
<ClInclude Include="DEV9\net.h" />
|
||||
<ClInclude Include="DEV9\PacketReader\ARP\ARP_Packet.h" />
|
||||
<ClInclude Include="DEV9\PacketReader\ARP\ARP_PacketEditor.h" />
|
||||
<ClInclude Include="DEV9\PacketReader\EthernetFrameEditor.h" />
|
||||
<ClInclude Include="DEV9\PacketReader\EthernetFrame.h" />
|
||||
<ClInclude Include="DEV9\PacketReader\MAC_Address.h" />
|
||||
<ClInclude Include="DEV9\PacketReader\IP\ICMP\ICMP_Packet.h" />
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user