bool GrPorterDuffXPFactory::willReadDstColor(const GrDrawTargetCaps& caps,
                                             const GrProcOptInfo& colorPOI,
                                             const GrProcOptInfo& coveragePOI) const {
    // We can always blend correctly if we have dual source blending.
    if (caps.shaderCaps()->dualSourceBlendingSupport()) {
        return false;
    }

    if (can_tweak_alpha_for_coverage(fDstCoeff)) {
        return false;
    }

    bool srcAIsOne = colorPOI.isOpaque();

    if (kZero_GrBlendCoeff == fDstCoeff) {
        if (kZero_GrBlendCoeff == fSrcCoeff || srcAIsOne) {
            return false;
        }
    }

    // Reduces to: coeffS * (Cov*S) + D
    if (kSA_GrBlendCoeff == fDstCoeff && srcAIsOne) {
        return false;
    }

    // We can always blend correctly if we have solid coverage.
    if (coveragePOI.isSolidWhite()) {
        return false;
    }

    return true;
}
void GrPorterDuffXPFactory::getInvariantBlendedColor(const GrProcOptInfo& colorPOI,
                                                     InvariantBlendedColor* blendedColor) const {
    // Find the blended color info based on the formula that does not have coverage.
    BlendFormula colorFormula = gBlendTable[colorPOI.isOpaque()][0][fXfermode];
    if (colorFormula.usesDstColor()) {
        blendedColor->fWillBlendWithDst = true;
        blendedColor->fKnownColorFlags = kNone_GrColorComponentFlags;
        return;
    }

    blendedColor->fWillBlendWithDst = false;

    SkASSERT(kAdd_GrBlendEquation == colorFormula.fBlendEquation);

    switch (colorFormula.fSrcCoeff) {
        case kZero_GrBlendCoeff:
            blendedColor->fKnownColor = 0;
            blendedColor->fKnownColorFlags = kRGBA_GrColorComponentFlags;
            return;

        case kOne_GrBlendCoeff:
            blendedColor->fKnownColor = colorPOI.color();
            blendedColor->fKnownColorFlags = colorPOI.validFlags();
            return;

        default:
            blendedColor->fKnownColorFlags = kNone_GrColorComponentFlags;
            return;
    }
}
GrXferProcessor::OptFlags
PorterDuffXferProcessor::onGetOptimizations(const GrProcOptInfo& colorPOI,
                                            const GrProcOptInfo& coveragePOI,
                                            bool doesStencilWrite,
                                            GrColor* overrideColor,
                                            const GrCaps& caps) {
    GrXferProcessor::OptFlags optFlags = GrXferProcessor::kNone_OptFlags;
    if (!fBlendFormula.modifiesDst()) {
        if (!doesStencilWrite) {
            optFlags |= GrXferProcessor::kSkipDraw_OptFlag;
        }
        optFlags |= (GrXferProcessor::kIgnoreColor_OptFlag |
                     GrXferProcessor::kIgnoreCoverage_OptFlag |
                     GrXferProcessor::kCanTweakAlphaForCoverage_OptFlag);
    } else {
        if (!fBlendFormula.usesInputColor()) {
            optFlags |= GrXferProcessor::kIgnoreColor_OptFlag;
        }
        if (coveragePOI.isSolidWhite()) {
            optFlags |= GrXferProcessor::kIgnoreCoverage_OptFlag;
        }
        if (colorPOI.allStagesMultiplyInput() && fBlendFormula.canTweakAlphaForCoverage()) {
            optFlags |= GrXferProcessor::kCanTweakAlphaForCoverage_OptFlag;
        }
    }
    return optFlags;
}
void GrPipeline::adjustProgramFromOptimizations(const GrPipelineBuilder& pipelineBuilder,
                                                GrXferProcessor::OptFlags flags,
                                                const GrProcOptInfo& colorPOI,
                                                const GrProcOptInfo& coveragePOI,
                                                int* firstColorStageIdx,
                                                int* firstCoverageStageIdx) {
    fReadsFragPosition = fXferProcessor->willReadFragmentPosition();

    if ((flags & GrXferProcessor::kIgnoreColor_OptFlag) ||
        (flags & GrXferProcessor::kOverrideColor_OptFlag)) {
        *firstColorStageIdx = pipelineBuilder.numColorFragmentStages();
    } else {
        if (coveragePOI.readsFragPosition()) {
            fReadsFragPosition = true;
        }
    }

    if (flags & GrXferProcessor::kIgnoreCoverage_OptFlag) {
        *firstCoverageStageIdx = pipelineBuilder.numCoverageFragmentStages();
    } else {
        if (coveragePOI.readsFragPosition()) {
            fReadsFragPosition = true;
        }
    }
}
static BlendFormula get_blend_formula(SkXfermode::Mode xfermode,
                                      const GrProcOptInfo& colorPOI,
                                      const GrProcOptInfo& coveragePOI) {
    SkASSERT(xfermode >= 0 && xfermode <= SkXfermode::kLastCoeffMode);
    SkASSERT(!coveragePOI.isFourChannelOutput());

    return gBlendTable[colorPOI.isOpaque()][!coveragePOI.isSolidWhite()][xfermode];
}
static BlendFormula get_blend_formula(const GrProcOptInfo& colorPOI,
                                      const GrProcOptInfo& coveragePOI,
                                      bool hasMixedSamples,
                                      SkXfermode::Mode xfermode) {
    SkASSERT(xfermode >= 0 && xfermode <= SkXfermode::kLastCoeffMode);
    SkASSERT(!coveragePOI.isFourChannelOutput());

    bool conflatesCoverage = !coveragePOI.isSolidWhite() || hasMixedSamples;
    return gBlendTable[colorPOI.isOpaque()][conflatesCoverage][xfermode];
}
Exemple #7
0
bool GrPaint::isConstantBlendedColor(GrColor* color) const {
    GrProcOptInfo colorProcInfo;
    colorProcInfo.calcWithInitialValues(fColorStages.begin(), this->numColorStages(), fColor,
                                        kRGBA_GrColorComponentFlags, false);

    GrXPFactory::InvariantBlendedColor blendedColor;
    fXPFactory->getInvariantBlendedColor(colorProcInfo, &blendedColor);

    if (kRGBA_GrColorComponentFlags == blendedColor.fKnownColorFlags) {
        *color = blendedColor.fKnownColor;
        return true;
    }
    return false;
}
Exemple #8
0
static BlendFormula get_lcd_blend_formula(const GrProcOptInfo& coveragePOI,
                                          SkXfermode::Mode xfermode) {
    SkASSERT(xfermode >= 0 && xfermode <= SkXfermode::kLastCoeffMode);
    SkASSERT(coveragePOI.isFourChannelOutput());

    return gLCDBlendTable[xfermode];
}
static BlendFormula get_lcd_blend_formula(const GrProcOptInfo& coveragePOI,
                                          SkBlendMode xfermode) {
    SkASSERT((unsigned)xfermode <= (unsigned)SkBlendMode::kLastCoeffMode);
    SkASSERT(coveragePOI.isFourChannelOutput());

    return gLCDBlendTable[(int)xfermode];
}
bool GrPorterDuffXPFactory::willReadDstColor(const GrCaps& caps,
                                             const GrProcOptInfo& colorPOI,
                                             const GrProcOptInfo& coveragePOI) const {
    if (coveragePOI.isFourChannelOutput()) {
        return false; // The LCD XP never does a dst read.
    }

    // We fallback on the shader XP when the blend formula would use dual source blending but we
    // don't have support for it.
    return !caps.shaderCaps()->dualSourceBlendingSupport() &&
           get_blend_formula(fXfermode, colorPOI, coveragePOI).hasSecondaryOutput();
}
bool GrPorterDuffXPFactory::willReadDstColor(const GrCaps& caps,
                                             const GrProcOptInfo& colorPOI,
                                             const GrProcOptInfo& covPOI,
                                             bool hasMixedSamples) const {
    if (caps.shaderCaps()->dualSourceBlendingSupport()) {
        return false;
    }
    
    // When we have four channel coverage we always need to read the dst in order to correctly
    // blend. The one exception is when we are using srcover mode and we know the input color into
    // the XP.
    if (covPOI.isFourChannelOutput()) {
        if (SkXfermode::kSrcOver_Mode == fXfermode &&
            kRGBA_GrColorComponentFlags == colorPOI.validFlags()) {
            return false;
        }
        return get_lcd_blend_formula(covPOI, fXfermode).hasSecondaryOutput();
    }
    // We fallback on the shader XP when the blend formula would use dual source blending but we
    // don't have support for it.
    return get_blend_formula(colorPOI, covPOI, hasMixedSamples, fXfermode).hasSecondaryOutput();
}
Exemple #12
0
bool GrPaint::isConstantBlendedColor(GrColor* color) const {
    GrProcOptInfo colorProcInfo;
    colorProcInfo.calcWithInitialValues(fColorFragmentProcessors.begin(),
                                        this->numColorFragmentProcessors(), fColor,
                                        kRGBA_GrColorComponentFlags, false);

    GrXPFactory::InvariantBlendedColor blendedColor;
    if (fXPFactory) {
        fXPFactory->getInvariantBlendedColor(colorProcInfo, &blendedColor);
    } else {
        GrPorterDuffXPFactory::SrcOverInvariantBlendedColor(colorProcInfo.color(),
                                                            colorProcInfo.validFlags(),
                                                            colorProcInfo.isOpaque(),
                                                            &blendedColor); 
    }

    if (kRGBA_GrColorComponentFlags == blendedColor.fKnownColorFlags) {
        *color = blendedColor.fKnownColor;
        return true;
    }
    return false;
}
bool GrPorterDuffXPFactory::willReadDstColor(const GrCaps& caps,
                                             const GrProcOptInfo& colorPOI,
                                             const GrProcOptInfo& covPOI,
                                             bool hasMixedSamples) const {
    if (caps.shaderCaps()->dualSourceBlendingSupport()) {
        return false;
    }
    if (covPOI.isFourChannelOutput()) {
        return false; // The LCD XP will abort rather than doing a dst read.
    }
    // We fallback on the shader XP when the blend formula would use dual source blending but we
    // don't have support for it.
    return get_blend_formula(colorPOI, covPOI, hasMixedSamples, fXfermode).hasSecondaryOutput();
}
Exemple #14
0
static bool can_use_hw_blend_equation(GrBlendEquation equation,
                                      const GrProcOptInfo& coveragePOI,
                                      const GrCaps& caps) {
    if (!caps.advancedBlendEquationSupport()) {
        return false;
    }
    if (coveragePOI.isFourChannelOutput()) {
        return false; // LCD coverage must be applied after the blend equation.
    }
    if (caps.canUseAdvancedBlendEquation(equation)) {
        return false;
    }
    return true;
}
GrXferProcessor::OptFlags
PorterDuffXferProcessor::getOptimizations(const GrProcOptInfo& colorPOI,
                                          const GrProcOptInfo& coveragePOI,
                                          bool doesStencilWrite,
                                          GrColor* overrideColor,
                                          const GrDrawTargetCaps& caps) {
    GrXferProcessor::OptFlags optFlags;
    // Optimizations when doing RGB Coverage
    if (coveragePOI.isFourChannelOutput()) {
        // We want to force our primary output to be alpha * Coverage, where alpha is the alpha
        // value of the blend the constant. We should already have valid blend coeff's if we are at
        // a point where we have RGB coverage. We don't need any color stages since the known color
        // output is already baked into the blendConstant.
        uint8_t alpha = GrColorUnpackA(fBlendConstant);
        *overrideColor = GrColorPackRGBA(alpha, alpha, alpha, alpha);
        optFlags = GrXferProcessor::kOverrideColor_OptFlag;
    } else {
        optFlags = this->internalGetOptimizations(colorPOI,
                                                  coveragePOI,
                                                  doesStencilWrite);
    }
    this->calcOutputTypes(optFlags, caps, coveragePOI.isSolidWhite());
    return optFlags;
}
void GrCoverageSetOpXPFactory::getInvariantOutput(const GrProcOptInfo& colorPOI,
                                                  const GrProcOptInfo& coveragePOI,
                                                  GrXPFactory::InvariantOutput* output) const {
    if (SkRegion::kReplace_Op == fRegionOp) {
        if (coveragePOI.isSolidWhite()) {
            output->fBlendedColor = GrColor_WHITE;
            output->fBlendedColorFlags = kRGBA_GrColorComponentFlags;
        } else {
            output->fBlendedColorFlags = 0;
        }

        output->fWillBlendWithDst = false;
    } else {
        output->fBlendedColorFlags = 0;
        output->fWillBlendWithDst = true;
    }
}
Exemple #17
0
GrXferProcessor::OptFlags GrXferProcessor::getOptimizations(const GrProcOptInfo& colorPOI,
                                                            const GrProcOptInfo& coveragePOI,
                                                            bool doesStencilWrite,
                                                            GrColor* overrideColor,
                                                            const GrCaps& caps) {
    GrXferProcessor::OptFlags flags = this->onGetOptimizations(colorPOI,
                                                               coveragePOI,
                                                               doesStencilWrite,
                                                               overrideColor,
                                                               caps);

    if (this->willReadDstColor()) {
        // When performing a dst read we handle coverage in the base class.
        SkASSERT(!(flags & GrXferProcessor::kIgnoreCoverage_OptFlag));
        if (coveragePOI.isSolidWhite()) {
            flags |= GrXferProcessor::kIgnoreCoverage_OptFlag;
        }
    }
    if (flags & GrXferProcessor::kIgnoreCoverage_OptFlag) {
        fReadsCoverage = false;
    }
    return flags;
}
GrPipeline::GrPipeline(const GrPipelineBuilder& pipelineBuilder,
                       const GrProcOptInfo& colorPOI,
                       const GrProcOptInfo& coveragePOI,
                       const GrDrawTargetCaps& caps,
                       const GrScissorState& scissorState,
                       const GrDeviceCoordTexture* dstCopy) {
    // Create XferProcessor from DS's XPFactory
    SkAutoTUnref<GrXferProcessor> xferProcessor(
        pipelineBuilder.getXPFactory()->createXferProcessor(colorPOI, coveragePOI, dstCopy, caps));

    GrColor overrideColor = GrColor_ILLEGAL;
    if (colorPOI.firstEffectiveStageIndex() != 0) {
        overrideColor = colorPOI.inputColorToEffectiveStage();
    }

    GrXferProcessor::OptFlags optFlags;
    if (xferProcessor) {
        fXferProcessor.reset(xferProcessor.get());

        optFlags = xferProcessor->getOptimizations(colorPOI,
                                                   coveragePOI,
                                                   pipelineBuilder.getStencil().doesWrite(),
                                                   &overrideColor,
                                                   caps);
    }

    // When path rendering the stencil settings are not always set on the GrPipelineBuilder
    // so we must check the draw type. In cases where we will skip drawing we simply return a
    // null GrPipeline.
    if (!xferProcessor || (GrXferProcessor::kSkipDraw_OptFlag & optFlags)) {
        // Set the fields that don't default init and return. The lack of a render target will
        // indicate that this can be skipped.
        fFlags = 0;
        fDrawFace = GrPipelineBuilder::kInvalid_DrawFace;
        return;
    }

    fRenderTarget.reset(pipelineBuilder.fRenderTarget.get());
    SkASSERT(fRenderTarget);
    fScissorState = scissorState;
    fStencilSettings = pipelineBuilder.getStencil();
    fDrawFace = pipelineBuilder.getDrawFace();

    fFlags = 0;
    if (pipelineBuilder.isHWAntialias()) {
        fFlags |= kHWAA_Flag;
    }
    if (pipelineBuilder.isDither()) {
        fFlags |= kDither_Flag;
    }
    if (pipelineBuilder.snapVerticesToPixelCenters()) {
        fFlags |= kSnapVertices_Flag;
    }

    int firstColorStageIdx = colorPOI.firstEffectiveStageIndex();

    // TODO: Once we can handle single or four channel input into coverage stages then we can use
    // GrPipelineBuilder's coverageProcInfo (like color above) to set this initial information.
    int firstCoverageStageIdx = 0;

    this->adjustProgramFromOptimizations(pipelineBuilder, optFlags, colorPOI, coveragePOI,
                                         &firstColorStageIdx, &firstCoverageStageIdx);

    bool usesLocalCoords = false;

    // Copy Stages from PipelineBuilder to Pipeline
    for (int i = firstColorStageIdx; i < pipelineBuilder.numColorFragmentStages(); ++i) {
        SkNEW_APPEND_TO_TARRAY(&fFragmentStages,
                               GrPendingFragmentStage,
                               (pipelineBuilder.fColorStages[i]));
        usesLocalCoords = usesLocalCoords ||
                          pipelineBuilder.fColorStages[i].processor()->usesLocalCoords();
    }

    fNumColorStages = fFragmentStages.count();
    for (int i = firstCoverageStageIdx; i < pipelineBuilder.numCoverageFragmentStages(); ++i) {
        SkNEW_APPEND_TO_TARRAY(&fFragmentStages,
                               GrPendingFragmentStage,
                               (pipelineBuilder.fCoverageStages[i]));
        usesLocalCoords = usesLocalCoords ||
                          pipelineBuilder.fCoverageStages[i].processor()->usesLocalCoords();
    }

    // let the GP init the batch tracker
    fInitBT.fColorIgnored = SkToBool(optFlags & GrXferProcessor::kIgnoreColor_OptFlag);
    fInitBT.fOverrideColor = fInitBT.fColorIgnored ? GrColor_ILLEGAL : overrideColor;
    fInitBT.fCoverageIgnored = SkToBool(optFlags & GrXferProcessor::kIgnoreCoverage_OptFlag);
    fInitBT.fUsesLocalCoords = usesLocalCoords;
    fInitBT.fCanTweakAlphaForCoverage =
        SkToBool(optFlags & GrXferProcessor::kCanTweakAlphaForCoverage_OptFlag);
}
GrXferProcessor::OptFlags
PorterDuffXferProcessor::internalGetOptimizations(const GrProcOptInfo& colorPOI,
                                                  const GrProcOptInfo& coveragePOI,
                                                  bool doesStencilWrite) {
    if (this->willReadDstColor()) {
        return GrXferProcessor::kNone_Opt;
    }

    bool srcAIsOne = colorPOI.isOpaque();
    bool hasCoverage = !coveragePOI.isSolidWhite();

    bool dstCoeffIsOne = kOne_GrBlendCoeff == fDstBlend ||
                         (kSA_GrBlendCoeff == fDstBlend && srcAIsOne);
    bool dstCoeffIsZero = kZero_GrBlendCoeff == fDstBlend ||
                         (kISA_GrBlendCoeff == fDstBlend && srcAIsOne);

    // When coeffs are (0,1) there is no reason to draw at all, unless
    // stenciling is enabled. Having color writes disabled is effectively
    // (0,1).
    if ((kZero_GrBlendCoeff == fSrcBlend && dstCoeffIsOne)) {
        if (doesStencilWrite) {
            return GrXferProcessor::kIgnoreColor_OptFlag |
                   GrXferProcessor::kSetCoverageDrawing_OptFlag;
        } else {
            fDstBlend = kOne_GrBlendCoeff;
            return GrXferProcessor::kSkipDraw_OptFlag;
        }
    }

    // if we don't have coverage we can check whether the dst
    // has to read at all. If not, we'll disable blending.
    if (!hasCoverage) {
        if (dstCoeffIsZero) {
            if (kOne_GrBlendCoeff == fSrcBlend) {
                // if there is no coverage and coeffs are (1,0) then we
                // won't need to read the dst at all, it gets replaced by src
                fDstBlend = kZero_GrBlendCoeff;
                return GrXferProcessor::kNone_Opt;
            } else if (kZero_GrBlendCoeff == fSrcBlend) {
                // if the op is "clear" then we don't need to emit a color
                // or blend, just write transparent black into the dst.
                fSrcBlend = kOne_GrBlendCoeff;
                fDstBlend = kZero_GrBlendCoeff;
                return GrXferProcessor::kIgnoreColor_OptFlag |
                       GrXferProcessor::kIgnoreCoverage_OptFlag;
            }
        }
    }  else {
        // check whether coverage can be safely rolled into alpha
        // of if we can skip color computation and just emit coverage
        if (can_tweak_alpha_for_coverage(fDstBlend)) {
            if (colorPOI.allStagesMultiplyInput()) {
                return GrXferProcessor::kSetCoverageDrawing_OptFlag |
                       GrXferProcessor::kCanTweakAlphaForCoverage_OptFlag;
            } else {
                return GrXferProcessor::kSetCoverageDrawing_OptFlag;

            }
        }
        if (dstCoeffIsZero) {
            if (kZero_GrBlendCoeff == fSrcBlend) {
                // the source color is not included in the blend
                // the dst coeff is effectively zero so blend works out to:
                // (c)(0)D + (1-c)D = (1-c)D.
                fDstBlend = kISA_GrBlendCoeff;
                return GrXferProcessor::kIgnoreColor_OptFlag |
                       GrXferProcessor::kSetCoverageDrawing_OptFlag;
            } else if (srcAIsOne) {
                // the dst coeff is effectively zero so blend works out to:
                // cS + (c)(0)D + (1-c)D = cS + (1-c)D.
                // If Sa is 1 then we can replace Sa with c
                // and set dst coeff to 1-Sa.
                fDstBlend = kISA_GrBlendCoeff;
                if (colorPOI.allStagesMultiplyInput()) {
                    return GrXferProcessor::kSetCoverageDrawing_OptFlag |
                           GrXferProcessor::kCanTweakAlphaForCoverage_OptFlag;
                } else {
                    return GrXferProcessor::kSetCoverageDrawing_OptFlag;

                }
            }
        } else if (dstCoeffIsOne) {
            // the dst coeff is effectively one so blend works out to:
            // cS + (c)(1)D + (1-c)D = cS + D.
            fDstBlend = kOne_GrBlendCoeff;
            if (colorPOI.allStagesMultiplyInput()) {
                return GrXferProcessor::kSetCoverageDrawing_OptFlag |
                       GrXferProcessor::kCanTweakAlphaForCoverage_OptFlag;
            } else {
                return GrXferProcessor::kSetCoverageDrawing_OptFlag;

            }
            return GrXferProcessor::kSetCoverageDrawing_OptFlag;
        }
    }

    return GrXferProcessor::kNone_Opt;
}
Exemple #20
0
GrXferProcessor::OptFlags CustomXP::onGetOptimizations(const GrProcOptInfo& colorPOI,
                                                       const GrProcOptInfo& coveragePOI,
                                                       bool doesStencilWrite,
                                                       GrColor* overrideColor,
                                                       const GrCaps& caps) {
  /*
    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_OptFlags;
    if (colorPOI.allStagesMultiplyInput()) {
        flags |= kCanTweakAlphaForCoverage_OptFlag;
    }
    if (this->hasHWBlendEquation() && coveragePOI.isSolidWhite()) {
        flags |= kIgnoreCoverage_OptFlag;
    }
    return flags;
}
void GrPorterDuffXPFactory::getInvariantOutput(const GrProcOptInfo& colorPOI,
                                               const GrProcOptInfo& coveragePOI,
                                               GrXPFactory::InvariantOutput* output) const {
    if (!coveragePOI.isSolidWhite()) {
        output->fWillBlendWithDst = true;
        output->fBlendedColorFlags = 0;
        return;
    }

    GrBlendCoeff srcCoeff = fSrcCoeff;
    GrBlendCoeff dstCoeff = fDstCoeff;

    // TODO: figure out to merge this simplify with other current optimization code paths and
    // eventually remove from GrBlend
    GrSimplifyBlend(&srcCoeff, &dstCoeff, colorPOI.color(), colorPOI.validFlags(),
                    0, 0, 0);

    if (GrBlendCoeffRefsDst(srcCoeff)) {
        output->fWillBlendWithDst = true;
        output->fBlendedColorFlags = 0;
        return;
    }

    if (kZero_GrBlendCoeff != dstCoeff) {
        bool srcAIsOne = colorPOI.isOpaque();
        if (kISA_GrBlendCoeff != dstCoeff || !srcAIsOne) {
            output->fWillBlendWithDst = true;
        }
        output->fBlendedColorFlags = 0;
        return;
    }

    switch (srcCoeff) {
        case kZero_GrBlendCoeff:
            output->fBlendedColor = 0;
            output->fBlendedColorFlags = kRGBA_GrColorComponentFlags;
            break;

        case kOne_GrBlendCoeff:
            output->fBlendedColor = colorPOI.color();
            output->fBlendedColorFlags = colorPOI.validFlags();
            break;

            // The src coeff should never refer to the src and if it refers to dst then opaque
            // should have been false.
        case kSC_GrBlendCoeff:
        case kISC_GrBlendCoeff:
        case kDC_GrBlendCoeff:
        case kIDC_GrBlendCoeff:
        case kSA_GrBlendCoeff:
        case kISA_GrBlendCoeff:
        case kDA_GrBlendCoeff:
        case kIDA_GrBlendCoeff:
        default:
            SkFAIL("srcCoeff should not refer to src or dst.");
            break;

            // TODO: update this once GrPaint actually has a const color.
        case kConstC_GrBlendCoeff:
        case kIConstC_GrBlendCoeff:
        case kConstA_GrBlendCoeff:
        case kIConstA_GrBlendCoeff:
            output->fBlendedColorFlags = 0;
            break;
    }

    output->fWillBlendWithDst = false;
}