xref: /aosp_15_r20/external/skia/src/gpu/graphite/ShaderCodeDictionary.h (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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