[graphite] Add fixed-function blending support

Bug: skia:12701
Change-Id: I1fd8dede3eb216c28408bd613119448704c0e7c7
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/512356
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
Commit-Queue: Robert Phillips <robertphillips@google.com>
This commit is contained in:
Robert Phillips 2022-03-10 17:01:46 -05:00 committed by SkCQ
parent 02527b7182
commit 44263c5d08
7 changed files with 266 additions and 29 deletions

View File

@ -328,16 +328,104 @@ MTLVertexDescriptor* create_vertex_descriptor(const RenderStep* step) {
return vertexDescriptor;
}
// TODO: share this w/ Ganesh Metal backend?
static MTLBlendFactor blend_coeff_to_mtl_blend(skgpu::BlendCoeff coeff) {
switch (coeff) {
case skgpu::BlendCoeff::kZero:
return MTLBlendFactorZero;
case skgpu::BlendCoeff::kOne:
return MTLBlendFactorOne;
case skgpu::BlendCoeff::kSC:
return MTLBlendFactorSourceColor;
case skgpu::BlendCoeff::kISC:
return MTLBlendFactorOneMinusSourceColor;
case skgpu::BlendCoeff::kDC:
return MTLBlendFactorDestinationColor;
case skgpu::BlendCoeff::kIDC:
return MTLBlendFactorOneMinusDestinationColor;
case skgpu::BlendCoeff::kSA:
return MTLBlendFactorSourceAlpha;
case skgpu::BlendCoeff::kISA:
return MTLBlendFactorOneMinusSourceAlpha;
case skgpu::BlendCoeff::kDA:
return MTLBlendFactorDestinationAlpha;
case skgpu::BlendCoeff::kIDA:
return MTLBlendFactorOneMinusDestinationAlpha;
case skgpu::BlendCoeff::kConstC:
return MTLBlendFactorBlendColor;
case skgpu::BlendCoeff::kIConstC:
return MTLBlendFactorOneMinusBlendColor;
case skgpu::BlendCoeff::kS2C:
if (@available(macOS 10.12, iOS 11.0, *)) {
return MTLBlendFactorSource1Color;
} else {
return MTLBlendFactorZero;
}
case skgpu::BlendCoeff::kIS2C:
if (@available(macOS 10.12, iOS 11.0, *)) {
return MTLBlendFactorOneMinusSource1Color;
} else {
return MTLBlendFactorZero;
}
case skgpu::BlendCoeff::kS2A:
if (@available(macOS 10.12, iOS 11.0, *)) {
return MTLBlendFactorSource1Alpha;
} else {
return MTLBlendFactorZero;
}
case skgpu::BlendCoeff::kIS2A:
if (@available(macOS 10.12, iOS 11.0, *)) {
return MTLBlendFactorOneMinusSource1Alpha;
} else {
return MTLBlendFactorZero;
}
case skgpu::BlendCoeff::kIllegal:
return MTLBlendFactorZero;
}
SK_ABORT("Unknown blend coefficient");
}
// TODO: share this w/ Ganesh Metal backend?
static MTLBlendOperation blend_equation_to_mtl_blend_op(skgpu::BlendEquation equation) {
static const MTLBlendOperation gTable[] = {
MTLBlendOperationAdd, // skgpu::BlendEquation::kAdd
MTLBlendOperationSubtract, // skgpu::BlendEquation::kSubtract
MTLBlendOperationReverseSubtract, // skgpu::BlendEquation::kReverseSubtract
};
static_assert(SK_ARRAY_COUNT(gTable) == (int)skgpu::BlendEquation::kFirstAdvanced);
static_assert(0 == (int)skgpu::BlendEquation::kAdd);
static_assert(1 == (int)skgpu::BlendEquation::kSubtract);
static_assert(2 == (int)skgpu::BlendEquation::kReverseSubtract);
SkASSERT((unsigned)equation < skgpu::kBlendEquationCnt);
return gTable[(int)equation];
}
static MTLRenderPipelineColorAttachmentDescriptor* create_color_attachment(
MTLPixelFormat format,
const SkPipelineData::BlendInfo& blendInfo) {
skgpu::BlendEquation equation = blendInfo.fEquation;
skgpu::BlendCoeff srcCoeff = blendInfo.fSrcBlend;
skgpu::BlendCoeff dstCoeff = blendInfo.fDstBlend;
bool blendOn = !skgpu::BlendShouldDisable(equation, srcCoeff, dstCoeff);
// TODO: I *think* this gets cleaned up by the pipelineDescriptor?
auto mtlColorAttachment = [[MTLRenderPipelineColorAttachmentDescriptor alloc] init];
mtlColorAttachment.pixelFormat = format;
mtlColorAttachment.blendingEnabled = FALSE;
mtlColorAttachment.blendingEnabled = blendOn;
if (blendOn) {
mtlColorAttachment.sourceRGBBlendFactor = blend_coeff_to_mtl_blend(srcCoeff);
mtlColorAttachment.destinationRGBBlendFactor = blend_coeff_to_mtl_blend(dstCoeff);
mtlColorAttachment.rgbBlendOperation = blend_equation_to_mtl_blend_op(equation);
mtlColorAttachment.sourceAlphaBlendFactor = blend_coeff_to_mtl_blend(srcCoeff);
mtlColorAttachment.destinationAlphaBlendFactor = blend_coeff_to_mtl_blend(dstCoeff);
mtlColorAttachment.alphaBlendOperation = blend_equation_to_mtl_blend_op(equation);
}
mtlColorAttachment.writeMask = blendInfo.fWritesColor ? MTLColorWriteMaskAll
: MTLColorWriteMaskNone;

View File

@ -50,8 +50,47 @@ sk_sp<SkShader> create_image_shader() {
sk_sp<SkShader> create_blend_shader(SkBlendMode bm) {
constexpr SkColor4f kTransYellow = {1.0f, 1.0f, 0.0f, 0.5f};
sk_sp<SkShader> solid = SkShaders::Color(kTransYellow, nullptr);
return SkShaders::Blend(bm, std::move(solid), create_image_shader());
sk_sp<SkShader> dst = SkShaders::Color(kTransYellow, nullptr);
return SkShaders::Blend(bm, std::move(dst), create_image_shader());
}
void draw_blend_mode_swatches(SkCanvas* canvas, SkRect clipRect) {
static const int kTileHeight = 16;
static const int kTileWidth = 16;
static const SkColor4f kOpaqueWhite { 1.0f, 1.0f, 1.0f, 1.0f };
static const SkColor4f kTransBluish { 0.0f, 0.5f, 1.0f, 0.5f };
static const SkColor4f kTransWhite { 1.0f, 1.0f, 1.0f, 0.75f };
SkPaint dstPaint;
dstPaint.setColor(kOpaqueWhite);
dstPaint.setBlendMode(SkBlendMode::kSrc);
dstPaint.setAntiAlias(false);
SkPaint srcPaint;
srcPaint.setColor(kTransBluish);
srcPaint.setAntiAlias(false);
SkRect r = SkRect::MakeXYWH(clipRect.fLeft, clipRect.fTop, kTileWidth, kTileHeight);
// For the first pass we draw: transparent bluish on top of opaque white
// For the second pass we draw: transparent white on top of transparent bluish
for (int passes = 0; passes < 2; ++passes) {
for (int i = 0; i <= (int)SkBlendMode::kLastCoeffMode; ++i) {
if (r.fLeft+kTileWidth > clipRect.fRight) {
r.offsetTo(clipRect.fLeft, r.fTop+kTileHeight);
}
canvas->drawRect(r.makeInset(1.0f, 1.0f), dstPaint);
srcPaint.setBlendMode(static_cast<SkBlendMode>(i));
canvas->drawRect(r.makeInset(2.0f, 2.0f), srcPaint);
r.offset(kTileWidth, 0.0f);
}
r.offsetTo(clipRect.fLeft, r.fTop+kTileHeight);
srcPaint.setColor(kTransWhite);
dstPaint.setColor(kTransBluish);
}
}
} // anonymous namespace
@ -62,17 +101,20 @@ namespace skiagm {
class GraphiteStartGM : public GM {
public:
GraphiteStartGM() {
this->setBGColor(0xFFCCCCCC);
this->setBGColor(SK_ColorBLACK);
GetResourceAsBitmap("images/color_wheel.gif", &fBitmap);
}
protected:
static const int kWidth = 256;
static const int kHeight = 384;
SkString onShortName() override {
return SkString("graphitestart");
}
SkISize onISize() override {
return SkISize::Make(256, 384);
return SkISize::Make(kWidth, kHeight);
}
void onDraw(SkCanvas* canvas) override {
@ -110,8 +152,11 @@ protected:
// TODO: failing serialize test on Linux, not sure what's going on
canvas->writePixels(fBitmap, 0, 256);
#endif
draw_blend_mode_swatches(canvas, SkRect::MakeXYWH(128, 256, 128, 128));
}
private:
SkBitmap fBitmap;
};

View File

@ -32,6 +32,7 @@ enum class SkBuiltInCodeSnippetID : uint8_t {
kBlendShader, // aka ComposeShader
// BlendMode code snippets
kFixedFunctionBlender,
kShaderBasedBlender,
kLast = kShaderBasedBlender

View File

@ -16,6 +16,7 @@
#ifdef SK_GRAPHITE_ENABLED
#include "experimental/graphite/src/UniformManager.h"
#include "src/gpu/Blend.h"
#endif
constexpr SkPMColor4f kErrorColor = { 1, 0, 0, 1 };
@ -471,10 +472,53 @@ void AddToKey(SkShaderCodeDictionary* dict,
} // namespace BlendShaderBlock
//--------------------------------------------------------------------------------------------------
#ifdef SK_GRAPHITE_ENABLED
namespace {
constexpr SkPipelineData::BlendInfo make_simple_blendInfo(skgpu::BlendCoeff srcCoeff,
skgpu::BlendCoeff dstCoeff) {
return { skgpu::BlendEquation::kAdd,
srcCoeff,
dstCoeff,
SK_PMColor4fTRANSPARENT,
skgpu::BlendModifiesDst(skgpu::BlendEquation::kAdd, srcCoeff, dstCoeff) };
}
/*>> No coverage, input color unknown <<*/
static constexpr SkPipelineData::BlendInfo gBlendTable[(int)SkBlendMode::kLastCoeffMode + 1] = {
/* clear */ make_simple_blendInfo(skgpu::BlendCoeff::kZero, skgpu::BlendCoeff::kZero),
/* src */ make_simple_blendInfo(skgpu::BlendCoeff::kOne, skgpu::BlendCoeff::kZero),
/* dst */ make_simple_blendInfo(skgpu::BlendCoeff::kZero, skgpu::BlendCoeff::kOne),
/* src-over */ make_simple_blendInfo(skgpu::BlendCoeff::kOne, skgpu::BlendCoeff::kISA),
/* dst-over */ make_simple_blendInfo(skgpu::BlendCoeff::kIDA, skgpu::BlendCoeff::kOne),
/* src-in */ make_simple_blendInfo(skgpu::BlendCoeff::kDA, skgpu::BlendCoeff::kZero),
/* dst-in */ make_simple_blendInfo(skgpu::BlendCoeff::kZero, skgpu::BlendCoeff::kSA),
/* src-out */ make_simple_blendInfo(skgpu::BlendCoeff::kIDA, skgpu::BlendCoeff::kZero),
/* dst-out */ make_simple_blendInfo(skgpu::BlendCoeff::kZero, skgpu::BlendCoeff::kISA),
/* src-atop */ make_simple_blendInfo(skgpu::BlendCoeff::kDA, skgpu::BlendCoeff::kISA),
/* dst-atop */ make_simple_blendInfo(skgpu::BlendCoeff::kIDA, skgpu::BlendCoeff::kSA),
/* xor */ make_simple_blendInfo(skgpu::BlendCoeff::kIDA, skgpu::BlendCoeff::kISA),
/* plus */ make_simple_blendInfo(skgpu::BlendCoeff::kOne, skgpu::BlendCoeff::kOne),
/* modulate */ make_simple_blendInfo(skgpu::BlendCoeff::kZero, skgpu::BlendCoeff::kSC),
/* screen */ make_simple_blendInfo(skgpu::BlendCoeff::kOne, skgpu::BlendCoeff::kISC)
};
const SkPipelineData::BlendInfo& get_blend_info(SkBlendMode bm) {
if (bm <= SkBlendMode::kLastCoeffMode) {
return gBlendTable[(int) bm];
}
return gBlendTable[(int) SkBlendMode::kSrc];
}
} // anonymous namespace
#endif // SK_GRAPHITE_ENABLED
namespace BlendModeBlock {
#ifdef SK_GRAPHITE_ENABLED
static const int kBlockDataSize = 1;
static const int kFixedFunctionBlockDataSize = 1;
static const int kShaderBasedBlockDataSize = 1;
#endif
void AddToKey(SkShaderCodeDictionary* dict,
@ -484,17 +528,31 @@ void AddToKey(SkShaderCodeDictionary* dict,
#ifdef SK_GRAPHITE_ENABLED
if (builder->backend() == SkBackend::kGraphite) {
builder->beginBlock(SkBuiltInCodeSnippetID::kShaderBasedBlender);
add_blendmode_to_key(builder, bm);
builder->endBlock();
if (bm <= SkBlendMode::kLastCoeffMode) {
builder->beginBlock(SkBuiltInCodeSnippetID::kFixedFunctionBlender);
add_blendmode_to_key(builder, bm);
builder->endBlock();
validate_block_header(builder,
SkBuiltInCodeSnippetID::kShaderBasedBlender,
kBlockDataSize);
validate_block_header(builder,
SkBuiltInCodeSnippetID::kFixedFunctionBlender,
kFixedFunctionBlockDataSize);
if (pipelineData) {
// TODO: set up the correct blend info
pipelineData->setBlendInfo(SkPipelineData::BlendInfo());
if (pipelineData) {
pipelineData->setBlendInfo(get_blend_info(bm));
}
} else {
builder->beginBlock(SkBuiltInCodeSnippetID::kShaderBasedBlender);
add_blendmode_to_key(builder, bm);
builder->endBlock();
validate_block_header(builder,
SkBuiltInCodeSnippetID::kShaderBasedBlender,
kShaderBasedBlockDataSize);
if (pipelineData) {
// TODO: set up the correct blend info
pipelineData->setBlendInfo(SkPipelineData::BlendInfo());
}
}
return;
}
@ -543,7 +601,7 @@ SkUniquePaintParamsID CreateKey(SkShaderCodeDictionary* dict,
}
// TODO: the blendInfo should be filled in by BlendModeBlock::AddToKey
SkPipelineData::BlendInfo blendInfo;
SkPipelineData::BlendInfo blendInfo = get_blend_info(bm);
BlendModeBlock::AddToKey(dict, builder, /* pipelineData*/ nullptr, bm);
SkPaintParamsKey key = builder->lockAsKey();

View File

@ -97,7 +97,7 @@ std::string SkShaderInfo::toSkSL() const {
std::string result = skgpu::mtl::GetMtlUniforms(2, "FS", fBlockReaders);
std::set<const char*> emittedStaticSnippets;
for (auto reader : fBlockReaders) {
for (const auto& reader : fBlockReaders) {
const SkShaderSnippet* e = reader.entry();
if (emittedStaticSnippets.find(e->fStaticFunctionName) == emittedStaticSnippets.end()) {
result += e->fStaticSkSL;
@ -326,7 +326,7 @@ static const char* kImageShaderName = "image_shader";
static const char* kImageShaderSkSL =
"half4 image_shader() {\n"
" float c = fract(abs(sk_FragCoord.x/10.0));\n"
" return half4(c, c, c, 1.0);\n"
" return half4(1.0, 1.0 - c, 1.0 - c, 1.0);\n"
"}\n";
static constexpr int kNumImageShaderFields = 2;
@ -447,6 +447,36 @@ static const char* kErrorSkSL =
" return half4(1.0, 0.0, 1.0, 1.0);\n"
"}\n";
//--------------------------------------------------------------------------------------------------
static constexpr int kNumFixedFunctionBlenderFields = 1;
static constexpr DataPayloadField kFixedFunctionBlenderFields[kNumFixedFunctionBlenderFields] = {
{ "blendmode", SkPaintParamsKey::DataPayloadType::kByte, 1 }
};
// This method generates the glue code for the case where the SkBlendMode-based blending is
// handled with fixed function blending.
std::string GenerateFixedFunctionBlenderGlueCode(const std::string& resultName,
int entryIndex,
const SkPaintParamsKey::BlockReader& reader,
const std::string& priorStageOutputName,
const std::vector<std::string>& childNames,
int indent) {
SkASSERT(childNames.empty());
SkASSERT(reader.entry()->fUniforms.empty());
SkASSERT(reader.numDataPayloadFields() == 1);
// The actual blending is set up via the fixed function pipeline so we don't actually
// need to access the blend mode in the glue code.
std::string result;
add_indent(&result, indent);
result += "// Fixed-function blending\n";
add_indent(&result, indent);
SkSL::String::appendf(&result, "%s = %s;", resultName.c_str(), priorStageOutputName.c_str());
return result;
}
//--------------------------------------------------------------------------------------------------
static constexpr int kNumShaderBasedBlenderFields = 1;
static constexpr DataPayloadField kShaderBasedBlenderFields[kNumShaderBasedBlenderFields] = {
@ -471,6 +501,8 @@ std::string GenerateShaderBasedBlenderGlueCode(const std::string& resultName,
std::string result;
add_indent(&result, indent);
result += "// Shader-based blending\n";
// TODO: emit code to perform dest read
add_indent(&result, indent);
result += "half4 dummyDst = half4(1.0, 1.0, 1.0, 1.0);\n";
@ -577,8 +609,15 @@ SkShaderCodeDictionary::SkShaderCodeDictionary() {
kNumBlendShaderChildren,
{}
};
fBuiltInCodeSnippets[(int) SkBuiltInCodeSnippetID::kFixedFunctionBlender] = {
{ }, // no uniforms
"", "", // fixed function blending doesn't have any static SkSL
GenerateFixedFunctionBlenderGlueCode,
kNoChildren,
{ kFixedFunctionBlenderFields, kNumFixedFunctionBlenderFields }
};
fBuiltInCodeSnippets[(int) SkBuiltInCodeSnippetID::kShaderBasedBlender] = {
{ },
{ }, // no uniforms
kBlendHelperName, kBlendHelperSkSL,
GenerateShaderBasedBlenderGlueCode,
kNoChildren,

View File

@ -16,7 +16,7 @@ namespace skgpu {
/**
* Equations for alpha-blending.
*/
enum class BlendEquation {
enum class BlendEquation : uint8_t {
// Basic blend equations.
kAdd, //<! Cs*S + Cd*D
kSubtract, //<! Cs*S - Cd*D
@ -51,7 +51,7 @@ static const int kBlendEquationCnt = static_cast<int>(BlendEquation::kLast) + 1;
/**
* Coefficients for alpha-blending.
*/
enum class BlendCoeff {
enum class BlendCoeff : uint8_t {
kZero, //<! 0
kOne, //<! 1
kSC, //<! src color

View File

@ -95,19 +95,25 @@ DEF_GRAPHITE_TEST_FOR_CONTEXTS(UniformTest, reporter, context) {
for (auto bm : { SkBlendMode::kSrc, SkBlendMode::kSrcOver }) {
auto [ p, expectedNumUniforms ] = create_paint(s, tm, bm);
auto [ uniqueID1, uniformBlock] = ExtractPaintData(dict, &builder, PaintParams(p));
int actualNumUniforms = uniformBlock->count();
auto [ uniqueID1, pipelineData] = ExtractPaintData(dict, &builder, PaintParams(p));
SkUniquePaintParamsID uniqueID2 = CreateKey(dict, &builder, s, tm, bm);
// ExtractPaintData and CreateKey agree
REPORTER_ASSERT(reporter, uniqueID1 == uniqueID2);
REPORTER_ASSERT(reporter, expectedNumUniforms == actualNumUniforms);
for (auto& u : *uniformBlock) {
for (int i = 0; i < u->count(); ++i) {
REPORTER_ASSERT(reporter,
u->offset(i) >= 0 && u->offset(i) < u->dataSize());
// ExtractPaintData made the pipeline data we expected
{
int actualNumUniforms = pipelineData->count();
REPORTER_ASSERT(reporter, expectedNumUniforms == actualNumUniforms);
for (const auto& u: *pipelineData) {
for (int i = 0; i < u->count(); ++i) {
REPORTER_ASSERT(reporter,
u->offset(i) >= 0 && u->offset(i) < u->dataSize());
}
}
// TODO: check the blendInfo here too
}
}
}