blob: 521366d2e100ddbf85fda14b455131817e6f9161 [file] [log] [blame]
/*
* 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/core/SkPaintParamsKey.h"
#include <cstring>
#include "src/core/SkKeyHelpers.h"
#include "src/core/SkShaderCodeDictionary.h"
using DataPayloadType = SkPaintParamsKey::DataPayloadType;
using DataPayloadField = SkPaintParamsKey::DataPayloadField;
//--------------------------------------------------------------------------------------------------
SkPaintParamsKeyBuilder::SkPaintParamsKeyBuilder(const SkShaderCodeDictionary* dict,
SkBackend backend)
: fDict(dict)
, fBackend(backend) {
}
#ifdef SK_DEBUG
void SkPaintParamsKeyBuilder::checkReset() {
SkASSERT(!this->isLocked());
SkASSERT(this->sizeInBytes() == 0);
SkASSERT(this->numPointers() == 0);
SkASSERT(fIsValid);
SkASSERT(fStack.empty());
#ifdef SK_GRAPHITE_ENABLED
SkASSERT(fBlendInfo == skgpu::BlendInfo());
#endif
}
#endif
// Block headers have the following structure:
// 1st byte: codeSnippetID
// 2nd byte: total blockSize in bytes
// This call stores the header's offset in the key on the stack to be used in 'endBlock'
void SkPaintParamsKeyBuilder::beginBlock(int 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::kByte, 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->addByte(SkTo<uint8_t>(codeSnippetID));
this->addByte(0); // this will be filled in when endBlock is called
#ifdef SK_DEBUG
const SkShaderSnippet* 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 SkPaintParamsKeyBuilder::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;
SkPaintParamsKey::Header* header =
reinterpret_cast<SkPaintParamsKey::Header*>(&fData[headerOffset]);
SkASSERT(header->codeSnippetID == fStack.back().fCodeSnippetID);
SkASSERT(header->blockSize == 0);
int blockSize = this->sizeInBytes() - headerOffset;
if (blockSize > SkPaintParamsKey::kMaxBlockSize) {
// SKGPU_LOG_W("Key's data payload is too large.");
this->makeInvalid();
return;
}
header->blockSize = blockSize;
fStack.pop();
#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 SkPaintParamsKeyBuilder::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:
case DataPayloadType::kPointerIndex: return 1;
case DataPayloadType::kInt: return 4;
case DataPayloadType::kFloat4: return 16;
}
SkUNREACHABLE;
}
void SkPaintParamsKeyBuilder::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 SkPaintParamsKeyBuilder::addBytes(uint32_t numBytes, const uint8_t* data) {
this->addToKey(numBytes, data, DataPayloadType::kByte);
}
void SkPaintParamsKeyBuilder::addInts(uint32_t numInts, const int32_t* data) {
this->addToKey(numInts, data, DataPayloadType::kInt);
}
void SkPaintParamsKeyBuilder::add(int numColors, const SkColor4f* colors) {
this->addToKey(numColors, colors, DataPayloadType::kFloat4);
}
void SkPaintParamsKeyBuilder::addPointer(const void* ptr) {
SkASSERT(fPointerData.size() <= 0xFF);
uint8_t pointerIndex = (uint8_t)fPointerData.size();
this->addToKey(1, &pointerIndex, DataPayloadType::kPointerIndex);
fPointerData.push_back(ptr);
}
SkPaintParamsKey SkPaintParamsKeyBuilder::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.rewind();
return SkPaintParamsKey(SkSpan(fData.begin(), fData.count()),
SkSpan(fPointerData.begin(), fPointerData.count()),
this);
}
void SkPaintParamsKeyBuilder::makeInvalid() {
SkASSERT(fIsValid);
SkASSERT(!this->isLocked());
fStack.rewind();
fData.rewind();
fPointerData.rewind();
this->beginBlock(SkBuiltInCodeSnippetID::kError);
this->endBlock();
SkASSERT(fIsValid);
fIsValid = false;
}
//--------------------------------------------------------------------------------------------------
SkPaintParamsKey::SkPaintParamsKey(SkSpan<const uint8_t> span,
SkSpan<const void*> pointerSpan,
SkPaintParamsKeyBuilder* originatingBuilder)
: fData(span)
, fPointerData(pointerSpan)
, fOriginatingBuilder(originatingBuilder) {
fOriginatingBuilder->lock();
}
SkPaintParamsKey::SkPaintParamsKey(SkSpan<const uint8_t> rawData)
: fData(rawData)
, fOriginatingBuilder(nullptr) {
}
SkPaintParamsKey::~SkPaintParamsKey() {
if (fOriginatingBuilder) {
fOriginatingBuilder->unlock();
}
}
bool SkPaintParamsKey::operator==(const SkPaintParamsKey& that) const {
// Pointer data is intentionally ignored here; a cached key will not have pointer data.
return fData.size() == that.fData.size() &&
!memcmp(fData.data(), that.fData.data(), fData.size());
}
SkPaintParamsKey::BlockReader SkPaintParamsKey::reader(const SkShaderCodeDictionary* dict,
int headerOffset) const {
return BlockReader(dict, fData, fPointerData, headerOffset);
}
#ifdef SK_DEBUG
// This just iterates over the top-level blocks calling block-specific dump methods.
void SkPaintParamsKey::dump(const SkShaderCodeDictionary* dict) const {
SkDebugf("--------------------------------------\n");
SkDebugf("SkPaintParamsKey (%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 SkPaintParamsKey::AddBlockToShaderInfo(SkShaderCodeDictionary* dict,
const SkPaintParamsKey::BlockReader& reader,
SkShaderInfo* result) {
result->add(reader);
#ifdef SK_GRAPHITE_ENABLED
result->addFlags(dict->getSnippetRequirementFlags(reader.codeSnippetId()));
#endif
// 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) {
SkPaintParamsKey::BlockReader childReader = reader.child(dict, i);
AddBlockToShaderInfo(dict, childReader, result);
}
}
void SkPaintParamsKey::toShaderInfo(SkShaderCodeDictionary* dict, SkShaderInfo* result) const {
int curHeaderOffset = 0;
while (curHeaderOffset < this->sizeInBytes()) {
SkPaintParamsKey::BlockReader reader = this->reader(dict, curHeaderOffset);
AddBlockToShaderInfo(dict, reader, result);
curHeaderOffset += reader.blockSize();
}
}
#if GR_TEST_UTILS
bool SkPaintParamsKey::isErrorKey() const {
return this->sizeInBytes() == sizeof(Header) &&
fData[0] == static_cast<int>(SkBuiltInCodeSnippetID::kError) &&
fData[1] == sizeof(Header);
}
#endif
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace {
[[maybe_unused]] void output_indent(int indent) {
SkDebugf("%*c", 4 * indent, ' ');
}
SkPaintParamsKey::Header read_header(SkSpan<const uint8_t> parentSpan, int headerOffset) {
SkASSERT(headerOffset + sizeof(SkPaintParamsKey::Header) <= parentSpan.size());
const SkPaintParamsKey::Header* header =
reinterpret_cast<const SkPaintParamsKey::Header*>(&parentSpan[headerOffset]);
SkASSERT(header->blockSize >= sizeof(SkPaintParamsKey::Header));
SkASSERT(headerOffset + header->blockSize <= static_cast<int>(parentSpan.size()));
return *header;
}
} // anonymous namespace
SkPaintParamsKey::BlockReader::BlockReader(const SkShaderCodeDictionary* dict,
SkSpan<const uint8_t> parentSpan,
SkSpan<const void*> pointerSpan,
int offsetInParent) {
Header header = read_header(parentSpan, offsetInParent);
fBlock = parentSpan.subspan(offsetInParent, header.blockSize);
fPointerSpan = pointerSpan;
fEntry = dict->getEntry(header.codeSnippetID);
SkASSERT(fEntry);
}
int SkPaintParamsKey::BlockReader::numChildren() const { return fEntry->fNumChildren; }
SkPaintParamsKey::BlockReader SkPaintParamsKey::BlockReader::child(
const SkShaderCodeDictionary* 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, fPointerSpan, childOffset);
}
SkSpan<const uint8_t> SkPaintParamsKey::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> SkPaintParamsKey::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> SkPaintParamsKey::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> SkPaintParamsKey::BlockReader::colors(int fieldIndex) const {
SkASSERT(fEntry->fDataPayloadExpectations[fieldIndex].fType == DataPayloadType::kFloat4);
return payload_subspan_for_field<SkColor4f>(this->dataPayload(),
fEntry->fDataPayloadExpectations,
fieldIndex);
}
const void* SkPaintParamsKey::BlockReader::pointer(int fieldIndex) const {
SkASSERT(fEntry->fDataPayloadExpectations[fieldIndex].fType == DataPayloadType::kPointerIndex);
SkASSERT(fEntry->fDataPayloadExpectations[fieldIndex].fCount == 1);
SkSpan dataSpan = payload_subspan_for_field<uint8_t>(this->dataPayload(),
fEntry->fDataPayloadExpectations,
fieldIndex);
return fPointerSpan[dataSpan[0]];
}
#ifdef SK_DEBUG
int SkPaintParamsKey::BlockReader::numDataPayloadFields() const {
return fEntry->fDataPayloadExpectations.size();
}
void SkPaintParamsKey::BlockReader::dump(const SkShaderCodeDictionary* 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);
SkPaintParamsKey::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