From 10a5d2f810bb06761ad185b718d145693d3438e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 30 Jul 2026 09:55:39 +0200 Subject: [PATCH] OpenGL: Improve checks for fragment shader float precision. Fixes #22001 (although there may be other artifacts). --- Common/GPU/D3D11/thin3d_d3d11.cpp | 1 + Common/GPU/OpenGL/GLFeatures.cpp | 2 +- Common/GPU/OpenGL/GLFeatures.h | 9 +++++++++ Common/GPU/OpenGL/thin3d_gl.cpp | 13 +++++++++---- Common/GPU/Vulkan/thin3d_vulkan.cpp | 1 + Common/GPU/thin3d.h | 1 + GPU/Common/ShaderId.cpp | 28 ++++++++++++++++++---------- GPU/GPUCommonHW.cpp | 4 ++++ GPU/GPUState.h | 2 +- UI/SystemInfoScreen.cpp | 5 ++++- 10 files changed, 49 insertions(+), 17 deletions(-) diff --git a/Common/GPU/D3D11/thin3d_d3d11.cpp b/Common/GPU/D3D11/thin3d_d3d11.cpp index 88de7fa0e5..836d0ab8ad 100644 --- a/Common/GPU/D3D11/thin3d_d3d11.cpp +++ b/Common/GPU/D3D11/thin3d_d3d11.cpp @@ -297,6 +297,7 @@ D3D11DrawContext::D3D11DrawContext(ComPtr device, ComPtr= D3D_FEATURE_LEVEL_9_3); caps_.coordConvention = CoordConvention::Direct3D11; + caps_.fragmentShaderFullPrecisionFloat = true; switch (featureLevel_) { case D3D_FEATURE_LEVEL_11_1: diff --git a/Common/GPU/OpenGL/GLFeatures.cpp b/Common/GPU/OpenGL/GLFeatures.cpp index 0c8ce93759..18aa1bd714 100644 --- a/Common/GPU/OpenGL/GLFeatures.cpp +++ b/Common/GPU/OpenGL/GLFeatures.cpp @@ -494,7 +494,7 @@ bool CheckGLExtensions() { GL_LOW_FLOAT, GL_MEDIUM_FLOAT, GL_HIGH_FLOAT, GL_LOW_INT, GL_MEDIUM_INT, GL_HIGH_INT }; - GLint shaderTypes[2] = { + const GLint shaderTypes[2] = { GL_VERTEX_SHADER, GL_FRAGMENT_SHADER }; for (int st = 0; st < 2; st++) { diff --git a/Common/GPU/OpenGL/GLFeatures.h b/Common/GPU/OpenGL/GLFeatures.h index 66170f1677..26172e2b12 100644 --- a/Common/GPU/OpenGL/GLFeatures.h +++ b/Common/GPU/OpenGL/GLFeatures.h @@ -138,6 +138,15 @@ struct GLExtensions { // greater-or-equal than bool VersionGEThan(int major, int minor, int sub = 0); int GLSLVersion(); + + bool FullPrecisionIntInFragment() const { + return range[1][5][0] >= 30 && range[1][5][1] >= 30; + } + + // If 23, normal floats are supported. + int HighpFragmentFloatMantissaBits() const { + return precision[1][2]; + } }; extern GLExtensions gl_extensions; diff --git a/Common/GPU/OpenGL/thin3d_gl.cpp b/Common/GPU/OpenGL/thin3d_gl.cpp index 6defe05ead..1947cf6cf2 100644 --- a/Common/GPU/OpenGL/thin3d_gl.cpp +++ b/Common/GPU/OpenGL/thin3d_gl.cpp @@ -561,11 +561,11 @@ OpenGLContext::OpenGLContext(bool canChangeSwapInterval) : renderManager_(frameT } else { caps_.preferredDepthBufferFormat = DataFormat::D16; } + if (gl_extensions.FullPrecisionIntInFragment()) { + caps_.fragmentShaderInt32Supported = true; + } if (gl_extensions.GLES3) { - // Mali reports 30 but works fine... - if (gl_extensions.range[1][5][1] >= 30) { - caps_.fragmentShaderInt32Supported = true; - } + // 30 is an ok value for the max since it's 2^31-1, which is the max value for a signed int. NVIDIA also reports this. caps_.samplerLodControl = true; } caps_.texture3DSupported = gl_extensions.GLES3 || gl_extensions.OES_texture_3D; @@ -574,12 +574,17 @@ OpenGLContext::OpenGLContext(bool canChangeSwapInterval) : renderManager_(frameT if (gl_extensions.VersionGEThan(3, 3, 0)) { caps_.fragmentShaderInt32Supported = true; } + caps_.fragmentShaderFullPrecisionFloat = true; caps_.preferredDepthBufferFormat = DataFormat::D24_S8; caps_.texture3DSupported = true; caps_.textureDepthSupported = true; caps_.samplerLodControl = true; } + if (gl_extensions.HighpFragmentFloatMantissaBits() >= 23 && !(gl_extensions.bugs & BUG_PVR_SHADER_PRECISION_TERRIBLE)) { + caps_.fragmentShaderFullPrecisionFloat = true; + } + caps_.maxTextureSize = gl_extensions.maxTextureSize; caps_.coordConvention = CoordConvention::OpenGL; caps_.setMaxFrameLatencySupported = true; diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index 5e0dfceaa5..fbca02d4e0 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -918,6 +918,7 @@ VKContext::VKContext(VulkanContext *vulkan, bool useRenderThread) // Make sure that the surface has been initialized. _dbg_assert_(vulkan->GetAvailablePresentModes().size() > 0); + caps_.fragmentShaderFullPrecisionFloat = true; caps_.coordConvention = CoordConvention::Vulkan; caps_.setMaxFrameLatencySupported = true; caps_.anisoSupported = vulkan->GetDeviceFeatures().enabled.standard.samplerAnisotropy != 0; diff --git a/Common/GPU/thin3d.h b/Common/GPU/thin3d.h index 4a776bab56..a31c8e102e 100644 --- a/Common/GPU/thin3d.h +++ b/Common/GPU/thin3d.h @@ -612,6 +612,7 @@ struct DeviceCaps { bool framebufferStencilBlitSupported; bool framebufferFetchSupported; bool texture3DSupported; + bool fragmentShaderFullPrecisionFloat; // Without this, our clip plane workaround doesn't work bool fragmentShaderInt32Supported; bool fragmentShaderDepthWriteSupported; bool fragmentShaderStencilWriteSupported; diff --git a/GPU/Common/ShaderId.cpp b/GPU/Common/ShaderId.cpp index 4f2dae4230..72d9932131 100644 --- a/GPU/Common/ShaderId.cpp +++ b/GPU/Common/ShaderId.cpp @@ -138,11 +138,15 @@ void ComputeVertexShaderID(VShaderID *id_out, u32 vertType, bool useHWTransform, id.SetBit(VS_BIT_NORM_REVERSE, gstate.areNormalsReversed()); } - if (clipInfoFlags & ClipInfoFlags::DepthClampFragment) { - id.SetBit(VS_BIT_FS_DEPTH_CLAMP); - } - if (clipInfoFlags & ClipInfoFlags::MinMaxZDiscard) { - id.SetBit(VS_BIT_FS_MINMAX_DISCARD); + if (gstate_c.Use(GPU_USE_FULL_PRECISION_IN_FRAGMENT)) { + if (clipInfoFlags & ClipInfoFlags::DepthClampFragment) { + id.SetBit(VS_BIT_FS_DEPTH_CLAMP); + } + if (clipInfoFlags & ClipInfoFlags::MinMaxZDiscard) { + id.SetBit(VS_BIT_FS_MINMAX_DISCARD); + } + } else { + // TODO: We're gonna need to soft-clip... Ugh. } id.SetBit(VS_BIT_FLATSHADE, doFlatShading); @@ -286,11 +290,15 @@ void ComputeFragmentShaderID(FShaderID *id_out, const ComputedPipelineState &pip // NOTE: This check MUST be identical to the one in ComputeVertexShaderID, otherwise we might get mismatches between VS and FS and end up with no shader at all. if (!isModeThrough) { - if (clipInfoFlags & ClipInfoFlags::DepthClampFragment) { - id.SetBit(FS_BIT_DEPTH_CLAMP); - } - if (clipInfoFlags & ClipInfoFlags::MinMaxZDiscard) { - id.SetBit(FS_BIT_MINMAX_DISCARD); + if (gstate_c.Use(GPU_USE_FULL_PRECISION_IN_FRAGMENT)) { + if (clipInfoFlags & ClipInfoFlags::DepthClampFragment) { + id.SetBit(FS_BIT_DEPTH_CLAMP); + } + if (clipInfoFlags & ClipInfoFlags::MinMaxZDiscard) { + id.SetBit(FS_BIT_MINMAX_DISCARD); + } + } else { + // TODO: We're gonna need to soft-clip... Ugh. } } else { _dbg_assert_(0 == (clipInfoFlags & (ClipInfoFlags::DepthClampFragment | ClipInfoFlags::MinMaxZDiscard))); diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp index 7dd3dbda6f..aa8908af90 100644 --- a/GPU/GPUCommonHW.cpp +++ b/GPU/GPUCommonHW.cpp @@ -578,6 +578,10 @@ u32 GPUCommonHW::CheckGPUFeatures() const { features |= GPU_USE_CULL_DISTANCE; } + if (draw_->GetDeviceCaps().fragmentShaderFullPrecisionFloat) { + features |= GPU_USE_FULL_PRECISION_IN_FRAGMENT; + } + if (draw_->GetDeviceCaps().depthClampSupported) { features |= GPU_USE_DEPTH_CLAMP; } diff --git a/GPU/GPUState.h b/GPU/GPUState.h index 201d466921..0422d67d89 100644 --- a/GPU/GPUState.h +++ b/GPU/GPUState.h @@ -483,7 +483,7 @@ enum : u32 { // Free bit: 18 GPU_USE_FRAMEBUFFER_ARRAYS = FLAG_BIT(19), GPU_USE_FRAMEBUFFER_FETCH = FLAG_BIT(20), - // Free bit: 21 + GPU_USE_FULL_PRECISION_IN_FRAGMENT = FLAG_BIT(21), // Required for the min-max/depthclamp in fragment shader stuff GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT = FLAG_BIT(22), GPU_ROUND_DEPTH_TO_16BIT = FLAG_BIT(23), // Can be disabled either per game or if we use a real 16-bit depth buffer GPU_USE_CLIP_DISTANCE = FLAG_BIT(24), diff --git a/UI/SystemInfoScreen.cpp b/UI/SystemInfoScreen.cpp index 3aeae7ed98..ca7e5ce2cd 100644 --- a/UI/SystemInfoScreen.cpp +++ b/UI/SystemInfoScreen.cpp @@ -184,6 +184,8 @@ void SystemInfoScreen::CreateDeviceInfoTab(UI::LinearLayout *deviceSpecs) { const int highp_int_max = gl_extensions.range[1][5][1]; const int highp_float_min = gl_extensions.range[1][2][0]; const int highp_float_max = gl_extensions.range[1][2][1]; + const int highp_int_precision = gl_extensions.precision[1][5]; + const int highp_float_precision = gl_extensions.precision[1][2]; if (highp_int_max != 0) { char temp[128]; snprintf(temp, sizeof(temp), "%d-%d", highp_int_min, highp_int_max); @@ -191,7 +193,8 @@ void SystemInfoScreen::CreateDeviceInfoTab(UI::LinearLayout *deviceSpecs) { } if (highp_float_max != 0) { char temp[128]; - snprintf(temp, sizeof(temp), "%d-%d", highp_float_min, highp_float_max); + // if highp_float_precision is 23, full floats are available which is good. + snprintf(temp, sizeof(temp), "%d-%d (%d bits)", highp_float_min, highp_float_max, highp_float_precision); gpuInfo->Add(new InfoItem(si->T("High precision float range"), temp)); } }