1 /* 2 * Copyright 2022 Google LLC 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8 #ifndef skgpu_graphite_ShaderCodeDictionary_DEFINED 9 #define skgpu_graphite_ShaderCodeDictionary_DEFINED 10 11 #include "include/core/SkSpan.h" 12 #include "include/private/base/SkTo.h" 13 #include "src/base/SkArenaAlloc.h" 14 #include "src/base/SkEnumBitMask.h" 15 #include "src/base/SkSpinlock.h" 16 #include "src/core/SkKnownRuntimeEffects.h" 17 #include "src/core/SkTHash.h" 18 #include "src/gpu/graphite/BuiltInCodeSnippetID.h" 19 #include "src/gpu/graphite/PaintParamsKey.h" 20 #include "src/gpu/graphite/ResourceTypes.h" 21 #include "src/gpu/graphite/Uniform.h" 22 #include "src/gpu/graphite/UniquePaintParamsID.h" 23 24 #include <array> 25 #include <cstddef> 26 #include <cstdint> 27 #include <memory> 28 #include <string> 29 #include <string_view> 30 31 class SkRuntimeEffect; 32 33 namespace skgpu::graphite { 34 35 // TODO: How to represent the type (e.g., 2D) of texture being sampled? 36 class TextureAndSampler { 37 public: TextureAndSampler(const char * name)38 constexpr TextureAndSampler(const char* name) : fName(name) {} 39 name()40 const char* name() const { return fName; } 41 42 private: 43 const char* fName; 44 }; 45 46 enum class SnippetRequirementFlags : uint32_t { 47 kNone = 0x0, 48 // Signature of the ShaderNode 49 kLocalCoords = 0x1, 50 kPriorStageOutput = 0x2, // AKA the "input" color, or the "src" argument for a blender 51 kBlenderDstColor = 0x4, // The "dst" argument for a blender 52 // Special values and/or behaviors required for the snippet 53 kPrimitiveColor = 0x8, 54 kGradientBuffer = 0x10, 55 kStoresData = 0x20, // Indicates that the node stores numerical data 56 }; 57 SK_MAKE_BITMASK_OPS(SnippetRequirementFlags) 58 59 class ShaderInfo; 60 class ShaderNode; 61 62 // ShaderSnippets define the "ABI" of a SkSL module function and its required uniform data, as 63 // well as functions for generating the invoking SkSL. Snippets are composed into an effect tree 64 // using ShaderNodes. 65 struct ShaderSnippet { 66 using GeneratePreambleForSnippetFn = std::string (*)(const ShaderInfo& shaderInfo, 67 const ShaderNode*); 68 struct Args { 69 std::string fPriorStageOutput; 70 std::string fBlenderDstColor; 71 std::string fFragCoord; 72 }; 73 74 static const Args kDefaultArgs; 75 76 ShaderSnippet() = default; 77 78 ShaderSnippet(const char* name, 79 const char* staticFn, 80 SkEnumBitMask<SnippetRequirementFlags> snippetRequirementFlags, 81 SkSpan<const Uniform> uniforms, 82 SkSpan<const TextureAndSampler> texturesAndSamplers = {}, 83 GeneratePreambleForSnippetFn preambleGenerator = nullptr, 84 int numChildren = 0) fNameShaderSnippet85 : fName(name) 86 , fStaticFunctionName(staticFn) 87 , fSnippetRequirementFlags(snippetRequirementFlags) 88 , fUniforms(uniforms) 89 , fTexturesAndSamplers(texturesAndSamplers) 90 , fNumChildren(numChildren) 91 , fPreambleGenerator(preambleGenerator) { 92 // Must always provide a name; static function is not optional if using the default (null) 93 // generation logic. 94 SkASSERT(name); 95 SkASSERT(staticFn || preambleGenerator); 96 } 97 needsLocalCoordsShaderSnippet98 bool needsLocalCoords() const { 99 return SkToBool(fSnippetRequirementFlags & SnippetRequirementFlags::kLocalCoords); 100 } needsPriorStageOutputShaderSnippet101 bool needsPriorStageOutput() const { 102 return SkToBool(fSnippetRequirementFlags & SnippetRequirementFlags::kPriorStageOutput); 103 } needsBlenderDstColorShaderSnippet104 bool needsBlenderDstColor() const { 105 return SkToBool(fSnippetRequirementFlags & SnippetRequirementFlags::kBlenderDstColor); 106 } storesDataShaderSnippet107 bool storesData() const { 108 return SkToBool(fSnippetRequirementFlags & SnippetRequirementFlags::kStoresData); 109 } 110 111 const char* fName = nullptr; 112 const char* fStaticFunctionName = nullptr; 113 114 // The features and args that this shader snippet requires in order to be invoked 115 SkEnumBitMask<SnippetRequirementFlags> fSnippetRequirementFlags{SnippetRequirementFlags::kNone}; 116 117 // If not null, the list of uniforms in `fUniforms` describes an existing struct type declared 118 // in the Graphite modules with the given name. Instead of inlining the each uniform in the 119 // top-level interface block or aggregate struct, there will be a single member of this struct's 120 // type. 121 const char* fUniformStructName = nullptr; 122 // If the uniforms are being embedded as a sub-struct, this is the required starting alignment. 123 int fRequiredAlignment = -1; 124 125 skia_private::TArray<Uniform> fUniforms; 126 skia_private::TArray<TextureAndSampler> fTexturesAndSamplers; 127 128 int fNumChildren = 0; 129 GeneratePreambleForSnippetFn fPreambleGenerator = nullptr; 130 }; 131 132 // ShaderNodes organize snippets into an effect tree, and provide random access to the dynamically 133 // bound child snippets. Each node has a fixed number of children defined by its code ID 134 // (either a BuiltInCodeSnippetID or a runtime effect's assigned ID). All children are non-null. 135 // A ShaderNode tree represents a decompressed PaintParamsKey. 136 class ShaderNode { 137 public: 138 // ShaderNodes should be created in conjunction with an SkArenaAlloc that owns all nodes. ShaderNode(const ShaderSnippet * snippet,SkSpan<const ShaderNode * > children,int codeID,int keyIndex,SkSpan<const uint32_t> data)139 ShaderNode(const ShaderSnippet* snippet, 140 SkSpan<const ShaderNode*> children, 141 int codeID, 142 int keyIndex, 143 SkSpan<const uint32_t> data) 144 : fEntry(snippet) 145 , fChildren(children) 146 , fCodeID(codeID) 147 , fKeyIndex(keyIndex) 148 , fRequiredFlags(snippet->fSnippetRequirementFlags) 149 , fData(data) { 150 SkASSERT(children.size() == (size_t) fEntry->fNumChildren); 151 152 const bool isCompose = codeID == (int) BuiltInCodeSnippetID::kCompose || 153 codeID == (int) BuiltInCodeSnippetID::kBlendCompose; 154 for (const ShaderNode* child : children) { 155 // Runtime effects invoke children with explicit parameters so those requirements never 156 // need to propagate to the root. Similarly, compose only needs to propagate the 157 // variable parameters for the inner children. 158 SkEnumBitMask<SnippetRequirementFlags> mask = SnippetRequirementFlags::kNone; 159 if (codeID >= kBuiltInCodeSnippetIDCount || (isCompose && child == children.back())) { 160 // Only mask off the variable arguments; any special behaviors always propagate. 161 mask = SnippetRequirementFlags::kLocalCoords | 162 SnippetRequirementFlags::kPriorStageOutput | 163 SnippetRequirementFlags::kBlenderDstColor; 164 } 165 166 fRequiredFlags |= (child->requiredFlags() & ~mask); 167 } 168 // Data should only be provided if the snippet has the kStoresData flag. 169 SkASSERT(fData.empty() || snippet->storesData()); 170 } 171 172 std::string generateDefaultPreamble(const ShaderInfo& shaderInfo) const; 173 std::string invokeAndAssign(const ShaderInfo& shaderInfo, 174 const ShaderSnippet::Args& args, 175 std::string* funcBody) const; 176 codeSnippetId()177 int32_t codeSnippetId() const { return fCodeID; } keyIndex()178 int32_t keyIndex() const { return fKeyIndex; } entry()179 const ShaderSnippet* entry() const { return fEntry; } 180 requiredFlags()181 SkEnumBitMask<SnippetRequirementFlags> requiredFlags() const { return fRequiredFlags; } 182 numChildren()183 int numChildren() const { return fEntry->fNumChildren; } children()184 SkSpan<const ShaderNode*> children() const { return fChildren; } child(int childIndex)185 const ShaderNode* child(int childIndex) const { return fChildren[childIndex]; } 186 data()187 SkSpan<const uint32_t> data() const { return fData; } 188 189 private: 190 const ShaderSnippet* fEntry; // Owned by the ShaderCodeDictionary 191 SkSpan<const ShaderNode*> fChildren; // Owned by the ShaderInfo's arena 192 193 int32_t fCodeID; 194 int32_t fKeyIndex; // index back to PaintParamsKey, unique across nodes within a ShaderInfo 195 196 SkEnumBitMask<SnippetRequirementFlags> fRequiredFlags; 197 SkSpan<const uint32_t> fData; // Subspan of PaintParamsKey's fData; shares same owner 198 }; 199 200 // ShaderCodeDictionary is a thread-safe dictionary of ShaderSnippets to code IDs for use with 201 // creating PaintParamKeys, as well as assigning unique IDs to each encountered PaintParamKey. 202 // It defines ShaderSnippets for every BuiltInCodeSnippetID and maintains records for IDs per 203 // SkRuntimeEffect, including de-duplicating equivalent SkRuntimeEffect objects. 204 class ShaderCodeDictionary { 205 public: 206 ShaderCodeDictionary(Layout layout); 207 208 UniquePaintParamsID findOrCreate(PaintParamsKeyBuilder*) SK_EXCLUDES(fSpinLock); 209 210 PaintParamsKey lookup(UniquePaintParamsID) const SK_EXCLUDES(fSpinLock); 211 idToString(UniquePaintParamsID id)212 SkString idToString(UniquePaintParamsID id) const { 213 return this->lookup(id).toString(this, /*includeData=*/false); 214 } 215 216 #if defined(SK_DEBUG) 217 bool isValidID(int snippetID) const SK_EXCLUDES(fSpinLock); 218 219 void dump(UniquePaintParamsID) const; 220 #endif 221 222 // This method can return nullptr 223 const ShaderSnippet* getEntry(int codeSnippetID) const SK_EXCLUDES(fSpinLock); getEntry(BuiltInCodeSnippetID codeSnippetID)224 const ShaderSnippet* getEntry(BuiltInCodeSnippetID codeSnippetID) const { 225 // Built-in code snippets are initialized once so there is no need to take a lock 226 return &fBuiltInCodeSnippets[SkTo<int>(codeSnippetID)]; 227 } 228 229 int findOrCreateRuntimeEffectSnippet(const SkRuntimeEffect* effect) SK_EXCLUDES(fSpinLock); 230 231 private: 232 const char* addTextToArena(std::string_view text); 233 234 SkSpan<const Uniform> convertUniforms(const SkRuntimeEffect* effect); 235 ShaderSnippet convertRuntimeEffect(const SkRuntimeEffect* effect, const char* name); 236 237 const Layout fLayout; 238 239 std::array<ShaderSnippet, kBuiltInCodeSnippetIDCount> fBuiltInCodeSnippets; 240 241 using KnownRuntimeEffectArray = std::array<ShaderSnippet, SkKnownRuntimeEffects::kStableKeyCnt>; 242 KnownRuntimeEffectArray fKnownRuntimeEffectCodeSnippets SK_GUARDED_BY(fSpinLock); 243 244 // The value returned from 'getEntry' must be stable so, hold the user-defined code snippet 245 // entries as pointers. 246 using RuntimeEffectArray = skia_private::TArray<ShaderSnippet>; 247 RuntimeEffectArray fUserDefinedCodeSnippets SK_GUARDED_BY(fSpinLock); 248 249 // TODO: can we do something better given this should have write-seldom/read-often behavior? 250 mutable SkSpinlock fSpinLock; 251 252 using PaintIDMap = skia_private::THashMap<PaintParamsKey, 253 UniquePaintParamsID, 254 PaintParamsKey::Hash>; 255 256 PaintIDMap fPaintKeyToID SK_GUARDED_BY(fSpinLock); 257 skia_private::TArray<PaintParamsKey> fIDToPaintKey SK_GUARDED_BY(fSpinLock); 258 259 SK_BEGIN_REQUIRE_DENSE 260 struct RuntimeEffectKey { 261 uint32_t fHash; 262 uint32_t fUniformSize; 263 264 bool operator==(RuntimeEffectKey rhs) const { 265 return fHash == rhs.fHash && fUniformSize == rhs.fUniformSize; 266 } 267 }; 268 SK_END_REQUIRE_DENSE 269 270 // A map from RuntimeEffectKeys (hash plus uniforms) to code-snippet IDs. RuntimeEffectKeys 271 // don't track the lifetime of a runtime effect at all; they live forever, and a newly- 272 // instantiated runtime effect with the same program as a previously-discarded effect will reuse 273 // an existing ID. Entries in the runtime-effect map are never removed; they only disappear when 274 // the context is discarded, which takes the ShaderCodeDictionary along with it. However, they 275 // are extremely small (< 20 bytes) so the memory footprint should be unnoticeable. 276 using RuntimeEffectMap = skia_private::THashMap<RuntimeEffectKey, int32_t>; 277 RuntimeEffectMap fRuntimeEffectMap SK_GUARDED_BY(fSpinLock); 278 279 // This arena holds: 280 // - the backing data for PaintParamsKeys in `fPaintKeyToID` and `fIDToPaintKey` 281 // - Uniform data created by `findOrCreateRuntimeEffectSnippet` 282 // and in all cases is guarded by `fSpinLock` 283 SkArenaAlloc fArena{256}; 284 }; 285 286 } // namespace skgpu::graphite 287 288 #endif // skgpu_graphite_ShaderCodeDictionary_DEFINED 289