| /* | |
| * 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/core/SkTypes.h" | |
| #include "include/private/SkSpinlock.h" | |
| #include "include/private/base/SkMacros.h" | |
| #include "include/private/base/SkThreadAnnotations.h" | |
| #include "include/private/base/SkTo.h" | |
| #include "src/base/SkArenaAlloc.h" | |
| #include "src/core/SkEnumBitMask.h" | |
| #include "src/core/SkTHash.h" | |
| #include "src/gpu/graphite/BuiltInCodeSnippetID.h" | |
| #include "src/gpu/graphite/PaintParamsKey.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> | |
| #include <vector> | |
| class SkRuntimeEffect; | |
| namespace skgpu::graphite { | |
| class RenderStep; | |
| class RuntimeEffectDictionary; | |
| struct ResourceBindingRequirements; | |
| // 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, | |
| kLocalCoords = 0x1, | |
| kPriorStageOutput = 0x2, // AKA the "input" color, or the "source" color for a blender | |
| kDestColor = 0x4, | |
| }; | |
| SK_MAKE_BITMASK_OPS(SnippetRequirementFlags); | |
| struct ShaderSnippet { | |
| using GeneratePreambleForSnippetFn = void (*)(const ShaderInfo& shaderInfo, | |
| int* entryIndex, | |
| const PaintParamsKey::BlockReader&, | |
| std::string* preamble); | |
| struct Args { | |
| std::string_view fPriorStageOutput; | |
| std::string_view fDestColor; | |
| std::string_view fFragCoord; | |
| }; | |
| using GenerateExpressionForSnippetFn = std::string (*)(const ShaderInfo& shaderInfo, | |
| int entryIndex, | |
| const PaintParamsKey::BlockReader&, | |
| const Args& args); | |
| ShaderSnippet() = default; | |
| ShaderSnippet(const char* name, | |
| SkSpan<const Uniform> uniforms, | |
| SkEnumBitMask<SnippetRequirementFlags> snippetRequirementFlags, | |
| SkSpan<const TextureAndSampler> texturesAndSamplers, | |
| const char* functionName, | |
| GenerateExpressionForSnippetFn expressionGenerator, | |
| GeneratePreambleForSnippetFn preambleGenerator, | |
| int numChildren, | |
| SkSpan<const PaintParamsKey::DataPayloadField> dataPayloadExpectations) | |
| : fName(name) | |
| , fUniforms(uniforms) | |
| , fSnippetRequirementFlags(snippetRequirementFlags) | |
| , fTexturesAndSamplers(texturesAndSamplers) | |
| , fStaticFunctionName(functionName) | |
| , fExpressionGenerator(expressionGenerator) | |
| , fPreambleGenerator(preambleGenerator) | |
| , fNumChildren(numChildren) | |
| , fDataPayloadExpectations(dataPayloadExpectations) {} | |
| std::string getMangledUniformName(const ShaderInfo& shaderInfo, | |
| int uniformIdx, | |
| int mangleId) const; | |
| std::string getMangledSamplerName(int samplerIdx, int mangleId) const; | |
| bool needsLocalCoords() const { | |
| return fSnippetRequirementFlags & SnippetRequirementFlags::kLocalCoords; | |
| } | |
| bool needsPriorStageOutput() const { | |
| return fSnippetRequirementFlags & SnippetRequirementFlags::kPriorStageOutput; | |
| } | |
| bool needsDestColor() const { | |
| return fSnippetRequirementFlags & SnippetRequirementFlags::kDestColor; | |
| } | |
| const char* fName = nullptr; | |
| SkSpan<const Uniform> fUniforms; | |
| SkEnumBitMask<SnippetRequirementFlags> fSnippetRequirementFlags{SnippetRequirementFlags::kNone}; | |
| SkSpan<const TextureAndSampler> fTexturesAndSamplers; | |
| const char* fStaticFunctionName = nullptr; | |
| GenerateExpressionForSnippetFn fExpressionGenerator = nullptr; | |
| GeneratePreambleForSnippetFn fPreambleGenerator = nullptr; | |
| int fNumChildren = 0; | |
| SkSpan<const PaintParamsKey::DataPayloadField> fDataPayloadExpectations; | |
| }; | |
| // This is just a simple collection object that gathers together all the information needed | |
| // for program creation and its invocation. | |
| class ShaderInfo { | |
| public: | |
| ShaderInfo(const RuntimeEffectDictionary* rteDict = nullptr, | |
| const char* ssboIndex = nullptr) | |
| : fRuntimeEffectDictionary(rteDict) | |
| , fSsboIndex(ssboIndex) {} | |
| ~ShaderInfo() = default; | |
| ShaderInfo(ShaderInfo&&) = default; | |
| ShaderInfo& operator=(ShaderInfo&&) = default; | |
| ShaderInfo(const ShaderInfo&) = delete; | |
| ShaderInfo& operator=(const ShaderInfo&) = delete; | |
| void add(const PaintParamsKey::BlockReader& reader) { | |
| fBlockReaders.push_back(reader); | |
| } | |
| void addFlags(SkEnumBitMask<SnippetRequirementFlags> flags) { | |
| fSnippetRequirementFlags |= flags; | |
| } | |
| bool needsLocalCoords() const { | |
| return fSnippetRequirementFlags & SnippetRequirementFlags::kLocalCoords; | |
| } | |
| const PaintParamsKey::BlockReader& blockReader(int index) const { | |
| return fBlockReaders[index]; | |
| } | |
| const RuntimeEffectDictionary* runtimeEffectDictionary() const { | |
| return fRuntimeEffectDictionary; | |
| } | |
| const char* ssboIndex() const { return fSsboIndex; } | |
| void setBlendInfo(const skgpu::BlendInfo& blendInfo) { | |
| fBlendInfo = blendInfo; | |
| } | |
| const skgpu::BlendInfo& blendInfo() const { return fBlendInfo; } | |
| std::string toSkSL(const ResourceBindingRequirements& bindingReqs, | |
| const RenderStep* step, | |
| const bool useStorageBuffers, | |
| const bool defineLocalCoordsVarying, | |
| int* numTexturesAndSamplersUsed) const; | |
| private: | |
| std::vector<PaintParamsKey::BlockReader> fBlockReaders; | |
| SkEnumBitMask<SnippetRequirementFlags> fSnippetRequirementFlags{SnippetRequirementFlags::kNone}; | |
| const RuntimeEffectDictionary* fRuntimeEffectDictionary = nullptr; | |
| const char* fSsboIndex; | |
| // The blendInfo doesn't actually contribute to the program's creation but, it contains the | |
| // matching fixed-function settings that the program's caller needs to set up. | |
| skgpu::BlendInfo fBlendInfo; | |
| }; | |
| class ShaderCodeDictionary { | |
| public: | |
| ShaderCodeDictionary(); | |
| struct Entry { | |
| public: | |
| UniquePaintParamsID uniqueID() const { | |
| SkASSERT(fUniqueID.isValid()); | |
| return fUniqueID; | |
| } | |
| const PaintParamsKey& paintParamsKey() const { return fKey; } | |
| const skgpu::BlendInfo& blendInfo() const { return fBlendInfo; } | |
| private: | |
| friend class ShaderCodeDictionary; | |
| Entry(const PaintParamsKey& key, const skgpu::BlendInfo& blendInfo) | |
| : fKey(key.asSpan()) | |
| , fBlendInfo(blendInfo) { | |
| } | |
| void setUniqueID(uint32_t newID) { | |
| SkASSERT(!fUniqueID.isValid()); | |
| fUniqueID = UniquePaintParamsID(newID); | |
| } | |
| UniquePaintParamsID fUniqueID; // fixed-size (uint32_t) unique ID assigned to a key | |
| PaintParamsKey fKey; // variable-length paint key descriptor | |
| // The BlendInfo isn't used in the hash (that is the key's job) but it does directly vary | |
| // with the key. It could, theoretically, be recreated from the key but that would add | |
| // extra complexity. | |
| skgpu::BlendInfo fBlendInfo; | |
| }; | |
| const Entry* findOrCreate(PaintParamsKeyBuilder*) SK_EXCLUDES(fSpinLock); | |
| const Entry* lookup(UniquePaintParamsID) const SK_EXCLUDES(fSpinLock); | |
| SkSpan<const Uniform> getUniforms(BuiltInCodeSnippetID) const; | |
| SkEnumBitMask<SnippetRequirementFlags> getSnippetRequirementFlags( | |
| BuiltInCodeSnippetID id) const { | |
| return fBuiltInCodeSnippets[(int) id].fSnippetRequirementFlags; | |
| } | |
| SkSpan<const PaintParamsKey::DataPayloadField> dataPayloadExpectations(int snippetID) const; | |
| bool isValidID(int snippetID) const; | |
| // This method can return nullptr | |
| const ShaderSnippet* getEntry(int codeSnippetID) const; | |
| const ShaderSnippet* getEntry(BuiltInCodeSnippetID codeSnippetID) const { | |
| return this->getEntry(SkTo<int>(codeSnippetID)); | |
| } | |
| void getShaderInfo(UniquePaintParamsID, ShaderInfo*) const; | |
| int findOrCreateRuntimeEffectSnippet(const SkRuntimeEffect* effect); | |
| int addUserDefinedSnippet(const char* name, | |
| SkSpan<const PaintParamsKey::DataPayloadField> expectations); | |
| private: | |
| Entry* makeEntry(const PaintParamsKey&, const skgpu::BlendInfo&); | |
| // TODO: this is still experimental but, most likely, it will need to be made thread-safe | |
| // It returns the code snippet ID to use to identify the supplied user-defined code | |
| int addUserDefinedSnippet( | |
| const char* name, | |
| SkSpan<const Uniform> uniforms, | |
| SkEnumBitMask<SnippetRequirementFlags> snippetRequirementFlags, | |
| SkSpan<const TextureAndSampler> texturesAndSamplers, | |
| const char* functionName, | |
| ShaderSnippet::GenerateExpressionForSnippetFn expressionGenerator, | |
| ShaderSnippet::GeneratePreambleForSnippetFn preambleGenerator, | |
| int numChildren, | |
| SkSpan<const PaintParamsKey::DataPayloadField> dataPayloadExpectations); | |
| const char* addTextToArena(std::string_view text); | |
| SkSpan<const Uniform> convertUniforms(const SkRuntimeEffect* effect); | |
| std::array<ShaderSnippet, kBuiltInCodeSnippetIDCount> fBuiltInCodeSnippets; | |
| // The value returned from 'getEntry' must be stable so, hold the user-defined code snippet | |
| // entries as pointers. | |
| std::vector<std::unique_ptr<ShaderSnippet>> fUserDefinedCodeSnippets; | |
| // TODO: can we do something better given this should have write-seldom/read-often behavior? | |
| mutable SkSpinlock fSpinLock; | |
| struct PaintParamsKeyPtr { | |
| const PaintParamsKey* fKey; | |
| bool operator==(PaintParamsKeyPtr rhs) const { | |
| return *fKey == *rhs.fKey; | |
| } | |
| struct Hash { | |
| size_t operator()(PaintParamsKeyPtr) const; | |
| }; | |
| }; | |
| using PaintHashMap = SkTHashMap<PaintParamsKeyPtr, Entry*, PaintParamsKeyPtr::Hash>; | |
| PaintHashMap fHash SK_GUARDED_BY(fSpinLock); | |
| std::vector<Entry*> fEntryVector 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; | |
| } | |
| struct Hash { | |
| size_t operator()(RuntimeEffectKey) const; | |
| }; | |
| }; | |
| 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 = SkTHashMap<RuntimeEffectKey, int32_t>; | |
| RuntimeEffectMap fRuntimeEffectMap SK_GUARDED_BY(fSpinLock); | |
| // This arena holds: | |
| // - the Entries held in `fHash` and `fEntryVector` | |
| // - Uniform data created by `findOrCreateRuntimeEffectSnippet` | |
| // and in all cases is guarded by `fSpinLock` | |
| SkArenaAlloc fArena{256}; | |
| }; | |
| } // namespace skgpu::graphite | |
| #endif // skgpu_graphite_ShaderCodeDictionary_DEFINED |