From 44263c5d08276edb3764fdfe79098de53123452a Mon Sep 17 00:00:00 2001 From: Robert Phillips Date: Thu, 10 Mar 2022 17:01:46 -0500 Subject: [PATCH] [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 Commit-Queue: Robert Phillips --- .../graphite/src/mtl/MtlGraphicsPipeline.mm | 90 ++++++++++++++++++- gm/graphitestart.cpp | 53 ++++++++++- src/core/SkBuiltInCodeSnippetID.h | 1 + src/core/SkKeyHelpers.cpp | 80 ++++++++++++++--- src/core/SkShaderCodeDictionary.cpp | 45 +++++++++- src/gpu/Blend.h | 4 +- tests/graphite/UniformTest.cpp | 22 +++-- 7 files changed, 266 insertions(+), 29 deletions(-) diff --git a/experimental/graphite/src/mtl/MtlGraphicsPipeline.mm b/experimental/graphite/src/mtl/MtlGraphicsPipeline.mm index 617bf15b33..bea365cdc4 100644 --- a/experimental/graphite/src/mtl/MtlGraphicsPipeline.mm +++ b/experimental/graphite/src/mtl/MtlGraphicsPipeline.mm @@ -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; diff --git a/gm/graphitestart.cpp b/gm/graphitestart.cpp index fba4f3128f..e1b3a831ff 100644 --- a/gm/graphitestart.cpp +++ b/gm/graphitestart.cpp @@ -50,8 +50,47 @@ sk_sp create_image_shader() { sk_sp create_blend_shader(SkBlendMode bm) { constexpr SkColor4f kTransYellow = {1.0f, 1.0f, 0.0f, 0.5f}; - sk_sp solid = SkShaders::Color(kTransYellow, nullptr); - return SkShaders::Blend(bm, std::move(solid), create_image_shader()); + sk_sp 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(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; }; diff --git a/src/core/SkBuiltInCodeSnippetID.h b/src/core/SkBuiltInCodeSnippetID.h index b119ff5e9e..46f649a5b4 100644 --- a/src/core/SkBuiltInCodeSnippetID.h +++ b/src/core/SkBuiltInCodeSnippetID.h @@ -32,6 +32,7 @@ enum class SkBuiltInCodeSnippetID : uint8_t { kBlendShader, // aka ComposeShader // BlendMode code snippets + kFixedFunctionBlender, kShaderBasedBlender, kLast = kShaderBasedBlender diff --git a/src/core/SkKeyHelpers.cpp b/src/core/SkKeyHelpers.cpp index 97ba386866..c173d1026f 100644 --- a/src/core/SkKeyHelpers.cpp +++ b/src/core/SkKeyHelpers.cpp @@ -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(); diff --git a/src/core/SkShaderCodeDictionary.cpp b/src/core/SkShaderCodeDictionary.cpp index ffb4c281cb..806c4b207f 100644 --- a/src/core/SkShaderCodeDictionary.cpp +++ b/src/core/SkShaderCodeDictionary.cpp @@ -97,7 +97,7 @@ std::string SkShaderInfo::toSkSL() const { std::string result = skgpu::mtl::GetMtlUniforms(2, "FS", fBlockReaders); std::set 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& 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, diff --git a/src/gpu/Blend.h b/src/gpu/Blend.h index 8094ae71a3..7fd212f600 100644 --- a/src/gpu/Blend.h +++ b/src/gpu/Blend.h @@ -16,7 +16,7 @@ namespace skgpu { /** * Equations for alpha-blending. */ -enum class BlendEquation { +enum class BlendEquation : uint8_t { // Basic blend equations. kAdd, //(BlendEquation::kLast) + 1; /** * Coefficients for alpha-blending. */ -enum class BlendCoeff { +enum class BlendCoeff : uint8_t { kZero, //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 } } }