• 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_PaintParamsKey_DEFINED
9 #define skgpu_graphite_PaintParamsKey_DEFINED
10 
11 #include "include/core/SkSpan.h"
12 #include "include/core/SkTypes.h"
13 #include "include/private/base/SkMacros.h"
14 #include "include/private/base/SkTArray.h"
15 #include "src/core/SkChecksum.h"
16 #include "src/gpu/graphite/BuiltInCodeSnippetID.h"
17 
18 #include <limits>
19 #include <cstring> // for memcmp
20 
21 class SkArenaAlloc;
22 struct SkSamplingOptions;
23 enum class SkTileMode;
24 
25 namespace skgpu::graphite {
26 
27 class Caps;
28 class ShaderCodeDictionary;
29 class ShaderNode;
30 class TextureProxy;
31 class UniquePaintParamsID;
32 
33 /**
34  * This class is a compact representation of the shader needed to implement a given
35  * PaintParams. Its structure is a series of nodes where each node consists of:
36  *   4 bytes: code-snippet ID
37  *   N child nodes, where N is the constant number of children defined by the ShaderCodeDictionary
38  *     for the node's snippet ID.
39  *
40  * Some snippet definitions support embedding data into the PaintParamsKey, used when something
41  * external to the generated SkSL needs produce unique pipelines (e.g. immutable samplers). For
42  * snippets that store data, the data is stored immediately after the ID as:
43  *   4 bytes: code-snippet ID
44  *   4 bytes: data length
45  *   0-M: variable length data
46  *   N child nodes
47  *
48  * All children of a child node are stored in the key before the next child is encoded in the key,
49  * e.g. iterating the data in a key is a depth-first traversal of the node tree.
50  *
51  * The PaintParamsKey stores multiple root nodes, with each root representing an effect tree that
52  * affects different parts of the shading pipeline. The key is can only hold 2 or 3 roots:
53  *  1. Color root node: produces the "src" color used in final blending with the "dst" color.
54  *  2. Final blend node: defines the blend function combining src and dst colors. If this is a
55  *     FixedBlend snippet the final pipeline may be able to lift it to HW blending.
56  *  3. Clipping: optional, produces analytic coverage from a clip shader or shape.
57  *
58  * Logically the root effects produce a src color and the src coverage (augmenting any other
59  * coverage coming from the RenderStep). A single src shading node could be used instead of the
60  * two for color and blending, but its structure would always be:
61  *
62  *    [ BlendCompose [ [ color-root-node ] surface-color [ final-blend ] ] ]
63  *
64  * where "surface-color" would be a special snippet that produces the current dst color value.
65  * To keep PaintParamsKeys memory cost lower, the BlendCompose and "surface-color" nodes are implied
66  * when generating the SkSL and pipeline.
67  */
68 class PaintParamsKey {
69 public:
70     // PaintParamsKey can only be created by using a PaintParamsKeyBuilder or by cloning the key
71     // data from a Builder-owned key, but they can be passed around by value after that.
72     constexpr PaintParamsKey(const PaintParamsKey&) = default;
73 
PaintParamsKey(SkSpan<const uint32_t> span)74     constexpr PaintParamsKey(SkSpan<const uint32_t> span) : fData(span) {}
75 
76     ~PaintParamsKey() = default;
77     PaintParamsKey& operator=(const PaintParamsKey&) = default;
78 
Invalid()79     static constexpr PaintParamsKey Invalid() { return PaintParamsKey(SkSpan<const uint32_t>()); }
isValid()80     bool isValid() const { return !fData.empty(); }
81 
82     // Return a PaintParamsKey whose data is owned by the provided arena and is not attached to
83     // a PaintParamsKeyBuilder. The caller must ensure that the SkArenaAlloc remains alive longer
84     // than the returned key.
85     PaintParamsKey clone(SkArenaAlloc*) const;
86 
87     // Converts the key into a forest of ShaderNode trees. If the key is valid this will return at
88     // least one root node. If the key contains unknown shader snippet IDs, returns an empty span.
89     // All shader nodes, and the returned span's backing data, are owned by the provided arena.
90     //
91     // A valid key will produce either 2 or 3 root nodes. The first root node represents how the
92     // source color is computed. The second node defines the final blender between the calculated
93     // source color and the current pixel's dst color. If provided, the third node calculates an
94     // additional analytic coverage value to combine with the geometry's coverage.
95     SkSpan<const ShaderNode*> getRootNodes(const ShaderCodeDictionary*, SkArenaAlloc*) const;
96 
97     // Converts the key to a structured list of snippet information for debugging or labeling
98     // purposes.
99     SkString toString(const ShaderCodeDictionary* dict, bool includeData) const;
100 
101 #ifdef SK_DEBUG
102     void dump(const ShaderCodeDictionary*, UniquePaintParamsID) const;
103 #endif
104 
105     bool operator==(const PaintParamsKey& that) const {
106         return fData.size() == that.fData.size() &&
107                !memcmp(fData.data(), that.fData.data(), fData.size());
108     }
109     bool operator!=(const PaintParamsKey& that) const { return !(*this == that); }
110 
111     struct Hash {
operatorHash112         uint32_t operator()(const PaintParamsKey& k) const {
113             return SkChecksum::Hash32(k.fData.data(), k.fData.size_bytes());
114         }
115     };
116 
data()117     SkSpan<const uint32_t> data() const { return fData; }
118 
119     // Checks that a given key is viable for serialization and, also, that a deserialized
120     // key is, at least, correctly formed. Other than that all the sizes make sense, this method
121     // also checks that only Skia-internal shader code snippets appear in the key.
122     [[nodiscard]] bool isSerializable(const ShaderCodeDictionary*) const;
123 
124 private:
125     friend class PaintParamsKeyBuilder;   // for the parented-data ctor
126 
127     // Returns null if the node or any of its children have an invalid snippet ID. Recursively
128     // creates a node and all of its children, incrementing 'currentIndex' by the total number of
129     // nodes created.
130     const ShaderNode* createNode(const ShaderCodeDictionary*,
131                                  int* currentIndex,
132                                  SkArenaAlloc* arena) const;
133 
134     // The memory referenced in 'fData' is always owned by someone else. It either shares the span
135     // from the Builder, or clone() puts the span in an arena.
136     SkSpan<const uint32_t> fData;
137 };
138 
139 // The PaintParamsKeyBuilder and the PaintParamsKeys snapped from it share the same
140 // underlying block of memory. When an PaintParamsKey is snapped from the builder it 'locks'
141 // the memory and 'unlocks' it in its destructor. Because of this relationship, the builder
142 // can only have one extant key and that key must be destroyed before the builder can be reused
143 // to create another one.
144 //
145 // This arrangement is intended to improve performance in the expected case, where a builder is
146 // being used in a tight loop to generate keys which can be recycled once they've been used to
147 // find the dictionary's matching uniqueID. We don't expect the cost of copying the key's memory
148 // into the dictionary to be prohibitive since that should be infrequent.
149 class PaintParamsKeyBuilder {
150 public:
PaintParamsKeyBuilder(const ShaderCodeDictionary * dict)151     PaintParamsKeyBuilder(const ShaderCodeDictionary* dict) {
152         SkDEBUGCODE(fDict = dict;)
153     }
154 
~PaintParamsKeyBuilder()155     ~PaintParamsKeyBuilder() { SkASSERT(!fLocked); }
156 
beginBlock(BuiltInCodeSnippetID id)157     void beginBlock(BuiltInCodeSnippetID id) { this->beginBlock(static_cast<int32_t>(id)); }
beginBlock(int32_t codeSnippetID)158     void beginBlock(int32_t codeSnippetID) {
159         SkASSERT(!fLocked);
160         SkDEBUGCODE(this->pushStack(codeSnippetID);)
161         fData.push_back(codeSnippetID);
162     }
163 
164     // TODO: Have endBlock() be handled automatically with RAII, in which case we could have it
165     // validate the snippet ID being popped off the stack frame.
endBlock()166     void endBlock() {
167         SkDEBUGCODE(this->popStack();)
168     }
169 
170 #ifdef SK_DEBUG
171     // Check that the builder has been reset to its initial state prior to creating a new key.
172     void checkReset();
173 #endif
174 
175     // Helper to add blocks that don't have children
addBlock(BuiltInCodeSnippetID id)176     void addBlock(BuiltInCodeSnippetID id) {
177         this->beginBlock(id);
178         this->endBlock();
179     }
180 
addData(SkSpan<const uint32_t> data)181     void addData(SkSpan<const uint32_t> data) {
182         // First push the data size followed by the actual data.
183         SkDEBUGCODE(this->validateData(data.size()));
184         fData.push_back(data.size());
185         fData.push_back_n(data.size(), data.begin());
186     }
187 
188 private:
189     friend class AutoLockBuilderAsKey; // for lockAsKey() and unlock()
190 
191     // Returns a view of this builder as a PaintParamsKey. The Builder cannot be used until the
192     // returned Key goes out of scope.
lockAsKey()193     PaintParamsKey lockAsKey() {
194         SkASSERT(!fLocked);       // lockAsKey() is not re-entrant
195         SkASSERT(fStack.empty()); // All beginBlocks() had a matching endBlock()
196 
197         SkDEBUGCODE(fLocked = true;)
198         return PaintParamsKey({fData.data(), fData.size()});
199     }
200 
201     // Invalidates any PaintParamsKey returned by lockAsKey() unless it has been cloned.
unlock()202     void unlock() {
203         SkASSERT(fLocked);
204         fData.clear();
205 
206         SkDEBUGCODE(fLocked = false;)
207         SkDEBUGCODE(fStack.clear();)
208         SkDEBUGCODE(this->checkReset();)
209     }
210 
211     // The data array uses clear() on unlock so that it's underlying storage and repeated use of the
212     // builder will hit a high-water mark and avoid lots of allocations when recording draws.
213     skia_private::TArray<uint32_t> fData;
214 
215 #ifdef SK_DEBUG
216     void pushStack(int32_t codeSnippetID);
217     void validateData(size_t dataSize);
218     void popStack();
219 
220     // Information about the current block being written
221     struct StackFrame {
222         int fCodeSnippetID;
223         int fNumExpectedChildren;
224         int fNumActualChildren = 0;
225         int fDataSize = -1;
226     };
227 
228     const ShaderCodeDictionary* fDict;
229     skia_private::TArray<StackFrame> fStack;
230     bool fLocked = false;
231 #endif
232 };
233 
234 class AutoLockBuilderAsKey {
235 public:
AutoLockBuilderAsKey(PaintParamsKeyBuilder * builder)236     AutoLockBuilderAsKey(PaintParamsKeyBuilder* builder)
237             : fBuilder(builder)
238             , fKey(builder->lockAsKey()) {}
239 
~AutoLockBuilderAsKey()240     ~AutoLockBuilderAsKey() {
241         fBuilder->unlock();
242     }
243 
244     // Use as a PaintParamsKey
245     const PaintParamsKey& operator*() const { return fKey; }
246     const PaintParamsKey* operator->() const { return &fKey; }
247 
248 private:
249     PaintParamsKeyBuilder* fBuilder;
250     PaintParamsKey fKey;
251 };
252 
253 }  // namespace skgpu::graphite
254 
255 #endif // skgpu_graphite_PaintParamsKey_DEFINED
256