Add the concept of an "input" FP to GrSkSLFP

FPs are used to represent effects with varying semantics. In particular,
shaders (that generate colors with no real input), and color filters
(that always have a "primary" input color on which they operate).

Even FPs that are not directly tied to a particular SkShader or
SkColorFilter tend to fall into these two categories. GrSkSLFP was
tailored to the shader semantics. It always supported having child FPs,
but these weren't considered "special" in any way - there could be
multiple, and each one could be sampled in whatever way the SkSL wanted.

This CL adds a dedicated "input" FP slot, so that color filters (and FPs
that resemble color filters) can naturally map better. Previously, we
had to inject an additional FP to do composition of the input and the
SkSL. That worked fine - this change is really about cutting down on
allocations and extra CPU work for that extra FP in the tree.

Change-Id: Ic37457b31f8ed7b4aa8d814a5e78806caa19e1c5
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/418739
Commit-Queue: Brian Osman <brianosman@google.com>
Reviewed-by: John Stiles <johnstiles@google.com>
This commit is contained in:
Brian Osman 2021-06-15 14:36:17 -04:00 committed by Skia Commit-Bot
parent fba8a742cb
commit b2cb817d23
8 changed files with 113 additions and 65 deletions

View File

@ -636,34 +636,37 @@ static sk_sp<SkData> get_xformed_uniforms(const SkRuntimeEffect* effect,
}
#if SK_SUPPORT_GPU
static std::unique_ptr<GrFragmentProcessor> make_effect_fp(
sk_sp<SkRuntimeEffect> effect,
const char* name,
sk_sp<SkData> uniforms,
SkSpan<const SkRuntimeEffect::ChildPtr> children,
const GrFPArgs& childArgs) {
auto fp = GrSkSLFP::Make(std::move(effect), name, std::move(uniforms));
static GrFPResult make_effect_fp(sk_sp<SkRuntimeEffect> effect,
const char* name,
sk_sp<SkData> uniforms,
std::unique_ptr<GrFragmentProcessor> inputFP,
SkSpan<const SkRuntimeEffect::ChildPtr> children,
const GrFPArgs& childArgs) {
SkSTArray<8, std::unique_ptr<GrFragmentProcessor>> childFPs;
for (const auto& child : children) {
if (child.shader) {
auto childFP = as_SB(child.shader)->asFragmentProcessor(childArgs);
if (!childFP) {
return nullptr;
return GrFPFailure(std::move(inputFP));
}
fp->addChild(std::move(childFP));
childFPs.push_back(std::move(childFP));
} else if (child.colorFilter) {
auto [success, childFP] = as_CFB(child.colorFilter)
->asFragmentProcessor(/*inputFP=*/nullptr,
childArgs.fContext,
*childArgs.fDstColorInfo);
if (!success) {
return nullptr;
return GrFPFailure(std::move(inputFP));
}
fp->addChild(std::move(childFP));
childFPs.push_back(std::move(childFP));
} else {
fp->addChild(nullptr);
childFPs.push_back(nullptr);
}
}
return std::move(fp);
auto fp = GrSkSLFP::MakeWithData(
std::move(effect), name, std::move(inputFP), std::move(uniforms), SkMakeSpan(childFPs));
SkASSERT(fp);
return GrFPSuccess(std::move(fp));
}
#endif
@ -685,20 +688,12 @@ public:
SkASSERT(uniforms);
GrFPArgs childArgs(context, SkSimpleMatrixProvider(SkMatrix::I()), &colorInfo);
auto fp = make_effect_fp(fEffect,
"Runtime_Color_Filter",
std::move(uniforms),
SkMakeSpan(fChildren),
childArgs);
if (!fp) {
return GrFPFailure(std::move(inputFP));
}
// Runtime effect scripts are written to take an input color, not a fragment processor.
// We need to pass the input to the runtime filter using Compose. This ensures that it will
// be invoked exactly once, and the result will be returned when null children are sampled,
// or as the (default) input color for non-null children.
return GrFPSuccess(GrFragmentProcessor::Compose(std::move(fp), std::move(inputFP)));
return make_effect_fp(fEffect,
"runtime_color_filter",
std::move(uniforms),
std::move(inputFP),
SkMakeSpan(fChildren),
childArgs);
}
#endif
@ -853,18 +848,22 @@ public:
GrFPArgs childArgs = args;
childArgs.fInputColorIsOpaque = false;
auto result = make_effect_fp(
fEffect, "runtime_shader", std::move(uniforms), SkMakeSpan(fChildren), childArgs);
if (!result) {
auto [success, fp] = make_effect_fp(fEffect,
"runtime_shader",
std::move(uniforms),
/*inputFP=*/nullptr,
SkMakeSpan(fChildren),
childArgs);
if (!success) {
return nullptr;
}
// If the shader was created with isOpaque = true, we *force* that result here.
// CPU does the same thing (in SkShaderBase::program).
if (fIsOpaque) {
result = GrFragmentProcessor::SwizzleOutput(std::move(result), GrSwizzle::RGB1());
fp = GrFragmentProcessor::SwizzleOutput(std::move(fp), GrSwizzle::RGB1());
}
result = GrMatrixEffect::Make(matrix, std::move(result));
fp = GrMatrixEffect::Make(matrix, std::move(fp));
// Three cases of GrClampType to think about:
// kAuto - Normalized fixed-point. If fIsOpaque, then A is 1 (above), and the format's
// range ensures RGB must be no larger. If !fIsOpaque, we clamp here.
@ -873,9 +872,9 @@ public:
// kNone - Unclamped floating point. No clamping is done, ever.
GrClampType clampType = GrColorTypeClampType(args.fDstColorInfo->colorType());
if (clampType == GrClampType::kManual || (clampType == GrClampType::kAuto && !fIsOpaque)) {
return GrFragmentProcessor::ClampPremulOutput(std::move(result));
return GrFragmentProcessor::ClampPremulOutput(std::move(fp));
} else {
return result;
return std::move(fp);
}
}
#endif
@ -1128,19 +1127,22 @@ sk_sp<SkImage> SkRuntimeEffect::makeImage(GrRecordingContext* recordingContext,
uniforms = get_xformed_uniforms(this, std::move(uniforms), resultInfo.colorSpace());
SkASSERT(uniforms);
auto fp = GrSkSLFP::Make(sk_ref_sp(this),
"runtime_image",
std::move(uniforms));
SkSimpleMatrixProvider matrixProvider(SkMatrix::I());
GrColorInfo colorInfo(resultInfo.colorInfo());
GrFPArgs args(recordingContext, matrixProvider, &colorInfo);
SkSTArray<8, std::unique_ptr<GrFragmentProcessor>> childFPs;
for (size_t i = 0; i < childCount; ++i) {
if (!children[i]) {
return nullptr;
}
auto childFP = as_SB(children[i])->asFragmentProcessor(args);
fp->addChild(std::move(childFP));
childFPs.push_back(as_SB(children[i])->asFragmentProcessor(args));
}
auto fp = GrSkSLFP::MakeWithData(sk_ref_sp(this),
"runtime_image",
/*inputFP=*/nullptr,
std::move(uniforms),
SkMakeSpan(childFPs));
if (localMatrix) {
SkMatrix invLM;
if (!localMatrix->invert(&invLM)) {

View File

@ -324,7 +324,7 @@ std::unique_ptr<GrFragmentProcessor> make_arithmetic_fp(
return color;
}
)");
return GrSkSLFP::Make(effect, "arithmetic_fp",
return GrSkSLFP::Make(effect, "arithmetic_fp", /*inputFP=*/nullptr,
"srcFP", std::move(srcFP),
"dstFP", std::move(dstFP),
"k", k,

View File

@ -135,7 +135,7 @@ static std::unique_ptr<GrFragmentProcessor> make_magnifier_fp(
1.f / bounds.width(),
1.f / bounds.height()};
return GrSkSLFP::Make(effect, "magnifier_fp",
return GrSkSLFP::Make(effect, "magnifier_fp", /*inputFP=*/nullptr,
"src", std::move(input),
"boundsUniform", boundsUniform,
"xInvZoom", xInvZoom,

View File

@ -214,7 +214,7 @@ std::unique_ptr<GrFragmentProcessor> GrFragmentProcessor::MakeColor(SkPMColor4f
uniform half4 color;
half4 main(half4 inColor) { return color; }
)");
return GrSkSLFP::Make(effect, "color_fp", "color", color);
return GrSkSLFP::Make(effect, "color_fp", /*inputFP=*/nullptr, "color", color);
}
std::unique_ptr<GrFragmentProcessor> GrFragmentProcessor::MulChildByInputAlpha(

View File

@ -159,6 +159,14 @@ public:
int fUniformIndex = 0;
};
// If we have an input child, we invoke it now, and make the result of that be the "input
// color" for all other purposes later (eg, the default passed via sample calls, etc.)
if (fp.fInputChildIndex >= 0) {
args.fFragBuilder->codeAppendf("%s = %s;\n",
args.fInputColor,
this->invokeChild(fp.fInputChildIndex, args).c_str());
}
// Snap off a global copy of the input color at the start of main. We need this when
// we call child processors (particularly from helper functions, which can't "see" the
// parameter to main). Even from within main, if the code mutates the parameter, calls to
@ -225,9 +233,12 @@ public:
std::vector<UniformHandle> fUniformHandles;
};
std::unique_ptr<GrSkSLFP> GrSkSLFP::Make(sk_sp<SkRuntimeEffect> effect,
const char* name,
sk_sp<SkData> uniforms) {
std::unique_ptr<GrSkSLFP> GrSkSLFP::MakeWithData(
sk_sp<SkRuntimeEffect> effect,
const char* name,
std::unique_ptr<GrFragmentProcessor> inputFP,
sk_sp<SkData> uniforms,
SkSpan<std::unique_ptr<GrFragmentProcessor>> childFPs) {
if (uniforms->size() != effect->uniformSize()) {
return nullptr;
}
@ -236,6 +247,12 @@ std::unique_ptr<GrSkSLFP> GrSkSLFP::Make(sk_sp<SkRuntimeEffect> effect,
std::unique_ptr<GrSkSLFP> fp(new (uniformSize + uniformFlagSize)
GrSkSLFP(std::move(effect), name));
sk_careful_memcpy(fp->uniformData(), uniforms->data(), uniformSize);
for (auto& childFP : childFPs) {
fp->addChild(std::move(childFP));
}
if (inputFP) {
fp->setInput(std::move(inputFP));
}
return fp;
}
@ -246,7 +263,7 @@ GrSkSLFP::GrSkSLFP(sk_sp<SkRuntimeEffect> effect, const char* name)
: kNone_OptimizationFlags)
, fEffect(std::move(effect))
, fName(name)
, fUniformSize(fEffect->uniformSize()) {
, fUniformSize(SkToU32(fEffect->uniformSize())) {
memset(this->uniformFlags(), 0, fEffect->uniforms().count() * sizeof(UniformFlags));
if (fEffect->usesSampleCoords()) {
this->setUsesSampleCoordsDirectly();
@ -257,7 +274,8 @@ GrSkSLFP::GrSkSLFP(const GrSkSLFP& other)
: INHERITED(kGrSkSLFP_ClassID, other.optimizationFlags())
, fEffect(other.fEffect)
, fName(other.fName)
, fUniformSize(other.fUniformSize) {
, fUniformSize(other.fUniformSize)
, fInputChildIndex(other.fInputChildIndex) {
sk_careful_memcpy(this->uniformFlags(),
other.uniformFlags(),
fEffect->uniforms().count() * sizeof(UniformFlags));
@ -271,12 +289,21 @@ GrSkSLFP::GrSkSLFP(const GrSkSLFP& other)
}
void GrSkSLFP::addChild(std::unique_ptr<GrFragmentProcessor> child) {
SkASSERTF(fInputChildIndex == -1, "all addChild calls must happen before setInput");
int childIndex = this->numChildProcessors();
SkASSERT((size_t)childIndex < fEffect->fSampleUsages.size());
this->mergeOptimizationFlags(ProcessorOptimizationFlags(child.get()));
this->registerChild(std::move(child), fEffect->fSampleUsages[childIndex]);
}
void GrSkSLFP::setInput(std::unique_ptr<GrFragmentProcessor> input) {
SkASSERTF(fInputChildIndex == -1, "setInput should not be called more than once");
fInputChildIndex = this->numChildProcessors();
SkASSERT((size_t)fInputChildIndex == fEffect->fSampleUsages.size());
this->mergeOptimizationFlags(ProcessorOptimizationFlags(input.get()));
this->registerChild(std::move(input), SkSL::SampleUsage::PassThrough());
}
std::unique_ptr<GrGLSLFragmentProcessor> GrSkSLFP::onMakeProgramImpl() const {
return std::make_unique<GrGLSLSkSLFP>();
}
@ -286,7 +313,7 @@ void GrSkSLFP::onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBui
// That ensures that we will (at worst) use the wrong program, but one that expects the same
// amount of uniform data.
b->add32(fEffect->hash());
b->add32(SkToU32(fUniformSize));
b->add32(fUniformSize);
const UniformFlags* flags = this->uniformFlags();
const uint8_t* uniformData = this->uniformData();
@ -325,7 +352,11 @@ SkPMColor4f GrSkSLFP::constantOutputForConstantInput(const SkPMColor4f& inputCol
return ConstantOutputForConstantInput(this->childProcessor(index), color);
};
return program->eval(inputColor, this->uniformData(), evalChild);
SkPMColor4f color = (fInputChildIndex >= 0)
? ConstantOutputForConstantInput(
this->childProcessor(fInputChildIndex), inputColor)
: inputColor;
return program->eval(color, this->uniformData(), evalChild);
}
/**************************************************************************************************/

View File

@ -60,20 +60,25 @@ public:
kSpecialize_Flag = 0x1,
};
/**
* Both factories support a single 'input' FP, as well as a collection of other 'child' FPs.
* The 'child' FPs correspond to the children declared in the effect's SkSL. The inputFP is
* optional, and intended for instances that have color filter semantics. This is an implicit
* child - if present, it's evaluated to produce the input color fed to the SkSL. Otherwise,
* the SkSL receives this FP's input color directly.
*/
/**
* Creates a new fragment processor from an SkRuntimeEffect and a data blob containing values
* for all of the 'uniform' variables in the SkSL source. The layout of the uniforms blob is
* dictated by the SkRuntimeEffect.
*/
static std::unique_ptr<GrSkSLFP> Make(sk_sp<SkRuntimeEffect> effect,
const char* name,
sk_sp<SkData> uniforms);
const char* name() const override { return fName; }
void addChild(std::unique_ptr<GrFragmentProcessor> child);
std::unique_ptr<GrFragmentProcessor> clone() const override;
static std::unique_ptr<GrSkSLFP> MakeWithData(
sk_sp<SkRuntimeEffect> effect,
const char* name,
std::unique_ptr<GrFragmentProcessor> inputFP,
sk_sp<SkData> uniforms,
SkSpan<std::unique_ptr<GrFragmentProcessor>> childFPs);
/*
* Constructs a GrSkSLFP from a series of name-value pairs, corresponding to the children and
@ -93,7 +98,7 @@ public:
* std::unique_ptr<GrFragmentProcessor> child = ...;
* float scaleVal = ...;
* SkV2 ptVal = ...;
* auto fp = GrSkSLFP::Make(effect, "my_effect",
* auto fp = GrSkSLFP::Make(effect, "my_effect", nullptr,
* "input", std::move(child),
* "scale", scaleVal,
* "pt", ptVal);
@ -106,6 +111,7 @@ public:
template <typename... Args>
static std::unique_ptr<GrSkSLFP> Make(sk_sp<SkRuntimeEffect> effect,
const char* name,
std::unique_ptr<GrFragmentProcessor> inputFP,
Args&&... args) {
#ifdef SK_DEBUG
checkArgs(effect->fUniforms.begin(),
@ -118,13 +124,22 @@ public:
size_t uniformPayloadSize = UniformPayloadSize(effect.get());
std::unique_ptr<GrSkSLFP> fp(new (uniformPayloadSize) GrSkSLFP(std::move(effect), name));
fp->appendArgs(fp->uniformData(), fp->uniformFlags(), std::forward<Args>(args)...);
if (inputFP) {
fp->setInput(std::move(inputFP));
}
return fp;
}
const char* name() const override { return fName; }
std::unique_ptr<GrFragmentProcessor> clone() const override;
private:
GrSkSLFP(sk_sp<SkRuntimeEffect> effect, const char* name);
GrSkSLFP(const GrSkSLFP& other);
void addChild(std::unique_ptr<GrFragmentProcessor> child);
void setInput(std::unique_ptr<GrFragmentProcessor> input);
std::unique_ptr<GrGLSLFragmentProcessor> onMakeProgramImpl() const override;
void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
@ -260,7 +275,8 @@ private:
sk_sp<SkRuntimeEffect> fEffect;
const char* fName;
size_t fUniformSize;
uint32_t fUniformSize;
int fInputChildIndex = -1;
GR_DECLARE_FRAGMENT_PROCESSOR_TEST

View File

@ -95,8 +95,7 @@ GrFPResult SkGaussianColorFilter::asFragmentProcessor(std::unique_ptr<GrFragment
return half4(factor);
}
)");
auto fp = GrSkSLFP::Make(effect, "gaussian_fp");
return GrFPSuccess(GrFragmentProcessor::Compose(std::move(fp), std::move(inputFP)));
return GrFPSuccess(GrSkSLFP::Make(effect, "gaussian_fp", std::move(inputFP)));
}
#endif

View File

@ -662,7 +662,7 @@ DEF_TEST(SkRuntimeShaderSampleCoords, r) {
REPORTER_ASSERT(r, effect);
auto child = GrFragmentProcessor::MakeColor({ 1, 1, 1, 1 });
auto fp = GrSkSLFP::Make(effect, "test_fp", "child", std::move(child));
auto fp = GrSkSLFP::Make(effect, "test_fp", /*inputFP=*/nullptr, "child", std::move(child));
REPORTER_ASSERT(r, fp);
REPORTER_ASSERT(r, fp->childProcessor(0)->isSampledWithExplicitCoords() == expectExplicit);
@ -715,8 +715,8 @@ DEF_GPUTEST_FOR_ALL_CONTEXTS(GrSkSLFP_Specialized, r, ctxInfo) {
half4 main(float2 xy) { return color; }
)");
FpAndKey result;
result.fp = GrSkSLFP::Make(
std::move(effect), "color_fp", "color", GrSkSLFP::SpecializeIf(specialize, color));
result.fp = GrSkSLFP::Make(std::move(effect), "color_fp", /*inputFP=*/nullptr,
"color", GrSkSLFP::SpecializeIf(specialize, color));
GrProcessorKeyBuilder builder(&result.key);
result.fp->getGLSLProcessorKey(*ctxInfo.directContext()->priv().caps()->shaderCaps(),
&builder);