diff --git a/Core/Compatibility.cpp b/Core/Compatibility.cpp index 0740e41711..a9f1a7556e 100644 --- a/Core/Compatibility.cpp +++ b/Core/Compatibility.cpp @@ -102,5 +102,10 @@ void Compatibility::CheckSettings(IniFile &iniFile, const std::string &gameID) { void Compatibility::CheckSetting(IniFile &iniFile, const std::string &gameID, const char *option, bool *flag) { if (ignored_.find(option) == ignored_.end()) { iniFile.Get(option, gameID.c_str(), flag, *flag); + + // Shortcut for debugging, sometimes useful to globally enable compat flags. + bool all = false; + iniFile.Get(option, "ALL", &all, false); + *flag |= all; } } diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index 2173130295..cf67eb0037 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -108,11 +108,6 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu blueToAlpha = true; } - GEBlendSrcFactor replaceBlendFuncA = (GEBlendSrcFactor)id.Bits(FS_BIT_BLENDFUNC_A, 4); - GEBlendDstFactor replaceBlendFuncB = (GEBlendDstFactor)id.Bits(FS_BIT_BLENDFUNC_B, 4); - GEBlendMode replaceBlendEq = (GEBlendMode)id.Bits(FS_BIT_BLENDEQ, 3); - StencilValueType replaceAlphaWithStencilType = (StencilValueType)id.Bits(FS_BIT_REPLACE_ALPHA_WITH_STENCIL_TYPE, 4); - bool isModeClear = id.Bit(FS_BIT_CLEARMODE); const char *shading = ""; @@ -121,7 +116,16 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu bool useDiscardStencilBugWorkaround = id.Bit(FS_BIT_NO_DEPTH_CANNOT_DISCARD_STENCIL); - bool readFramebuffer = replaceBlend == REPLACE_BLEND_READ_FRAMEBUFFER || colorWriteMask; + GEBlendSrcFactor replaceBlendFuncA = (GEBlendSrcFactor)id.Bits(FS_BIT_BLENDFUNC_A, 4); + GEBlendDstFactor replaceBlendFuncB = (GEBlendDstFactor)id.Bits(FS_BIT_BLENDFUNC_B, 4); + GEBlendMode replaceBlendEq = (GEBlendMode)id.Bits(FS_BIT_BLENDEQ, 3); + StencilValueType replaceAlphaWithStencilType = (StencilValueType)id.Bits(FS_BIT_REPLACE_ALPHA_WITH_STENCIL_TYPE, 4); + + // Distinct from the logic op simulation support. + GELogicOp replaceLogicOpType = isModeClear ? GE_LOGIC_COPY : (GELogicOp)id.Bits(FS_BIT_REPLACE_LOGIC_OP, 4); + bool replaceLogicOp = replaceLogicOpType != GE_LOGIC_COPY && compat.bitwiseOps; + + bool readFramebuffer = replaceBlend == REPLACE_BLEND_READ_FRAMEBUFFER || colorWriteMask || replaceLogicOp; bool readFramebufferTex = readFramebuffer && !gstate_c.Supports(GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH); bool needFragCoord = readFramebuffer || gstate_c.Supports(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT); @@ -425,7 +429,7 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu } // Provide implementations of packUnorm4x8 and unpackUnorm4x8 if not available. - if (colorWriteMask && !hasPackUnorm4x8) { + if ((colorWriteMask || replaceLogicOp) && !hasPackUnorm4x8) { WRITE(p, "uint packUnorm4x8(%svec4 v) {\n", compat.shaderLanguage == GLSL_VULKAN ? "highp " : ""); WRITE(p, " highp vec4 f = clamp(v, 0.0, 1.0);\n"); WRITE(p, " uvec4 u = uvec4(255.0 * f);\n"); @@ -1078,16 +1082,37 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu return false; } - // Final color computed - apply color write mask. - // TODO: Maybe optimize to only do math on the affected channels? - // Or .. meh. That would require more shader bits. Though we could - // of course optimize for the common mask 0xF00000, though again, blue-to-alpha - // does a better job with that. - if (colorWriteMask) { + // Final color computed - apply logic ops and bitwise color write mask, through shader blending, if specified. + if (colorWriteMask || replaceLogicOp) { WRITE(p, " highp uint v32 = packUnorm4x8(%s);\n", compat.fragColor0); WRITE(p, " highp uint d32 = packUnorm4x8(destColor);\n"); - // Note that the mask has been flipped to the PC way - 1 means write. - WRITE(p, " v32 = (v32 & u_colorWriteMask) | (d32 & ~u_colorWriteMask);\n"); + + // v32 is both the "s" to the logical operation, and the value that we'll merge to the destination with masking later. + // d32 is the "d" to the logical operation. + // NOTE: Alpha of v32 needs to be preserved. Same equations as in the software renderer. + switch (replaceLogicOpType) { + case GE_LOGIC_CLEAR: p.C(" v32 &= 0xFF000000u;\n"); break; + case GE_LOGIC_AND: p.C(" v32 = v32 & (d32 | 0xFF000000u);\n"); break; + case GE_LOGIC_AND_REVERSE: p.C(" v32 = v32 & (~d32 | 0xFF000000u);\n"); break; + case GE_LOGIC_COPY: break; // source to dest, do nothing. Will be set to this, if not used. + case GE_LOGIC_AND_INVERTED: p.C(" v32 = (~v32 & (d32 & 0x00FFFFFFu)) | (v32 & 0xFF000000u);\n"); break; + case GE_LOGIC_NOOP: p.C(" v32 = (d32 & 0x00FFFFFFu) | (v32 & 0xFF000000u);\n"); break; + case GE_LOGIC_XOR: p.C(" v32 = v32 ^ (d32 & 0x00FFFFFFu);\n"); break; + case GE_LOGIC_OR: p.C(" v32 = v32 | (d32 & 0x00FFFFFFu);\n"); break; + case GE_LOGIC_NOR: p.C(" v32 = (~(v32 | d32) & 0x00FFFFFFu) | (v32 & 0xFF000000u);\n"); break; + case GE_LOGIC_EQUIV: p.C(" v32 = (~(v32 ^ d32) & 0x00FFFFFFu) | (v32 & 0xFF000000u);\n"); break; + case GE_LOGIC_INVERTED: p.C(" v32 = (~d32 & 0x00FFFFFFu) | (v32 & 0xFF000000u);\n"); break; + case GE_LOGIC_OR_REVERSE: p.C(" v32 = v32 | (~d32 & 0x00FFFFFFu);\n"); break; + case GE_LOGIC_COPY_INVERTED: p.C(" v32 = (~v32 & 0x00FFFFFFu) | (v32 &0xFF000000u);\n"); break; + case GE_LOGIC_OR_INVERTED: p.C(" v32 = ((~v32 | d32) & 0x00FFFFFFu) | (v32 & 0xFF000000u);\n"); break; + case GE_LOGIC_NAND: p.C(" v32 = (~(v32 & d32) & 0x00FFFFFFu) | (v32 & 0xFF000000u);\n"); break; + case GE_LOGIC_SET: p.C(" v32 |= 0x00FFFFFF;\n"); break; + } + + // Note that the mask has already been flipped to the PC way - 1 means write. + if (colorWriteMask) { + WRITE(p, " v32 = (v32 & u_colorWriteMask) | (d32 & ~u_colorWriteMask);\n"); + } WRITE(p, " %s = unpackUnorm4x8(v32);\n", compat.fragColor0); } diff --git a/GPU/Common/GPUStateUtils.cpp b/GPU/Common/GPUStateUtils.cpp index c0d2df2e5b..7b016f3a27 100644 --- a/GPU/Common/GPUStateUtils.cpp +++ b/GPU/Common/GPUStateUtils.cpp @@ -996,7 +996,7 @@ void ApplyStencilReplaceAndLogicOpIgnoreBlend(ReplaceAlphaType replaceAlphaWithS // we read from the framebuffer (or a copy of it). // We also prepare uniformMask so that if doing this in the shader gets forced-on, // we have the right mask already. -void ConvertMaskState(GenericMaskState &maskState, bool shaderBitOpsSupported) { +static void ConvertMaskState(GenericMaskState &maskState, bool shaderBitOpsSupported) { if (gstate_c.blueToAlpha) { maskState.applyFramebufferRead = false; maskState.uniformMask = 0xFF000000; @@ -1042,7 +1042,7 @@ void ConvertMaskState(GenericMaskState &maskState, bool shaderBitOpsSupported) { } // Called even if AlphaBlendEnable == false - it also deals with stencil-related blend state. -void ConvertBlendState(GenericBlendState &blendState, bool forceReplaceBlend) { +static void ConvertBlendState(GenericBlendState &blendState, bool forceReplaceBlend) { // Blending is a bit complex to emulate. This is due to several reasons: // // * Doubled blend modes (src, dst, inversed) aren't supported in OpenGL. @@ -1334,6 +1334,33 @@ void ConvertBlendState(GenericBlendState &blendState, bool forceReplaceBlend) { blendState.setEquation(colorEq, alphaEq); } +static void ConvertLogicOpState(GenericLogicState &logicOpState, bool logicSupported, bool shaderBitOpsSupported, bool forceApplyFramebuffer) { + // TODO: We can get more detailed with checks here. Some logic ops don't involve the destination at all. + // Several can be trivially supported even without any bitwise logic. + if (gstate.isLogicOpEnabled() && gstate.getLogicOp() != GE_LOGIC_COPY && forceApplyFramebuffer && shaderBitOpsSupported) { + // We have to emulate logic ops in the shader. + logicOpState.logicOpEnabled = false; // Don't use any hardware logic op, supported or not. + logicOpState.applyFramebufferRead = true; + logicOpState.logicOp = gstate.getLogicOp(); + } else if (logicSupported) { + // We can use hardware logic ops, if needed. + logicOpState.applyFramebufferRead = false; + if (gstate.isLogicOpEnabled()) { + logicOpState.logicOpEnabled = true; + logicOpState.logicOp = gstate.getLogicOp(); + } else { + logicOpState.logicOpEnabled = false; + logicOpState.logicOp = GE_LOGIC_COPY; + } + } else { + // In this case, the SIMULATE fallback should kick in. + // Need to make sure this is checking for the same things though... + logicOpState.logicOpEnabled = false; + logicOpState.logicOp = GE_LOGIC_COPY; + logicOpState.applyFramebufferRead = false; + } +} + static void ConvertStencilFunc5551(GenericStencilFuncState &state) { // Flaws: // - INVERT should convert 1, 5, 0xFF to 0. Currently it won't always. @@ -1466,19 +1493,6 @@ static void ConvertStencilFunc5551(GenericStencilFuncState &state) { } } -void ConvertLogicOpState(GenericLogicState &logicOpState, bool logicSupported, bool shaderBitOpsSupported) { - // Just do the non-shader case for now. - if (gstate_c.Supports(GPU_SUPPORTS_LOGIC_OP)) { - logicOpState.logicOpEnabled = gstate.isLogicOpEnabled() && logicSupported; - logicOpState.logicOp = gstate.isLogicOpEnabled() ? gstate.getLogicOp() : GE_LOGIC_COPY; - logicOpState.applyFramebufferRead = false; - } else { - logicOpState.logicOpEnabled = false; - logicOpState.logicOp = GE_LOGIC_COPY; - logicOpState.applyFramebufferRead = false; // true later? - } -} - static void ConvertStencilMask5551(GenericStencilFuncState &state) { state.writeMask = state.writeMask >= 0x80 ? 0xff : 0x00; } @@ -1538,6 +1552,13 @@ void GenericBlendState::Log() { void ComputedPipelineState::Convert(bool shaderBitOpsSuppported) { ConvertMaskState(maskState, shaderBitOpsSuppported); - ConvertLogicOpState(logicState, gstate_c.Supports(GPU_SUPPORTS_LOGIC_OP), shaderBitOpsSuppported); + ConvertLogicOpState(logicState, gstate_c.Supports(GPU_SUPPORTS_LOGIC_OP), shaderBitOpsSuppported, maskState.applyFramebufferRead); ConvertBlendState(blendState, maskState.applyFramebufferRead); + + // Note: If the blend state decided it had to use framebuffer reads, + // we need to switch mask and logic over to also use it, otherwise things will go wrong. + if (blendState.applyFramebufferRead) { + maskState.ConvertToShaderBlend(); + logicState.ConvertToShaderBlend(); + } } diff --git a/GPU/Common/GPUStateUtils.h b/GPU/Common/GPUStateUtils.h index b91311207d..ea65441a59 100644 --- a/GPU/Common/GPUStateUtils.h +++ b/GPU/Common/GPUStateUtils.h @@ -184,7 +184,6 @@ struct GenericBlendState { void Log(); }; -void ConvertBlendState(GenericBlendState &blendState, bool forceReplaceBlend); void ApplyStencilReplaceAndLogicOpIgnoreBlend(ReplaceAlphaType replaceAlphaWithStencil, GenericBlendState &blendState); struct GenericMaskState { @@ -204,8 +203,6 @@ struct GenericMaskState { void Log(); }; -void ConvertMaskState(GenericMaskState &maskState, bool shaderBitOpsSupported); - struct GenericStencilFuncState { bool enabled; GEComparison testFunc; @@ -240,8 +237,6 @@ struct GenericLogicState { void Log(); }; -void ConvertLogicOpState(GenericLogicState &logicOpState, bool logicSupported, bool shaderBitOpsSupported); - struct ComputedPipelineState { GenericBlendState blendState; GenericMaskState maskState; diff --git a/GPU/Common/ShaderId.cpp b/GPU/Common/ShaderId.cpp index 53176ebcba..d05cc3a986 100644 --- a/GPU/Common/ShaderId.cpp +++ b/GPU/Common/ShaderId.cpp @@ -268,6 +268,7 @@ void ComputeFragmentShaderID(FShaderID *id_out, const ComputedPipelineState &pip ReplaceBlendType replaceBlend = pipelineState.blendState.replaceBlend; ReplaceAlphaType stencilToAlpha = pipelineState.blendState.replaceAlphaWithStencil; SimulateLogicOpType simulateLogicOpType = pipelineState.blendState.simulateLogicOpType; + GELogicOp replaceLogicOpType = pipelineState.logicState.applyFramebufferRead ? pipelineState.logicState.logicOp : GE_LOGIC_COPY; // All texfuncs except replace are the same for RGB as for RGBA with full alpha. // Note that checking this means that we must dirty the fragment shader ID whenever textureFullAlpha changes. @@ -325,6 +326,9 @@ void ComputeFragmentShaderID(FShaderID *id_out, const ComputedPipelineState &pip // 2 bits. id.SetBits(FS_BIT_SIMULATE_LOGIC_OP_TYPE, 2, simulateLogicOpType); + // 4 bits. Set to GE_LOGIC_COPY if not used, which does nothing in the shader generator. + id.SetBits(FS_BIT_REPLACE_LOGIC_OP, 4, (int)replaceLogicOpType); + // If replaceBlend == REPLACE_BLEND_STANDARD (or REPLACE_BLEND_NO) nothing is done, so we kill these bits. if (replaceBlend == REPLACE_BLEND_BLUE_TO_ALPHA) { id.SetBits(FS_BIT_REPLACE_BLEND, 3, replaceBlend); diff --git a/GPU/Common/ShaderId.h b/GPU/Common/ShaderId.h index 914824d6bc..bd54e2bd82 100644 --- a/GPU/Common/ShaderId.h +++ b/GPU/Common/ShaderId.h @@ -95,6 +95,7 @@ enum FShaderBit : uint8_t { FS_BIT_COLOR_WRITEMASK = 50, FS_BIT_3D_TEXTURE = 51, FS_BIT_SHADER_SMOOTHED_DEPAL = 52, + FS_BIT_REPLACE_LOGIC_OP = 53, // 4 bits. GE_LOGIC_COPY means no-op/off. }; static inline FShaderBit operator +(FShaderBit bit, int i) { diff --git a/GPU/Vulkan/ShaderManagerVulkan.cpp b/GPU/Vulkan/ShaderManagerVulkan.cpp index 52c2103a88..eb30aa4728 100644 --- a/GPU/Vulkan/ShaderManagerVulkan.cpp +++ b/GPU/Vulkan/ShaderManagerVulkan.cpp @@ -370,7 +370,7 @@ VulkanFragmentShader *ShaderManagerVulkan::GetFragmentShaderFromModule(VkShaderM // instantaneous. #define CACHE_HEADER_MAGIC 0xff51f420 -#define CACHE_VERSION 21 +#define CACHE_VERSION 22 struct VulkanCacheHeader { uint32_t magic; uint32_t version; diff --git a/assets/compat.ini b/assets/compat.ini index bbe058a707..b2ffe62920 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -1120,14 +1120,12 @@ ULJM05812 = true NPJH50371 = true # Colin McRae's DiRT 2 - issue #13012 (car lighting) -# Previously used ReinterpretFramebuffers + ShaderColorBitmask ULUS10471 = true ULJM05533 = true NPJH50006 = true ULES01301 = true # Outrun 2006: Coast to Coast - issue #11358 (car reflections) -# Previously used ReinterpretFramebuffers + ShaderColorBitmask ULES00262 = true ULUS10064 = true ULKS46087 = true @@ -1138,7 +1136,16 @@ NPUZ00043 = true NPEZ00198 = true [ShaderColorBitmask] -# No users right now, but keeping it around as a more accurate option than BlueToAlpha, for debugging mainly Outrun. +# Colin McRae's DiRT 2 - issue #13012 (water) +ULUS10471 = true +ULJM05533 = true +NPJH50006 = true +ULES01301 = true + +# Outrun 2006: Coast to Coast - issue #11358 (car reflections), #11928 (water) +ULES00262 = true +ULUS10064 = true +ULKS46087 = true [DisableFirstFrameReadback] # Wipeout Pure: Temporary workaround for lens flare flicker. See #13344