/* * 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 #include #include #include #include #include #include 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 uniforms, SkEnumBitMask snippetRequirementFlags, SkSpan texturesAndSamplers, const char* functionName, GenerateExpressionForSnippetFn expressionGenerator, GeneratePreambleForSnippetFn preambleGenerator, int numChildren, SkSpan 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 fUniforms; SkEnumBitMask fSnippetRequirementFlags{SnippetRequirementFlags::kNone}; SkSpan fTexturesAndSamplers; const char* fStaticFunctionName = nullptr; GenerateExpressionForSnippetFn fExpressionGenerator = nullptr; GeneratePreambleForSnippetFn fPreambleGenerator = nullptr; int fNumChildren = 0; SkSpan 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 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 fBlockReaders; SkEnumBitMask 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 getUniforms(BuiltInCodeSnippetID) const; SkEnumBitMask getSnippetRequirementFlags( BuiltInCodeSnippetID id) const { return fBuiltInCodeSnippets[(int) id].fSnippetRequirementFlags; } SkSpan 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(codeSnippetID)); } void getShaderInfo(UniquePaintParamsID, ShaderInfo*) const; int findOrCreateRuntimeEffectSnippet(const SkRuntimeEffect* effect); int addUserDefinedSnippet(const char* name, SkSpan 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 uniforms, SkEnumBitMask snippetRequirementFlags, SkSpan texturesAndSamplers, const char* functionName, ShaderSnippet::GenerateExpressionForSnippetFn expressionGenerator, ShaderSnippet::GeneratePreambleForSnippetFn preambleGenerator, int numChildren, SkSpan dataPayloadExpectations); const char* addTextToArena(std::string_view text); SkSpan convertUniforms(const SkRuntimeEffect* effect); std::array fBuiltInCodeSnippets; // The value returned from 'getEntry' must be stable so, hold the user-defined code snippet // entries as pointers. std::vector> 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; PaintHashMap fHash SK_GUARDED_BY(fSpinLock); std::vector 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; 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