| /* |
| * Copyright 2022 Google LLC |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #ifndef skgpu_graphite_ShaderCodeDictionary_DEFINED |
| #define skgpu_graphite_ShaderCodeDictionary_DEFINED |
| |
| #include "include/core/SkSpan.h" |
| #include "include/effects/SkRuntimeEffect.h" |
| #include "include/private/base/SkTo.h" |
| #include "src/base/SkArenaAlloc.h" |
| #include "src/base/SkEnumBitMask.h" |
| #include "src/base/SkSpinlock.h" |
| #include "src/core/SkKnownRuntimeEffects.h" |
| #include "src/core/SkSLTypeShared.h" |
| #include "src/core/SkTHash.h" |
| #include "src/gpu/graphite/Attribute.h" |
| #include "src/gpu/graphite/BuiltInCodeSnippetID.h" |
| #include "src/gpu/graphite/PaintParamsKey.h" |
| #include "src/gpu/graphite/ResourceTypes.h" |
| #include "src/gpu/graphite/Uniform.h" |
| #include "src/gpu/graphite/UniquePaintParamsID.h" |
| |
| #include <array> |
| #include <cstddef> |
| #include <cstdint> |
| #include <memory> |
| #include <string> |
| #include <string_view> |
| |
| class SkRuntimeEffect; |
| |
| namespace skgpu::graphite { |
| |
| // TODO: How to represent the type (e.g., 2D) of texture being sampled? |
| class TextureAndSampler { |
| public: |
| constexpr TextureAndSampler(const char* name) : fName(name) {} |
| |
| const char* name() const { return fName; } |
| |
| private: |
| const char* fName; |
| }; |
| |
| enum class SnippetRequirementFlags : uint32_t { |
| kNone = 0x0, |
| |
| // Signature of the ShaderNode |
| kLocalCoords = 0x1, |
| kPriorStageOutput = 0x2, // AKA the "input" color, or the "src" argument for a blender |
| kBlenderDstColor = 0x4, // The "dst" argument for a blender |
| |
| // Special values and/or behaviors required for the snippet |
| kPrimitiveColor = 0x8, |
| kGradientBuffer = 0x10, |
| kStoresSamplerDescData = 0x20, // Indicates that the node stores numerical sampler data |
| kPassthroughLocalCoords = 0x40, // Indicates that the node will pass through local coords |
| // unmodified to its children. |
| kLiftExpression = 0x80, // Indicates that the liftable expression generated by this |
| // node will be lifted to the vertex shader and passed to the |
| // fragment shader as a varying. |
| kOmitExpression = 0x100, // Indicates that the liftable expression generated by this |
| // node will be used in a compound expression lifted to the |
| // vertex shader and omitted from the fragment shader entirely. |
| }; |
| SK_MAKE_BITMASK_OPS(SnippetRequirementFlags) |
| |
| class ShaderInfo; |
| class ShaderNode; |
| |
| // ShaderSnippets define the "ABI" of a SkSL module function and its required uniform data, as |
| // well as functions for generating the invoking SkSL. Snippets are composed into an effect tree |
| // using ShaderNodes. |
| struct ShaderSnippet { |
| struct Args { |
| std::string fPriorStageOutput; |
| std::string fBlenderDstColor; |
| std::string fFragCoord; |
| }; |
| |
| using GeneratePreambleForSnippetFn = std::string (*)(const ShaderInfo& shaderInfo, |
| const ShaderNode*); |
| using GenerateLiftableExpressionFn = std::string (*)(const ShaderInfo& shaderInfo, |
| const ShaderNode*, |
| const ShaderSnippet::Args& args); |
| static const Args kDefaultArgs; |
| |
| // If this snippet has an expression that can be lifted from the fragment shader to the vertex |
| // shader, the LiftableExpressionType specifies how the resolved expression is used by |
| // subsequent shader nodes. |
| enum class LiftableExpressionType : uint32_t { |
| kNone, |
| kLocalCoords, |
| kPriorStageOutput, |
| }; |
| |
| ShaderSnippet() = default; |
| |
| ShaderSnippet(const char* name, |
| const char* staticFn, |
| SkEnumBitMask<SnippetRequirementFlags> snippetRequirementFlags, |
| SkSpan<const Uniform> uniforms, |
| SkSpan<const TextureAndSampler> texturesAndSamplers = {}, |
| GeneratePreambleForSnippetFn preambleGenerator = nullptr, |
| int numChildren = 0, |
| GenerateLiftableExpressionFn liftableExpression = nullptr, |
| LiftableExpressionType liftableExpressionType = LiftableExpressionType::kNone, |
| Interpolation liftableExpressionInterpolation = Interpolation::kPerspective) |
| : fName(name) |
| , fStaticFunctionName(staticFn) |
| , fSnippetRequirementFlags(snippetRequirementFlags) |
| , fUniforms(uniforms) |
| , fTexturesAndSamplers(texturesAndSamplers) |
| , fNumChildren(numChildren) |
| , fPreambleGenerator(preambleGenerator) |
| , fLiftableExpressionInterpolation(liftableExpressionInterpolation) |
| , fLiftableExpressionType(liftableExpressionType) |
| , fLiftableExpressionGenerator(liftableExpression) { |
| // Must always provide a name; static function is not optional if using the default (null) |
| // generation logic. |
| SkASSERT(name); |
| SkASSERT(staticFn || preambleGenerator); |
| } |
| |
| bool needsLocalCoords() const { |
| return SkToBool(fSnippetRequirementFlags & SnippetRequirementFlags::kLocalCoords); |
| } |
| bool needsPriorStageOutput() const { |
| return SkToBool(fSnippetRequirementFlags & SnippetRequirementFlags::kPriorStageOutput); |
| } |
| bool needsBlenderDstColor() const { |
| return SkToBool(fSnippetRequirementFlags & SnippetRequirementFlags::kBlenderDstColor); |
| } |
| bool storesSamplerDescData() const { |
| return SkToBool(fSnippetRequirementFlags & SnippetRequirementFlags::kStoresSamplerDescData); |
| } |
| |
| const char* fName = nullptr; |
| const char* fStaticFunctionName = nullptr; |
| |
| // The features and args that this shader snippet requires in order to be invoked |
| SkEnumBitMask<SnippetRequirementFlags> fSnippetRequirementFlags{SnippetRequirementFlags::kNone}; |
| |
| // If not null, the list of uniforms in `fUniforms` describes an existing struct type declared |
| // in the Graphite modules with the given name. Instead of inlining the each uniform in the |
| // top-level interface block or aggregate struct, there will be a single member of this struct's |
| // type. |
| const char* fUniformStructName = nullptr; |
| // If the uniforms are being embedded as a sub-struct, this is the required starting alignment. |
| int fRequiredAlignment = -1; |
| |
| skia_private::TArray<Uniform> fUniforms; |
| skia_private::TArray<TextureAndSampler> fTexturesAndSamplers; |
| |
| int fNumChildren = 0; |
| GeneratePreambleForSnippetFn fPreambleGenerator = nullptr; |
| |
| Interpolation fLiftableExpressionInterpolation = Interpolation::kPerspective; |
| LiftableExpressionType fLiftableExpressionType = LiftableExpressionType::kNone; |
| GenerateLiftableExpressionFn fLiftableExpressionGenerator = nullptr; |
| }; |
| |
| // ShaderNodes organize snippets into an effect tree, and provide random access to the dynamically |
| // bound child snippets. Each node has a fixed number of children defined by its code ID |
| // (either a BuiltInCodeSnippetID or a runtime effect's assigned ID). All children are non-null. |
| // A ShaderNode tree represents a decompressed PaintParamsKey. |
| class ShaderNode { |
| public: |
| // ShaderNodes should be created in conjunction with an SkArenaAlloc that owns all nodes. |
| ShaderNode(const ShaderSnippet* snippet, |
| SkSpan<ShaderNode*> children, |
| int codeID, |
| int keyIndex, |
| SkSpan<const uint32_t> data) |
| : fEntry(snippet) |
| , fChildren(children) |
| , fCodeID(codeID) |
| , fKeyIndex(keyIndex) |
| , fRequiredFlags(snippet->fSnippetRequirementFlags) |
| , fData(data) { |
| SkASSERT(children.size() == (size_t) fEntry->fNumChildren); |
| |
| // Propagate requirement flags from children towards root. |
| const bool isRuntimeEffect = codeID >= kBuiltInCodeSnippetIDCount; |
| const bool isCompose = codeID == (int) BuiltInCodeSnippetID::kCompose || |
| codeID == (int) BuiltInCodeSnippetID::kBlendCompose; |
| for (const ShaderNode* child : children) { |
| // Mask off flags to not propagate. |
| SkEnumBitMask<SnippetRequirementFlags> mask = |
| SnippetRequirementFlags::kPassthroughLocalCoords | |
| SnippetRequirementFlags::kLiftExpression | |
| SnippetRequirementFlags::kOmitExpression; |
| // Runtime effects invoke children with explicit parameters so those requirements never |
| // need to propagate to the root. Similarly, compose only needs to propagate the |
| // variable parameters for the inner children. |
| if (isRuntimeEffect || (isCompose && child == children.back())) { |
| mask |= SnippetRequirementFlags::kLocalCoords | |
| SnippetRequirementFlags::kPriorStageOutput | |
| SnippetRequirementFlags::kBlenderDstColor; |
| } |
| fRequiredFlags |= (child->requiredFlags() & ~mask); |
| } |
| |
| // Data should only be provided if the snippet has the kStoresSamplerDescData flag. |
| SkASSERT(fData.empty() || snippet->storesSamplerDescData()); |
| } |
| |
| std::string generateDefaultPreamble(const ShaderInfo& shaderInfo) const; |
| std::string invokeAndAssign(const ShaderInfo& shaderInfo, |
| const ShaderSnippet::Args& args, |
| std::string* funcBody) const; |
| |
| std::string getExpressionVaryingName() const; |
| |
| int32_t codeSnippetId() const { return fCodeID; } |
| int32_t keyIndex() const { return fKeyIndex; } |
| const ShaderSnippet* entry() const { return fEntry; } |
| |
| SkEnumBitMask<SnippetRequirementFlags> requiredFlags() const { return fRequiredFlags; } |
| void setLiftExpressionFlag() { fRequiredFlags |= SnippetRequirementFlags::kLiftExpression; } |
| void setOmitExpressionFlag() { fRequiredFlags |= SnippetRequirementFlags::kOmitExpression; } |
| void unsetLocalCoordsFlag() { fRequiredFlags &= ~SnippetRequirementFlags::kLocalCoords; } |
| |
| int numChildren() const { return fEntry->fNumChildren; } |
| SkSpan<ShaderNode*> children() { return fChildren; } |
| SkSpan<const ShaderNode*> children() const { |
| return SkSpan<const ShaderNode*>(const_cast<const ShaderNode**>(fChildren.data()), |
| fChildren.size()); |
| } |
| const ShaderNode* child(int childIndex) const { return fChildren[childIndex]; } |
| |
| SkSpan<const uint32_t> data() const { return fData; } |
| |
| private: |
| const ShaderSnippet* fEntry; // Owned by the ShaderCodeDictionary |
| SkSpan<ShaderNode*> fChildren; // Owned by the ShaderInfo's arena |
| |
| int32_t fCodeID; |
| int32_t fKeyIndex; // index back to PaintParamsKey, unique across nodes within a ShaderInfo |
| |
| SkEnumBitMask<SnippetRequirementFlags> fRequiredFlags; |
| SkSpan<const uint32_t> fData; // Subspan of PaintParamsKey's fData; shares same owner |
| }; |
| |
| // ShaderCodeDictionary is a thread-safe dictionary of ShaderSnippets to code IDs for use with |
| // creating PaintParamKeys, as well as assigning unique IDs to each encountered PaintParamKey. |
| // It defines ShaderSnippets for every BuiltInCodeSnippetID and maintains records for IDs per |
| // SkRuntimeEffect, including de-duplicating equivalent SkRuntimeEffect objects. |
| class ShaderCodeDictionary { |
| public: |
| ShaderCodeDictionary(Layout layout, |
| SkSpan<sk_sp<SkRuntimeEffect>> userDefinedKnownRuntimeEffects); |
| |
| UniquePaintParamsID findOrCreate(const PaintParamsKey&); |
| |
| UniquePaintParamsID findOrCreate(PaintParamsKeyBuilder*) SK_EXCLUDES(fSpinLock); |
| |
| PaintParamsKey lookup(UniquePaintParamsID) const SK_EXCLUDES(fSpinLock); |
| |
| SkString idToString(UniquePaintParamsID id) const { return this->lookup(id).toString(this); } |
| |
| #if defined(SK_DEBUG) |
| bool isValidID(int snippetID) const SK_EXCLUDES(fSpinLock); |
| |
| void dump(UniquePaintParamsID) const; |
| #endif |
| |
| // This method can return nullptr |
| const ShaderSnippet* getEntry(int codeSnippetID) const SK_EXCLUDES(fSpinLock); |
| const ShaderSnippet* getEntry(BuiltInCodeSnippetID codeSnippetID) const { |
| // Built-in code snippets are initialized once so there is no need to take a lock |
| return &fBuiltInCodeSnippets[SkTo<int>(codeSnippetID)]; |
| } |
| |
| // getEntry can be used to retrieve the ShaderSnippet for a user-defined known runtime effect |
| // but, since the ShaderCodeDictionary owns those runtime effects, we need another entry |
| // point to retrieve the actual effect. For unknown runtime effects this is handled by the |
| // RuntimeEffectDictionary which, transiently, holds a ref on the encountered runtime effects. |
| const SkRuntimeEffect* getUserDefinedKnownRuntimeEffect(int codeSnippetID) const; |
| |
| // Returns -1 on failure |
| int findOrCreateRuntimeEffectSnippet(const SkRuntimeEffect* effect) SK_EXCLUDES(fSpinLock); |
| |
| bool isUserDefinedKnownRuntimeEffect(int candidate) const; |
| #if defined(GPU_TEST_UTILS) |
| int numUserDefinedRuntimeEffects() const SK_EXCLUDES(fSpinLock); |
| int numUserDefinedKnownRuntimeEffects() const; |
| #endif |
| |
| private: |
| const char* addTextToArena(std::string_view text); |
| |
| SkSpan<const Uniform> convertUniforms(const SkRuntimeEffect* effect); |
| ShaderSnippet convertRuntimeEffect(const SkRuntimeEffect* effect, const char* name); |
| |
| void registerUserDefinedKnownRuntimeEffects(SkSpan<sk_sp<SkRuntimeEffect>>); |
| |
| const Layout fLayout; |
| |
| std::array<ShaderSnippet, kBuiltInCodeSnippetIDCount> fBuiltInCodeSnippets; |
| |
| using KnownRuntimeEffectArray = std::array<ShaderSnippet, SkKnownRuntimeEffects::kStableKeyCnt>; |
| KnownRuntimeEffectArray fKnownRuntimeEffectCodeSnippets SK_GUARDED_BY(fSpinLock); |
| |
| using ShaderSnippetArray = skia_private::TArray<ShaderSnippet>; |
| using RuntimeEffectArray = skia_private::TArray<sk_sp<SkRuntimeEffect>>; |
| |
| // These two arrays are not guarded by a lock since they are only initialized in the ctor |
| ShaderSnippetArray fUserDefinedKnownCodeSnippets; |
| RuntimeEffectArray fUserDefinedKnownRuntimeEffects; |
| |
| // The value returned from 'getEntry' must be stable so, hold the user-defined code snippet |
| // entries as pointers. |
| ShaderSnippetArray fUserDefinedCodeSnippets SK_GUARDED_BY(fSpinLock); |
| |
| // TODO: can we do something better given this should have write-seldom/read-often behavior? |
| mutable SkSpinlock fSpinLock; |
| |
| using PaintIDMap = skia_private::THashMap<PaintParamsKey, |
| UniquePaintParamsID, |
| PaintParamsKey::Hash>; |
| |
| PaintIDMap fPaintKeyToID SK_GUARDED_BY(fSpinLock); |
| skia_private::TArray<PaintParamsKey> fIDToPaintKey SK_GUARDED_BY(fSpinLock); |
| |
| SK_BEGIN_REQUIRE_DENSE |
| struct RuntimeEffectKey { |
| uint32_t fHash; |
| uint32_t fUniformSize; |
| |
| bool operator==(RuntimeEffectKey rhs) const { |
| return fHash == rhs.fHash && fUniformSize == rhs.fUniformSize; |
| } |
| }; |
| SK_END_REQUIRE_DENSE |
| |
| // A map from RuntimeEffectKeys (hash plus uniforms) to code-snippet IDs. RuntimeEffectKeys |
| // don't track the lifetime of a runtime effect at all; they live forever, and a newly- |
| // instantiated runtime effect with the same program as a previously-discarded effect will reuse |
| // an existing ID. Entries in the runtime-effect map are never removed; they only disappear when |
| // the context is discarded, which takes the ShaderCodeDictionary along with it. However, they |
| // are extremely small (< 20 bytes) so the memory footprint should be unnoticeable. |
| using RuntimeEffectMap = skia_private::THashMap<RuntimeEffectKey, int32_t>; |
| RuntimeEffectMap fRuntimeEffectMap SK_GUARDED_BY(fSpinLock); |
| |
| // This arena holds: |
| // - the backing data for PaintParamsKeys in `fPaintKeyToID` and `fIDToPaintKey` |
| // - Uniform data created by `findOrCreateRuntimeEffectSnippet` |
| // and in all cases is guarded by `fSpinLock` |
| SkArenaAlloc fArena{256}; |
| }; |
| |
| } // namespace skgpu::graphite |
| |
| #endif // skgpu_graphite_ShaderCodeDictionary_DEFINED |