diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index 1a694a5e60..2173130295 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -1060,9 +1060,10 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu return false; } - // TODO: This could support more ops using the shader blending mechanism. - LogicOpReplaceType replaceLogicOpType = (LogicOpReplaceType)id.Bits(FS_BIT_REPLACE_LOGIC_OP_TYPE, 2); - switch (replaceLogicOpType) { + // 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. + SimulateLogicOpType simulateLogicOpType = (SimulateLogicOpType)id.Bits(FS_BIT_SIMULATE_LOGIC_OP_TYPE, 2); + switch (simulateLogicOpType) { case LOGICOPTYPE_ONE: WRITE(p, " %s.rgb = splat3(1.0);\n", compat.fragColor0); break; diff --git a/GPU/Common/GPUStateUtils.cpp b/GPU/Common/GPUStateUtils.cpp index c0886343a5..33548c2f5d 100644 --- a/GPU/Common/GPUStateUtils.cpp +++ b/GPU/Common/GPUStateUtils.cpp @@ -489,27 +489,6 @@ ReplaceBlendType ReplaceBlendWithShader(GEBufferFormat bufferFormat) { return REPLACE_BLEND_STANDARD; } -LogicOpReplaceType ReplaceLogicOpType() { - if (!gstate_c.Supports(GPU_SUPPORTS_LOGIC_OP) && gstate.isLogicOpEnabled()) { - switch (gstate.getLogicOp()) { - case GE_LOGIC_COPY_INVERTED: - case GE_LOGIC_AND_INVERTED: - case GE_LOGIC_OR_INVERTED: - case GE_LOGIC_NOR: - case GE_LOGIC_NAND: - case GE_LOGIC_EQUIV: - return LOGICOPTYPE_INVERT; - case GE_LOGIC_INVERTED: - return LOGICOPTYPE_ONE; - case GE_LOGIC_SET: - return LOGICOPTYPE_ONE; - default: - return LOGICOPTYPE_NORMAL; - } - } - return LOGICOPTYPE_NORMAL; -} - static const float DEPTH_SLICE_FACTOR_HIGH = 4.0f; static const float DEPTH_SLICE_FACTOR_16BIT = 256.0f; @@ -874,8 +853,10 @@ static inline bool blendColorSimilar(uint32_t a, uint32_t b, int margin = 25) { return false; } -// Try to simulate some common logic ops. -static void ApplyLogicOp(BlendFactor &srcBlend, BlendFactor &dstBlend, BlendEq &blendEq) { +// Try to simulate some common logic ops by using blend, if needed. +// The shader might also need modification, the below function SimulateLogicOpShaderTypeIfNeeded +// takes care of that. +static void SimulateLogicOpIfNeeded(BlendFactor &srcBlend, BlendFactor &dstBlend, BlendEq &blendEq) { // Note: our shader solution applies logic ops BEFORE blending, not correctly after. // This is however fine for the most common ones, like CLEAR/NOOP/SET, etc. if (!gstate_c.Supports(GPU_SUPPORTS_LOGIC_OP)) { @@ -937,6 +918,28 @@ static void ApplyLogicOp(BlendFactor &srcBlend, BlendFactor &dstBlend, BlendEq & } } +// Choose the shader part of the above logic op fallback simulation. +SimulateLogicOpType SimulateLogicOpShaderTypeIfNeeded() { + if (!gstate_c.Supports(GPU_SUPPORTS_LOGIC_OP) && gstate.isLogicOpEnabled()) { + switch (gstate.getLogicOp()) { + case GE_LOGIC_COPY_INVERTED: + case GE_LOGIC_AND_INVERTED: + case GE_LOGIC_OR_INVERTED: + case GE_LOGIC_NOR: + case GE_LOGIC_NAND: + case GE_LOGIC_EQUIV: + return LOGICOPTYPE_INVERT; + case GE_LOGIC_INVERTED: + return LOGICOPTYPE_ONE; + case GE_LOGIC_SET: + return LOGICOPTYPE_ONE; + default: + return LOGICOPTYPE_NORMAL; + } + } + return LOGICOPTYPE_NORMAL; +} + void ApplyStencilReplaceAndLogicOpIgnoreBlend(ReplaceAlphaType replaceAlphaWithStencil, GenericBlendState &blendState) { StencilValueType stencilType = STENCIL_VALUE_KEEP; if (replaceAlphaWithStencil == REPLACE_ALPHA_YES) { @@ -947,7 +950,7 @@ void ApplyStencilReplaceAndLogicOpIgnoreBlend(ReplaceAlphaType replaceAlphaWithS BlendFactor srcBlend = BlendFactor::ONE; BlendFactor dstBlend = BlendFactor::ZERO; BlendEq blendEq = BlendEq::ADD; - ApplyLogicOp(srcBlend, dstBlend, blendEq); + SimulateLogicOpIfNeeded(srcBlend, dstBlend, blendEq); // We're not blending, but we may still want to "blend" for stencil. // This is only useful for INCR/DECR/INVERT. Others can write directly. @@ -1245,8 +1248,8 @@ void ConvertBlendState(GenericBlendState &blendState, bool forceReplaceBlend) { colorEq = eqLookupNoMinMax[blendFuncEq]; } - // Attempt to apply the logic op, if any. - ApplyLogicOp(glBlendFuncA, glBlendFuncB, colorEq); + // Attempt to apply simulated logic ops, if any and if needed. + SimulateLogicOpIfNeeded(glBlendFuncA, glBlendFuncB, colorEq); // The stencil-to-alpha in fragment shader doesn't apply here (blending is enabled), and we shouldn't // do any blending in the alpha channel as that doesn't seem to happen on PSP. So, we attempt to @@ -1508,3 +1511,8 @@ void ConvertStencilFuncState(GenericStencilFuncState &state) { break; } } + +void ComputedPipelineState::Convert(bool shaderBitOpsSuppported) { + ConvertMaskState(maskState, shaderBitOpsSuppported); + ConvertBlendState(blendState, maskState.applyFramebufferRead); +} diff --git a/GPU/Common/GPUStateUtils.h b/GPU/Common/GPUStateUtils.h index a1e7f0a06e..cfa5fb33fb 100644 --- a/GPU/Common/GPUStateUtils.h +++ b/GPU/Common/GPUStateUtils.h @@ -45,7 +45,7 @@ enum ReplaceBlendType { REPLACE_BLEND_BLUE_TO_ALPHA, }; -enum LogicOpReplaceType { +enum SimulateLogicOpType { LOGICOPTYPE_NORMAL, LOGICOPTYPE_ONE, LOGICOPTYPE_INVERT, @@ -62,7 +62,8 @@ StencilValueType ReplaceAlphaWithStencilType(); ReplaceAlphaType ReplaceAlphaWithStencil(ReplaceBlendType replaceBlend); ReplaceBlendType ReplaceBlendWithShader(GEBufferFormat bufferFormat); -LogicOpReplaceType ReplaceLogicOpType(); +// This is for the fallback path if real logic ops are not available. +SimulateLogicOpType SimulateLogicOpShaderTypeIfNeeded(); // Common representation, should be able to set this directly with any modern API. struct ViewportAndScissor { @@ -186,7 +187,16 @@ void ApplyStencilReplaceAndLogicOpIgnoreBlend(ReplaceAlphaType replaceAlphaWithS struct GenericMaskState { bool applyFramebufferRead; uint32_t uniformMask; // For each bit, opposite to the PSP. + + // The hardware channel masks, 1 bit per color component. From bit 0, order is RGBA like in all APIs! uint8_t channelMask; + + void ConvertToShaderBlend() { + // If we have to do it in the shader, we simply pass through all channels but mask only in the shader instead. + // Some GPUs have minor penalties for masks that are not all-channels-on or all-channels-off. + channelMask = 0xF; + applyFramebufferRead = true; + } }; void ConvertMaskState(GenericMaskState &maskState, bool shaderBitOpsSupported); @@ -207,6 +217,8 @@ struct ComputedPipelineState { GenericBlendState blendState; GenericMaskState maskState; // TODO: Add logic and possibly stencil here. + + void Convert(bool shaderBitOpsSupported); }; // See issue #15898 diff --git a/GPU/Common/ShaderId.cpp b/GPU/Common/ShaderId.cpp index e5ca1aad80..0c5ffd2d52 100644 --- a/GPU/Common/ShaderId.cpp +++ b/GPU/Common/ShaderId.cpp @@ -267,16 +267,13 @@ void ComputeFragmentShaderID(FShaderID *id_out, const ComputedPipelineState &pip ReplaceBlendType replaceBlend = pipelineState.blendState.replaceBlend; ReplaceAlphaType stencilToAlpha = pipelineState.blendState.replaceAlphaWithStencil; - - // For debugging, can probably delete soon. - // _assert_(colorWriteMask == IsColorWriteMaskComplex()); - // _assert_(replaceBlend == ReplaceBlendWithShader(gstate_c.framebufFormat); - // _assert_(stencilToAlpha == ReplaceAlphaWithStencil(replaceBlend)); + SimulateLogicOpType simulateLogicOpType = SimulateLogicOpShaderTypeIfNeeded(); // 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. - if (gstate_c.textureFullAlpha && gstate.getTextureFunction() != GE_TEXFUNC_REPLACE) + if (gstate_c.textureFullAlpha && gstate.getTextureFunction() != GE_TEXFUNC_REPLACE) { doTextureAlpha = false; + } if (gstate.isTextureMapEnabled()) { id.SetBit(FS_BIT_DO_TEXTURE); @@ -326,7 +323,7 @@ void ComputeFragmentShaderID(FShaderID *id_out, const ComputedPipelineState &pip } // 2 bits. - id.SetBits(FS_BIT_REPLACE_LOGIC_OP_TYPE, 2, ReplaceLogicOpType()); + id.SetBits(FS_BIT_SIMULATE_LOGIC_OP_TYPE, 2, simulateLogicOpType); // If replaceBlend == REPLACE_BLEND_STANDARD (or REPLACE_BLEND_NO) nothing is done, so we kill these bits. if (replaceBlend == REPLACE_BLEND_BLUE_TO_ALPHA) { diff --git a/GPU/Common/ShaderId.h b/GPU/Common/ShaderId.h index bfde30c281..914824d6bc 100644 --- a/GPU/Common/ShaderId.h +++ b/GPU/Common/ShaderId.h @@ -83,7 +83,7 @@ enum FShaderBit : uint8_t { FS_BIT_COLOR_DOUBLE = 23, FS_BIT_STENCIL_TO_ALPHA = 24, // 2 bits FS_BIT_REPLACE_ALPHA_WITH_STENCIL_TYPE = 26, // 4 bits (ReplaceAlphaType) - FS_BIT_REPLACE_LOGIC_OP_TYPE = 30, // 2 bits + FS_BIT_SIMULATE_LOGIC_OP_TYPE = 30, // 2 bits FS_BIT_REPLACE_BLEND = 32, // 3 bits (ReplaceBlendType) FS_BIT_BLENDEQ = 35, // 3 bits FS_BIT_BLENDFUNC_A = 38, // 4 bits diff --git a/GPU/D3D11/StateMappingD3D11.cpp b/GPU/D3D11/StateMappingD3D11.cpp index e42cf795b1..0f2c55f19f 100644 --- a/GPU/D3D11/StateMappingD3D11.cpp +++ b/GPU/D3D11/StateMappingD3D11.cpp @@ -147,10 +147,9 @@ void DrawEngineD3D11::ApplyDrawState(int prim) { } else { keys_.blend.value = 0; + pipelineState_.Convert(draw_->GetDeviceCaps().fragmentShaderInt32Supported); GenericMaskState &maskState = pipelineState_.maskState; GenericBlendState &blendState = pipelineState_.blendState; - ConvertMaskState(maskState, draw_->GetDeviceCaps().fragmentShaderInt32Supported); - ConvertBlendState(blendState, maskState.applyFramebufferRead); if (blendState.applyFramebufferRead || maskState.applyFramebufferRead) { bool fboTexNeedsBind = false; diff --git a/GPU/Directx9/StateMappingDX9.cpp b/GPU/Directx9/StateMappingDX9.cpp index a8ba75f6c7..70c2f7518d 100644 --- a/GPU/Directx9/StateMappingDX9.cpp +++ b/GPU/Directx9/StateMappingDX9.cpp @@ -127,10 +127,9 @@ void DrawEngineDX9::ApplyDrawState(int prim) { } dxstate.colorMask.set(mask); } else { + pipelineState_.Convert(draw_->GetDeviceCaps().fragmentShaderInt32Supported); GenericMaskState &maskState = pipelineState_.maskState; GenericBlendState &blendState = pipelineState_.blendState; - ConvertMaskState(maskState, draw_->GetDeviceCaps().fragmentShaderInt32Supported); - ConvertBlendState(blendState, maskState.applyFramebufferRead); if (blendState.applyFramebufferRead || maskState.applyFramebufferRead) { bool fboTexNeedsBind = false; diff --git a/GPU/GLES/StateMappingGLES.cpp b/GPU/GLES/StateMappingGLES.cpp index d3cbf11f21..f86f35f830 100644 --- a/GPU/GLES/StateMappingGLES.cpp +++ b/GPU/GLES/StateMappingGLES.cpp @@ -143,10 +143,9 @@ void DrawEngineGLES::ApplyDrawState(int prim) { bool alphaMask = gstate.isClearModeAlphaMask(); renderManager->SetNoBlendAndMask((colorMask ? 7 : 0) | (alphaMask ? 8 : 0)); } else { + pipelineState_.Convert(draw_->GetDeviceCaps().fragmentShaderInt32Supported); GenericMaskState &maskState = pipelineState_.maskState; GenericBlendState &blendState = pipelineState_.blendState; - ConvertMaskState(maskState, draw_->GetDeviceCaps().fragmentShaderInt32Supported); - ConvertBlendState(blendState, maskState.applyFramebufferRead); if (blendState.applyFramebufferRead || maskState.applyFramebufferRead) { bool fboTexNeedsBind = false; diff --git a/GPU/Vulkan/StateMappingVulkan.cpp b/GPU/Vulkan/StateMappingVulkan.cpp index b0625b4e72..781b0c990a 100644 --- a/GPU/Vulkan/StateMappingVulkan.cpp +++ b/GPU/Vulkan/StateMappingVulkan.cpp @@ -147,18 +147,9 @@ void DrawEngineVulkan::ConvertStateToVulkanKey(FramebufferManagerVulkan &fbManag bool alphaMask = gstate.isClearModeAlphaMask(); key.colorWriteMask = (colorMask ? (VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT) : 0) | (alphaMask ? VK_COLOR_COMPONENT_A_BIT : 0); } else { - if (gstate_c.Supports(GPU_SUPPORTS_LOGIC_OP) && gstate.isLogicOpEnabled() && gstate.getLogicOp() != GE_LOGIC_COPY) { - key.logicOpEnable = true; - key.logicOp = logicOps[gstate.getLogicOp()]; - } else { - key.logicOpEnable = false; - key.logicOp = VK_LOGIC_OP_CLEAR; - } - + pipelineState_.Convert(draw_->GetDeviceCaps().fragmentShaderInt32Supported); GenericMaskState &maskState = pipelineState_.maskState; GenericBlendState &blendState = pipelineState_.blendState; - ConvertMaskState(maskState, draw_->GetDeviceCaps().fragmentShaderInt32Supported); - ConvertBlendState(blendState, maskState.applyFramebufferRead); if (blendState.applyFramebufferRead || maskState.applyFramebufferRead) { ApplyFramebufferRead(&fboTexNeedsBind_); @@ -218,6 +209,14 @@ void DrawEngineVulkan::ConvertStateToVulkanKey(FramebufferManagerVulkan &fbManag key.srcColor = VK_BLEND_FACTOR_ZERO; key.destColor = VK_BLEND_FACTOR_ONE; } + + if (gstate_c.Supports(GPU_SUPPORTS_LOGIC_OP) && gstate.isLogicOpEnabled() && gstate.getLogicOp() != GE_LOGIC_COPY) { + key.logicOpEnable = true; + key.logicOp = logicOps[gstate.getLogicOp()]; + } else { + key.logicOpEnable = false; + key.logicOp = VK_LOGIC_OP_CLEAR; + } } }