| /* |
| * Copyright 2022 Google LLC |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "src/gpu/graphite/PaintParamsKey.h" |
| |
| #include <cstring> |
| #include "src/gpu/graphite/KeyHelpers.h" |
| #include "src/gpu/graphite/ShaderCodeDictionary.h" |
| |
| namespace skgpu::graphite { |
| |
| using DataPayloadType = PaintParamsKey::DataPayloadType; |
| using DataPayloadField = PaintParamsKey::DataPayloadField; |
| |
| //-------------------------------------------------------------------------------------------------- |
| static PaintParamsKey::Header read_header(SkSpan<const uint8_t> parentSpan, int headerOffset) { |
| SkASSERT(headerOffset + sizeof(PaintParamsKey::Header) <= parentSpan.size()); |
| |
| PaintParamsKey::Header header; |
| memcpy(&header, &parentSpan[headerOffset], sizeof(PaintParamsKey::Header)); |
| SkASSERT(header.blockSize >= sizeof(PaintParamsKey::Header)); |
| SkASSERT(headerOffset + header.blockSize <= static_cast<int>(parentSpan.size())); |
| |
| return header; |
| } |
| |
| //-------------------------------------------------------------------------------------------------- |
| PaintParamsKeyBuilder::PaintParamsKeyBuilder(const ShaderCodeDictionary* dict) |
| : fDict(dict) {} |
| |
| #ifdef SK_DEBUG |
| void PaintParamsKeyBuilder::checkReset() { |
| SkASSERT(!this->isLocked()); |
| SkASSERT(this->sizeInBytes() == 0); |
| SkASSERT(fIsValid); |
| SkASSERT(fStack.empty()); |
| SkASSERT(fBlendInfo == skgpu::BlendInfo()); |
| } |
| #endif |
| |
| // Block headers have the following structure: |
| // 4 bytes: code-snippet ID |
| // 1 byte: size of the block, in bytes (header, plus all data payload bytes) |
| // This call stores the header's offset in the key on the stack to be used in 'endBlock' |
| void PaintParamsKeyBuilder::beginBlock(int32_t codeSnippetID) { |
| if (!this->isValid()) { |
| return; |
| } |
| |
| if (!fDict->isValidID(codeSnippetID)) { |
| // SKGPU_LOG_W("Unknown code snippet ID."); |
| this->makeInvalid(); |
| return; |
| } |
| |
| #ifdef SK_DEBUG |
| if (!fStack.empty()) { |
| // The children of a block should appear before any of the parent's data |
| SkASSERT(fStack.back().fCurDataPayloadEntry == 0); |
| fStack.back().fNumActualChildren++; |
| } |
| |
| static constexpr DataPayloadField kHeader[2] = { |
| {"snippetID", DataPayloadType::kInt, 1}, |
| {"blockSize", DataPayloadType::kByte, 1}, |
| }; |
| |
| static const SkSpan<const DataPayloadField> kHeaderExpectations(kHeader); |
| #endif |
| |
| SkASSERT(!this->isLocked()); |
| |
| fStack.push_back({ codeSnippetID, this->sizeInBytes(), |
| SkDEBUGCODE(kHeaderExpectations, 0) }); |
| |
| this->addInt(codeSnippetID); |
| this->addByte(0); // this will be filled in when endBlock is called |
| |
| #ifdef SK_DEBUG |
| const ShaderSnippet* snippet = fDict->getEntry(codeSnippetID); |
| |
| fStack.back().fDataPayloadExpectations = snippet->fDataPayloadExpectations; |
| fStack.back().fCurDataPayloadEntry = 0; |
| fStack.back().fNumExpectedChildren = snippet->fNumChildren; |
| fStack.back().fNumActualChildren = 0; |
| #endif |
| } |
| |
| // Update the size byte of a block header |
| void PaintParamsKeyBuilder::endBlock() { |
| if (!this->isValid()) { |
| return; |
| } |
| |
| if (fStack.empty()) { |
| // SKGPU_LOG_W("Mismatched beginBlock/endBlocks."); |
| this->makeInvalid(); |
| return; |
| } |
| |
| // All the expected fields should be filled in at this point |
| SkASSERT(fStack.back().fCurDataPayloadEntry == |
| SkTo<int>(fStack.back().fDataPayloadExpectations.size())); |
| SkASSERT(fStack.back().fNumActualChildren == fStack.back().fNumExpectedChildren); |
| SkASSERT(!this->isLocked()); |
| |
| int headerOffset = fStack.back().fHeaderOffset; |
| |
| #ifdef SK_DEBUG |
| // We don't use `read_header` here because the header block size isn't valid yet. |
| PaintParamsKey::Header header; |
| memcpy(&header, &fData[headerOffset], sizeof(PaintParamsKey::Header)); |
| SkASSERT(header.codeSnippetID == fStack.back().fCodeSnippetID); |
| SkASSERT(header.blockSize == 0); |
| #endif |
| |
| int blockSize = this->sizeInBytes() - headerOffset; |
| if (blockSize > PaintParamsKey::kMaxBlockSize) { |
| // SKGPU_LOG_W("Key's data payload is too large."); |
| this->makeInvalid(); |
| return; |
| } |
| |
| fData[headerOffset + PaintParamsKey::kBlockSizeOffsetInBytes] = blockSize; |
| |
| fStack.pop_back(); |
| |
| #ifdef SK_DEBUG |
| if (!fStack.empty()) { |
| // The children of a block should appear before any of the parent's data |
| SkASSERT(fStack.back().fCurDataPayloadEntry == 0); |
| } |
| #endif |
| } |
| |
| #ifdef SK_DEBUG |
| void PaintParamsKeyBuilder::checkExpectations(DataPayloadType actualType, uint32_t actualCount) { |
| StackFrame& frame = fStack.back(); |
| const auto& expectations = frame.fDataPayloadExpectations; |
| |
| // TODO: right now we reject writing 'n' bytes one at a time. We could allow it by tracking |
| // the number of bytes written in the stack frame. |
| SkASSERT(expectations[frame.fCurDataPayloadEntry].fType == actualType); |
| SkASSERT(expectations[frame.fCurDataPayloadEntry].fCount == actualCount); |
| |
| frame.fCurDataPayloadEntry++; |
| } |
| #endif // SK_DEBUG |
| |
| static int field_size(DataPayloadType type) { |
| switch (type) { |
| case DataPayloadType::kByte: return 1; |
| case DataPayloadType::kInt: return 4; |
| case DataPayloadType::kFloat4: return 16; |
| } |
| SkUNREACHABLE; |
| } |
| |
| void PaintParamsKeyBuilder::addToKey(uint32_t count, |
| const void* data, |
| DataPayloadType payloadType) { |
| if (!this->isValid()) { |
| return; |
| } |
| |
| if (fStack.empty()) { |
| // SKGPU_LOG_W("Missing call to 'beginBlock'."); |
| this->makeInvalid(); |
| return; |
| } |
| |
| SkDEBUGCODE(this->checkExpectations(payloadType, count);) |
| SkASSERT(!this->isLocked()); |
| |
| fData.append(field_size(payloadType) * count, reinterpret_cast<const uint8_t*>(data)); |
| } |
| |
| void PaintParamsKeyBuilder::addBytes(uint32_t numBytes, const uint8_t* data) { |
| this->addToKey(numBytes, data, DataPayloadType::kByte); |
| } |
| |
| void PaintParamsKeyBuilder::addInts(uint32_t numInts, const int32_t* data) { |
| this->addToKey(numInts, data, DataPayloadType::kInt); |
| } |
| |
| void PaintParamsKeyBuilder::add(int numColors, const SkColor4f* colors) { |
| this->addToKey(numColors, colors, DataPayloadType::kFloat4); |
| } |
| |
| PaintParamsKey PaintParamsKeyBuilder::lockAsKey() { |
| if (!fStack.empty()) { |
| // SKGPU_LOG_W("Mismatched beginBlock/endBlocks."); |
| this->makeInvalid(); // fall through |
| } |
| |
| SkASSERT(!this->isLocked()); |
| |
| // Partially reset for reuse. Note that the key resulting from this call will be holding a lock |
| // on this builder and must be deleted before this builder is fully reset. |
| fIsValid = true; |
| fStack.clear(); |
| |
| return PaintParamsKey(SkSpan(fData.begin(), fData.size()), this); |
| } |
| |
| void PaintParamsKeyBuilder::makeInvalid() { |
| SkASSERT(fIsValid); |
| SkASSERT(!this->isLocked()); |
| |
| fStack.clear(); |
| fData.clear(); |
| this->beginBlock(BuiltInCodeSnippetID::kError); |
| this->endBlock(); |
| |
| SkASSERT(fIsValid); |
| fIsValid = false; |
| } |
| |
| //-------------------------------------------------------------------------------------------------- |
| PaintParamsKey::PaintParamsKey(SkSpan<const uint8_t> span, |
| PaintParamsKeyBuilder* originatingBuilder) |
| : fData(span) |
| , fOriginatingBuilder(originatingBuilder) { |
| fOriginatingBuilder->lock(); |
| } |
| |
| PaintParamsKey::PaintParamsKey(SkSpan<const uint8_t> rawData) |
| : fData(rawData) |
| , fOriginatingBuilder(nullptr) { |
| } |
| |
| PaintParamsKey::~PaintParamsKey() { |
| if (fOriginatingBuilder) { |
| fOriginatingBuilder->unlock(); |
| } |
| } |
| |
| bool PaintParamsKey::operator==(const PaintParamsKey& that) const { |
| return fData.size() == that.fData.size() && |
| !memcmp(fData.data(), that.fData.data(), fData.size()); |
| } |
| |
| PaintParamsKey::BlockReader PaintParamsKey::reader(const ShaderCodeDictionary* dict, |
| int headerOffset) const { |
| return BlockReader(dict, fData, headerOffset); |
| } |
| |
| #ifdef SK_DEBUG |
| |
| // This just iterates over the top-level blocks calling block-specific dump methods. |
| void PaintParamsKey::dump(const ShaderCodeDictionary* dict) const { |
| SkDebugf("--------------------------------------\n"); |
| SkDebugf("PaintParamsKey (%dB):\n", this->sizeInBytes()); |
| |
| int curHeaderOffset = 0; |
| while (curHeaderOffset < this->sizeInBytes()) { |
| BlockReader reader = this->reader(dict, curHeaderOffset); |
| reader.dump(dict, /*indent=*/0); |
| curHeaderOffset += reader.blockSize(); |
| } |
| } |
| #endif // SK_DEBUG |
| |
| void PaintParamsKey::AddBlockToShaderInfo(const ShaderCodeDictionary* dict, |
| const PaintParamsKey::BlockReader& reader, |
| ShaderInfo* result) { |
| |
| result->add(reader); |
| result->addFlags(dict->getEntry(reader.codeSnippetId())->fSnippetRequirementFlags); |
| |
| // The child blocks appear right after the parent block's header in the key and go |
| // right after the parent's SnippetEntry in the shader info |
| for (int i = 0; i < reader.numChildren(); ++i) { |
| BlockReader childReader = reader.child(dict, i); |
| |
| AddBlockToShaderInfo(dict, childReader, result); |
| } |
| } |
| |
| void PaintParamsKey::toShaderInfo(const ShaderCodeDictionary* dict, |
| ShaderInfo* result) const { |
| |
| int curHeaderOffset = 0; |
| while (curHeaderOffset < this->sizeInBytes()) { |
| PaintParamsKey::BlockReader reader = this->reader(dict, curHeaderOffset); |
| AddBlockToShaderInfo(dict, reader, result); |
| curHeaderOffset += reader.blockSize(); |
| } |
| } |
| |
| #if GR_TEST_UTILS |
| bool PaintParamsKey::isErrorKey() const { |
| if (this->sizeInBytes() != sizeof(Header)) { |
| return false; |
| } |
| Header header = read_header(this->asSpan(), /*headerOffset=*/0); |
| return header.codeSnippetID == (int32_t) BuiltInCodeSnippetID::kError && |
| header.blockSize == sizeof(Header); |
| } |
| #endif |
| |
| //////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| PaintParamsKey::BlockReader::BlockReader(const ShaderCodeDictionary* dict, |
| SkSpan<const uint8_t> parentSpan, |
| int offsetInParent) { |
| Header header = read_header(parentSpan, offsetInParent); |
| |
| fBlock = parentSpan.subspan(offsetInParent, header.blockSize); |
| fEntry = dict->getEntry(header.codeSnippetID); |
| SkASSERT(fEntry); |
| } |
| |
| int PaintParamsKey::BlockReader::numChildren() const { return fEntry->fNumChildren; } |
| |
| PaintParamsKey::BlockReader PaintParamsKey::BlockReader::child( |
| const ShaderCodeDictionary* dict, |
| int childIndex) const { |
| SkASSERT(childIndex < fEntry->fNumChildren); |
| |
| int childOffset = sizeof(Header); |
| for (int i = 0; i < childIndex; ++i) { |
| Header header = read_header(fBlock, childOffset); |
| childOffset += header.blockSize; |
| } |
| |
| return BlockReader(dict, fBlock, childOffset); |
| } |
| |
| int32_t PaintParamsKey::BlockReader::codeSnippetId() const { |
| Header header = read_header(fBlock, 0); |
| return header.codeSnippetID; |
| } |
| |
| SkSpan<const uint8_t> PaintParamsKey::BlockReader::dataPayload() const { |
| int payloadOffset = sizeof(Header); |
| for (int i = 0; i < fEntry->fNumChildren; ++i) { |
| Header header = read_header(fBlock, payloadOffset); |
| payloadOffset += header.blockSize; |
| } |
| |
| int payloadSize = this->blockSize() - payloadOffset; |
| return fBlock.subspan(payloadOffset, payloadSize); |
| } |
| |
| static int field_offset(SkSpan<const DataPayloadField> fields, int fieldIndex) { |
| int byteOffset = 0; |
| for (int i = 0; i < fieldIndex; ++i) { |
| byteOffset += field_size(fields[i].fType) * fields[i].fCount; |
| } |
| return byteOffset; |
| } |
| |
| template <typename T> |
| static SkSpan<const T> payload_subspan_for_field(SkSpan<const uint8_t> dataPayload, |
| SkSpan<const DataPayloadField> fields, |
| int fieldIndex) { |
| int offset = field_offset(fields, fieldIndex); |
| return {reinterpret_cast<const T*>(&dataPayload[offset]), fields[fieldIndex].fCount}; |
| } |
| |
| SkSpan<const uint8_t> PaintParamsKey::BlockReader::bytes(int fieldIndex) const { |
| SkASSERT(fEntry->fDataPayloadExpectations[fieldIndex].fType == DataPayloadType::kByte); |
| return payload_subspan_for_field<uint8_t>(this->dataPayload(), |
| fEntry->fDataPayloadExpectations, |
| fieldIndex); |
| } |
| |
| SkSpan<const int32_t> PaintParamsKey::BlockReader::ints(int fieldIndex) const { |
| SkASSERT(fEntry->fDataPayloadExpectations[fieldIndex].fType == DataPayloadType::kInt); |
| return payload_subspan_for_field<int32_t>(this->dataPayload(), |
| fEntry->fDataPayloadExpectations, |
| fieldIndex); |
| } |
| |
| SkSpan<const SkColor4f> PaintParamsKey::BlockReader::colors(int fieldIndex) const { |
| SkASSERT(fEntry->fDataPayloadExpectations[fieldIndex].fType == DataPayloadType::kFloat4); |
| return payload_subspan_for_field<SkColor4f>(this->dataPayload(), |
| fEntry->fDataPayloadExpectations, |
| fieldIndex); |
| } |
| |
| #ifdef SK_DEBUG |
| |
| int PaintParamsKey::BlockReader::numDataPayloadFields() const { |
| return fEntry->fDataPayloadExpectations.size(); |
| } |
| |
| static void output_indent(int indent) { |
| SkDebugf("%*c", 4 * indent, ' '); |
| } |
| |
| void PaintParamsKey::BlockReader::dump(const ShaderCodeDictionary* dict, int indent) const { |
| uint8_t id = static_cast<uint8_t>(this->codeSnippetId()); |
| uint8_t blockSize = this->blockSize(); |
| |
| auto entry = dict->getEntry(id); |
| if (!entry) { |
| output_indent(indent); |
| SkDebugf("unknown block! (%dB)\n", blockSize); |
| } |
| |
| output_indent(indent); |
| SkDebugf("%s block (%dB)\n", entry->fStaticFunctionName, blockSize); |
| |
| for (int i = 0; i < this->numChildren(); ++i) { |
| output_indent(indent); |
| // TODO: it would be nice if the names of the children were also stored (i.e., "src"/"dst") |
| SkDebugf("child %d:\n", i); |
| |
| PaintParamsKey::BlockReader childReader = this->child(dict, i); |
| childReader.dump(dict, indent+1); |
| } |
| |
| for (int i = 0; i < (int) fEntry->fDataPayloadExpectations.size(); ++i) { |
| output_indent(indent); |
| SkDebugf("%s[%d]: ", |
| fEntry->fDataPayloadExpectations[i].fName, |
| fEntry->fDataPayloadExpectations[i].fCount); |
| SkSpan<const uint8_t> bytes = this->bytes(i); |
| for (uint8_t b : bytes) { |
| SkDebugf("%d,", b); |
| } |
| |
| SkDebugf("\n"); |
| } |
| } |
| |
| #endif // SK_DEBUG |
| |
| } // namespace skgpu::graphite |