• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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