blob: 0f8eb35d999d302c4ce64e2d0156f851c52890bb [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.
*/
#ifndef GrFragmentProcessor_DEFINED
#define GrFragmentProcessor_DEFINED
#include "include/private/SkColorData.h"
#include "include/private/SkSLSampleUsage.h"
#include "include/private/base/SkMacros.h"
#include "src/gpu/ganesh/GrProcessor.h"
#include "src/gpu/ganesh/glsl/GrGLSLUniformHandler.h"
#include "src/sksl/SkSLString.h"
#include <tuple>
class GrGLSLFPFragmentBuilder;
class GrGLSLProgramDataManager;
class GrPaint;
class GrPipeline;
struct GrShaderCaps;
class GrTextureEffect;
namespace skgpu {
class KeyBuilder;
class Swizzle;
}
/**
* Some fragment-processor creation methods have preconditions that might not be satisfied by the
* calling code. Those methods can return a `GrFPResult` from their factory methods. If creation
* succeeds, the new fragment processor is created and `success` is true. If a precondition is not
* met, `success` is set to false and the input FP is returned unchanged.
*/
class GrFragmentProcessor;
using GrFPResult = std::tuple<bool /*success*/, std::unique_ptr<GrFragmentProcessor>>;
/** Provides custom fragment shader code. Fragment processors receive an input position and
produce an output color. They may contain uniforms and may have children fragment processors
that are sampled.
*/
class GrFragmentProcessor : public GrProcessor {
public:
/**
* Every GrFragmentProcessor must be capable of creating a subclass of ProgramImpl. The
* ProgramImpl emits the fragment shader code that implements the GrFragmentProcessor, is
* attached to the generated backend API pipeline/program and used to extract uniform data from
* GrFragmentProcessor instances.
*/
class ProgramImpl;
/** Always returns 'color'. */
static std::unique_ptr<GrFragmentProcessor> MakeColor(SkPMColor4f color);
/**
* Returns the input color, modulated by the child's alpha.
*
* output = input * child.a
*/
static std::unique_ptr<GrFragmentProcessor> MulInputByChildAlpha(
std::unique_ptr<GrFragmentProcessor> child);
/**
* Invokes child with an opaque version of the input color, then applies the input alpha to
* the result. Used to incorporate paint alpha to the evaluation of an SkShader tree FP.
*/
static std::unique_ptr<GrFragmentProcessor> ApplyPaintAlpha(
std::unique_ptr<GrFragmentProcessor> child);
/**
* Returns a fragment processor that generates the passed-in color, modulated by the child's
* RGBA color. The child's input color will be the parent's fInputColor. (Pass a null FP to use
* the color from fInputColor instead of a child FP.)
*/
static std::unique_ptr<GrFragmentProcessor> ModulateRGBA(
std::unique_ptr<GrFragmentProcessor> child, const SkPMColor4f& color);
/**
* Returns a parent fragment processor that adopts the passed fragment processor as a child.
* The parent will ignore its input color and instead feed the passed in color as input to the
* child.
*/
static std::unique_ptr<GrFragmentProcessor> OverrideInput(std::unique_ptr<GrFragmentProcessor>,
const SkPMColor4f&);
/**
* Returns a parent fragment processor that adopts the passed fragment processor as a child.
* The parent will simply return the child's color, but disable the coverage-as-alpha
* optimization.
*/
static std::unique_ptr<GrFragmentProcessor> DisableCoverageAsAlpha(
std::unique_ptr<GrFragmentProcessor>);
/**
* Returns a fragment processor which returns `args.fDestColor`. This is only meaningful in
* contexts like blenders, which use a source and dest color.)
*/
static std::unique_ptr<GrFragmentProcessor> DestColor();
/**
* Returns a fragment processor that calls the passed in fragment processor, and then swizzles
* the output.
*/
static std::unique_ptr<GrFragmentProcessor> SwizzleOutput(std::unique_ptr<GrFragmentProcessor>,
const skgpu::Swizzle&);
/**
* Returns a fragment processor that calls the passed in fragment processor, and then clamps
* the output to [0, 1].
*/
static std::unique_ptr<GrFragmentProcessor> ClampOutput(std::unique_ptr<GrFragmentProcessor>);
/**
* Returns a fragment processor that composes two fragment processors `f` and `g` into f(g(x)).
* This is equivalent to running them in series (`g`, then `f`). This is not the same as
* transfer-mode composition; there is no blending step.
*/
static std::unique_ptr<GrFragmentProcessor> Compose(std::unique_ptr<GrFragmentProcessor> f,
std::unique_ptr<GrFragmentProcessor> g);
/*
* Returns a fragment processor that calls the passed in fragment processor, then runs the
* resulting color through the supplied color matrix.
*/
static std::unique_ptr<GrFragmentProcessor> ColorMatrix(
std::unique_ptr<GrFragmentProcessor> child,
const float matrix[20],
bool unpremulInput,
bool clampRGBOutput,
bool premulOutput);
/**
* Returns a fragment processor that reads back the color on the surface being painted; that is,
* sampling this will return the color of the pixel that is currently being painted over.
*/
static std::unique_ptr<GrFragmentProcessor> SurfaceColor();
/**
* Returns a fragment processor that calls the passed in fragment processor, but evaluates it
* in device-space (rather than local space).
*/
static std::unique_ptr<GrFragmentProcessor> DeviceSpace(std::unique_ptr<GrFragmentProcessor>);
/**
* "Shape" FPs, often used for clipping. Each one evaluates a particular kind of shape (rect,
* circle, ellipse), and modulates the coverage of that shape against the results of the input
* FP. GrClipEdgeType is used to select inverse/normal fill, and AA or non-AA edges.
*/
static std::unique_ptr<GrFragmentProcessor> Rect(std::unique_ptr<GrFragmentProcessor>,
GrClipEdgeType,
SkRect);
static GrFPResult Circle(std::unique_ptr<GrFragmentProcessor>,
GrClipEdgeType,
SkPoint center,
float radius);
static GrFPResult Ellipse(std::unique_ptr<GrFragmentProcessor>,
GrClipEdgeType,
SkPoint center,
SkPoint radii,
const GrShaderCaps&);
/**
* Returns a fragment processor that calls the passed in fragment processor, but ensures the
* entire program is compiled with high-precision types.
*/
static std::unique_ptr<GrFragmentProcessor> HighPrecision(std::unique_ptr<GrFragmentProcessor>);
/**
* Makes a copy of this fragment processor that draws equivalently to the original.
* If the processor has child processors they are cloned as well.
*/
virtual std::unique_ptr<GrFragmentProcessor> clone() const = 0;
// The FP this was registered with as a child function. This will be null if this is a root.
const GrFragmentProcessor* parent() const { return fParent; }
std::unique_ptr<ProgramImpl> makeProgramImpl() const;
void addToKey(const GrShaderCaps& caps, skgpu::KeyBuilder* b) const {
this->onAddToKey(caps, b);
for (const auto& child : fChildProcessors) {
if (child) {
child->addToKey(caps, b);
}
}
}
int numChildProcessors() const { return fChildProcessors.size(); }
int numNonNullChildProcessors() const;
GrFragmentProcessor* childProcessor(int index) { return fChildProcessors[index].get(); }
const GrFragmentProcessor* childProcessor(int index) const {
return fChildProcessors[index].get();
}
SkDEBUGCODE(bool isInstantiated() const;)
/** Do any of the FPs in this tree read back the color from the destination surface? */
bool willReadDstColor() const {
return SkToBool(fFlags & kWillReadDstColor_Flag);
}
/** Does the SkSL for this FP take two colors as its input arguments? */
bool isBlendFunction() const {
return SkToBool(fFlags & kIsBlendFunction_Flag);
}
/**
* True if this FP refers directly to the sample coordinate parameter of its function
* (e.g. uses EmitArgs::fSampleCoord in emitCode()). This is decided at FP-tree construction
* time and is not affected by lifting coords to varyings.
*/
bool usesSampleCoordsDirectly() const {
return SkToBool(fFlags & kUsesSampleCoordsDirectly_Flag);
}
/**
* True if this FP uses its input coordinates or if any descendant FP uses them through a chain
* of non-explicit sample usages. (e.g. uses EmitArgs::fSampleCoord in emitCode()). This is
* decided at FP-tree construction time and is not affected by lifting coords to varyings.
*/
bool usesSampleCoords() const {
return SkToBool(fFlags & (kUsesSampleCoordsDirectly_Flag |
kUsesSampleCoordsIndirectly_Flag));
}
// The SampleUsage describing how this FP is invoked by its parent. This only reflects the
// immediate sampling from parent to this FP.
const SkSL::SampleUsage& sampleUsage() const {
return fUsage;
}
/**
* A GrDrawOp may premultiply its antialiasing coverage into its GrGeometryProcessor's color
* output under the following scenario:
* * all the color fragment processors report true to this query,
* * all the coverage fragment processors report true to this query,
* * the blend mode arithmetic allows for it it.
* To be compatible a fragment processor's output must be a modulation of its input color or
* alpha with a computed premultiplied color or alpha that is in 0..1 range. The computed color
* or alpha that is modulated against the input cannot depend on the input's alpha. The computed
* value cannot depend on the input's color channels unless it unpremultiplies the input color
* channels by the input alpha.
*/
bool compatibleWithCoverageAsAlpha() const {
return SkToBool(fFlags & kCompatibleWithCoverageAsAlpha_OptimizationFlag);
}
/**
* If this is true then all opaque input colors to the processor produce opaque output colors.
*/
bool preservesOpaqueInput() const {
return SkToBool(fFlags & kPreservesOpaqueInput_OptimizationFlag);
}
/**
* Tests whether given a constant input color the processor produces a constant output color
* (for all fragments). If true outputColor will contain the constant color produces for
* inputColor.
*/
bool hasConstantOutputForConstantInput(SkPMColor4f inputColor, SkPMColor4f* outputColor) const {
if (fFlags & kConstantOutputForConstantInput_OptimizationFlag) {
*outputColor = this->constantOutputForConstantInput(inputColor);
return true;
}
return false;
}
bool hasConstantOutputForConstantInput() const {
return SkToBool(fFlags & kConstantOutputForConstantInput_OptimizationFlag);
}
void clearConstantOutputForConstantInputFlag() {
fFlags &= ~kConstantOutputForConstantInput_OptimizationFlag;
}
/** Returns true if this and other processor conservatively draw identically. It can only return
true when the two processor are of the same subclass (i.e. they return the same object from
from getFactory()).
A return value of true from isEqual() should not be used to test whether the processor would
generate the same shader code. To test for identical code generation use addToKey.
*/
bool isEqual(const GrFragmentProcessor& that) const;
void visitProxies(const GrVisitProxyFunc&) const;
void visitTextureEffects(const std::function<void(const GrTextureEffect&)>&) const;
void visitWithImpls(const std::function<void(const GrFragmentProcessor&, ProgramImpl&)>&,
ProgramImpl&) const;
GrTextureEffect* asTextureEffect();
const GrTextureEffect* asTextureEffect() const;
#if defined(GR_TEST_UTILS)
// Generates debug info for this processor tree by recursively calling dumpInfo() on this
// processor and its children.
SkString dumpTreeInfo() const;
#endif
protected:
enum OptimizationFlags : uint32_t {
kNone_OptimizationFlags,
kCompatibleWithCoverageAsAlpha_OptimizationFlag = 0x1,
kPreservesOpaqueInput_OptimizationFlag = 0x2,
kConstantOutputForConstantInput_OptimizationFlag = 0x4,
kAll_OptimizationFlags = kCompatibleWithCoverageAsAlpha_OptimizationFlag |
kPreservesOpaqueInput_OptimizationFlag |
kConstantOutputForConstantInput_OptimizationFlag
};
SK_DECL_BITFIELD_OPS_FRIENDS(OptimizationFlags)
/**
* Can be used as a helper to decide which fragment processor OptimizationFlags should be set.
* This assumes that the subclass output color will be a modulation of the input color with a
* value read from a texture of the passed color type and that the texture contains
* premultiplied color or alpha values that are in range.
*
* Since there are multiple ways in which a sampler may have its coordinates clamped or wrapped,
* callers must determine on their own if the sampling uses a decal strategy in any way, in
* which case the texture may become transparent regardless of the color type.
*/
static OptimizationFlags ModulateForSamplerOptFlags(SkAlphaType alphaType, bool samplingDecal) {
if (samplingDecal) {
return kCompatibleWithCoverageAsAlpha_OptimizationFlag;
} else {
return ModulateForClampedSamplerOptFlags(alphaType);
}
}
// As above, but callers should somehow ensure or assert their sampler still uses clamping
static OptimizationFlags ModulateForClampedSamplerOptFlags(SkAlphaType alphaType) {
if (alphaType == kOpaque_SkAlphaType) {
return kCompatibleWithCoverageAsAlpha_OptimizationFlag |
kPreservesOpaqueInput_OptimizationFlag;
} else {
return kCompatibleWithCoverageAsAlpha_OptimizationFlag;
}
}
GrFragmentProcessor(ClassID classID, OptimizationFlags optimizationFlags)
: INHERITED(classID), fFlags(optimizationFlags) {
SkASSERT((optimizationFlags & ~kAll_OptimizationFlags) == 0);
}
explicit GrFragmentProcessor(const GrFragmentProcessor& src)
: INHERITED(src.classID()), fFlags(src.fFlags) {
this->cloneAndRegisterAllChildProcessors(src);
}
OptimizationFlags optimizationFlags() const {
return static_cast<OptimizationFlags>(kAll_OptimizationFlags & fFlags);
}
/** Useful when you can't call fp->optimizationFlags() on a base class object from a subclass.*/
static OptimizationFlags ProcessorOptimizationFlags(const GrFragmentProcessor* fp) {
return fp ? fp->optimizationFlags() : kAll_OptimizationFlags;
}
/**
* This allows one subclass to access another subclass's implementation of
* constantOutputForConstantInput. It must only be called when
* hasConstantOutputForConstantInput() is known to be true.
*/
static SkPMColor4f ConstantOutputForConstantInput(const GrFragmentProcessor* fp,
const SkPMColor4f& input) {
if (fp) {
SkASSERT(fp->hasConstantOutputForConstantInput());
return fp->constantOutputForConstantInput(input);
} else {
return input;
}
}
/**
* FragmentProcessor subclasses call this from their constructor to register any child
* FragmentProcessors they have. This must be called AFTER all texture accesses and coord
* transforms have been added.
* This is for processors whose shader code will be composed of nested processors whose output
* colors will be combined somehow to produce its output color. Registering these child
* processors will allow the ProgramBuilder to automatically handle their transformed coords and
* texture accesses and mangle their uniform and output color names.
*
* The SampleUsage parameter describes all of the ways that the child is sampled by the parent.
*/
void registerChild(std::unique_ptr<GrFragmentProcessor> child,
SkSL::SampleUsage sampleUsage = SkSL::SampleUsage::PassThrough());
/**
* This method takes an existing fragment processor, clones all of its children, and registers
* the clones as children of this fragment processor.
*/
void cloneAndRegisterAllChildProcessors(const GrFragmentProcessor& src);
// FP implementations must call this function if their matching ProgramImpl's emitCode()
// function uses the EmitArgs::fSampleCoord variable in generated SkSL.
void setUsesSampleCoordsDirectly() {
fFlags |= kUsesSampleCoordsDirectly_Flag;
}
// FP implementations must set this flag if their ProgramImpl's emitCode() function calls
// dstColor() to read back the framebuffer.
void setWillReadDstColor() {
fFlags |= kWillReadDstColor_Flag;
}
// FP implementations must set this flag if their ProgramImpl's emitCode() function emits a
// blend function (taking two color inputs instead of just one).
void setIsBlendFunction() {
fFlags |= kIsBlendFunction_Flag;
}
void mergeOptimizationFlags(OptimizationFlags flags) {
SkASSERT((flags & ~kAll_OptimizationFlags) == 0);
fFlags &= (flags | ~kAll_OptimizationFlags);
}
private:
virtual SkPMColor4f constantOutputForConstantInput(const SkPMColor4f& /* inputColor */) const {
SK_ABORT("Subclass must override this if advertising this optimization.");
}
/**
* Returns a new instance of the appropriate ProgramImpl subclass for the given
* GrFragmentProcessor. It will emit the appropriate code and live with the cached program
* to setup uniform data for each draw that uses the program.
*/
virtual std::unique_ptr<ProgramImpl> onMakeProgramImpl() const = 0;
virtual void onAddToKey(const GrShaderCaps&, skgpu::KeyBuilder*) const = 0;
/**
* Subclass implements this to support isEqual(). It will only be called if it is known that
* the two processors are of the same subclass (i.e. have the same ClassID).
*/
virtual bool onIsEqual(const GrFragmentProcessor&) const = 0;
enum PrivateFlags {
kFirstPrivateFlag = kAll_OptimizationFlags + 1,
// Propagates up the FP tree to either root or first explicit sample usage.
kUsesSampleCoordsIndirectly_Flag = kFirstPrivateFlag,
// Does not propagate at all. It means this FP uses its input sample coords in some way.
// Note passthrough and matrix sampling of children don't count as a usage of the coords.
// Because indirect sampling stops at an explicit sample usage it is imperative that a FP
// that calculates explicit coords for its children using its own sample coords sets this.
kUsesSampleCoordsDirectly_Flag = kFirstPrivateFlag << 1,
// Does not propagate at all.
kIsBlendFunction_Flag = kFirstPrivateFlag << 2,
// Propagates up the FP tree to the root.
kWillReadDstColor_Flag = kFirstPrivateFlag << 3,
};
skia_private::STArray<1, std::unique_ptr<GrFragmentProcessor>, true> fChildProcessors;
const GrFragmentProcessor* fParent = nullptr;
uint32_t fFlags = 0;
SkSL::SampleUsage fUsage;
using INHERITED = GrProcessor;
};
//////////////////////////////////////////////////////////////////////////////
class GrFragmentProcessor::ProgramImpl {
public:
ProgramImpl() = default;
virtual ~ProgramImpl() = default;
using UniformHandle = GrGLSLUniformHandler::UniformHandle;
using SamplerHandle = GrGLSLUniformHandler::SamplerHandle;
/** Called when the program stage should insert its code into the shaders. The code in each
shader will be in its own block ({}) and so locally scoped names will not collide across
stages.
@param fragBuilder Interface used to emit code in the shaders.
@param uniformHandler Interface used for accessing information about our uniforms
@param caps The capabilities of the GPU which will render this FP
@param fp The processor that generated this program stage.
@param inputColor A half4 that holds the input color to the stage in the FS (or the
source color, for blend processors). nullptr inputs are converted
to "half4(1.0)" (solid white) during construction.
TODO: Better system for communicating optimization info
(e.g. input color is solid white, trans black, known to be opaque,
etc.) that allows the processor to communicate back similar known
info about its output.
@param destColor A half4 that holds the dest color to the stage. Only meaningful
when the "is blend processor" FP flag is set.
@param sampleCoord The name of a local coord reference to a float2 variable. Only
meaningful when the "references sample coords" FP flag is set.
*/
struct EmitArgs {
EmitArgs(GrGLSLFPFragmentBuilder* fragBuilder,
GrGLSLUniformHandler* uniformHandler,
const GrShaderCaps* caps,
const GrFragmentProcessor& fp,
const char* inputColor,
const char* destColor,
const char* sampleCoord)
: fFragBuilder(fragBuilder)
, fUniformHandler(uniformHandler)
, fShaderCaps(caps)
, fFp(fp)
, fInputColor(inputColor ? inputColor : "half4(1.0)")
, fDestColor(destColor)
, fSampleCoord(sampleCoord) {}
GrGLSLFPFragmentBuilder* fFragBuilder;
GrGLSLUniformHandler* fUniformHandler;
const GrShaderCaps* fShaderCaps;
const GrFragmentProcessor& fFp;
const char* fInputColor;
const char* fDestColor;
const char* fSampleCoord;
};
virtual void emitCode(EmitArgs&) = 0;
// This does not recurse to any attached child processors. Recursing the entire processor tree
// is the responsibility of the caller.
void setData(const GrGLSLProgramDataManager& pdman, const GrFragmentProcessor& processor);
int numChildProcessors() const { return fChildProcessors.size(); }
ProgramImpl* childProcessor(int index) const { return fChildProcessors[index].get(); }
void setFunctionName(SkString name) {
SkASSERT(fFunctionName.isEmpty());
fFunctionName = std::move(name);
}
const char* functionName() const {
SkASSERT(!fFunctionName.isEmpty());
return fFunctionName.c_str();
}
// Invoke the child with the default input and destination colors (solid white)
inline SkString invokeChild(int childIndex,
EmitArgs& parentArgs,
std::string_view skslCoords = {}) {
return this->invokeChild(childIndex,
/*inputColor=*/nullptr,
/*destColor=*/nullptr,
parentArgs,
skslCoords);
}
inline SkString invokeChildWithMatrix(int childIndex, EmitArgs& parentArgs) {
return this->invokeChildWithMatrix(childIndex,
/*inputColor=*/nullptr,
/*destColor=*/nullptr,
parentArgs);
}
// Invoke the child with the default destination color (solid white)
inline SkString invokeChild(int childIndex,
const char* inputColor,
EmitArgs& parentArgs,
std::string_view skslCoords = {}) {
return this->invokeChild(childIndex,
inputColor,
/*destColor=*/nullptr,
parentArgs,
skslCoords);
}
inline SkString invokeChildWithMatrix(int childIndex,
const char* inputColor,
EmitArgs& parentArgs) {
return this->invokeChildWithMatrix(childIndex,
inputColor,
/*destColor=*/nullptr,
parentArgs);
}
/** Invokes a child proc in its own scope. Pass in the parent's EmitArgs and invokeChild will
* automatically extract the coords and samplers of that child and pass them on to the child's
* emitCode(). Also, any uniforms or functions emitted by the child will have their names
* mangled to prevent redefinitions. The returned string contains the output color (as a call
* to the child's helper function). It is legal to pass nullptr as inputColor, since all
* fragment processors are required to work without an input color.
*
* When skslCoords is empty, the child is invoked at the sample coordinates from parentArgs.
* When skslCoords is not empty, is must be an SkSL expression that evaluates to a float2.
* That expression is passed to the child's processor function as the "_coords" argument.
*/
SkString invokeChild(int childIndex,
const char* inputColor,
const char* destColor,
EmitArgs& parentArgs,
std::string_view skslCoords = {});
/**
* As invokeChild, but transforms the coordinates according to the matrix expression attached
* to the child's SampleUsage object. This is only valid if the child is sampled with a
* const-uniform matrix.
*/
SkString invokeChildWithMatrix(int childIndex,
const char* inputColor,
const char* destColor,
EmitArgs& parentArgs);
/**
* Pre-order traversal of a GLSLFP hierarchy, or of multiple trees with roots in an array of
* GLSLFPS. If initialized with an array color followed by coverage processors installed in a
* program thenthe iteration order will agree with a GrFragmentProcessor::Iter initialized with
* a GrPipeline that produces the same program key.
*/
class Iter {
public:
Iter(std::unique_ptr<ProgramImpl> fps[], int cnt);
Iter(ProgramImpl& fp) { fFPStack.push_back(&fp); }
ProgramImpl& operator*() const;
ProgramImpl* operator->() const;
Iter& operator++();
explicit operator bool() const { return !fFPStack.empty(); }
// Because each iterator carries a stack we want to avoid copies.
Iter(const Iter&) = delete;
Iter& operator=(const Iter&) = delete;
private:
skia_private::STArray<4, ProgramImpl*, true> fFPStack;
};
private:
/**
* A ProgramImpl instance can be reused with any GrFragmentProcessor that produces the same
* the same key; this function reads data from a GrFragmentProcessor and uploads any
* uniform variables required by the shaders created in emitCode(). The GrFragmentProcessor
* parameter is guaranteed to be of the same type that created this ProgramImpl and
* to have an identical key as the one that created this ProgramImpl.
*/
virtual void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) {}
// The (mangled) name of our entry-point function
SkString fFunctionName;
skia_private::TArray<std::unique_ptr<ProgramImpl>, true> fChildProcessors;
friend class GrFragmentProcessor;
};
//////////////////////////////////////////////////////////////////////////////
SK_MAKE_BITFIELD_OPS(GrFragmentProcessor::OptimizationFlags)
static inline GrFPResult GrFPFailure(std::unique_ptr<GrFragmentProcessor> fp) {
return {false, std::move(fp)};
}
static inline GrFPResult GrFPSuccess(std::unique_ptr<GrFragmentProcessor> fp) {
SkASSERT(fp);
return {true, std::move(fp)};
}
// Equivalent to GrFPSuccess except it allows the returned fragment processor to be null.
static inline GrFPResult GrFPNullableSuccess(std::unique_ptr<GrFragmentProcessor> fp) {
return {true, std::move(fp)};
}
#endif