Add templated GrSkSLFP factory to eliminate nearly all overhead
In debug builds, this validates that all of the arguments being passed
have names and sizes that match the SkSL expectation. In release build,
those checks are skipped, and this trusts the caller. In that situation,
nearly everthing is inlined away:
- We ask the effect how much space is needed to hold the uniform blob
- We allocate the FP with that much additional space
- We call addChild for each child FP passed, and memcpy every other
value directly into the FP's footer.
Change-Id: I28e08feea8415e45e5cdf8b080e8c78ae1e28fb7
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/415900
Reviewed-by: John Stiles <johnstiles@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
diff --git a/src/effects/imagefilters/SkArithmeticImageFilter.cpp b/src/effects/imagefilters/SkArithmeticImageFilter.cpp
index 0a46995..5e557c3 100644
--- a/src/effects/imagefilters/SkArithmeticImageFilter.cpp
+++ b/src/effects/imagefilters/SkArithmeticImageFilter.cpp
@@ -308,7 +308,7 @@
std::unique_ptr<GrFragmentProcessor> dstFP,
const SkV4& k,
bool enforcePMColor) {
- static constexpr char kCode[] = R"(
+ static auto effect = SkRuntimeEffect::MakeForShader(SkString(R"(
uniform shader srcFP;
uniform shader dstFP;
uniform half4 k;
@@ -323,13 +323,12 @@
color.rgb = min(color.rgb, max(color.a, pmClamp));
return color;
}
- )";
- auto builder = GrRuntimeFPBuilder::Make<kCode, SkRuntimeEffect::MakeForShader>();
- builder.child("srcFP") = std::move(srcFP);
- builder.child("dstFP") = std::move(dstFP);
- builder.uniform("k") = k;
- builder.uniform("pmClamp") = enforcePMColor ? 0.0f : 1.0f;
- return builder.makeFP();
+ )")).effect;
+ return GrSkSLFP::Make(effect, "arithmetic_fp",
+ "srcFP", std::move(srcFP),
+ "dstFP", std::move(dstFP),
+ "k", k,
+ "pmClamp", enforcePMColor ? 0.0f : 1.0f);
}
sk_sp<SkSpecialImage> SkArithmeticImageFilter::filterImageGPU(
diff --git a/src/gpu/GrFragmentProcessor.cpp b/src/gpu/GrFragmentProcessor.cpp
index 41090f1..afd3b3d 100644
--- a/src/gpu/GrFragmentProcessor.cpp
+++ b/src/gpu/GrFragmentProcessor.cpp
@@ -209,13 +209,11 @@
std::unique_ptr<GrFragmentProcessor> GrFragmentProcessor::MakeColor(SkPMColor4f color) {
// Use ColorFilter signature/factory to get the constant output for constant input optimization
- static constexpr char kCode[] = R"(
+ static auto effect = SkRuntimeEffect::MakeForColorFilter(SkString(R"(
uniform half4 color;
half4 main(half4 inColor) { return color; }
- )";
- auto builder = GrRuntimeFPBuilder::Make<kCode, SkRuntimeEffect::MakeForColorFilter>();
- builder.uniform("color") = color;
- return builder.makeFP();
+ )")).effect;
+ return GrSkSLFP::Make(effect, "color_fp", "color", color);
}
std::unique_ptr<GrFragmentProcessor> GrFragmentProcessor::MulChildByInputAlpha(
diff --git a/src/gpu/effects/GrSkSLFP.cpp b/src/gpu/effects/GrSkSLFP.cpp
index bc62788..6733908 100644
--- a/src/gpu/effects/GrSkSLFP.cpp
+++ b/src/gpu/effects/GrSkSLFP.cpp
@@ -193,6 +193,19 @@
new (uniformSize) GrSkSLFP(std::move(effect), name, std::move(uniforms)));
}
+GrSkSLFP::GrSkSLFP(sk_sp<SkRuntimeEffect> effect, const char* name)
+ : INHERITED(kGrSkSLFP_ClassID,
+ effect->getFilterColorProgram()
+ ? kConstantOutputForConstantInput_OptimizationFlag
+ : kNone_OptimizationFlags)
+ , fEffect(std::move(effect))
+ , fName(name)
+ , fUniformSize(fEffect->uniformSize()) {
+ if (fEffect->usesSampleCoords()) {
+ this->setUsesSampleCoordsDirectly();
+ }
+}
+
GrSkSLFP::GrSkSLFP(sk_sp<SkRuntimeEffect> effect, const char* name, sk_sp<SkData> uniforms)
: INHERITED(kGrSkSLFP_ClassID,
effect->getFilterColorProgram()
diff --git a/src/gpu/effects/GrSkSLFP.h b/src/gpu/effects/GrSkSLFP.h
index 82905c4..67c3836 100644
--- a/src/gpu/effects/GrSkSLFP.h
+++ b/src/gpu/effects/GrSkSLFP.h
@@ -12,7 +12,10 @@
#include "include/effects/SkRuntimeEffect.h"
#include "include/gpu/GrContextOptions.h"
#include "src/gpu/GrFragmentProcessor.h"
+
#include <atomic>
+#include <utility>
+#include <vector>
class GrShaderCaps;
class SkData;
@@ -35,9 +38,55 @@
std::unique_ptr<GrFragmentProcessor> clone() const override;
-private:
- GrSkSLFP(sk_sp<SkRuntimeEffect> effect, const char* name, sk_sp<SkData> uniforms);
+ /*
+ * Constructs a GrSkSLFP from a series of name-value pairs, corresponding to the children and
+ * uniform data members of the effect's SkSL.
+ * The variable length args... must contain all of the children and uniforms expected.
+ * Each individual argument must be preceded by a name that matches the SkSL name of the value
+ * being set. For children, the next argument must be a std::unique_ptr<GrFragmentProcessor>.
+ * For uniforms, the next argument must be data of the correct size and type.
+ *
+ * For example, given:
+ * uniform shader input;
+ * uniform float scale;
+ * uniform half2 pt;
+ * half4 main() { ... }
+ *
+ * A call to GrSkSLFP would be formatted like:
+ * std::unique_ptr<GrFragmentProcessor> child = ...;
+ * float scaleVal = ...;
+ * SkV2 ptVal = ...;
+ * auto fp = GrSkSLFP::Make(effect, "my_effect",
+ * "input", std::move(child),
+ * "scale", scaleVal,
+ * "pt", ptVal);
+ *
+ * The uniforms must appear in the correct order, as must the children. Technically, the two
+ * lists can be interleaved. In debug builds, the number, names, and sizes of all arguments are
+ * checked with assertions. In release builds, all checks are elided. In either case, the
+ * uniform data is directly copied into the footer allocated after the FP.
+ */
+ template <typename... Args>
+ static std::unique_ptr<GrSkSLFP> Make(sk_sp<SkRuntimeEffect> effect,
+ const char* name,
+ Args&&... args) {
+#ifdef SK_DEBUG
+ checkArgs(effect->fUniforms.begin(),
+ effect->fUniforms.end(),
+ effect->fChildren.begin(),
+ effect->fChildren.end(),
+ std::forward<Args>(args)...);
+#endif
+ size_t uniformSize = effect->uniformSize();
+ std::unique_ptr<GrSkSLFP> fp(new (uniformSize) GrSkSLFP(std::move(effect), name));
+ fp->appendArgs(fp->uniformData(), std::forward<Args>(args)...);
+ return fp;
+ }
+
+private:
+ GrSkSLFP(sk_sp<SkRuntimeEffect> effect, const char* name);
+ GrSkSLFP(sk_sp<SkRuntimeEffect> effect, const char* name, sk_sp<SkData> uniforms);
GrSkSLFP(const GrSkSLFP& other);
std::unique_ptr<GrGLSLFragmentProcessor> onMakeProgramImpl() const override;
@@ -50,6 +99,69 @@
void* uniformData() const { return (void*)(this + 1); }
+ // Helpers to attach variadic template args to a newly constructed FP:
+ void appendArgs(void* ptr) {}
+ template <typename... Args>
+ void appendArgs(void* ptr,
+ const char* name,
+ std::unique_ptr<GrFragmentProcessor>&& child,
+ Args&&... remainder) {
+ this->addChild(std::move(child));
+ this->appendArgs(ptr, std::forward<Args>(remainder)...);
+ }
+ template <typename T, typename... Args>
+ void appendArgs(void* ptr, const char* name, const T& val, Args&&... remainder) {
+ memcpy(ptr, &val, sizeof(val));
+ this->appendArgs(SkTAddOffset<void>(ptr, sizeof(val)), std::forward<Args>(remainder)...);
+ }
+
+#ifdef SK_DEBUG
+ // Validates that all args passed to the template factory have the right names and sizes
+ using child_iterator = std::vector<SkRuntimeEffect::Child>::const_iterator;
+ using uniform_iterator = std::vector<SkRuntimeEffect::Uniform>::const_iterator;
+ static void checkArgs(uniform_iterator uIter,
+ uniform_iterator uEnd,
+ child_iterator cIter,
+ child_iterator cEnd) {
+ SkASSERTF(uIter == uEnd, "Expected more uniforms, starting with '%s'", uIter->name.c_str());
+ SkASSERTF(cIter == cEnd, "Expected more children, starting with '%s'", cIter->name.c_str());
+ }
+ template <typename... Args>
+ static void checkArgs(uniform_iterator uIter,
+ uniform_iterator uEnd,
+ child_iterator cIter,
+ child_iterator cEnd,
+ const char* name,
+ std::unique_ptr<GrFragmentProcessor>&& child,
+ Args&&... remainder) {
+ // NOTE: This function (necessarily) gets an rvalue reference to child, but deliberately
+ // does not use it. We leave it intact, and our caller (Make) will pass another rvalue
+ // reference to appendArgs, which will then move it to call addChild.
+ SkASSERTF(cIter != cEnd, "Too many children, wasn't expecting '%s'", name);
+ SkASSERTF(cIter->name.equals(name),
+ "Expected child '%s', got '%s' instead",
+ cIter->name.c_str(), name);
+ checkArgs(uIter, uEnd, ++cIter, cEnd, std::forward<Args>(remainder)...);
+ }
+ template <typename T, typename... Args>
+ static void checkArgs(uniform_iterator uIter,
+ uniform_iterator uEnd,
+ child_iterator cIter,
+ child_iterator cEnd,
+ const char* name,
+ const T& val,
+ Args&&... remainder) {
+ SkASSERTF(uIter != uEnd, "Too many uniforms, wasn't expecting '%s'", name);
+ SkASSERTF(uIter->name.equals(name),
+ "Expected uniform '%s', got '%s' instead",
+ uIter->name.c_str(), name);
+ SkASSERTF(uIter->sizeInBytes() == sizeof(val),
+ "Expected uniform '%s' to be %zu bytes, got %zu instead",
+ name, uIter->sizeInBytes(), sizeof(val));
+ checkArgs(++uIter, uEnd, cIter, cEnd, std::forward<Args>(remainder)...);
+ }
+#endif
+
sk_sp<SkRuntimeEffect> fEffect;
const char* fName;
size_t fUniformSize;
diff --git a/src/utils/SkShadowUtils.cpp b/src/utils/SkShadowUtils.cpp
index 709c9e5..88ee681 100644
--- a/src/utils/SkShadowUtils.cpp
+++ b/src/utils/SkShadowUtils.cpp
@@ -87,15 +87,15 @@
GrFPResult SkGaussianColorFilter::asFragmentProcessor(std::unique_ptr<GrFragmentProcessor> inputFP,
GrRecordingContext*,
const GrColorInfo&) const {
- static constexpr char kCode[] = R"(
+ static auto effect = SkRuntimeEffect::MakeForColorFilter(SkString(R"(
half4 main(half4 inColor) {
half factor = 1 - inColor.a;
factor = exp(-factor * factor * 4) - 0.018;
return half4(factor);
}
- )";
- auto builder = GrRuntimeFPBuilder::Make<kCode, SkRuntimeEffect::MakeForColorFilter>();
- return GrFPSuccess(GrFragmentProcessor::Compose(builder.makeFP(), std::move(inputFP)));
+ )")).effect;
+ auto fp = GrSkSLFP::Make(effect, "gaussian_fp");
+ return GrFPSuccess(GrFragmentProcessor::Compose(std::move(fp), std::move(inputFP)));
}
#endif