diff --git a/GPU/Common/DrawEngineCommon.cpp b/GPU/Common/DrawEngineCommon.cpp index 413a278a59..47eb2dbe8a 100644 --- a/GPU/Common/DrawEngineCommon.cpp +++ b/GPU/Common/DrawEngineCommon.cpp @@ -421,7 +421,7 @@ bool DrawEngineCommon::TestBoundingBoxFast(const float *worldViewProj, const voi // Check for weird scaling that can make graphics extend beyond the viewport. // NOTE: These checks are not bullet proof. float mtx[16]; - if (vpXCenter != 2048.0f || vpYCenter != 2048.0f || vpXScale < ((scissorX2 + 1) >> 1) || vpYScale < ((scissorY2 + 1) >> 1)) { + if (vpXCenter != 2048.0f || vpYCenter != 2048.0f || vpXScale < ((scissorX2 + 1) >> 1) || fabsf(vpYScale) < ((scissorY2 + 1) >> 1)) { // Note that the PSP does not clip against the viewport. const Vec2f baseOffset = Vec2f(gstate.getOffsetX(), gstate.getOffsetY()); // Region1 (rate) is used as an X1/Y1 here, matching PSP behavior. diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index 45b94e3446..c5658d9f73 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -58,6 +58,9 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu bool highpTexcoord = false; bool enableFragmentTestCache = gstate_c.Use(GPU_USE_FRAGMENT_TEST_CACHE); + const bool fsMinmaxDiscard = id.Bit(FS_BIT_MINMAX_DISCARD); + const bool fsDepthClamp = id.Bit(FS_BIT_DEPTH_CLAMP); + if (compat.gles) { // PowerVR needs highp to do the fog in MHU correctly. // Others don't, and some can't handle highp in the fragment shader. @@ -183,7 +186,7 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu } bool needFragCoord = readFramebufferTex || gstate_c.Use(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT); - bool writeDepth = gstate_c.Use(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT) && !forceDepthWritesOff; + bool writeDepth = (gstate_c.Use(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT) && !forceDepthWritesOff) || fsDepthClamp; // TODO: We could have a separate mechanism to support more ops using the shader blending mechanism, // on hardware that can do proper bit math in fragment shaders. @@ -225,6 +228,9 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu if (doTexture) { WRITE(p, "layout (location = 0) in highp vec3 v_texcoord;\n"); } + if (fsMinmaxDiscard || fsDepthClamp) { + WRITE(p, "layout (location = 4) in highp vec2 v_zw;\n"); + } if (enableAlphaTest && !alphaTestAgainstZero) { WRITE(p, "int roundAndScaleTo255i(in highp float x) { return int(floor(x * 255.0 + 0.5)); }\n"); @@ -291,6 +297,9 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu WRITE(p, " vec4 pixelPos : SV_POSITION;\n"); } } + if (fsMinmaxDiscard || fsDepthClamp) { + WRITE(p, " vec2 v_zw : TEXCOORD2;\n"); + } WRITE(p, "};\n"); if (compat.shaderLanguage == HLSL_D3D11) { @@ -402,6 +411,9 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu if (doTexture) { WRITE(p, "%s %s vec3 v_texcoord;\n", compat.varying_fs, highpTexcoord ? "highp" : "mediump"); } + if (fsMinmaxDiscard || fsDepthClamp) { + WRITE(p, "%s vec2 v_zw;\n", compat.varying_fs); + } if (!enableFragmentTestCache) { if (enableAlphaTest && !alphaTestAgainstZero) { @@ -499,6 +511,9 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu if (doTexture) { WRITE(p, " vec3 v_texcoord = In.v_texcoord;\n"); } + if (fsMinmaxDiscard || fsDepthClamp) { + WRITE(p, " vec2 v_zw = In.v_zw;\n"); + } } // Two things read from the old framebuffer - shader replacement blending and bit-level masking. @@ -1151,12 +1166,27 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu WRITE(p, " %s = vec4(0.0, 0.0, 0.0, %s.z); // blue to alpha\n", compat.fragColor0, compat.fragColor0); } + if (fsDepthClamp || fsMinmaxDiscard) { + WRITE(p, " highp float projZ = v_zw.x / v_zw.y;\n"); + } + + if (fsMinmaxDiscard) { + // See the vertex shader generator for the explanation for this. + WRITE(p, " float clipZ = floor(projZ * 0.5 + 0.5) * 2.0;\n"); + WRITE(p, " if (u_minZmaxZ.x > 0 && clipZ < u_minZmaxZ.x) DISCARD;\n"); + WRITE(p, " if (u_minZmaxZ.y < 65535 && clipZ > u_minZmaxZ.y) DISCARD;\n"); + } + if (gstate_c.Use(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT)) { DepthScaleFactors depthScale = GetDepthScaleFactors(gstate_c.UseFlags()); const double scale = depthScale.ScaleU16(); - WRITE(p, " highp float z = gl_FragCoord.z;\n"); + if (fsDepthClamp) { + WRITE(p, " highp float z = clamp(projZ, 0.0, 65536.0) / 65536.0;\n"); + } else { + WRITE(p, " highp float z = gl_FragCoord.z;\n"); + } // We center the depth with an offset, but only its fraction matters. // When (DepthSliceFactor() - 1) is odd, it will be 0.5, otherwise 0. if (((int)(depthScale.Scale() - 1.0f) & 1) == 1) { @@ -1165,6 +1195,8 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu WRITE(p, " z = floor(z * %f) * (1.0 / %f);\n", scale, scale); } WRITE(p, " gl_FragDepth = z;\n"); + } else if (fsDepthClamp) { + WRITE(p, " gl_FragDepth = clamp(projZ, 0.0, 65536.0) / 65536.0;\n"); } else if (useDiscardStencilBugWorkaround) { // Adreno and some Mali drivers apply early frag tests even with discard in the shader, // when only stencil is used. The exact situation seems to vary by driver. diff --git a/GPU/Common/ShaderId.cpp b/GPU/Common/ShaderId.cpp index 64f6fe0bc9..f8b6591381 100644 --- a/GPU/Common/ShaderId.cpp +++ b/GPU/Common/ShaderId.cpp @@ -13,12 +13,23 @@ #include "GPU/Common/ShaderId.h" #include "GPU/Common/VertexDecoderCommon.h" + +// Shared ID checks for when the vertex and fragment shaders need to coordinate. + +// NOTE: Both of these assume non - through - mode.Don't check these if in through mode. +static bool needFragmentMinMaxClipping() { + return gstate.getDepthRangeMin() != 0 && gstate.getDepthRangeMax() != 0xFFFF && !gstate_c.Use(GPU_USE_CLIP_DISTANCE); +} +static bool needFragmentDepthClamp() { + // If gstate.isDepthClipEnabled is false, clamping does not happen, instead fragments are culled as normal. + return (gstate.getDepthRangeMin() == 0 || gstate.getDepthRangeMax() == 0xFFFF) && gstate.isDepthClipEnabled() && !gstate_c.Use(GPU_USE_DEPTH_CLAMP); +} + std::string VertexShaderDesc(const VShaderID &id) { std::stringstream desc; desc << StringFromFormat("%08x:%08x ", id.d[1], id.d[0]); if (id.Bit(VS_BIT_IS_THROUGH)) desc << "THR "; if (id.Bit(VS_BIT_CLIP_ENABLE)) desc << "Clip "; - if (id.Bit(VS_BIT_MINMAX_DISCARD)) desc << "ZClip "; if (id.Bit(VS_BIT_USE_HW_TRANSFORM)) desc << "HWX "; if (id.Bit(VS_BIT_HAS_COLOR)) desc << "C "; if (id.Bit(VS_BIT_HAS_TEXCOORD)) desc << "T "; @@ -37,6 +48,7 @@ std::string VertexShaderDesc(const VShaderID &id) { if (uvgMode) desc << uvgModes[uvgMode]; if (id.Bit(VS_BIT_ENABLE_BONES)) desc << "Bones:" << (id.Bits(VS_BIT_BONES, 3) + 1) << " "; + // Lights if (id.Bit(VS_BIT_LIGHTING_ENABLE)) { desc << "Light: "; @@ -64,12 +76,15 @@ std::string VertexShaderDesc(const VShaderID &id) { if (id.Bit(VS_BIT_VERTEX_RANGE_CULLING)) desc << "RangeCull "; if (id.Bit(VS_BIT_SIMPLE_STEREO)) desc << "SimpleStereo "; + if (id.Bit(VS_BIT_FS_MINMAX_DISCARD)) desc << "FSMinMax "; + if (id.Bit(VS_BIT_FS_DEPTH_CLAMP)) desc << "FSDepthClamp "; return desc.str(); } void ComputeVertexShaderID(VShaderID *id_out, u32 vertType, bool useHWTransform, bool useHWTessellation, bool weightsAsFloat, bool useSkinInDecode) { - bool isModeThrough = (vertType & GE_VTYPE_THROUGH) != 0; + const bool isModeThrough = (vertType & GE_VTYPE_THROUGH) != 0; + const bool isSoftwareFallback = !isModeThrough && !useHWTransform && g_Config.bHardwareTransform; bool doTexture = gstate.isTextureMapEnabled() && !gstate.isModeClear(); bool doShadeMapping = doTexture && (gstate.getUVGenMode() == GE_TEXMAP_ENVIRONMENT_MAP); bool doFlatShading = gstate.getShadeMode() == GE_SHADE_FLAT && !gstate.isModeClear(); @@ -106,6 +121,7 @@ void ComputeVertexShaderID(VShaderID *id_out, u32 vertType, bool useHWTransform, } if (useHWTransform) { + _dbg_assert_(!isModeThrough); id.SetBit(VS_BIT_USE_HW_TRANSFORM); id.SetBit(VS_BIT_HAS_NORMAL, vtypeHasNormal); @@ -160,6 +176,16 @@ void ComputeVertexShaderID(VShaderID *id_out, u32 vertType, bool useHWTransform, } id.SetBit(VS_BIT_NORM_REVERSE_TESS, gstate.isPatchNormalsReversed()); } + } else { // !useHwTransform + // Various conditions that require per-pixel depth manipulation (very expensive!) + if (!isModeThrough) { + if (needFragmentDepthClamp()) { + id.SetBit(VS_BIT_FS_DEPTH_CLAMP); + } + if (needFragmentMinMaxClipping()) { + id.SetBit(VS_BIT_FS_MINMAX_DISCARD); + } + } } id.SetBit(VS_BIT_FLATSHADE, doFlatShading); @@ -267,6 +293,8 @@ std::string FragmentShaderDesc(const FShaderID &id) { if (id.Bit(FS_BIT_SAMPLE_ARRAY_TEXTURE)) desc << "TexArray "; if (id.Bit(FS_BIT_STEREO)) desc << "Stereo "; if (id.Bit(FS_BIT_USE_FRAMEBUFFER_FETCH)) desc << "(fetch)"; + if (id.Bit(FS_BIT_MINMAX_DISCARD)) desc << "FragMinMaxDiscard "; + if (id.Bit(FS_BIT_DEPTH_CLAMP)) desc << "FragDepthClamp "; return desc.str(); } @@ -285,13 +313,26 @@ inline u32 SanitizeBlendMode(GEBlendMode mode) { // Here we must take all the bits of the gstate that determine what the fragment shader will // look like, and concatenate them together into an ID. -void ComputeFragmentShaderID(FShaderID *id_out, const ComputedPipelineState &pipelineState, const Draw::Bugs &bugs) { +void ComputeFragmentShaderID(FShaderID *id_out, const ComputedPipelineState &pipelineState, const Draw::Bugs &bugs, bool useHwTransform) { FShaderID id; + bool isModeThrough = gstate.isModeThrough(); + + // We exclude hwTransform mode here, although we could absolutely do this in hwtransform as well, because we detect + // draws that needs this and use software transform as the fallback for them. That logic will have to change if we change that. + // 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 (!useHwTransform && !isModeThrough) { + if (needFragmentDepthClamp()) { + id.SetBit(FS_BIT_DEPTH_CLAMP); + } + if (needFragmentMinMaxClipping()) { + id.SetBit(FS_BIT_MINMAX_DISCARD); + } + } + if (gstate.isModeClear()) { // We only need one clear shader, so let's ignore the rest of the bits. id.SetBit(FS_BIT_CLEARMODE); } else { - bool isModeThrough = gstate.isModeThrough(); bool lmode = gstate.isUsingSecondaryColor() && gstate.isLightingEnabled() && !isModeThrough; bool enableFog = gstate.isFogEnabled() && !isModeThrough; bool enableAlphaTest = gstate.isAlphaTestEnabled() && !IsAlphaTestTriviallyTrue(); @@ -404,6 +445,9 @@ void ComputeFragmentShaderID(FShaderID *id_out, const ComputedPipelineState &pip } } + // Various conditions that require per-pixel depth manipulation (very expensive!) + bool needMinMaxClipping = gstate.getDepthRangeMin() != 0 && gstate.getDepthRangeMax() != 0xFFFF && !isModeThrough; + // Forcibly disable NEVER + depth-write on Mali. // TODO: Take this from computed depth test instead of directly from the gstate. // That will take more refactoring though. diff --git a/GPU/Common/ShaderId.h b/GPU/Common/ShaderId.h index 535058957e..04e3957750 100644 --- a/GPU/Common/ShaderId.h +++ b/GPU/Common/ShaderId.h @@ -16,7 +16,7 @@ enum VShaderBit : uint8_t { VS_BIT_CLIP_ENABLE = 4, VS_BIT_VERTEX_RANGE_CULLING = 5, VS_BIT_SIMPLE_STEREO = 6, - VS_BIT_MINMAX_DISCARD = 7, // Do min/max in the fragment shader. + // bit 7 is free, VS_BIT_USE_HW_TRANSFORM = 8, VS_BIT_HAS_NORMAL = 9, // conditioned on hw transform VS_BIT_NORM_REVERSE = 10, @@ -54,6 +54,8 @@ enum VShaderBit : uint8_t { VS_BIT_LIGHTING_ENABLE = 56, VS_BIT_WEIGHT_FMTSCALE = 57, // only two bits // 59 - 61 are free. + VS_BIT_FS_MINMAX_DISCARD = 59, // Do min/max and/or depth clamp in the fragment shader. It just means we need to forward Z and W to the fragment shader. + VS_BIT_FS_DEPTH_CLAMP = 60, // Do depth clamp in the fragment shader. VS_BIT_FLATSHADE = 62, // 1 bit VS_BIT_BEZIER = 63, // 1 bit // No more free @@ -103,6 +105,8 @@ enum FShaderBit : uint8_t { FS_BIT_USE_FRAMEBUFFER_FETCH = 59, FS_BIT_UBERSHADER = 60, FS_BIT_DEPTH_TEST_NEVER = 61, // Only used on Mali. Set when depth == NEVER. We forcibly avoid writing to depth in this case, since it crashes the driver. + FS_BIT_DEPTH_CLAMP = 62, // These both are connected to VS_BIT_MINMAX_DISCARD_OR_DEPTH_CLAMP in the vertex shader. + // Free bit: 63 }; static inline FShaderBit operator +(FShaderBit bit, int i) { @@ -249,7 +253,7 @@ void ComputeVertexShaderID(VShaderID *id, u32 vertType, bool useHWTransform, boo std::string VertexShaderDesc(const VShaderID &id); struct ComputedPipelineState; -void ComputeFragmentShaderID(FShaderID *id, const ComputedPipelineState &pipelineState, const Draw::Bugs &bugs); +void ComputeFragmentShaderID(FShaderID *id, const ComputedPipelineState &pipelineState, const Draw::Bugs &bugs, bool useHwTransform); std::string FragmentShaderDesc(const FShaderID &id); // For sanity checking. diff --git a/GPU/Common/SoftwareTransformCommon.cpp b/GPU/Common/SoftwareTransformCommon.cpp index e59ed7c8e5..28d8b30e69 100644 --- a/GPU/Common/SoftwareTransformCommon.cpp +++ b/GPU/Common/SoftwareTransformCommon.cpp @@ -731,10 +731,18 @@ SoftwareTransformAction SoftwareTransform::ProjectClipAndExpand(SoftwareTransfor // Now that we're done culling and generating clipped vertices if needed (not yet implemented), we go ahead and project. ProjectVertices(transformed, numDecodedVerts); +#if 0 + // NOTE! This code is effectively obsolete now that we have implemented depth clamp in the fragment shader, + // However, this can be an alternate partial solution for low-performance hardware in the future. + // Alright! Now, we can approximate Z-clamping, if the hardware lacks support for doing it for us. // Now, this can only be done exactly if all vertices in a triangle are beyond the far plane. // If not we technically need to cut it in two parts to clamp accurately. + // However, in most cases that matter (such as missing skies, etc), this is fine. + // We could be aggressive and clamp every individual vertex, but this takes the safer (but not 100% safe) route and only clamps vertices + // that are part of a triangle where all three are beyond the same plane. + const int maxZInt = gstate.getDepthRangeMax(); // float maxZ = maxZInt / 65535.0f; const int minZInt = gstate.getDepthRangeMin(); @@ -742,7 +750,6 @@ SoftwareTransformAction SoftwareTransform::ProjectClipAndExpand(SoftwareTransfor // We only need to clamp if minZ and maxZ aren't at the extreme in each direction, as otherwise // minZ and maxZ will cut things off. - // If gstate_c.Use(GPU_USE_DEPTH_CLAMP), we can theoretically skip this. However, to keep behavior the same regardless of the flag, we won't. if (gstate.isDepthClipEnabled() && (minZInt == 0 || maxZInt == 65535)) { for (int i = 0; i < numTrans - 2; i += 3) { TransformedVertex &v0 = transformed[newInds[i]]; @@ -779,6 +786,7 @@ SoftwareTransformAction SoftwareTransform::ProjectClipAndExpand(SoftwareTransfor } } } +#endif } } else { _dbg_assert_(false); diff --git a/GPU/Common/VertexShaderGenerator.cpp b/GPU/Common/VertexShaderGenerator.cpp index fb0abb14f6..cb165e6509 100644 --- a/GPU/Common/VertexShaderGenerator.cpp +++ b/GPU/Common/VertexShaderGenerator.cpp @@ -222,6 +222,11 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag bool hasNormalTess = id.Bit(VS_BIT_HAS_NORMAL_TESS); bool flipNormalTess = id.Bit(VS_BIT_NORM_REVERSE_TESS); + // Should we do the min/max discard in the shader and/or or use depth clamping? + // In both cases we need to just forward + const bool fsMinmaxDiscard = id.Bit(VS_BIT_FS_MINMAX_DISCARD); + const bool fsDepthClamp = id.Bit(VS_BIT_FS_DEPTH_CLAMP); + const char *shading = ""; if (compat.glslES30 || compat.shaderLanguage == GLSL_VULKAN) shading = doFlatShading ? "flat " : ""; @@ -298,6 +303,10 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag WRITE(p, "layout (location = 3) out highp float v_fogdepth;\n"); + if (fsMinmaxDiscard || fsDepthClamp) { + WRITE(p, "layout (location = 4) out highp vec2 v_zw;\n"); + } + WRITE(p, "invariant gl_Position;\n"); } else if (compat.shaderLanguage == HLSL_D3D11) { // Note: These two share some code after this hellishly large if/else. @@ -356,6 +365,10 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag } WRITE(p, " float v_fogdepth : TEXCOORD1;\n"); + if (fsMinmaxDiscard || fsDepthClamp) { + WRITE(p, " vec2 v_zw : TEXCOORD2;\n"); + } + // gl_Position must be last for D3D11. WRITE(p, " vec4 gl_Position : SV_Position;\n"); minZClipPlaneSuffix = ".x"; maxZClipPlaneSuffix = ".y"; @@ -387,7 +400,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag if (useHWTransform) WRITE(p, "%s vec3 position;\n", compat.attribute); else - WRITE(p, "%s vec4 position;\n", compat.attribute); // need to pass the fog coord in w + WRITE(p, "%s vec4 position;\n", compat.attribute); // XYZW clip space coordinate. *attrMask |= 1 << ATTR_POSITION; if (useHWTransform && hasNormal) { @@ -523,6 +536,10 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag } else { WRITE(p, "%s mediump float v_fogdepth;\n", compat.varying_vs); } + + if (fsMinmaxDiscard || fsDepthClamp) { + WRITE(p, "%s highp vec2 v_zw;\n", compat.varying_vs); + } } // Hardware tessellation @@ -747,9 +764,13 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag WRITE(p, " vec4 outPos = position;\n"); WRITE(p, " outPos.z *= 65536.0;\n"); // TODO: This multiplication should be moved to the vertex decoders for through mode. } else { - // The viewport has already been applied here. + // The viewport has already been applied here, along with the division. WRITE(p, " vec4 outPos = position;\n"); } + + if (fsMinmaxDiscard || fsDepthClamp) { + WRITE(p, " %sv_zw = vec2(outPos.z * outPos.w, outPos.w);\n", compat.vsOutPrefix); + } } else { // Step 1: World Transform / Skinning if (!enableBones) { @@ -836,6 +857,8 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag // We're in clip space, so here we check for clipping. We check if the actual hardware will clip. // If so, we skip the "range culling" (x and y out-of-bounds checks) since they wouldn't have happened, most likely. + // NOTE: There are some games that depend on clipping already having been done when checking the range culling. + // We can't handle that here, we use the software transform pipeline for that. WRITE(p, " if (outPos.z < -outPos.w) {\n"); WRITE(p, " zClipped = true;\n"); WRITE(p, " }\n"); @@ -846,7 +869,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag if (depthCullEnable) { // Before the viewport, discard any primitives that are fully outside the clipping volume in Z. - // NOTE: This volume is very slightly too small. It's unclear to me how to allow hex 0x3F8000XX (up to 1.0000304) where XX are arbitrary. + // NOTE: We add a small offset to the clip distance to allow hex 0x3F8000XX (up to 1.0000304) where XX are arbitrary. WRITE(p, " %sgl_CullDistance%s = outPos.z + outPos.w + 0.0000304 / outPos.w;\n", compat.vsOutPrefix, cullDistanceNearSuffix); WRITE(p, " %sgl_CullDistance%s = outPos.w - outPos.z + 0.0000304 / outPos.w;\n", compat.vsOutPrefix, cullDistanceFarSuffix); } @@ -855,6 +878,10 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag // In software transform mode, this is performed in on the CPU. WRITE(p, " outPos.xyz = (outPos.xyz / outPos.w) * u_vpScale.xyz + u_vpOffset.xyz;\n"); + if (fsMinmaxDiscard || fsDepthClamp) { + WRITE(p, " %sv_zw = vec2(outPos.z * outPos.w, outPos.w);\n", compat.vsOutPrefix); + } + // TODO: Declare variables for dots for shade mapping if needed. const char *srcCol = "color0"; @@ -1286,6 +1313,12 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag WRITE(p, " %sgl_Position.y *= u_scaleY;\n", compat.vsOutPrefix); } + if (fsDepthClamp) { + // Overwrite Z with a value that will not be clipped. + // Then we will overwrite the Z in the fragment shader with the per-pixel value computed from the interpolated v_zw. + WRITE(p, " %sgl_Position.z = (u_minZmaxZ.x + u_minZmaxZ.y) * 0.5 * (1.0 / 65536.0);\n", compat.vsOutPrefix, compat.vsOutPrefix); + } + if (compat.depthMinusOneToOne) { // Convert from 0->1 to -1->1 depth range. WRITE(p, " %sgl_Position.z = %sgl_Position.z * 2.0 - %sgl_Position.w;\n", compat.vsOutPrefix, compat.vsOutPrefix, compat.vsOutPrefix); diff --git a/GPU/D3D11/ShaderManagerD3D11.cpp b/GPU/D3D11/ShaderManagerD3D11.cpp index e51f3a40af..ce01ad89e2 100644 --- a/GPU/D3D11/ShaderManagerD3D11.cpp +++ b/GPU/D3D11/ShaderManagerD3D11.cpp @@ -199,7 +199,7 @@ void ShaderManagerD3D11::GetShaders(int prim, u32 vertexType, D3D11VertexShader if (gstate_c.IsDirty(DIRTY_FRAGMENTSHADER_STATE)) { gstate_c.Clean(DIRTY_FRAGMENTSHADER_STATE); - ComputeFragmentShaderID(&FSID, pipelineState, draw_->GetBugs()); + ComputeFragmentShaderID(&FSID, pipelineState, draw_->GetBugs(), useHWTransform); } else { FSID = lastFSID_; } @@ -222,6 +222,7 @@ void ShaderManagerD3D11::GetShaders(int prim, u32 vertexType, D3D11VertexShader VertexShaderFlags flags; GenerateVertexShader(VSID, codeBuffer_, draw_->GetShaderLanguageDesc(), draw_->GetBugs(), &attrMask, &uniformMask, &flags, &genErrorString); _assert_msg_(strlen(codeBuffer_) < CODE_BUFFER_SIZE, "VS length error: %d", (int)strlen(codeBuffer_)); + // OutputDebugStringA(codeBuffer_); vs = new D3D11VertexShader(device_, featureLevel_, VSID, codeBuffer_, useHWTransform); vsCache_[VSID] = vs; } else { @@ -238,6 +239,7 @@ void ShaderManagerD3D11::GetShaders(int prim, u32 vertexType, D3D11VertexShader FragmentShaderFlags flags; GenerateFragmentShader(FSID, codeBuffer_, draw_->GetShaderLanguageDesc(), draw_->GetBugs(), &uniformMask, &flags, &genErrorString); _assert_msg_(strlen(codeBuffer_) < CODE_BUFFER_SIZE, "FS length error: %d", (int)strlen(codeBuffer_)); + // OutputDebugStringA(codeBuffer_); fs = new D3D11FragmentShader(device_, featureLevel_, FSID, codeBuffer_, useHWTransform); fsCache_[FSID] = fs; } else { @@ -246,6 +248,11 @@ void ShaderManagerD3D11::GetShaders(int prim, u32 vertexType, D3D11VertexShader lastFSID_ = FSID; + _dbg_assert_(FSID.Bit(FS_BIT_FLATSHADE) == VSID.Bit(VS_BIT_FLATSHADE)); + _dbg_assert_(FSID.Bit(FS_BIT_LMODE) == VSID.Bit(VS_BIT_LMODE)); + _dbg_assert_(FSID.Bit(FS_BIT_MINMAX_DISCARD) == VSID.Bit(VS_BIT_FS_MINMAX_DISCARD)); + _dbg_assert_(FSID.Bit(FS_BIT_DEPTH_CLAMP) == VSID.Bit(VS_BIT_FS_DEPTH_CLAMP)); + lastVShader_ = vs; lastFShader_ = fs; diff --git a/GPU/GLES/DrawEngineGLES.cpp b/GPU/GLES/DrawEngineGLES.cpp index 6629b2a971..cf582575d6 100644 --- a/GPU/GLES/DrawEngineGLES.cpp +++ b/GPU/GLES/DrawEngineGLES.cpp @@ -274,12 +274,14 @@ void DrawEngineGLES::Flush() { Shader *vshader = shaderManager_->ApplyVertexShader(useHWTransform, useHWTessellation_, dec_->VertexType(), decOptions_.expandAllWeightsToFloat, applySkinInDecode_ || !useHWTransform, &vsid); + useHWTransform = vshader->UseHWTransform(); // In case shader compilation failed and it fell back. However, this can no longer really happen... Need to fix this. + GLRBuffer *vertexBuffer = nullptr; GLRBuffer *indexBuffer = nullptr; uint32_t vertexBufferOffset = 0; uint32_t indexBufferOffset = 0; - if (vshader->UseHWTransform()) { + if (useHWTransform) { if (applySkinInDecode_ && (lastVType_ & GE_VTYPE_WEIGHT_MASK)) { // If software skinning, we're predecoding into "decoded". So make sure we're done, then push that content. DecodeVerts(dec_, decoded_); @@ -322,7 +324,7 @@ void DrawEngineGLES::Flush() { ApplyDrawState(prim); ApplyDrawStateLate(false, 0); - LinkedShader *program = shaderManager_->ApplyFragmentShader(vsid, vshader, pipelineState_); + LinkedShader *program = shaderManager_->ApplyFragmentShader(vsid, vshader, pipelineState_, true); GLRInputLayout *inputLayout = SetupDecFmtForDraw(dec_->GetDecVtxFmt()); if (useElements) { render_->DrawIndexed(inputLayout, @@ -408,7 +410,7 @@ void DrawEngineGLES::Flush() { ApplyDrawState(prim); ApplyDrawStateLate(result.setStencil, result.stencilValue); - LinkedShader *linked = shaderManager_->ApplyFragmentShader(vsid, vshader, pipelineState_); + LinkedShader *linked = shaderManager_->ApplyFragmentShader(vsid, vshader, pipelineState_, false); if (!linked) { // Not much we can do here. Let's skip drawing. goto bail; diff --git a/GPU/GLES/ShaderManagerGLES.cpp b/GPU/GLES/ShaderManagerGLES.cpp index 8796428303..21b30f5345 100644 --- a/GPU/GLES/ShaderManagerGLES.cpp +++ b/GPU/GLES/ShaderManagerGLES.cpp @@ -808,7 +808,7 @@ Shader *ShaderManagerGLES::ApplyVertexShader(bool useHWTransform, bool useHWTess return vs; } -LinkedShader *ShaderManagerGLES::ApplyFragmentShader(VShaderID VSID, Shader *vs, const ComputedPipelineState &pipelineState) { +LinkedShader *ShaderManagerGLES::ApplyFragmentShader(VShaderID VSID, Shader *vs, const ComputedPipelineState &pipelineState, bool useHwTransform) { uint64_t dirty = gstate_c.GetDirtyUniforms(); if (dirty) { if (lastShader_) @@ -820,7 +820,7 @@ LinkedShader *ShaderManagerGLES::ApplyFragmentShader(VShaderID VSID, Shader *vs, FShaderID FSID; if (gstate_c.IsDirty(DIRTY_FRAGMENTSHADER_STATE)) { gstate_c.Clean(DIRTY_FRAGMENTSHADER_STATE); - ComputeFragmentShaderID(&FSID, pipelineState, draw_->GetBugs()); + ComputeFragmentShaderID(&FSID, pipelineState, draw_->GetBugs(), useHwTransform); } else { FSID = lastFSID_; } @@ -862,6 +862,8 @@ LinkedShader *ShaderManagerGLES::ApplyFragmentShader(VShaderID VSID, Shader *vs, if (ls == nullptr) { _dbg_assert_(FSID.Bit(FS_BIT_LMODE) == VSID.Bit(VS_BIT_LMODE)); _dbg_assert_(FSID.Bit(FS_BIT_FLATSHADE) == VSID.Bit(VS_BIT_FLATSHADE)); + _dbg_assert_(FSID.Bit(FS_BIT_MINMAX_DISCARD) == VSID.Bit(VS_BIT_FS_MINMAX_DISCARD)); + _dbg_assert_(FSID.Bit(FS_BIT_DEPTH_CLAMP) == VSID.Bit(VS_BIT_FS_DEPTH_CLAMP)); if (vs == nullptr || fs == nullptr) { // Can't draw. This shouldn't really happen (but can happen if fragment shader generation fails) diff --git a/GPU/GLES/ShaderManagerGLES.h b/GPU/GLES/ShaderManagerGLES.h index cef406915c..b8aae87b9d 100644 --- a/GPU/GLES/ShaderManagerGLES.h +++ b/GPU/GLES/ShaderManagerGLES.h @@ -171,7 +171,7 @@ public: // This is the old ApplyShader split into two parts, because of annoying information dependencies. // If you call ApplyVertexShader, you MUST call ApplyFragmentShader soon afterwards. Shader *ApplyVertexShader(bool useHWTransform, bool useHWTessellation, u32 vertexType, bool weightsAsFloat, bool useSkinInDecode, VShaderID *VSID); - LinkedShader *ApplyFragmentShader(VShaderID VSID, Shader *vs, const ComputedPipelineState &pipelineState); + LinkedShader *ApplyFragmentShader(VShaderID VSID, Shader *vs, const ComputedPipelineState &pipelineState, bool useHWTransform); void DeviceLost() override; void DeviceRestore(Draw::DrawContext *draw) override; diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp index 7c28dd24d6..cb89adf832 100644 --- a/GPU/GPUCommonHW.cpp +++ b/GPU/GPUCommonHW.cpp @@ -200,11 +200,11 @@ const CommonCommandTableEntry commonCommandTable[] = { { GE_CMD_VIEWPORTYCENTER, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_VIEWPORT_UNIFORMS }, { GE_CMD_VIEWPORTZSCALE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_VIEWPORT_UNIFORMS }, { GE_CMD_VIEWPORTZCENTER, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_VIEWPORT_UNIFORMS }, - { GE_CMD_DEPTHCLIPENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE }, - // Z range. - { GE_CMD_MINZ, FLAG_FLUSHBEFOREONCHANGE, DIRTY_RASTER_STATE | DIRTY_RASTER_OFFSET}, - { GE_CMD_MAXZ, FLAG_FLUSHBEFOREONCHANGE, DIRTY_RASTER_STATE | DIRTY_RASTER_OFFSET}, + // Z range and clipping + { GE_CMD_DEPTHCLIPENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE }, + { GE_CMD_MINZ, FLAG_FLUSHBEFOREONCHANGE, DIRTY_RASTER_STATE | DIRTY_RASTER_OFFSET | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE }, + { GE_CMD_MAXZ, FLAG_FLUSHBEFOREONCHANGE, DIRTY_RASTER_STATE | DIRTY_RASTER_OFFSET | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE }, // Region { GE_CMD_REGION1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_VIEWPORTSCISSOR_STATE }, diff --git a/GPU/Vulkan/DrawEngineVulkan.cpp b/GPU/Vulkan/DrawEngineVulkan.cpp index 6d4c93e785..2f737c7c58 100644 --- a/GPU/Vulkan/DrawEngineVulkan.cpp +++ b/GPU/Vulkan/DrawEngineVulkan.cpp @@ -241,13 +241,12 @@ void DrawEngineVulkan::Flush() { provokingVertexOk = true; } bool useHWTransform = CanUseHardwareTransform(prim) && provokingVertexOk; - if (useHWTransform && boundingDepths_.valid) { if (boundingDepths_.hitClipSpaceZW) { // Revert to software transform so we can clip more accurately. useHWTransform = false; } - if ((boundingDepths_.minProjZ < 0.0 || boundingDepths_.maxProjZ > 65535.0) && !gstate_c.Use(GPU_USE_DEPTH_CLAMP)) { + if ((boundingDepths_.minProjZ < gstate.getDepthRangeMin() || boundingDepths_.maxProjZ > gstate.getDepthRangeMax()) && !gstate_c.Use(GPU_USE_DEPTH_CLAMP)) { // Revert to software transform so we can clamp more accurately. useHWTransform = false; } @@ -255,7 +254,8 @@ void DrawEngineVulkan::Flush() { } if (useHWTransform != lastUseHwTransform_) { - gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_RASTER_STATE); + // Need to re-evaluate software transform fallbacks. + gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE); lastUseHwTransform_ = useHWTransform; } diff --git a/GPU/Vulkan/ShaderManagerVulkan.cpp b/GPU/Vulkan/ShaderManagerVulkan.cpp index c28f2cbd0a..25dea93c9e 100644 --- a/GPU/Vulkan/ShaderManagerVulkan.cpp +++ b/GPU/Vulkan/ShaderManagerVulkan.cpp @@ -249,8 +249,11 @@ void ShaderManagerVulkan::GetShaders(int prim, u32 vertexType, VulkanVertexShade VShaderID VSID; VulkanVertexShader *vs = nullptr; + bool recomputedVS = false; + bool recomputedFS = false; if (gstate_c.IsDirty(DIRTY_VERTEXSHADER_STATE)) { gstate_c.Clean(DIRTY_VERTEXSHADER_STATE); + recomputedVS = true; ComputeVertexShaderID(&VSID, vertexType, useHWTransform, useHWTessellation, weightsAsFloat, useSkinInDecode); if (VSID == lastVSID_) { _dbg_assert_(lastVShader_ != nullptr); @@ -281,7 +284,8 @@ void ShaderManagerVulkan::GetShaders(int prim, u32 vertexType, VulkanVertexShade VulkanFragmentShader *fs = nullptr; if (gstate_c.IsDirty(DIRTY_FRAGMENTSHADER_STATE)) { gstate_c.Clean(DIRTY_FRAGMENTSHADER_STATE); - ComputeFragmentShaderID(&FSID, pipelineState, draw_->GetBugs()); + ComputeFragmentShaderID(&FSID, pipelineState, draw_->GetBugs(), useHWTransform); + recomputedFS = true; if (FSID == lastFSID_) { _dbg_assert_(lastFShader_ != nullptr); fs = lastFShader_; @@ -305,8 +309,12 @@ void ShaderManagerVulkan::GetShaders(int prim, u32 vertexType, VulkanVertexShade } *fshader = fs; + // If you hit these, look at recomputedVS and recomputedFS to determine if it's a dirty-flag problem + // or an ID generation problem (if any of them are false, it's a dirty-flag problem). _dbg_assert_(FSID.Bit(FS_BIT_FLATSHADE) == VSID.Bit(VS_BIT_FLATSHADE)); _dbg_assert_(FSID.Bit(FS_BIT_LMODE) == VSID.Bit(VS_BIT_LMODE)); + _dbg_assert_(FSID.Bit(FS_BIT_MINMAX_DISCARD) == VSID.Bit(VS_BIT_FS_MINMAX_DISCARD)); + _dbg_assert_(FSID.Bit(FS_BIT_DEPTH_CLAMP) == VSID.Bit(VS_BIT_FS_DEPTH_CLAMP)); _dbg_assert_msg_((*vshader)->UseHWTransform() == useHWTransform, "Bad vshader was computed"); }