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 #include "src/gpu/graphite/PaintParamsKey.h"
9
10 #include "src/base/SkArenaAlloc.h"
11 #include "src/base/SkAutoMalloc.h"
12 #include "src/base/SkBase64.h"
13 #include "src/base/SkStringView.h"
14 #include "src/gpu/graphite/Caps.h"
15 #include "src/gpu/graphite/KeyHelpers.h"
16 #include "src/gpu/graphite/Log.h"
17 #include "src/gpu/graphite/ShaderCodeDictionary.h"
18
19 using namespace skia_private;
20
21 namespace skgpu::graphite {
22
23 //--------------------------------------------------------------------------------------------------
24 // PaintParamsKeyBuilder
25
26 #ifdef SK_DEBUG
27
checkReset()28 void PaintParamsKeyBuilder::checkReset() {
29 SkASSERT(!fLocked);
30 SkASSERT(fData.empty());
31 SkASSERT(fStack.empty());
32 }
33
pushStack(int32_t codeSnippetID)34 void PaintParamsKeyBuilder::pushStack(int32_t codeSnippetID) {
35 SkASSERT(fDict->isValidID(codeSnippetID));
36
37 if (!fStack.empty()) {
38 fStack.back().fNumActualChildren++;
39 SkASSERT(fStack.back().fNumActualChildren <= fStack.back().fNumExpectedChildren);
40 }
41
42 const ShaderSnippet* snippet = fDict->getEntry(codeSnippetID);
43 fStack.push_back({codeSnippetID, snippet->fNumChildren});
44 }
45
validateData(size_t dataSize)46 void PaintParamsKeyBuilder::validateData(size_t dataSize) {
47 SkASSERT(!fStack.empty()); // addData() called within code snippet block
48 // Check that addData() is only called for snippets that support it and is only called once
49 const ShaderSnippet* snippet = fDict->getEntry(fStack.back().fCodeSnippetID);
50 SkASSERT(snippet->storesSamplerDescData());
51 SkASSERT(fStack.back().fDataSize < 0);
52
53 fStack.back().fDataSize = SkTo<int>(dataSize);
54 }
55
popStack()56 void PaintParamsKeyBuilder::popStack() {
57 SkASSERT(!fStack.empty());
58 SkASSERT(fStack.back().fNumActualChildren == fStack.back().fNumExpectedChildren);
59 const bool expectsData = fDict->getEntry(fStack.back().fCodeSnippetID)->storesSamplerDescData();
60 const bool hasData = fStack.back().fDataSize >= 0;
61 SkASSERT(expectsData == hasData);
62 fStack.pop_back();
63 }
64
65 #endif // SK_DEBUG
66
67 //--------------------------------------------------------------------------------------------------
68 // PaintParamsKey
69
clone(SkArenaAlloc * arena) const70 PaintParamsKey PaintParamsKey::clone(SkArenaAlloc* arena) const {
71 uint32_t* newData = arena->makeArrayDefault<uint32_t>(fData.size());
72 memcpy(newData, fData.data(), fData.size_bytes());
73 return PaintParamsKey({newData, fData.size()});
74 }
75
76
createNode(const ShaderCodeDictionary * dict,int * currentIndex,SkArenaAlloc * arena) const77 const ShaderNode* PaintParamsKey::createNode(const ShaderCodeDictionary* dict,
78 int* currentIndex,
79 SkArenaAlloc* arena) const {
80 SkASSERT(*currentIndex < SkTo<int>(fData.size()));
81 const int32_t index = (*currentIndex)++;
82 const int32_t id = fData[index];
83
84 const ShaderSnippet* entry = dict->getEntry(id);
85 if (!entry) {
86 SKGPU_LOG_E("Unknown snippet ID in key: %d", id);
87 return nullptr;
88 }
89
90 SkSpan<const uint32_t> dataSpan = {};
91 if (entry->storesSamplerDescData()) {
92 // If a snippet stores data, then the subsequent paint key index signifies the length of
93 // its data. Determine this data length and iterate currentIndex past it.
94 const int storedDataLengthIdx = (*currentIndex)++;
95 SkASSERT(storedDataLengthIdx < SkTo<int>(fData.size()));
96 const int dataLength = fData[storedDataLengthIdx];
97 SkASSERT(storedDataLengthIdx + dataLength < SkTo<int>(fData.size()));
98
99 // Gather the data contents (length can now be inferred by the consumers of the data) to
100 // pass into ShaderNode creation. Iterate the paint key index past the data indices.
101 dataSpan = fData.subspan(storedDataLengthIdx + 1, dataLength);
102 *currentIndex += dataLength;
103 }
104
105 const ShaderNode** childArray = arena->makeArray<const ShaderNode*>(entry->fNumChildren);
106 for (int i = 0; i < entry->fNumChildren; ++i) {
107 const ShaderNode* child = this->createNode(dict, currentIndex, arena);
108 if (!child) {
109 return nullptr;
110 }
111 childArray[i] = child;
112 }
113
114 return arena->make<ShaderNode>(entry,
115 SkSpan(childArray, entry->fNumChildren),
116 id,
117 index,
118 dataSpan);
119 }
120
getRootNodes(const ShaderCodeDictionary * dict,SkArenaAlloc * arena) const121 SkSpan<const ShaderNode*> PaintParamsKey::getRootNodes(const ShaderCodeDictionary* dict,
122 SkArenaAlloc* arena) const {
123 // TODO: Once the PaintParamsKey creation is organized to represent a single tree starting at
124 // the final blend, there will only be a single root node and this can be simplified.
125 // For now, we don't know how many roots there are, so collect them into a local array before
126 // copying into the arena.
127 const int keySize = SkTo<int>(fData.size());
128
129 // Normal PaintParams creation will have up to 7 roots for the different stages.
130 STArray<7, const ShaderNode*> roots;
131 int currentIndex = 0;
132 while (currentIndex < keySize) {
133 const ShaderNode* root = this->createNode(dict, ¤tIndex, arena);
134 if (!root) {
135 return {}; // a bad key
136 }
137 roots.push_back(root);
138 }
139
140 // Copy the accumulated roots into a span stored in the arena
141 const ShaderNode** rootSpan = arena->makeArray<const ShaderNode*>(roots.size());
142 memcpy(rootSpan, roots.data(), roots.size_bytes());
143 return SkSpan(rootSpan, roots.size());
144 }
145
key_to_string(SkString * str,const ShaderCodeDictionary * dict,SkSpan<const uint32_t> keyData,int currentIndex,bool includeData,int indent)146 static int key_to_string(SkString* str,
147 const ShaderCodeDictionary* dict,
148 SkSpan<const uint32_t> keyData,
149 int currentIndex,
150 bool includeData,
151 int indent) {
152 SkASSERT(currentIndex < SkTo<int>(keyData.size()));
153
154 const bool multiline = indent >= 0;
155 if (multiline) {
156 // Format for multi-line printing
157 str->appendf("%*c", 2 * indent, ' ');
158 }
159
160 uint32_t id = keyData[currentIndex++];
161 auto entry = dict->getEntry(id);
162 if (!entry) {
163 str->append("UnknownCodeSnippetID:");
164 str->appendS32(id);
165 str->append(" ");
166 return currentIndex;
167 }
168
169 std::string_view name = entry->fName;
170 if (skstd::ends_with(name, "Shader")) {
171 name.remove_suffix(6);
172 }
173 str->append(name);
174
175 if (entry->storesSamplerDescData()) {
176 SkASSERT(currentIndex + 1 < SkTo<int>(keyData.size()));
177 const int dataLength = keyData[currentIndex++];
178 SkASSERT(currentIndex + dataLength < SkTo<int>(keyData.size()));
179
180 // Define a compact representation for the common case of shader snippets using just one
181 // dynamic sampler. Immutable samplers require a data length > 1 to be represented while a
182 // dynamic sampler is represented with just one, so we can simply consult the data length.
183 if (dataLength == 1) {
184 str->append("(0)");
185 } else {
186 str->append("(");
187 str->appendU32(dataLength);
188 if (includeData) {
189 // Encode data in base64 to shorten it
190 str->append(": ");
191 SkAutoMalloc encodedData{SkBase64::EncodedSize(dataLength)};
192 char* dst = static_cast<char*>(encodedData.get());
193 size_t encodedLen = SkBase64::Encode(&keyData[currentIndex], dataLength, dst);
194 str->append(dst, encodedLen);
195 }
196 str->append(")");
197 }
198
199 currentIndex += dataLength;
200 }
201
202 if (entry->fNumChildren > 0) {
203 if (multiline) {
204 str->append(":\n");
205 indent++;
206 } else {
207 str->append(" [ ");
208 }
209
210 for (int i = 0; i < entry->fNumChildren; ++i) {
211 currentIndex = key_to_string(str, dict, keyData, currentIndex, includeData, indent);
212 }
213
214 if (!multiline) {
215 str->append("]");
216 }
217 }
218
219 if (!multiline) {
220 str->append(" ");
221 } else if (entry->fNumChildren == 0) {
222 str->append("\n");
223 }
224 return currentIndex;
225 }
226
toString(const ShaderCodeDictionary * dict,bool includeData) const227 SkString PaintParamsKey::toString(const ShaderCodeDictionary* dict, bool includeData) const {
228 SkString str;
229 const int keySize = SkTo<int>(fData.size());
230 for (int currentIndex = 0; currentIndex < keySize;) {
231 currentIndex = key_to_string(&str, dict, fData, currentIndex, includeData, /*indent=*/-1);
232 }
233 return str.isEmpty() ? SkString("(empty)") : str;
234 }
235
236 #ifdef SK_DEBUG
237
dump(const ShaderCodeDictionary * dict,UniquePaintParamsID id) const238 void PaintParamsKey::dump(const ShaderCodeDictionary* dict, UniquePaintParamsID id) const {
239 const int keySize = SkTo<int>(fData.size());
240
241 SkDebugf("--------------------------------------\n");
242 SkDebugf("PaintParamsKey %u (keySize: %d):\n", id.asUInt(), keySize);
243
244 int currentIndex = 0;
245 while (currentIndex < keySize) {
246 SkString nodeStr;
247 currentIndex = key_to_string(&nodeStr, dict, fData, currentIndex,
248 /*includeData=*/true, /*indent=*/1);
249 SkDebugf("%s", nodeStr.c_str());
250 }
251 }
252
253 #endif // SK_DEBUG
254
255 } // namespace skgpu::graphite
256