blob: 8eb26d837067e5bd6f1c875757c4bd4cdfdf484c [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.
#ifndef skgpu_graphite_PaintParamsKey_DEFINED
#define skgpu_graphite_PaintParamsKey_DEFINED
#include "include/core/SkColor.h"
#include "include/core/SkSpan.h"
#include "include/core/SkTypes.h"
#include "include/private/base/SkMacros.h"
#include "include/private/base/SkTDArray.h"
#include "src/gpu/Blend.h"
#include "src/gpu/graphite/BuiltInCodeSnippetID.h"
#include <array>
#include <limits>
namespace skgpu::graphite {
class PaintParamsKeyBuilder;
class ShaderCodeDictionary;
class ShaderInfo;
struct ShaderSnippet;
// This class is a compact representation of the shader needed to implement a given
// PaintParams. Its structure is a series of blocks where each block has a
// Header, consisting of 2 bytes:
// 4 bytes: code-snippet ID
// 1 byte: size of the block, in bytes (header, plus all data payload bytes)
// The rest of the data in the block is dependent on the individual code snippet.
// If a given block has child blocks, they appear in the key right after their parent
// block's header.
class PaintParamsKey {
#pragma pack(push, 1)
struct Header {
int32_t codeSnippetID;
uint8_t blockSize;
#pragma pack(pop)
static const int kBlockSizeOffsetInBytes = offsetof(Header, blockSize);
static const int kMaxBlockSize = std::numeric_limits<uint8_t>::max();
enum class DataPayloadType {
// A given snippet's data payload is stored as an SkSpan of DataPayloadFields in the
// ShaderCodeDictionary. That span just defines the structure of the data payload. The actual
// data is stored in the paint params key.
struct DataPayloadField {
const char* fName;
DataPayloadType fType;
uint32_t fCount;
class BlockReader {
// Returns the combined size of the header, all children, and every data payload.
uint8_t blockSize() const {
SkASSERT(fBlock[kBlockSizeOffsetInBytes] == fBlock.size());
return SkTo<uint8_t>(fBlock.size());
int numChildren() const;
// Returns the code-snippet ID of this block.
int32_t codeSnippetId() const;
// Return the childIndex-th child's BlockReader
BlockReader child(const ShaderCodeDictionary*, int childIndex) const;
// Retrieve the fieldIndex-th field in the data payload as a span. The type being read
// is checked against the data payload's structure.
SkSpan<const uint8_t> bytes(int fieldIndex) const;
SkSpan<const int32_t> ints(int fieldIndex) const;
SkSpan<const SkColor4f> colors(int fieldIndex) const;
const ShaderSnippet* entry() const { return fEntry; }
#ifdef SK_DEBUG
int numDataPayloadFields() const;
void dump(const ShaderCodeDictionary*, int indent) const;
friend class PaintParamsKey; // for ctor
BlockReader(const ShaderCodeDictionary*,
SkSpan<const uint8_t> parentSpan,
int offsetInParent);
// The data payload appears after any children and occupies the remainder of the
// block's space.
SkSpan<const uint8_t> dataPayload() const;
SkSpan<const uint8_t> fBlock;
const ShaderSnippet* fEntry;
BlockReader reader(const ShaderCodeDictionary*, int headerOffset) const;
#ifdef SK_DEBUG
uint8_t byte(int offset) const {
SkASSERT(offset < this->sizeInBytes());
return fData[offset];
void dump(const ShaderCodeDictionary*) const;
void toShaderInfo(const ShaderCodeDictionary*, ShaderInfo*) const;
SkSpan<const uint8_t> asSpan() const { return fData; }
const uint8_t* data() const { return; }
int sizeInBytes() const { return SkTo<int>(fData.size()); }
bool operator==(const PaintParamsKey& that) const;
bool operator!=(const PaintParamsKey& that) const { return !(*this == that); }
bool isErrorKey() const;
friend class PaintParamsKeyBuilder; // for the parented-data ctor
friend class ShaderCodeDictionary; // for the raw-data ctor
// This ctor is to be used when paintparams keys are being consecutively generated
// by a key builder. The memory backing this key's span is shared between the
// builder and its keys.
PaintParamsKey(SkSpan<const uint8_t> span, PaintParamsKeyBuilder* originatingBuilder);
// This ctor is used when this key isn't being created by a builder (i.e., when the key
// is in the dictionary). In this case the dictionary will own the memory backing the span.
PaintParamsKey(SkSpan<const uint8_t> rawData);
static void AddBlockToShaderInfo(const ShaderCodeDictionary*,
const BlockReader&,
// The memory referenced in 'fData' is always owned by someone else.
// If 'fOriginatingBuilder' is null, the dictionary's SkArena owns the 'fData' memory and no
// explicit freeing is required.
// If 'fOriginatingBuilder' is non-null then the 'fData' memory must be explicitly locked (in
// the ctor) and unlocked (in the dtor) on the 'fOriginatingBuilder' object.
SkSpan<const uint8_t> fData;
// This class should only ever access the 'lock' and 'unlock' calls on 'fOriginatingBuilder'
PaintParamsKeyBuilder* fOriginatingBuilder;
// The PaintParamsKeyBuilder and the PaintParamsKeys snapped from it share the same
// underlying block of memory. When an PaintParamsKey is snapped from the builder it 'locks'
// the memory and 'unlocks' it in its destructor. Because of this relationship, the builder
// can only have one extant key and that key must be destroyed before the builder can be reused
// to create another one.
// This arrangement is intended to improve performance in the expected case, where a builder is
// being used in a tight loop to generate keys which can be recycled once they've been used to
// find the dictionary's matching uniqueID. We don't expect the cost of copying the key's memory
// into the dictionary to be prohibitive since that should be infrequent.
class PaintParamsKeyBuilder {
PaintParamsKeyBuilder(const ShaderCodeDictionary*);
~PaintParamsKeyBuilder() {
void setBlendInfo(const skgpu::BlendInfo& blendInfo) {
fBlendInfo = blendInfo;
const skgpu::BlendInfo& blendInfo() const { return fBlendInfo; }
void beginBlock(int32_t codeSnippetID);
void beginBlock(BuiltInCodeSnippetID id) { this->beginBlock(static_cast<int32_t>(id)); }
void endBlock();
void addBytes(uint32_t numBytes, const uint8_t* data);
void addByte(uint8_t data) {
this->addBytes(1, &data);
void addInts(uint32_t numInts, const int32_t* data);
void addInt(int32_t data) {
this->addInts(1, &data);
void add(int numColors, const SkColor4f* colors);
void add(const SkColor4f& color) {
this->add(/*numColors=*/1, &color);
#ifdef SK_DEBUG
// Check that the builder has been reset to its initial state prior to creating a new key.
void checkReset();
uint8_t byte(int offset) const { return fData[offset]; }
PaintParamsKey lockAsKey();
int sizeInBytes() const { return fData.size(); }
bool isValid() const { return fIsValid; }
void lock() {
SkDEBUGCODE(fLocked = true;)
void unlock() {
fBlendInfo = {};
SkDEBUGCODE(fLocked = false;)
SkDEBUGCODE(bool isLocked() const { return fLocked; })
void addToKey(uint32_t count, const void* data, PaintParamsKey::DataPayloadType payloadType);
void makeInvalid();
#ifdef SK_DEBUG
void checkExpectations(PaintParamsKey::DataPayloadType actualType, uint32_t actualCount);
// Information about the current block being written
struct StackFrame {
int fCodeSnippetID;
int fHeaderOffset;
#ifdef SK_DEBUG
SkSpan<const PaintParamsKey::DataPayloadField> fDataPayloadExpectations;
int fCurDataPayloadEntry = 0;
int fNumExpectedChildren = 0;
int fNumActualChildren = 0;
const ShaderCodeDictionary* fDict;
bool fIsValid = true;
SkDEBUGCODE(bool fLocked = false;)
// Use SkTDArray so that we can guarantee that rewind() preserves the underlying storage and
// repeated use of the builder will hit a high-water mark and avoid lots of allocations.
SkTDArray<StackFrame> fStack;
SkTDArray<uint8_t> fData;
skgpu::BlendInfo fBlendInfo;
} // skgpu::graphite
#endif // skgpu_graphite_PaintParamsKey_DEFINED