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 <cstring>
11 #include "src/gpu/graphite/KeyHelpers.h"
12 #include "src/gpu/graphite/ShaderCodeDictionary.h"
13
14 namespace skgpu::graphite {
15
16 using DataPayloadType = PaintParamsKey::DataPayloadType;
17 using DataPayloadField = PaintParamsKey::DataPayloadField;
18
19 //--------------------------------------------------------------------------------------------------
read_header(SkSpan<const uint8_t> parentSpan,int headerOffset)20 static PaintParamsKey::Header read_header(SkSpan<const uint8_t> parentSpan, int headerOffset) {
21 SkASSERT(headerOffset + sizeof(PaintParamsKey::Header) <= parentSpan.size());
22
23 PaintParamsKey::Header header;
24 memcpy(&header, &parentSpan[headerOffset], sizeof(PaintParamsKey::Header));
25 SkASSERT(header.blockSize >= sizeof(PaintParamsKey::Header));
26 SkASSERT(headerOffset + header.blockSize <= static_cast<int>(parentSpan.size()));
27
28 return header;
29 }
30
31 //--------------------------------------------------------------------------------------------------
PaintParamsKeyBuilder(const ShaderCodeDictionary * dict)32 PaintParamsKeyBuilder::PaintParamsKeyBuilder(const ShaderCodeDictionary* dict)
33 : fDict(dict) {}
34
35 #ifdef SK_DEBUG
checkReset()36 void PaintParamsKeyBuilder::checkReset() {
37 SkASSERT(!this->isLocked());
38 SkASSERT(this->sizeInBytes() == 0);
39 SkASSERT(fIsValid);
40 SkASSERT(fStack.empty());
41 SkASSERT(fBlendInfo == skgpu::BlendInfo());
42 }
43 #endif
44
45 // Block headers have the following structure:
46 // 4 bytes: code-snippet ID
47 // 1 byte: size of the block, in bytes (header, plus all data payload bytes)
48 // This call stores the header's offset in the key on the stack to be used in 'endBlock'
beginBlock(int32_t codeSnippetID)49 void PaintParamsKeyBuilder::beginBlock(int32_t codeSnippetID) {
50 if (!this->isValid()) {
51 return;
52 }
53
54 if (!fDict->isValidID(codeSnippetID)) {
55 // SKGPU_LOG_W("Unknown code snippet ID.");
56 this->makeInvalid();
57 return;
58 }
59
60 #ifdef SK_DEBUG
61 if (!fStack.empty()) {
62 // The children of a block should appear before any of the parent's data
63 SkASSERT(fStack.back().fCurDataPayloadEntry == 0);
64 fStack.back().fNumActualChildren++;
65 }
66
67 static constexpr DataPayloadField kHeader[2] = {
68 {"snippetID", DataPayloadType::kInt, 1},
69 {"blockSize", DataPayloadType::kByte, 1},
70 };
71
72 static const SkSpan<const DataPayloadField> kHeaderExpectations(kHeader);
73 #endif
74
75 SkASSERT(!this->isLocked());
76
77 fStack.push_back({ codeSnippetID, this->sizeInBytes(),
78 SkDEBUGCODE(kHeaderExpectations, 0) });
79
80 this->addInt(codeSnippetID);
81 this->addByte(0); // this will be filled in when endBlock is called
82
83 #ifdef SK_DEBUG
84 const ShaderSnippet* snippet = fDict->getEntry(codeSnippetID);
85
86 fStack.back().fDataPayloadExpectations = snippet->fDataPayloadExpectations;
87 fStack.back().fCurDataPayloadEntry = 0;
88 fStack.back().fNumExpectedChildren = snippet->fNumChildren;
89 fStack.back().fNumActualChildren = 0;
90 #endif
91 }
92
93 // Update the size byte of a block header
endBlock()94 void PaintParamsKeyBuilder::endBlock() {
95 if (!this->isValid()) {
96 return;
97 }
98
99 if (fStack.empty()) {
100 // SKGPU_LOG_W("Mismatched beginBlock/endBlocks.");
101 this->makeInvalid();
102 return;
103 }
104
105 // All the expected fields should be filled in at this point
106 SkASSERT(fStack.back().fCurDataPayloadEntry ==
107 SkTo<int>(fStack.back().fDataPayloadExpectations.size()));
108 SkASSERT(fStack.back().fNumActualChildren == fStack.back().fNumExpectedChildren);
109 SkASSERT(!this->isLocked());
110
111 int headerOffset = fStack.back().fHeaderOffset;
112
113 #ifdef SK_DEBUG
114 // We don't use `read_header` here because the header block size isn't valid yet.
115 PaintParamsKey::Header header;
116 memcpy(&header, &fData[headerOffset], sizeof(PaintParamsKey::Header));
117 SkASSERT(header.codeSnippetID == fStack.back().fCodeSnippetID);
118 SkASSERT(header.blockSize == 0);
119 #endif
120
121 int blockSize = this->sizeInBytes() - headerOffset;
122 if (blockSize > PaintParamsKey::kMaxBlockSize) {
123 // SKGPU_LOG_W("Key's data payload is too large.");
124 this->makeInvalid();
125 return;
126 }
127
128 fData[headerOffset + PaintParamsKey::kBlockSizeOffsetInBytes] = blockSize;
129
130 fStack.pop_back();
131
132 #ifdef SK_DEBUG
133 if (!fStack.empty()) {
134 // The children of a block should appear before any of the parent's data
135 SkASSERT(fStack.back().fCurDataPayloadEntry == 0);
136 }
137 #endif
138 }
139
140 #ifdef SK_DEBUG
checkExpectations(DataPayloadType actualType,uint32_t actualCount)141 void PaintParamsKeyBuilder::checkExpectations(DataPayloadType actualType, uint32_t actualCount) {
142 StackFrame& frame = fStack.back();
143 const auto& expectations = frame.fDataPayloadExpectations;
144
145 // TODO: right now we reject writing 'n' bytes one at a time. We could allow it by tracking
146 // the number of bytes written in the stack frame.
147 SkASSERT(expectations[frame.fCurDataPayloadEntry].fType == actualType);
148 SkASSERT(expectations[frame.fCurDataPayloadEntry].fCount == actualCount);
149
150 frame.fCurDataPayloadEntry++;
151 }
152 #endif // SK_DEBUG
153
field_size(DataPayloadType type)154 static int field_size(DataPayloadType type) {
155 switch (type) {
156 case DataPayloadType::kByte: return 1;
157 case DataPayloadType::kInt: return 4;
158 case DataPayloadType::kFloat4: return 16;
159 }
160 SkUNREACHABLE;
161 }
162
addToKey(uint32_t count,const void * data,DataPayloadType payloadType)163 void PaintParamsKeyBuilder::addToKey(uint32_t count,
164 const void* data,
165 DataPayloadType payloadType) {
166 if (!this->isValid()) {
167 return;
168 }
169
170 if (fStack.empty()) {
171 // SKGPU_LOG_W("Missing call to 'beginBlock'.");
172 this->makeInvalid();
173 return;
174 }
175
176 SkDEBUGCODE(this->checkExpectations(payloadType, count);)
177 SkASSERT(!this->isLocked());
178
179 fData.append(field_size(payloadType) * count, reinterpret_cast<const uint8_t*>(data));
180 }
181
addBytes(uint32_t numBytes,const uint8_t * data)182 void PaintParamsKeyBuilder::addBytes(uint32_t numBytes, const uint8_t* data) {
183 this->addToKey(numBytes, data, DataPayloadType::kByte);
184 }
185
addInts(uint32_t numInts,const int32_t * data)186 void PaintParamsKeyBuilder::addInts(uint32_t numInts, const int32_t* data) {
187 this->addToKey(numInts, data, DataPayloadType::kInt);
188 }
189
add(int numColors,const SkColor4f * colors)190 void PaintParamsKeyBuilder::add(int numColors, const SkColor4f* colors) {
191 this->addToKey(numColors, colors, DataPayloadType::kFloat4);
192 }
193
lockAsKey()194 PaintParamsKey PaintParamsKeyBuilder::lockAsKey() {
195 if (!fStack.empty()) {
196 // SKGPU_LOG_W("Mismatched beginBlock/endBlocks.");
197 this->makeInvalid(); // fall through
198 }
199
200 SkASSERT(!this->isLocked());
201
202 // Partially reset for reuse. Note that the key resulting from this call will be holding a lock
203 // on this builder and must be deleted before this builder is fully reset.
204 fIsValid = true;
205 fStack.clear();
206
207 return PaintParamsKey(SkSpan(fData.begin(), fData.size()), this);
208 }
209
makeInvalid()210 void PaintParamsKeyBuilder::makeInvalid() {
211 SkASSERT(fIsValid);
212 SkASSERT(!this->isLocked());
213
214 fStack.clear();
215 fData.clear();
216 this->beginBlock(BuiltInCodeSnippetID::kError);
217 this->endBlock();
218
219 SkASSERT(fIsValid);
220 fIsValid = false;
221 }
222
223 //--------------------------------------------------------------------------------------------------
PaintParamsKey(SkSpan<const uint8_t> span,PaintParamsKeyBuilder * originatingBuilder)224 PaintParamsKey::PaintParamsKey(SkSpan<const uint8_t> span,
225 PaintParamsKeyBuilder* originatingBuilder)
226 : fData(span)
227 , fOriginatingBuilder(originatingBuilder) {
228 fOriginatingBuilder->lock();
229 }
230
PaintParamsKey(SkSpan<const uint8_t> rawData)231 PaintParamsKey::PaintParamsKey(SkSpan<const uint8_t> rawData)
232 : fData(rawData)
233 , fOriginatingBuilder(nullptr) {
234 }
235
~PaintParamsKey()236 PaintParamsKey::~PaintParamsKey() {
237 if (fOriginatingBuilder) {
238 fOriginatingBuilder->unlock();
239 }
240 }
241
operator ==(const PaintParamsKey & that) const242 bool PaintParamsKey::operator==(const PaintParamsKey& that) const {
243 return fData.size() == that.fData.size() &&
244 !memcmp(fData.data(), that.fData.data(), fData.size());
245 }
246
reader(const ShaderCodeDictionary * dict,int headerOffset) const247 PaintParamsKey::BlockReader PaintParamsKey::reader(const ShaderCodeDictionary* dict,
248 int headerOffset) const {
249 return BlockReader(dict, fData, headerOffset);
250 }
251
252 #ifdef SK_DEBUG
253
254 // This just iterates over the top-level blocks calling block-specific dump methods.
dump(const ShaderCodeDictionary * dict) const255 void PaintParamsKey::dump(const ShaderCodeDictionary* dict) const {
256 SkDebugf("--------------------------------------\n");
257 SkDebugf("PaintParamsKey (%dB):\n", this->sizeInBytes());
258
259 int curHeaderOffset = 0;
260 while (curHeaderOffset < this->sizeInBytes()) {
261 BlockReader reader = this->reader(dict, curHeaderOffset);
262 reader.dump(dict, /*indent=*/0);
263 curHeaderOffset += reader.blockSize();
264 }
265 }
266 #endif // SK_DEBUG
267
AddBlockToShaderInfo(const ShaderCodeDictionary * dict,const PaintParamsKey::BlockReader & reader,ShaderInfo * result)268 void PaintParamsKey::AddBlockToShaderInfo(const ShaderCodeDictionary* dict,
269 const PaintParamsKey::BlockReader& reader,
270 ShaderInfo* result) {
271
272 result->add(reader);
273 result->addFlags(dict->getEntry(reader.codeSnippetId())->fSnippetRequirementFlags);
274
275 // The child blocks appear right after the parent block's header in the key and go
276 // right after the parent's SnippetEntry in the shader info
277 for (int i = 0; i < reader.numChildren(); ++i) {
278 BlockReader childReader = reader.child(dict, i);
279
280 AddBlockToShaderInfo(dict, childReader, result);
281 }
282 }
283
toShaderInfo(const ShaderCodeDictionary * dict,ShaderInfo * result) const284 void PaintParamsKey::toShaderInfo(const ShaderCodeDictionary* dict,
285 ShaderInfo* result) const {
286
287 int curHeaderOffset = 0;
288 while (curHeaderOffset < this->sizeInBytes()) {
289 PaintParamsKey::BlockReader reader = this->reader(dict, curHeaderOffset);
290 AddBlockToShaderInfo(dict, reader, result);
291 curHeaderOffset += reader.blockSize();
292 }
293 }
294
295 #if GRAPHITE_TEST_UTILS
isErrorKey() const296 bool PaintParamsKey::isErrorKey() const {
297 if (this->sizeInBytes() != sizeof(Header)) {
298 return false;
299 }
300 Header header = read_header(this->asSpan(), /*headerOffset=*/0);
301 return header.codeSnippetID == (int32_t) BuiltInCodeSnippetID::kError &&
302 header.blockSize == sizeof(Header);
303 }
304 #endif
305
306 ////////////////////////////////////////////////////////////////////////////////////////////////////
307
BlockReader(const ShaderCodeDictionary * dict,SkSpan<const uint8_t> parentSpan,int offsetInParent)308 PaintParamsKey::BlockReader::BlockReader(const ShaderCodeDictionary* dict,
309 SkSpan<const uint8_t> parentSpan,
310 int offsetInParent) {
311 Header header = read_header(parentSpan, offsetInParent);
312
313 fBlock = parentSpan.subspan(offsetInParent, header.blockSize);
314 fEntry = dict->getEntry(header.codeSnippetID);
315 SkASSERT(fEntry);
316 }
317
numChildren() const318 int PaintParamsKey::BlockReader::numChildren() const { return fEntry->fNumChildren; }
319
child(const ShaderCodeDictionary * dict,int childIndex) const320 PaintParamsKey::BlockReader PaintParamsKey::BlockReader::child(
321 const ShaderCodeDictionary* dict,
322 int childIndex) const {
323 SkASSERT(childIndex < fEntry->fNumChildren);
324
325 int childOffset = sizeof(Header);
326 for (int i = 0; i < childIndex; ++i) {
327 Header header = read_header(fBlock, childOffset);
328 childOffset += header.blockSize;
329 }
330
331 return BlockReader(dict, fBlock, childOffset);
332 }
333
codeSnippetId() const334 int32_t PaintParamsKey::BlockReader::codeSnippetId() const {
335 Header header = read_header(fBlock, 0);
336 return header.codeSnippetID;
337 }
338
dataPayload() const339 SkSpan<const uint8_t> PaintParamsKey::BlockReader::dataPayload() const {
340 int payloadOffset = sizeof(Header);
341 for (int i = 0; i < fEntry->fNumChildren; ++i) {
342 Header header = read_header(fBlock, payloadOffset);
343 payloadOffset += header.blockSize;
344 }
345
346 int payloadSize = this->blockSize() - payloadOffset;
347 return fBlock.subspan(payloadOffset, payloadSize);
348 }
349
field_offset(SkSpan<const DataPayloadField> fields,int fieldIndex)350 static int field_offset(SkSpan<const DataPayloadField> fields, int fieldIndex) {
351 int byteOffset = 0;
352 for (int i = 0; i < fieldIndex; ++i) {
353 byteOffset += field_size(fields[i].fType) * fields[i].fCount;
354 }
355 return byteOffset;
356 }
357
358 template <typename T>
payload_subspan_for_field(SkSpan<const uint8_t> dataPayload,SkSpan<const DataPayloadField> fields,int fieldIndex)359 static SkSpan<const T> payload_subspan_for_field(SkSpan<const uint8_t> dataPayload,
360 SkSpan<const DataPayloadField> fields,
361 int fieldIndex) {
362 int offset = field_offset(fields, fieldIndex);
363 return {reinterpret_cast<const T*>(&dataPayload[offset]), fields[fieldIndex].fCount};
364 }
365
bytes(int fieldIndex) const366 SkSpan<const uint8_t> PaintParamsKey::BlockReader::bytes(int fieldIndex) const {
367 SkASSERT(fEntry->fDataPayloadExpectations[fieldIndex].fType == DataPayloadType::kByte);
368 return payload_subspan_for_field<uint8_t>(this->dataPayload(),
369 fEntry->fDataPayloadExpectations,
370 fieldIndex);
371 }
372
ints(int fieldIndex) const373 SkSpan<const int32_t> PaintParamsKey::BlockReader::ints(int fieldIndex) const {
374 SkASSERT(fEntry->fDataPayloadExpectations[fieldIndex].fType == DataPayloadType::kInt);
375 return payload_subspan_for_field<int32_t>(this->dataPayload(),
376 fEntry->fDataPayloadExpectations,
377 fieldIndex);
378 }
379
colors(int fieldIndex) const380 SkSpan<const SkColor4f> PaintParamsKey::BlockReader::colors(int fieldIndex) const {
381 SkASSERT(fEntry->fDataPayloadExpectations[fieldIndex].fType == DataPayloadType::kFloat4);
382 return payload_subspan_for_field<SkColor4f>(this->dataPayload(),
383 fEntry->fDataPayloadExpectations,
384 fieldIndex);
385 }
386
387 #ifdef SK_DEBUG
388
numDataPayloadFields() const389 int PaintParamsKey::BlockReader::numDataPayloadFields() const {
390 return fEntry->fDataPayloadExpectations.size();
391 }
392
output_indent(int indent)393 static void output_indent(int indent) {
394 SkDebugf("%*c", 4 * indent, ' ');
395 }
396
dump(const ShaderCodeDictionary * dict,int indent) const397 void PaintParamsKey::BlockReader::dump(const ShaderCodeDictionary* dict, int indent) const {
398 uint8_t id = static_cast<uint8_t>(this->codeSnippetId());
399 uint8_t blockSize = this->blockSize();
400
401 auto entry = dict->getEntry(id);
402 if (!entry) {
403 output_indent(indent);
404 SkDebugf("unknown block! (%dB)\n", blockSize);
405 }
406
407 output_indent(indent);
408 SkDebugf("%s block (%dB)\n", entry->fStaticFunctionName, blockSize);
409
410 for (int i = 0; i < this->numChildren(); ++i) {
411 output_indent(indent);
412 // TODO: it would be nice if the names of the children were also stored (i.e., "src"/"dst")
413 SkDebugf("child %d:\n", i);
414
415 PaintParamsKey::BlockReader childReader = this->child(dict, i);
416 childReader.dump(dict, indent+1);
417 }
418
419 for (int i = 0; i < (int) fEntry->fDataPayloadExpectations.size(); ++i) {
420 output_indent(indent);
421 SkDebugf("%s[%d]: ",
422 fEntry->fDataPayloadExpectations[i].fName,
423 fEntry->fDataPayloadExpectations[i].fCount);
424 SkSpan<const uint8_t> bytes = this->bytes(i);
425 for (uint8_t b : bytes) {
426 SkDebugf("%d,", b);
427 }
428
429 SkDebugf("\n");
430 }
431 }
432
433 #endif // SK_DEBUG
434
435 } // namespace skgpu::graphite
436