/* * 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 #include 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 { public: #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::max(); enum class DataPayloadType { kByte, kInt, kFloat4, }; // 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; }; ~PaintParamsKey(); class BlockReader { public: // Returns the combined size of the header, all children, and every data payload. uint8_t blockSize() const { SkASSERT(fBlock[kBlockSizeOffsetInBytes] == fBlock.size()); return SkTo(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 bytes(int fieldIndex) const; SkSpan ints(int fieldIndex) const; SkSpan colors(int fieldIndex) const; const ShaderSnippet* entry() const { return fEntry; } #ifdef SK_DEBUG int numDataPayloadFields() const; void dump(const ShaderCodeDictionary*, int indent) const; #endif private: friend class PaintParamsKey; // for ctor BlockReader(const ShaderCodeDictionary*, SkSpan parentSpan, int offsetInParent); // The data payload appears after any children and occupies the remainder of the // block's space. SkSpan dataPayload() const; SkSpan 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; #endif void toShaderInfo(const ShaderCodeDictionary*, ShaderInfo*) const; SkSpan asSpan() const { return fData; } const uint8_t* data() const { return fData.data(); } int sizeInBytes() const { return SkTo(fData.size()); } bool operator==(const PaintParamsKey& that) const; bool operator!=(const PaintParamsKey& that) const { return !(*this == that); } #if GRAPHITE_TEST_UTILS bool isErrorKey() const; #endif private: 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 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 rawData); static void AddBlockToShaderInfo(const ShaderCodeDictionary*, const BlockReader&, ShaderInfo*); // 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 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 { public: PaintParamsKeyBuilder(const ShaderCodeDictionary*); ~PaintParamsKeyBuilder() { SkASSERT(!this->isLocked()); } 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(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]; } #endif PaintParamsKey lockAsKey(); int sizeInBytes() const { return fData.size(); } bool isValid() const { return fIsValid; } void lock() { SkASSERT(!fLocked); SkDEBUGCODE(fLocked = true;) } void unlock() { SkASSERT(fLocked); fData.clear(); fBlendInfo = {}; SkDEBUGCODE(fLocked = false;) SkDEBUGCODE(this->checkReset();) } SkDEBUGCODE(bool isLocked() const { return fLocked; }) private: void addToKey(uint32_t count, const void* data, PaintParamsKey::DataPayloadType payloadType); void makeInvalid(); #ifdef SK_DEBUG void checkExpectations(PaintParamsKey::DataPayloadType actualType, uint32_t actualCount); #endif // Information about the current block being written struct StackFrame { int fCodeSnippetID; int fHeaderOffset; #ifdef SK_DEBUG SkSpan fDataPayloadExpectations; int fCurDataPayloadEntry = 0; int fNumExpectedChildren = 0; int fNumActualChildren = 0; #endif }; 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 fStack; SkTDArray fData; skgpu::BlendInfo fBlendInfo; }; } // skgpu::graphite #endif // skgpu_graphite_PaintParamsKey_DEFINED