/* * Copyright 2019 Google LLC * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #ifndef SkRuntimeEffect_DEFINED #define SkRuntimeEffect_DEFINED #include "include/core/SkBlender.h" #include "include/core/SkColorFilter.h" #include "include/core/SkData.h" #include "include/core/SkImageInfo.h" #include "include/core/SkMatrix.h" #include "include/core/SkShader.h" #include "include/core/SkSpan.h" #include "include/core/SkString.h" #include "include/core/SkTypes.h" #include "include/private/SkSLSampleUsage.h" #include "include/private/base/SkOnce.h" #include "include/private/base/SkTemplates.h" #include #include #include #ifdef SK_ENABLE_SKSL #include "include/sksl/SkSLVersion.h" class GrRecordingContext; class SkFilterColorProgram; class SkImage; class SkRuntimeImageFilter; namespace SkSL { class DebugTrace; class ErrorReporter; class FunctionDefinition; struct Program; enum class ProgramKind : int8_t; struct ProgramSettings; } // namespace SkSL namespace skvm { class Program; } namespace SkSL::RP { class Program; } /* * SkRuntimeEffect supports creating custom SkShader and SkColorFilter objects using Skia's SkSL * shading language. * * NOTE: This API is experimental and subject to change. */ class SK_API SkRuntimeEffect : public SkRefCnt { public: // Reflected description of a uniform variable in the effect's SkSL struct Uniform { enum class Type { kFloat, kFloat2, kFloat3, kFloat4, kFloat2x2, kFloat3x3, kFloat4x4, kInt, kInt2, kInt3, kInt4, }; enum Flags { // Uniform is declared as an array. 'count' contains array length. kArray_Flag = 0x1, // Uniform is declared with layout(color). Colors should be supplied as unpremultiplied, // extended-range (unclamped) sRGB (ie SkColor4f). The uniform will be automatically // transformed to unpremultiplied extended-range working-space colors. kColor_Flag = 0x2, // When used with SkMeshSpecification, indicates that the uniform is present in the // vertex shader. Not used with SkRuntimeEffect. kVertex_Flag = 0x4, // When used with SkMeshSpecification, indicates that the uniform is present in the // fragment shader. Not used with SkRuntimeEffect. kFragment_Flag = 0x8, // This flag indicates that the SkSL uniform uses a medium-precision type // (i.e., `half` instead of `float`). kHalfPrecision_Flag = 0x10, }; std::string_view name; size_t offset; Type type; int count; uint32_t flags; bool isArray() const { return SkToBool(this->flags & kArray_Flag); } bool isColor() const { return SkToBool(this->flags & kColor_Flag); } size_t sizeInBytes() const; }; // Reflected description of a uniform child (shader or colorFilter) in the effect's SkSL enum class ChildType { kShader, kColorFilter, kBlender, }; struct Child { std::string_view name; ChildType type; int index; }; class Options { public: // For testing purposes, disables optimization and inlining. (Normally, Runtime Effects // don't run the inliner directly, but they still get an inlining pass once they are // painted.) bool forceUnoptimized = false; private: friend class SkRuntimeEffect; friend class SkRuntimeEffectPriv; // This flag allows Runtime Effects to access Skia implementation details like sk_FragCoord // and functions with private identifiers (e.g. $rgb_to_hsl). bool allowPrivateAccess = false; // TODO(skia:11209) - Replace this with a promised SkCapabilities? // This flag lifts the ES2 restrictions on Runtime Effects that are gated by the // `strictES2Mode` check. Be aware that the software renderer and pipeline-stage effect are // still largely ES3-unaware and can still fail or crash if post-ES2 features are used. // This is only intended for use by tests and certain internally created effects. SkSL::Version maxVersionAllowed = SkSL::Version::k100; }; // If the effect is compiled successfully, `effect` will be non-null. // Otherwise, `errorText` will contain the reason for failure. struct Result { sk_sp effect; SkString errorText; }; // MakeForColorFilter and MakeForShader verify that the SkSL code is valid for those stages of // the Skia pipeline. In all of the signatures described below, color parameters and return // values are flexible. They are listed as being 'vec4', but they can also be 'half4' or // 'float4'. ('vec4' is an alias for 'float4'). // We can't use a default argument for `options` due to a bug in Clang. // https://bugs.llvm.org/show_bug.cgi?id=36684 // Color filter SkSL requires an entry point that looks like: // vec4 main(vec4 inColor) { ... } static Result MakeForColorFilter(SkString sksl, const Options&); static Result MakeForColorFilter(SkString sksl) { return MakeForColorFilter(std::move(sksl), Options{}); } // Shader SkSL requires an entry point that looks like: // vec4 main(vec2 inCoords) { ... } static Result MakeForShader(SkString sksl, const Options&); static Result MakeForShader(SkString sksl) { return MakeForShader(std::move(sksl), Options{}); } // Blend SkSL requires an entry point that looks like: // vec4 main(vec4 srcColor, vec4 dstColor) { ... } static Result MakeForBlender(SkString sksl, const Options&); static Result MakeForBlender(SkString sksl) { return MakeForBlender(std::move(sksl), Options{}); } // Object that allows passing a SkShader, SkColorFilter or SkBlender as a child class ChildPtr { public: ChildPtr() = default; ChildPtr(sk_sp s) : fChild(std::move(s)) {} ChildPtr(sk_sp cf) : fChild(std::move(cf)) {} ChildPtr(sk_sp b) : fChild(std::move(b)) {} // Asserts that the flattenable is either null, or one of the legal derived types ChildPtr(sk_sp f); std::optional type() const; SkShader* shader() const; SkColorFilter* colorFilter() const; SkBlender* blender() const; SkFlattenable* flattenable() const { return fChild.get(); } using sk_is_trivially_relocatable = std::true_type; private: sk_sp fChild; static_assert(::sk_is_trivially_relocatable::value); }; sk_sp makeShader(sk_sp uniforms, sk_sp children[], size_t childCount, const SkMatrix* localMatrix = nullptr) const; sk_sp makeShader(sk_sp uniforms, SkSpan children, const SkMatrix* localMatrix = nullptr) const; sk_sp makeImage(GrRecordingContext*, sk_sp uniforms, SkSpan children, const SkMatrix* localMatrix, SkImageInfo resultInfo, bool mipmapped) const; sk_sp makeColorFilter(sk_sp uniforms) const; sk_sp makeColorFilter(sk_sp uniforms, sk_sp children[], size_t childCount) const; sk_sp makeColorFilter(sk_sp uniforms, SkSpan children) const; sk_sp makeBlender(sk_sp uniforms, SkSpan children = {}) const; /** * Creates a new Runtime Effect patterned after an already-existing one. The new shader behaves * like the original, but also creates a debug trace of its execution at the requested * coordinate. After painting with this shader, the associated DebugTrace object will contain a * shader execution trace. Call `writeTrace` on the debug trace object to generate a full trace * suitable for a debugger, or call `dump` to emit a human-readable trace. * * Debug traces are only supported on a raster (non-GPU) canvas. * Debug traces are currently only supported on shaders. Color filter and blender tracing is a * work-in-progress. */ struct TracedShader { sk_sp shader; sk_sp debugTrace; }; static TracedShader MakeTraced(sk_sp shader, const SkIPoint& traceCoord); // Returns the SkSL source of the runtime effect shader. const std::string& source() const; // Combined size of all 'uniform' variables. When calling makeColorFilter or makeShader, // provide an SkData of this size, containing values for all of those variables. size_t uniformSize() const; SkSpan uniforms() const { return SkSpan(fUniforms); } SkSpan children() const { return SkSpan(fChildren); } // Returns pointer to the named uniform variable's description, or nullptr if not found const Uniform* findUniform(std::string_view name) const; // Returns pointer to the named child's description, or nullptr if not found const Child* findChild(std::string_view name) const; // Allows the runtime effect type to be identified. bool allowShader() const { return (fFlags & kAllowShader_Flag); } bool allowColorFilter() const { return (fFlags & kAllowColorFilter_Flag); } bool allowBlender() const { return (fFlags & kAllowBlender_Flag); } static void RegisterFlattenables(); ~SkRuntimeEffect() override; private: enum Flags { kUsesSampleCoords_Flag = 0x01, kAllowColorFilter_Flag = 0x02, kAllowShader_Flag = 0x04, kAllowBlender_Flag = 0x08, kSamplesOutsideMain_Flag = 0x10, kUsesColorTransform_Flag = 0x20, kAlwaysOpaque_Flag = 0x40, }; SkRuntimeEffect(std::unique_ptr baseProgram, const Options& options, const SkSL::FunctionDefinition& main, std::vector&& uniforms, std::vector&& children, std::vector&& sampleUsages, uint32_t flags); sk_sp makeUnoptimizedClone(); static Result MakeFromSource(SkString sksl, const Options& options, SkSL::ProgramKind kind); static Result MakeInternal(std::unique_ptr program, const Options& options, SkSL::ProgramKind kind); static SkSL::ProgramSettings MakeSettings(const Options& options); uint32_t hash() const { return fHash; } bool usesSampleCoords() const { return (fFlags & kUsesSampleCoords_Flag); } bool samplesOutsideMain() const { return (fFlags & kSamplesOutsideMain_Flag); } bool usesColorTransform() const { return (fFlags & kUsesColorTransform_Flag); } bool alwaysOpaque() const { return (fFlags & kAlwaysOpaque_Flag); } const SkFilterColorProgram* getFilterColorProgram() const; const SkSL::RP::Program* getRPProgram() const; #if defined(SK_GANESH) friend class GrSkSLFP; // fBaseProgram, fSampleUsages friend class GrGLSLSkSLFP; // #endif friend class SkRTShader; // fBaseProgram, fMain, fSampleUsages, getRPProgram() friend class SkRuntimeBlender; // friend class SkRuntimeColorFilter; // friend class SkFilterColorProgram; friend class SkRuntimeEffectPriv; uint32_t fHash; std::unique_ptr fBaseProgram; std::unique_ptr fRPProgram; mutable SkOnce fCompileRPProgramOnce; const SkSL::FunctionDefinition& fMain; std::vector fUniforms; std::vector fChildren; std::vector fSampleUsages; std::unique_ptr fFilterColorProgram; uint32_t fFlags; // Flags }; /** Base class for SkRuntimeShaderBuilder, defined below. */ class SkRuntimeEffectBuilder { public: struct BuilderUniform { // Copy 'val' to this variable. No type conversion is performed - 'val' must be same // size as expected by the effect. Information about the variable can be queried by // looking at fVar. If the size is incorrect, no copy will be performed, and debug // builds will abort. If this is the result of querying a missing variable, fVar will // be nullptr, and assigning will also do nothing (and abort in debug builds). template std::enable_if_t::value, BuilderUniform&> operator=( const T& val) { if (!fVar) { SkDEBUGFAIL("Assigning to missing variable"); } else if (sizeof(val) != fVar->sizeInBytes()) { SkDEBUGFAIL("Incorrect value size"); } else { memcpy(SkTAddOffset(fOwner->writableUniformData(), fVar->offset), &val, sizeof(val)); } return *this; } BuilderUniform& operator=(const SkMatrix& val) { if (!fVar) { SkDEBUGFAIL("Assigning to missing variable"); } else if (fVar->sizeInBytes() != 9 * sizeof(float)) { SkDEBUGFAIL("Incorrect value size"); } else { float* data = SkTAddOffset(fOwner->writableUniformData(), (ptrdiff_t)fVar->offset); data[0] = val.get(0); data[1] = val.get(3); data[2] = val.get(6); data[3] = val.get(1); data[4] = val.get(4); data[5] = val.get(7); data[6] = val.get(2); data[7] = val.get(5); data[8] = val.get(8); } return *this; } template bool set(const T val[], const int count) { static_assert(std::is_trivially_copyable::value, "Value must be trivial copyable"); if (!fVar) { SkDEBUGFAIL("Assigning to missing variable"); return false; } else if (sizeof(T) * count != fVar->sizeInBytes()) { SkDEBUGFAIL("Incorrect value size"); return false; } else { memcpy(SkTAddOffset(fOwner->writableUniformData(), fVar->offset), val, sizeof(T) * count); } return true; } SkRuntimeEffectBuilder* fOwner; const SkRuntimeEffect::Uniform* fVar; // nullptr if the variable was not found }; struct BuilderChild { template BuilderChild& operator=(sk_sp val) { if (!fChild) { SkDEBUGFAIL("Assigning to missing child"); } else { fOwner->fChildren[(size_t)fChild->index] = std::move(val); } return *this; } BuilderChild& operator=(std::nullptr_t) { if (!fChild) { SkDEBUGFAIL("Assigning to missing child"); } else { fOwner->fChildren[(size_t)fChild->index] = SkRuntimeEffect::ChildPtr{}; } return *this; } SkRuntimeEffectBuilder* fOwner; const SkRuntimeEffect::Child* fChild; // nullptr if the child was not found }; const SkRuntimeEffect* effect() const { return fEffect.get(); } BuilderUniform uniform(std::string_view name) { return { this, fEffect->findUniform(name) }; } BuilderChild child(std::string_view name) { return { this, fEffect->findChild(name) }; } // Get access to the collated uniforms and children (in the order expected by APIs like // makeShader on the effect): sk_sp uniforms() { return fUniforms; } SkSpan children() { return fChildren; } protected: SkRuntimeEffectBuilder() = delete; explicit SkRuntimeEffectBuilder(sk_sp effect) : fEffect(std::move(effect)) , fUniforms(SkData::MakeZeroInitialized(fEffect->uniformSize())) , fChildren(fEffect->children().size()) {} explicit SkRuntimeEffectBuilder(sk_sp effect, sk_sp uniforms) : fEffect(std::move(effect)) , fUniforms(std::move(uniforms)) , fChildren(fEffect->children().size()) {} SkRuntimeEffectBuilder(SkRuntimeEffectBuilder&&) = default; SkRuntimeEffectBuilder(const SkRuntimeEffectBuilder&) = default; SkRuntimeEffectBuilder& operator=(SkRuntimeEffectBuilder&&) = delete; SkRuntimeEffectBuilder& operator=(const SkRuntimeEffectBuilder&) = delete; private: void* writableUniformData() { if (!fUniforms->unique()) { fUniforms = SkData::MakeWithCopy(fUniforms->data(), fUniforms->size()); } return fUniforms->writable_data(); } sk_sp fEffect; sk_sp fUniforms; std::vector fChildren; }; /** * SkRuntimeShaderBuilder is a utility to simplify creating SkShader objects from SkRuntimeEffects. * * NOTE: Like SkRuntimeEffect, this API is experimental and subject to change! * * Given an SkRuntimeEffect, the SkRuntimeShaderBuilder manages creating an input data block and * provides named access to the 'uniform' variables in that block, as well as named access * to a list of child shader slots. Usage: * * sk_sp effect = ...; * SkRuntimeShaderBuilder builder(effect); * builder.uniform("some_uniform_float") = 3.14f; * builder.uniform("some_uniform_matrix") = SkM44::Rotate(...); * builder.child("some_child_effect") = mySkImage->makeShader(...); * ... * sk_sp shader = builder.makeShader(nullptr, false); * * Note that SkRuntimeShaderBuilder is built entirely on the public API of SkRuntimeEffect, * so can be used as-is or serve as inspiration for other interfaces or binding techniques. */ class SK_API SkRuntimeShaderBuilder : public SkRuntimeEffectBuilder { public: explicit SkRuntimeShaderBuilder(sk_sp); // This is currently required by Android Framework but may go away if that dependency // can be removed. SkRuntimeShaderBuilder(const SkRuntimeShaderBuilder&) = default; ~SkRuntimeShaderBuilder(); sk_sp makeShader(const SkMatrix* localMatrix = nullptr); sk_sp makeImage(GrRecordingContext*, const SkMatrix* localMatrix, SkImageInfo resultInfo, bool mipmapped); private: using INHERITED = SkRuntimeEffectBuilder; explicit SkRuntimeShaderBuilder(sk_sp effect, sk_sp uniforms) : INHERITED(std::move(effect), std::move(uniforms)) {} friend class SkRuntimeImageFilter; }; /** * SkRuntimeColorFilterBuilder makes it easy to setup and assign uniforms to runtime color filters. */ class SK_API SkRuntimeColorFilterBuilder : public SkRuntimeEffectBuilder { public: explicit SkRuntimeColorFilterBuilder(sk_sp); ~SkRuntimeColorFilterBuilder(); SkRuntimeColorFilterBuilder(const SkRuntimeColorFilterBuilder&) = delete; SkRuntimeColorFilterBuilder& operator=(const SkRuntimeColorFilterBuilder&) = delete; sk_sp makeColorFilter(); private: using INHERITED = SkRuntimeEffectBuilder; }; /** * SkRuntimeBlendBuilder is a utility to simplify creation and uniform setup of runtime blenders. */ class SK_API SkRuntimeBlendBuilder : public SkRuntimeEffectBuilder { public: explicit SkRuntimeBlendBuilder(sk_sp); ~SkRuntimeBlendBuilder(); SkRuntimeBlendBuilder(const SkRuntimeBlendBuilder&) = delete; SkRuntimeBlendBuilder& operator=(const SkRuntimeBlendBuilder&) = delete; sk_sp makeBlender(); private: using INHERITED = SkRuntimeEffectBuilder; }; #endif // SK_ENABLE_SKSL #endif // SkRuntimeEffect_DEFINED