Index: src/gpu/effects/GrAdvancedEquationXPFactory.cpp |
diff --git a/src/gpu/effects/GrAdvancedEquationXPFactory.cpp b/src/gpu/effects/GrAdvancedEquationXPFactory.cpp |
new file mode 100644 |
index 0000000000000000000000000000000000000000..def4cca2ceeeb866e99f7c01c587a577c583f94f |
--- /dev/null |
+++ b/src/gpu/effects/GrAdvancedEquationXPFactory.cpp |
@@ -0,0 +1,259 @@ |
+/* |
+ * Copyright 2015 Google Inc. |
+ * |
+ * Use of this source code is governed by a BSD-style license that can be |
+ * found in the LICENSE file. |
+ */ |
+ |
+#include "effects/GrAdvancedEquationXPFactory.h" |
+ |
+#include "GrDrawTargetCaps.h" |
+#include "GrGpu.h" |
+#include "gl/GrGLXferProcessor.h" |
+#include "gl/builders/GrGLFragmentShaderBuilder.h" |
+#include "gl/builders/GrGLProgramBuilder.h" |
+ |
+bool GrAdvancedEquationXPFactory::IsSupported(const GrContext* context, SkXfermode::Mode mode) { |
+ if (!context->getGpu()->caps()->advancedBlendEquationSupport()) { |
+ return false; |
+ } |
+ if (mode > SkXfermode::kLastMode) { |
+ return false; |
+ } |
+ return mode == SkXfermode::kScreen_Mode || mode > SkXfermode::kLastCoeffMode; |
+} |
+ |
+template<GrBlendEquation Equation> GrXPFactory* GrAdvancedEquationXPFactory::TakeFactoryRef() { |
+ GR_STATIC_ASSERT(GrBlendEquationIsAdvanced(Equation)); |
+ static GrAdvancedEquationXPFactory* factory; |
+ if (!factory) { |
+ factory = SkNEW_ARGS(GrAdvancedEquationXPFactory, (Equation)); |
+ } |
+ return SkRef(factory); |
+} |
+ |
+GrXPFactory* GrAdvancedEquationXPFactory::Create(const GrContext* context, SkXfermode::Mode mode) { |
+ if (!context->getGpu()->caps()->advancedBlendEquationSupport()) { |
+ return NULL; |
+ } |
+ |
+ switch (mode) { |
+ default: break; |
+ case SkXfermode::kScreen_Mode: return TakeFactoryRef<kScreen_GrBlendEquation>(); |
+ case SkXfermode::kOverlay_Mode: return TakeFactoryRef<kOverlay_GrBlendEquation>(); |
+ case SkXfermode::kDarken_Mode: return TakeFactoryRef<kDarken_GrBlendEquation>(); |
+ case SkXfermode::kLighten_Mode: return TakeFactoryRef<kLighten_GrBlendEquation>(); |
+ case SkXfermode::kColorDodge_Mode: return TakeFactoryRef<kColorDodge_GrBlendEquation>(); |
+ case SkXfermode::kColorBurn_Mode: return TakeFactoryRef<kColorBurn_GrBlendEquation>(); |
+ case SkXfermode::kHardLight_Mode: return TakeFactoryRef<kHardLight_GrBlendEquation>(); |
+ case SkXfermode::kSoftLight_Mode: return TakeFactoryRef<kSoftLight_GrBlendEquation>(); |
+ case SkXfermode::kDifference_Mode: return TakeFactoryRef<kDifference_GrBlendEquation>(); |
+ case SkXfermode::kExclusion_Mode: return TakeFactoryRef<kExclusion_GrBlendEquation>(); |
+ case SkXfermode::kMultiply_Mode: return TakeFactoryRef<kMultiply_GrBlendEquation>(); |
+ case SkXfermode::kHue_Mode: return TakeFactoryRef<kHSLHue_GrBlendEquation>(); |
+ case SkXfermode::kSaturation_Mode: return TakeFactoryRef<kHSLSaturation_GrBlendEquation>(); |
+ case SkXfermode::kColor_Mode: return TakeFactoryRef<kHSLColor_GrBlendEquation>(); |
+ case SkXfermode::kLuminosity_Mode: return TakeFactoryRef<kHSLLuminosity_GrBlendEquation>(); |
+ } |
+ |
+ return NULL; |
+} |
+ |
+bool GrAdvancedEquationXPFactory::canTweakAlphaForCoverage() const { |
+ /* |
+ 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 "advanced" blend mode, X=Y=Z=1 and this equation reduces to the |
+ PDF blend equation. |
+ |
+ It can be shown that when X=Y=Z=1, canTweakAlphaForCoverage() is true. |
Mark Kilgard
2015/04/03 18:30:03
great to see this analysis
|
+ |
+ |
+ == 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, Skia uses 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 |
+ |
+ 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] |
+ |
+ 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()] |
Mark Kilgard
2015/04/03 18:30:04
great analysis
I think you should motivate the "C
Chris Dalton
2015/04/17 08:37:18
Done.
|
+ |
+ |
+ == 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) |
+ */ |
+ |
+ return true; |
+} |
+ |
+bool GrAdvancedEquationXPFactory::supportsRGBCoverage(GrColor knownColor, |
+ uint32_t knownColorFlags) const { |
+ /* |
+ We do coverage modulation by multiplying it into the src color before |
+ blending. The GPU runs the blend equation *after* the fragment shader, so |
+ it is not within our control to apply coverage at that point. This model |
+ is mathematically correct, but the proof lies in the following property: |
+ |
+ f*Sca / f*Sa == Sc |
+ |
+ (That is, the general case, f,Sa,Da != 0. See canTweakAlphaForCoverage().) |
+ |
+ But when coverage is unique per channel, this property no longer holds: |
Mark Kilgard
2015/04/03 18:30:03
"when coverage is unique per channel" ??
huh? Wh
Chris Dalton
2015/04/17 08:37:19
Yeah, "RGB coverage" in the context of GrXferProce
|
+ |
+ fc*Sca / fa*Sa == fc/fa * Sc != Sc [fc!=fa] |
+ |
+ The only time it is still true is when Sc == 0. |
Chris Dalton
2015/03/31 10:51:49
This is most likely wrong. I'll update the comment
|
+ |
+ (It would also work out if we could know the dst color was trans black.) |
Mark Kilgard
2015/04/03 18:30:03
I'm assume by "trans black" you mean transparent b
|
+ */ |
+ |
+ return kRGB_GrColorComponentFlags == (knownColorFlags & kRGB_GrColorComponentFlags) && |
+ 0 == (knownColor & ~(0xffu << GrColor_SHIFT_A)); // RGB=0 |
Mark Kilgard
2015/04/03 18:30:03
Is there not some constant to use instead of 0xffu
Chris Dalton
2015/04/17 08:37:19
That code is completely gone now.
|
+} |
+ |
+bool GrAdvancedEquationXPFactory::willBlendCoherently(const GrDrawTargetCaps& caps) const { |
+ return GrDrawTargetCaps::kAdvancedCoherent_BlendEquationSupport == caps.blendEquationSupport(); |
+} |
+ |
+GrXferProcessor* GrAdvancedEquationXPFactory::onCreateXferProcessor( |
+ const GrDrawTargetCaps& caps, |
+ const GrProcOptInfo& colorPOI, |
+ const GrProcOptInfo& coveragePOI, |
Mark Kilgard
2015/04/03 18:30:03
wish I knew what "XP" stands for
can I assume POI
Chris Dalton
2015/04/17 08:37:19
XP = Xfer Processor = An object that controls the
|
+ const GrDeviceCoordTexture* dstCopy) const { |
+ SkASSERT(caps.advancedBlendEquationSupport()); |
+ SkASSERT(!dstCopy || !dstCopy->texture()); |
+ |
+ if (coveragePOI.isFourChannelOutput() && !this->supportsRGBCoverage(colorPOI.color(), |
+ colorPOI.validFlags())) { |
+ return NULL; |
+ } |
+ if (!coveragePOI.isSolidWhite()) { |
+ return SkRef(&fXferProcessorWithCoverage); |
+ } |
+ return SkRef(&fXferProcessor); |
+} |
+ |
+void GrAdvancedEquationXPFactory::XferProcessorBase::onGetGLProcessorKey(const GrGLCaps&, |
+ GrProcessorKeyBuilder*) const { |
+ // Our XP's don't emit variable fragment code. (HasCoverage is accounted for by the class ID.) |
+ return; |
+} |
+ |
+template</*HasCoverage = true*/> |
+GrGLXferProcessor* GrAdvancedEquationXPFactory::XferProcessor<true>::createGLInstance() const { |
+ class GLXferProcessorWithCoverage : public GrGLXferProcessor { |
+ private: |
+ void onEmitCode(const EmitArgs& args) override { |
+ // We do coverage modulation by multiplying it into the src color before blending. |
+ // (See canTweakAlphaForCoverage()) |
+ GrGLFPFragmentBuilder* fsBuilder = args.fPB->getFragmentShaderBuilder(); |
+ fsBuilder->codeAppendf("%s = %s * %s;", |
+ args.fOutputPrimary, args.fInputColor, args.fInputCoverage); |
+ } |
+ void onSetData(const GrGLProgramDataManager&, const GrXferProcessor&) override {} |
+ }; |
+ return SkNEW(GLXferProcessorWithCoverage); |
+} |
+ |
+template</*HasCoverage = false*/> |
+GrGLXferProcessor* GrAdvancedEquationXPFactory::XferProcessor<false>::createGLInstance() const { |
+ class GLXferProcessor : public GrGLXferProcessor { |
+ private: |
+ void onEmitCode(const EmitArgs& args) override { |
+ GrGLFPFragmentBuilder* fsBuilder = args.fPB->getFragmentShaderBuilder(); |
+ fsBuilder->codeAppendf("%s = %s;", args.fOutputPrimary, args.fInputColor); |
+ } |
+ void onSetData(const GrGLProgramDataManager&, const GrXferProcessor&) override {} |
+ }; |
+ return SkNEW(GLXferProcessor); |
+} |
+ |
+void GrAdvancedEquationXPFactory::XferProcessorBase::onGetBlendInfo(BlendInfo* blendInfo) const { |
+ SkASSERT(GrBlendEquationIsAdvanced(fEquation)); |
+ blendInfo->fEquation = fEquation; |
+} |
+ |
+ |
+GR_DEFINE_XP_FACTORY_TEST(GrAdvancedEquationXPFactory); |
+ |
+GrXPFactory* GrAdvancedEquationXPFactory::TestCreate(SkRandom* random, |
+ GrContext* context, |
+ const GrDrawTargetCaps& caps, |
+ GrTexture*[]) { |
+ int mode = random->nextRangeU(SkXfermode::kLastCoeffMode, SkXfermode::kLastMode); |
+ if (mode == SkXfermode::kLastCoeffMode) { |
+ mode = SkXfermode::kScreen_Mode; |
+ } |
+ return GrAdvancedEquationXPFactory::Create(context, static_cast<SkXfermode::Mode>(mode)); |
+} |