diff options
author | 2015-05-06 13:40:21 -0700 | |
---|---|---|
committer | 2015-05-06 13:40:21 -0700 | |
commit | 8917d62ef4d9bde9ec4f879dc42b309b03a0ad98 (patch) | |
tree | 155b2ec06eecc8aadee64f9152b6ea95515778ce /src/gpu/effects | |
parent | e0cab96599764f7611de015f558a6b22162c3eda (diff) |
Implement support for KHR_blend_equation_advanced
Uses KHR(or NV)_blend_equation_advanced to implement custom Xfer modes
in hardware.
BUG=skia:
Review URL: https://codereview.chromium.org/1037123003
Diffstat (limited to 'src/gpu/effects')
-rw-r--r-- | src/gpu/effects/GrCustomXfermode.cpp | 230 | ||||
-rw-r--r-- | src/gpu/effects/GrCustomXfermodePriv.h | 4 |
2 files changed, 218 insertions, 16 deletions
diff --git a/src/gpu/effects/GrCustomXfermode.cpp b/src/gpu/effects/GrCustomXfermode.cpp index 2f8a970290..b15e2c2b9a 100644 --- a/src/gpu/effects/GrCustomXfermode.cpp +++ b/src/gpu/effects/GrCustomXfermode.cpp @@ -17,6 +17,7 @@ #include "GrTextureAccess.h" #include "SkXfermode.h" #include "gl/GrGLCaps.h" +#include "gl/GrGLGpu.h" #include "gl/GrGLProcessor.h" #include "gl/GrGLProgramDataManager.h" #include "gl/builders/GrGLProgramBuilder.h" @@ -29,6 +30,27 @@ bool GrCustomXfermode::IsSupportedMode(SkXfermode::Mode mode) { // Static helpers /////////////////////////////////////////////////////////////////////////////// +static GrBlendEquation hw_blend_equation(SkXfermode::Mode mode) { + enum { kOffset = kOverlay_GrBlendEquation - SkXfermode::kOverlay_Mode }; + return static_cast<GrBlendEquation>(mode + kOffset); + + GR_STATIC_ASSERT(kOverlay_GrBlendEquation == SkXfermode::kOverlay_Mode + kOffset); + GR_STATIC_ASSERT(kDarken_GrBlendEquation == SkXfermode::kDarken_Mode + kOffset); + GR_STATIC_ASSERT(kLighten_GrBlendEquation == SkXfermode::kLighten_Mode + kOffset); + GR_STATIC_ASSERT(kColorDodge_GrBlendEquation == SkXfermode::kColorDodge_Mode + kOffset); + GR_STATIC_ASSERT(kColorBurn_GrBlendEquation == SkXfermode::kColorBurn_Mode + kOffset); + GR_STATIC_ASSERT(kHardLight_GrBlendEquation == SkXfermode::kHardLight_Mode + kOffset); + GR_STATIC_ASSERT(kSoftLight_GrBlendEquation == SkXfermode::kSoftLight_Mode + kOffset); + GR_STATIC_ASSERT(kDifference_GrBlendEquation == SkXfermode::kDifference_Mode + kOffset); + GR_STATIC_ASSERT(kExclusion_GrBlendEquation == SkXfermode::kExclusion_Mode + kOffset); + GR_STATIC_ASSERT(kMultiply_GrBlendEquation == SkXfermode::kMultiply_Mode + kOffset); + GR_STATIC_ASSERT(kHSLHue_GrBlendEquation == SkXfermode::kHue_Mode + kOffset); + GR_STATIC_ASSERT(kHSLSaturation_GrBlendEquation == SkXfermode::kSaturation_Mode + kOffset); + GR_STATIC_ASSERT(kHSLColor_GrBlendEquation == SkXfermode::kColor_Mode + kOffset); + GR_STATIC_ASSERT(kHSLLuminosity_GrBlendEquation == SkXfermode::kLuminosity_Mode + kOffset); + GR_STATIC_ASSERT(kTotalGrBlendEquationCount == SkXfermode::kLastMode + 1 + kOffset); +} + static void hard_light(GrGLFragmentBuilder* fsBuilder, const char* final, const char* src, @@ -510,15 +532,30 @@ public: const GrDrawTargetCaps& caps) override; SkXfermode::Mode mode() const { return fMode; } + bool hasCoverage() const { return fHasCoverage; } + bool hasHWBlendEquation() const { return kInvalid_GrBlendEquation != fHWBlendEquation; } + + GrBlendEquation hwBlendEquation() const { + SkASSERT(this->hasHWBlendEquation()); + return fHWBlendEquation; + } private: CustomXP(SkXfermode::Mode mode, const GrDeviceCoordTexture* dstCopy, bool willReadDstColor); void onGetGLProcessorKey(const GrGLSLCaps& caps, GrProcessorKeyBuilder* b) const override; + bool onWillNeedXferBarrier(const GrRenderTarget* rt, + const GrDrawTargetCaps& caps, + GrXferBarrierType* outBarrierType) const override; + + void onGetBlendInfo(BlendInfo*) const override; + bool onIsEqual(const GrXferProcessor& xpBase) const override; SkXfermode::Mode fMode; + bool fHasCoverage; + GrBlendEquation fHWBlendEquation; typedef GrXferProcessor INHERITED; }; @@ -540,24 +577,48 @@ public: GLCustomXP(const GrXferProcessor&) {} ~GLCustomXP() override {} - static void GenKey(const GrXferProcessor& proc, const GrGLSLCaps&, GrProcessorKeyBuilder* b) { - uint32_t key = proc.numTextures(); + static void GenKey(const GrXferProcessor& p, const GrGLSLCaps& caps, GrProcessorKeyBuilder* b) { + const CustomXP& xp = p.cast<CustomXP>(); + uint32_t key = xp.numTextures(); SkASSERT(key <= 1); - key |= proc.cast<CustomXP>().mode() << 1; + key |= xp.hasCoverage() << 1; + if (xp.hasHWBlendEquation()) { + SkASSERT(caps.advBlendEqInteraction() > 0); // 0 will mean !xp.hasHWBlendEquation(). + key |= caps.advBlendEqInteraction() << 2; + } + if (!xp.hasHWBlendEquation() || caps.mustEnableSpecificAdvBlendEqs()) { + GR_STATIC_ASSERT(GrGLSLCaps::kLast_AdvBlendEqInteraction < 4); + key |= xp.mode() << 4; + } b->add32(key); } private: void onEmitCode(const EmitArgs& args) override { - SkXfermode::Mode mode = args.fXP.cast<CustomXP>().mode(); + const CustomXP& xp = args.fXP.cast<CustomXP>(); GrGLXPFragmentBuilder* fsBuilder = args.fPB->getFragmentShaderBuilder(); - const char* dstColor = fsBuilder->dstColor(); - emit_custom_xfermode_code(mode, fsBuilder, args.fOutputPrimary, args.fInputColor, dstColor); - - fsBuilder->codeAppendf("%s = %s * %s + (vec4(1.0) - %s) * %s;", - args.fOutputPrimary, args.fOutputPrimary, args.fInputCoverage, - args.fInputCoverage, dstColor); + if (xp.hasHWBlendEquation()) { + // The blend mode will be implemented in hardware; only output the src color. + fsBuilder->enableAdvancedBlendEquationIfNeeded(xp.hwBlendEquation()); + if (xp.hasCoverage()) { + // Do coverage modulation by multiplying it into the src color before blending. + // (See getOptimizations()) + fsBuilder->codeAppendf("%s = %s * %s;", + args.fOutputPrimary, args.fInputCoverage, args.fInputColor); + } else { + fsBuilder->codeAppendf("%s = %s;", args.fOutputPrimary, args.fInputColor); + } + } else { + const char* dstColor = fsBuilder->dstColor(); + emit_custom_xfermode_code(xp.mode(), fsBuilder, args.fOutputPrimary, args.fInputColor, + dstColor); + if (xp.hasCoverage()) { + fsBuilder->codeAppendf("%s = %s * %s + (vec4(1.0) - %s) * %s;", + args.fOutputPrimary, args.fOutputPrimary, + args.fInputCoverage, args.fInputCoverage, dstColor); + } + } } void onSetData(const GrGLProgramDataManager&, const GrXferProcessor&) override {} @@ -569,7 +630,10 @@ private: CustomXP::CustomXP(SkXfermode::Mode mode, const GrDeviceCoordTexture* dstCopy, bool willReadDstColor) - : INHERITED(dstCopy, willReadDstColor), fMode(mode) { + : INHERITED(dstCopy, willReadDstColor), + fMode(mode), + fHasCoverage(true), + fHWBlendEquation(kInvalid_GrBlendEquation) { this->initClassID<CustomXP>(); } @@ -578,12 +642,15 @@ void CustomXP::onGetGLProcessorKey(const GrGLSLCaps& caps, GrProcessorKeyBuilder } GrGLXferProcessor* CustomXP::createGLInstance() const { + SkASSERT(this->willReadDstColor() != this->hasHWBlendEquation()); return SkNEW_ARGS(GLCustomXP, (*this)); } bool CustomXP::onIsEqual(const GrXferProcessor& other) const { const CustomXP& s = other.cast<CustomXP>(); - return fMode == s.fMode; + return fMode == s.fMode && + fHasCoverage == s.fHasCoverage && + fHWBlendEquation == s.fHWBlendEquation; } GrXferProcessor::OptFlags CustomXP::getOptimizations(const GrProcOptInfo& colorPOI, @@ -591,7 +658,131 @@ GrXferProcessor::OptFlags CustomXP::getOptimizations(const GrProcOptInfo& colorP bool doesStencilWrite, GrColor* overrideColor, const GrDrawTargetCaps& caps) { - return GrXferProcessor::kNone_Opt; + /* + Most the optimizations we do here are based on tweaking alpha for coverage. + + The general SVG blend equation is defined in the spec as follows: + + Dca' = B(Sc, Dc) * Sa * Da + Y * Sca * (1-Da) + Z * Dca * (1-Sa) + Da' = X * Sa * Da + Y * Sa * (1-Da) + Z * Da * (1-Sa) + + (Note that Sca, Dca indicate RGB vectors that are premultiplied by alpha, + and that B(Sc, Dc) is a mode-specific function that accepts non-multiplied + RGB colors.) + + For every blend mode supported by this class, i.e. the "advanced" blend + modes, X=Y=Z=1 and this equation reduces to the PDF blend equation. + + It can be shown that when X=Y=Z=1, these equations can modulate alpha for + coverage. + + + == Color == + + We substitute Y=Z=1 and define a blend() function that calculates Dca' in + terms of premultiplied alpha only: + + blend(Sca, Dca, Sa, Da) = {Dca : if Sa == 0, + Sca : if Da == 0, + B(Sca/Sa, Dca/Da) * Sa * Da + Sca * (1-Da) + Dca * (1-Sa) : if Sa,Da != 0} + + And for coverage modulation, we use a post blend src-over model: + + Dca'' = f * blend(Sca, Dca, Sa, Da) + (1-f) * Dca + + (Where f is the fractional coverage.) + + Next we show that canTweakAlphaForCoverage() is true by proving the + following relationship: + + blend(f*Sca, Dca, f*Sa, Da) == f * blend(Sca, Dca, Sa, Da) + (1-f) * Dca + + General case (f,Sa,Da != 0): + + f * blend(Sca, Dca, Sa, Da) + (1-f) * Dca + = f * (B(Sca/Sa, Dca/Da) * Sa * Da + Sca * (1-Da) + Dca * (1-Sa)) + (1-f) * Dca [Sa,Da != 0, definition of blend()] + = B(Sca/Sa, Dca/Da) * f*Sa * Da + f*Sca * (1-Da) + f*Dca * (1-Sa) + Dca - f*Dca + = B(Sca/Sa, Dca/Da) * f*Sa * Da + f*Sca - f*Sca * Da + f*Dca - f*Dca * Sa + Dca - f*Dca + = B(Sca/Sa, Dca/Da) * f*Sa * Da + f*Sca - f*Sca * Da - f*Dca * Sa + Dca + = B(Sca/Sa, Dca/Da) * f*Sa * Da + f*Sca * (1-Da) - f*Dca * Sa + Dca + = B(Sca/Sa, Dca/Da) * f*Sa * Da + f*Sca * (1-Da) + Dca * (1 - f*Sa) + = B(f*Sca/f*Sa, Dca/Da) * f*Sa * Da + f*Sca * (1-Da) + Dca * (1 - f*Sa) [f!=0] + = blend(f*Sca, Dca, f*Sa, Da) [definition of blend()] + + Corner cases (Sa=0, Da=0, and f=0): + + Sa=0: f * blend(Sca, Dca, Sa, Da) + (1-f) * Dca + = f * Dca + (1-f) * Dca [Sa=0, definition of blend()] + = Dca + = blend(0, Dca, 0, Da) [definition of blend()] + = blend(f*Sca, Dca, f*Sa, Da) [Sa=0] + + Da=0: f * blend(Sca, Dca, Sa, Da) + (1-f) * Dca + = f * Sca + (1-f) * Dca [Da=0, definition of blend()] + = f * Sca [Da=0] + = blend(f*Sca, 0, f*Sa, 0) [definition of blend()] + = blend(f*Sca, Dca, f*Sa, Da) [Da=0] + + f=0: f * blend(Sca, Dca, Sa, Da) + (1-f) * Dca + = Dca [f=0] + = blend(0, Dca, 0, Da) [definition of blend()] + = blend(f*Sca, Dca, f*Sa, Da) [f=0] + + == Alpha == + + We substitute X=Y=Z=1 and define a blend() function that calculates Da': + + blend(Sa, Da) = Sa * Da + Sa * (1-Da) + Da * (1-Sa) + = Sa * Da + Sa - Sa * Da + Da - Da * Sa + = Sa + Da - Sa * Da + + We use the same model for coverage modulation as we did with color: + + Da'' = f * blend(Sa, Da) + (1-f) * Da + + And show that canTweakAlphaForCoverage() is true by proving the following + relationship: + + blend(f*Sa, Da) == f * blend(Sa, Da) + (1-f) * Da + + + f * blend(Sa, Da) + (1-f) * Da + = f * (Sa + Da - Sa * Da) + (1-f) * Da + = f*Sa + f*Da - f*Sa * Da + Da - f*Da + = f*Sa - f*Sa * Da + Da + = f*Sa + Da - f*Sa * Da + = blend(f*Sa, Da) + */ + + OptFlags flags = kNone_Opt; + if (colorPOI.allStagesMultiplyInput()) { + flags = flags | kCanTweakAlphaForCoverage_OptFlag; + } + if (coveragePOI.isSolidWhite()) { + flags = flags | kIgnoreCoverage_OptFlag; + fHasCoverage = false; + } + if (caps.advancedBlendEquationSupport() && !coveragePOI.isFourChannelOutput()) { + // This blend mode can be implemented in hardware. + fHWBlendEquation = hw_blend_equation(fMode); + } + return flags; +} + +bool CustomXP::onWillNeedXferBarrier(const GrRenderTarget* rt, + const GrDrawTargetCaps& caps, + GrXferBarrierType* outBarrierType) const { + if (this->hasHWBlendEquation() && !caps.advancedCoherentBlendEquationSupport()) { + *outBarrierType = kBlend_GrXferBarrierType; + return true; + } + return false; +} + +void CustomXP::onGetBlendInfo(BlendInfo* blendInfo) const { + if (this->hasHWBlendEquation()) { + blendInfo->fEquation = this->hwBlendEquation(); + } } /////////////////////////////////////////////////////////////////////////////// @@ -609,6 +800,19 @@ GrCustomXPFactory::onCreateXferProcessor(const GrDrawTargetCaps& caps, return CustomXP::Create(fMode, dstCopy, this->willReadDstColor(caps, colorPOI, coveragePOI)); } +bool GrCustomXPFactory::willReadDstColor(const GrDrawTargetCaps& caps, + const GrProcOptInfo& colorPOI, + const GrProcOptInfo& coveragePOI) const { + if (!caps.advancedBlendEquationSupport()) { + // No hardware support for advanced blend equations; we will need to do it in the shader. + return true; + } + if (coveragePOI.isFourChannelOutput()) { + // Advanced blend equations can't tweak alpha for RGB coverage. + return true; + } + return false; +} void GrCustomXPFactory::getInvariantOutput(const GrProcOptInfo& colorPOI, const GrProcOptInfo& coveragePOI, diff --git a/src/gpu/effects/GrCustomXfermodePriv.h b/src/gpu/effects/GrCustomXfermodePriv.h index edcdcc0a6e..43aaeabc54 100644 --- a/src/gpu/effects/GrCustomXfermodePriv.h +++ b/src/gpu/effects/GrCustomXfermodePriv.h @@ -75,9 +75,7 @@ private: bool willReadDstColor(const GrDrawTargetCaps& caps, const GrProcOptInfo& colorPOI, - const GrProcOptInfo& coveragePOI) const override { - return true; - } + const GrProcOptInfo& coveragePOI) const override; bool onIsEqual(const GrXPFactory& xpfBase) const override { const GrCustomXPFactory& xpf = xpfBase.cast<GrCustomXPFactory>(); |