| /* |
| * Copyright 2014 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "effects/GrPorterDuffXferProcessor.h" |
| |
| #include "GrBlend.h" |
| #include "GrDrawTargetCaps.h" |
| #include "GrProcessor.h" |
| #include "GrProcOptInfo.h" |
| #include "GrTypes.h" |
| #include "GrXferProcessor.h" |
| #include "gl/GrGLXferProcessor.h" |
| #include "gl/builders/GrGLFragmentShaderBuilder.h" |
| #include "gl/builders/GrGLProgramBuilder.h" |
| |
| static bool can_tweak_alpha_for_coverage(GrBlendCoeff dstCoeff) { |
| /* |
| The fractional coverage is f. |
| The src and dst coeffs are Cs and Cd. |
| The dst and src colors are S and D. |
| We want the blend to compute: f*Cs*S + (f*Cd + (1-f))D. By tweaking the source color's alpha |
| we're replacing S with S'=fS. It's obvious that that first term will always be ok. The second |
| term can be rearranged as [1-(1-Cd)f]D. By substituting in the various possibilities for Cd we |
| find that only 1, ISA, and ISC produce the correct destination when applied to S' and D. |
| */ |
| return kOne_GrBlendCoeff == dstCoeff || |
| kISA_GrBlendCoeff == dstCoeff || |
| kISC_GrBlendCoeff == dstCoeff; |
| } |
| |
| class GrGLPorterDuffXferProcessor : public GrGLXferProcessor { |
| public: |
| GrGLPorterDuffXferProcessor(const GrProcessor&) {} |
| |
| virtual ~GrGLPorterDuffXferProcessor() {} |
| |
| void emitCode(const EmitArgs& args) SK_OVERRIDE { |
| const GrPorterDuffXferProcessor& xp = args.fXP.cast<GrPorterDuffXferProcessor>(); |
| GrGLFPFragmentBuilder* fsBuilder = args.fPB->getFragmentShaderBuilder(); |
| if (xp.hasSecondaryOutput()) { |
| switch(xp.secondaryOutputType()) { |
| case GrPorterDuffXferProcessor::kCoverage_SecondaryOutputType: |
| fsBuilder->codeAppendf("%s = %s;", args.fOutputSecondary, args.fInputCoverage); |
| break; |
| case GrPorterDuffXferProcessor::kCoverageISA_SecondaryOutputType: |
| fsBuilder->codeAppendf("%s = (1.0 - %s.a) * %s;", |
| args.fOutputSecondary, args.fInputColor, |
| args.fInputCoverage); |
| break; |
| case GrPorterDuffXferProcessor::kCoverageISC_SecondaryOutputType: |
| fsBuilder->codeAppendf("%s = (vec4(1.0) - %s) * %s;", |
| args.fOutputSecondary, args.fInputColor, |
| args.fInputCoverage); |
| break; |
| default: |
| SkFAIL("Unexpected Secondary Output"); |
| } |
| } |
| |
| switch (xp.primaryOutputType()) { |
| case GrPorterDuffXferProcessor::kNone_PrimaryOutputType: |
| fsBuilder->codeAppendf("%s = vec4(0);", args.fOutputPrimary); |
| break; |
| case GrPorterDuffXferProcessor::kColor_PrimaryOutputType: |
| fsBuilder->codeAppendf("%s = %s;", args.fOutputPrimary, args.fInputColor); |
| break; |
| case GrPorterDuffXferProcessor::kCoverage_PrimaryOutputType: |
| fsBuilder->codeAppendf("%s = %s;", args.fOutputPrimary, args.fInputCoverage); |
| break; |
| case GrPorterDuffXferProcessor::kModulate_PrimaryOutputType: |
| fsBuilder->codeAppendf("%s = %s * %s;", args.fOutputPrimary, args.fInputColor, |
| args.fInputCoverage); |
| break; |
| default: |
| SkFAIL("Unexpected Primary Output"); |
| } |
| } |
| |
| void setData(const GrGLProgramDataManager&, const GrXferProcessor&) SK_OVERRIDE {}; |
| |
| static void GenKey(const GrProcessor& processor, const GrGLCaps& caps, |
| GrProcessorKeyBuilder* b) { |
| const GrPorterDuffXferProcessor& xp = processor.cast<GrPorterDuffXferProcessor>(); |
| b->add32(xp.primaryOutputType()); |
| b->add32(xp.secondaryOutputType()); |
| }; |
| |
| private: |
| typedef GrGLXferProcessor INHERITED; |
| }; |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| GrPorterDuffXferProcessor::GrPorterDuffXferProcessor(GrBlendCoeff srcBlend, GrBlendCoeff dstBlend, |
| GrColor constant) |
| : fSrcBlend(srcBlend) |
| , fDstBlend(dstBlend) |
| , fBlendConstant(constant) |
| , fPrimaryOutputType(kModulate_PrimaryOutputType) |
| , fSecondaryOutputType(kNone_SecondaryOutputType) { |
| this->initClassID<GrPorterDuffXferProcessor>(); |
| } |
| |
| GrPorterDuffXferProcessor::~GrPorterDuffXferProcessor() { |
| } |
| |
| void GrPorterDuffXferProcessor::getGLProcessorKey(const GrGLCaps& caps, |
| GrProcessorKeyBuilder* b) const { |
| GrGLPorterDuffXferProcessor::GenKey(*this, caps, b); |
| } |
| |
| GrGLXferProcessor* GrPorterDuffXferProcessor::createGLInstance() const { |
| return SkNEW_ARGS(GrGLPorterDuffXferProcessor, (*this)); |
| } |
| |
| GrXferProcessor::OptFlags |
| GrPorterDuffXferProcessor::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 GrPorterDuffXferProcessor::calcOutputTypes(GrXferProcessor::OptFlags optFlags, |
| const GrDrawTargetCaps& caps, |
| bool hasSolidCoverage) { |
| if (optFlags & kIgnoreColor_OptFlag) { |
| if (optFlags & kIgnoreCoverage_OptFlag) { |
| fPrimaryOutputType = kNone_PrimaryOutputType; |
| return; |
| } else { |
| fPrimaryOutputType = kCoverage_PrimaryOutputType; |
| return; |
| } |
| } else if (optFlags & kIgnoreCoverage_OptFlag) { |
| fPrimaryOutputType = kColor_PrimaryOutputType; |
| return; |
| } |
| |
| // If we do have coverage determine whether it matters. Dual source blending is expensive so |
| // we don't do it if we are doing coverage drawing. If we aren't then We always do dual source |
| // blending if we have any effective coverage stages OR the geometry processor doesn't emits |
| // solid coverage. |
| if (!(optFlags & kSetCoverageDrawing_OptFlag) && !hasSolidCoverage) { |
| if (caps.dualSourceBlendingSupport()) { |
| if (kZero_GrBlendCoeff == fDstBlend) { |
| // write the coverage value to second color |
| fSecondaryOutputType = kCoverage_SecondaryOutputType; |
| fDstBlend = (GrBlendCoeff)GrGpu::kIS2C_GrBlendCoeff; |
| } else if (kSA_GrBlendCoeff == fDstBlend) { |
| // SA dst coeff becomes 1-(1-SA)*coverage when dst is partially covered. |
| fSecondaryOutputType = kCoverageISA_SecondaryOutputType; |
| fDstBlend = (GrBlendCoeff)GrGpu::kIS2C_GrBlendCoeff; |
| } else if (kSC_GrBlendCoeff == fDstBlend) { |
| // SA dst coeff becomes 1-(1-SA)*coverage when dst is partially covered. |
| fSecondaryOutputType = kCoverageISC_SecondaryOutputType; |
| fDstBlend = (GrBlendCoeff)GrGpu::kIS2C_GrBlendCoeff; |
| } |
| } |
| } |
| } |
| |
| GrXferProcessor::OptFlags |
| GrPorterDuffXferProcessor::internalGetOptimizations(const GrProcOptInfo& colorPOI, |
| const GrProcOptInfo& coveragePOI, |
| bool doesStencilWrite) { |
| bool srcAIsOne; |
| bool hasCoverage; |
| |
| srcAIsOne = colorPOI.isOpaque(); |
| 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)) { |
| 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; |
| 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; |
| return GrXferProcessor::kSetCoverageDrawing_OptFlag; |
| } |
| } |
| |
| return GrXferProcessor::kNone_Opt; |
| } |
| |
| bool GrPorterDuffXferProcessor::hasSecondaryOutput() const { |
| return kNone_SecondaryOutputType != fSecondaryOutputType; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| GrPorterDuffXPFactory::GrPorterDuffXPFactory(GrBlendCoeff src, GrBlendCoeff dst) |
| : fSrcCoeff(src), fDstCoeff(dst) { |
| this->initClassID<GrPorterDuffXPFactory>(); |
| } |
| |
| GrXPFactory* GrPorterDuffXPFactory::Create(SkXfermode::Mode mode) { |
| switch (mode) { |
| case SkXfermode::kClear_Mode: { |
| static GrPorterDuffXPFactory gClearPDXPF(kZero_GrBlendCoeff, kZero_GrBlendCoeff); |
| return SkRef(&gClearPDXPF); |
| break; |
| } |
| case SkXfermode::kSrc_Mode: { |
| static GrPorterDuffXPFactory gSrcPDXPF(kOne_GrBlendCoeff, kZero_GrBlendCoeff); |
| return SkRef(&gSrcPDXPF); |
| break; |
| } |
| case SkXfermode::kDst_Mode: { |
| static GrPorterDuffXPFactory gDstPDXPF(kZero_GrBlendCoeff, kOne_GrBlendCoeff); |
| return SkRef(&gDstPDXPF); |
| break; |
| } |
| case SkXfermode::kSrcOver_Mode: { |
| static GrPorterDuffXPFactory gSrcOverPDXPF(kOne_GrBlendCoeff, kISA_GrBlendCoeff); |
| return SkRef(&gSrcOverPDXPF); |
| break; |
| } |
| case SkXfermode::kDstOver_Mode: { |
| static GrPorterDuffXPFactory gDstOverPDXPF(kIDA_GrBlendCoeff, kOne_GrBlendCoeff); |
| return SkRef(&gDstOverPDXPF); |
| break; |
| } |
| case SkXfermode::kSrcIn_Mode: { |
| static GrPorterDuffXPFactory gSrcInPDXPF(kDA_GrBlendCoeff, kZero_GrBlendCoeff); |
| return SkRef(&gSrcInPDXPF); |
| break; |
| } |
| case SkXfermode::kDstIn_Mode: { |
| static GrPorterDuffXPFactory gDstInPDXPF(kZero_GrBlendCoeff, kSA_GrBlendCoeff); |
| return SkRef(&gDstInPDXPF); |
| break; |
| } |
| case SkXfermode::kSrcOut_Mode: { |
| static GrPorterDuffXPFactory gSrcOutPDXPF(kIDA_GrBlendCoeff, kZero_GrBlendCoeff); |
| return SkRef(&gSrcOutPDXPF); |
| break; |
| } |
| case SkXfermode::kDstOut_Mode: { |
| static GrPorterDuffXPFactory gDstOutPDXPF(kZero_GrBlendCoeff, kISA_GrBlendCoeff); |
| return SkRef(&gDstOutPDXPF); |
| break; |
| } |
| case SkXfermode::kSrcATop_Mode: { |
| static GrPorterDuffXPFactory gSrcATopPDXPF(kDA_GrBlendCoeff, kISA_GrBlendCoeff); |
| return SkRef(&gSrcATopPDXPF); |
| break; |
| } |
| case SkXfermode::kDstATop_Mode: { |
| static GrPorterDuffXPFactory gDstATopPDXPF(kIDA_GrBlendCoeff, kSA_GrBlendCoeff); |
| return SkRef(&gDstATopPDXPF); |
| break; |
| } |
| case SkXfermode::kXor_Mode: { |
| static GrPorterDuffXPFactory gXorPDXPF(kIDA_GrBlendCoeff, kISA_GrBlendCoeff); |
| return SkRef(&gXorPDXPF); |
| break; |
| } |
| case SkXfermode::kPlus_Mode: { |
| static GrPorterDuffXPFactory gPlusPDXPF(kOne_GrBlendCoeff, kOne_GrBlendCoeff); |
| return SkRef(&gPlusPDXPF); |
| break; |
| } |
| case SkXfermode::kModulate_Mode: { |
| static GrPorterDuffXPFactory gModulatePDXPF(kZero_GrBlendCoeff, kSC_GrBlendCoeff); |
| return SkRef(&gModulatePDXPF); |
| break; |
| } |
| case SkXfermode::kScreen_Mode: { |
| static GrPorterDuffXPFactory gScreenPDXPF(kOne_GrBlendCoeff, kISC_GrBlendCoeff); |
| return SkRef(&gScreenPDXPF); |
| break; |
| } |
| default: |
| return NULL; |
| } |
| } |
| |
| GrXferProcessor* GrPorterDuffXPFactory::createXferProcessor(const GrProcOptInfo& colorPOI, |
| const GrProcOptInfo& covPOI) const { |
| if (!covPOI.isFourChannelOutput()) { |
| return GrPorterDuffXferProcessor::Create(fSrcCoeff, fDstCoeff); |
| } else { |
| if (this->supportsRGBCoverage(colorPOI.color(), colorPOI.validFlags())) { |
| SkASSERT(kRGBA_GrColorComponentFlags == colorPOI.validFlags()); |
| GrColor blendConstant = GrUnPreMulColor(colorPOI.color()); |
| return GrPorterDuffXferProcessor::Create(kConstC_GrBlendCoeff, kISC_GrBlendCoeff, |
| blendConstant); |
| } else { |
| return NULL; |
| } |
| } |
| } |
| |
| bool GrPorterDuffXPFactory::supportsRGBCoverage(GrColor /*knownColor*/, |
| uint32_t knownColorFlags) const { |
| if (kOne_GrBlendCoeff == fSrcCoeff && kISA_GrBlendCoeff == fDstCoeff && |
| kRGBA_GrColorComponentFlags == knownColorFlags) { |
| return true; |
| } |
| return false; |
| } |
| |
| bool GrPorterDuffXPFactory::canApplyCoverage(const GrProcOptInfo& colorPOI, |
| const GrProcOptInfo& coveragePOI) const { |
| bool srcAIsOne = colorPOI.isOpaque(); |
| |
| bool dstCoeffIsOne = kOne_GrBlendCoeff == fDstCoeff || |
| (kSA_GrBlendCoeff == fDstCoeff && srcAIsOne); |
| bool dstCoeffIsZero = kZero_GrBlendCoeff == fDstCoeff || |
| (kISA_GrBlendCoeff == fDstCoeff && srcAIsOne); |
| |
| if ((kZero_GrBlendCoeff == fSrcCoeff && dstCoeffIsOne)) { |
| return true; |
| } |
| |
| // if we don't have coverage we can check whether the dst |
| // has to read at all. |
| // check whether coverage can be safely rolled into alpha |
| // of if we can skip color computation and just emit coverage |
| if (this->canTweakAlphaForCoverage()) { |
| return true; |
| } |
| if (dstCoeffIsZero) { |
| if (kZero_GrBlendCoeff == fSrcCoeff) { |
| return true; |
| } else if (srcAIsOne) { |
| return true; |
| } |
| } else if (dstCoeffIsOne) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool GrPorterDuffXPFactory::canTweakAlphaForCoverage() const { |
| return can_tweak_alpha_for_coverage(fDstCoeff); |
| } |
| |
| 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; |
| } |
| |
| GR_DEFINE_XP_FACTORY_TEST(GrPorterDuffXPFactory); |
| |
| GrXPFactory* GrPorterDuffXPFactory::TestCreate(SkRandom* random, |
| GrContext*, |
| const GrDrawTargetCaps&, |
| GrTexture*[]) { |
| GrBlendCoeff src; |
| do { |
| src = GrBlendCoeff(random->nextRangeU(kFirstPublicGrBlendCoeff, kLastPublicGrBlendCoeff)); |
| } while (GrBlendCoeffRefsSrc(src)); |
| |
| GrBlendCoeff dst; |
| do { |
| dst = GrBlendCoeff(random->nextRangeU(kFirstPublicGrBlendCoeff, kLastPublicGrBlendCoeff)); |
| } while (GrBlendCoeffRefsDst(dst)); |
| |
| return GrPorterDuffXPFactory::Create(src, dst); |
| } |
| |