/* * Copyright 2023 Google LLC * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "src/gpu/BlendFormula.h" #include "include/core/SkBlendMode.h" namespace { using skgpu::BlendFormula; /** * When there is no coverage, or the blend mode can tweak alpha for coverage, we use the standard * Porter Duff formula. */ constexpr BlendFormula MakeCoeffFormula(skgpu::BlendCoeff srcCoeff, skgpu::BlendCoeff dstCoeff) { // When the coeffs are (Zero, Zero) or (Zero, One) we set the primary output to none. return (skgpu::BlendCoeff::kZero == srcCoeff && (skgpu::BlendCoeff::kZero == dstCoeff || skgpu::BlendCoeff::kOne == dstCoeff)) ? BlendFormula(BlendFormula::kNone_OutputType, BlendFormula::kNone_OutputType, skgpu::BlendEquation::kAdd, skgpu::BlendCoeff::kZero, dstCoeff) : BlendFormula(BlendFormula::kModulate_OutputType, BlendFormula::kNone_OutputType, skgpu::BlendEquation::kAdd, srcCoeff, dstCoeff); } /** * Basic coeff formula similar to MakeCoeffFormula but we will make the src f*Sa. This is used in * LCD dst-out. */ constexpr BlendFormula MakeSAModulateFormula(skgpu::BlendCoeff srcCoeff, skgpu::BlendCoeff dstCoeff) { return BlendFormula(BlendFormula::kSAModulate_OutputType, BlendFormula::kNone_OutputType, skgpu::BlendEquation::kAdd, srcCoeff, dstCoeff); } /** * When there is coverage, the equation with f=coverage is: * * D' = f * (S * srcCoeff + D * dstCoeff) + (1-f) * D * * This can be rewritten as: * * D' = f * S * srcCoeff + D * (1 - [f * (1 - dstCoeff)]) * * To implement this formula, we output [f * (1 - dstCoeff)] for the secondary color and replace the * HW dst coeff with IS2C. * * Xfer modes: dst-atop (Sa!=1) */ constexpr BlendFormula MakeCoverageFormula(BlendFormula::OutputType oneMinusDstCoeffModulateOutput, skgpu::BlendCoeff srcCoeff) { return BlendFormula(BlendFormula::kModulate_OutputType, oneMinusDstCoeffModulateOutput, skgpu::BlendEquation::kAdd, srcCoeff, skgpu::BlendCoeff::kIS2C); } /** * When there is coverage and the src coeff is Zero, the equation with f=coverage becomes: * * D' = f * D * dstCoeff + (1-f) * D * * This can be rewritten as: * * D' = D - D * [f * (1 - dstCoeff)] * * To implement this formula, we output [f * (1 - dstCoeff)] for the primary color and use a reverse * subtract HW blend equation with coeffs of (DC, One). * * Xfer modes: clear, dst-out (Sa=1), dst-in (Sa!=1), modulate (Sc!=1) */ constexpr BlendFormula MakeCoverageSrcCoeffZeroFormula( BlendFormula::OutputType oneMinusDstCoeffModulateOutput) { return BlendFormula(oneMinusDstCoeffModulateOutput, BlendFormula::kNone_OutputType, skgpu::BlendEquation::kReverseSubtract, skgpu::BlendCoeff::kDC, skgpu::BlendCoeff::kOne); } /** * When there is coverage and the dst coeff is Zero, the equation with f=coverage becomes: * * D' = f * S * srcCoeff + (1-f) * D * * To implement this formula, we output [f] for the secondary color and replace the HW dst coeff * with IS2A. (Note that we can avoid dual source blending when Sa=1 by using ISA.) * * Xfer modes (Sa!=1): src, src-in, src-out */ constexpr BlendFormula MakeCoverageDstCoeffZeroFormula(skgpu::BlendCoeff srcCoeff) { return BlendFormula(BlendFormula::kModulate_OutputType, BlendFormula::kCoverage_OutputType, skgpu::BlendEquation::kAdd, srcCoeff, skgpu::BlendCoeff::kIS2A); } /** * This table outlines the blend formulas we will use with each xfermode, with and without coverage, * with and without an opaque input color. Optimization properties are deduced at compile time so we * can make runtime decisions quickly. RGB coverage is not supported. */ constexpr BlendFormula gBlendTable[2][2][(int)SkBlendMode::kLastCoeffMode + 1] = { /*>> No coverage, input color unknown <<*/ {{ /* clear */ MakeCoeffFormula(skgpu::BlendCoeff::kZero, skgpu::BlendCoeff::kZero), /* src */ MakeCoeffFormula(skgpu::BlendCoeff::kOne, skgpu::BlendCoeff::kZero), /* dst */ MakeCoeffFormula(skgpu::BlendCoeff::kZero, skgpu::BlendCoeff::kOne), /* src-over */ MakeCoeffFormula(skgpu::BlendCoeff::kOne, skgpu::BlendCoeff::kISA), /* dst-over */ MakeCoeffFormula(skgpu::BlendCoeff::kIDA, skgpu::BlendCoeff::kOne), /* src-in */ MakeCoeffFormula(skgpu::BlendCoeff::kDA, skgpu::BlendCoeff::kZero), /* dst-in */ MakeCoeffFormula(skgpu::BlendCoeff::kZero, skgpu::BlendCoeff::kSA), /* src-out */ MakeCoeffFormula(skgpu::BlendCoeff::kIDA, skgpu::BlendCoeff::kZero), /* dst-out */ MakeCoeffFormula(skgpu::BlendCoeff::kZero, skgpu::BlendCoeff::kISA), /* src-atop */ MakeCoeffFormula(skgpu::BlendCoeff::kDA, skgpu::BlendCoeff::kISA), /* dst-atop */ MakeCoeffFormula(skgpu::BlendCoeff::kIDA, skgpu::BlendCoeff::kSA), /* xor */ MakeCoeffFormula(skgpu::BlendCoeff::kIDA, skgpu::BlendCoeff::kISA), /* plus */ MakeCoeffFormula(skgpu::BlendCoeff::kOne, skgpu::BlendCoeff::kOne), /* modulate */ MakeCoeffFormula(skgpu::BlendCoeff::kZero, skgpu::BlendCoeff::kSC), /* screen */ MakeCoeffFormula(skgpu::BlendCoeff::kOne, skgpu::BlendCoeff::kISC), }, /*>> Has coverage, input color unknown <<*/ { /* clear */ MakeCoverageSrcCoeffZeroFormula(BlendFormula::kCoverage_OutputType), /* src */ MakeCoverageDstCoeffZeroFormula(skgpu::BlendCoeff::kOne), /* dst */ MakeCoeffFormula(skgpu::BlendCoeff::kZero, skgpu::BlendCoeff::kOne), /* src-over */ MakeCoeffFormula(skgpu::BlendCoeff::kOne, skgpu::BlendCoeff::kISA), /* dst-over */ MakeCoeffFormula(skgpu::BlendCoeff::kIDA, skgpu::BlendCoeff::kOne), /* src-in */ MakeCoverageDstCoeffZeroFormula(skgpu::BlendCoeff::kDA), /* dst-in */ MakeCoverageSrcCoeffZeroFormula(BlendFormula::kISAModulate_OutputType), /* src-out */ MakeCoverageDstCoeffZeroFormula(skgpu::BlendCoeff::kIDA), /* dst-out */ MakeCoeffFormula(skgpu::BlendCoeff::kZero, skgpu::BlendCoeff::kISA), /* src-atop */ MakeCoeffFormula(skgpu::BlendCoeff::kDA, skgpu::BlendCoeff::kISA), /* dst-atop */ MakeCoverageFormula(BlendFormula::kISAModulate_OutputType, skgpu::BlendCoeff::kIDA), /* xor */ MakeCoeffFormula(skgpu::BlendCoeff::kIDA, skgpu::BlendCoeff::kISA), /* plus */ MakeCoeffFormula(skgpu::BlendCoeff::kOne, skgpu::BlendCoeff::kOne), /* modulate */ MakeCoverageSrcCoeffZeroFormula(BlendFormula::kISCModulate_OutputType), /* screen */ MakeCoeffFormula(skgpu::BlendCoeff::kOne, skgpu::BlendCoeff::kISC), }}, /*>> No coverage, input color opaque <<*/ {{ /* clear */ MakeCoeffFormula(skgpu::BlendCoeff::kZero, skgpu::BlendCoeff::kZero), /* src */ MakeCoeffFormula(skgpu::BlendCoeff::kOne, skgpu::BlendCoeff::kZero), /* dst */ MakeCoeffFormula(skgpu::BlendCoeff::kZero, skgpu::BlendCoeff::kOne), /* src-over */ MakeCoeffFormula(skgpu::BlendCoeff::kOne, skgpu::BlendCoeff::kISA), // see comment below /* dst-over */ MakeCoeffFormula(skgpu::BlendCoeff::kIDA, skgpu::BlendCoeff::kOne), /* src-in */ MakeCoeffFormula(skgpu::BlendCoeff::kDA, skgpu::BlendCoeff::kZero), /* dst-in */ MakeCoeffFormula(skgpu::BlendCoeff::kZero, skgpu::BlendCoeff::kOne), /* src-out */ MakeCoeffFormula(skgpu::BlendCoeff::kIDA, skgpu::BlendCoeff::kZero), /* dst-out */ MakeCoeffFormula(skgpu::BlendCoeff::kZero, skgpu::BlendCoeff::kZero), /* src-atop */ MakeCoeffFormula(skgpu::BlendCoeff::kDA, skgpu::BlendCoeff::kZero), /* dst-atop */ MakeCoeffFormula(skgpu::BlendCoeff::kIDA, skgpu::BlendCoeff::kOne), /* xor */ MakeCoeffFormula(skgpu::BlendCoeff::kIDA, skgpu::BlendCoeff::kZero), /* plus */ MakeCoeffFormula(skgpu::BlendCoeff::kOne, skgpu::BlendCoeff::kOne), /* modulate */ MakeCoeffFormula(skgpu::BlendCoeff::kZero, skgpu::BlendCoeff::kSC), /* screen */ MakeCoeffFormula(skgpu::BlendCoeff::kOne, skgpu::BlendCoeff::kISC), }, /*>> Has coverage, input color opaque <<*/ { /* clear */ MakeCoverageSrcCoeffZeroFormula(BlendFormula::kCoverage_OutputType), /* src */ MakeCoeffFormula(skgpu::BlendCoeff::kOne, skgpu::BlendCoeff::kISA), /* dst */ MakeCoeffFormula(skgpu::BlendCoeff::kZero, skgpu::BlendCoeff::kOne), /* src-over */ MakeCoeffFormula(skgpu::BlendCoeff::kOne, skgpu::BlendCoeff::kISA), /* dst-over */ MakeCoeffFormula(skgpu::BlendCoeff::kIDA, skgpu::BlendCoeff::kOne), /* src-in */ MakeCoeffFormula(skgpu::BlendCoeff::kDA, skgpu::BlendCoeff::kISA), /* dst-in */ MakeCoeffFormula(skgpu::BlendCoeff::kZero, skgpu::BlendCoeff::kOne), /* src-out */ MakeCoeffFormula(skgpu::BlendCoeff::kIDA, skgpu::BlendCoeff::kISA), /* dst-out */ MakeCoverageSrcCoeffZeroFormula(BlendFormula::kCoverage_OutputType), /* src-atop */ MakeCoeffFormula(skgpu::BlendCoeff::kDA, skgpu::BlendCoeff::kISA), /* dst-atop */ MakeCoeffFormula(skgpu::BlendCoeff::kIDA, skgpu::BlendCoeff::kOne), /* xor */ MakeCoeffFormula(skgpu::BlendCoeff::kIDA, skgpu::BlendCoeff::kISA), /* plus */ MakeCoeffFormula(skgpu::BlendCoeff::kOne, skgpu::BlendCoeff::kOne), /* modulate */ MakeCoverageSrcCoeffZeroFormula(BlendFormula::kISCModulate_OutputType), /* screen */ MakeCoeffFormula(skgpu::BlendCoeff::kOne, skgpu::BlendCoeff::kISC), }}}; // In the above table src-over is not optimized to src mode when the color is opaque because we // found no advantage to doing so. Also, we are using a global src-over blend in most cases which is // not specialized for opaque input. For GPUs where dropping to src (and thus able to disable // blending) is an advantage we change the blend mode to src before getting the blend formula from // this table. constexpr BlendFormula gLCDBlendTable[(int)SkBlendMode::kLastCoeffMode + 1] = { /* clear */ MakeCoverageSrcCoeffZeroFormula(BlendFormula::kCoverage_OutputType), /* src */ MakeCoverageFormula(BlendFormula::kCoverage_OutputType, skgpu::BlendCoeff::kOne), /* dst */ MakeCoeffFormula(skgpu::BlendCoeff::kZero, skgpu::BlendCoeff::kOne), /* src-over */ MakeCoverageFormula(BlendFormula::kSAModulate_OutputType, skgpu::BlendCoeff::kOne), /* dst-over */ MakeCoeffFormula(skgpu::BlendCoeff::kIDA, skgpu::BlendCoeff::kOne), /* src-in */ MakeCoverageFormula(BlendFormula::kCoverage_OutputType, skgpu::BlendCoeff::kDA), /* dst-in */ MakeCoverageSrcCoeffZeroFormula(BlendFormula::kISAModulate_OutputType), /* src-out */ MakeCoverageFormula(BlendFormula::kCoverage_OutputType, skgpu::BlendCoeff::kIDA), /* dst-out */ MakeSAModulateFormula(skgpu::BlendCoeff::kZero, skgpu::BlendCoeff::kISC), /* src-atop */ MakeCoverageFormula(BlendFormula::kSAModulate_OutputType, skgpu::BlendCoeff::kDA), /* dst-atop */ MakeCoverageFormula(BlendFormula::kISAModulate_OutputType, skgpu::BlendCoeff::kIDA), /* xor */ MakeCoverageFormula(BlendFormula::kSAModulate_OutputType, skgpu::BlendCoeff::kIDA), /* plus */ MakeCoeffFormula(skgpu::BlendCoeff::kOne, skgpu::BlendCoeff::kOne), /* modulate */ MakeCoverageSrcCoeffZeroFormula(BlendFormula::kISCModulate_OutputType), /* screen */ MakeCoeffFormula(skgpu::BlendCoeff::kOne, skgpu::BlendCoeff::kISC), }; } // anonymous namespace namespace skgpu { BlendFormula GetBlendFormula(bool isOpaque, bool hasCoverage, SkBlendMode xfermode) { SkASSERT((unsigned)xfermode <= (unsigned)SkBlendMode::kLastCoeffMode); return gBlendTable[isOpaque][hasCoverage][(int)xfermode]; } BlendFormula GetLCDBlendFormula(SkBlendMode xfermode) { SkASSERT((unsigned)xfermode <= (unsigned)SkBlendMode::kLastCoeffMode); return gLCDBlendTable[(int)xfermode]; } } // namespace skgpu