GS: driver-bug database and per-driver shader workarounds

Ported from EmuCoreX with sashkinbro's approval. The GPU family alone was
never enough to decide behaviour: the same Mali part behaves differently
under ARM's proprietary driver than under Mesa PanVK, a lesson this tree
learned twice the expensive way - the r44p1 DEVICE_LOST fix had to be
gated on driverID rather than vendorID, and the 8 Elite push-descriptor
disable likewise. Driver identity, version and a bug set are now recorded
in a table, so the next device quirk is an entry rather than another
bespoke branch. Twenty-five rules, with match confidence so a vendor-wide
rule never overrides a driver-version-specific one.

The shaders gain gpu_bitwise_and / gpu_bitwise_not / gpu_boolean_not,
whose bodies the device emits: plain a & b normally, scalarized where the
database says the driver miscompiles vector bitwise ops. A device matching
no rule renders exactly as before.

opengl/tfx_fs.glsl already carried this fix as IAND3/UAND2/UAND4 macros
gated on GPU_PROFILE_MALI. Those are replaced by the shared helpers, but
the GL macro is deliberately (workaround || IsMaliGPUProfile()): Mali
reached through ANGLE or Panfrost resolves a non-ARM driver and matches no
rule, so a database-only gate would have silently removed a fix those
users have today. Widening only.

SHADER_CACHE_VERSION 108 -> 109. Every TFX and convert shader's source
text changed, so a blob cached from 108 no longer matches the source that
produced it; without the bump users would get stale binaries and garbage
rendering after updating.

RewriteConstantLoads is deliberately absent - the database records
BrokenConstantLoad with no workaround bits, so the macro would be
permanently zero and the shader code dead.

Shaders validated offline with glslc across 136 Vulkan and 296 GL/GLES
permutations, every macro on and off; the failure set is byte-identical to
the pre-change baseline.
This commit is contained in:
jpolo1224
2026-07-26 14:53:48 -04:00
committed by jpolo1224
parent 7c05cfd117
commit 4d8701a49d
13 changed files with 1069 additions and 68 deletions
+8 -8
View File
@@ -92,13 +92,13 @@ vec4 sample_c()
uint rgba8_to_uint(vec4 c)
{
uvec4 i = uvec4(c * 255.5f) & 0xFFu;
uvec4 i = gpu_bitwise_and(uvec4(c * 255.5f), uvec4(0xFFu));
return i.r | (i.g << 8) | (i.b << 16) | (i.a << 24);
}
uint rgb5a1_to_uint(vec4 c)
{
uvec4 i = uvec4(c * 255.5f) & uvec4(0xF8u, 0xF8u, 0xF8u, 0x80u);
uvec4 i = gpu_bitwise_and(uvec4(c * 255.5f), uvec4(0xF8u, 0xF8u, 0xF8u, 0x80u));
return (i.r >> 3) | (i.g << 2) | (i.b << 7) | (i.a << 8);
}
@@ -114,7 +114,7 @@ vec4 uint_to_rgba8(uint i)
vec4 uint_to_rgb5a1(uint i)
{
return vec4(uvec4(i << 3, i >> 2, i >> 7, i >> 8) & uvec4(0xF8u, 0xF8u, 0xF8u, 0x80u)) / 255.0f;
return vec4(gpu_bitwise_and(uvec4(i << 3, i >> 2, i >> 7, i >> 8), uvec4(0xF8u, 0xF8u, 0xF8u, 0x80u))) / 255.0f;
}
float uint_to_depth32(uint i)
@@ -323,8 +323,8 @@ void ps_convert_rgb5a1_8i()
uvec2 pos = uvec2(gl_FragCoord.xy);
// Collapse separate R G B A areas into their base pixel
uvec2 column = (pos & ~uvec2(0u, 3u)) / uvec2(1u, 2u);
uvec2 subcolumn = (pos & uvec2(0u, 1u));
uvec2 column = gpu_bitwise_and(pos, ~uvec2(0u, 3u)) / uvec2(1u, 2u);
uvec2 subcolumn = gpu_bitwise_and(pos, uvec2(0u, 1u));
column.x -= (column.x / 128u) * 64u;
column.y += (column.y / 32u) * 32u;
@@ -441,8 +441,8 @@ void ps_convert_rgba_8i()
uvec2 pos = uvec2(gl_FragCoord.xy);
// Collapse separate R G B A areas into their base pixel
uvec2 block = (pos & ~uvec2(15u, 3u)) >> 1;
uvec2 subblock = pos & uvec2(7u, 1u);
uvec2 block = gpu_bitwise_and(pos, ~uvec2(15u, 3u)) >> 1;
uvec2 subblock = gpu_bitwise_and(pos, uvec2(7u, 1u));
uvec2 coord = block | subblock;
// Compensate for potentially differing page pitch.
@@ -545,7 +545,7 @@ void ps_colclip_init()
void ps_colclip_resolve()
{
vec4 value = sample_c();
o_col0 = vec4(vec3(uvec3(value.rgb * 65535.0f) & 255u) / 255.0f, value.a);
o_col0 = vec4(vec3(gpu_bitwise_and(uvec3(value.rgb * 65535.0f), uvec3(255u))) / 255.0f, value.a);
}
#endif
+18 -27
View File
@@ -32,20 +32,11 @@
#define AFAIL_RGB_ONLY_SW_Z 5
#endif
// ARM Mali GLSL compilers miscompile a bitwise AND between two integer vectors
// when an operand is non-constant (the result collapses to the constant operand,
// dropping the mask). Scalarize per component on Mali so the compiler never sees
// a runtime vector & vector; the result is identical on conformant GPUs, so the
// non-Mali arm keeps the plain vector AND. (cf. Dolphin BUG_BROKEN_VECTOR_BITWISE_AND)
#if GPU_PROFILE_MALI
#define IAND3(v, m) ivec3((v).x & (m).x, (v).y & (m).y, (v).z & (m).z)
#define UAND2(v, m) uvec2((v).x & (m).x, (v).y & (m).y)
#define UAND4(v, m) uvec4((v).x & (m).x, (v).y & (m).y, (v).z & (m).z, (v).w & (m).w)
#else
#define IAND3(v, m) ((v) & (m))
#define UAND2(v, m) ((v) & (m))
#define UAND4(v, m) ((v) & (m))
#endif
// Driver-compiler workarounds (gpu_bitwise_and / gpu_bitwise_not / gpu_boolean_not) are defined in
// the generated shader header, gated on the driver-bug database. They replace the old IAND3/UAND2/
// UAND4 macros, which fixed the same ARM Mali defect (a runtime vector & vector collapses to the
// constant operand, dropping the mask) but keyed it on the GPU vendor rather than the driver blob.
// On anything without a matching rule they compile down to the plain operator.
#ifndef PS_ATST_NONE
#define PS_ATST_NONE 0
@@ -450,7 +441,7 @@ vec4 clamp_wrap_uv(vec4 uv)
// textures. Fixes Xenosaga's hair issue.
uv = fract(uv);
#endif
uv_out = vec4(UAND4(uvec4(uv * tex_size), floatBitsToUint(MinMax.xyxy)) | floatBitsToUint(MinMax.zwzw)) / tex_size;
uv_out = vec4(gpu_bitwise_and(uvec4(uv * tex_size), floatBitsToUint(MinMax.xyxy)) | floatBitsToUint(MinMax.zwzw)) / tex_size;
#endif
#else // PS_WMS != PS_WMT
@@ -468,7 +459,7 @@ vec4 clamp_wrap_uv(vec4 uv)
#if PS_FST == 0
uv.xz = fract(uv.xz);
#endif
uv_out.xz = vec2(UAND2(uvec2(uv.xz * tex_size.xx), floatBitsToUint(MinMax.xx)) | floatBitsToUint(MinMax.zz)) / tex_size.xx;
uv_out.xz = vec2(gpu_bitwise_and(uvec2(uv.xz * tex_size.xx), floatBitsToUint(MinMax.xx)) | floatBitsToUint(MinMax.zz)) / tex_size.xx;
#endif
@@ -485,7 +476,7 @@ vec4 clamp_wrap_uv(vec4 uv)
#if PS_FST == 0
uv.yw = fract(uv.yw);
#endif
uv_out.yw = vec2(UAND2(uvec2(uv.yw * tex_size.yy), floatBitsToUint(MinMax.yy)) | floatBitsToUint(MinMax.ww)) / tex_size.yy;
uv_out.yw = vec2(gpu_bitwise_and(uvec2(uv.yw * tex_size.yy), floatBitsToUint(MinMax.yy)) | floatBitsToUint(MinMax.ww)) / tex_size.yy;
#endif
#endif
@@ -834,7 +825,7 @@ vec4 sample_color(vec2 st)
c[i].a = ( (PS_AEM == 0) || any(bvec3(c[i].rgb)) ) ? TA.x : 0.0f;
//c[i].a = ( (PS_AEM == 0) || (sum > 0.0f) ) ? TA.x : 0.0f;
#elif (PS_AEM_FMT == FMT_16)
c[i].a = c[i].a >= 0.5 ? TA.y : ( (PS_AEM == 0) || any(bvec3(IAND3(ivec3(c[i].rgb * 255.0f), ivec3(0xF8)))) ) ? TA.x : 0.0f;
c[i].a = c[i].a >= 0.5 ? TA.y : ( (PS_AEM == 0) || any(bvec3(gpu_bitwise_and(ivec3(c[i].rgb * 255.0f), ivec3(0xF8)))) ) ? TA.x : 0.0f;
//c[i].a = c[i].a >= 0.5 ? TA.y : ( (PS_AEM == 0) || (sum > 0.0f) ) ? TA.x : 0.0f;
#endif
}
@@ -969,7 +960,7 @@ vec4 ps_color()
T.a = float(denorm_c_before.g & 0x80u);
#endif
T.a = ((T.a >= 127.5f) ? TA.y : ((PS_AEM == 0 || any(bvec3(IAND3(ivec3(T.rgb), ivec3(0xF8))))) ? TA.x : 0.0f)) * 255.0f;
T.a = ((T.a >= 127.5f) ? TA.y : ((PS_AEM == 0 || any(bvec3(gpu_bitwise_and(ivec3(T.rgb), ivec3(0xF8))))) ? TA.x : 0.0f)) * 255.0f;
#endif
vec4 C = tfx(T, PSin.c);
@@ -988,7 +979,7 @@ void ps_fbmask(inout vec4 C)
#else
vec4 RT = trunc(sample_from_rt() * 255.0f + 0.1f);
#endif
C = vec4(UAND4(uvec4(C), ~FbMask) | UAND4(uvec4(RT), FbMask));
C = vec4(gpu_bitwise_and(uvec4(C), gpu_bitwise_not(FbMask)) | gpu_bitwise_and(uvec4(RT), FbMask));
#endif
}
@@ -1046,13 +1037,13 @@ void ps_color_clamp_wrap(inout vec3 C)
// GPU: Color = 1/255, Alpha = 255/255 * 255/128 => output 1.9921875
#if PS_DST_FMT == FMT_16 && PS_DITHER < 3 && (PS_BLEND_MIX == 0 || PS_DITHER)
// In 16 bits format, only 5 bits of colors are used. It impacts shadows computation of Castlevania
C = vec3(IAND3(ivec3(C), ivec3(0xF8)));
C = vec3(gpu_bitwise_and(ivec3(C), ivec3(0xF8)));
#elif PS_COLCLIP == 1 || PS_COLCLIP_HW == 1
C = vec3(IAND3(ivec3(C), ivec3(0xFF)));
C = vec3(gpu_bitwise_and(ivec3(C), ivec3(0xFF)));
#endif
#elif PS_DST_FMT == FMT_16 && PS_DITHER != 3 && PS_BLEND_MIX == 0 && PS_BLEND_HW == 0
C = vec3(IAND3(ivec3(C), ivec3(0xF8)));
C = vec3(gpu_bitwise_and(ivec3(C), ivec3(0xF8)));
#endif
}
@@ -1328,7 +1319,7 @@ void ps_main()
bool atst_pass = atst(C);
#if PS_ATST != PS_ATST_NONE && PS_AFAIL == AFAIL_KEEP
if (!atst_pass)
if (gpu_boolean_not(atst_pass))
discard;
#endif
@@ -1434,13 +1425,13 @@ void ps_main()
// Alpha test with feedback
#if PS_AFAIL == AFAIL_FB_ONLY
if (!atst_pass)
if (gpu_boolean_not(atst_pass))
input_z = sample_from_depth();
#elif PS_AFAIL == AFAIL_ZB_ONLY
if (!atst_pass)
if (gpu_boolean_not(atst_pass))
C = sample_from_rt();
#elif (PS_AFAIL == AFAIL_RGB_ONLY || PS_AFAIL == AFAIL_RGB_ONLY_SW_Z)
if (!atst_pass)
if (gpu_boolean_not(atst_pass))
{
C.a = sample_from_rt().a;
#if PS_AFAIL == AFAIL_RGB_ONLY_SW_Z
+8 -8
View File
@@ -55,13 +55,13 @@ vec4 sample_c(vec2 uv)
uint rgba8_to_uint(vec4 c)
{
uvec4 i = uvec4(c * 255.5f) & 0xFFu;
uvec4 i = gpu_bitwise_and(uvec4(c * 255.5f), uvec4(0xFFu));
return i.r | (i.g << 8) | (i.b << 16) | (i.a << 24);
}
uint rgb5a1_to_uint(vec4 c)
{
uvec4 i = uvec4(c * 255.5f) & uvec4(0xF8u, 0xF8u, 0xF8u, 0x80u);
uvec4 i = gpu_bitwise_and(uvec4(c * 255.5f), uvec4(0xF8u, 0xF8u, 0xF8u, 0x80u));
return (i.r >> 3) | (i.g << 2) | (i.b << 7) | (i.a << 8);
}
@@ -77,7 +77,7 @@ vec4 uint_to_rgba8(uint i)
vec4 uint_to_rgb5a1(uint i)
{
return vec4(uvec4(i << 3, i >> 2, i >> 7, i >> 8) & uvec4(0xF8u, 0xF8u, 0xF8u, 0x80u)) / 255.0f;
return vec4(gpu_bitwise_and(uvec4(i << 3, i >> 2, i >> 7, i >> 8), uvec4(0xF8u, 0xF8u, 0xF8u, 0x80u))) / 255.0f;
}
float uint_to_depth32(uint i)
@@ -245,7 +245,7 @@ void ps_colclip_init()
void ps_colclip_resolve()
{
vec4 value = sample_c(v_tex);
OUTPUT = vec4(vec3(uvec3(value.rgb * 65535.5f) & 255u) / 255.0f, value.a);
OUTPUT = vec4(vec3(gpu_bitwise_and(uvec3(value.rgb * 65535.5f), uvec3(255u))) / 255.0f, value.a);
}
#endif
@@ -367,8 +367,8 @@ void ps_convert_rgb5a1_8i()
uvec2 pos = uvec2(gl_FragCoord.xy);
// Collapse separate R G B A areas into their base pixel
uvec2 column = (pos & ~uvec2(0u, 3u)) / uvec2(1u, 2u);
uvec2 subcolumn = (pos & uvec2(0u, 1u));
uvec2 column = gpu_bitwise_and(pos, ~uvec2(0u, 3u)) / uvec2(1u, 2u);
uvec2 subcolumn = gpu_bitwise_and(pos, uvec2(0u, 1u));
column.x -= (column.x / 128u) * 64u;
column.y += (column.y / 32u) * 32u;
@@ -490,8 +490,8 @@ void ps_convert_rgba_8i()
uvec2 pos = uvec2(gl_FragCoord.xy);
// Collapse separate R G B A areas into their base pixel
uvec2 block = (pos & ~uvec2(15u, 3u)) >> 1;
uvec2 subblock = pos & uvec2(7u, 1u);
uvec2 block = gpu_bitwise_and(pos, ~uvec2(15u, 3u)) >> 1;
uvec2 subblock = gpu_bitwise_and(pos, uvec2(7u, 1u));
uvec2 coord = block | subblock;
// Compensate for potentially differing page pitch.
+10 -10
View File
@@ -941,7 +941,7 @@ vec4 clamp_wrap_uv(vec4 uv)
// textures. Fixes Xenosaga's hair issue.
uv = fract(uv);
#endif
uv = vec4((uvec4(uv * tex_size) & floatBitsToUint(MinMax.xyxy)) | floatBitsToUint(MinMax.zwzw)) / tex_size;
uv = vec4(gpu_bitwise_and(uvec4(uv * tex_size), floatBitsToUint(MinMax.xyxy)) | floatBitsToUint(MinMax.zwzw)) / tex_size;
}
#endif
}
@@ -964,7 +964,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) & floatBitsToUint(MinMax.xx)) | floatBitsToUint(MinMax.zz)) / tex_size.xx;
uv.xz = vec2(gpu_bitwise_and(uvec2(uv.xz * tex_size.xx), floatBitsToUint(MinMax.xx)) | floatBitsToUint(MinMax.zz)) / tex_size.xx;
}
#endif
#if PS_REGION_RECT == 1 && PS_WMT == 0
@@ -984,7 +984,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) & floatBitsToUint(MinMax.yy)) | floatBitsToUint(MinMax.ww)) / tex_size.yy;
uv.yw = vec2(gpu_bitwise_and(uvec2(uv.yw * tex_size.yy), floatBitsToUint(MinMax.yy)) | floatBitsToUint(MinMax.ww)) / tex_size.yy;
}
#endif
}
@@ -1324,7 +1324,7 @@ vec4 sample_color(vec2 st)
#if (PS_AEM_FMT == FMT_24)
c[i].a = (PS_AEM == 0 || any(bvec3(c[i].rgb))) ? TA.x : 0.0f;
#elif (PS_AEM_FMT == FMT_16)
c[i].a = (c[i].a >= 0.5) ? TA.y : ((PS_AEM == 0 || any(bvec3(ivec3(c[i].rgb * 255.0f) & ivec3(0xF8)))) ? TA.x : 0.0f);
c[i].a = (c[i].a >= 0.5) ? TA.y : ((PS_AEM == 0 || any(bvec3(gpu_bitwise_and(ivec3(c[i].rgb * 255.0f), ivec3(0xF8))))) ? TA.x : 0.0f);
#endif
}
@@ -1456,7 +1456,7 @@ vec4 ps_color()
T.a = float(denorm_c_before.g & 0x80u);
#endif
T.a = ((T.a >= 127.5f) ? TA.y : ((PS_AEM == 0 || any(bvec3(ivec3(T.rgb) & ivec3(0xF8)))) ? TA.x : 0.0f)) * 255.0f;
T.a = ((T.a >= 127.5f) ? TA.y : ((PS_AEM == 0 || any(bvec3(gpu_bitwise_and(ivec3(T.rgb), ivec3(0xF8))))) ? TA.x : 0.0f)) * 255.0f;
#endif
vec4 C = tfx(T, vsIn.c);
@@ -1474,7 +1474,7 @@ void ps_fbmask(inout vec4 C)
#else
vec4 RT = trunc(sample_from_rt() * 255.0f + 0.1f);
#endif
C = vec4((uvec4(C) & ~FbMask) | (uvec4(RT) & FbMask));
C = vec4(gpu_bitwise_and(uvec4(C), ~FbMask) | gpu_bitwise_and(uvec4(RT), FbMask));
#endif
}
@@ -1489,7 +1489,7 @@ void ps_dither(inout vec3 C, float As)
fpos = ivec2(gl_FragCoord.xy * RcpScaleFactor);
#endif
float value = DitherMatrix[fpos.y & 3][fpos.x & 3];
float value = gpu_matrix_element(DitherMatrix, fpos.y & 3, fpos.x & 3);
// The idea here is we add on the dither amount adjusted by the alpha before it goes to the hw blend
// so after the alpha blend the resulting value should be the same as (Cs - Cd) * As + Cd + Dither.
@@ -1535,13 +1535,13 @@ void ps_color_clamp_wrap(inout vec3 C)
// GPU: Color = 1/255, Alpha = 255/255 * 255/128 => output 1.9921875
#if PS_DST_FMT == FMT_16 && PS_DITHER != 3 && (PS_BLEND_MIX == 0 || PS_DITHER > 0)
// In 16 bits format, only 5 bits of colors are used. It impacts shadows computation of Castlevania
C = vec3(ivec3(C) & ivec3(0xF8));
C = vec3(gpu_bitwise_and(ivec3(C), ivec3(0xF8)));
#elif PS_COLCLIP == 1 || PS_COLCLIP_HW == 1
C = vec3(ivec3(C) & ivec3(0xFF));
C = vec3(gpu_bitwise_and(ivec3(C), ivec3(0xFF)));
#endif
#elif PS_DST_FMT == FMT_16 && PS_DITHER != 3 && PS_BLEND_MIX == 0 && PS_BLEND_HW == 0
C = vec3(ivec3(C) & ivec3(0xF8));
C = vec3(gpu_bitwise_and(ivec3(C), ivec3(0xF8)));
#endif
}
+2 -1
View File
@@ -1168,7 +1168,8 @@ if(ANDROID)
list(APPEND pcsx2GSSources
GS/Renderers/OpenGL/GLContextEGL.cpp
GS/Renderers/OpenGL/GLContextEGLAndroid.cpp
GS/Renderers/Common/GSGPUProfile.cpp
GS/Renderers/Common/GSGPUDriverProfile.cpp
GS/Renderers/Common/GSGPUProfile.cpp
GS/Renderers/Common/GSGPUProfileMali.cpp
GS/Renderers/Common/GSGPUProfileAdreno.cpp
GS/Renderers/Common/GSGPUProfilePowerVR.cpp)
+17
View File
@@ -1471,6 +1471,12 @@ protected:
// GPU-profile system (sashkinbro/EmuCoreX). Drives texture/target pool sizing on Android below.
MobileGpuIdentity m_mobile_gpu_identity;
MobileGsTuning m_mobile_gs_tuning;
// Resolved driver identity + the workarounds the driver-bug database says this exact
// driver needs. Kept beside the GPU identity because the two answer different questions:
// the identity is "which silicon", this is "which blob", and the blob is what actually
// miscompiles shaders (see GSGPUDriverProfile.cpp). Empty/conservative until a backend
// resolves it, so a device with no matching rule behaves exactly as it did before.
MobileDriverProfile m_mobile_driver_profile;
// Android: true when the SoC is MediaTek (Dimensity/Helio). Hoisted from GSDeviceVK
// so both backends + GS.cpp Android GameDB overrides can read it. Set during device
// open from the resolved GPU profile.
@@ -1665,6 +1671,17 @@ public:
__fi void SetMobileGSTuning(const MobileGsTuning& tuning) { m_mobile_gs_tuning = tuning; }
__fi const MobileGpuIdentity& GetMobileGPUIdentity() const { return m_mobile_gpu_identity; }
__fi const MobileGsTuning& GetMobileGSTuning() const { return m_mobile_gs_tuning; }
__fi void SetMobileDriverProfile(const MobileDriverProfile& profile) { m_mobile_driver_profile = profile; }
__fi const MobileDriverProfile& GetMobileDriverProfile() const { return m_mobile_driver_profile; }
/// The driver is *known* to have this defect. Diagnostics only — never gate rendering on it,
/// because a recorded bug whose mitigation is not integrated yet still has no workaround bit.
__fi bool HasMobileDriverBug(DriverBug bug) const { return m_mobile_driver_profile.HasBug(bug); }
/// The one call sites should use: "is this mitigation active for this driver". False for every
/// device the database has no rule for, so untouched hardware keeps its existing behaviour.
__fi bool UsesMobileDriverWorkaround(DriverWorkaround workaround) const
{
return m_mobile_driver_profile.UsesWorkaround(workaround);
}
__fi bool IsConstrainedMobileGPUProfile() const { return m_mobile_gs_tuning.constrained; }
__fi RuntimeGpuProfile GetRuntimeGPUProfile() const { return m_runtime_gpu_profile; }
__fi void SetMediaTekSoC(bool v) { m_is_mediatek_soc = v; }
File diff suppressed because it is too large Load Diff
+113 -7
View File
@@ -87,11 +87,15 @@ static std::string GetAndroidProperty(const char* name)
}
#endif
static std::string BuildHints(std::string_view gpu_vendor, std::string_view gpu_renderer_or_name)
static std::string BuildHints(std::string_view gpu_vendor, std::string_view gpu_renderer_or_name,
const MobileDriverContext& driver_context)
{
std::string hints;
AppendHint(hints, "gpu_vendor", gpu_vendor);
AppendHint(hints, "gpu", gpu_renderer_or_name);
AppendHint(hints, "driver_name", driver_context.driver_name);
AppendHint(hints, "driver_info", driver_context.driver_info);
AppendHint(hints, "api_version", driver_context.api_version_string);
#if defined(__ANDROID__)
static constexpr const char* property_names[] = {
@@ -254,33 +258,135 @@ static void ApplyResolvedProfile(GpuProfileSelection& selection, RuntimeGpuProfi
selection.gs_tuning = resolved.tuning;
}
const char* GpuProfileDetector::ApiToString(MobileGpuApi value)
{
switch (value)
{
case MobileGpuApi::OpenGL: return "OpenGL";
case MobileGpuApi::Vulkan: return "Vulkan";
case MobileGpuApi::Unknown:
default: return "Unknown";
}
}
const char* GpuProfileDetector::DriverToString(MobileGpuDriver value)
{
switch (value)
{
case MobileGpuDriver::ArmProprietary: return "ARM proprietary";
case MobileGpuDriver::MesaPanVK: return "Mesa PanVK";
case MobileGpuDriver::QualcommProprietary: return "Qualcomm proprietary";
case MobileGpuDriver::MesaTurnip: return "Mesa Turnip";
case MobileGpuDriver::ImaginationProprietary: return "Imagination proprietary";
case MobileGpuDriver::MesaPowerVR: return "Mesa PowerVR";
case MobileGpuDriver::Angle: return "ANGLE";
case MobileGpuDriver::Unknown:
default: return "Unknown";
}
}
const char* GpuProfileDetector::BugToString(DriverBug value)
{
switch (value)
{
case DriverBug::BrokenBufferStreaming: return "BrokenBufferStreaming";
case DriverBug::BrokenUnsynchronizedMapping: return "BrokenUnsynchronizedMapping";
case DriverBug::BrokenNegatedBoolean: return "BrokenNegatedBoolean";
case DriverBug::BrokenVectorBitwiseAnd: return "BrokenVectorBitwiseAnd";
case DriverBug::BrokenBitwiseOpNegation: return "BrokenBitwiseOpNegation";
case DriverBug::BrokenPrimitiveRestart: return "BrokenPrimitiveRestart";
case DriverBug::BrokenPushDescriptors: return "BrokenPushDescriptors";
case DriverBug::BrokenProvokingVertex: return "BrokenProvokingVertex";
case DriverBug::BrokenAttachmentFeedbackLoopLayout: return "BrokenAttachmentFeedbackLoopLayout";
case DriverBug::BrokenSubpassFeedback: return "BrokenSubpassFeedback";
case DriverBug::BrokenColorWriteMaskWithDepthTest: return "BrokenColorWriteMaskWithDepthTest";
case DriverBug::BrokenDepthStencilDiscard: return "BrokenDepthStencilDiscard";
case DriverBug::BrokenReversedDepthRange: return "BrokenReversedDepthRange";
case DriverBug::SlowCachedReadbackMemory: return "SlowCachedReadbackMemory";
case DriverBug::SlowOptimalImageToBufferCopy: return "SlowOptimalImageToBufferCopy";
case DriverBug::BrokenClearLoadOpRenderPass: return "BrokenClearLoadOpRenderPass";
case DriverBug::Broken16BitTextureFormats: return "Broken16BitTextureFormats";
case DriverBug::BrokenGenerateMipmapTallTexture: return "BrokenGenerateMipmapTallTexture";
case DriverBug::BrokenEmptyRenderPass: return "BrokenEmptyRenderPass";
case DriverBug::BrokenConstantLoad: return "BrokenConstantLoad";
case DriverBug::BrokenUniformIndexing: return "BrokenUniformIndexing";
case DriverBug::BrokenVSync: return "BrokenVSync";
case DriverBug::BrokenMultithreadedShaderCompilation: return "BrokenMultithreadedShaderCompilation";
case DriverBug::BrokenDynamicRendering: return "BrokenDynamicRendering";
case DriverBug::BrokenImagelessFramebuffer: return "BrokenImagelessFramebuffer";
case DriverBug::BrokenExtendedDynamicState: return "BrokenExtendedDynamicState";
case DriverBug::BrokenPrimitiveTopologyDynamicState: return "BrokenPrimitiveTopologyDynamicState";
case DriverBug::BrokenGraphicsPipelineLibrary: return "BrokenGraphicsPipelineLibrary";
case DriverBug::Count:
default: return "Unknown";
}
}
const char* GpuProfileDetector::WorkaroundToString(DriverWorkaround value)
{
switch (value)
{
case DriverWorkaround::RewriteBooleanNegation: return "RewriteBooleanNegation";
case DriverWorkaround::ScalarizeVectorBitwiseAnd: return "ScalarizeVectorBitwiseAnd";
case DriverWorkaround::StoreBitwiseNegationInTemporary: return "StoreBitwiseNegationInTemporary";
case DriverWorkaround::UseDescriptorSets: return "UseDescriptorSets";
case DriverWorkaround::DisableProvokingVertex: return "DisableProvokingVertex";
case DriverWorkaround::DisableAttachmentFeedbackLoopLayout: return "DisableAttachmentFeedbackLoopLayout";
case DriverWorkaround::EmulateColorWriteMask: return "EmulateColorWriteMask";
case DriverWorkaround::PreferCoherentReadback: return "PreferCoherentReadback";
case DriverWorkaround::UseStagingImageForReadback: return "UseStagingImageForReadback";
case DriverWorkaround::AvoidClearLoadOpRenderPass: return "AvoidClearLoadOpRenderPass";
case DriverWorkaround::GenerateMipmapManuallyForTallTextures: return "GenerateMipmapManuallyForTallTextures";
case DriverWorkaround::RewriteUniformIndexing: return "RewriteUniformIndexing";
case DriverWorkaround::ForceFifoPresent: return "ForceFifoPresent";
case DriverWorkaround::AlignSwapchainWidthTo32: return "AlignSwapchainWidthTo32";
case DriverWorkaround::Count:
default: return "Unknown";
}
}
GpuProfileSelection GpuProfileDetector::Resolve(std::string_view override_value, std::string_view gpu_vendor,
std::string_view gpu_renderer_or_name)
{
// No driver context: the driver profile stays in its conservative-fallback state, so callers
// that have not been taught to pass one behave exactly as before.
return Resolve(override_value, gpu_vendor, gpu_renderer_or_name, MobileDriverContext{});
}
GpuProfileSelection GpuProfileDetector::Resolve(std::string_view override_value, std::string_view gpu_vendor,
std::string_view gpu_renderer_or_name, const MobileDriverContext& driver_context)
{
GpuProfileSelection selection;
selection.override_mode = ParseOverride(override_value);
selection.hints = BuildHints(gpu_vendor, gpu_renderer_or_name);
selection.hints = BuildHints(gpu_vendor, gpu_renderer_or_name, driver_context);
const std::string lowered_hints = GpuProfileDetail::ToLowerASCII(selection.hints);
const std::string lowered_override = GpuProfileDetail::ToLowerASCII(override_value);
selection.is_mediatek_soc = (lowered_override == "mediatek") || LooksLikeMediaTekSoc(lowered_hints);
selection.gs_tuning = GpuProfileDetail::MakeConservativeMobileGsTuning();
// Attached on every exit path so the driver profile is always populated, whether the family
// came from an override, from detection, or from nothing at all.
const auto finalize = [&]() {
selection.driver = GpuProfileDetail::ResolveDriverProfile(selection, driver_context, lowered_hints);
return selection;
};
if (selection.override_mode == GpuProfileOverride::Mali)
{
ApplyResolvedProfile(selection, RuntimeGpuProfile::Mali, GpuProfileDetail::ResolveMaliProfile(lowered_hints));
return selection;
return finalize();
}
if (selection.override_mode == GpuProfileOverride::Adreno)
{
ApplyResolvedProfile(selection, RuntimeGpuProfile::Adreno, GpuProfileDetail::ResolveAdrenoProfile(lowered_hints));
return selection;
return finalize();
}
if (selection.override_mode == GpuProfileOverride::PowerVR)
{
ApplyResolvedProfile(selection, RuntimeGpuProfile::PowerVR, GpuProfileDetail::ResolvePowerVRProfile(lowered_hints));
return selection;
return finalize();
}
if (selection.override_mode == GpuProfileOverride::Xclipse)
@@ -291,7 +397,7 @@ GpuProfileSelection GpuProfileDetector::Resolve(std::string_view override_value,
// profile is enough for GSDeviceVK to force fbfetch off; keep the conservative GS
// tuning already assigned above.
selection.runtime_profile = RuntimeGpuProfile::Xclipse;
return selection;
return finalize();
}
if (GpuProfileDetail::LooksLikeAdreno(lowered_hints))
@@ -307,5 +413,5 @@ GpuProfileSelection GpuProfileDetector::Resolve(std::string_view override_value,
ApplyResolvedProfile(selection, RuntimeGpuProfile::Mali, GpuProfileDetail::ResolveMaliProfile(lowered_hints));
}
return selection;
return finalize();
}
+166
View File
@@ -54,6 +54,163 @@ enum class MobileGpuArchitecture : u8
PowerVR,
};
// ---------------------------------------------------------------------------------------------
// Driver identity and known-bug model, ported from EmuCoreX (sashkinbro) with his approval.
//
// The GPU family alone is not enough to decide behaviour: the same Mali part behaves differently
// under Arm's proprietary driver than under Mesa PanVK, which is a lesson this tree learned the
// expensive way (the r44p1 DEVICE_LOST fix had to be gated on driverID, not vendorID, and the
// 8 Elite push-descriptor disable likewise). Recording it as driver + version + a bug set means
// the next device-specific quirk is a table entry rather than another bespoke branch.
// ---------------------------------------------------------------------------------------------
enum class MobileGpuApi : u8
{
Unknown,
OpenGL,
Vulkan,
};
// Deliberately independent of VkDriverId so profile resolution stays unit-testable without
// pulling in Vulkan headers, and so the OpenGL path can use the same table.
enum class MobileGpuDriver : u8
{
Unknown,
ArmProprietary,
MesaPanVK,
QualcommProprietary,
MesaTurnip,
ImaginationProprietary,
MesaPowerVR,
Angle,
};
/// How specifically a profile was matched. A rule matched on the exact driver version is worth
/// more than one matched on the vendor alone, so a broad entry never overrides a precise one.
enum class DriverProfileConfidence : u8
{
Unknown,
Vendor,
Model,
Driver,
DriverVersion,
};
/// Observed driver defects. Naming is descriptive of the DEFECT, not of the fix, so one bug can
/// drive several workarounds and the table stays readable.
enum class DriverBug : u8
{
BrokenBufferStreaming,
BrokenUnsynchronizedMapping,
BrokenNegatedBoolean,
BrokenVectorBitwiseAnd,
BrokenBitwiseOpNegation,
BrokenPrimitiveRestart,
BrokenPushDescriptors,
BrokenProvokingVertex,
BrokenAttachmentFeedbackLoopLayout,
BrokenSubpassFeedback,
BrokenColorWriteMaskWithDepthTest,
BrokenDepthStencilDiscard,
BrokenReversedDepthRange,
SlowCachedReadbackMemory,
SlowOptimalImageToBufferCopy,
BrokenClearLoadOpRenderPass,
Broken16BitTextureFormats,
BrokenGenerateMipmapTallTexture,
BrokenEmptyRenderPass,
BrokenConstantLoad,
BrokenUniformIndexing,
BrokenVSync,
BrokenMultithreadedShaderCompilation,
BrokenDynamicRendering,
BrokenImagelessFramebuffer,
BrokenExtendedDynamicState,
BrokenPrimitiveTopologyDynamicState,
BrokenGraphicsPipelineLibrary,
Count,
};
/// What we actually DO about a bug. Kept separate from [DriverBug] because the same mitigation
/// answers several defects, and because a workaround can be forced on for testing without
/// claiming the device has the bug.
enum class DriverWorkaround : u8
{
RewriteBooleanNegation,
ScalarizeVectorBitwiseAnd,
StoreBitwiseNegationInTemporary,
UseDescriptorSets,
DisableProvokingVertex,
DisableAttachmentFeedbackLoopLayout,
EmulateColorWriteMask,
PreferCoherentReadback,
UseStagingImageForReadback,
AvoidClearLoadOpRenderPass,
GenerateMipmapManuallyForTallTextures,
RewriteUniformIndexing,
ForceFifoPresent,
AlignSwapchainWidthTo32,
Count,
};
struct MobileDriverVersion
{
u32 raw = 0;
u16 major = 0;
u16 minor = 0;
u16 patch = 0;
u32 build = 0;
bool known = false;
bool legacy_hash = false;
};
/// Everything the resolver is allowed to look at. Filled from VkPhysicalDeviceProperties on the
/// Vulkan path and from the GL strings otherwise.
struct MobileDriverContext
{
MobileGpuApi api = MobileGpuApi::Unknown;
u32 vendor_id = 0;
u32 device_id = 0;
u32 driver_version = 0;
u32 driver_id = 0;
u32 api_version = 0;
u32 android_sdk = 0;
u32 max_draw_indirect_count = 0;
std::string_view driver_name;
std::string_view driver_info;
std::string_view api_version_string;
};
struct MobileDriverProfile
{
static constexpr u32 DATABASE_VERSION = 1;
MobileGpuApi api = MobileGpuApi::Unknown;
MobileGpuDriver driver = MobileGpuDriver::Unknown;
MobileDriverVersion version;
u64 bugs = 0;
u64 workarounds = 0;
u32 matched_rule_count = 0;
DriverProfileConfidence confidence = DriverProfileConfidence::Unknown;
/// True when nothing in the table matched and the safe defaults are in force.
bool conservative_fallback = true;
std::string driver_name;
constexpr bool HasBug(DriverBug bug) const
{
return (bugs & (u64{1} << static_cast<u8>(bug))) != 0;
}
constexpr bool UsesWorkaround(DriverWorkaround workaround) const
{
return (workarounds & (u64{1} << static_cast<u8>(workaround))) != 0;
}
};
// Both sets are u64 bitfields, so neither enum may exceed 64 entries without widening them.
static_assert(static_cast<u8>(DriverBug::Count) <= 64);
static_assert(static_cast<u8>(DriverWorkaround::Count) <= 64);
struct MobileGsTuning
{
bool constrained = true;
@@ -81,6 +238,7 @@ struct GpuProfileSelection
bool is_mediatek_soc = false;
MobileGpuIdentity gpu;
MobileGsTuning gs_tuning;
MobileDriverProfile driver;
std::string hints;
};
@@ -92,7 +250,15 @@ public:
static const char* OverrideToString(GpuProfileOverride value);
static const char* RuntimeProfileToString(RuntimeGpuProfile value);
static const char* ArchitectureToString(MobileGpuArchitecture value);
static const char* ApiToString(MobileGpuApi value);
static const char* DriverToString(MobileGpuDriver value);
static const char* BugToString(DriverBug value);
static const char* WorkaroundToString(DriverWorkaround value);
static GpuProfileSelection Resolve(std::string_view override_value, std::string_view gpu_vendor,
std::string_view gpu_renderer_or_name);
/// Overload that also resolves the driver profile. The three-argument form keeps working and
/// simply leaves GpuProfileSelection::driver in its conservative-fallback state.
static GpuProfileSelection Resolve(std::string_view override_value, std::string_view gpu_vendor,
std::string_view gpu_renderer_or_name, const MobileDriverContext& driver_context);
};
@@ -32,4 +32,6 @@ ResolvedGpuProfile ResolveMaliProfile(std::string_view lowered_hints);
bool LooksLikePowerVR(std::string_view lowered_hints);
ResolvedGpuProfile ResolvePowerVRProfile(std::string_view lowered_hints);
MobileDriverProfile ResolveDriverProfile(const GpuProfileSelection& selection,
const MobileDriverContext& context, std::string_view lowered_hints);
} // namespace GpuProfileDetail
+104 -5
View File
@@ -787,8 +787,12 @@ bool GSDeviceOGL::CheckFeatures()
const char* vendor_raw = (const char*)glGetString(GL_VENDOR);
const char* renderer_raw = (const char*)glGetString(GL_RENDERER);
const char* gl_version_raw = (const char*)glGetString(GL_VERSION);
const char* vendor_str = vendor_raw ? vendor_raw : "";
const char* renderer_str = renderer_raw ? renderer_raw : "";
// GL_VERSION is the only place the GLES stack names its own build ("OpenGL ES 3.2 v1.r44p1-...",
// "... V@0502", "... build 1.9@4850625"), so it is what the driver-profile resolver parses.
const char* gl_version_str = gl_version_raw ? gl_version_raw : "";
if (std::strstr(vendor_str, "Advanced Micro Devices") || std::strstr(vendor_str, "ATI Technologies Inc.") ||
std::strstr(vendor_str, "ATI"))
@@ -838,15 +842,35 @@ bool GSDeviceOGL::CheckFeatures()
// regressed FPS on Mali-G615). The Mali-G77 crash under ANGLE was NOT these hacks but stale
// program binaries from the native driver being fed to ANGLE's glProgramBinary(); that is
// fixed at the source in GLShaderCache (driver-keyed cache), so no per-GPU profile gating here.
const GpuProfileSelection profile_selection =
GpuProfileDetector::Resolve(GSConfig.AndroidGpuProfileOverride, vendor_str, renderer_str);
//
// The driver context below feeds the driver-bug database (ported from EmuCoreX/sashkinbro with
// his approval). GL has no equivalent of VkPhysicalDeviceDriverProperties, so the renderer and
// version strings are all the identity there is; the resolver parses the vendor-specific build
// tag out of them. Nothing here changes behaviour on its own — every workaround it can turn on
// is off unless a rule matches this exact driver.
MobileDriverContext driver_context;
driver_context.api = MobileGpuApi::OpenGL;
driver_context.driver_name = renderer_str;
driver_context.api_version_string = gl_version_str;
const GpuProfileSelection profile_selection = GpuProfileDetector::Resolve(
GSConfig.AndroidGpuProfileOverride, vendor_str, renderer_str, driver_context);
SetRuntimeGPUProfile(profile_selection.runtime_profile);
SetMobileGPUIdentity(profile_selection.gpu);
SetMobileGSTuning(profile_selection.gs_tuning);
SetMobileDriverProfile(profile_selection.driver);
SetMediaTekSoC(profile_selection.is_mediatek_soc);
Console.WriteLn("GL: GPU profile override='%s' resolved='%s'.",
Console.WriteLn("GL: GPU profile override='%s' resolved='%s' driver='%s' version=%u.%u.%u.%u "
"rules=%u bugs=%016llx workarounds=%016llx.",
GpuProfileDetector::OverrideToConfigString(profile_selection.override_mode),
GpuProfileDetector::RuntimeProfileToString(profile_selection.runtime_profile));
GpuProfileDetector::RuntimeProfileToString(profile_selection.runtime_profile),
GpuProfileDetector::DriverToString(profile_selection.driver.driver),
static_cast<unsigned>(profile_selection.driver.version.major),
static_cast<unsigned>(profile_selection.driver.version.minor),
static_cast<unsigned>(profile_selection.driver.version.patch),
static_cast<unsigned>(profile_selection.driver.version.build),
static_cast<unsigned>(profile_selection.driver.matched_rule_count),
static_cast<unsigned long long>(profile_selection.driver.bugs),
static_cast<unsigned long long>(profile_selection.driver.workarounds));
DevCon.WriteLn("GL: GPU profile hints: %s", profile_selection.hints.c_str());
bool use_mali_profile = IsMaliGPUProfile();
bool use_adreno_profile = IsAdrenoGPUProfile();
@@ -880,7 +904,7 @@ bool GSDeviceOGL::CheckFeatures()
// Log extension string for debugging purposes.
Console.WriteLn(fmt::format("GL_VENDOR: {}", reinterpret_cast<const char*>(glGetString(GL_VENDOR))));
Console.WriteLn(fmt::format("GL_VERSION: {}", reinterpret_cast<const char*>(glGetString(GL_VERSION))));
Console.WriteLn(fmt::format("GL_VERSION: {}", gl_version_str));
Console.WriteLn(fmt::format("GL_RENDERER: {}", reinterpret_cast<const char*>(glGetString(GL_RENDERER))));
Console.WriteLn(fmt::format(
"GL_SHADING_LANGUAGE_VERSION: {}", reinterpret_cast<const char*>(glGetString(GL_SHADING_LANGUAGE_VERSION))));
@@ -2060,6 +2084,21 @@ std::string GSDeviceOGL::GenGlslHeader(const std::string_view entry, GLenum type
header += fmt::format("#define GPU_PROFILE_POWERVR {}\n", IsPowerVRGPUProfile() ? 1 : 0);
header += fmt::format("#define HAS_ARM_DEPTH_FETCH {}\n", m_arm_depth_fetch ? 1 : 0);
// Shader-compiler workarounds from the driver-bug database (ported from EmuCoreX/sashkinbro
// with his approval). Each one is off unless a rule matched this exact driver, so the emitted
// GLSL is byte-identical to before on anything the database does not know about.
//
// ScalarizeVectorBitwiseAnd additionally keeps the pre-existing IsMaliGPUProfile() gate: this
// tree has scalarized vector ANDs on every Mali GL profile since the original fix, including
// Mali reached through ANGLE or Panfrost where the database resolves a non-ARM driver and would
// otherwise match nothing. Widening only, never narrowing — nobody loses a fix they had.
header += fmt::format("#define DRIVER_SCALARIZE_VECTOR_BITWISE_AND {}\n",
(UsesMobileDriverWorkaround(DriverWorkaround::ScalarizeVectorBitwiseAnd) || IsMaliGPUProfile()) ? 1 : 0);
header += fmt::format("#define DRIVER_REWRITE_BOOLEAN_NEGATION {}\n",
UsesMobileDriverWorkaround(DriverWorkaround::RewriteBooleanNegation) ? 1 : 0);
header += fmt::format("#define DRIVER_STORE_BITWISE_NEGATION_IN_TEMPORARY {}\n",
UsesMobileDriverWorkaround(DriverWorkaround::StoreBitwiseNegationInTemporary) ? 1 : 0);
if (GLAD_GL_ARB_conservative_depth)
{
header += "#extension GL_ARB_conservative_depth : enable\n";
@@ -2116,6 +2155,66 @@ std::string GSDeviceOGL::GenGlslHeader(const std::string_view entry, GLenum type
header += macro;
// Emitted last, after every #extension directive above: GLSL requires those to precede any
// non-preprocessor token, and these are real function definitions. The bodies come straight
// from EmuCoreX so the .glsl call sites stay identical between the two trees.
header += R"(
bool gpu_boolean_not(bool value)
{
#if DRIVER_REWRITE_BOOLEAN_NEGATION
return value == false;
#else
return !value;
#endif
}
uvec2 gpu_bitwise_and(uvec2 a, uvec2 b)
{
#if DRIVER_SCALARIZE_VECTOR_BITWISE_AND
return uvec2(a.x & b.x, a.y & b.y);
#else
return a & b;
#endif
}
uvec3 gpu_bitwise_and(uvec3 a, uvec3 b)
{
#if DRIVER_SCALARIZE_VECTOR_BITWISE_AND
return uvec3(a.x & b.x, a.y & b.y, a.z & b.z);
#else
return a & b;
#endif
}
uvec4 gpu_bitwise_and(uvec4 a, uvec4 b)
{
#if DRIVER_SCALARIZE_VECTOR_BITWISE_AND
return uvec4(a.x & b.x, a.y & b.y, a.z & b.z, a.w & b.w);
#else
return a & b;
#endif
}
ivec3 gpu_bitwise_and(ivec3 a, ivec3 b)
{
#if DRIVER_SCALARIZE_VECTOR_BITWISE_AND
return ivec3(a.x & b.x, a.y & b.y, a.z & b.z);
#else
return a & b;
#endif
}
uvec4 gpu_bitwise_not(uvec4 value)
{
#if DRIVER_STORE_BITWISE_NEGATION_IN_TEMPORARY
uvec4 result = ~value;
return result;
#else
return ~value;
#endif
}
)";
return header;
}
+107 -1
View File
@@ -3270,8 +3270,29 @@ bool GSDeviceVK::CheckFeatures()
// through ROAA (black / missing textures) across GPU generations, so detect the SoC
// here and disable fbfetch below. Ported from sashkinbro/EmuCoreX. Detection reads the
// ro.soc.* props already folded into the profile hints (no new JNI needed).
//
// The driver context feeds the driver-bug database (ported from EmuCoreX/sashkinbro with his
// approval). Vulkan is the good case: VkPhysicalDeviceDriverProperties names the blob outright,
// which is what the r44p1 DEVICE_LOST and 8-Elite push-descriptor fixes both learned the hard
// way — gate on driverID, never on vendorID, or Turnip/PanVK inherit proprietary workarounds.
// ProcessDeviceExtensions() has already filled m_device_driver_properties by the time we get
// here (CreateDeviceAndSwapChain runs before CheckFeatures), so one resolve sees everything.
MobileDriverContext driver_context;
driver_context.api = MobileGpuApi::Vulkan;
driver_context.vendor_id = m_device_properties.vendorID;
driver_context.device_id = m_device_properties.deviceID;
driver_context.driver_version = m_device_properties.driverVersion;
driver_context.api_version = m_device_properties.apiVersion;
driver_context.max_draw_indirect_count = m_device_properties.limits.maxDrawIndirectCount;
if (m_optional_extensions.vk_khr_driver_properties)
{
driver_context.driver_id = static_cast<u32>(m_device_driver_properties.driverID);
driver_context.driver_name = m_device_driver_properties.driverName;
driver_context.driver_info = m_device_driver_properties.driverInfo;
}
const GpuProfileSelection mobile_profile = GpuProfileDetector::Resolve(
GSConfig.AndroidGpuProfileOverride, std::string_view(), m_device_properties.deviceName);
GSConfig.AndroidGpuProfileOverride, std::string_view(), m_device_properties.deviceName,
driver_context);
// ★ Vulkan resolved mobile_profile and pushed every OTHER piece of it into the device
// (MediaTek SoC, GPU identity, GS tuning) but never the runtime profile itself, so
// IsMaliGPUProfile()/IsAdrenoGPUProfile() answered from the default for the entire Vulkan
@@ -3287,6 +3308,20 @@ bool GSDeviceVK::CheckFeatures()
// This is what constrains texture/target caching on weaker Mali (e.g. G615). From EmuCoreX.
SetMobileGPUIdentity(mobile_profile.gpu);
SetMobileGSTuning(mobile_profile.gs_tuning);
SetMobileDriverProfile(mobile_profile.driver);
Console.WriteLn("VK: GPU profile override='%s' resolved='%s' driver='%s' version=%u.%u.%u "
"raw=%08x rules=%u bugs=%016llx workarounds=%016llx.",
GpuProfileDetector::OverrideToConfigString(mobile_profile.override_mode),
GpuProfileDetector::RuntimeProfileToString(mobile_profile.runtime_profile),
GpuProfileDetector::DriverToString(mobile_profile.driver.driver),
static_cast<unsigned>(mobile_profile.driver.version.major),
static_cast<unsigned>(mobile_profile.driver.version.minor),
static_cast<unsigned>(mobile_profile.driver.version.patch),
static_cast<unsigned>(mobile_profile.driver.version.raw),
static_cast<unsigned>(mobile_profile.driver.matched_rule_count),
static_cast<unsigned long long>(mobile_profile.driver.bugs),
static_cast<unsigned long long>(mobile_profile.driver.workarounds));
DevCon.WriteLn("VK: GPU profile hints: %s", mobile_profile.hints.c_str());
#endif
// framebuffer_fetch: the tiler-native ordered Cd read (ROAA / subpassLoad in tile
@@ -4874,6 +4909,77 @@ static void AddShaderHeader(std::stringstream& ss)
ss << "#extension GL_ARB_fragment_shader_interlock : require\n";
ss << "#extension GL_ARB_shader_image_load_store : require\n";
}
// Shader-compiler workarounds from the driver-bug database (ported from EmuCoreX/sashkinbro
// with his approval). Both default to 0, so the generated SPIR-V is unchanged on any driver
// the database has no rule for. Emitted after the #extension directives above because GLSL
// wants those before any real code, and the wrapper bodies below are real code.
AddMacro(ss, "DRIVER_SCALARIZE_VECTOR_BITWISE_AND",
dev->UsesMobileDriverWorkaround(DriverWorkaround::ScalarizeVectorBitwiseAnd) ? 1 : 0);
AddMacro(ss, "DRIVER_REWRITE_UNIFORM_INDEXING",
dev->UsesMobileDriverWorkaround(DriverWorkaround::RewriteUniformIndexing) ? 1 : 0);
ss << R"(
uvec2 gpu_bitwise_and(uvec2 a, uvec2 b)
{
#if DRIVER_SCALARIZE_VECTOR_BITWISE_AND
return uvec2(a.x & b.x, a.y & b.y);
#else
return a & b;
#endif
}
uvec3 gpu_bitwise_and(uvec3 a, uvec3 b)
{
#if DRIVER_SCALARIZE_VECTOR_BITWISE_AND
return uvec3(a.x & b.x, a.y & b.y, a.z & b.z);
#else
return a & b;
#endif
}
uvec4 gpu_bitwise_and(uvec4 a, uvec4 b)
{
#if DRIVER_SCALARIZE_VECTOR_BITWISE_AND
return uvec4(a.x & b.x, a.y & b.y, a.z & b.z, a.w & b.w);
#else
return a & b;
#endif
}
ivec3 gpu_bitwise_and(ivec3 a, ivec3 b)
{
#if DRIVER_SCALARIZE_VECTOR_BITWISE_AND
return ivec3(a.x & b.x, a.y & b.y, a.z & b.z);
#else
return a & b;
#endif
}
float gpu_matrix_element(mat4 value, int column, int row)
{
#if DRIVER_REWRITE_UNIFORM_INDEXING
vec4 selected_column;
if (column == 0)
selected_column = value[0];
else if (column == 1)
selected_column = value[1];
else if (column == 2)
selected_column = value[2];
else
selected_column = value[3];
if (row == 0)
return selected_column[0];
if (row == 1)
return selected_column[1];
if (row == 2)
return selected_column[2];
return selected_column[3];
#else
return value[column][row];
#endif
}
)";
}
static void AddShaderStageMacro(std::stringstream& ss, bool vs, bool gs, bool fs)
+5 -1
View File
@@ -3,4 +3,8 @@
/// 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 = 108; // Last changed in PR 14688
// 109: driver-workaround shader wrappers (gpu_bitwise_and / gpu_bitwise_not / gpu_boolean_not /
// gpu_matrix_element). Every TFX and convert shader's source text changed, so a cached blob from
// 108 no longer matches the source that produced it — leaving this alone hands users stale
// binaries and garbage rendering after the update.
static constexpr u32 SHADER_CACHE_VERSION = 109; // 108 was upstream PR 14688