blob: 5038aa333fa21b67e36bd865d72d85f787444bcb [file] [log] [blame]
/*
* 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);
}