mirror of
https://github.com/PCSX2/pcsx2.git
synced 2026-07-31 03:08:40 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3206094545 | ||
|
|
5cfae80701 | ||
|
|
b4d140c6bb | ||
|
|
c65eb3c3ee | ||
|
|
eec0984dbe | ||
|
|
9b1163f959 | ||
|
|
5b5edc506d | ||
|
|
b30b4375e7 | ||
|
|
1b0c03d892 | ||
|
|
bca49184e7 | ||
|
|
26d6c33163 | ||
|
|
9420615317 | ||
|
|
7198c6b8c6 | ||
|
|
9513864851 | ||
|
|
f9b8aa1862 | ||
|
|
34371c070c | ||
|
|
d6099dd263 | ||
|
|
a1bc39141e | ||
|
|
13ed41d077 | ||
|
|
8a0a8f718f | ||
|
|
7d08a54ad9 | ||
|
|
b9b47e3ec7 | ||
|
|
16989f2122 | ||
|
|
892d3a370d | ||
|
|
6af7ca9867 | ||
|
|
c3c354f794 | ||
|
|
43572a1560 | ||
|
|
79daed63ee | ||
|
|
21d3ad86d4 | ||
|
|
31ebe842e8 | ||
|
|
88487de72f | ||
|
|
616da8c99d | ||
|
|
ec8712cceb | ||
|
|
3d6923b2a1 | ||
|
|
f04337becf | ||
|
|
9a5dd4c19d | ||
|
|
643e0b1039 | ||
|
|
330061a6e7 | ||
|
|
0a292715cf | ||
|
|
52f034a513 | ||
|
|
25e05388ba | ||
|
|
7ad9a1af03 | ||
|
|
9346c69343 | ||
|
|
4a3f0ccf96 | ||
|
|
52a1396e29 | ||
|
|
f556dd2584 | ||
|
|
aea5c09825 | ||
|
|
8c3c9a1219 | ||
|
|
a4f1f383a7 | ||
|
|
ee73c5c1b5 | ||
|
|
c2904a4633 | ||
|
|
6faa2249f9 | ||
|
|
119c3acfe7 | ||
|
|
138a2683f2 |
+260
-113
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -42,7 +44,6 @@
|
||||
#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 +159,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 +184,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 +232,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 +247,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 +261,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 +272,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 +362,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 +685,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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -361,10 +362,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 +421,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 +469,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 +484,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 +499,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 +511,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 +598,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 +915,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
|
||||
|
||||
@@ -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;
|
||||
|
||||
+42
-6
@@ -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;
|
||||
};
|
||||
|
||||
@@ -2258,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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
/* PCSX2 - PS2 Emulator for PCs
|
||||
* Copyright (C) 2002-2022 PCSX2 Dev Team
|
||||
*
|
||||
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with PCSX2.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "PrecompiledHeader.h"
|
||||
#include "CDVD/CDVDdiscReader.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
#include <IOKit/storage/IOMedia.h>
|
||||
#include <IOKit/storage/IOCDMedia.h>
|
||||
#include <IOKit/storage/IODVDMedia.h>
|
||||
#include <IOKit/IOBSD.h>
|
||||
#include <IOKit/IOKitLib.h>
|
||||
#endif
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
std::vector<std::string> GetDriveListFromClasses(CFMutableDictionaryRef classes)
|
||||
{
|
||||
io_iterator_t iterator = IO_OBJECT_NULL;
|
||||
kern_return_t result;
|
||||
std::vector<std::string> drives;
|
||||
|
||||
CFDictionarySetValue(classes, CFSTR(kIOMediaEjectableKey), kCFBooleanTrue);
|
||||
result = IOServiceGetMatchingServices(kIOMasterPortDefault, classes, &iterator);
|
||||
if (result != KERN_SUCCESS)
|
||||
return drives;
|
||||
while (io_object_t media = IOIteratorNext(iterator))
|
||||
{
|
||||
CFTypeRef path_cfstr = IORegistryEntryCreateCFProperty(media, CFSTR(kIOBSDNameKey), kCFAllocatorDefault, 0);
|
||||
if (path_cfstr)
|
||||
{
|
||||
char path[PATH_MAX] = {0};
|
||||
strlcpy(path, "/dev/r", PATH_MAX);
|
||||
size_t path_prefix_len = strnlen(path, PATH_MAX);
|
||||
result = CFStringGetCString((CFStringRef)path_cfstr, path + path_prefix_len, PATH_MAX - path_prefix_len, kCFStringEncodingUTF8);
|
||||
if (result)
|
||||
{
|
||||
drives.emplace_back(path);
|
||||
}
|
||||
CFRelease(path_cfstr);
|
||||
}
|
||||
IOObjectRelease(media);
|
||||
}
|
||||
IOObjectRelease(iterator);
|
||||
return drives;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
std::vector<std::string> GetOpticalDriveList()
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
std::vector<std::string> drives;
|
||||
|
||||
if (CFMutableDictionaryRef cd_classes = IOServiceMatching(kIOCDMediaClass))
|
||||
{
|
||||
std::vector<std::string> cd = GetDriveListFromClasses(cd_classes);
|
||||
drives.insert(drives.end(), cd.begin(), cd.end());
|
||||
}
|
||||
|
||||
if (CFMutableDictionaryRef dvd_classes = IOServiceMatching(kIODVDMediaClass))
|
||||
{
|
||||
std::vector<std::string> dvd = GetDriveListFromClasses(dvd_classes);
|
||||
drives.insert(drives.end(), dvd.begin(), dvd.end());
|
||||
}
|
||||
return drives;
|
||||
#else
|
||||
return {};
|
||||
#endif
|
||||
}
|
||||
|
||||
void GetValidDrive(std::string& drive)
|
||||
{
|
||||
if (!drive.empty())
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
int fd = open(drive.c_str(), O_RDONLY | O_NONBLOCK);
|
||||
if (fd != -1)
|
||||
{
|
||||
close(fd);
|
||||
}
|
||||
else
|
||||
{
|
||||
drive.clear();
|
||||
}
|
||||
#else
|
||||
drive.clear();
|
||||
#endif
|
||||
}
|
||||
if (drive.empty())
|
||||
{
|
||||
auto drives = GetOpticalDriveList();
|
||||
if (!drives.empty())
|
||||
drive = drives.front();
|
||||
}
|
||||
if (!drive.empty())
|
||||
DevCon.WriteLn("CDVD: Opening drive '%s'...", drive.c_str());
|
||||
}
|
||||
@@ -0,0 +1,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
|
||||
}
|
||||
|
||||
@@ -916,14 +916,14 @@ set(pcsx2LinuxSources
|
||||
)
|
||||
|
||||
set(pcsx2OSXSources
|
||||
CDVD/Linux/DriveUtility.cpp
|
||||
CDVD/Linux/IOCtlSrc.cpp
|
||||
CDVD/Darwin/DriveUtility.cpp
|
||||
CDVD/Darwin/IOCtlSrc.cpp
|
||||
Darwin/DarwinFlatFileReader.cpp
|
||||
)
|
||||
|
||||
set(pcsx2FreeBSDSources
|
||||
CDVD/Linux/DriveUtility.cpp
|
||||
CDVD/Linux/IOCtlSrc.cpp
|
||||
CDVD/Darwin/DriveUtility.cpp
|
||||
CDVD/Darwin/IOCtlSrc.cpp
|
||||
Darwin/DarwinFlatFileReader.cpp
|
||||
)
|
||||
|
||||
|
||||
@@ -15,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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
+48
-44
@@ -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();
|
||||
|
||||
@@ -683,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]];
|
||||
@@ -708,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]];
|
||||
@@ -726,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]];
|
||||
@@ -1072,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);
|
||||
}
|
||||
@@ -1699,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)
|
||||
@@ -1854,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)
|
||||
{
|
||||
@@ -1874,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:
|
||||
@@ -1942,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];
|
||||
@@ -2761,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();
|
||||
|
||||
@@ -2830,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
|
||||
@@ -2838,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);
|
||||
@@ -2877,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
|
||||
@@ -2896,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();
|
||||
@@ -2934,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()) {
|
||||
@@ -2950,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()) {
|
||||
@@ -2988,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;
|
||||
@@ -3028,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);
|
||||
@@ -3036,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)
|
||||
@@ -3048,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:
|
||||
@@ -3127,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)
|
||||
{
|
||||
@@ -3231,7 +3232,7 @@ __forceinline void GSState::HandleAutoFlush()
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr size_t NumIndicesForPrim(u32 prim)
|
||||
static constexpr u32 NumIndicesForPrim(u32 prim)
|
||||
{
|
||||
switch (prim)
|
||||
{
|
||||
@@ -3254,20 +3255,20 @@ static constexpr size_t NumIndicesForPrim(u32 prim)
|
||||
template <u32 prim, bool auto_flush, bool index_swap>
|
||||
__forceinline void GSState::VertexKick(u32 skip)
|
||||
{
|
||||
constexpr size_t n = NumIndicesForPrim(prim);
|
||||
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
|
||||
|
||||
@@ -3286,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;
|
||||
@@ -3515,7 +3516,7 @@ __forceinline void GSState::VertexKick(u32 skip)
|
||||
temp_draw_rect = min.upl64(max).rintersect(scissor);
|
||||
}
|
||||
|
||||
CLUTAutoFlush();
|
||||
CLUTAutoFlush(prim);
|
||||
}
|
||||
|
||||
/// Checks if region repeat is used (applying it does something to at least one of the values in min...max)
|
||||
@@ -3583,8 +3584,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;
|
||||
|
||||
|
||||
+5
-4
@@ -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);
|
||||
|
||||
@@ -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,7 +354,6 @@ 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;
|
||||
@@ -554,11 +555,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;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -305,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());
|
||||
|
||||
@@ -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);
|
||||
@@ -164,7 +166,6 @@ void GSDevice11::SetupPS(const PSSelector& sel, const GSHWDrawConfig::PSConstant
|
||||
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);
|
||||
|
||||
@@ -1483,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);
|
||||
@@ -1505,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);
|
||||
|
||||
@@ -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 || (
|
||||
|
||||
@@ -333,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];
|
||||
@@ -403,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
|
||||
@@ -465,7 +465,7 @@ 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)
|
||||
{
|
||||
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;
|
||||
|
||||
@@ -516,7 +516,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;
|
||||
|
||||
@@ -539,7 +539,7 @@ 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)
|
||||
{
|
||||
if (write_ba)
|
||||
v[i].XYZ.X -= 128u;
|
||||
@@ -572,7 +572,7 @@ 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)
|
||||
{
|
||||
if (write_ba)
|
||||
v[i].XYZ.X -= 128u;
|
||||
@@ -709,7 +709,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))
|
||||
{
|
||||
@@ -722,7 +722,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;
|
||||
@@ -1125,10 +1125,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)
|
||||
|
||||
@@ -1279,10 +1279,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
|
||||
|
||||
@@ -1435,6 +1431,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))
|
||||
{
|
||||
@@ -1526,18 +1577,54 @@ 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);
|
||||
|
||||
// Okami mustn't call this code
|
||||
if (m_texture_shuffle && m_vertex.next < 3 && PRIM->FST && ((m_context->FRAME.FBMSK & fm_mask) == 0))
|
||||
@@ -1575,7 +1662,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.
|
||||
@@ -1598,6 +1684,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
|
||||
@@ -1642,7 +1729,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);
|
||||
|
||||
@@ -1665,84 +1752,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(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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, unscaled_size.x, unscaled_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 (rt)
|
||||
{
|
||||
// Be sure texture shuffle detection is properly propagated
|
||||
@@ -1841,7 +1851,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)
|
||||
@@ -1858,7 +1868,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
|
||||
@@ -1969,7 +1979,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;
|
||||
@@ -1982,7 +1992,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;
|
||||
@@ -1990,7 +2000,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;
|
||||
@@ -2013,7 +2023,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);
|
||||
@@ -3105,6 +3115,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.
|
||||
@@ -3112,9 +3142,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;
|
||||
@@ -3290,14 +3331,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)
|
||||
{
|
||||
@@ -3318,18 +3383,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);
|
||||
@@ -4038,7 +4091,7 @@ GSRendererHW::CLUTDrawTestResult GSRendererHW::PossibleCLUTDraw()
|
||||
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())
|
||||
{
|
||||
|
||||
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);
|
||||
|
||||
@@ -322,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
|
||||
@@ -332,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;
|
||||
@@ -358,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).
|
||||
|
||||
@@ -1374,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);
|
||||
@@ -1403,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;
|
||||
@@ -1594,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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "GLLoader.h"
|
||||
#include "GS/GS.h"
|
||||
#include "Host.h"
|
||||
#include "HostSettings.h"
|
||||
|
||||
namespace ReplaceGL
|
||||
{
|
||||
@@ -112,8 +113,8 @@ namespace GLLoader
|
||||
bool vendor_id_amd = false;
|
||||
bool vendor_id_nvidia = false;
|
||||
bool vendor_id_intel = false;
|
||||
bool mesa_driver = false;
|
||||
bool buggy_pbo = false;
|
||||
bool disable_download_pbo = false;
|
||||
|
||||
bool is_gles = false;
|
||||
bool has_dual_source_blend = false;
|
||||
@@ -131,14 +132,8 @@ namespace GLLoader
|
||||
vendor_id_amd = true;
|
||||
else if (strstr(vendor, "NVIDIA Corporation"))
|
||||
vendor_id_nvidia = true;
|
||||
|
||||
#ifdef _WIN32
|
||||
else if (strstr(vendor, "Intel"))
|
||||
vendor_id_intel = true;
|
||||
#else
|
||||
// On linux assumes the free driver if it isn't nvidia or amd pro driver
|
||||
mesa_driver = !vendor_id_nvidia && !vendor_id_amd;
|
||||
#endif
|
||||
|
||||
if (GSConfig.OverrideGeometryShaders != -1)
|
||||
{
|
||||
@@ -225,10 +220,16 @@ namespace GLLoader
|
||||
|
||||
// Don't use PBOs when we don't have ARB_buffer_storage, orphaning buffers probably ends up worse than just
|
||||
// using the normal texture update routines and letting the driver take care of it.
|
||||
GLLoader::buggy_pbo = !GLAD_GL_VERSION_4_4 && !GLAD_GL_ARB_buffer_storage && !GLAD_GL_EXT_buffer_storage;
|
||||
if (GLLoader::buggy_pbo)
|
||||
buggy_pbo = !GLAD_GL_VERSION_4_4 && !GLAD_GL_ARB_buffer_storage && !GLAD_GL_EXT_buffer_storage;
|
||||
if (buggy_pbo)
|
||||
Console.Warning("Not using PBOs for texture uploads because buffer_storage is unavailable.");
|
||||
|
||||
// Give the user the option to disable PBO usage for downloads.
|
||||
// Most drivers seem to be faster with PBO.
|
||||
disable_download_pbo = Host::GetBoolSettingValue("EmuCore/GS", "DisableGLDownloadPBO", false);
|
||||
if (disable_download_pbo)
|
||||
Console.Warning("Not using PBOs for texture downloads, this may reduce performance.");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,9 +29,8 @@ namespace GLLoader
|
||||
extern bool vendor_id_amd;
|
||||
extern bool vendor_id_nvidia;
|
||||
extern bool vendor_id_intel;
|
||||
extern bool mesa_driver;
|
||||
extern bool buggy_pbo;
|
||||
extern bool in_replayer;
|
||||
extern bool disable_download_pbo;
|
||||
|
||||
// GL
|
||||
extern bool is_gles;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
@@ -437,7 +455,8 @@ std::unique_ptr<GSDownloadTextureOGL> GSDownloadTextureOGL::Create(u32 width, u3
|
||||
{
|
||||
const u32 buffer_size = GetBufferSize(width, height, format, GSTexture::GetCompressedBytesPerBlock(format));
|
||||
|
||||
const bool use_buffer_storage = (GLAD_GL_VERSION_4_4 || GLAD_GL_ARB_buffer_storage || GLAD_GL_EXT_buffer_storage);
|
||||
const bool use_buffer_storage = (GLAD_GL_VERSION_4_4 || GLAD_GL_ARB_buffer_storage || GLAD_GL_EXT_buffer_storage) &&
|
||||
!GLLoader::disable_download_pbo;
|
||||
if (use_buffer_storage)
|
||||
{
|
||||
GLuint buffer_id;
|
||||
@@ -494,11 +513,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 +529,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 +540,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)
|
||||
|
||||
@@ -245,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 = {};
|
||||
@@ -1948,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);
|
||||
@@ -1955,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);
|
||||
@@ -2046,6 +2050,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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]; }
|
||||
|
||||
+33
-17
@@ -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;
|
||||
|
||||
@@ -311,7 +311,7 @@ bool LoadBIOS()
|
||||
if (EmuConfig.CurrentIRX.length() > 3)
|
||||
LoadIrx(EmuConfig.CurrentIRX, &eeMem->ROM[0x3C0000], sizeof(eeMem->ROM) - 0x3C0000);
|
||||
|
||||
CurrentBiosInformation.threadListAddr = 0;
|
||||
CurrentBiosInformation.eeThreadListAddr = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,9 @@ const u32 ThreadListInstructions[3] =
|
||||
|
||||
struct BiosDebugInformation
|
||||
{
|
||||
u32 threadListAddr;
|
||||
u32 eeThreadListAddr;
|
||||
u32 iopThreadListAddr;
|
||||
u32 iopModListAddr;
|
||||
};
|
||||
|
||||
extern BiosDebugInformation CurrentBiosInformation;
|
||||
|
||||
Reference in New Issue
Block a user