From 4d8701a49d22ed608101eaee20569af9d165f2d1 Mon Sep 17 00:00:00 2001 From: jpolo1224 Date: Sun, 26 Jul 2026 14:53:39 -0400 Subject: [PATCH] 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. --- bin/resources/shaders/opengl/convert.glsl | 16 +- bin/resources/shaders/opengl/tfx_fs.glsl | 45 +- bin/resources/shaders/vulkan/convert.glsl | 16 +- bin/resources/shaders/vulkan/tfx.glsl | 20 +- pcsx2/CMakeLists.txt | 3 +- pcsx2/GS/Renderers/Common/GSDevice.h | 17 + .../Renderers/Common/GSGPUDriverProfile.cpp | 509 ++++++++++++++++++ pcsx2/GS/Renderers/Common/GSGPUProfile.cpp | 120 ++++- pcsx2/GS/Renderers/Common/GSGPUProfile.h | 166 ++++++ .../GS/Renderers/Common/GSGPUProfilePrivate.h | 2 + pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp | 109 +++- pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp | 108 +++- pcsx2/ShaderCacheVersion.h | 6 +- 13 files changed, 1069 insertions(+), 68 deletions(-) create mode 100644 pcsx2/GS/Renderers/Common/GSGPUDriverProfile.cpp diff --git a/bin/resources/shaders/opengl/convert.glsl b/bin/resources/shaders/opengl/convert.glsl index 05061dfb5f..816df0abb5 100644 --- a/bin/resources/shaders/opengl/convert.glsl +++ b/bin/resources/shaders/opengl/convert.glsl @@ -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 diff --git a/bin/resources/shaders/opengl/tfx_fs.glsl b/bin/resources/shaders/opengl/tfx_fs.glsl index ab2d685cca..0e2ac88c97 100644 --- a/bin/resources/shaders/opengl/tfx_fs.glsl +++ b/bin/resources/shaders/opengl/tfx_fs.glsl @@ -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 diff --git a/bin/resources/shaders/vulkan/convert.glsl b/bin/resources/shaders/vulkan/convert.glsl index 23f6b3c89b..3ff03cbf34 100644 --- a/bin/resources/shaders/vulkan/convert.glsl +++ b/bin/resources/shaders/vulkan/convert.glsl @@ -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. diff --git a/bin/resources/shaders/vulkan/tfx.glsl b/bin/resources/shaders/vulkan/tfx.glsl index 3a22580f78..1bb533ec41 100644 --- a/bin/resources/shaders/vulkan/tfx.glsl +++ b/bin/resources/shaders/vulkan/tfx.glsl @@ -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 } diff --git a/pcsx2/CMakeLists.txt b/pcsx2/CMakeLists.txt index d37b952fb5..50b5b883a7 100644 --- a/pcsx2/CMakeLists.txt +++ b/pcsx2/CMakeLists.txt @@ -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) diff --git a/pcsx2/GS/Renderers/Common/GSDevice.h b/pcsx2/GS/Renderers/Common/GSDevice.h index 77a8ea2d73..c7de37af2d 100644 --- a/pcsx2/GS/Renderers/Common/GSDevice.h +++ b/pcsx2/GS/Renderers/Common/GSDevice.h @@ -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; } diff --git a/pcsx2/GS/Renderers/Common/GSGPUDriverProfile.cpp b/pcsx2/GS/Renderers/Common/GSGPUDriverProfile.cpp new file mode 100644 index 0000000000..e64d5f6f8d --- /dev/null +++ b/pcsx2/GS/Renderers/Common/GSGPUDriverProfile.cpp @@ -0,0 +1,509 @@ +// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +// Mobile GPU driver-bug database, ported from EmuCoreX (sashkinbro) with his approval. +// Adapted only where our enums differ; the rule table itself is his work. + +#include "GS/Renderers/Common/GSGPUProfilePrivate.h" + +#include +#include +#include + +namespace GpuProfileDetail +{ +namespace +{ +// VkDriverId values. Duplicating the numeric ABI values keeps this portable file independent from +// Vulkan headers and lets the same resolver serve OpenGL unit tests. +constexpr u32 DRIVER_ID_IMAGINATION_PROPRIETARY = 7; +constexpr u32 DRIVER_ID_QUALCOMM_PROPRIETARY = 8; +constexpr u32 DRIVER_ID_ARM_PROPRIETARY = 9; +constexpr u32 DRIVER_ID_MESA_TURNIP = 18; +constexpr u32 DRIVER_ID_MESA_PANVK = 20; +constexpr u32 DRIVER_ID_IMAGINATION_OPEN_SOURCE_MESA = 25; + +constexpr u64 Bug(DriverBug bug) +{ + return u64{1} << static_cast(bug); +} + +constexpr u64 Workaround(DriverWorkaround workaround) +{ + return u64{1} << static_cast(workaround); +} + +struct VersionBound +{ + u16 major = 0; + u16 minor = 0; + u16 patch = 0; + u32 build = 0; +}; + +struct DriverRule +{ + const char* id; + MobileGpuApi api = MobileGpuApi::Unknown; + RuntimeGpuProfile vendor = RuntimeGpuProfile::Unknown; + MobileGpuDriver driver = MobileGpuDriver::Unknown; + MobileGpuArchitecture architecture = MobileGpuArchitecture::Unknown; + u16 model_min = 0; + u16 model_max = 0; + u32 exact_raw_version = 0; + VersionBound min_version; + VersionBound max_version_exclusive; + u32 min_android_sdk = 0; + u32 max_android_sdk = 0; + bool match_unknown_version = false; + u64 bugs = 0; + u64 workarounds = 0; +}; + +constexpr int CompareVersion(const MobileDriverVersion& lhs, VersionBound rhs) +{ + if (lhs.major != rhs.major) + return (lhs.major < rhs.major) ? -1 : 1; + if (lhs.minor != rhs.minor) + return (lhs.minor < rhs.minor) ? -1 : 1; + if (lhs.patch != rhs.patch) + return (lhs.patch < rhs.patch) ? -1 : 1; + if (lhs.build != rhs.build) + return (lhs.build < rhs.build) ? -1 : 1; + return 0; +} + +constexpr bool HasVersionBound(VersionBound bound) +{ + return bound.major != 0 || bound.minor != 0 || bound.patch != 0 || bound.build != 0; +} + +static bool ParseUnsigned(std::string_view text, size_t start, u16* value, size_t* end) +{ + if (start >= text.size() || !std::isdigit(static_cast(text[start]))) + return false; + + u32 parsed = 0; + size_t pos = start; + while (pos < text.size() && std::isdigit(static_cast(text[pos]))) + { + parsed = parsed * 10 + static_cast(text[pos++] - '0'); + if (parsed > std::numeric_limits::max()) + return false; + } + + *value = static_cast(parsed); + *end = pos; + return true; +} + +static bool ParseUnsigned32(std::string_view text, size_t start, u32* value, size_t* end) +{ + if (start >= text.size() || !std::isdigit(static_cast(text[start]))) + return false; + + u64 parsed = 0; + size_t pos = start; + while (pos < text.size() && std::isdigit(static_cast(text[pos]))) + { + parsed = parsed * 10 + static_cast(text[pos++] - '0'); + if (parsed > std::numeric_limits::max()) + return false; + } + + *value = static_cast(parsed); + *end = pos; + return true; +} + +static MobileDriverVersion ParseOpenGLDriverVersion(std::string_view version_string, + RuntimeGpuProfile vendor) +{ + MobileDriverVersion version; + const std::string lowered = ToLowerASCII(version_string); + + // ARM strings commonly contain "r54p1"; this is the only portion that has stable ordering. + if (vendor == RuntimeGpuProfile::Mali) + { + for (size_t r = lowered.find('r'); r != std::string::npos; r = lowered.find('r', r + 1)) + { + size_t end = r + 1; + u16 release = 0; + if (!ParseUnsigned(lowered, r + 1, &release, &end)) + continue; + + u16 patch = 0; + if (end < lowered.size() && lowered[end] == 'p') + { + size_t patch_end = end + 1; + ParseUnsigned(lowered, end + 1, &patch, &patch_end); + } + + version.major = release; + version.minor = patch; + version.known = true; + return version; + } + } + + // Imagination strings use the form "OpenGL ES 3.2 build 1.9@4850625". The branch and + // change ID, rather than the leading GLES version, are the ordered driver identity. + if (vendor == RuntimeGpuProfile::PowerVR) + { + const size_t marker = lowered.find("build "); + if (marker == std::string::npos) + return version; + + size_t end = marker + 6; + u16 major = 0; + if (!ParseUnsigned(lowered, end, &major, &end) || end >= lowered.size() || lowered[end] != '.') + return version; + + u16 minor = 0; + if (!ParseUnsigned(lowered, end + 1, &minor, &end)) + return version; + + u32 build = 0; + if (end < lowered.size() && lowered[end] == '@') + { + size_t build_end = end + 1; + if (!ParseUnsigned32(lowered, end + 1, &build, &build_end)) + return version; + } + + version.major = major; + version.minor = minor; + version.build = build; + version.known = true; + return version; + } + + // Qualcomm strings commonly contain "V@0502". + size_t start = 0; + if (vendor == RuntimeGpuProfile::Adreno) + { + const size_t marker = lowered.find("v@"); + if (marker != std::string::npos) + start = marker + 2; + } + + for (size_t pos = start; pos < lowered.size(); pos++) + { + u16 major = 0; + size_t end = pos; + if (!ParseUnsigned(lowered, pos, &major, &end)) + continue; + + u16 minor = 0; + u16 patch = 0; + if (end < lowered.size() && lowered[end] == '.') + { + size_t minor_end = end + 1; + ParseUnsigned(lowered, end + 1, &minor, &minor_end); + if (minor_end < lowered.size() && lowered[minor_end] == '.') + { + size_t patch_end = minor_end + 1; + ParseUnsigned(lowered, minor_end + 1, &patch, &patch_end); + } + } + + version.major = major; + version.minor = minor; + version.patch = patch; + version.known = true; + return version; + } + + return version; +} + +static MobileDriverVersion ParseVulkanDriverVersion(const MobileDriverContext& context, + MobileGpuDriver driver) +{ + MobileDriverVersion version; + version.raw = context.driver_version; + if (context.driver_version == 0) + return version; + + const u32 raw = context.driver_version; + version.major = static_cast(raw >> 22); + version.minor = static_cast((raw >> 12) & 0x3ff); + version.patch = static_cast(raw & 0xfff); + + if (driver == MobileGpuDriver::QualcommProprietary && (raw & 0x80000000u) == 0) + { + // Older Qualcomm releases used an undocumented, non-orderable encoding. + version.known = false; + version.major = 0; + version.minor = 0; + version.patch = 0; + return version; + } + + if (driver == MobileGpuDriver::ArmProprietary && + (version.patch != 0 || version.major > 100)) + { + // Old Mali Vulkan releases placed a source hash in driverVersion. + version.known = false; + version.legacy_hash = true; + version.major = 0; + version.minor = 0; + version.patch = 0; + return version; + } + + version.known = true; + return version; +} + +static MobileGpuDriver DetectDriver(const GpuProfileSelection& selection, + const MobileDriverContext& context, std::string_view lowered_hints) +{ + switch (context.driver_id) + { + case DRIVER_ID_ARM_PROPRIETARY: return MobileGpuDriver::ArmProprietary; + case DRIVER_ID_MESA_PANVK: return MobileGpuDriver::MesaPanVK; + case DRIVER_ID_QUALCOMM_PROPRIETARY: return MobileGpuDriver::QualcommProprietary; + case DRIVER_ID_MESA_TURNIP: return MobileGpuDriver::MesaTurnip; + case DRIVER_ID_IMAGINATION_PROPRIETARY: return MobileGpuDriver::ImaginationProprietary; + case DRIVER_ID_IMAGINATION_OPEN_SOURCE_MESA: return MobileGpuDriver::MesaPowerVR; + default: break; + } + + const std::string driver_hints = ToLowerASCII( + std::string(context.driver_name) + " " + std::string(context.driver_info) + " " + + std::string(lowered_hints)); + if (ContainsAny(driver_hints, {"turnip", "freedreno"})) + return MobileGpuDriver::MesaTurnip; + if (ContainsAny(driver_hints, {"panvk", "panfrost"})) + return MobileGpuDriver::MesaPanVK; + if (ContainsAny(driver_hints, {"pvr mesa", "powervr mesa"})) + return MobileGpuDriver::MesaPowerVR; + if (ContainsAny(driver_hints, {"angle"})) + return MobileGpuDriver::Angle; + + // With no explicit driver ID, Vulkan and native GLES vendor IDs identify the proprietary stack. + switch (selection.runtime_profile) + { + case RuntimeGpuProfile::Mali: + return MobileGpuDriver::ArmProprietary; + case RuntimeGpuProfile::Adreno: + return MobileGpuDriver::QualcommProprietary; + case RuntimeGpuProfile::PowerVR: + return MobileGpuDriver::ImaginationProprietary; + default: + return MobileGpuDriver::Unknown; + } +} + +static bool RuleMatches(const DriverRule& rule, const GpuProfileSelection& selection, + const MobileDriverContext& context, const MobileDriverProfile& profile) +{ + if (rule.api != MobileGpuApi::Unknown && rule.api != profile.api) + return false; + if (rule.vendor != RuntimeGpuProfile::Unknown && rule.vendor != selection.runtime_profile) + return false; + if (rule.driver != MobileGpuDriver::Unknown && rule.driver != profile.driver) + return false; + if (rule.architecture != MobileGpuArchitecture::Unknown && + rule.architecture != selection.gpu.architecture) + { + return false; + } + if ((rule.model_min != 0 || rule.model_max != 0) && + (selection.gpu.model_number < rule.model_min || selection.gpu.model_number > rule.model_max)) + { + return false; + } + if (rule.exact_raw_version != 0 && profile.version.raw != rule.exact_raw_version) + return false; + if (rule.min_android_sdk != 0 && context.android_sdk < rule.min_android_sdk) + return false; + if (rule.max_android_sdk != 0 && context.android_sdk > rule.max_android_sdk) + return false; + + const bool has_version_range = + HasVersionBound(rule.min_version) || HasVersionBound(rule.max_version_exclusive); + if (has_version_range && !profile.version.known) + return rule.match_unknown_version; + if (HasVersionBound(rule.min_version) && CompareVersion(profile.version, rule.min_version) < 0) + return false; + if (HasVersionBound(rule.max_version_exclusive) && + CompareVersion(profile.version, rule.max_version_exclusive) >= 0) + { + return false; + } + return true; +} + +// Sources and exact upstream revisions are mirrored in docs/gpu-driver-database.json. A known +// driver bug is not automatically an active workaround: expensive renderer fallbacks stay disabled +// until their PCSX2 integration has a bounded, tested condition. +static constexpr std::array s_driver_rules = {{ + {"gl-arm-buffer-stream", MobileGpuApi::OpenGL, RuntimeGpuProfile::Mali, + MobileGpuDriver::ArmProprietary, MobileGpuArchitecture::Unknown, 0, 0, 0, {}, {}, 0, 0, false, + Bug(DriverBug::BrokenBufferStreaming) | Bug(DriverBug::BrokenUnsynchronizedMapping) | + Bug(DriverBug::BrokenVectorBitwiseAnd) | Bug(DriverBug::BrokenVSync), + Workaround(DriverWorkaround::ScalarizeVectorBitwiseAnd)}, + {"gl-arm-g57-fifo", MobileGpuApi::OpenGL, RuntimeGpuProfile::Mali, + MobileGpuDriver::ArmProprietary, MobileGpuArchitecture::Unknown, 57, 57, 0, {}, {}, 0, 0, false, + Bug(DriverBug::BrokenVSync), Workaround(DriverWorkaround::ForceFifoPresent)}, + {"gl-qualcomm-compiler", MobileGpuApi::OpenGL, RuntimeGpuProfile::Adreno, + MobileGpuDriver::QualcommProprietary, MobileGpuArchitecture::Unknown, 0, 0, 0, {}, {}, 0, 0, false, + Bug(DriverBug::BrokenBufferStreaming) | Bug(DriverBug::BrokenNegatedBoolean) | + Bug(DriverBug::BrokenPrimitiveRestart), + Workaround(DriverWorkaround::RewriteBooleanNegation)}, + {"gl-powervr-driver", MobileGpuApi::OpenGL, RuntimeGpuProfile::PowerVR, + MobileGpuDriver::ImaginationProprietary, MobileGpuArchitecture::Unknown, 0, 0, 0, {}, {}, 0, 0, false, + Bug(DriverBug::BrokenBufferStreaming), 0}, + {"gl-powervr-bitwise-before-1-8-4693462", MobileGpuApi::OpenGL, RuntimeGpuProfile::PowerVR, + MobileGpuDriver::ImaginationProprietary, MobileGpuArchitecture::Unknown, 0, 0, 0, + {}, {1, 8, 0, 4693462}, 0, 0, true, + Bug(DriverBug::BrokenBitwiseOpNegation), + Workaround(DriverWorkaround::StoreBitwiseNegationInTemporary)}, + // Upstream keys this on a PowerVRSeries5 architecture value we do not carry — our enum has a + // single PowerVR entry. The 500-599 model range is what actually selects SGX 5xx here, so the + // rule stays exactly as narrow without splitting the architecture enum. + {"gl-powervr-sgx-tall-mipmap", MobileGpuApi::OpenGL, RuntimeGpuProfile::PowerVR, + MobileGpuDriver::ImaginationProprietary, MobileGpuArchitecture::PowerVR, 500, 599, 0, + {}, {}, 0, 0, false, Bug(DriverBug::BrokenGenerateMipmapTallTexture), + Workaround(DriverWorkaround::GenerateMipmapManuallyForTallTextures)}, + {"gl-android-shader-serialization", MobileGpuApi::OpenGL, RuntimeGpuProfile::Unknown, + MobileGpuDriver::Unknown, MobileGpuArchitecture::Unknown, 0, 0, 0, {}, {}, 1, 0, false, + Bug(DriverBug::BrokenMultithreadedShaderCompilation), 0}, + {"vk-android-shader-serialization", MobileGpuApi::Vulkan, RuntimeGpuProfile::Unknown, + MobileGpuDriver::Unknown, MobileGpuArchitecture::Unknown, 0, 0, 0, {}, {}, 1, 0, false, + Bug(DriverBug::BrokenMultithreadedShaderCompilation), 0}, + {"vk-arm-proprietary", MobileGpuApi::Vulkan, RuntimeGpuProfile::Mali, + MobileGpuDriver::ArmProprietary, MobileGpuArchitecture::Unknown, 0, 0, 0, {}, {}, 0, 0, false, + Bug(DriverBug::BrokenPrimitiveRestart) | Bug(DriverBug::BrokenPushDescriptors) | + Bug(DriverBug::BrokenAttachmentFeedbackLoopLayout) | + Bug(DriverBug::SlowCachedReadbackMemory) | Bug(DriverBug::BrokenVectorBitwiseAnd), + Workaround(DriverWorkaround::UseDescriptorSets) | + Workaround(DriverWorkaround::DisableAttachmentFeedbackLoopLayout) | + Workaround(DriverWorkaround::PreferCoherentReadback) | + Workaround(DriverWorkaround::ScalarizeVectorBitwiseAnd)}, + {"vk-arm-empty-renderpass", MobileGpuApi::Vulkan, RuntimeGpuProfile::Mali, + MobileGpuDriver::ArmProprietary, MobileGpuArchitecture::Unknown, 0, 0, 0xaa9c4b29u, {}, {}, 0, 0, false, + Bug(DriverBug::BrokenEmptyRenderPass), 0}, + {"vk-arm-constant-load-r32-r39", MobileGpuApi::Vulkan, RuntimeGpuProfile::Mali, + MobileGpuDriver::ArmProprietary, MobileGpuArchitecture::Unknown, 0, 0, 0, {32, 0, 0}, {40, 0, 0}, + 0, 0, false, Bug(DriverBug::BrokenConstantLoad), 0}, + {"vk-arm-midgard-uniform-indexing", MobileGpuApi::Vulkan, RuntimeGpuProfile::Mali, + MobileGpuDriver::ArmProprietary, MobileGpuArchitecture::MaliMidgard, 830, 880, 0, {}, {}, 0, 0, false, + Bug(DriverBug::BrokenUniformIndexing), Workaround(DriverWorkaround::RewriteUniformIndexing)}, + {"vk-arm-imageless-r38", MobileGpuApi::Vulkan, RuntimeGpuProfile::Mali, + MobileGpuDriver::ArmProprietary, MobileGpuArchitecture::Unknown, 0, 0, 0, {38, 0, 0}, {38, 2, 0}, + 0, 0, false, Bug(DriverBug::BrokenImagelessFramebuffer), 0}, + {"vk-arm-extended-dynamic-before-r44p1", MobileGpuApi::Vulkan, RuntimeGpuProfile::Mali, + MobileGpuDriver::ArmProprietary, MobileGpuArchitecture::Unknown, 0, 0, 0, {}, {44, 1, 0}, + 0, 0, true, Bug(DriverBug::BrokenExtendedDynamicState), 0}, + {"vk-arm-dynamic-rendering-before-r52", MobileGpuApi::Vulkan, RuntimeGpuProfile::Mali, + MobileGpuDriver::ArmProprietary, MobileGpuArchitecture::Unknown, 0, 0, 0, {}, {52, 0, 0}, + 0, 0, true, Bug(DriverBug::BrokenDynamicRendering), 0}, + {"vk-qualcomm-proprietary", MobileGpuApi::Vulkan, RuntimeGpuProfile::Adreno, + MobileGpuDriver::QualcommProprietary, MobileGpuArchitecture::Unknown, 0, 0, 0, {}, {}, 0, 0, false, + Bug(DriverBug::BrokenPrimitiveRestart) | Bug(DriverBug::BrokenProvokingVertex) | + Bug(DriverBug::BrokenSubpassFeedback) | + Bug(DriverBug::BrokenReversedDepthRange) | Bug(DriverBug::SlowCachedReadbackMemory) | + Bug(DriverBug::SlowOptimalImageToBufferCopy), + Workaround(DriverWorkaround::DisableProvokingVertex) | + Workaround(DriverWorkaround::PreferCoherentReadback)}, + {"vk-qualcomm-pre-adreno8-readback", MobileGpuApi::Vulkan, RuntimeGpuProfile::Adreno, + MobileGpuDriver::QualcommProprietary, MobileGpuArchitecture::Unknown, 200, 799, 0, {}, {}, 0, 0, false, + Bug(DriverBug::SlowOptimalImageToBufferCopy), + Workaround(DriverWorkaround::UseStagingImageForReadback)}, + {"vk-adreno5xx-depth-stencil", MobileGpuApi::Vulkan, RuntimeGpuProfile::Adreno, + MobileGpuDriver::QualcommProprietary, MobileGpuArchitecture::Adreno5xx, 500, 599, 0, {}, {}, 0, 0, false, + Bug(DriverBug::BrokenDepthStencilDiscard) | Bug(DriverBug::BrokenColorWriteMaskWithDepthTest), + Workaround(DriverWorkaround::EmulateColorWriteMask)}, + {"vk-qualcomm-dynamic-rendering-before-512-801", MobileGpuApi::Vulkan, RuntimeGpuProfile::Adreno, + MobileGpuDriver::QualcommProprietary, MobileGpuArchitecture::Unknown, 0, 0, 0, {}, {512, 801, 0}, + 0, 0, true, Bug(DriverBug::BrokenDynamicRendering), 0}, + {"vk-qualcomm-imageless-before-512-806", MobileGpuApi::Vulkan, RuntimeGpuProfile::Adreno, + MobileGpuDriver::QualcommProprietary, MobileGpuArchitecture::Unknown, 0, 0, 0, {}, {512, 806, 0}, + 0, 0, true, Bug(DriverBug::BrokenImagelessFramebuffer), 0}, + {"vk-powervr-proprietary", MobileGpuApi::Vulkan, RuntimeGpuProfile::PowerVR, + MobileGpuDriver::ImaginationProprietary, MobileGpuArchitecture::Unknown, 0, 0, 0, {}, {}, 0, 0, false, + Bug(DriverBug::BrokenPushDescriptors) | Bug(DriverBug::BrokenAttachmentFeedbackLoopLayout) | + Bug(DriverBug::Broken16BitTextureFormats) | + Bug(DriverBug::BrokenDynamicRendering) | Bug(DriverBug::BrokenImagelessFramebuffer) | + Bug(DriverBug::BrokenPrimitiveTopologyDynamicState) | + Bug(DriverBug::BrokenGraphicsPipelineLibrary), + Workaround(DriverWorkaround::UseDescriptorSets) | + Workaround(DriverWorkaround::DisableAttachmentFeedbackLoopLayout)}, + {"vk-powervr-clear-loadop-1-7-to-1-10", MobileGpuApi::Vulkan, RuntimeGpuProfile::PowerVR, + MobileGpuDriver::ImaginationProprietary, MobileGpuArchitecture::Unknown, 0, 0, 0, + {1, 7, 0}, {1, 10, 0}, 0, 0, false, Bug(DriverBug::BrokenClearLoadOpRenderPass), + Workaround(DriverWorkaround::AvoidClearLoadOpRenderPass)}, + {"vk-powervr-old-swapchain-width", MobileGpuApi::Vulkan, RuntimeGpuProfile::PowerVR, + MobileGpuDriver::ImaginationProprietary, MobileGpuArchitecture::Unknown, 0, 0, 0, {}, {}, 0, 0, false, + 0, Workaround(DriverWorkaround::AlignSwapchainWidthTo32)}, + {"vk-powervr-primitive-topology", MobileGpuApi::Vulkan, RuntimeGpuProfile::PowerVR, + MobileGpuDriver::ImaginationProprietary, MobileGpuArchitecture::Unknown, 0, 0, 0, {}, {}, 0, 0, false, + Bug(DriverBug::BrokenPrimitiveTopologyDynamicState), 0}, + {"vk-arm-jm-r46-r50-extended-dynamic", MobileGpuApi::Vulkan, RuntimeGpuProfile::Mali, + MobileGpuDriver::ArmProprietary, MobileGpuArchitecture::Unknown, 0, 0, 0, {46, 0, 0}, {51, 0, 0}, + 0, 0, false, Bug(DriverBug::BrokenExtendedDynamicState), 0}, +}}; +} // namespace + +MobileDriverProfile ResolveDriverProfile(const GpuProfileSelection& selection, + const MobileDriverContext& context, std::string_view lowered_hints) +{ + MobileDriverProfile profile; + profile.api = context.api; + profile.driver = DetectDriver(selection, context, lowered_hints); + profile.driver_name = context.driver_name.empty() ? std::string() : std::string(context.driver_name); + profile.version = (context.api == MobileGpuApi::Vulkan) ? + ParseVulkanDriverVersion(context, profile.driver) : + ParseOpenGLDriverVersion(context.api_version_string, selection.runtime_profile); + profile.version.raw = context.driver_version; + + if (selection.runtime_profile != RuntimeGpuProfile::Unknown) + profile.confidence = selection.gpu.recognized ? + DriverProfileConfidence::Model : DriverProfileConfidence::Vendor; + if (profile.driver != MobileGpuDriver::Unknown) + profile.confidence = DriverProfileConfidence::Driver; + if (profile.version.known) + profile.confidence = DriverProfileConfidence::DriverVersion; + + for (const DriverRule& rule : s_driver_rules) + { + if (std::string_view(rule.id) == "vk-powervr-old-swapchain-width" && + (profile.version.raw == 0 || profile.version.raw >= 0x00582558u)) + { + continue; + } + if (std::string_view(rule.id) == "vk-qualcomm-pre-adreno8-readback" && + (selection.gpu.architecture == MobileGpuArchitecture::Adreno8xx || + selection.gpu.architecture == MobileGpuArchitecture::AdrenoX || + selection.gpu.architecture == MobileGpuArchitecture::Unknown)) + { + continue; + } + if (std::string_view(rule.id) == "vk-arm-midgard-uniform-indexing" && + (!profile.version.legacy_hash || + (selection.gpu.model_number != 830 && selection.gpu.model_number != 860 && + selection.gpu.model_number != 880))) + { + continue; + } + if (std::string_view(rule.id) == "vk-arm-jm-r46-r50-extended-dynamic" && + context.max_draw_indirect_count > 1) + { + continue; + } + if (!RuleMatches(rule, selection, context, profile)) + continue; + + profile.bugs |= rule.bugs; + profile.workarounds |= rule.workarounds; + profile.matched_rule_count++; + } + + profile.conservative_fallback = + (selection.runtime_profile == RuntimeGpuProfile::Unknown || profile.driver == MobileGpuDriver::Unknown); + return profile; +} +} // namespace GpuProfileDetail diff --git a/pcsx2/GS/Renderers/Common/GSGPUProfile.cpp b/pcsx2/GS/Renderers/Common/GSGPUProfile.cpp index ed833c23f5..8acfcdf16e 100644 --- a/pcsx2/GS/Renderers/Common/GSGPUProfile.cpp +++ b/pcsx2/GS/Renderers/Common/GSGPUProfile.cpp @@ -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(); } diff --git a/pcsx2/GS/Renderers/Common/GSGPUProfile.h b/pcsx2/GS/Renderers/Common/GSGPUProfile.h index ba5e09f074..e850af6b94 100644 --- a/pcsx2/GS/Renderers/Common/GSGPUProfile.h +++ b/pcsx2/GS/Renderers/Common/GSGPUProfile.h @@ -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(bug))) != 0; + } + + constexpr bool UsesWorkaround(DriverWorkaround workaround) const + { + return (workarounds & (u64{1} << static_cast(workaround))) != 0; + } +}; + +// Both sets are u64 bitfields, so neither enum may exceed 64 entries without widening them. +static_assert(static_cast(DriverBug::Count) <= 64); +static_assert(static_cast(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); }; diff --git a/pcsx2/GS/Renderers/Common/GSGPUProfilePrivate.h b/pcsx2/GS/Renderers/Common/GSGPUProfilePrivate.h index 2254d60004..c91baecc18 100644 --- a/pcsx2/GS/Renderers/Common/GSGPUProfilePrivate.h +++ b/pcsx2/GS/Renderers/Common/GSGPUProfilePrivate.h @@ -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 diff --git a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp index cd6f948141..0ca37c9213 100644 --- a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp +++ b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp @@ -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(profile_selection.driver.version.major), + static_cast(profile_selection.driver.version.minor), + static_cast(profile_selection.driver.version.patch), + static_cast(profile_selection.driver.version.build), + static_cast(profile_selection.driver.matched_rule_count), + static_cast(profile_selection.driver.bugs), + static_cast(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(glGetString(GL_VENDOR)))); - Console.WriteLn(fmt::format("GL_VERSION: {}", reinterpret_cast(glGetString(GL_VERSION)))); + Console.WriteLn(fmt::format("GL_VERSION: {}", gl_version_str)); Console.WriteLn(fmt::format("GL_RENDERER: {}", reinterpret_cast(glGetString(GL_RENDERER)))); Console.WriteLn(fmt::format( "GL_SHADING_LANGUAGE_VERSION: {}", reinterpret_cast(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; } diff --git a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp index 7b2f2938bc..b45706ef6f 100644 --- a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp +++ b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp @@ -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(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(mobile_profile.driver.version.major), + static_cast(mobile_profile.driver.version.minor), + static_cast(mobile_profile.driver.version.patch), + static_cast(mobile_profile.driver.version.raw), + static_cast(mobile_profile.driver.matched_rule_count), + static_cast(mobile_profile.driver.bugs), + static_cast(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) diff --git a/pcsx2/ShaderCacheVersion.h b/pcsx2/ShaderCacheVersion.h index 64e4a03ee8..142d6b2033 100644 --- a/pcsx2/ShaderCacheVersion.h +++ b/pcsx2/ShaderCacheVersion.h @@ -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