Implement Porter Duff XP with a blend table

Removes the runtime logic used by PorterDuffXferProcessor to decide
blend coeffs and shader outputs, and instead uses a compile-time
constant table of pre-selected blend formulas.

Introduces a new blend strategy for srcCoeff=0 that can apply coverage
with a reverse subtract blend equation instead of dual source
blending.

Adds new macros in GrBlend.h to analyze blend formulas both runtime.

Removes kSetCoverageDrawing_OptFlag and GrSimplifyBlend as they are no
longer used.

Adds a GM that verifies all xfermodes, including arithmetic, with the
color/coverage invariants used by Porter Duff.

Adds a unit test that verifies each Porter Duff formula with every
color/coverage invariant.

Major changes:

 * Uses a reverse subtract blend equation for coverage when srcCoeff=0
   (clear, dst-out [Sa=1], dst-in, modulate). Platforms that don't
   support dual source blending no longer require a dst copy for
   dst-in and modulate.

 * Sets BlendInfo::fWriteColor to false when the blend does not modify
   the dst. GrGLGpu will now use glColorMask instead of blending for
   these modes (dst, dst-in [Sa=1], modulate ignored for [Sc=1]).

 * Converts all SA blend coeffs to One for opaque inputs, and ISA to
   Zero if there is also no coverage. (We keep ISA around when there
   is coverage because we use it to tweak alpha for coverage.)

 * Abandons solid white optimizations for the sake of simplicity
   (screen was the only mode that previous had solid white opts).

Minor differences:

 * Inconsequential differences in opt flags (e.g. we now return
   kCanTweakAlphaForCoverage_OptFlag even when there is no coverage).

 * Src coeffs when the shader outputs 0.

 * IS2C vs IS2A when the secondary output is scalar.

BUG=skia:

Review URL: https://codereview.chromium.org/1124373002
This commit is contained in:
cdalton 2015-05-22 11:36:57 -07:00 committed by Commit bot
parent 2a97c55ae3
commit 9a70920db2
9 changed files with 2040 additions and 631 deletions

191
gm/aaxfermodes.cpp Normal file
View File

@ -0,0 +1,191 @@
/*
* 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 "gm.h"
#include "SkArithmeticMode.h"
#include "SkShader.h"
#include "SkXfermode.h"
enum {
kXfermodeCount = SkXfermode::kLastMode + 2, // All xfermodes plus arithmetic mode.
kShapeSize = 22,
kShapeSpacing = 36,
kShapeTypeSpacing = 4 * kShapeSpacing/3,
kPaintSpacing = 2 * kShapeSpacing,
kPadding = (kPaintSpacing - kShapeSpacing) / 2,
kPaintWidth = 3*kShapeSpacing + 2*kShapeTypeSpacing + kShapeSize,
kPaintPadding = kPaintSpacing - kShapeSize,
};
static const SkColor kBGColor = SkColorSetARGB(200, 210, 184, 135);
static const SkColor kShapeColors[3] = {
SkColorSetARGB(130, 255, 0, 128), // input color unknown
SkColorSetARGB(255, 0, 255, 255), // input color opaque
SkColorSetARGB(255, 255, 255, 255) // input solid white
};
enum Shape {
kSquare_Shape,
kDiamond_Shape,
kOval_Shape,
kLast_Shape = kOval_Shape
};
namespace skiagm {
static void draw_shape(SkCanvas* canvas, Shape shape, const SkColor color, size_t xfermodeIdx);
/**
* Verifies AA works properly on all Xfermodes, including arithmetic, with various color invariants.
*/
class AAXfermodesGM : public GM {
public:
AAXfermodesGM() {}
protected:
SkString onShortName() override {
return SkString("aaxfermodes");
}
SkISize onISize() override {
return SkISize::Make(3*kPaintWidth + 2*kPaintPadding + 2*kPadding,
(2 + SkXfermode::kLastCoeffMode) * kShapeSpacing + 2*kPadding);
}
void onDraw(SkCanvas* canvas) override {
sk_tool_utils::draw_checkerboard(canvas, 0xffffffff, 0xffc0c0c0, 10);
canvas->saveLayer(NULL, NULL);
canvas->drawColor(kBGColor, SkXfermode::kSrc_Mode);
canvas->translate(kPadding + kShapeSize/2, kPadding + kShapeSpacing + kShapeSize/2);
for (size_t colorIdx = 0; colorIdx < SK_ARRAY_COUNT(kShapeColors); colorIdx++) {
SkColor color = kShapeColors[colorIdx];
for (size_t shapeIdx = 0; shapeIdx <= kLast_Shape; shapeIdx++) {
Shape shape = static_cast<Shape>(shapeIdx);
canvas->save();
for (size_t xfermodeIdx = 0; xfermodeIdx < kXfermodeCount; xfermodeIdx++) {
draw_shape(canvas, shape, color, xfermodeIdx);
if (xfermodeIdx == SkXfermode::kLastCoeffMode) {
// New column.
canvas->restore();
canvas->translate(kShapeSpacing, 0);
canvas->save();
} else {
canvas->translate(0, kShapeSpacing);
}
}
canvas->restore();
if (shape != kLast_Shape) {
canvas->translate(kShapeTypeSpacing, 0);
} else {
canvas->translate(kPaintSpacing, 0);
}
}
}
canvas->restore();
SkPaint textPaint;
textPaint.setAntiAlias(true);
sk_tool_utils::set_portable_typeface(&textPaint);
textPaint.setTextAlign(SkPaint::kCenter_Align);
textPaint.setFakeBoldText(true);
textPaint.setTextSize(21 * kShapeSize/32);
canvas->translate(kPadding + kPaintWidth/2,
kPadding + kShapeSize/2 + textPaint.getTextSize()/4);
canvas->drawText("input color unknown", sizeof("input color unknown") - 1, 0, 0, textPaint);
canvas->translate(kPaintWidth + kPaintPadding, 0);
canvas->drawText("input color opaque", sizeof("input color opaque") - 1, 0, 0, textPaint);
canvas->translate(kPaintWidth + kPaintPadding, 0);
canvas->drawText("input solid white", sizeof("input solid white") - 1, 0, 0, textPaint);
}
private:
typedef GM INHERITED;
};
static void draw_shape(SkCanvas* canvas, Shape shape, const SkColor color, size_t xfermodeIdx) {
SkPaint shapePaint;
shapePaint.setAntiAlias(kSquare_Shape != shape);
shapePaint.setColor(color);
SkAutoTUnref<SkXfermode> xfermode;
if (xfermodeIdx <= SkXfermode::kLastMode) {
SkXfermode::Mode mode = static_cast<SkXfermode::Mode>(xfermodeIdx);
xfermode.reset(SkXfermode::Create(mode));
} else {
xfermode.reset(SkArithmeticMode::Create(+1.0f, +0.25f, -0.5f, +0.1f));
}
shapePaint.setXfermode(xfermode);
if (xfermodeIdx == SkXfermode::kPlus_Mode) {
// Check for overflow and dim the src and dst colors if we need to, otherwise we might get
// confusing AA artifacts.
int maxSum = SkTMax(SkTMax(SkColorGetA(kBGColor) + SkColorGetA(color),
SkColorGetR(kBGColor) + SkColorGetR(color)),
SkTMax(SkColorGetG(kBGColor) + SkColorGetG(color),
SkColorGetB(kBGColor) + SkColorGetB(color)));
if (maxSum > 255) {
SkPaint dimPaint;
dimPaint.setARGB(255 * 255 / maxSum, 0, 0, 0);
dimPaint.setAntiAlias(false);
dimPaint.setXfermode(SkXfermode::Create(SkXfermode::kDstIn_Mode));
canvas->drawRectCoords(-kShapeSpacing/2, -kShapeSpacing/2,
kShapeSpacing/2, kShapeSpacing/2, dimPaint);
shapePaint.setAlpha(255 * shapePaint.getAlpha() / maxSum);
}
}
switch (shape) {
case kSquare_Shape:
canvas->drawRectCoords(-kShapeSize/2, -kShapeSize/2, kShapeSize/2, kShapeSize/2,
shapePaint);
break;
case kDiamond_Shape:
canvas->save();
canvas->rotate(45);
canvas->drawRectCoords(-kShapeSize/2, -kShapeSize/2, kShapeSize/2, kShapeSize/2,
shapePaint);
canvas->restore();
break;
case kOval_Shape:
canvas->save();
canvas->rotate(static_cast<SkScalar>((511 * xfermodeIdx + 257) % 360));
canvas->drawArc(SkRect::MakeLTRB(-kShapeSize/2, -1.4f * kShapeSize/2,
kShapeSize/2, 1.4f * kShapeSize/2),
0, 360, true, shapePaint);
canvas->restore();
break;
default:
SkFAIL("Invalid shape.");
}
}
//////////////////////////////////////////////////////////////////////////////
static GM* MyFactory(void*) { return new AAXfermodesGM; }
static GMRegistry reg(MyFactory);
}

View File

@ -79,7 +79,6 @@
'<(skia_src_path)/gpu/GrBatchTarget.h',
'<(skia_src_path)/gpu/GrBatchTest.cpp',
'<(skia_src_path)/gpu/GrBatchTest.h',
'<(skia_src_path)/gpu/GrBlend.cpp',
'<(skia_src_path)/gpu/GrBlend.h',
'<(skia_src_path)/gpu/GrBufferAllocPool.cpp',
'<(skia_src_path)/gpu/GrBufferAllocPool.h',

View File

@ -142,6 +142,8 @@ enum GrColorComponentFlags {
kB_GrColorComponentFlag = 1 << (GrColor_SHIFT_B / 8),
kA_GrColorComponentFlag = 1 << (GrColor_SHIFT_A / 8),
kNone_GrColorComponentFlags = 0,
kRGB_GrColorComponentFlags = (kR_GrColorComponentFlag | kG_GrColorComponentFlag |
kB_GrColorComponentFlag),
@ -149,6 +151,8 @@ enum GrColorComponentFlags {
kB_GrColorComponentFlag | kA_GrColorComponentFlag)
};
GR_MAKE_BITFIELD_OPS(GrColorComponentFlags)
static inline char GrColorComponentFlagToChar(GrColorComponentFlags component) {
SkASSERT(SkIsPow2(component));
switch (component) {

View File

@ -51,10 +51,6 @@ enum GrBlendEquation {
static const int kGrBlendEquationCnt = kLast_GrBlendEquation + 1;
inline bool GrBlendEquationIsAdvanced(GrBlendEquation equation) {
return equation >= kFirstAdvancedGrBlendEquation;
}
/**
* Coeffecients for alpha-blending.
*/
@ -142,10 +138,6 @@ public:
* Clear color stages and override input color to that returned by getOptimizations
*/
kOverrideColor_OptFlag = 0x8,
/**
* Set CoverageDrawing_StateBit
*/
kSetCoverageDrawing_OptFlag = 0x10,
/**
* Can tweak alpha for coverage. Currently this flag should only be used by a batch
*/

View File

@ -24,7 +24,7 @@ public:
GrXPFactory::InvariantOutput*) const override;
private:
GrPorterDuffXPFactory(GrBlendCoeff src, GrBlendCoeff dst);
GrPorterDuffXPFactory(SkXfermode::Mode);
GrXferProcessor* onCreateXferProcessor(const GrCaps& caps,
const GrProcOptInfo& colorPOI,
@ -37,14 +37,15 @@ private:
bool onIsEqual(const GrXPFactory& xpfBase) const override {
const GrPorterDuffXPFactory& xpf = xpfBase.cast<GrPorterDuffXPFactory>();
return (fSrcCoeff == xpf.fSrcCoeff && fDstCoeff == xpf.fDstCoeff);
return fXfermode == xpf.fXfermode;
}
GR_DECLARE_XP_FACTORY_TEST;
static void TestGetXPOutputTypes(const GrXferProcessor*, int* outPrimary, int* outSecondary);
GrBlendCoeff fSrcCoeff;
GrBlendCoeff fDstCoeff;
SkXfermode::Mode fXfermode;
friend class GrPorterDuffTest; // for TestGetXPOutputTypes()
typedef GrXPFactory INHERITED;
};

View File

@ -1,154 +0,0 @@
/*
* Copyright 2013 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "GrBlend.h"
static inline GrBlendCoeff swap_coeff_src_dst(GrBlendCoeff coeff) {
switch (coeff) {
case kDC_GrBlendCoeff:
return kSC_GrBlendCoeff;
case kIDC_GrBlendCoeff:
return kISC_GrBlendCoeff;
case kDA_GrBlendCoeff:
return kSA_GrBlendCoeff;
case kIDA_GrBlendCoeff:
return kISA_GrBlendCoeff;
case kSC_GrBlendCoeff:
return kDC_GrBlendCoeff;
case kISC_GrBlendCoeff:
return kIDC_GrBlendCoeff;
case kSA_GrBlendCoeff:
return kDA_GrBlendCoeff;
case kISA_GrBlendCoeff:
return kIDA_GrBlendCoeff;
default:
return coeff;
}
}
static inline unsigned saturated_add(unsigned a, unsigned b) {
SkASSERT(a <= 255);
SkASSERT(b <= 255);
unsigned sum = a + b;
if (sum > 255) {
sum = 255;
}
return sum;
}
static GrColor add_colors(GrColor src, GrColor dst) {
unsigned r = saturated_add(GrColorUnpackR(src), GrColorUnpackR(dst));
unsigned g = saturated_add(GrColorUnpackG(src), GrColorUnpackG(dst));
unsigned b = saturated_add(GrColorUnpackB(src), GrColorUnpackB(dst));
unsigned a = saturated_add(GrColorUnpackA(src), GrColorUnpackA(dst));
return GrColorPackRGBA(r, g, b, a);
}
static inline bool valid_color(uint32_t compFlags) {
return (kRGBA_GrColorComponentFlags & compFlags) == kRGBA_GrColorComponentFlags;
}
static GrColor simplify_blend_term(GrBlendCoeff* srcCoeff,
GrColor srcColor, uint32_t srcCompFlags,
GrColor dstColor, uint32_t dstCompFlags,
GrColor constantColor) {
SkASSERT(!GrBlendCoeffRefsSrc(*srcCoeff));
SkASSERT(srcCoeff);
// Check whether srcCoeff can be reduced to kOne or kZero based on known color inputs.
// We could pick out the coeff r,g,b,a values here and use them to compute the blend term color,
// if possible, below but that is not implemented now.
switch (*srcCoeff) {
case kIDC_GrBlendCoeff:
dstColor = ~dstColor; // fallthrough
case kDC_GrBlendCoeff:
if (valid_color(dstCompFlags)) {
if (0xffffffff == dstColor) {
*srcCoeff = kOne_GrBlendCoeff;
} else if (0 == dstColor) {
*srcCoeff = kZero_GrBlendCoeff;
}
}
break;
case kIDA_GrBlendCoeff:
dstColor = ~dstColor; // fallthrough
case kDA_GrBlendCoeff:
if (kA_GrColorComponentFlag & dstCompFlags) {
if (0xff == GrColorUnpackA(dstColor)) {
*srcCoeff = kOne_GrBlendCoeff;
} else if (0 == GrColorUnpackA(dstColor)) {
*srcCoeff = kZero_GrBlendCoeff;
}
}
break;
case kIConstC_GrBlendCoeff:
constantColor = ~constantColor; // fallthrough
case kConstC_GrBlendCoeff:
if (0xffffffff == constantColor) {
*srcCoeff = kOne_GrBlendCoeff;
} else if (0 == constantColor) {
*srcCoeff = kZero_GrBlendCoeff;
}
break;
case kIConstA_GrBlendCoeff:
constantColor = ~constantColor; // fallthrough
case kConstA_GrBlendCoeff:
if (0xff == GrColorUnpackA(constantColor)) {
*srcCoeff = kOne_GrBlendCoeff;
} else if (0 == GrColorUnpackA(constantColor)) {
*srcCoeff = kZero_GrBlendCoeff;
}
break;
default:
break;
}
// We may have invalidated these above and shouldn't read them again.
SkDEBUGCODE(dstColor = constantColor = GrColor_ILLEGAL;)
if (kZero_GrBlendCoeff == *srcCoeff || (valid_color(srcCompFlags) && 0 == srcColor)) {
*srcCoeff = kZero_GrBlendCoeff;
return 0;
}
if (kOne_GrBlendCoeff == *srcCoeff && valid_color(srcCompFlags)) {
return srcColor;
} else {
return GrColor_ILLEGAL;
}
}
GrColor GrSimplifyBlend(GrBlendCoeff* srcCoeff,
GrBlendCoeff* dstCoeff,
GrColor srcColor, uint32_t srcCompFlags,
GrColor dstColor, uint32_t dstCompFlags,
GrColor constantColor) {
GrColor srcTermColor = simplify_blend_term(srcCoeff,
srcColor, srcCompFlags,
dstColor, dstCompFlags,
constantColor);
// We call the same function to simplify the dst blend coeff. We trick it out by swapping the
// src and dst.
GrBlendCoeff spoofedCoeff = swap_coeff_src_dst(*dstCoeff);
GrColor dstTermColor = simplify_blend_term(&spoofedCoeff,
dstColor, dstCompFlags,
srcColor, srcCompFlags,
constantColor);
*dstCoeff = swap_coeff_src_dst(spoofedCoeff);
if (GrColor_ILLEGAL != srcTermColor && GrColor_ILLEGAL != dstTermColor) {
return add_colors(srcTermColor, dstTermColor);
} else {
return GrColor_ILLEGAL;
}
}

View File

@ -7,13 +7,22 @@
*/
#include "GrTypes.h"
#include "GrColor.h"
#include "SkTLogic.h"
#include "GrXferProcessor.h"
#ifndef GrBlend_DEFINED
#define GrBlend_DEFINED
static inline bool GrBlendCoeffRefsSrc(GrBlendCoeff coeff) {
template<GrBlendCoeff Coeff>
struct GrTBlendCoeffRefsSrc : SkTBool<kSC_GrBlendCoeff == Coeff ||
kISC_GrBlendCoeff == Coeff ||
kSA_GrBlendCoeff == Coeff ||
kISA_GrBlendCoeff == Coeff> {};
#define GR_BLEND_COEFF_REFS_SRC(COEFF) \
GrTBlendCoeffRefsSrc<COEFF>::value
inline bool GrBlendCoeffRefsSrc(GrBlendCoeff coeff) {
switch (coeff) {
case kSC_GrBlendCoeff:
case kISC_GrBlendCoeff:
@ -25,7 +34,17 @@ static inline bool GrBlendCoeffRefsSrc(GrBlendCoeff coeff) {
}
}
static inline bool GrBlendCoeffRefsDst(GrBlendCoeff coeff) {
template<GrBlendCoeff Coeff>
struct GrTBlendCoeffRefsDst : SkTBool<kDC_GrBlendCoeff == Coeff ||
kIDC_GrBlendCoeff == Coeff ||
kDA_GrBlendCoeff == Coeff ||
kIDA_GrBlendCoeff == Coeff> {};
#define GR_BLEND_COEFF_REFS_DST(COEFF) \
GrTBlendCoeffRefsDst<COEFF>::value
inline bool GrBlendCoeffRefsDst(GrBlendCoeff coeff) {
switch (coeff) {
case kDC_GrBlendCoeff:
case kIDC_GrBlendCoeff:
@ -37,10 +56,88 @@ static inline bool GrBlendCoeffRefsDst(GrBlendCoeff coeff) {
}
}
GrColor GrSimplifyBlend(GrBlendCoeff* srcCoeff,
GrBlendCoeff* dstCoeff,
GrColor srcColor, uint32_t srcCompFlags,
GrColor dstColor, uint32_t dstCompFlags,
GrColor constantColor);
template<GrBlendCoeff Coeff>
struct GrTBlendCoeffRefsSrc2 : SkTBool<kS2C_GrBlendCoeff == Coeff ||
kIS2C_GrBlendCoeff == Coeff ||
kS2A_GrBlendCoeff == Coeff ||
kIS2A_GrBlendCoeff == Coeff> {};
#define GR_BLEND_COEFF_REFS_SRC2(COEFF) \
GrTBlendCoeffRefsSrc2<COEFF>::value
template<GrBlendCoeff SrcCoeff, GrBlendCoeff DstCoeff>
struct GrTBlendCoeffsUseSrcColor : SkTBool<kZero_GrBlendCoeff != SrcCoeff ||
GR_BLEND_COEFF_REFS_SRC(DstCoeff)> {};
#define GR_BLEND_COEFFS_USE_SRC_COLOR(SRC_COEFF, DST_COEFF) \
GrTBlendCoeffsUseSrcColor<SRC_COEFF, DST_COEFF>::value
template<GrBlendCoeff SrcCoeff, GrBlendCoeff DstCoeff>
struct GrTBlendCoeffsUseDstColor : SkTBool<GR_BLEND_COEFF_REFS_DST(SrcCoeff) ||
kZero_GrBlendCoeff != DstCoeff> {};
#define GR_BLEND_COEFFS_USE_DST_COLOR(SRC_COEFF, DST_COEFF) \
GrTBlendCoeffsUseDstColor<SRC_COEFF, DST_COEFF>::value
template<GrBlendEquation Equation>
struct GrTBlendEquationIsAdvanced : SkTBool<Equation >= kFirstAdvancedGrBlendEquation> {};
#define GR_BLEND_EQUATION_IS_ADVANCED(EQUATION) \
GrTBlendEquationIsAdvanced<EQUATION>::value
inline bool GrBlendEquationIsAdvanced(GrBlendEquation equation) {
return equation >= kFirstAdvancedGrBlendEquation;
}
template<GrBlendEquation BlendEquation, GrBlendCoeff SrcCoeff, GrBlendCoeff DstCoeff>
struct GrTBlendModifiesDst : SkTBool<(kAdd_GrBlendEquation != BlendEquation &&
kReverseSubtract_GrBlendEquation != BlendEquation) ||
kZero_GrBlendCoeff != SrcCoeff ||
kOne_GrBlendCoeff != DstCoeff> {};
#define GR_BLEND_MODIFIES_DST(EQUATION, SRC_COEFF, DST_COEFF) \
GrTBlendModifiesDst<EQUATION, SRC_COEFF, DST_COEFF>::value
/**
* Advanced blend equations can always tweak alpha for coverage. (See GrCustomXfermode.cpp)
*
* For "add" and "reverse subtract" the blend equation with f=coverage is:
*
* D' = f * (S * srcCoeff + D * dstCoeff) + (1-f) * D
* = f * S * srcCoeff + D * (f * dstCoeff + (1 - f))
*
* (Let srcCoeff be negative for reverse subtract.) We can tweak alpha for coverage when the
* following relationship holds:
*
* (f*S) * srcCoeff' + D * dstCoeff' == f * S * srcCoeff + D * (f * dstCoeff + (1 - f))
*
* (Where srcCoeff' and dstCoeff' have any reference to S pre-multiplied by f.)
*
* It's easy to see this works for the src term as long as srcCoeff' == srcCoeff (meaning srcCoeff
* does not reference S). For the dst term, this will work as long as the following is true:
*|
* dstCoeff' == f * dstCoeff + (1 - f)
* dstCoeff' == 1 - f * (1 - dstCoeff)
*
* By inspection we can see this will work as long as dstCoeff has a 1, and any other term in
* dstCoeff references S.
*/
template<GrBlendEquation Equation, GrBlendCoeff SrcCoeff, GrBlendCoeff DstCoeff>
struct GrTBlendCanTweakAlphaForCoverage : SkTBool<GR_BLEND_EQUATION_IS_ADVANCED(Equation) ||
((kAdd_GrBlendEquation == Equation ||
kReverseSubtract_GrBlendEquation == Equation) &&
!GR_BLEND_COEFF_REFS_SRC(SrcCoeff) &&
(kOne_GrBlendCoeff == DstCoeff ||
kISC_GrBlendCoeff == DstCoeff ||
kISA_GrBlendCoeff == DstCoeff))> {};
#define GR_BLEND_CAN_TWEAK_ALPHA_FOR_COVERAGE(EQUATION, SRC_COEFF, DST_COEFF) \
GrTBlendCanTweakAlphaForCoverage<EQUATION, SRC_COEFF, DST_COEFF>::value
#endif

File diff suppressed because it is too large Load Diff

1280
tests/GrPorterDuffTest.cpp Normal file

File diff suppressed because it is too large Load Diff