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