From 256436b594a049ac1c480ef006e9cca6a996b78c Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 16 Jun 2014 23:25:56 -0700 Subject: [PATCH 1/5] Support fixed/fixed blending without a blit. --- GPU/GLES/FragmentShaderGenerator.cpp | 32 ++++++++++++++++++++++------ GPU/GLES/FragmentShaderGenerator.h | 1 + GPU/GLES/ShaderManager.cpp | 8 +++++-- GPU/GLES/StateMapping.cpp | 26 +++++++++++++++++++++- 4 files changed, 57 insertions(+), 10 deletions(-) diff --git a/GPU/GLES/FragmentShaderGenerator.cpp b/GPU/GLES/FragmentShaderGenerator.cpp index da74dc8751..c5a7348652 100644 --- a/GPU/GLES/FragmentShaderGenerator.cpp +++ b/GPU/GLES/FragmentShaderGenerator.cpp @@ -335,6 +335,19 @@ bool ShouldUseShaderBlending() { return false; } +// Doesn't need to be in the shader id, ShouldUseShaderBlending contains all parts. +bool ShouldUseShaderFixedBlending() { + if (!ShouldUseShaderBlending()) { + return false; + } + + if (gstate.getBlendFuncA() == GE_SRCBLEND_FIXA && gstate.getBlendFuncB() == GE_DSTBLEND_FIXB) { + GEBlendMode blendEq = gstate.getBlendEq(); + return blendEq != GE_BLENDMODE_MIN && blendEq != GE_BLENDMODE_MAX && blendEq != GE_BLENDMODE_ABSDIFF; + } + return false; +} + // 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(FragmentShaderID *id) { @@ -349,10 +362,11 @@ void ComputeFragmentShaderID(FragmentShaderID *id) { bool enableAlphaTest = gstate.isAlphaTestEnabled() && !IsAlphaTestTriviallyTrue() && !g_Config.bDisableAlphaTest; bool alphaTestAgainstZero = gstate.getAlphaTestRef() == 0 && gstate.getAlphaTestMask() == 0xFF; bool enableColorTest = gstate.isColorTestEnabled() && !IsColorTestTriviallyTrue(); - bool alphaToColorDoubling = AlphaToColorDoubling(); + bool useShaderBlending = ShouldUseShaderBlending(); + bool alphaToColorDoubling = AlphaToColorDoubling() && !useShaderBlending; bool enableColorDoubling = (gstate.isColorDoublingEnabled() && gstate.isTextureMapEnabled()) || alphaToColorDoubling; // This isn't really correct, but it's a hack to get doubled blend modes to work more correctly. - bool enableAlphaDoubling = !alphaToColorDoubling && CanDoubleSrcBlendMode(); + bool enableAlphaDoubling = !alphaToColorDoubling && !useShaderBlending && CanDoubleSrcBlendMode(); bool doTextureProjection = gstate.getUVGenMode() == GE_TEXMAP_TEXTURE_MATRIX; bool doTextureAlpha = gstate.isTextureAlphaUsed(); ReplaceAlphaType stencilToAlpha = ReplaceAlphaWithStencil(); @@ -409,7 +423,7 @@ void ComputeFragmentShaderID(FragmentShaderID *id) { // 29 - 31 are free. - if (ShouldUseShaderBlending()) { + if (useShaderBlending) { // 12 bits total. id1 |= 1 << 0; id1 |= gstate.getBlendEq() << 1; @@ -500,10 +514,11 @@ void GenerateFragmentShader(char *buffer) { bool enableAlphaTest = gstate.isAlphaTestEnabled() && !IsAlphaTestTriviallyTrue() && !gstate.isModeClear() && !g_Config.bDisableAlphaTest; bool alphaTestAgainstZero = gstate.getAlphaTestRef() == 0 && gstate.getAlphaTestMask() == 0xFF; bool enableColorTest = gstate.isColorTestEnabled() && !IsColorTestTriviallyTrue() && !gstate.isModeClear(); - bool alphaToColorDoubling = AlphaToColorDoubling(); + bool useShaderBlending = ShouldUseShaderBlending(); + bool alphaToColorDoubling = AlphaToColorDoubling() && !useShaderBlending; bool enableColorDoubling = (gstate.isColorDoublingEnabled() && gstate.isTextureMapEnabled()) || alphaToColorDoubling; // This isn't really correct, but it's a hack to get doubled blend modes to work more correctly. - bool enableAlphaDoubling = !alphaToColorDoubling && CanDoubleSrcBlendMode(); + bool enableAlphaDoubling = !alphaToColorDoubling && !useShaderBlending && CanDoubleSrcBlendMode(); bool doTextureProjection = gstate.getUVGenMode() == GE_TEXMAP_TEXTURE_MATRIX; bool doTextureAlpha = gstate.isTextureAlphaUsed(); bool textureAtOffset = gstate_c.curTextureXOffset != 0 || gstate_c.curTextureYOffset != 0; @@ -514,7 +529,7 @@ void GenerateFragmentShader(char *buffer) { if (doTexture) WRITE(p, "uniform sampler2D tex;\n"); - if (!gstate.isModeClear() && ShouldUseShaderBlending()) { + if (!gstate.isModeClear() && useShaderBlending) { if (!gl_extensions.NV_shader_framebuffer_fetch) { if (!gl_extensions.VersionGEThan(3, 0, 0) && !gl_extensions.GLES3) { WRITE(p, "uniform vec2 u_fbotexSize;\n"); @@ -776,7 +791,10 @@ void GenerateFragmentShader(char *buffer) { // WRITE(p, " v.x = v_depth;\n"); } - if (ShouldUseShaderBlending()) { + if (ShouldUseShaderFixedBlending()) { + // Just premultiply by u_blendFixA. + WRITE(p, " v.rgb = v.rgb * u_blendFixA;\n"); + } else if (useShaderBlending) { // If we have NV_shader_framebuffer_fetch / EXT_shader_framebuffer_fetch, we skip the blit. // We can just read the prev value more directly. // TODO: EXT_shader_framebuffer_fetch on iOS 6, possibly others. diff --git a/GPU/GLES/FragmentShaderGenerator.h b/GPU/GLES/FragmentShaderGenerator.h index 2357739d37..c931c5efe5 100644 --- a/GPU/GLES/FragmentShaderGenerator.h +++ b/GPU/GLES/FragmentShaderGenerator.h @@ -63,3 +63,4 @@ bool IsColorTestTriviallyTrue(); StencilValueType ReplaceAlphaWithStencilType(); ReplaceAlphaType ReplaceAlphaWithStencil(); bool ShouldUseShaderBlending(); +bool ShouldUseShaderFixedBlending(); diff --git a/GPU/GLES/ShaderManager.cpp b/GPU/GLES/ShaderManager.cpp index 6feb5e2ac3..e103430b5f 100644 --- a/GPU/GLES/ShaderManager.cpp +++ b/GPU/GLES/ShaderManager.cpp @@ -563,8 +563,12 @@ void LinkedShader::UpdateUniforms(u32 vertType) { #endif if (dirty & DIRTY_SHADERBLEND) { - SetColorUniform3(u_blendFixA, gstate.getFixA()); - SetColorUniform3(u_blendFixB, gstate.getFixB()); + if (u_blendFixA != -1) { + SetColorUniform3(u_blendFixA, gstate.getFixA()); + } + if (u_blendFixB != -1) { + SetColorUniform3(u_blendFixB, gstate.getFixB()); + } const float fbotexSize[2] = { 1.0f / (float)gstate_c.curRTRenderWidth, diff --git a/GPU/GLES/StateMapping.cpp b/GPU/GLES/StateMapping.cpp index 245d8d1c43..fa5034b09f 100644 --- a/GPU/GLES/StateMapping.cpp +++ b/GPU/GLES/StateMapping.cpp @@ -164,6 +164,29 @@ static inline bool blendColorSimilar(const Vec3f &a, const Vec3f &b, float margi } bool TransformDrawEngine::ApplyShaderBlending() { + if (ShouldUseShaderFixedBlending()) { + // If both sides are fixed, we can do this without a blit but still in the shader. + Vec3f fixB = Vec3f::FromRGB(gstate.getFixB()); + GEBlendMode blendFuncEq = gstate.getBlendEq(); + + // Okay, so we'll use src * 1.0 + dst * fixB, and then premultiply in the shader. + const float blendColor[4] = {fixB.x, fixB.y, fixB.z, 1.0f}; + glstate.blendColor.set(blendColor); + + ReplaceAlphaType replaceAlphaWithStencil = ReplaceAlphaWithStencil(); + if (replaceAlphaWithStencil != REPLACE_ALPHA_NO) { + glstate.blendFuncSeparate.set(GL_ONE, GL_CONSTANT_COLOR, GL_ONE, GL_ZERO); + } else { + glstate.blendFuncSeparate.set(GL_ONE, GL_CONSTANT_COLOR, GL_ZERO, GL_ONE); + } + + // Min/max/absdiff are not possible here. + glstate.blendEquation.set(eqLookup[blendFuncEq]); + + shaderManager_->DirtyUniform(DIRTY_SHADERBLEND); + return false; + } + bool skipBlit = false; if (gl_extensions.NV_shader_framebuffer_fetch) { return true; @@ -231,7 +254,8 @@ void TransformDrawEngine::ApplyDrawState(int prim) { } glstate.blend.set(wantBlend); - if (wantBlend) { + // For ShouldUseShaderFixedBlending(), we already set the params. + if (wantBlend && !ShouldUseShaderFixedBlending()) { // This can't be done exactly as there are several PSP blend modes that are impossible to do on OpenGL ES 2.0, and some even on regular OpenGL for desktop. // HOWEVER - we should be able to approximate the 2x modes in the shader, although they will clip wrongly. From 143ad92afd538eb5372ee6a4fce43142c7cbf366 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 16 Jun 2014 23:33:15 -0700 Subject: [PATCH 2/5] Convert blending to doubling in more cases. We're not doubling the alpha, so all these cases are safe regardless of the alpha value. --- GPU/GLES/FragmentShaderGenerator.cpp | 37 ++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/GPU/GLES/FragmentShaderGenerator.cpp b/GPU/GLES/FragmentShaderGenerator.cpp index c5a7348652..4d935bce3a 100644 --- a/GPU/GLES/FragmentShaderGenerator.cpp +++ b/GPU/GLES/FragmentShaderGenerator.cpp @@ -236,17 +236,38 @@ static bool AlphaToColorDoubling() { if (!gstate.isAlphaBlendEnabled()) { return false; } - // 2x alpha in the source function and full alpha = source color doubling. - // If we see this, we don't really need to care about the dest alpha function - sure we can't handle - // the doubling dest ones, but there's nothing we can do about that. - if (gstate.getBlendFuncA() != GE_SRCBLEND_DOUBLESRCALPHA) { + // 2x alpha in the source function and not in the dest = source color doubling. + switch (gstate.getBlendFuncA()) { + case GE_SRCBLEND_DOUBLESRCALPHA: + case GE_SRCBLEND_DOUBLEINVSRCALPHA: + break; + + case GE_SRCBLEND_DOUBLEDSTALPHA: + case GE_SRCBLEND_DOUBLEINVDSTALPHA: + // Even dest alpha is safe, since we're moving the * 2.0 into the src color. + break; + + default: return false; } - if (gstate.getBlendFuncB() == GE_DSTBLEND_INVSRCALPHA) { - // If it's 1.0 or 0.0, then we can still color double (since 0.0 will blend out anyway.) - return (gstate_c.vertexFullAlpha && (gstate_c.textureSimpleAlpha || !gstate.isTextureAlphaUsed())); + switch (gstate.getBlendFuncB()) { + case GE_DSTBLEND_SRCCOLOR: + case GE_DSTBLEND_INVSRCCOLOR: + // Can't double, we need the source color to be correct. + return false; + + case GE_DSTBLEND_DOUBLESRCALPHA: + case GE_DSTBLEND_DOUBLEINVSRCALPHA: + case GE_DSTBLEND_DOUBLEDSTALPHA: + case GE_DSTBLEND_DOUBLEINVDSTALPHA: + // Won't do the trick, would be better to double both sides. + return false; + + default: + // In all other cases, we're pre-multiplying the src side by 2. + // For example, src * (2.0 * a) + dst * fixB, we're just moving the 2.0. + return true; } - return (gstate_c.vertexFullAlpha && (gstate_c.textureFullAlpha || !gstate.isTextureAlphaUsed())); } static bool CanDoubleSrcBlendMode() { From 17f5acd8b0e77978ae7b20e2201d174497362d36 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 16 Jun 2014 23:34:51 -0700 Subject: [PATCH 3/5] Support more src alpha doubling modes. These prevent the need for more blits. --- GPU/GLES/FragmentShaderGenerator.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/GPU/GLES/FragmentShaderGenerator.cpp b/GPU/GLES/FragmentShaderGenerator.cpp index 4d935bce3a..92ff4c4fa7 100644 --- a/GPU/GLES/FragmentShaderGenerator.cpp +++ b/GPU/GLES/FragmentShaderGenerator.cpp @@ -277,18 +277,17 @@ static bool CanDoubleSrcBlendMode() { int funcA = gstate.getBlendFuncA(); int funcB = gstate.getBlendFuncB(); - if (funcA != GE_SRCBLEND_DOUBLESRCALPHA) { + if (funcA != GE_SRCBLEND_DOUBLESRCALPHA && funcA != GE_SRCBLEND_DOUBLEINVSRCALPHA) { funcB = funcA; funcA = gstate.getBlendFuncB(); } - if (funcA != GE_SRCBLEND_DOUBLESRCALPHA) { + if (funcA != GE_SRCBLEND_DOUBLESRCALPHA && funcA != GE_SRCBLEND_DOUBLEINVSRCALPHA) { return false; } // One side should be doubled. Let's check the other side. - // LittleBigPlanet, for example, uses 2.0 * src, 1.0 - src, which can't double. - // Persona 2 uses the same function, which is the reason for its darkness. It only ever passes - // 1.0 as src alpha though, so in effect it's a color doubling. + // LittleBigPlanet and Persona 2, for example, uses 2.0 * src.a, 1.0 - src.a, which can't double. + // In that case, we can double the src rgb instead. switch (funcB) { case GE_DSTBLEND_SRCALPHA: case GE_DSTBLEND_INVSRCALPHA: From 640c58a892bd0472d943ea6f34c018ec82eca8fd Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 16 Jun 2014 23:47:21 -0700 Subject: [PATCH 4/5] Reorganize alphablend state mapping logic. --- GPU/GLES/StateMapping.cpp | 390 ++++++++++++++++++----------------- GPU/GLES/TransformPipeline.h | 3 +- 2 files changed, 203 insertions(+), 190 deletions(-) diff --git a/GPU/GLES/StateMapping.cpp b/GPU/GLES/StateMapping.cpp index fa5034b09f..b9b4620286 100644 --- a/GPU/GLES/StateMapping.cpp +++ b/GPU/GLES/StateMapping.cpp @@ -164,29 +164,6 @@ static inline bool blendColorSimilar(const Vec3f &a, const Vec3f &b, float margi } bool TransformDrawEngine::ApplyShaderBlending() { - if (ShouldUseShaderFixedBlending()) { - // If both sides are fixed, we can do this without a blit but still in the shader. - Vec3f fixB = Vec3f::FromRGB(gstate.getFixB()); - GEBlendMode blendFuncEq = gstate.getBlendEq(); - - // Okay, so we'll use src * 1.0 + dst * fixB, and then premultiply in the shader. - const float blendColor[4] = {fixB.x, fixB.y, fixB.z, 1.0f}; - glstate.blendColor.set(blendColor); - - ReplaceAlphaType replaceAlphaWithStencil = ReplaceAlphaWithStencil(); - if (replaceAlphaWithStencil != REPLACE_ALPHA_NO) { - glstate.blendFuncSeparate.set(GL_ONE, GL_CONSTANT_COLOR, GL_ONE, GL_ZERO); - } else { - glstate.blendFuncSeparate.set(GL_ONE, GL_CONSTANT_COLOR, GL_ZERO, GL_ONE); - } - - // Min/max/absdiff are not possible here. - glstate.blendEquation.set(eqLookup[blendFuncEq]); - - shaderManager_->DirtyUniform(DIRTY_SHADERBLEND); - return false; - } - bool skipBlit = false; if (gl_extensions.NV_shader_framebuffer_fetch) { return true; @@ -222,11 +199,203 @@ bool TransformDrawEngine::ApplyShaderBlending() { return true; } -void TransformDrawEngine::ResetShaderBlending() { - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, 0); - glActiveTexture(GL_TEXTURE0); - fboTexBound_ = false; +inline void TransformDrawEngine::ResetShaderBlending() { + if (fboTexBound_) { + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE0); + fboTexBound_ = false; + } +} + +void TransformDrawEngine::ApplyBlendState() { + // Blending is a bit complex to emulate. This is due to several reasons: + // + // * Doubled blend modes (src, dst, inversed) aren't supported in OpenGL. + // If possible, we double the src color or src alpha in the shader to account for these. + // These may clip incorrectly, but they're close. + // * OpenGL only has one arbitrary fixed color. We premultiply the other in the shader. + // * The written output alpha should actually be the stencil value. Alpha is not written. + // + // If we can't apply blending, we make a copy of the framebuffer and do it manually. + + GEBlendMode blendFuncEq = gstate.getBlendEq(); + + if (ShouldUseShaderBlending()) { + if (ShouldUseShaderFixedBlending()) { + // If both sides are fixed, we can do this without a blit but still in the shader. + Vec3f fixB = Vec3f::FromRGB(gstate.getFixB()); + + // Okay, so we'll use src * 1.0 + dst * fixB, and then premultiply in the shader. + const float blendColor[4] = {fixB.x, fixB.y, fixB.z, 1.0f}; + glstate.blend.enable(); + glstate.blendColor.set(blendColor); + + ReplaceAlphaType replaceAlphaWithStencil = ReplaceAlphaWithStencil(); + if (replaceAlphaWithStencil != REPLACE_ALPHA_NO) { + glstate.blendFuncSeparate.set(GL_ONE, GL_CONSTANT_COLOR, GL_ONE, GL_ZERO); + } else { + glstate.blendFuncSeparate.set(GL_ONE, GL_CONSTANT_COLOR, GL_ZERO, GL_ONE); + } + + // Min/max/absdiff are not possible here. + glstate.blendEquation.set(eqLookup[blendFuncEq]); + + shaderManager_->DirtyUniform(DIRTY_SHADERBLEND); + ResetShaderBlending(); + return; + } else if (ApplyShaderBlending()) { + // None of the below logic is interesting, we're gonna do it entirely in the shader. + glstate.blend.disable(); + return; + } + } + ResetShaderBlending(); + + glstate.blend.enable(); + + int blendFuncA = gstate.getBlendFuncA(); + int blendFuncB = gstate.getBlendFuncB(); + if (blendFuncA > GE_SRCBLEND_FIXA) blendFuncA = GE_SRCBLEND_FIXA; + if (blendFuncB > GE_DSTBLEND_FIXB) blendFuncB = GE_DSTBLEND_FIXB; + + float constantAlpha = 1.0f; + ReplaceAlphaType replaceAlphaWithStencil = ReplaceAlphaWithStencil(); + if (gstate.isStencilTestEnabled() && replaceAlphaWithStencil == REPLACE_ALPHA_NO) { + if (ReplaceAlphaWithStencilType() == STENCIL_VALUE_UNIFORM) { + constantAlpha = (float) gstate.getStencilTestRef() * (1.0f / 255.0f); + } + } + + // Shortcut by using GL_ONE where possible, no need to set blendcolor + GLuint glBlendFuncA = blendFuncA == GE_SRCBLEND_FIXA ? blendColor2Func(gstate.getFixA()) : aLookup[blendFuncA]; + GLuint glBlendFuncB = blendFuncB == GE_DSTBLEND_FIXB ? blendColor2Func(gstate.getFixB()) : bLookup[blendFuncB]; + + if (replaceAlphaWithStencil == REPLACE_ALPHA_DUALSOURCE) { + glBlendFuncA = toDualSource(glBlendFuncA); + glBlendFuncB = toDualSource(glBlendFuncB); + } + + if (blendFuncA == GE_SRCBLEND_FIXA || blendFuncB == GE_DSTBLEND_FIXB) { + Vec3f fixA = Vec3f::FromRGB(gstate.getFixA()); + Vec3f fixB = Vec3f::FromRGB(gstate.getFixB()); + if (glBlendFuncA == GL_INVALID_ENUM && glBlendFuncB != GL_INVALID_ENUM) { + // Can use blendcolor trivially. + const float blendColor[4] = {fixA.x, fixA.y, fixA.z, constantAlpha}; + glstate.blendColor.set(blendColor); + glBlendFuncA = GL_CONSTANT_COLOR; + } else if (glBlendFuncA != GL_INVALID_ENUM && glBlendFuncB == GL_INVALID_ENUM) { + // Can use blendcolor trivially. + const float blendColor[4] = {fixB.x, fixB.y, fixB.z, constantAlpha}; + glstate.blendColor.set(blendColor); + glBlendFuncB = GL_CONSTANT_COLOR; + } else if (glBlendFuncA == GL_INVALID_ENUM && glBlendFuncB == GL_INVALID_ENUM) { + if (blendColorSimilar(fixA, Vec3f::AssignToAll(constantAlpha) - fixB)) { + glBlendFuncA = GL_CONSTANT_COLOR; + glBlendFuncB = GL_ONE_MINUS_CONSTANT_COLOR; + const float blendColor[4] = {fixA.x, fixA.y, fixA.z, constantAlpha}; + glstate.blendColor.set(blendColor); + } else if (blendColorSimilar(fixA, fixB)) { + glBlendFuncA = GL_CONSTANT_COLOR; + glBlendFuncB = GL_CONSTANT_COLOR; + const float blendColor[4] = {fixA.x, fixA.y, fixA.z, constantAlpha}; + glstate.blendColor.set(blendColor); + } else { + static bool didReportBlend = false; + if (!didReportBlend) + Reporting::ReportMessage("ERROR INVALID blendcolorstate: FixA=%06x FixB=%06x FuncA=%i FuncB=%i", gstate.getFixA(), gstate.getFixB(), gstate.getBlendFuncA(), gstate.getBlendFuncB()); + didReportBlend = true; + + DEBUG_LOG(G3D, "ERROR INVALID blendcolorstate: FixA=%06x FixB=%06x FuncA=%i FuncB=%i", gstate.getFixA(), gstate.getFixB(), gstate.getBlendFuncA(), gstate.getBlendFuncB()); + // Let's approximate, at least. Close is better than totally off. + const bool nearZeroA = blendColorSimilar(fixA, Vec3f::AssignToAll(0.0f), 0.25f); + const bool nearZeroB = blendColorSimilar(fixB, Vec3f::AssignToAll(0.0f), 0.25f); + if (nearZeroA || blendColorSimilar(fixA, Vec3f::AssignToAll(1.0f), 0.25f)) { + glBlendFuncA = nearZeroA ? GL_ZERO : GL_ONE; + glBlendFuncB = GL_CONSTANT_COLOR; + const float blendColor[4] = {fixB.x, fixB.y, fixB.z, constantAlpha}; + glstate.blendColor.set(blendColor); + // We need to pick something. Let's go with A as the fixed color. + } else { + glBlendFuncA = GL_CONSTANT_COLOR; + glBlendFuncB = nearZeroB ? GL_ZERO : GL_ONE; + const float blendColor[4] = {fixA.x, fixA.y, fixA.z, constantAlpha}; + glstate.blendColor.set(blendColor); + } + } + } else { + // We optimized both, but that's probably not necessary, so let's pick one to be constant. + // For now let's just pick whichever was fixed instead of checking error. + if (blendFuncA == GE_SRCBLEND_FIXA) { + glBlendFuncA = GL_CONSTANT_COLOR; + const float blendColor[4] = {fixA.x, fixA.y, fixA.z, constantAlpha}; + glstate.blendColor.set(blendColor); + } else { + glBlendFuncB = GL_CONSTANT_COLOR; + const float blendColor[4] = {fixB.x, fixB.y, fixB.z, constantAlpha}; + glstate.blendColor.set(blendColor); + } + } + } else if (constantAlpha < 1.0f) { + const float blendColor[4] = {1.0f, 1.0f, 1.0f, constantAlpha}; + glstate.blendColor.set(blendColor); + } + + // Some Android devices (especially Mali, it seems) composite badly if there's alpha in the backbuffer. + // So in non-buffered rendering, we will simply consider the dest alpha to be zero in blending equations. +#ifdef ANDROID + if (g_Config.iRenderingMode == FB_NON_BUFFERED_MODE) { + if (glBlendFuncA == GL_DST_ALPHA) glBlendFuncA = GL_ZERO; + if (glBlendFuncB == GL_DST_ALPHA) glBlendFuncB = GL_ZERO; + if (glBlendFuncA == GL_ONE_MINUS_DST_ALPHA) glBlendFuncA = GL_ONE; + if (glBlendFuncB == GL_ONE_MINUS_DST_ALPHA) glBlendFuncB = GL_ONE; + } +#endif + + // At this point, through all paths above, glBlendFuncA and glBlendFuncB will be set right somehow. + + // 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 lacking a better option, + // the only value we can set alpha to here without multipass and dual source alpha is zero (by setting + // the factors to zero). So let's do that. + if (replaceAlphaWithStencil != REPLACE_ALPHA_NO) { + // Let the fragment shader take care of it. + glstate.blendFuncSeparate.set(glBlendFuncA, glBlendFuncB, GL_ONE, GL_ZERO); + } else if (gstate.isStencilTestEnabled()) { + switch (ReplaceAlphaWithStencilType()) { + case STENCIL_VALUE_KEEP: + glstate.blendFuncSeparate.set(glBlendFuncA, glBlendFuncB, GL_ZERO, GL_ONE); + break; + case STENCIL_VALUE_ONE: + // This won't give one but it's our best shot... + glstate.blendFuncSeparate.set(glBlendFuncA, glBlendFuncB, GL_ONE, GL_ONE); + break; + case STENCIL_VALUE_ZERO: + glstate.blendFuncSeparate.set(glBlendFuncA, glBlendFuncB, GL_ZERO, GL_ZERO); + break; + case STENCIL_VALUE_UNIFORM: + // This won't give a correct value (it multiplies) but it may be better than random values. + glstate.blendFuncSeparate.set(glBlendFuncA, glBlendFuncB, GL_CONSTANT_ALPHA, GL_ZERO); + break; + case STENCIL_VALUE_UNKNOWN: + // For now, let's err at zero. This is INVERT or INCR/DECR. + glstate.blendFuncSeparate.set(glBlendFuncA, glBlendFuncB, GL_ZERO, GL_ZERO); + break; + } + } else { + // Retain the existing value when stencil testing is off. + glstate.blendFuncSeparate.set(glBlendFuncA, glBlendFuncB, GL_ZERO, GL_ONE); + } + + if (blendFuncEq == GE_BLENDMODE_ABSDIFF) { + WARN_LOG_REPORT_ONCE(blendAbsdiff, G3D, "Unsupported absdiff blend mode"); + } + + if (((blendFuncEq >= GE_BLENDMODE_MIN) && gl_extensions.EXT_blend_minmax) || gl_extensions.GLES3) { + glstate.blendEquation.set(eqLookup[blendFuncEq]); + } else { + glstate.blendEquation.set(eqLookupNoMinMax[blendFuncEq]); + } } void TransformDrawEngine::ApplyDrawState(int prim) { @@ -243,170 +412,13 @@ void TransformDrawEngine::ApplyDrawState(int prim) { } // Set blend - unless we need to do it in the shader. - bool wantBlend = !gstate.isModeClear() && gstate.isAlphaBlendEnabled(); - if (wantBlend && ShouldUseShaderBlending()) { - if (ApplyShaderBlending()) { - // None of the below logic is interesting, we're gonna do it entirely in the shader. - wantBlend = false; - } - } else if (fboTexBound_) { + if (!gstate.isModeClear() && gstate.isAlphaBlendEnabled()) { + ApplyBlendState(); + } else { + glstate.blend.disable(); ResetShaderBlending(); } - glstate.blend.set(wantBlend); - // For ShouldUseShaderFixedBlending(), we already set the params. - if (wantBlend && !ShouldUseShaderFixedBlending()) { - // This can't be done exactly as there are several PSP blend modes that are impossible to do on OpenGL ES 2.0, and some even on regular OpenGL for desktop. - // HOWEVER - we should be able to approximate the 2x modes in the shader, although they will clip wrongly. - - // Examples of seen unimplementable blend states: - // Mortal Kombat Unchained: FixA=0000ff FixB=000080 FuncA=10 FuncB=10 - - int blendFuncA = gstate.getBlendFuncA(); - int blendFuncB = gstate.getBlendFuncB(); - GEBlendMode blendFuncEq = gstate.getBlendEq(); - if (blendFuncA > GE_SRCBLEND_FIXA) blendFuncA = GE_SRCBLEND_FIXA; - if (blendFuncB > GE_DSTBLEND_FIXB) blendFuncB = GE_DSTBLEND_FIXB; - - float constantAlpha = 1.0f; - ReplaceAlphaType replaceAlphaWithStencil = ReplaceAlphaWithStencil(); - if (gstate.isStencilTestEnabled() && replaceAlphaWithStencil == REPLACE_ALPHA_NO) { - if (ReplaceAlphaWithStencilType() == STENCIL_VALUE_UNIFORM) { - constantAlpha = (float) gstate.getStencilTestRef() * (1.0f / 255.0f); - } - } - - // Shortcut by using GL_ONE where possible, no need to set blendcolor - GLuint glBlendFuncA = blendFuncA == GE_SRCBLEND_FIXA ? blendColor2Func(gstate.getFixA()) : aLookup[blendFuncA]; - GLuint glBlendFuncB = blendFuncB == GE_DSTBLEND_FIXB ? blendColor2Func(gstate.getFixB()) : bLookup[blendFuncB]; - - if (replaceAlphaWithStencil == REPLACE_ALPHA_DUALSOURCE) { - glBlendFuncA = toDualSource(glBlendFuncA); - glBlendFuncB = toDualSource(glBlendFuncB); - } - - if (blendFuncA == GE_SRCBLEND_FIXA || blendFuncB == GE_DSTBLEND_FIXB) { - Vec3f fixA = Vec3f::FromRGB(gstate.getFixA()); - Vec3f fixB = Vec3f::FromRGB(gstate.getFixB()); - if (glBlendFuncA == GL_INVALID_ENUM && glBlendFuncB != GL_INVALID_ENUM) { - // Can use blendcolor trivially. - const float blendColor[4] = {fixA.x, fixA.y, fixA.z, constantAlpha}; - glstate.blendColor.set(blendColor); - glBlendFuncA = GL_CONSTANT_COLOR; - } else if (glBlendFuncA != GL_INVALID_ENUM && glBlendFuncB == GL_INVALID_ENUM) { - // Can use blendcolor trivially. - const float blendColor[4] = {fixB.x, fixB.y, fixB.z, constantAlpha}; - glstate.blendColor.set(blendColor); - glBlendFuncB = GL_CONSTANT_COLOR; - } else if (glBlendFuncA == GL_INVALID_ENUM && glBlendFuncB == GL_INVALID_ENUM) { - if (blendColorSimilar(fixA, Vec3f::AssignToAll(constantAlpha) - fixB)) { - glBlendFuncA = GL_CONSTANT_COLOR; - glBlendFuncB = GL_ONE_MINUS_CONSTANT_COLOR; - const float blendColor[4] = {fixA.x, fixA.y, fixA.z, constantAlpha}; - glstate.blendColor.set(blendColor); - } else if (blendColorSimilar(fixA, fixB)) { - glBlendFuncA = GL_CONSTANT_COLOR; - glBlendFuncB = GL_CONSTANT_COLOR; - const float blendColor[4] = {fixA.x, fixA.y, fixA.z, constantAlpha}; - glstate.blendColor.set(blendColor); - } else { - static bool didReportBlend = false; - if (!didReportBlend) - Reporting::ReportMessage("ERROR INVALID blendcolorstate: FixA=%06x FixB=%06x FuncA=%i FuncB=%i", gstate.getFixA(), gstate.getFixB(), gstate.getBlendFuncA(), gstate.getBlendFuncB()); - didReportBlend = true; - - DEBUG_LOG(G3D, "ERROR INVALID blendcolorstate: FixA=%06x FixB=%06x FuncA=%i FuncB=%i", gstate.getFixA(), gstate.getFixB(), gstate.getBlendFuncA(), gstate.getBlendFuncB()); - // Let's approximate, at least. Close is better than totally off. - const bool nearZeroA = blendColorSimilar(fixA, Vec3f::AssignToAll(0.0f), 0.25f); - const bool nearZeroB = blendColorSimilar(fixB, Vec3f::AssignToAll(0.0f), 0.25f); - if (nearZeroA || blendColorSimilar(fixA, Vec3f::AssignToAll(1.0f), 0.25f)) { - glBlendFuncA = nearZeroA ? GL_ZERO : GL_ONE; - glBlendFuncB = GL_CONSTANT_COLOR; - const float blendColor[4] = {fixB.x, fixB.y, fixB.z, constantAlpha}; - glstate.blendColor.set(blendColor); - // We need to pick something. Let's go with A as the fixed color. - } else { - glBlendFuncA = GL_CONSTANT_COLOR; - glBlendFuncB = nearZeroB ? GL_ZERO : GL_ONE; - const float blendColor[4] = {fixA.x, fixA.y, fixA.z, constantAlpha}; - glstate.blendColor.set(blendColor); - } - } - } else { - // We optimized both, but that's probably not necessary, so let's pick one to be constant. - // For now let's just pick whichever was fixed instead of checking error. - if (blendFuncA == GE_SRCBLEND_FIXA) { - glBlendFuncA = GL_CONSTANT_COLOR; - const float blendColor[4] = {fixA.x, fixA.y, fixA.z, constantAlpha}; - glstate.blendColor.set(blendColor); - } else { - glBlendFuncB = GL_CONSTANT_COLOR; - const float blendColor[4] = {fixB.x, fixB.y, fixB.z, constantAlpha}; - glstate.blendColor.set(blendColor); - } - } - } else if (constantAlpha < 1.0f) { - const float blendColor[4] = {1.0f, 1.0f, 1.0f, constantAlpha}; - glstate.blendColor.set(blendColor); - } - - // Some Android devices (especially Mali, it seems) composite badly if there's alpha in the backbuffer. - // So in non-buffered rendering, we will simply consider the dest alpha to be zero in blending equations. -#ifdef ANDROID - if (g_Config.iRenderingMode == FB_NON_BUFFERED_MODE) { - if (glBlendFuncA == GL_DST_ALPHA) glBlendFuncA = GL_ZERO; - if (glBlendFuncB == GL_DST_ALPHA) glBlendFuncB = GL_ZERO; - if (glBlendFuncA == GL_ONE_MINUS_DST_ALPHA) glBlendFuncA = GL_ONE; - if (glBlendFuncB == GL_ONE_MINUS_DST_ALPHA) glBlendFuncB = GL_ONE; - } -#endif - - // At this point, through all paths above, glBlendFuncA and glBlendFuncB will be set right somehow. - - // 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 lacking a better option, - // the only value we can set alpha to here without multipass and dual source alpha is zero (by setting - // the factors to zero). So let's do that. - if (replaceAlphaWithStencil != REPLACE_ALPHA_NO) { - // Let the fragment shader take care of it. - glstate.blendFuncSeparate.set(glBlendFuncA, glBlendFuncB, GL_ONE, GL_ZERO); - } else if (gstate.isStencilTestEnabled()) { - switch (ReplaceAlphaWithStencilType()) { - case STENCIL_VALUE_KEEP: - glstate.blendFuncSeparate.set(glBlendFuncA, glBlendFuncB, GL_ZERO, GL_ONE); - break; - case STENCIL_VALUE_ONE: - // This won't give one but it's our best shot... - glstate.blendFuncSeparate.set(glBlendFuncA, glBlendFuncB, GL_ONE, GL_ONE); - break; - case STENCIL_VALUE_ZERO: - glstate.blendFuncSeparate.set(glBlendFuncA, glBlendFuncB, GL_ZERO, GL_ZERO); - break; - case STENCIL_VALUE_UNIFORM: - // This won't give a correct value (it multiplies) but it may be better than random values. - glstate.blendFuncSeparate.set(glBlendFuncA, glBlendFuncB, GL_CONSTANT_ALPHA, GL_ZERO); - break; - case STENCIL_VALUE_UNKNOWN: - // For now, let's err at zero. This is INVERT or INCR/DECR. - glstate.blendFuncSeparate.set(glBlendFuncA, glBlendFuncB, GL_ZERO, GL_ZERO); - break; - } - } else { - // Retain the existing value when stencil testing is off. - glstate.blendFuncSeparate.set(glBlendFuncA, glBlendFuncB, GL_ZERO, GL_ONE); - } - - if (blendFuncEq == GE_BLENDMODE_ABSDIFF) { - WARN_LOG_REPORT_ONCE(blendAbsdiff, G3D, "Unsupported absdiff blend mode"); - } - - if (((blendFuncEq >= GE_BLENDMODE_MIN) && gl_extensions.EXT_blend_minmax) || gl_extensions.GLES3) { - glstate.blendEquation.set(eqLookup[blendFuncEq]); - } else { - glstate.blendEquation.set(eqLookupNoMinMax[blendFuncEq]); - } - } - bool alwaysDepthWrite = g_Config.bAlwaysDepthWrite; bool enableStencilTest = !g_Config.bDisableStencilTest; diff --git a/GPU/GLES/TransformPipeline.h b/GPU/GLES/TransformPipeline.h index 1ec8aa455f..62851d117a 100644 --- a/GPU/GLES/TransformPipeline.h +++ b/GPU/GLES/TransformPipeline.h @@ -174,8 +174,9 @@ private: void DoFlush(); void SoftwareTransformAndDraw(int prim, u8 *decoded, LinkedShader *program, int vertexCount, u32 vertexType, void *inds, int indexType, const DecVtxFormat &decVtxFormat, int maxIndex); void ApplyDrawState(int prim); + void ApplyBlendState(); bool ApplyShaderBlending(); - void ResetShaderBlending(); + inline void ResetShaderBlending(); bool IsReallyAClear(int numVerts) const; GLuint AllocateBuffer(); void FreeBuffer(GLuint buf); From 6e751f8092d6b8cb5ebe1f51e941b5b59c541569 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 16 Jun 2014 23:52:08 -0700 Subject: [PATCH 5/5] Use shader blend for min/max when unsupported. --- GPU/GLES/FragmentShaderGenerator.cpp | 11 ++++++++++- GPU/GLES/StateMapping.cpp | 6 +----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/GPU/GLES/FragmentShaderGenerator.cpp b/GPU/GLES/FragmentShaderGenerator.cpp index 92ff4c4fa7..16af7be7f9 100644 --- a/GPU/GLES/FragmentShaderGenerator.cpp +++ b/GPU/GLES/FragmentShaderGenerator.cpp @@ -311,8 +311,17 @@ bool ShouldUseShaderBlending() { GEBlendDstFactor funcB = gstate.getBlendFuncB(); GEBlendMode eq = gstate.getBlendEq(); - if (eq == GE_BLENDMODE_ABSDIFF) { + switch (eq) { + case GE_BLENDMODE_ABSDIFF: return true; + + case GE_BLENDMODE_MIN: + case GE_BLENDMODE_MAX: + // These don't use the factors. + return !gl_extensions.EXT_blend_minmax && !gl_extensions.GLES3; + + default: + break; } // This normally involves a blit, so try to skip it. diff --git a/GPU/GLES/StateMapping.cpp b/GPU/GLES/StateMapping.cpp index b9b4620286..ddd40b6d53 100644 --- a/GPU/GLES/StateMapping.cpp +++ b/GPU/GLES/StateMapping.cpp @@ -387,11 +387,7 @@ void TransformDrawEngine::ApplyBlendState() { glstate.blendFuncSeparate.set(glBlendFuncA, glBlendFuncB, GL_ZERO, GL_ONE); } - if (blendFuncEq == GE_BLENDMODE_ABSDIFF) { - WARN_LOG_REPORT_ONCE(blendAbsdiff, G3D, "Unsupported absdiff blend mode"); - } - - if (((blendFuncEq >= GE_BLENDMODE_MIN) && gl_extensions.EXT_blend_minmax) || gl_extensions.GLES3) { + if (gl_extensions.EXT_blend_minmax || gl_extensions.GLES3) { glstate.blendEquation.set(eqLookup[blendFuncEq]); } else { glstate.blendEquation.set(eqLookupNoMinMax[blendFuncEq]);