Compare commits

...
54 Commits
Author SHA1 Message Date
lightningterror 3206094545 GameDB: Add full mipmap and trilinear ps2 to Hard Hitter games.
Improves ground texture rendering.
2023-02-10 22:24:33 +01:00
Stenzekandrefractionpcsx2 5cfae80701 GL/OpenGL: Add a hidden [EmuCore/GS] DisableGLDownloadPBO option
.. to disable the use of PBOs when reading back.
2023-02-10 14:38:21 +00:00
JordanTheToasterandrefractionpcsx2 b4d140c6bb GameDB: Fixes for 187 Ride or Die
Add autoflush to soften bloom and HPO Special Texture to fix misaligned bloom.
2023-02-10 14:15:51 +00:00
Stenzekandrefractionpcsx2 c65eb3c3ee GS/HW: Fix crash with AVX2 due to unaligned pitch 2023-02-10 14:15:33 +00:00
Mrlinkwiiandlightningterror eec0984dbe Gamedb: remove skipdraw from Need for Speed - Undercover 2023-02-10 14:47:52 +01:00
Stenzekandrefractionpcsx2 9b1163f959 GS/HW: Use sampler for clamp/repeat when it's redundant
But only for local memory sources (not targets), it causes issues with
upscaling otherwise.
2023-02-10 11:20:37 +00:00
Stenzekandrefractionpcsx2 5b5edc506d Misc: Warning fixes 2023-02-10 11:20:37 +00:00
refractionpcsx2 b30b4375e7 GS: Detect 16bit source copy in to 32bit target shuffle 2023-02-10 11:19:41 +00:00
Stenzekandrefractionpcsx2 1b0c03d892 GS/HW: Fix fixed/invalid TEX0 case in TimeSplitters 2 2023-02-10 03:43:10 +00:00
ZiemasandGitHub bca49184e7 Debugger: Fix missing return path. (#8094) 2023-02-09 19:48:54 +00:00
Stenzekandlightningterror 26d6c33163 GSRunner: Clear bindings and disable game settings 2023-02-09 17:29:47 +01:00
Ziemasandrefractionpcsx2 9420615317 Implement thread listing for IOP
Abstracts away threads behind a common interface for both EE and IOP
2023-02-09 15:49:56 +00:00
Stenzekandrefractionpcsx2 7198c6b8c6 GS: Use u32 instead of size_t for index storage
These are never going to be greater than 2^32-1, and they're truncated
in a bunch of places anyway.
2023-02-09 14:54:42 +00:00
Stenzekandrefractionpcsx2 9513864851 GS: Make prim a template parameter for auto flush
It's force inlined into VertexKick(), which also has prim as a template
parameter.
2023-02-09 14:54:42 +00:00
Stenzekandrefractionpcsx2 f9b8aa1862 GS: Handle index swap case for auto flush
DirectX and OpenGL no longer have different draw splitting.
2023-02-09 14:54:42 +00:00
refractionpcsx2 34371c070c Mcd-Folder: Prevent crash trying to delete non-existent node. 2023-02-09 14:06:21 +00:00
Stenzekandrefractionpcsx2 d6099dd263 GS/Vulkan: Use Bresenham line rasterization when supported 2023-02-09 13:42:05 +00:00
Stenzekandrefractionpcsx2 a1bc39141e PAD: Fix trigger deadzone/scale setting 2023-02-09 12:30:20 +00:00
Connor McLaughlinandrefractionpcsx2 13ed41d077 GS/DX11: Disable multisampling in rasterizer state 2023-02-09 11:13:42 +00:00
Stenzekandrefractionpcsx2 8a0a8f718f GameDB: Remove preloading disable for a bunch of games
These are fine now.
2023-02-09 10:51:06 +00:00
Stenzekandrefractionpcsx2 7d08a54ad9 GS/HW: Optimize TC source size based on CLAMP 2023-02-09 10:51:06 +00:00
Stenzekandrefractionpcsx2 b9b47e3ec7 GS/OGL: Fix upload row length for texture Update() 2023-02-09 10:51:06 +00:00
refractionpcsx2 16989f2122 GS-HW: On overwrite of large incompatible format texture, erase old. 2023-02-09 10:50:40 +00:00
Berylskidandrefractionpcsx2 892d3a370d GameDB: Fix texture flickers of Armored Core Nexus 2023-02-09 09:37:17 +00:00
Stenzekandlightningterror 6af7ca9867 PAD: Fix invert in deadzone computation 2023-02-09 10:03:55 +01:00
Stenzekandlightningterror c3c354f794 PAD: Add trigger deadzone/sensitivity settings 2023-02-09 10:03:55 +01:00
Stenzekandlightningterror 43572a1560 Qt: Mark widget binder functions as inline
Should also fix the unused warning without using [[maybe_unused]];
2023-02-09 10:03:55 +01:00
Stenzekandlightningterror 79daed63ee Qt: Handle multiplier in controller settings 2023-02-09 10:03:55 +01:00
Stenzekandlightningterror 21d3ad86d4 Qt: Make controller settings page scrollable 2023-02-09 10:03:55 +01:00
Stenzekandlightningterror 31ebe842e8 Qt: Add per-bind sensitivity/deadzone controls (shift-click) 2023-02-09 10:03:55 +01:00
eyfixandGitHub 88487de72f USB: Add values to GunCon2 config. (#8078) 2023-02-09 09:55:23 +01:00
Harukaandlightningterror 616da8c99d CDVD: simplify code 2023-02-09 09:52:00 +01:00
Harukaandlightningterror ec8712cceb CDVD: use DevCon logging instead of stdout / stderr 2023-02-09 09:52:00 +01:00
Harukaandlightningterror 3d6923b2a1 CDVD: implement SetSpindleSpeed on macOS 2023-02-09 09:52:00 +01:00
Harukaandlightningterror f04337becf CDVD: fix macOS CD reading 2023-02-09 09:52:00 +01:00
Harukaandlightningterror 9a5dd4c19d CDVD: macOS physical disc support 2023-02-09 09:52:00 +01:00
lightningterror 643e0b1039 GameDB: Add Autoflush hw fix to MGS3 Subsistence.
Fixes lens flare.
2023-02-09 07:06:21 +01:00
Stenzekandlightningterror 330061a6e7 GS/OGL: Fix offseted downloads not being offset
Fixes background screen effect in Gradius V.
2023-02-09 07:04:50 +01:00
Stenzekandlightningterror 0a292715cf GS/OGL: Align texture uploads to 64 bytes
Fixes potential crash in some games with odd-sized targets
and preloading (e.g. Densha De Go).
2023-02-09 07:04:50 +01:00
Berylskidandlightningterror 52f034a513 GameDB: Add missing quatation marks in memcardFilters for Armored Core Last Raven 2023-02-08 17:32:49 +01:00
Stenzekandlightningterror 25e05388ba Qt: Open fullscreen window on same display as main 2023-02-08 17:29:59 +01:00
JordanTheToasterandlightningterror 7ad9a1af03 GameDB: Add Paltex to Gacharoku
Stops the hash cache from being silly and exploding.
2023-02-08 17:29:22 +01:00
refractionpcsx2 9346c69343 GS-HW: Iterate dirty rects in reverse to join existing ones. 2023-02-08 00:05:44 +00:00
refractionpcsx2 4a3f0ccf96 GS-HW: Process dirty rects separately, improved Tex in RT compatibility 2023-02-08 00:05:44 +00:00
refractionpcsx2 52a1396e29 GSDumpRunner: Fix new options, add missing Preload Frame 2023-02-07 23:40:01 +00:00
JordanTheToasterandrefractionpcsx2 f556dd2584 GameDB: Add missing The Godfather fixes
Adds missing fixes for other entry's of The Godfarther.
2023-02-07 23:26:04 +00:00
refractionpcsx2 aea5c09825 GSDumpRunner: Ability to enable manual hw hacks for testing 2023-02-07 22:31:36 +00:00
refractionpcsx2 8c3c9a1219 GSDumpRunner: Tweaks to reduce console messages and redundant frame dumps 2023-02-07 22:31:36 +00:00
eyfixandGitHub a4f1f383a7 USB/LightGun/GunCon2: Fix wrong code of GunCon2. (#8066)
240 is used for the default screen height elsewhere in the file.
BID_SHOOT_OFFSCREEN is already handled at line 295.
2023-02-07 06:21:48 +01:00
JordanTheToasterandlightningterror ee73c5c1b5 GameDB: Various small fixes
Fixes for Transformers The Game to fix bloom and mipmapping issues and add missing db entrys.
2023-02-07 06:19:59 +01:00
refractionpcsx2 c2904a4633 GS-HW: Don't enable merge sprite in native resolution 2023-02-06 23:59:06 +00:00
JordanTheToasterandlightningterror 6faa2249f9 GameDB: Add Disable InstantVU to MGS 3 Subsistence
Disables InstantVU1 to fix missing letters in text such as the letter E also adds more fixes and missing fixes for Forever Kingdom / Evergrace 2.
2023-02-07 00:24:47 +01:00
lightningterror 119c3acfe7 GS-tc: Cleanup hw texturecache.
Initializations, constants, casts.
2023-02-07 00:22:46 +01:00
lightningterror 138a2683f2 USB: Fix Variable is assigned a value that is never used warning.
Codacy.
2023-02-07 00:22:46 +01:00
74 changed files with 2465 additions and 1023 deletions
File diff suppressed because it is too large Load Diff
+22 -17
View File
@@ -21,6 +21,8 @@
#define PS_FST 0
#define PS_WMS 0
#define PS_WMT 0
#define PS_ADJS 0
#define PS_ADJT 0
#define PS_AEM_FMT FMT_32
#define PS_AEM 0
#define PS_TFX 0
@@ -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
//////////////////////////////////////////////////////////////////////
+19 -14
View File
@@ -109,7 +109,20 @@ vec4 sample_c(vec2 uv)
// As of 2018 this issue is still present.
uv = (trunc(uv * WH.zw) + vec2(0.5, 0.5)) / WH.zw;
#endif
uv *= STScale;
#if !PS_ADJS && !PS_ADJT
uv *= STScale;
#else
#if PS_ADJS
uv.x = (uv.x - STRange.x) * STRange.z;
#else
uv.x = uv.x * STScale.x;
#endif
#if PS_ADJT
uv.y = (uv.y - STRange.y) * STRange.w;
#else
uv.y = uv.y * STScale.y;
#endif
#endif
#if PS_AUTOMATIC_LOD == 1
return texture(TextureSampler, uv);
@@ -146,11 +159,7 @@ vec4 sample_p_norm(float u)
vec4 clamp_wrap_uv(vec4 uv)
{
vec4 uv_out = uv;
#if PS_INVALID_TEX0 == 1
vec4 tex_size = WH.zwzw;
#else
vec4 tex_size = WH.xyxy;
#endif
#if PS_WMS == PS_WMT
@@ -162,7 +171,7 @@ vec4 clamp_wrap_uv(vec4 uv)
// textures. Fixes Xenosaga's hair issue.
uv = fract(uv);
#endif
uv_out = vec4((uvec4(uv * tex_size) & MskFix.xyxy) | MskFix.zwzw) / tex_size;
uv_out = vec4((uvec4(uv * tex_size) & floatBitsToUint(MinMax.xyxy)) | floatBitsToUint(MinMax.zwzw)) / tex_size;
#endif
#else // PS_WMS != PS_WMT
@@ -174,7 +183,7 @@ vec4 clamp_wrap_uv(vec4 uv)
#if PS_FST == 0
uv.xz = fract(uv.xz);
#endif
uv_out.xz = vec2((uvec2(uv.xz * tex_size.xx) & MskFix.xx) | MskFix.zz) / tex_size.xx;
uv_out.xz = vec2((uvec2(uv.xz * tex_size.xx) & floatBitsToUint(MinMax.xx)) | floatBitsToUint(MinMax.zz)) / tex_size.xx;
#endif
@@ -185,7 +194,7 @@ vec4 clamp_wrap_uv(vec4 uv)
#if PS_FST == 0
uv.yw = fract(uv.yw);
#endif
uv_out.yw = vec2((uvec2(uv.yw * tex_size.yy) & MskFix.yy) | MskFix.ww) / tex_size.yy;
uv_out.yw = vec2((uvec2(uv.yw * tex_size.yy) & floatBitsToUint(MinMax.yy)) | floatBitsToUint(MinMax.ww)) / tex_size.yy;
#endif
#endif
@@ -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
+22 -18
View File
@@ -312,6 +312,8 @@ void main()
#define PS_FST 0
#define PS_WMS 0
#define PS_WMT 0
#define PS_ADJS 0
#define PS_ADJT 0
#define PS_FMT FMT_32
#define PS_AEM 0
#define PS_TFX 0
@@ -332,7 +334,6 @@ void main()
#define PS_CHANNEL_FETCH 0
#define PS_TALES_OF_ABYSS_HLE 0
#define PS_URBAN_CHAOS_HLE 0
#define PS_INVALID_TEX0 0
#define PS_SCALE_FACTOR 1.0
#define PS_HDR 0
#define PS_COLCLIP 0
@@ -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
+14 -1
View File
@@ -153,6 +153,9 @@ namespace Vulkan
m_provoking_vertex = {};
m_provoking_vertex.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_PROVOKING_VERTEX_STATE_CREATE_INFO_EXT;
m_line_rasterization_state = {};
m_line_rasterization_state.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_LINE_STATE_CREATE_INFO_EXT;
// set defaults
SetNoCullRasterizationState();
SetNoDepthTestState();
@@ -255,7 +258,17 @@ namespace Vulkan
m_ci.pRasterizationState = &m_rasterization_state;
}
void GraphicsPipelineBuilder::SetLineWidth(float width) { m_rasterization_state.lineWidth = width; }
void GraphicsPipelineBuilder::SetLineWidth(float width)
{
m_rasterization_state.lineWidth = width;
}
void GraphicsPipelineBuilder::SetLineRasterizationMode(VkLineRasterizationModeEXT mode)
{
Util::AddPointerToChain(&m_rasterization_state, &m_line_rasterization_state);
m_line_rasterization_state.lineRasterizationMode = mode;
}
void GraphicsPipelineBuilder::SetMultisamples(u32 multisamples, bool per_sample_shading)
{
+2
View File
@@ -96,6 +96,7 @@ namespace Vulkan
void SetRasterizationState(VkPolygonMode polygon_mode, VkCullModeFlags cull_mode, VkFrontFace front_face);
void SetLineWidth(float width);
void SetLineRasterizationMode(VkLineRasterizationModeEXT mode);
void SetMultisamples(u32 multisamples, bool per_sample_shading);
void SetNoCullRasterizationState();
@@ -157,6 +158,7 @@ namespace Vulkan
VkPipelineMultisampleStateCreateInfo m_multisample_state;
VkPipelineRasterizationProvokingVertexStateCreateInfoEXT m_provoking_vertex;
VkPipelineRasterizationLineStateCreateInfoEXT m_line_rasterization_state;
};
class ComputePipelineBuilder
+16
View File
@@ -458,6 +458,8 @@ namespace Vulkan
SupportsExtension(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME, false);
m_optional_extensions.vk_ext_calibrated_timestamps =
SupportsExtension(VK_EXT_CALIBRATED_TIMESTAMPS_EXTENSION_NAME, false);
m_optional_extensions.vk_ext_line_rasterization =
SupportsExtension(VK_EXT_LINE_RASTERIZATION_EXTENSION_NAME, false);
m_optional_extensions.vk_khr_driver_properties =
SupportsExtension(VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME, false);
m_optional_extensions.vk_arm_rasterization_order_attachment_access =
@@ -654,12 +656,19 @@ namespace Vulkan
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_FEATURES_EXT};
VkPhysicalDeviceRasterizationOrderAttachmentAccessFeaturesARM rasterization_order_access_feature = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_FEATURES_ARM};
VkPhysicalDeviceLineRasterizationFeaturesEXT line_rasterization_feature = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LINE_RASTERIZATION_FEATURES_EXT};
if (m_optional_extensions.vk_ext_provoking_vertex)
{
provoking_vertex_feature.provokingVertexLast = VK_TRUE;
Util::AddPointerToChain(&device_info, &provoking_vertex_feature);
}
if (m_optional_extensions.vk_ext_line_rasterization)
{
line_rasterization_feature.bresenhamLines = VK_TRUE;
Util::AddPointerToChain(&device_info, &line_rasterization_feature);
}
if (m_optional_extensions.vk_arm_rasterization_order_attachment_access)
{
rasterization_order_access_feature.rasterizationOrderColorAttachmentAccess = VK_TRUE;
@@ -724,12 +733,16 @@ namespace Vulkan
VkPhysicalDeviceFeatures2 features2 = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2};
VkPhysicalDeviceProvokingVertexFeaturesEXT provoking_vertex_features = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_FEATURES_EXT};
VkPhysicalDeviceLineRasterizationFeaturesEXT line_rasterization_feature = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LINE_RASTERIZATION_FEATURES_EXT};
VkPhysicalDeviceRasterizationOrderAttachmentAccessFeaturesARM rasterization_order_access_feature = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_FEATURES_ARM};
// add in optional feature structs
if (m_optional_extensions.vk_ext_provoking_vertex)
Util::AddPointerToChain(&features2, &provoking_vertex_features);
if (m_optional_extensions.vk_ext_line_rasterization)
Util::AddPointerToChain(&features2, &line_rasterization_feature);
if (m_optional_extensions.vk_arm_rasterization_order_attachment_access)
Util::AddPointerToChain(&features2, &rasterization_order_access_feature);
@@ -739,6 +752,7 @@ namespace Vulkan
// confirm we actually support it
m_optional_extensions.vk_ext_provoking_vertex &= (provoking_vertex_features.provokingVertexLast == VK_TRUE);
m_optional_extensions.vk_arm_rasterization_order_attachment_access &= (rasterization_order_access_feature.rasterizationOrderColorAttachmentAccess == VK_TRUE);
m_optional_extensions.vk_ext_line_rasterization &= (line_rasterization_feature.bresenhamLines == VK_TRUE);
VkPhysicalDeviceProperties2 properties2 = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2};
void** pNext = &properties2.pNext;
@@ -789,6 +803,8 @@ namespace Vulkan
Console.WriteLn("VK_EXT_provoking_vertex is %s",
m_optional_extensions.vk_ext_provoking_vertex ? "supported" : "NOT supported");
Console.WriteLn("VK_EXT_line_rasterization is %s",
m_optional_extensions.vk_ext_line_rasterization ? "supported" : "NOT supported");
Console.WriteLn("VK_EXT_calibrated_timestamps is %s",
m_optional_extensions.vk_ext_calibrated_timestamps ? "supported" : "NOT supported");
Console.WriteLn("VK_ARM_rasterization_order_attachment_access is %s",
+1
View File
@@ -52,6 +52,7 @@ namespace Vulkan
bool vk_ext_provoking_vertex : 1;
bool vk_ext_memory_budget : 1;
bool vk_ext_calibrated_timestamps : 1;
bool vk_ext_line_rasterization : 1;
bool vk_khr_driver_properties : 1;
bool vk_arm_rasterization_order_attachment_access : 1;
bool vk_khr_fragment_shader_barycentric : 1;
+42 -6
View File
@@ -48,6 +48,7 @@
#include "pcsx2/HostDisplay.h"
#include "pcsx2/HostSettings.h"
#include "pcsx2/INISettingsInterface.h"
#include "pcsx2/PAD/Host/PAD.h"
#include "pcsx2/PerformanceMetrics.h"
#include "pcsx2/VMManager.h"
@@ -79,6 +80,7 @@ static std::optional<bool> s_use_window;
// Owned by the GS thread.
static u32 s_dump_frame_number = 0;
static u32 s_loop_number = s_loop_count;
bool GSRunner::InitializeConfig()
{
@@ -102,6 +104,13 @@ bool GSRunner::InitializeConfig()
// we don't need any sound output
si.SetStringValue("SPU2/Output", "OutputModule", "nullout");
// none of the bindings are going to resolve to anything
PAD::ClearPortBindings(si, 0);
si.ClearSection("Hotkeys");
// make sure any gamesettings inis in your tree don't get loaded
si.SetBoolValue("EmuCore", "EnablePerGameSettings", false);
// force logging
si.SetBoolValue("Logging", "EnableSystemConsole", true);
si.SetBoolValue("Logging", "EnableTimestamps", true);
@@ -273,13 +282,15 @@ void Host::ReleaseHostDisplay(bool clear_state)
bool Host::BeginPresentFrame(bool frame_skip)
{
// when we wrap around, don't race other files
GSJoinSnapshotThreads();
// queue dumping of this frame
std::string dump_path(fmt::format("{}_frame{}.png", s_output_prefix, s_dump_frame_number));
GSQueueSnapshot(dump_path);
if (s_loop_number == 0)
{
// when we wrap around, don't race other files
GSJoinSnapshotThreads();
// queue dumping of this frame
std::string dump_path(fmt::format("{}_frame{}.png", s_output_prefix, s_dump_frame_number));
GSQueueSnapshot(dump_path);
}
if (g_host_display->BeginPresent(frame_skip))
return true;
@@ -519,6 +530,29 @@ static bool ParseCommandLineArgs(int argc, char* argv[], VMBootParameters& param
s_settings_interface.SetIntValue("EmuCore/GS", "Renderer", static_cast<int>(type));
continue;
}
else if (CHECK_ARG_PARAM("-renderhacks"))
{
std::string str(argv[++i]);
s_settings_interface.SetBoolValue("EmuCore/GS", "UserHacks", true);
if(str.find("af") != std::string::npos)
s_settings_interface.SetBoolValue("EmuCore/GS", "UserHacks_AutoFlush", true);
if (str.find("cpufb") != std::string::npos)
s_settings_interface.SetBoolValue("EmuCore/GS", "UserHacks_CPU_FB_Conversion", true);
if (str.find("dds") != std::string::npos)
s_settings_interface.SetBoolValue("EmuCore/GS", "UserHacks_DisableDepthSupport", true);
if (str.find("dpi") != std::string::npos)
s_settings_interface.SetBoolValue("EmuCore/GS", "UserHacks_DisablePartialInvalidation", true);
if (str.find("dsf") != std::string::npos)
s_settings_interface.SetBoolValue("EmuCore/GS", "UserHacks_DisableSafeFeatures", true);
if (str.find("tinrt") != std::string::npos)
s_settings_interface.SetBoolValue("EmuCore/GS", "UserHacks_TextureInsideRt", true);
if (str.find("plf") != std::string::npos)
s_settings_interface.SetBoolValue("EmuCore/GS", "preload_frame_with_gs_data", true);
continue;
}
else if (CHECK_ARG_PARAM("-logfile"))
{
const char* logfile = argv[++i];
@@ -627,6 +661,7 @@ int main(int argc, char* argv[])
// apply new settings (e.g. pick up renderer change)
VMManager::ApplySettings();
GSDumpReplayer::SetIsDumpRunner(true);
if (VMManager::Initialize(params))
{
@@ -651,6 +686,7 @@ void Host::CPUThreadVSync()
{
// update GS thread copy of frame number
GetMTGS().RunOnGSThread([frame_number = GSDumpReplayer::GetFrameNumber()]() { s_dump_frame_number = frame_number; });
GetMTGS().RunOnGSThread([loop_number = GSDumpReplayer::GetLoopCount()]() { s_loop_number = loop_number; });
// process any window messages (but we shouldn't really have any)
GSRunner::PumpPlatformMessages();
+10 -5
View File
@@ -16,7 +16,7 @@ def is_gs_path(path):
return False
def run_regression_test(runner, dumpdir, renderer, parallel, gspath):
def run_regression_test(runner, dumpdir, renderer, parallel, renderhacks, gspath):
args = [runner]
gsname = Path(gspath).name
while gsname.rfind('.') >= 0:
@@ -28,6 +28,10 @@ def run_regression_test(runner, dumpdir, renderer, parallel, gspath):
if renderer is not None:
args.extend(["-renderer", renderer])
if renderhacks is not None:
args.extend(["-renderhacks", renderhacks])
args.extend(["-dumpdir", real_dumpdir])
args.extend(["-logfile", os.path.join(real_dumpdir, "emulog.txt")])
@@ -46,7 +50,7 @@ def run_regression_test(runner, dumpdir, renderer, parallel, gspath):
subprocess.run(args)
def run_regression_tests(runner, gsdir, dumpdir, renderer, parallel=1):
def run_regression_tests(runner, gsdir, dumpdir, renderer, renderhacks, parallel=1):
paths = glob.glob(gsdir + "/*.*", recursive=True)
gamepaths = list(filter(is_gs_path, paths))
@@ -57,10 +61,10 @@ def run_regression_tests(runner, gsdir, dumpdir, renderer, parallel=1):
if parallel <= 1:
for game in gamepaths:
run_regression_test(runner, dumpdir, renderer, parallel, game)
run_regression_test(runner, dumpdir, renderer, parallel, renderhacks, game)
else:
print("Processing %u games on %u processors" % (len(gamepaths), parallel))
func = partial(run_regression_test, runner, dumpdir, renderer, parallel)
func = partial(run_regression_test, runner, dumpdir, renderer, parallel, renderhacks)
pool = multiprocessing.Pool(parallel)
pool.map(func, gamepaths)
pool.close()
@@ -76,10 +80,11 @@ if __name__ == "__main__":
parser.add_argument("-dumpdir", action="store", required=True, help="Base directory to dump frames to")
parser.add_argument("-renderer", action="store", required=False, help="Renderer to use")
parser.add_argument("-parallel", action="store", type=int, default=1, help="Number of proceeses to run")
parser.add_argument("-renderhacks", action="store", required=False, help="Enable HW Renering hacks")
args = parser.parse_args()
if not run_regression_tests(args.runner, os.path.realpath(args.gsdir), os.path.realpath(args.dumpdir), args.renderer, args.parallel):
if not run_regression_tests(args.runner, os.path.realpath(args.gsdir), os.path.realpath(args.dumpdir), args.renderer, args.renderhacks, args.parallel):
sys.exit(1)
else:
sys.exit(0)
+3 -6
View File
@@ -107,18 +107,15 @@ QVariant StackModel::headerData(int section, Qt::Orientation orientation, int ro
void StackModel::refreshData()
{
if (m_cpu.getCpuType() == BREAKPOINT_IOP)
return;
// Hopefully in the near future we can get a stack frame for
// each thread
beginResetModel();
for (const auto& thread : getEEThreads())
for (const auto& thread : m_cpu.GetThreadList())
{
if (thread.data.status == THS_RUN)
if (thread->Status() == ThreadStatus::THS_RUN)
{
m_stackFrames = MipsStackWalk::Walk(&m_cpu, m_cpu.getPC(), m_cpu.getRegister(0, 31), m_cpu.getRegister(0, 29),
thread.data.entry_init, thread.data.stack);
thread->EntryPoint(), thread->StackTop());
break;
}
}
+24 -28
View File
@@ -27,10 +27,7 @@ ThreadModel::ThreadModel(DebugInterface& cpu, QObject* parent)
int ThreadModel::rowCount(const QModelIndex&) const
{
if (m_cpu.getCpuType() == BREAKPOINT_EE)
return getEEThreads().size();
else
return 0;
return m_cpu.GetThreadList().size();
}
int ThreadModel::columnCount(const QModelIndex&) const
@@ -40,66 +37,65 @@ int ThreadModel::columnCount(const QModelIndex&) const
QVariant ThreadModel::data(const QModelIndex& index, int role) const
{
const auto threads = m_cpu.GetThreadList();
auto* const thread = threads.at(index.row()).get();
if (role == Qt::DisplayRole)
{
const auto thread = getEEThreads().at(index.row());
switch (index.column())
{
case ThreadModel::ID:
return thread.tid;
return thread->TID();
case ThreadModel::PC:
{
if (thread.data.status == THS_RUN)
if (thread->Status() == ThreadStatus::THS_RUN)
return QtUtils::FilledQStringFromValue(m_cpu.getPC(), 16);
else
return QtUtils::FilledQStringFromValue(thread.data.entry, 16);
return QtUtils::FilledQStringFromValue(thread->PC(), 16);
}
case ThreadModel::ENTRY:
return QtUtils::FilledQStringFromValue(thread.data.entry_init, 16);
return QtUtils::FilledQStringFromValue(thread->EntryPoint(), 16);
case ThreadModel::PRIORITY:
return QString::number(thread.data.currentPriority);
return QString::number(thread->Priority());
case ThreadModel::STATE:
{
const auto& state = ThreadStateStrings.find(thread.data.status);
const auto& state = ThreadStateStrings.find(thread->Status());
if (state != ThreadStateStrings.end())
return state->second;
else
return tr("INVALID");
return tr("INVALID");
}
case ThreadModel::WAIT_TYPE:
{
const auto& waitType = ThreadWaitStrings.find(thread.data.waitType);
const auto& waitType = ThreadWaitStrings.find(thread->Wait());
if (waitType != ThreadWaitStrings.end())
return waitType->second;
else
return tr("INVALID");
return tr("INVALID");
}
}
}
else if (role == Qt::UserRole)
{
const auto thread = getEEThreads().at(index.row());
switch (index.column())
{
case ThreadModel::ID:
return thread.tid;
return thread->TID();
case ThreadModel::PC:
{
if (thread.data.status == THS_RUN)
if (thread->Status() == ThreadStatus::THS_RUN)
return m_cpu.getPC();
else
return thread.data.entry;
return thread->PC();
}
case ThreadModel::ENTRY:
return thread.data.entry_init;
return thread->EntryPoint();
case ThreadModel::PRIORITY:
return thread.data.currentPriority;
return thread->Priority();
case ThreadModel::STATE:
return thread.data.status;
return static_cast<u32>(thread->Status());
case ThreadModel::WAIT_TYPE:
return thread.data.waitType;
return static_cast<u32>(thread->Wait());
default:
return QVariant();
}
+20 -12
View File
@@ -48,19 +48,27 @@ public:
void refreshData();
private:
const std::map<int, QString> ThreadStateStrings{
{THS_BAD, tr("BAD")},
{THS_RUN, tr("RUN")},
{THS_READY, tr("READY")},
{THS_WAIT, tr("WAIT")},
{THS_SUSPEND, tr("SUSPEND")},
{THS_WAIT_SUSPEND, tr("WAIT SUSPEND")},
{THS_DORMANT, tr("DORMANT")}};
const std::map<ThreadStatus, QString> ThreadStateStrings{
{ThreadStatus::THS_BAD, tr("BAD")},
{ThreadStatus::THS_RUN, tr("RUN")},
{ThreadStatus::THS_READY, tr("READY")},
{ThreadStatus::THS_WAIT, tr("WAIT")},
{ThreadStatus::THS_SUSPEND, tr("SUSPEND")},
{ThreadStatus::THS_WAIT_SUSPEND, tr("WAIT SUSPEND")},
{ThreadStatus::THS_DORMANT, tr("DORMANT")},
};
const std::map<int, QString> ThreadWaitStrings{
{WAIT_NONE, tr("NONE")},
{WAIT_WAKEUP_REQ, tr("WAKEUP REQUEST")},
{WAIT_SEMA, tr("SEMAPHORE")}};
const std::map<WaitState, QString> ThreadWaitStrings{
{WaitState::NONE, tr("NONE")},
{WaitState::WAKEUP_REQ, tr("WAKEUP REQUEST")},
{WaitState::SEMA, tr("SEMAPHORE")},
{WaitState::SLEEP, tr("SLEEP")},
{WaitState::DELAY, tr("DELAY")},
{WaitState::EVENTFLAG, tr("EVENTFLAG")},
{WaitState::MBOX, tr("MBOX")},
{WaitState::VPOOL, tr("VPOOL")},
{WaitState::FIXPOOL, tr("FIXPOOL")},
};
DebugInterface& m_cpu;
};
+6 -1
View File
@@ -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();
+12 -12
View File
@@ -595,7 +595,7 @@ namespace SettingWidgetBinder
/// Binds a widget's value to a setting, updating it when the value changes.
template <typename WidgetType>
static void BindWidgetToBoolSetting(
static inline void BindWidgetToBoolSetting(
SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, bool default_value)
{
using Accessor = SettingAccessor<WidgetType>;
@@ -636,7 +636,7 @@ namespace SettingWidgetBinder
}
template <typename WidgetType>
static void BindWidgetToIntSetting(
static inline void BindWidgetToIntSetting(
SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, int default_value, int option_offset = 0)
{
using Accessor = SettingAccessor<WidgetType>;
@@ -677,7 +677,7 @@ namespace SettingWidgetBinder
}
template <typename WidgetType>
static void BindWidgetToFloatSetting(
static inline void BindWidgetToFloatSetting(
SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, float default_value)
{
using Accessor = SettingAccessor<WidgetType>;
@@ -718,7 +718,7 @@ namespace SettingWidgetBinder
}
template <typename WidgetType>
static void BindWidgetToNormalizedSetting(
static inline void BindWidgetToNormalizedSetting(
SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, float range, float default_value)
{
using Accessor = SettingAccessor<WidgetType>;
@@ -759,7 +759,7 @@ namespace SettingWidgetBinder
}
template <typename WidgetType>
static void BindWidgetToStringSetting(
static inline void BindWidgetToStringSetting(
SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, std::string default_value = std::string())
{
using Accessor = SettingAccessor<WidgetType>;
@@ -804,7 +804,7 @@ namespace SettingWidgetBinder
}
template <typename WidgetType, typename DataType>
static void BindWidgetToEnumSetting(SettingsInterface* sif, WidgetType* widget, std::string section, std::string key,
static inline void BindWidgetToEnumSetting(SettingsInterface* sif, WidgetType* widget, std::string section, std::string key,
std::optional<DataType> (*from_string_function)(const char* str), const char* (*to_string_function)(DataType value),
DataType default_value)
{
@@ -866,7 +866,7 @@ namespace SettingWidgetBinder
}
template <typename WidgetType, typename DataType>
static void BindWidgetToEnumSetting(
static inline void BindWidgetToEnumSetting(
SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, const char** enum_names, DataType default_value)
{
using Accessor = SettingAccessor<WidgetType>;
@@ -928,7 +928,7 @@ namespace SettingWidgetBinder
}
template <typename WidgetType>
static void BindWidgetToEnumSetting(SettingsInterface* sif, WidgetType* widget, std::string section, std::string key,
static inline void BindWidgetToEnumSetting(SettingsInterface* sif, WidgetType* widget, std::string section, std::string key,
const char** enum_names, const char** enum_values, const char* default_value)
{
using Accessor = SettingAccessor<WidgetType>;
@@ -992,7 +992,7 @@ namespace SettingWidgetBinder
}
template <typename WidgetType>
static void BindWidgetToFolderSetting(SettingsInterface* sif, WidgetType* widget, QAbstractButton* browse_button,
static inline void BindWidgetToFolderSetting(SettingsInterface* sif, WidgetType* widget, QAbstractButton* browse_button,
QAbstractButton* open_button, QAbstractButton* reset_button, std::string section, std::string key, std::string default_value,
bool use_relative = true)
{
@@ -1068,7 +1068,7 @@ namespace SettingWidgetBinder
}
}
[[maybe_unused]] static void BindSliderToIntSetting(SettingsInterface* sif, QSlider* slider, QLabel* label, const QString& label_suffix,
static inline void BindSliderToIntSetting(SettingsInterface* sif, QSlider* slider, QLabel* label, const QString& label_suffix,
std::string section, std::string key, s32 default_value)
{
const s32 global_value = Host::GetBaseIntSettingValue(section.c_str(), key.c_str(), default_value);
@@ -1109,8 +1109,8 @@ namespace SettingWidgetBinder
});
slider->connect(slider, &QSlider::valueChanged, slider,
[sif, label, label_suffix, section = std::move(section), key = std::move(key),
orig_font = std::move(orig_font), bold_font = std::move(bold_font)](int value) {
[sif, label, label_suffix, section = std::move(section), key = std::move(key), orig_font = std::move(orig_font),
bold_font = std::move(bold_font)](int value) {
label->setText(QStringLiteral("%1%2").arg(value).arg(label_suffix));
if (label->font() != bold_font)
+47 -38
View File
@@ -42,10 +42,12 @@
ControllerBindingWidget::ControllerBindingWidget(QWidget* parent, ControllerSettingsDialog* dialog, u32 port)
: QWidget(parent)
, m_dialog(dialog)
, m_config_section(StringUtil::StdStringFromFormat("Pad%u", port + 1u))
, m_config_section(fmt::format("Pad{}", port + 1))
, m_port_number(port)
{
m_ui.setupUi(this);
m_ui.groupBox->setTitle(tr("Controller Port %1").arg(port + 1));
populateControllerTypes();
onTypeChanged();
@@ -113,10 +115,9 @@ void ControllerBindingWidget::onTypeChanged()
if (has_settings)
{
const QString settings_title(tr("%1 Settings").arg(qApp->translate("PAD", cinfo->display_name)));
const gsl::span<const SettingInfo> settings(cinfo->settings, cinfo->num_settings);
m_settings_widget = new ControllerCustomSettingsWidget(
settings, m_config_section, std::string(), settings_title, cinfo->name, getDialog(), m_ui.stackedWidget);
m_settings_widget =
new ControllerCustomSettingsWidget(settings, m_config_section, std::string(), cinfo->name, getDialog(), m_ui.stackedWidget);
m_ui.stackedWidget->addWidget(m_settings_widget);
}
@@ -458,8 +459,7 @@ void ControllerMacroEditWidget::updateBinds()
//////////////////////////////////////////////////////////////////////////
ControllerCustomSettingsWidget::ControllerCustomSettingsWidget(gsl::span<const SettingInfo> settings, std::string config_section,
std::string config_prefix, const QString& group_title, const char* translation_ctx, ControllerSettingsDialog* dialog,
QWidget* parent_widget)
std::string config_prefix, const char* translation_ctx, ControllerSettingsDialog* dialog, QWidget* parent_widget)
: QWidget(parent_widget)
, m_settings(settings)
, m_config_section(std::move(config_section))
@@ -469,22 +469,19 @@ ControllerCustomSettingsWidget::ControllerCustomSettingsWidget(gsl::span<const S
if (settings.empty())
return;
QGroupBox* gbox = new QGroupBox(group_title, this);
QGridLayout* gbox_layout = new QGridLayout(gbox);
createSettingWidgets(translation_ctx, gbox, gbox_layout);
QScrollArea* sarea = new QScrollArea(this);
QWidget* swidget = new QWidget(sarea);
sarea->setWidget(swidget);
sarea->setWidgetResizable(true);
sarea->setFrameShape(QFrame::StyledPanel);
sarea->setFrameShadow(QFrame::Sunken);
QGridLayout* swidget_layout = new QGridLayout(swidget);
createSettingWidgets(translation_ctx, swidget, swidget_layout);
QVBoxLayout* layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(gbox);
QHBoxLayout* bottom_hlayout = new QHBoxLayout();
QPushButton* restore_defaults = new QPushButton(tr("Restore Default Settings"), this);
restore_defaults->setIcon(QIcon::fromTheme(QStringLiteral("restart-line")));
connect(restore_defaults, &QPushButton::clicked, this, &ControllerCustomSettingsWidget::restoreDefaults);
bottom_hlayout->addStretch(1);
bottom_hlayout->addWidget(restore_defaults);
layout->addLayout(bottom_hlayout);
layout->addStretch(1);
layout->addWidget(sarea);
}
ControllerCustomSettingsWidget::~ControllerCustomSettingsWidget() = default;
@@ -503,8 +500,6 @@ static std::tuple<QString, QString> getPrefixAndSuffixForIntFormat(const QString
return std::tie(prefix, suffix);
}
#if 0
// Unused until we handle multiplier below.
static std::tuple<QString, QString, int> getPrefixAndSuffixForFloatFormat(const QString& format)
{
QString prefix, suffix;
@@ -532,7 +527,6 @@ static std::tuple<QString, QString, int> getPrefixAndSuffixForFloatFormat(const
return std::tie(prefix, suffix, decimals);
}
#endif
void ControllerCustomSettingsWidget::createSettingWidgets(const char* translation_ctx, QWidget* widget_parent, QGridLayout* layout)
{
@@ -569,7 +563,8 @@ void ControllerCustomSettingsWidget::createSettingWidgets(const char* translatio
sb->setPrefix(prefix);
sb->setSuffix(suffix);
}
SettingWidgetBinder::BindWidgetToIntSetting(sif, sb, m_config_section, std::move(key_name), si.IntegerDefaultValue());
ControllerSettingWidgetBinder::BindWidgetToInputProfileInt(
sif, sb, m_config_section, std::move(key_name), si.IntegerDefaultValue());
layout->addWidget(new QLabel(qApp->translate(translation_ctx, si.display_name), widget_parent), current_row, 0);
layout->addWidget(sb, current_row, 1, 1, 3);
current_row++;
@@ -582,7 +577,7 @@ void ControllerCustomSettingsWidget::createSettingWidgets(const char* translatio
cb->setObjectName(QString::fromUtf8(si.name));
for (u32 i = 0; si.options[i] != nullptr; i++)
cb->addItem(qApp->translate(translation_ctx, si.options[i]));
SettingWidgetBinder::BindWidgetToIntSetting(
ControllerSettingWidgetBinder::BindWidgetToInputProfileInt(
sif, cb, m_config_section, std::move(key_name), si.IntegerDefaultValue(), si.IntegerMinValue());
layout->addWidget(new QLabel(qApp->translate(translation_ctx, si.display_name), widget_parent), current_row, 0);
layout->addWidget(cb, current_row, 1, 1, 3);
@@ -594,11 +589,10 @@ void ControllerCustomSettingsWidget::createSettingWidgets(const char* translatio
{
QDoubleSpinBox* sb = new QDoubleSpinBox(widget_parent);
sb->setObjectName(QString::fromUtf8(si.name));
sb->setMinimum(si.FloatMinValue());
sb->setMaximum(si.FloatMaxValue());
sb->setSingleStep(si.FloatStepValue());
#if 0
// We can't use this until we handle multiplier.
sb->setMinimum(si.FloatMinValue() * si.multiplier);
sb->setMaximum(si.FloatMaxValue() * si.multiplier);
sb->setSingleStep(si.FloatStepValue() * si.multiplier);
if (si.format)
{
const auto [prefix, suffix, decimals] = getPrefixAndSuffixForFloatFormat(QString::fromUtf8(si.format));
@@ -607,8 +601,9 @@ void ControllerCustomSettingsWidget::createSettingWidgets(const char* translatio
sb->setDecimals(decimals);
sb->setSuffix(suffix);
}
#endif
SettingWidgetBinder::BindWidgetToFloatSetting(sif, sb, m_config_section, std::move(key_name), si.FloatDefaultValue());
ControllerSettingWidgetBinder::BindWidgetToInputProfileFloat(
sif, sb, m_config_section, std::move(key_name), si.FloatDefaultValue(), si.multiplier);
layout->addWidget(new QLabel(qApp->translate(translation_ctx, si.display_name), widget_parent), current_row, 0);
layout->addWidget(sb, current_row, 1, 1, 3);
current_row++;
@@ -619,7 +614,8 @@ void ControllerCustomSettingsWidget::createSettingWidgets(const char* translatio
{
QLineEdit* le = new QLineEdit(widget_parent);
le->setObjectName(QString::fromUtf8(si.name));
SettingWidgetBinder::BindWidgetToStringSetting(sif, le, m_config_section, std::move(key_name), si.StringDefaultValue());
ControllerSettingWidgetBinder::BindWidgetToInputProfileString(
sif, le, m_config_section, std::move(key_name), si.StringDefaultValue());
layout->addWidget(new QLabel(qApp->translate(translation_ctx, si.display_name), widget_parent), current_row, 0);
layout->addWidget(le, current_row, 1, 1, 3);
current_row++;
@@ -641,7 +637,8 @@ void ControllerCustomSettingsWidget::createSettingWidgets(const char* translatio
for (u32 i = 0; si.options[i] != nullptr; i++)
cb->addItem(qApp->translate(translation_ctx, si.options[i]), QString::fromUtf8(si.options[i]));
}
SettingWidgetBinder::BindWidgetToStringSetting(sif, cb, m_config_section, std::move(key_name), si.StringDefaultValue());
ControllerSettingWidgetBinder::BindWidgetToInputProfileString(
sif, cb, m_config_section, std::move(key_name), si.StringDefaultValue());
layout->addWidget(new QLabel(qApp->translate(translation_ctx, si.display_name), widget_parent), current_row, 0);
layout->addWidget(cb, current_row, 1, 1, 3);
current_row++;
@@ -653,7 +650,8 @@ void ControllerCustomSettingsWidget::createSettingWidgets(const char* translatio
QLineEdit* le = new QLineEdit(widget_parent);
le->setObjectName(QString::fromUtf8(si.name));
QPushButton* browse_button = new QPushButton(tr("Browse..."), widget_parent);
SettingWidgetBinder::BindWidgetToStringSetting(sif, le, m_config_section, std::move(key_name), si.StringDefaultValue());
ControllerSettingWidgetBinder::BindWidgetToInputProfileString(
sif, le, m_config_section, std::move(key_name), si.StringDefaultValue());
connect(browse_button, &QPushButton::clicked, [this, le]() {
const QString path(QDir::toNativeSeparators(QFileDialog::getOpenFileName(this, tr("Select File"))));
if (!path.isEmpty())
@@ -677,6 +675,16 @@ void ControllerCustomSettingsWidget::createSettingWidgets(const char* translatio
layout->addItem(new QSpacerItem(1, 10, QSizePolicy::Minimum, QSizePolicy::Fixed), current_row++, 0, 1, 4);
}
QHBoxLayout* bottom_hlayout = new QHBoxLayout();
QPushButton* restore_defaults = new QPushButton(tr("Restore Default Settings"), this);
restore_defaults->setIcon(QIcon::fromTheme(QStringLiteral("restart-line")));
connect(restore_defaults, &QPushButton::clicked, this, &ControllerCustomSettingsWidget::restoreDefaults);
bottom_hlayout->addStretch(1);
bottom_hlayout->addWidget(restore_defaults);
layout->addLayout(bottom_hlayout, current_row++, 0, 1, 4);
layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Expanding), current_row++, 0, 1, 4);
}
void ControllerCustomSettingsWidget::restoreDefaults()
@@ -715,7 +723,7 @@ void ControllerCustomSettingsWidget::restoreDefaults()
{
QDoubleSpinBox* widget = findChild<QDoubleSpinBox*>(QString::fromStdString(si.name));
if (widget)
widget->setValue(si.FloatDefaultValue());
widget->setValue(si.FloatDefaultValue() * si.multiplier);
}
break;
@@ -851,10 +859,12 @@ ControllerBindingWidget_Base* ControllerBindingWidget_DualShock2::createInstance
USBDeviceWidget::USBDeviceWidget(QWidget* parent, ControllerSettingsDialog* dialog, u32 port)
: QWidget(parent)
, m_dialog(dialog)
, m_config_section(StringUtil::StdStringFromFormat("USB%u", port + 1u))
, m_config_section(fmt::format("USB{}", port + 1))
, m_port_number(port)
{
m_ui.setupUi(this);
m_ui.groupBox->setTitle(tr("USB Port %1").arg(port + 1));
populateDeviceTypes();
populatePages();
@@ -926,9 +936,8 @@ void USBDeviceWidget::populatePages()
if (!settings.empty())
{
const QString settings_title(tr("Device Settings"));
m_settings_widget = new ControllerCustomSettingsWidget(
settings, m_config_section, m_device_type + "_", settings_title, m_device_type.c_str(), m_dialog, m_ui.stackedWidget);
settings, m_config_section, m_device_type + "_", m_device_type.c_str(), m_dialog, m_ui.stackedWidget);
m_ui.stackedWidget->addWidget(m_settings_widget);
}
+1 -1
View File
@@ -139,7 +139,7 @@ class ControllerCustomSettingsWidget : public QWidget
public:
ControllerCustomSettingsWidget(gsl::span<const SettingInfo> settings, std::string config_section, std::string config_prefix,
const QString& group_title, const char* translation_ctx, ControllerSettingsDialog* dialog, QWidget* parent_widget);
const char* translation_ctx, ControllerSettingsDialog* dialog, QWidget* parent_widget);
~ControllerCustomSettingsWidget();
private Q_SLOTS:
@@ -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);
+33
View File
@@ -17,6 +17,7 @@
#include "QtHost.h"
#include "QtUtils.h"
#include "Settings/ControllerSettingWidgetBinder.h"
#include "Settings/InputBindingDialog.h"
#include "Settings/InputBindingWidget.h"
#include <QtCore/QTimer>
@@ -24,6 +25,8 @@
#include <QtGui/QMouseEvent>
#include <QtGui/QWheelEvent>
#include "fmt/format.h"
// _BitScanForward()
#include "pcsx2/GS/GSIntrin.h"
@@ -45,6 +48,26 @@ InputBindingDialog::InputBindingDialog(SettingsInterface* sif, InputBindingInfo:
connect(m_ui.clearBindings, &QPushButton::clicked, this, &InputBindingDialog::onClearBindingsButtonClicked);
connect(m_ui.buttonBox, &QDialogButtonBox::rejected, [this]() { done(0); });
updateList();
// Only show the sensitivity controls for binds where it's applicable.
if (bind_type == InputBindingInfo::Type::Button || bind_type == InputBindingInfo::Type::Axis ||
bind_type == InputBindingInfo::Type::HalfAxis)
{
ControllerSettingWidgetBinder::BindWidgetToInputProfileNormalized(
sif, m_ui.sensitivity, m_section_name, fmt::format("{}Scale", m_key_name), 100.0f, 1.0f);
ControllerSettingWidgetBinder::BindWidgetToInputProfileNormalized(
sif, m_ui.deadzone, m_section_name, fmt::format("{}Deadzone", m_key_name), 100.0f, 0.0f);
connect(m_ui.sensitivity, &QSlider::valueChanged, this, &InputBindingDialog::onSensitivityChanged);
connect(m_ui.deadzone, &QSlider::valueChanged, this, &InputBindingDialog::onDeadzoneChanged);
onSensitivityChanged(m_ui.sensitivity->value());
onDeadzoneChanged(m_ui.deadzone->value());
}
else
{
m_ui.verticalLayout->removeWidget(m_ui.sensitivityWidget);
}
}
InputBindingDialog::~InputBindingDialog()
@@ -318,6 +341,16 @@ void InputBindingDialog::inputManagerHookCallback(InputBindingKey key, float val
}
}
void InputBindingDialog::onSensitivityChanged(int value)
{
m_ui.sensitivityValue->setText(tr("%1%").arg(value));
}
void InputBindingDialog::onDeadzoneChanged(int value)
{
m_ui.deadzoneValue->setText(tr("%1%").arg(value));
}
void InputBindingDialog::hookInputManager()
{
InputManager::SetHook([this](InputBindingKey key, float value) {
+3
View File
@@ -41,6 +41,9 @@ protected Q_SLOTS:
void onInputListenTimerTimeout();
void inputManagerHookCallback(InputBindingKey key, float value);
void onSensitivityChanged(int value);
void onDeadzoneChanged(int value);
protected:
enum : u32
{
+88 -1
View File
@@ -10,7 +10,7 @@
<x>0</x>
<y>0</y>
<width>533</width>
<height>283</height>
<height>266</height>
</rect>
</property>
<property name="windowTitle">
@@ -30,6 +30,93 @@
<item>
<widget class="QListWidget" name="bindingList"/>
</item>
<item>
<widget class="QWidget" name="sensitivityWidget" native="true">
<layout class="QGridLayout" name="sensitivityLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="sensitivityLabel">
<property name="text">
<string>Sensitivity:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSlider" name="sensitivity">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>200</number>
</property>
<property name="value">
<number>100</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TicksBelow</enum>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="sensitivityValue">
<property name="text">
<string>100%</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="deadzoneLabel">
<property name="text">
<string>Deadzone:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSlider" name="deadzone">
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>1</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TicksBelow</enum>
</property>
<property name="tickInterval">
<number>5</number>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLabel" name="deadzoneValue">
<property name="text">
<string>100%</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLabel" name="status">
<property name="text">
+115
View File
@@ -0,0 +1,115 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "CDVD/CDVDdiscReader.h"
#ifdef __APPLE__
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/storage/IOMedia.h>
#include <IOKit/storage/IOCDMedia.h>
#include <IOKit/storage/IODVDMedia.h>
#include <IOKit/IOBSD.h>
#include <IOKit/IOKitLib.h>
#endif
#include <fcntl.h>
#include <unistd.h>
#ifdef __APPLE__
std::vector<std::string> GetDriveListFromClasses(CFMutableDictionaryRef classes)
{
io_iterator_t iterator = IO_OBJECT_NULL;
kern_return_t result;
std::vector<std::string> drives;
CFDictionarySetValue(classes, CFSTR(kIOMediaEjectableKey), kCFBooleanTrue);
result = IOServiceGetMatchingServices(kIOMasterPortDefault, classes, &iterator);
if (result != KERN_SUCCESS)
return drives;
while (io_object_t media = IOIteratorNext(iterator))
{
CFTypeRef path_cfstr = IORegistryEntryCreateCFProperty(media, CFSTR(kIOBSDNameKey), kCFAllocatorDefault, 0);
if (path_cfstr)
{
char path[PATH_MAX] = {0};
strlcpy(path, "/dev/r", PATH_MAX);
size_t path_prefix_len = strnlen(path, PATH_MAX);
result = CFStringGetCString((CFStringRef)path_cfstr, path + path_prefix_len, PATH_MAX - path_prefix_len, kCFStringEncodingUTF8);
if (result)
{
drives.emplace_back(path);
}
CFRelease(path_cfstr);
}
IOObjectRelease(media);
}
IOObjectRelease(iterator);
return drives;
}
#endif
std::vector<std::string> GetOpticalDriveList()
{
#ifdef __APPLE__
std::vector<std::string> drives;
if (CFMutableDictionaryRef cd_classes = IOServiceMatching(kIOCDMediaClass))
{
std::vector<std::string> cd = GetDriveListFromClasses(cd_classes);
drives.insert(drives.end(), cd.begin(), cd.end());
}
if (CFMutableDictionaryRef dvd_classes = IOServiceMatching(kIODVDMediaClass))
{
std::vector<std::string> dvd = GetDriveListFromClasses(dvd_classes);
drives.insert(drives.end(), dvd.begin(), dvd.end());
}
return drives;
#else
return {};
#endif
}
void GetValidDrive(std::string& drive)
{
if (!drive.empty())
{
#ifdef __APPLE__
int fd = open(drive.c_str(), O_RDONLY | O_NONBLOCK);
if (fd != -1)
{
close(fd);
}
else
{
drive.clear();
}
#else
drive.clear();
#endif
}
if (drive.empty())
{
auto drives = GetOpticalDriveList();
if (!drives.empty())
drive = drives.front();
}
if (!drive.empty())
DevCon.WriteLn("CDVD: Opening drive '%s'...", drive.c_str());
}
+263
View File
@@ -0,0 +1,263 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "CDVD/CDVDdiscReader.h"
#include "CDVD/CDVD.h"
#ifdef __APPLE__
#include <IOKit/storage/IOCDMediaBSDClient.h>
#include <IOKit/storage/IODVDMediaBSDClient.h>
#endif
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <cerrno>
#include <cstring>
IOCtlSrc::IOCtlSrc(decltype(m_filename) filename)
: m_filename(filename)
{
if (!Reopen())
throw std::runtime_error(" * CDVD: Error opening source.\n");
}
IOCtlSrc::~IOCtlSrc()
{
if (m_device != -1)
{
SetSpindleSpeed(true);
close(m_device);
}
}
bool IOCtlSrc::Reopen()
{
if (m_device != -1)
close(m_device);
// O_NONBLOCK allows a valid file descriptor to be returned even if the
// drive is empty. Probably does other things too.
m_device = open(m_filename.c_str(), O_RDONLY | O_NONBLOCK);
if (m_device == -1)
return false;
// DVD detection MUST be first on Linux - The TOC ioctls work for both
// CDs and DVDs.
if (ReadDVDInfo() || ReadCDInfo())
SetSpindleSpeed(false);
return true;
}
void IOCtlSrc::SetSpindleSpeed(bool restore_defaults) const
{
u16 speed = restore_defaults ? 0xFFFF : m_media_type >= 0 ? 5540 :
3600;
int ioctl_code = m_media_type >= 0 ? DKIOCDVDSETSPEED : DKIOCCDSETSPEED;
if (ioctl(m_device, ioctl_code, &speed) == -1)
{
DevCon.Warning("CDVD: Failed to set spindle speed: %s", strerror(errno));
}
else if (!restore_defaults)
{
DevCon.WriteLn("CDVD: Spindle speed set to %d", speed);
}
}
u32 IOCtlSrc::GetSectorCount() const
{
return m_sectors;
}
u32 IOCtlSrc::GetLayerBreakAddress() const
{
return m_layer_break;
}
s32 IOCtlSrc::GetMediaType() const
{
return m_media_type;
}
const std::vector<toc_entry>& IOCtlSrc::ReadTOC() const
{
return m_toc;
}
bool IOCtlSrc::ReadSectors2048(u32 sector, u32 count, u8* buffer) const
{
const ssize_t bytes_to_read = 2048 * count;
ssize_t bytes_read = pread(m_device, buffer, bytes_to_read, sector * 2048ULL);
if (bytes_read == bytes_to_read)
return true;
if (bytes_read == -1)
DevCon.Warning("CDVD: read sectors %u-%u failed: %s",
sector, sector + count - 1, strerror(errno));
else
DevCon.Warning("CDVD: read sectors %u-%u: %zd bytes read, %zd bytes expected",
sector, sector + count - 1, bytes_read, bytes_to_read);
return false;
}
bool IOCtlSrc::ReadSectors2352(u32 sector, u32 count, u8* buffer) const
{
#ifdef __APPLE__
dk_cd_read_t desc;
memset(&desc, 0, sizeof(dk_cd_read_t));
desc.sectorArea = kCDSectorAreaSync | kCDSectorAreaHeader | kCDSectorAreaSubHeader | kCDSectorAreaUser | kCDSectorAreaAuxiliary;
desc.sectorType = kCDSectorTypeUnknown;
for (u32 i = 0; i < count; ++i)
{
desc.offset = (sector + i) * 2352ULL;
desc.buffer = buffer + i * 2352;
desc.bufferLength = 2352;
if (ioctl(m_device, DKIOCCDREAD, &desc) == -1)
{
DevCon.Warning("CDVD: DKIOCCDREAD sector %u failed: %s",
sector + i, strerror(errno));
return false;
}
}
return true;
#else
return false;
#endif
}
bool IOCtlSrc::ReadDVDInfo()
{
#ifdef __APPLE__
dk_dvd_read_structure_t dvdrs;
memset(&dvdrs, 0, sizeof(dk_dvd_read_structure_t));
dvdrs.format = kDVDStructureFormatPhysicalFormatInfo;
dvdrs.layer = 0;
DVDPhysicalFormatInfo layer0;
dvdrs.buffer = &layer0;
dvdrs.bufferLength = sizeof(DVDPhysicalFormatInfo);
int ret = ioctl(m_device, DKIOCDVDREADSTRUCTURE, &dvdrs);
if (ret == -1)
{
return false;
}
u32 start_sector = *(u32*)layer0.startingPhysicalSectorNumberOfDataArea;
u32 end_sector = *(u32*)layer0.endPhysicalSectorNumberOfDataArea;
if (layer0.numberOfLayers == 0)
{
// Single layer
m_media_type = 0;
m_layer_break = 0;
m_sectors = end_sector - start_sector + 1;
}
else if (layer0.trackPath == 0)
{
// Dual layer, Parallel Track Path
DVDPhysicalFormatInfo layer1;
dvdrs.layer = 1;
dvdrs.buffer = &layer1;
dvdrs.bufferLength = sizeof(DVDPhysicalFormatInfo);
ret = ioctl(m_device, DKIOCDVDREADSTRUCTURE, &dvdrs);
if (ret == -1)
return false;
u32 layer1_start_sector = *(u32*)layer1.startingPhysicalSectorNumberOfDataArea;
u32 layer1_end_sector = *(u32*)layer1.endPhysicalSectorNumberOfDataArea;
m_media_type = 1;
m_layer_break = end_sector - start_sector;
m_sectors = end_sector - start_sector + 1 + layer1_end_sector - layer1_start_sector + 1;
}
else
{
// Dual layer, Opposite Track Path
u32 end_sector_layer0 = *(u32*)layer0.endSectorNumberInLayerZero;
m_media_type = 2;
m_layer_break = end_sector_layer0 - start_sector;
m_sectors = end_sector_layer0 - start_sector + 1 + end_sector - (~end_sector_layer0 & 0xFFFFFFU) + 1;
}
return true;
#else
return false;
#endif
}
bool IOCtlSrc::ReadCDInfo()
{
#ifdef __APPLE__
u8* buffer = (u8*)malloc(2048);
dk_cd_read_toc_t cdrt;
memset(&cdrt, 0, sizeof(dk_cd_read_toc_t));
cdrt.format = kCDTOCFormatTOC;
cdrt.formatAsTime = 1;
cdrt.address.track = 0;
cdrt.buffer = buffer;
cdrt.bufferLength = 2048;
memset(buffer, 0, 2048);
if (ioctl(m_device, DKIOCCDREADTOC, &cdrt) == -1)
{
DevCon.Warning("CDVD: DKIOCCDREADTOC failed: %s\n", strerror(errno));
return false;
}
CDTOC* toc = (CDTOC*)buffer;
u32 desc_count = CDTOCGetDescriptorCount(toc);
for (u32 i = 0; i < desc_count; ++i)
{
CDTOCDescriptor desc = toc->descriptors[i];
if (desc.point < 0xa0 && desc.adr == 1)
{
u32 lba = CDConvertMSFToLBA(desc.p);
m_toc.push_back({lba, desc.point, desc.adr, desc.control});
}
else if (desc.point == 0xa2) // lead out, use to get total sector count
{
m_sectors = CDConvertMSFToLBA(desc.p);
}
}
m_media_type = -1;
free(buffer);
return true;
#else
return false;
#endif
}
bool IOCtlSrc::DiscReady()
{
#ifdef __APPLE__
if (m_device == -1)
return false;
if (!m_sectors)
{
Reopen();
}
return !!m_sectors;
#else
return false;
#endif
}
-11
View File
@@ -16,18 +16,14 @@
#include "PrecompiledHeader.h"
#include "CDVD/CDVDdiscReader.h"
#ifdef __linux__
#include <libudev.h>
#include <linux/cdrom.h>
#endif
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
std::vector<std::string> GetOpticalDriveList()
{
#ifdef __linux__
udev* udev_context = udev_new();
if (!udev_context)
return {};
@@ -56,16 +52,12 @@ std::vector<std::string> GetOpticalDriveList()
udev_unref(udev_context);
return drives;
#else
return {};
#endif
}
void GetValidDrive(std::string& drive)
{
if (!drive.empty())
{
#ifdef __linux__
int fd = open(drive.c_str(), O_RDONLY | O_NONBLOCK);
if (fd != -1)
{
@@ -77,9 +69,6 @@ void GetValidDrive(std::string& drive)
{
drive.clear();
}
#else
drive.clear();
#endif
}
if (drive.empty())
{
-19
View File
@@ -17,10 +17,7 @@
#include "CDVD/CDVDdiscReader.h"
#include "CDVD/CDVD.h"
#ifdef __linux__
#include <linux/cdrom.h>
#endif
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
@@ -108,7 +105,6 @@ bool IOCtlSrc::ReadSectors2048(u32 sector, u32 count, u8* buffer) const
bool IOCtlSrc::ReadSectors2352(u32 sector, u32 count, u8* buffer) const
{
#ifdef __linux__
union
{
cdrom_msf msf;
@@ -130,14 +126,10 @@ bool IOCtlSrc::ReadSectors2352(u32 sector, u32 count, u8* buffer) const
}
return true;
#else
return false;
#endif
}
bool IOCtlSrc::ReadDVDInfo()
{
#ifdef __linux__
dvd_struct dvdrs;
dvdrs.type = DVD_STRUCT_PHYSICAL;
dvdrs.physical.layer_num = 0;
@@ -180,14 +172,10 @@ bool IOCtlSrc::ReadDVDInfo()
}
return true;
#else
return false;
#endif
}
bool IOCtlSrc::ReadCDInfo()
{
#ifdef __linux__
cdrom_tochdr header;
if (ioctl(m_device, CDROMREADTOCHDR, &header) == -1)
@@ -214,14 +202,10 @@ bool IOCtlSrc::ReadCDInfo()
m_media_type = -1;
return true;
#else
return false;
#endif
}
bool IOCtlSrc::DiscReady()
{
#ifdef __linux__
if (m_device == -1)
return false;
@@ -239,7 +223,4 @@ bool IOCtlSrc::DiscReady()
}
return !!m_sectors;
#else
return false;
#endif
}
+4 -4
View File
@@ -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
)
+51 -10
View File
@@ -15,29 +15,70 @@
#include "PrecompiledHeader.h"
#include "BiosDebugData.h"
#include "IopMem.h"
#include "Memory.h"
std::vector<EEThread> getEEThreads()
{
std::vector<EEThread> threads;
if (CurrentBiosInformation.threadListAddr <= 0)
std::vector<std::unique_ptr<BiosThread>> getEEThreads()
{
std::vector<std::unique_ptr<BiosThread>> threads;
if (CurrentBiosInformation.eeThreadListAddr <= 0)
return threads;
const u32 start = CurrentBiosInformation.threadListAddr & 0x3fffff;
const u32 start = CurrentBiosInformation.eeThreadListAddr & 0x3fffff;
for (int tid = 0; tid < 256; tid++)
{
EEThread thread;
EEInternalThread* internal = static_cast<EEInternalThread*>(PSM(start + tid * sizeof(EEInternalThread)));
if (internal->status != THS_BAD)
if (internal->status != (int)ThreadStatus::THS_BAD)
{
thread.tid = tid;
thread.data = *internal;
threads.push_back(thread);
auto thread = std::make_unique<EEThread>(tid, *internal);
threads.push_back(std::move(thread));
}
}
return threads;
}
std::vector<std::unique_ptr<BiosThread>> getIOPThreads()
{
std::vector<std::unique_ptr<BiosThread>> threads;
if (CurrentBiosInformation.iopThreadListAddr <= 0)
return threads;
u32 item = iopMemRead32(CurrentBiosInformation.iopThreadListAddr);
while (item != 0)
{
IOPInternalThread data{};
u16 tag = iopMemRead16(item + 0x8);
if (tag != 0x7f01)
{
// something went wrong
return {};
}
data.stackTop = iopMemRead32(item + 0x3c);
data.status = iopMemRead8(item + 0xc);
data.tid = iopMemRead16(item + 0xa);
data.entrypoint = iopMemRead32(item + 0x38);
data.waitstate = iopMemRead16(item + 0xe);
data.initPriority = iopMemRead16(item + 0x2e);
data.SavedSP = iopMemRead32(item + 0x10);
data.PC = iopMemRead32(data.SavedSP + 0x8c);
auto thread = std::make_unique<IOPThread>(data);
threads.push_back(std::move(thread));
item = iopMemRead32(item + 0x24);
}
return threads;
}
+149 -20
View File
@@ -15,9 +15,24 @@
#pragma once
#include "common/Pcsx2Types.h"
#include <memory>
#include <vector>
#include "ps2/BiosTools.h"
struct EEInternalThread { // internal struct
enum class ThreadStatus
{
THS_BAD = 0x00,
THS_RUN = 0x01,
THS_READY = 0x02,
THS_WAIT = 0x04,
THS_SUSPEND = 0x08,
THS_WAIT_SUSPEND = 0x0C,
THS_DORMANT = 0x10,
};
struct EEInternalThread
{ // internal struct
u32 prev;
u32 next;
int status;
@@ -40,26 +55,140 @@ struct EEInternalThread { // internal struct
u32 heap_base;
};
enum {
THS_BAD = 0x00,
THS_RUN = 0x01,
THS_READY = 0x02,
THS_WAIT = 0x04,
THS_SUSPEND = 0x08,
THS_WAIT_SUSPEND = 0x0C,
THS_DORMANT = 0x10,
};
enum {
WAIT_NONE = 0,
WAIT_WAKEUP_REQ = 1,
WAIT_SEMA = 2,
};
struct EEThread
// Not the full struct, just what we care about
struct IOPInternalThread
{
int tid;
u32 tid;
u32 PC;
u32 stackTop;
u32 SavedSP;
u32 status;
u32 entrypoint;
u32 waitstate;
u32 initPriority;
};
enum class IOPWaitStatus
{
TSW_SLEEP = 1,
TSW_DELAY = 2,
TSW_SEMA = 3,
TSW_EVENTFLAG = 4,
TSW_MBX = 5,
TSW_VPL = 6,
TSW_FPL = 7,
};
enum class EEWaitStatus
{
WAIT_NONE = 0,
WAIT_WAKEUP_REQ = 1,
WAIT_SEMA = 2,
};
enum class WaitState
{
NONE,
WAKEUP_REQ,
SEMA,
SLEEP,
DELAY,
EVENTFLAG,
MBOX,
VPOOL,
FIXPOOL,
};
class BiosThread
{
public:
virtual ~BiosThread() = default;
[[nodiscard]] virtual u32 TID() const = 0;
[[nodiscard]] virtual u32 PC() const = 0;
[[nodiscard]] virtual ThreadStatus Status() const = 0;
[[nodiscard]] virtual WaitState Wait() const = 0;
[[nodiscard]] virtual u32 EntryPoint() const = 0;
[[nodiscard]] virtual u32 StackTop() const = 0;
[[nodiscard]] virtual u32 Priority() const = 0;
};
class EEThread : public BiosThread
{
public:
EEThread(int tid, EEInternalThread th)
: tid(tid)
, data(th)
{
}
~EEThread() override = default;
[[nodiscard]] u32 TID() const override { return tid; };
[[nodiscard]] u32 PC() const override { return data.entry; };
[[nodiscard]] ThreadStatus Status() const override { return static_cast<ThreadStatus>(data.status); };
[[nodiscard]] WaitState Wait() const override
{
auto wait = static_cast<EEWaitStatus>(data.waitType);
switch (wait)
{
case EEWaitStatus::WAIT_NONE:
return WaitState::NONE;
case EEWaitStatus::WAIT_WAKEUP_REQ:
return WaitState::WAKEUP_REQ;
case EEWaitStatus::WAIT_SEMA:
return WaitState::SEMA;
}
return WaitState::NONE;
};
[[nodiscard]] u32 EntryPoint() const override { return data.entry_init; };
[[nodiscard]] u32 StackTop() const override { return data.stack; };
[[nodiscard]] u32 Priority() const override { return data.currentPriority; };
private:
u32 tid;
EEInternalThread data;
};
std::vector<EEThread> getEEThreads();
class IOPThread : public BiosThread
{
public:
IOPThread(IOPInternalThread th)
: data(th)
{
}
~IOPThread() override = default;
[[nodiscard]] u32 TID() const override { return data.tid; };
[[nodiscard]] u32 PC() const override { return data.PC; };
[[nodiscard]] ThreadStatus Status() const override { return static_cast<ThreadStatus>(data.status); };
[[nodiscard]] WaitState Wait() const override
{
auto wait = static_cast<IOPWaitStatus>(data.waitstate);
switch (wait)
{
case IOPWaitStatus::TSW_DELAY:
return WaitState::DELAY;
case IOPWaitStatus::TSW_EVENTFLAG:
return WaitState::EVENTFLAG;
case IOPWaitStatus::TSW_SLEEP:
return WaitState::SLEEP;
case IOPWaitStatus::TSW_SEMA:
return WaitState::SEMA;
case IOPWaitStatus::TSW_MBX:
return WaitState::MBOX;
case IOPWaitStatus::TSW_VPL:
return WaitState::VPOOL;
case IOPWaitStatus::TSW_FPL:
return WaitState::FIXPOOL;
}
return WaitState::NONE;
};
[[nodiscard]] u32 EntryPoint() const override { return data.entrypoint; };
[[nodiscard]] u32 StackTop() const override { return data.stackTop; };
[[nodiscard]] u32 Priority() const override { return data.initPriority; };
private:
IOPInternalThread data;
};
std::vector<std::unique_ptr<BiosThread>> getIOPThreads();
std::vector<std::unique_ptr<BiosThread>> getEEThreads();
+11
View File
@@ -728,6 +728,12 @@ SymbolMap& R5900DebugInterface::GetSymbolMap() const
return R5900SymbolMap;
}
std::vector<std::unique_ptr<BiosThread>> R5900DebugInterface::GetThreadList() const
{
return getEEThreads();
}
//
// R3000DebugInterface
//
@@ -1000,3 +1006,8 @@ SymbolMap& R3000DebugInterface::GetSymbolMap() const
{
return R3000SymbolMap;
}
std::vector<std::unique_ptr<BiosThread>> R3000DebugInterface::GetThreadList() const
{
return getIOPThreads();
}
+4
View File
@@ -14,6 +14,7 @@
*/
#pragma once
#include "DebugTools/BiosDebugData.h"
#include "MemoryTypes.h"
#include "ExpressionParser.h"
#include "SymbolMap.h"
@@ -84,6 +85,7 @@ public:
virtual u32 getCycles() = 0;
virtual BreakPointCpu getCpuType() = 0;
[[nodiscard]] virtual SymbolMap& GetSymbolMap() const = 0;
[[nodiscard]] virtual std::vector<std::unique_ptr<BiosThread>> GetThreadList() const = 0;
bool initExpression(const char* exp, PostfixExpression& dest);
bool parseExpression(PostfixExpression& exp, u64& dest);
@@ -130,6 +132,7 @@ public:
void setPc(u32 newPc) override;
void setRegister(int cat, int num, u128 newValue) override;
[[nodiscard]] SymbolMap& GetSymbolMap() const override;
[[nodiscard]] std::vector<std::unique_ptr<BiosThread>> GetThreadList() const override;
std::string disasm(u32 address, bool simplify) override;
bool isValidAddress(u32 address) override;
@@ -168,6 +171,7 @@ public:
void setPc(u32 newPc) override;
void setRegister(int cat, int num, u128 newValue) override;
[[nodiscard]] SymbolMap& GetSymbolMap() const override;
[[nodiscard]] std::vector<std::unique_ptr<BiosThread>> GetThreadList() const override;
std::string disasm(u32 address, bool simplify) override;
bool isValidAddress(u32 address) override;
+19 -6
View File
@@ -105,6 +105,7 @@ namespace InputManager
static bool ParseBindingAndGetSource(const std::string_view& binding, InputBindingKey* key, InputSource** source);
static bool IsAxisHandler(const InputEventHandler& handler);
static float ApplySingleBindingScale(float sensitivity, float deadzone, float value);
static void AddHotkeyBindings(SettingsInterface& si);
static void AddPadBindings(SettingsInterface& si, u32 pad, const char* default_type);
@@ -585,6 +586,12 @@ std::string InputManager::GetPointerDeviceName(u32 pointer_index)
// Binding Enumeration
// ------------------------------------------------------------------------
float InputManager::ApplySingleBindingScale(float scale, float deadzone, float value)
{
const float svalue = std::clamp(value * scale, 0.0f, 1.0f);
return (deadzone > 0.0f && svalue < deadzone) ? 0.0f : svalue;
}
std::vector<const HotkeyInfo*> InputManager::GetHotkeyList()
{
std::vector<const HotkeyInfo*> ret;
@@ -613,7 +620,7 @@ void InputManager::AddHotkeyBindings(SettingsInterface& si)
void InputManager::AddPadBindings(SettingsInterface& si, u32 pad_index, const char* default_type)
{
const std::string section(StringUtil::StdStringFromFormat("Pad%u", pad_index + 1));
const std::string section(fmt::format("Pad{}", pad_index + 1));
const std::string type(si.GetStringValue(section.c_str(), "Type", default_type));
if (type.empty() || type == "None")
return;
@@ -636,9 +643,12 @@ void InputManager::AddPadBindings(SettingsInterface& si, u32 pad_index, const ch
if (!bindings.empty())
{
// we use axes for all pad bindings to simplify things, and because they are pressure sensitive
AddBindings(bindings, InputAxisEventHandler{[pad_index, bind_index = bi.bind_index](float value) {
PAD::SetControllerState(pad_index, bind_index, value);
}});
const float sensitivity = si.GetFloatValue(section.c_str(), fmt::format("{}Scale", bi.name).c_str(), 1.0f);
const float deadzone = si.GetFloatValue(section.c_str(), fmt::format("{}Deadzone", bi.name).c_str(), 0.0f);
AddBindings(
bindings, InputAxisEventHandler{[pad_index, bind_index = bi.bind_index, sensitivity, deadzone](float value) {
PAD::SetControllerState(pad_index, bind_index, ApplySingleBindingScale(sensitivity, deadzone, value));
}});
}
}
break;
@@ -717,8 +727,11 @@ void InputManager::AddUSBBindings(SettingsInterface& si, u32 port)
const std::vector<std::string> bindings(si.GetStringList(section.c_str(), bind_name.c_str()));
if (!bindings.empty())
{
AddBindings(bindings, InputAxisEventHandler{[port, bind_index = bi.bind_index](
float value) { USB::SetDeviceBindValue(port, bind_index, value); }});
const float sensitivity = si.GetFloatValue(section.c_str(), fmt::format("{}Scale", bi.name).c_str(), 1.0f);
const float deadzone = si.GetFloatValue(section.c_str(), fmt::format("{}Deadzone", bi.name).c_str(), 0.0f);
AddBindings(bindings, InputAxisEventHandler{[port, bind_index = bi.bind_index, sensitivity, deadzone](float value) {
USB::SetDeviceBindValue(port, bind_index, ApplySingleBindingScale(sensitivity, deadzone, value));
}});
}
}
break;
+1 -48
View File
@@ -130,7 +130,7 @@ GIFRegTEX0 GSDrawingContext::GetSizeFixedTEX0(const GSVector4& st, bool linear,
res.TW = tw > 10 ? 0 : tw;
res.TH = th > 10 ? 0 : th;
if (GSConfig.Renderer == GSRendererType::SW && (TEX0.TW != res.TW || TEX0.TH != res.TH))
if (TEX0.TW != res.TW || TEX0.TH != res.TH)
{
GL_DBG("FixedTEX0 %05x %d %d tw %d=>%d th %d=>%d st (%.0f,%.0f,%.0f,%.0f) uvmax %d,%d wm %d,%d (%d,%d,%d,%d)",
(int)TEX0.TBP0, (int)TEX0.TBW, (int)TEX0.PSM,
@@ -142,50 +142,3 @@ GIFRegTEX0 GSDrawingContext::GetSizeFixedTEX0(const GSVector4& st, bool linear,
return res;
}
void GSDrawingContext::ComputeFixedTEX0(const GSVector4& st)
{
// It is quite complex to handle rescaling so this function is less stricter than GetSizeFixedTEX0,
// therefore we remove the reduce optimization and we don't handle bilinear filtering which might create wrong interpolation at the border.
int tw = TEX0.TW;
int th = TEX0.TH;
int wms = (int)CLAMP.WMS;
int wmt = (int)CLAMP.WMT;
int minu = (int)CLAMP.MINU;
int minv = (int)CLAMP.MINV;
int maxu = (int)CLAMP.MAXU;
int maxv = (int)CLAMP.MAXV;
if (wms != CLAMP_REGION_CLAMP)
tw = tw > 10 ? 0 : tw;
if (wmt != CLAMP_REGION_CLAMP)
th = th > 10 ? 0 : th;
GSVector4i uv = GSVector4i(st.floor().xyzw(st.ceil()));
uv.x = findmax(uv.x, uv.z, (1 << tw) - 1, wms, minu, maxu);
uv.y = findmax(uv.y, uv.w, (1 << th) - 1, wmt, minv, maxv);
if (wms == CLAMP_REGION_CLAMP || wms == CLAMP_REGION_REPEAT)
tw = extend(uv.x, tw);
if (wmt == CLAMP_REGION_CLAMP || wmt == CLAMP_REGION_REPEAT)
th = extend(uv.y, th);
tw = std::clamp<int>(tw, 0, 10);
th = std::clamp<int>(th, 0, 10);
if ((tw != (int)TEX0.TW) || (th != (int)TEX0.TH))
{
m_fixed_tex0 = true;
TEX0.TW = tw;
TEX0.TH = th;
GL_DBG("FixedTEX0 TW %d=>%d, TH %d=>%d wm %d,%d",
(int)stack.TEX0.TW, (int)TEX0.TW, (int)stack.TEX0.TH, (int)TEX0.TH,
(int)CLAMP.WMS, (int)CLAMP.WMT);
}
}
-9
View File
@@ -69,12 +69,8 @@ public:
GIFRegZBUF ZBUF;
} stack;
bool m_fixed_tex0;
GSDrawingContext()
{
m_fixed_tex0 = false;
memset(&offset, 0, sizeof(offset));
Reset();
@@ -140,8 +136,6 @@ public:
}
GIFRegTEX0 GetSizeFixedTEX0(const GSVector4& st, bool linear, bool mipmap = false) const;
void ComputeFixedTEX0(const GSVector4& st);
bool HasFixedTEX0() const { return m_fixed_tex0; }
// Save & Restore before/after draw allow to correct/optimize current register for current draw
// Note: we could avoid the restore part if all renderer code is updated to use a local copy instead
@@ -159,9 +153,6 @@ public:
stack.FBA = FBA;
stack.FRAME = FRAME;
stack.ZBUF = ZBUF;
// This function is called before the draw so take opportunity to reset m_fixed_tex0
m_fixed_tex0 = false;
}
void RestoreReg()
+1
View File
@@ -823,6 +823,7 @@ union
REG_END2
__forceinline bool IsRepeating() const
{
// This is actually "does the texture span more than one page".
if (TBW < 2)
{
if (PSM == PSM_PSMT8)
+48 -44
View File
@@ -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
View File
@@ -151,15 +151,15 @@ protected:
struct
{
GSVertex* buff;
size_t head, tail, next, maxcount; // head: first vertex, tail: last vertex + 1, next: last indexed + 1
size_t xy_tail;
u32 head, tail, next, maxcount; // head: first vertex, tail: last vertex + 1, next: last indexed + 1
u32 xy_tail;
u64 xy[4];
} m_vertex;
struct
{
u32* buff;
size_t tail;
u32 tail;
} m_index;
void UpdateContext();
@@ -169,8 +169,9 @@ protected:
void GrowVertexBuffer();
bool IsAutoFlushDraw();
template<u32 prim, bool index_swap>
void HandleAutoFlush();
void CLUTAutoFlush();
void CLUTAutoFlush(u32 prim);
template <u32 prim, bool auto_flush, bool index_swap>
void VertexKick(u32 skip);
+3 -2
View File
@@ -309,6 +309,8 @@ struct alignas(16) GSHWDrawConfig
u32 tcc : 1;
u32 wms : 2;
u32 wmt : 2;
u32 adjs : 1;
u32 adjt : 1;
u32 ltf : 1;
// Shuffle and fbmask effect
u32 shuffle : 1;
@@ -352,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;
+27 -6
View File
@@ -15,6 +15,7 @@
#include "PrecompiledHeader.h"
#include "GSDirtyRect.h"
#include <vector>
GSDirtyRect::GSDirtyRect() :
r(GSVector4i::zero()),
@@ -30,7 +31,7 @@ GSDirtyRect::GSDirtyRect(GSVector4i& r, u32 psm, u32 bw) :
{
}
GSVector4i GSDirtyRect::GetDirtyRect(GIFRegTEX0& TEX0) const
GSVector4i GSDirtyRect::GetDirtyRect(GIFRegTEX0& TEX0)
{
GSVector4i _r;
@@ -53,9 +54,7 @@ GSVector4i GSDirtyRect::GetDirtyRect(GIFRegTEX0& TEX0) const
return _r;
}
//
GSVector4i GSDirtyRectList::GetDirtyRect(GIFRegTEX0& TEX0, const GSVector2i& size) const
GSVector4i GSDirtyRectList::GetTotalRect(GIFRegTEX0& TEX0, const GSVector2i& size)
{
if (!empty())
{
@@ -74,9 +73,31 @@ GSVector4i GSDirtyRectList::GetDirtyRect(GIFRegTEX0& TEX0, const GSVector2i& siz
return GSVector4i::zero();
}
GSVector4i GSDirtyRectList::GetDirtyRect(GIFRegTEX0& TEX0, const GSVector2i& size, bool clear)
{
if (!empty())
{
const std::vector<GSDirtyRect>::iterator &it = begin();
const GSVector4i r = it[0].GetDirtyRect(TEX0);
if (clear)
erase(it);
GSVector2i bs = GSLocalMemory::m_psm[TEX0.PSM].bs;
return r.ralign<Align_Outside>(bs).rintersect(GSVector4i(0, 0, size.x, size.y));
}
return GSVector4i::zero();
}
GSVector4i GSDirtyRectList::GetDirtyRectAndClear(GIFRegTEX0& TEX0, const GSVector2i& size)
{
GSVector4i r = GetDirtyRect(TEX0, size);
clear();
const GSVector4i r = GetDirtyRect(TEX0, size, true);
return r;
}
void GSDirtyRectList::ClearDirty()
{
clear();
}
+4 -2
View File
@@ -26,13 +26,15 @@ public:
GSDirtyRect();
GSDirtyRect(GSVector4i& r, u32 psm, u32 bw);
GSVector4i GetDirtyRect(GIFRegTEX0& TEX0) const;
GSVector4i GetDirtyRect(GIFRegTEX0& TEX0);
};
class GSDirtyRectList : public std::vector<GSDirtyRect>
{
public:
GSDirtyRectList() {}
GSVector4i GetDirtyRect(GIFRegTEX0& TEX0, const GSVector2i& size) const;
GSVector4i GetTotalRect(GIFRegTEX0& TEX0, const GSVector2i& size);
GSVector4i GetDirtyRect(GIFRegTEX0& TEX0, const GSVector2i& size, bool clear = false);
GSVector4i GetDirtyRectAndClear(GIFRegTEX0& TEX0, const GSVector2i& size);
void ClearDirty();
};
+7 -3
View File
@@ -17,6 +17,7 @@
#include "GSRenderer.h"
#include "GS/GSCapture.h"
#include "GS/GSGL.h"
#include "GSDumpReplayer.h"
#include "Host.h"
#include "HostDisplay.h"
#include "PerformanceMetrics.h"
@@ -554,15 +555,18 @@ static void CompressAndWriteScreenshot(std::string filename, u32 width, u32 heig
image.SetPixels(width, height, std::move(pixels));
std::string key(fmt::format("GSScreenshot_{}", filename));
Host::AddIconOSDMessage(key, ICON_FA_CAMERA, fmt::format("Saving screenshot to '{}'.", Path::GetFileName(filename)), 60.0f);
if(!GSDumpReplayer::IsRunner())
Host::AddIconOSDMessage(key, ICON_FA_CAMERA, fmt::format("Saving screenshot to '{}'.", Path::GetFileName(filename)), 60.0f);
// maybe std::async would be better here.. but it's definitely worth threading, large screenshots take a while to compress.
std::unique_lock lock(s_screenshot_threads_mutex);
s_screenshot_threads.emplace_back([key = std::move(key), filename = std::move(filename), image = std::move(image), quality = GSConfig.ScreenshotQuality]() {
if (image.SaveToFile(filename.c_str(), quality))
{
Host::AddIconOSDMessage(std::move(key), ICON_FA_CAMERA,
fmt::format("Saved screenshot to '{}'.", Path::GetFileName(filename)), Host::OSD_INFO_DURATION);
if(!GSDumpReplayer::IsRunner())
Host::AddIconOSDMessage(std::move(key), ICON_FA_CAMERA,
fmt::format("Saved screenshot to '{}'.", Path::GetFileName(filename)), Host::OSD_INFO_DURATION);
}
else
{
+1 -1
View File
@@ -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());
+2 -1
View File
@@ -142,6 +142,8 @@ void GSDevice11::SetupPS(const PSSelector& sel, const GSHWDrawConfig::PSConstant
sm.AddMacro("PS_FST", sel.fst);
sm.AddMacro("PS_WMS", sel.wms);
sm.AddMacro("PS_WMT", sel.wmt);
sm.AddMacro("PS_ADJS", sel.adjs);
sm.AddMacro("PS_ADJT", sel.adjt);
sm.AddMacro("PS_AEM_FMT", sel.aem_fmt);
sm.AddMacro("PS_AEM", sel.aem);
sm.AddMacro("PS_TFX", sel.tfx);
@@ -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);
+2 -1
View File
@@ -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);
+1 -1
View File
@@ -838,7 +838,7 @@ bool GSHwHack::GSC_BlueTongueGames(GSRendererHW& r, const GSFrameInfo& fi, int&
bool GSHwHack::OI_PointListPalette(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t)
{
const size_t n_vertices = r.m_vertex.next;
const u32 n_vertices = r.m_vertex.next;
const int w = r.m_r.width();
const int h = r.m_r.height();
const bool is_copy = !r.PRIM->ABE || (
+174 -121
View File
@@ -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
+51 -9
View File
@@ -44,6 +44,44 @@ public:
return valid && overlap;
}
struct SourceRegion
{
u64 bits;
bool HasX() const { return static_cast<u32>(bits) != 0; }
bool HasY() const { return static_cast<u32>(bits >> 32) != 0; }
bool HasEither() const { return (bits != 0); }
void SetX(u32 min, u32 max) { bits |= (min | (max << 16)); }
void SetY(u32 min, u32 max) { bits |= ((static_cast<u64>(min) << 32) | (static_cast<u64>(max) << 48)); }
u32 GetMinX() const { return static_cast<u32>(bits) & 0xFFFFu; }
u32 GetMaxX() const { return static_cast<u32>(bits >> 16) & 0xFFFFu; }
u32 GetMinY() const { return static_cast<u32>(bits >> 32) & 0xFFFFu; }
u32 GetMaxY() const { return static_cast<u32>(bits >> 48); }
u32 GetWidth() const { return (GetMaxX() - GetMinX()); }
u32 GetHeight() const { return (GetMaxY() - GetMinY()); }
/// Returns true if the area of the region exceeds the TW/TH size (i.e. "fixed tex0").
bool IsFixedTEX0(int tw, int th) const;
bool IsFixedTEX0W(int tw) const;
bool IsFixedTEX0H(int th) const;
/// Returns the rectangle relative to the texture base pointer that the region occupies.
GSVector4i GetRect(int tw, int th) const;
/// When TW/TH is less than the extents covered by the region ("fixed tex0"), returns the offset
/// which should be applied to any coordinates to relocate them to the actual region.
GSVector4i GetOffset(int tw, int th) const;
/// Reduces the range of texels relative to the specified mipmap level.
SourceRegion AdjustForMipmap(u32 level) const;
/// Adjusts the texture base pointer and block width relative to the region.
void AdjustTEX0(GIFRegTEX0* TEX0) const;
};
using HashType = u64;
struct HashCacheKey
@@ -51,10 +89,11 @@ public:
HashType TEX0Hash, CLUTHash;
GIFRegTEX0 TEX0;
GIFRegTEXA TEXA;
SourceRegion region;
HashCacheKey();
static HashCacheKey Create(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const u32* clut, const GSVector2i* lod);
static HashCacheKey Create(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const u32* clut, const GSVector2i* lod, SourceRegion region);
HashCacheKey WithRemovedCLUTHash() const;
void RemoveCLUTHash();
@@ -148,7 +187,7 @@ public:
{
GSVector4i* rect;
u32 count;
} m_write;
} m_write = {};
void PreloadLevel(int level);
@@ -161,6 +200,7 @@ public:
GSTexture* m_palette;
GSVector4i m_valid_rect;
GSVector2i m_lod;
SourceRegion m_region = {};
u8 m_valid_hashes = 0;
u8 m_complete_layers = 0;
bool m_target;
@@ -178,11 +218,13 @@ public:
GSOffset::PageLooper m_pages;
public:
Source(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, bool dummy_container = false);
Source(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA);
virtual ~Source();
__fi bool CanPreload() const { return CanPreloadTextureSize(m_TEX0.TW, m_TEX0.TH); }
void SetPages();
void Update(const GSVector4i& rect, int layer = 0);
void UpdateLayer(const GIFRegTEX0& TEX0, const GSVector4i& rect, int layer = 0);
@@ -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);
+98 -32
View File
@@ -42,6 +42,8 @@
// this is a #define instead of a variable to avoid warnings from non-literal format strings
#define TEXTURE_FILENAME_FORMAT_STRING "%" PRIx64 "-%08x"
#define TEXTURE_FILENAME_CLUT_FORMAT_STRING "%" PRIx64 "-%" PRIx64 "-%08x"
#define TEXTURE_FILENAME_REGION_FORMAT_STRING "%" PRIx64 "-r%" PRIx64 "-" "-%08x"
#define TEXTURE_FILENAME_REGION_CLUT_FORMAT_STRING "%" PRIx64 "-%" PRIx64 "-r%" PRIx64 "-%08x"
#define TEXTURE_REPLACEMENT_SUBDIRECTORY_NAME "replacements"
#define TEXTURE_DUMP_SUBDIRECTORY_NAME "dumps"
@@ -51,6 +53,7 @@ namespace
{
u64 TEX0Hash;
u64 CLUTHash;
GSTextureCache::SourceRegion region;
union
{
@@ -68,9 +71,10 @@ namespace
};
u32 miplevel;
__fi u32 Width() const { return (1u << TEX0_TW); }
__fi u32 Height() const { return (1u << TEX0_TH); }
__fi u32 Width() const { return (region.HasX() ? region.GetWidth() : (1u << TEX0_TW)); }
__fi u32 Height() const { return (region.HasY() ? region.GetWidth() : (1u << TEX0_TH)); }
__fi bool HasPalette() const { return (GSLocalMemory::m_psm[TEX0_PSM].pal > 0); }
__fi bool HasRegion() const { return region.HasEither(); }
__fi GSVector2 ReplacementScale(const GSTextureReplacements::ReplacementTexture& rtex) const
{
@@ -79,14 +83,27 @@ namespace
__fi GSVector2 ReplacementScale(u32 rwidth, u32 rheight) const
{
return GSVector2(static_cast<float>(rwidth) / static_cast<float>(Width()), static_cast<float>(rheight) / static_cast<float>(Height()));
return GSVector2(static_cast<float>(rwidth) / static_cast<float>(Width()),
static_cast<float>(rheight) / static_cast<float>(Height()));
}
__fi bool operator==(const TextureName& rhs) const { return std::tie(TEX0Hash, CLUTHash, bits) == std::tie(rhs.TEX0Hash, rhs.CLUTHash, rhs.bits); }
__fi bool operator!=(const TextureName& rhs) const { return std::tie(TEX0Hash, CLUTHash, bits) != std::tie(rhs.TEX0Hash, rhs.CLUTHash, rhs.bits); }
__fi bool operator<(const TextureName& rhs) const { return std::tie(TEX0Hash, CLUTHash, bits) < std::tie(rhs.TEX0Hash, rhs.CLUTHash, rhs.bits); }
__fi bool operator==(const TextureName& rhs) const
{
return std::tie(TEX0Hash, CLUTHash, region.bits, bits) ==
std::tie(rhs.TEX0Hash, rhs.CLUTHash, region.bits, rhs.bits);
}
__fi bool operator!=(const TextureName& rhs) const
{
return std::tie(TEX0Hash, CLUTHash, region.bits, bits) !=
std::tie(rhs.TEX0Hash, rhs.CLUTHash, region.bits, rhs.bits);
}
__fi bool operator<(const TextureName& rhs) const
{
return std::tie(TEX0Hash, CLUTHash, region.bits, bits) <
std::tie(rhs.TEX0Hash, rhs.CLUTHash, region.bits, rhs.bits);
}
};
static_assert(sizeof(TextureName) == 24, "ReplacementTextureName is expected size");
static_assert(sizeof(TextureName) == 32, "ReplacementTextureName is expected size");
} // namespace
namespace std
@@ -97,7 +114,7 @@ namespace std
std::size_t operator()(const TextureName& val) const
{
std::size_t h = 0;
HashCombine(h, val.TEX0Hash, val.CLUTHash, val.bits, val.miplevel);
HashCombine(h, val.TEX0Hash, val.CLUTHash, val.region.bits, val.bits, val.miplevel);
return h;
}
};
@@ -169,6 +186,7 @@ TextureName GSTextureReplacements::CreateTextureName(const GSTextureCache::HashC
name.TEX0Hash = hash.TEX0Hash;
name.CLUTHash = name.HasPalette() ? hash.CLUTHash : 0;
name.miplevel = miplevel;
name.region = hash.region;
return name;
}
@@ -184,6 +202,7 @@ GSTextureCache::HashCacheKey GSTextureReplacements::HashCacheKeyFromTextureName(
key.TEXA.TA1 = tn.TEXA_TA1;
key.TEX0Hash = tn.TEX0Hash;
key.CLUTHash = tn.HasPalette() ? tn.CLUTHash : 0;
key.region = tn.region;
return key;
}
@@ -192,15 +211,38 @@ std::optional<TextureName> GSTextureReplacements::ParseReplacementName(const std
TextureName ret;
ret.miplevel = 0;
// TODO(Stenzek): Make this better.
char extension_dot;
if (std::sscanf(filename.c_str(), TEXTURE_FILENAME_CLUT_FORMAT_STRING "%c", &ret.TEX0Hash, &ret.CLUTHash, &ret.bits, &extension_dot) != 4 || extension_dot != '.')
if (std::sscanf(filename.c_str(), TEXTURE_FILENAME_REGION_CLUT_FORMAT_STRING "%c", &ret.TEX0Hash, &ret.CLUTHash,
&ret.region.bits, &ret.bits, &extension_dot) == 5 &&
extension_dot == '.')
{
if (std::sscanf(filename.c_str(), TEXTURE_FILENAME_FORMAT_STRING "%c", &ret.TEX0Hash, &ret.bits, &extension_dot) != 3 || extension_dot != '.')
return std::nullopt;
return ret;
}
return ret;
if (std::sscanf(filename.c_str(), TEXTURE_FILENAME_REGION_FORMAT_STRING "%c", &ret.TEX0Hash, &ret.region.bits,
&ret.bits, &extension_dot) == 4 &&
extension_dot == '.')
{
return ret;
}
ret.region.bits = 0;
if (std::sscanf(filename.c_str(), TEXTURE_FILENAME_CLUT_FORMAT_STRING "%c", &ret.TEX0Hash, &ret.CLUTHash, &ret.bits,
&extension_dot) == 4 &&
extension_dot == '.')
{
return ret;
}
if (std::sscanf(filename.c_str(), TEXTURE_FILENAME_FORMAT_STRING "%c", &ret.TEX0Hash, &ret.bits, &extension_dot) ==
3 &&
extension_dot == '.')
{
return ret;
}
return std::nullopt;
}
std::string GSTextureReplacements::GetGameTextureDirectory()
@@ -229,23 +271,45 @@ std::string GSTextureReplacements::GetDumpFilename(const TextureName& name, u32
const std::string game_subdir(Path::Combine(game_dir, TEXTURE_DUMP_SUBDIRECTORY_NAME));
if (name.HasPalette())
std::string filename;
if (name.HasRegion())
{
const std::string filename(
(level > 0) ?
StringUtil::StdStringFromFormat(TEXTURE_FILENAME_CLUT_FORMAT_STRING "-mip%u.png", name.TEX0Hash, name.CLUTHash, name.bits, level) :
StringUtil::StdStringFromFormat(TEXTURE_FILENAME_CLUT_FORMAT_STRING ".png", name.TEX0Hash, name.CLUTHash, name.bits));
ret = Path::Combine(game_subdir, filename);
if (name.HasPalette())
{
filename = (level > 0) ?
StringUtil::StdStringFromFormat(TEXTURE_FILENAME_REGION_CLUT_FORMAT_STRING "-mip%u.png",
name.TEX0Hash, name.CLUTHash, name.region.bits, name.bits, level) :
StringUtil::StdStringFromFormat(TEXTURE_FILENAME_REGION_CLUT_FORMAT_STRING ".png",
name.TEX0Hash, name.CLUTHash, name.region.bits, name.bits);
}
else
{
filename = (level > 0) ? StringUtil::StdStringFromFormat(
TEXTURE_FILENAME_FORMAT_STRING "-mip%u.png", name.TEX0Hash, name.bits, level) :
StringUtil::StdStringFromFormat(
TEXTURE_FILENAME_FORMAT_STRING ".png", name.TEX0Hash, name.bits);
}
}
else
{
const std::string filename(
(level > 0) ?
StringUtil::StdStringFromFormat(TEXTURE_FILENAME_FORMAT_STRING "-mip%u.png", name.TEX0Hash, name.bits, level) :
StringUtil::StdStringFromFormat(TEXTURE_FILENAME_FORMAT_STRING ".png", name.TEX0Hash, name.bits));
ret = Path::Combine(game_subdir, filename);
if (name.HasPalette())
{
filename = (level > 0) ? StringUtil::StdStringFromFormat(TEXTURE_FILENAME_CLUT_FORMAT_STRING "-mip%u.png",
name.TEX0Hash, name.CLUTHash, name.bits, level) :
StringUtil::StdStringFromFormat(TEXTURE_FILENAME_CLUT_FORMAT_STRING ".png",
name.TEX0Hash, name.CLUTHash, name.bits);
}
else
{
filename = (level > 0) ? StringUtil::StdStringFromFormat(
TEXTURE_FILENAME_FORMAT_STRING "-mip%u.png", name.TEX0Hash, name.bits, level) :
StringUtil::StdStringFromFormat(
TEXTURE_FILENAME_FORMAT_STRING ".png", name.TEX0Hash, name.bits);
}
}
ret = Path::Combine(game_subdir, filename);
return ret;
}
@@ -569,7 +633,8 @@ void GSTextureReplacements::ProcessAsyncLoadedTextures()
s_async_loaded_textures.clear();
}
void GSTextureReplacements::DumpTexture(const GSTextureCache::HashCacheKey& hash, const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, GSLocalMemory& mem, u32 level)
void GSTextureReplacements::DumpTexture(const GSTextureCache::HashCacheKey& hash, const GIFRegTEX0& TEX0,
const GIFRegTEXA& TEXA, GSTextureCache::SourceRegion region, GSLocalMemory& mem, u32 level)
{
// check if it's been dumped or replaced already
const TextureName name(CreateTextureName(hash, level));
@@ -589,12 +654,12 @@ void GSTextureReplacements::DumpTexture(const GSTextureCache::HashCacheKey& hash
// compute width/height
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[TEX0.PSM];
const GSVector2i& bs = psm.bs;
const int tw = 1 << TEX0.TW;
const int th = 1 << TEX0.TH;
const GSVector4i rect(0, 0, tw, th);
const int tw = region.HasX() ? region.GetWidth() : (1 << TEX0.TW);
const int th = region.HasY() ? region.GetHeight() : (1 << TEX0.TH);
const GSVector4i rect(region.GetRect(tw, th));
const GSVector4i block_rect(rect.ralign<Align_Outside>(bs));
const int read_width = std::max(tw, psm.bs.x);
const int read_height = std::max(th, psm.bs.y);
const int read_width = block_rect.width();
const int read_height = block_rect.height();
const u32 pitch = static_cast<u32>(read_width) * sizeof(u32);
// use per-texture buffer so we can compress the texture asynchronously and not block the GS thread
@@ -603,8 +668,9 @@ void GSTextureReplacements::DumpTexture(const GSTextureCache::HashCacheKey& hash
psm.rtx(mem, mem.GetOffset(TEX0.TBP0, TEX0.TBW, TEX0.PSM), block_rect, buffer.GetPtr(), pitch, TEXA);
// okay, now we can actually dump it
QueueWorkerThreadItem([filename = std::move(filename), tw, th, pitch, buffer = std::move(buffer)]() {
if (!SavePNGImage(filename.c_str(), tw, th, buffer.GetPtr(), pitch))
const u32 buffer_offset = ((rect.top - block_rect.top) * pitch) + ((rect.left - block_rect.left) * sizeof(u32));
QueueWorkerThreadItem([filename = std::move(filename), tw, th, pitch, buffer = std::move(buffer), buffer_offset]() {
if (!SavePNGImage(filename.c_str(), tw, th, buffer.GetPtr() + buffer_offset, pitch))
Console.Error("Failed to dump texture to '%s'.", filename.c_str());
});
}
@@ -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).
+3 -2
View File
@@ -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));
+8 -3
View File
@@ -108,11 +108,15 @@ struct GSMTLMainPSUniform
vector_float2 ta;
float max_depth;
float alpha_fix;
vector_uint4 uv_msk_fix;
vector_uint4 fbmask;
vector_float4 half_texel;
vector_float4 uv_min_max;
union
{
vector_float4 uv_min_max;
vector_uint4 uv_msk_fix;
};
vector_float4 st_range;
struct
{
unsigned int blue_mask;
@@ -166,6 +170,8 @@ enum GSMTLFnConstants
GSMTLConstantIndex_PS_TCC,
GSMTLConstantIndex_PS_WMS,
GSMTLConstantIndex_PS_WMT,
GSMTLConstantIndex_PS_ADJS,
GSMTLConstantIndex_PS_ADJT,
GSMTLConstantIndex_PS_LTF,
GSMTLConstantIndex_PS_SHUFFLE,
GSMTLConstantIndex_PS_READ_BA,
@@ -194,6 +200,5 @@ enum GSMTLFnConstants
GSMTLConstantIndex_PS_AUTOMATIC_LOD,
GSMTLConstantIndex_PS_MANUAL_LOD,
GSMTLConstantIndex_PS_POINT_SAMPLER,
GSMTLConstantIndex_PS_INVALID_TEX0,
GSMTLConstantIndex_PS_SCANMSK,
};
+19 -9
View File
@@ -37,6 +37,8 @@ constant uint PS_TFX [[function_constant(GSMTLConstantIndex_PS_TF
constant bool PS_TCC [[function_constant(GSMTLConstantIndex_PS_TCC)]];
constant uint PS_WMS [[function_constant(GSMTLConstantIndex_PS_WMS)]];
constant uint PS_WMT [[function_constant(GSMTLConstantIndex_PS_WMT)]];
constant bool PS_ADJS [[function_constant(GSMTLConstantIndex_PS_ADJS)]];
constant bool PS_ADJT [[function_constant(GSMTLConstantIndex_PS_ADJT)]];
constant bool PS_LTF [[function_constant(GSMTLConstantIndex_PS_LTF)]];
constant bool PS_SHUFFLE [[function_constant(GSMTLConstantIndex_PS_SHUFFLE)]];
constant bool PS_READ_BA [[function_constant(GSMTLConstantIndex_PS_READ_BA)]];
@@ -65,7 +67,6 @@ constant bool PS_TEX_IS_FB [[function_constant(GSMTLConstantIndex_PS_TE
constant bool PS_AUTOMATIC_LOD [[function_constant(GSMTLConstantIndex_PS_AUTOMATIC_LOD)]];
constant bool PS_MANUAL_LOD [[function_constant(GSMTLConstantIndex_PS_MANUAL_LOD)]];
constant bool PS_POINT_SAMPLER [[function_constant(GSMTLConstantIndex_PS_POINT_SAMPLER)]];
constant bool PS_INVALID_TEX0 [[function_constant(GSMTLConstantIndex_PS_INVALID_TEX0)]];
constant uint PS_SCANMSK [[function_constant(GSMTLConstantIndex_PS_SCANMSK)]];
constant GSMTLExpandType VS_EXPAND_TYPE = static_cast<GSMTLExpandType>(VS_EXPAND_TYPE_RAW);
@@ -321,7 +322,21 @@ struct PSMain
// As of 2018 this issue is still present.
uv = (trunc(uv * cb.wh.zw) + 0.5) / cb.wh.zw;
}
uv *= cb.st_scale;
if (!PS_ADJS && !PS_ADJT)
{
uv *= cb.st_scale;
}
else
{
if (PS_ADJS)
uv.x = (uv.x - cb.st_range.x) * cb.st_range.z;
else
uv.x = uv.x * cb.st_scale.x;
if (PS_ADJT)
uv.y = (uv.y - cb.st_range.y) * cb.st_range.w;
else
uv.y = uv.y * cb.st_scale.y;
}
if (PS_AUTOMATIC_LOD)
{
@@ -360,7 +375,7 @@ struct PSMain
float4 clamp_wrap_uv(float4 uv)
{
float4 uv_out = uv;
float4 tex_size = PS_INVALID_TEX0 ? cb.wh.zwzw : cb.wh.xyxy;
float4 tex_size = cb.wh.xyxy;
if (PS_WMS == PS_WMT)
{
@@ -724,12 +739,7 @@ struct PSMain
float4 ps_color()
{
float2 st, st_int;
if (!FST && PS_INVALID_TEX0)
{
st = (in.t.xy * cb.wh.xy) / (in.t.w * cb.wh.zw);
st_int = (in.ti.zw * cb.wh.xy) / (in.t.w * cb.wh.zw);
}
else if (!FST)
if (!FST)
{
st = in.t.xy / in.t.w;
st_int = in.ti.zw / in.t.w;
+10 -9
View File
@@ -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;
}
+1 -2
View File
@@ -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;
+2 -1
View File
@@ -1029,6 +1029,8 @@ std::string GSDeviceOGL::GetPSSource(const PSSelector& sel)
std::string macro = fmt::format("#define PS_FST {}\n", sel.fst)
+ fmt::format("#define PS_WMS {}\n", sel.wms)
+ fmt::format("#define PS_WMT {}\n", sel.wmt)
+ fmt::format("#define PS_ADJS {}\n", sel.adjs)
+ fmt::format("#define PS_ADJT {}\n", sel.adjt)
+ fmt::format("#define PS_AEM_FMT {}\n", sel.aem_fmt)
+ fmt::format("#define PS_PAL_FMT {}\n", sel.pal_fmt)
+ fmt::format("#define PS_DFMT {}\n", sel.dfmt)
@@ -1037,7 +1039,6 @@ std::string GSDeviceOGL::GetPSSource(const PSSelector& sel)
+ fmt::format("#define PS_URBAN_CHAOS_HLE {}\n", sel.urban_chaos_hle)
+ fmt::format("#define PS_TALES_OF_ABYSS_HLE {}\n", sel.tales_of_abyss_hle)
+ fmt::format("#define PS_TEX_IS_FB {}\n", sel.tex_is_fb)
+ fmt::format("#define PS_INVALID_TEX0 {}\n", sel.invalid_tex0)
+ fmt::format("#define PS_AEM {}\n", sel.aem)
+ fmt::format("#define PS_TFX {}\n", sel.tfx)
+ fmt::format("#define PS_TCC {}\n", sel.tcc)
+37 -19
View File
@@ -21,10 +21,17 @@
#include "GS/GSPerfMon.h"
#include "GS/GSPng.h"
#include "GS/GSGL.h"
#include "common/Align.h"
#include "common/AlignedMalloc.h"
#include "common/StringUtil.h"
static constexpr u32 TEXTURE_UPLOAD_ALIGNMENT = 256;
// Looking across a range of GPUs, the optimal copy alignment for Vulkan drivers seems
// to be between 1 (AMD/NV) and 64 (Intel). So, we'll go with 64 here.
static constexpr u32 TEXTURE_UPLOAD_ALIGNMENT = 64;
// The pitch alignment must be less or equal to the upload alignment.
// We need 32 here for AVX2, so 64 is also fine.
static constexpr u32 TEXTURE_UPLOAD_PITCH_ALIGNMENT = 64;
GSTextureOGL::GSTextureOGL(Type type, int width, int height, int levels, Format format)
{
@@ -214,8 +221,8 @@ bool GSTextureOGL::Update(const GSVector4i& r, const void* data, int pitch, int
m_clean = false;
u32 row_byte = r.width() << m_int_shift;
u32 map_size = r.height() * row_byte;
const u32 preferred_pitch = Common::AlignUpPow2(r.width() << m_int_shift, TEXTURE_UPLOAD_PITCH_ALIGNMENT);
const u32 map_size = r.height() * preferred_pitch;
#if 0
if (r.height() == 1) {
@@ -250,13 +257,18 @@ bool GSTextureOGL::Update(const GSVector4i& r, const void* data, int pitch, int
GL::StreamBuffer* const sb = GSDeviceOGL::GetTextureUploadBuffer();
const auto map = sb->Map(TEXTURE_UPLOAD_ALIGNMENT, map_size);
StringUtil::StrideMemCpy(map.pointer, row_byte, data, pitch, row_byte, r.height());
StringUtil::StrideMemCpy(map.pointer, preferred_pitch, data, pitch, r.width() << m_int_shift, r.height());
sb->Unmap(map_size);
sb->Bind();
const u32 row_length = CalcUploadRowLengthFromPitch(preferred_pitch);
glPixelStorei(GL_UNPACK_ROW_LENGTH, row_length);
glTextureSubImage2D(m_texture_id, layer, r.x, r.y, r.width(), r.height(), m_int_format, m_int_type,
reinterpret_cast<void*>(static_cast<uintptr_t>(map.buffer_offset)));
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
sb->Unbind();
}
@@ -275,13 +287,13 @@ bool GSTextureOGL::Map(GSMap& m, const GSVector4i* _r, int layer)
ASSERT(r.width() != 0);
ASSERT(r.height() != 0);
u32 row_byte = r.width() << m_int_shift;
m.pitch = row_byte;
const u32 pitch = Common::AlignUpPow2(r.width() << m_int_shift, TEXTURE_UPLOAD_PITCH_ALIGNMENT);
m.pitch = pitch;
if (m_type == Type::Texture || m_type == Type::RenderTarget)
{
const u32 map_size = r.height() * row_byte;
if (GLLoader::buggy_pbo || map_size > GSDeviceOGL::GetTextureUploadBuffer()->GetChunkSize())
const u32 upload_size = CalcUploadSize(r.height(), pitch);
if (GLLoader::buggy_pbo || upload_size > GSDeviceOGL::GetTextureUploadBuffer()->GetChunkSize())
return false;
GL_PUSH_("Upload Texture %d", m_texture_id); // POP is in Unmap
@@ -289,7 +301,7 @@ bool GSTextureOGL::Map(GSMap& m, const GSVector4i* _r, int layer)
m_clean = false;
const auto map = GSDeviceOGL::GetTextureUploadBuffer()->Map(TEXTURE_UPLOAD_ALIGNMENT, map_size);
const auto map = GSDeviceOGL::GetTextureUploadBuffer()->Map(TEXTURE_UPLOAD_ALIGNMENT, upload_size);
m.bits = static_cast<u8*>(map.pointer);
// Save the area for the unmap
@@ -310,14 +322,20 @@ void GSTextureOGL::Unmap()
{
if (m_type == Type::Texture || m_type == Type::RenderTarget)
{
const u32 map_size = (m_r_w << m_int_shift) * m_r_h;
const u32 pitch = Common::AlignUpPow2(m_r_w << m_int_shift, TEXTURE_UPLOAD_PITCH_ALIGNMENT);
const u32 upload_size = pitch * m_r_h;
GL::StreamBuffer* sb = GSDeviceOGL::GetTextureUploadBuffer();
sb->Unmap(map_size);
sb->Unmap(upload_size);
sb->Bind();
const u32 row_length = CalcUploadRowLengthFromPitch(pitch);
glPixelStorei(GL_UNPACK_ROW_LENGTH, row_length);
glTextureSubImage2D(m_texture_id, m_layer, m_r_x, m_r_y, m_r_w, m_r_h, m_int_format, m_int_type,
reinterpret_cast<void*>(static_cast<uintptr_t>(m_map_offset)));
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
sb->Unbind();
m_needs_mipmaps_generated = true;
@@ -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, &copy_offset, &copy_size, &copy_rows);
g_perfmon.Put(GSPerfMon::Readbacks, 1);
glBindFramebuffer(GL_READ_FRAMEBUFFER, GSDeviceOGL::GetInstance()->GetFBORead());
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, glTex->GetID(), 0);
glPixelStorei(GL_PACK_ALIGNMENT, 1u << glTex->GetIntShift());
glPixelStorei(GL_PACK_ROW_LENGTH, GSTexture::CalcUploadRowLengthFromPitch(m_format, m_current_pitch));
@@ -508,10 +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)
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -246,7 +246,7 @@ struct alignas(32) GSVertexSW
#endif
}
typedef void (*ConvertVertexBufferPtr)(const GSDrawingContext* RESTRICT ctx, GSVertexSW* RESTRICT dst, const GSVertex* RESTRICT src, size_t count);
typedef void (*ConvertVertexBufferPtr)(const GSDrawingContext* RESTRICT ctx, GSVertexSW* RESTRICT dst, const GSVertex* RESTRICT src, u32 count);
static ConvertVertexBufferPtr s_cvb[4][2][2][2];
+7 -1
View File
@@ -251,6 +251,9 @@ bool GSDeviceVK::CheckFeatures()
if (!m_features.texture_barrier)
Console.Warning("Texture buffers are disabled. This may break some graphical effects.");
if (!g_vulkan_context->GetOptionalExtensions().vk_ext_line_rasterization)
Console.WriteLn("VK_EXT_line_rasterization or the BRESENHAM mode is not supported, this may cause rendering inaccuracies.");
// Test for D32S8 support.
{
VkFormatProperties props = {};
@@ -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);
+16
View File
@@ -58,6 +58,7 @@ static bool s_dump_running = false;
static bool s_needs_state_loaded = false;
static u64 s_frame_ticks = 0;
static u64 s_next_frame_time = 0;
static bool s_is_dump_runner = false;
R5900cpu GSDumpReplayerCpu = {
GSDumpReplayerCpuReserve,
@@ -77,11 +78,26 @@ bool GSDumpReplayer::IsReplayingDump()
return static_cast<bool>(s_dump_file);
}
bool GSDumpReplayer::IsRunner()
{
return s_is_dump_runner;
}
void GSDumpReplayer::SetIsDumpRunner(bool is_runner)
{
s_is_dump_runner = is_runner;
}
void GSDumpReplayer::SetLoopCount(s32 loop_count)
{
s_dump_loop_count = loop_count - 1;
}
int GSDumpReplayer::GetLoopCount()
{
return s_dump_loop_count;
}
bool GSDumpReplayer::Initialize(const char* filename)
{
Common::Timer timer;
+3
View File
@@ -25,6 +25,9 @@ bool IsReplayingDump();
/// If set, playback will repeat once it reaches the last frame.
void SetLoopCount(s32 loop_count = 0);
int GetLoopCount();
bool IsRunner();
void SetIsDumpRunner(bool is_runner);
bool Initialize(const char* filename);
void Reset();
+40
View File
@@ -20,8 +20,12 @@
#include "R5900.h" // for g_GameStarted
#include "IopBios.h"
#include "IopMem.h"
#include "iR3000A.h"
#include "ps2/BiosTools.h"
#include "DebugTools/SymbolMap.h"
#include <ctype.h>
#include <fmt/format.h>
#include <string.h>
#include <sys/stat.h>
#include "common/FileSystem.h"
@@ -983,6 +987,37 @@ namespace R3000A
namespace loadcore
{
// Gets the thread list ptr from thbase
u32 GetThreadList(u32 a0reg, u32 version)
{
// Function 3 returns the main thread manager struct
u32 function = iopMemRead32(a0reg + 0x20);
// read the lui
u32 thstruct = (iopMemRead32(function) & 0xFFFF) << 16;
thstruct |= iopMemRead32(function + 4) & 0xFFFF;
u32 list = thstruct + 0x42c;
if (version > 0x101)
list = thstruct + 0x430;
return list;
}
int RegisterLibraryEntries_HLE()
{
const std::string modname = iopMemReadString(a0 + 12);
if (modname == "thbase")
{
const u32 version = iopMemRead32(a0 + 8);
CurrentBiosInformation.iopThreadListAddr = GetThreadList(a0, version);
}
return 0;
}
void RegisterLibraryEntries_DEBUG()
{
const std::string modname = iopMemReadString(a0 + 12);
@@ -1093,6 +1128,11 @@ namespace R3000A
EXPORT_H( 14, Kprintf)
END_MODULE
// For grabbing the thread list from thbase
MODULE(loadcore)
EXPORT_H( 6, RegisterLibraryEntries)
END_MODULE
// Special case with ioman and iomanX
// They are mostly compatible excluding stat structures
if(libname == "ioman" || libname == "iomanx")
+7 -4
View File
@@ -1919,10 +1919,13 @@ void FolderMemoryCard::DeleteFromIndex(const std::string& filePath, const std::s
if (yaml.has_value() && !yaml.value().empty())
{
ryml::NodeRef index = yaml.value().rootref();
index.remove_child(c4::csubstr(entry.data(), entry.length()));
// Write out the changes
SaveYAMLToFile(indexName.c_str(), index);
if (index.has_child(c4::csubstr(entry.data(), entry.length())))
{
index.remove_child(c4::csubstr(entry.data(), entry.length()));
// Write out the changes
SaveYAMLToFile(indexName.c_str(), index);
}
}
}
+36 -27
View File
@@ -55,6 +55,29 @@ void KeyStatus::Init()
void KeyStatus::Set(u32 pad, u32 index, float value)
{
// Since we reordered the buttons for better UI, we need to remap them here.
static constexpr std::array<u8, MAX_KEYS> bitmask_mapping = {{
12, // PAD_UP
13, // PAD_RIGHT
14, // PAD_DOWN
15, // PAD_LEFT
4, // PAD_TRIANGLE
5, // PAD_CIRCLE
6, // PAD_CROSS
7, // PAD_SQUARE
8, // PAD_SELECT
11, // PAD_START
2, // PAD_L1
0, // PAD_L2
3, // PAD_R1
1, // PAD_R2
9, // PAD_L3
10, // PAD_R3
16, // PAD_ANALOG
17, // PAD_PRESSURE
// remainder are analogs and not used here
}};
if (IsAnalogKey(index))
{
m_button_pressure[pad][index] = static_cast<u8>(std::clamp(value * m_axis_scale[pad][1] * 255.0f, 0.0f, 255.0f));
@@ -95,8 +118,8 @@ void KeyStatus::Set(u32 pad, u32 index, float value)
}
else
{
pos_x = m_analog[pad].invert_lx ? MERGE_F(pad, PAD_R_LEFT, PAD_R_RIGHT) : MERGE_F(pad, PAD_R_RIGHT, PAD_R_LEFT);
pos_y = m_analog[pad].invert_ly ? MERGE_F(pad, PAD_R_UP, PAD_R_DOWN) : MERGE_F(pad, PAD_R_DOWN, PAD_R_UP);
pos_x = m_analog[pad].invert_rx ? MERGE_F(pad, PAD_R_LEFT, PAD_R_RIGHT) : MERGE_F(pad, PAD_R_RIGHT, PAD_R_LEFT);
pos_y = m_analog[pad].invert_ry ? MERGE_F(pad, PAD_R_UP, PAD_R_DOWN) : MERGE_F(pad, PAD_R_DOWN, PAD_R_UP);
}
// No point checking if we're at dead center (usually keyboard with no buttons pressed).
@@ -123,38 +146,24 @@ void KeyStatus::Set(u32 pad, u32 index, float value)
}
#undef MERGE_F
}
}
else if (IsTriggerKey(index))
{
const float s_value = std::clamp(value * m_trigger_scale[pad][1], 0.0f, 1.0f);
const float dz_value = (m_trigger_scale[pad][0] > 0.0f && s_value < m_trigger_scale[pad][0]) ? 0.0f : s_value;
m_button_pressure[pad][index] = static_cast<u8>(dz_value * 255.0f);
if (dz_value > 0.0f)
m_button[pad] &= ~(1u << bitmask_mapping[index]);
else
m_button[pad] |= (1u << bitmask_mapping[index]);
}
else
{
// Don't affect L2/R2, since they are analog on most pads.
const float pmod = ((m_button[pad] & (1u << PAD_PRESSURE)) == 0 && !IsTriggerKey(index)) ? m_pressure_modifier[pad] : 1.0f;
const float pmod = ((m_button[pad] & (1u << PAD_PRESSURE)) == 0) ? m_pressure_modifier[pad] : 1.0f;
const float dz_value = (value < m_button_deadzone[pad]) ? 0.0f : value;
m_button_pressure[pad][index] = static_cast<u8>(std::clamp(dz_value * pmod * 255.0f, 0.0f, 255.0f));
// Since we reordered the buttons for better UI, we need to remap them here.
static constexpr std::array<u8, MAX_KEYS> bitmask_mapping = {{
12, // PAD_UP
13, // PAD_RIGHT
14, // PAD_DOWN
15, // PAD_LEFT
4, // PAD_TRIANGLE
5, // PAD_CIRCLE
6, // PAD_CROSS
7, // PAD_SQUARE
8, // PAD_SELECT
11, // PAD_START
2, // PAD_L1
0, // PAD_L2
3, // PAD_R1
1, // PAD_R2
9, // PAD_L3
10, // PAD_R3
16, // PAD_ANALOG
17, // PAD_PRESSURE
// remainder are analogs and not used here
}};
if (dz_value > 0.0f)
m_button[pad] &= ~(1u << bitmask_mapping[index]);
else
+6
View File
@@ -40,6 +40,7 @@ namespace PAD
u8 m_button_pressure[NUM_CONTROLLER_PORTS][MAX_KEYS];
PADAnalog m_analog[NUM_CONTROLLER_PORTS];
float m_axis_scale[NUM_CONTROLLER_PORTS][2];
float m_trigger_scale[NUM_CONTROLLER_PORTS][2];
float m_vibration_scale[NUM_CONTROLLER_PORTS][2];
float m_pressure_modifier[NUM_CONTROLLER_PORTS];
float m_button_deadzone[NUM_CONTROLLER_PORTS];
@@ -66,6 +67,11 @@ namespace PAD
m_axis_scale[pad][0] = deadzone;
m_axis_scale[pad][1] = scale;
}
__fi void SetTriggerScale(u32 pad, float deadzone, float scale)
{
m_trigger_scale[pad][0] = deadzone;
m_trigger_scale[pad][1] = scale;
}
__fi float GetVibrationScale(u32 pad, u32 motor) const { return m_vibration_scale[pad][motor]; }
__fi void SetVibrationScale(u32 pad, u32 motor, float scale) { m_vibration_scale[pad][motor] = scale; }
__fi float GetPressureModifier(u32 pad) const { return m_pressure_modifier[pad]; }
+33 -17
View File
@@ -217,8 +217,11 @@ void PAD::LoadConfig(const SettingsInterface& si)
const float axis_deadzone = si.GetFloatValue(section.c_str(), "Deadzone", DEFAULT_STICK_DEADZONE);
const float axis_scale = si.GetFloatValue(section.c_str(), "AxisScale", DEFAULT_STICK_SCALE);
const float trigger_deadzone = si.GetFloatValue(section.c_str(), "TriggerDeadzone", DEFAULT_TRIGGER_DEADZONE);
const float trigger_scale = si.GetFloatValue(section.c_str(), "TriggerScale", DEFAULT_TRIGGER_SCALE);
const float button_deadzone = si.GetFloatValue(section.c_str(), "ButtonDeadzone", DEFAULT_BUTTON_DEADZONE);
g_key_status.SetAxisScale(i, axis_deadzone, axis_scale);
g_key_status.SetTriggerScale(i, trigger_deadzone, trigger_scale);
g_key_status.SetButtonDeadzone(i, button_deadzone);
if (ci->vibration_caps != VibrationCapabilities::NoVibration)
@@ -412,9 +415,15 @@ static const SettingInfo s_dualshock2_settings[] = {
"Sets the analog stick deadzone, i.e. the fraction of the stick movement which will be ignored.",
"0.00", "0.00", "1.00", "0.01", "%.0f%%", nullptr, nullptr, 100.0f},
{SettingInfo::Type::Float, "AxisScale", "Analog Sensitivity",
"Sets the analog stick axis scaling factor. A value between 1.30 and 1.40 is recommended when using recent "
"Sets the analog stick axis scaling factor. A value between 130% and 140% is recommended when using recent "
"controllers, e.g. DualShock 4, Xbox One Controller.",
"1.33", "0.01", "2.00", "0.01", "%.0f%%", nullptr, nullptr, 100.0f},
{SettingInfo::Type::Float, "TriggerDeadzone", "Trigger Deadzone",
"Sets the analog stick deadzone, i.e. the fraction of the stick movement which will be ignored.",
"0.00", "0.00", "1.00", "0.01", "%.0f%%", nullptr, nullptr, 100.0f},
{SettingInfo::Type::Float, "TriggerScale", "Trigger Sensitivity",
"Sets the trigger scaling factor.",
"1.00", "0.01", "2.00", "0.01", "%.0f%%", nullptr, nullptr, 100.0f},
{SettingInfo::Type::Float, "LargeMotorScale", "Large Motor Vibration Scale",
"Increases or decreases the intensity of low frequency vibration sent by the game.",
"1.00", "0.00", "2.00", "0.01", "%.0f%%", nullptr, nullptr, 100.0f},
@@ -504,7 +513,12 @@ void PAD::ClearPortBindings(SettingsInterface& si, u32 port)
return;
for (u32 i = 0; i < info->num_bindings; i++)
si.DeleteValue(section.c_str(), info->bindings[i].name);
{
const InputBindingInfo& bi = info->bindings[i];
si.DeleteValue(section.c_str(), bi.name);
si.DeleteValue(section.c_str(), fmt::format("{}Scale", bi.name).c_str());
si.DeleteValue(section.c_str(), fmt::format("{}Deadzone", bi.name).c_str());
}
}
void PAD::CopyConfiguration(SettingsInterface* dest_si, const SettingsInterface& src_si,
@@ -545,6 +559,8 @@ void PAD::CopyConfiguration(SettingsInterface* dest_si, const SettingsInterface&
{
const InputBindingInfo& bi = info->bindings[i];
dest_si->CopyStringListValue(src_si, section.c_str(), bi.name);
dest_si->CopyFloatValue(src_si, section.c_str(), fmt::format("{}Sensitivity", bi.name).c_str());
dest_si->CopyFloatValue(src_si, section.c_str(), fmt::format("{}Deadzone", bi.name).c_str());
}
for (u32 i = 0; i < NUM_MACRO_BUTTONS_PER_CONTROLLER; i++)
@@ -557,14 +573,6 @@ void PAD::CopyConfiguration(SettingsInterface* dest_si, const SettingsInterface&
if (copy_pad_config)
{
dest_si->CopyFloatValue(src_si, section.c_str(), "AxisScale");
if (info->vibration_caps != VibrationCapabilities::NoVibration)
{
dest_si->CopyFloatValue(src_si, section.c_str(), "LargeMotorScale");
dest_si->CopyFloatValue(src_si, section.c_str(), "SmallMotorScale");
}
for (u32 i = 0; i < info->num_settings; i++)
{
const SettingInfo& csi = info->settings[i];
@@ -601,8 +609,8 @@ void PAD::CopyConfiguration(SettingsInterface* dest_si, const SettingsInterface&
}
static u32 TryMapGenericMapping(SettingsInterface& si, const std::string& section,
const InputManager::GenericInputBindingMapping& mapping, GenericInputBinding generic_name,
const char* bind_name)
const InputManager::GenericInputBindingMapping& mapping, InputBindingInfo::Type bind_type,
GenericInputBinding generic_name, const char* bind_name)
{
// find the mapping it corresponds to
const std::string* found_mapping = nullptr;
@@ -615,6 +623,14 @@ static u32 TryMapGenericMapping(SettingsInterface& si, const std::string& sectio
}
}
// Remove previously-set binding scales.
if (bind_type == InputBindingInfo::Type::Button || bind_type == InputBindingInfo::Type::Axis ||
bind_type == InputBindingInfo::Type::HalfAxis)
{
si.DeleteValue(section.c_str(), fmt::format("{}Scale", bind_name).c_str());
si.DeleteValue(section.c_str(), fmt::format("{}Deadzone", bind_name).c_str());
}
if (found_mapping)
{
Console.WriteLn("(MapController) Map %s/%s to '%s'", section.c_str(), bind_name, found_mapping->c_str());
@@ -645,17 +661,17 @@ bool PAD::MapController(SettingsInterface& si, u32 controller,
if (bi.generic_mapping == GenericInputBinding::Unknown)
continue;
num_mappings += TryMapGenericMapping(si, section, mapping, bi.generic_mapping, bi.name);
num_mappings += TryMapGenericMapping(si, section, mapping, bi.bind_type, bi.generic_mapping, bi.name);
}
if (info->vibration_caps == VibrationCapabilities::LargeSmallMotors)
{
num_mappings += TryMapGenericMapping(si, section, mapping, GenericInputBinding::SmallMotor, "SmallMotor");
num_mappings += TryMapGenericMapping(si, section, mapping, GenericInputBinding::LargeMotor, "LargeMotor");
num_mappings += TryMapGenericMapping(si, section, mapping, InputBindingInfo::Type::Motor, GenericInputBinding::SmallMotor, "SmallMotor");
num_mappings += TryMapGenericMapping(si, section, mapping, InputBindingInfo::Type::Motor, GenericInputBinding::LargeMotor, "LargeMotor");
}
else if (info->vibration_caps == VibrationCapabilities::SingleMotor)
{
if (TryMapGenericMapping(si, section, mapping, GenericInputBinding::LargeMotor, "Motor") == 0)
num_mappings += TryMapGenericMapping(si, section, mapping, GenericInputBinding::SmallMotor, "Motor");
if (TryMapGenericMapping(si, section, mapping, InputBindingInfo::Type::Motor, GenericInputBinding::LargeMotor, "Motor") == 0)
num_mappings += TryMapGenericMapping(si, section, mapping, InputBindingInfo::Type::Motor, GenericInputBinding::SmallMotor, "Motor");
else
num_mappings++;
}
+2
View File
@@ -76,6 +76,8 @@ namespace PAD
/// Default stick deadzone/sensitivity.
static constexpr float DEFAULT_STICK_DEADZONE = 0.0f;
static constexpr float DEFAULT_STICK_SCALE = 1.33f;
static constexpr float DEFAULT_TRIGGER_DEADZONE = 0.0f;
static constexpr float DEFAULT_TRIGGER_SCALE = 1.0f;
static constexpr float DEFAULT_MOTOR_SCALE = 1.0f;
static constexpr float DEFAULT_PRESSURE_MODIFIER = 0.5f;
static constexpr float DEFAULT_BUTTON_DEADZONE = 0.0f;
+6 -6
View File
@@ -519,7 +519,7 @@ void DSRLV(){ if (!_Rd_) return; cpuRegs.GPR.r[_Rd_].UD[0] = (u64)(cpuRegs.GPR.r
// exceptions, since the lower bits of the address are used to determine the portions
// of the address/register operations.
[[ noreturn ]] __noinline static void RaiseAddressError(u32 addr, bool store)
__noinline static void RaiseAddressError(u32 addr, bool store)
{
const std::string message(fmt::format("Address Error, addr=0x{:x} [{}]", addr, store ? "store" : "load"));
@@ -1012,7 +1012,7 @@ void SYSCALL()
case Syscall::StartThread:
case Syscall::ChangeThreadPriority:
{
if (CurrentBiosInformation.threadListAddr == 0)
if (CurrentBiosInformation.eeThreadListAddr == 0)
{
u32 offset = 0x0;
// Suprisingly not that slow :)
@@ -1030,16 +1030,16 @@ void SYSCALL()
// We've found the instruction pattern!
// We (well, I) know that the thread address is always 0x8001 + the immediate of the 6th instruction from here
const u32 op = memRead32(0x80000000 + offset + (sizeof(u32) * 6));
CurrentBiosInformation.threadListAddr = 0x80010000 + static_cast<u16>(op) - 8; // Subtract 8 because the address here is offset by 8.
DevCon.WriteLn("BIOS: Successfully found the instruction pattern. Assuming the thread list is here: %0x", CurrentBiosInformation.threadListAddr);
CurrentBiosInformation.eeThreadListAddr = 0x80010000 + static_cast<u16>(op) - 8; // Subtract 8 because the address here is offset by 8.
DevCon.WriteLn("BIOS: Successfully found the instruction pattern. Assuming the thread list is here: %0x", CurrentBiosInformation.eeThreadListAddr);
break;
}
offset += 4;
}
if (!CurrentBiosInformation.threadListAddr)
if (!CurrentBiosInformation.eeThreadListAddr)
{
// We couldn't find the address
CurrentBiosInformation.threadListAddr = -1;
CurrentBiosInformation.eeThreadListAddr = -1;
// If you're here because a user has reported this message, this means that the instruction pattern is not present on their bios, or it is aligned weirdly.
Console.Warning("BIOS Warning: Unable to get a thread list offset. The debugger thread and stack frame views will not be functional.");
}
+1 -1
View File
@@ -15,4 +15,4 @@
/// Version number for GS and other shaders. Increment whenever any of the contents of the
/// shaders change, to invalidate the cache.
static constexpr u32 SHADER_CACHE_VERSION = 11;
static constexpr u32 SHADER_CACHE_VERSION = 12;
+5 -2
View File
@@ -71,11 +71,13 @@ namespace usb_lightgun
{"SLUS-20485", 90.25f, 92.5f, 390, 132, 640, 240}, // Dino Stalker (U)
{"SLUS-20389", 89.25f, 93.5f, 422, 141, 640, 240}, // Endgame (U)
{"SLES-50936", 112.0f, 100.0f, 320, 120, 512, 256}, // Endgame (E) (Guncon2 needs to be connected to USB port 2)
{"SLPM-65139", 90.0f, 91.5f, 320, 120, 640, 240}, // Gun Survivor 3: Dino Crisis (J)
{"SLES-52620", 89.5f, 112.3f, 390, 147, 640, 256}, // Guncom 2 (E)
{"SLES-51289", 84.5f, 89.0f, 456, 164, 640, 256}, // Gunfighter 2 - Jesse James (E)
{"SLPS-25165", 90.25f, 98.0f, 390, 138, 640, 240}, // Gunvari Collection (J) (480i)
// {"SLPS-25165", 86.75f, 96.0f, 454, 164, 640, 256}, // Gunvari Collection (J) (480p)
{"SCES-50889", 90.25f, 94.5f, 390, 169, 640, 256}, // Ninja Assault (E)
{"SLPS-20218", 90.0f, 92.0f, 320, 134, 640, 240}, // Ninja Assault (J)
{"SLUS-20492", 90.25f, 92.5f, 390, 132, 640, 240}, // Ninja Assault (U)
{"SLES-50650", 84.75f, 96.0f, 454, 164, 640, 240}, // Resident Evil Survivor 2 (E)
{"SLES-51448", 90.25f, 95.0f, 420, 132, 640, 240}, // Resident Evil - Dead Aim (E)
@@ -89,13 +91,14 @@ namespace usb_lightgun
{"SLUS-20927", 90.25f, 99.0f, 390, 153, 640, 240}, // Time Crisis - Crisis Zone (U) (480i)
// {"SLUS-20927", 94.5f, 104.75f, 423, 407, 768, 768}, // Time Crisis - Crisis Zone (U) (480p)
{"SCES-50411", 89.8f, 99.9f, 421, 138, 640, 256}, // Vampire Night (E)
{"SLPS-25077", 90.0f, 97.5f, 422, 118, 640, 240}, // Vampire Night (J)
{"SLUS-20221", 89.8f, 102.5f, 422, 124, 640, 228}, // Vampire Night (U)
{"SLES-51229", 110.15f, 100.0f, 433, 159, 512, 256}, // Virtua Cop - Elite Edition (E,J) (480i)
// {"SLES-51229", 85.75f, 92.0f, 456, 164, 640, 256}, // Virtua Cop - Elite Edition (E,J) (480p)
};
static constexpr s32 DEFAULT_SCREEN_WIDTH = 640;
static constexpr s32 DEFAULT_SCREEN_HEIGHT = 480;
static constexpr s32 DEFAULT_SCREEN_HEIGHT = 240;
static constexpr float DEFAULT_CENTER_X = 320.0f;
static constexpr float DEFAULT_CENTER_Y = 120.0f;
static constexpr float DEFAULT_SCALE_X = 100.0f;
@@ -359,7 +362,7 @@ namespace usb_lightgun
GSTranslateWindowToDisplayCoordinates(abs_pos.first, abs_pos.second, &pointer_x, &pointer_y);
s16 pos_x, pos_y;
if (pointer_x < 0.0f || pointer_y < 0.0f || (button_state & BID_SHOOT_OFFSCREEN))
if (pointer_x < 0.0f || pointer_y < 0.0f)
{
// off-screen
pos_x = 0;
+1 -3
View File
@@ -729,9 +729,8 @@ namespace usb_msd
int index, int length, uint8_t* data)
{
MSDState* s = USB_CONTAINER_OF(dev, MSDState, dev);
int ret = 0;
const int ret = usb_desc_handle_control(dev, p, request, value, index, length, data);
ret = usb_desc_handle_control(dev, p, request, value, index, length, data);
if (ret >= 0)
{
return;
@@ -743,7 +742,6 @@ namespace usb_msd
case ClassInterfaceOutRequest | MassStorageReset:
/* Reset state ready for the next CBW. */
s->f.mode = USB_MSDM_CBW;
ret = 0;
break;
case ClassInterfaceRequest | GetMaxLun:
data[0] = 0;
+1 -1
View File
@@ -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;
}
+3 -1
View File
@@ -25,7 +25,9 @@ const u32 ThreadListInstructions[3] =
struct BiosDebugInformation
{
u32 threadListAddr;
u32 eeThreadListAddr;
u32 iopThreadListAddr;
u32 iopModListAddr;
};
extern BiosDebugInformation CurrentBiosInformation;