/* * 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/SkSpan.h" #include "include/core/SkTypes.h" #include "include/private/base/SkMacros.h" #include "include/private/base/SkTArray.h" #include "src/core/SkChecksum.h" #include "src/gpu/graphite/BuiltInCodeSnippetID.h" #include #include // for memcmp class SkArenaAlloc; struct SkSamplingOptions; enum class SkTileMode; namespace skgpu::graphite { class Caps; class ShaderCodeDictionary; class ShaderNode; class TextureProxy; class UniquePaintParamsID; /** * This class is a compact representation of the shader needed to implement a given * PaintParams. Its structure is a series of nodes where each node consists of: * 4 bytes: code-snippet ID * N child nodes, where N is the constant number of children defined by the ShaderCodeDictionary * for the node's snippet ID. * * Some snippet definitions support embedding data into the PaintParamsKey, used when something * external to the generated SkSL needs produce unique pipelines (e.g. immutable samplers). For * snippets that store data, the data is stored immediately after the ID as: * 4 bytes: code-snippet ID * 4 bytes: data length * 0-M: variable length data * N child nodes * * All children of a child node are stored in the key before the next child is encoded in the key, * e.g. iterating the data in a key is a depth-first traversal of the node tree. * * The PaintParamsKey stores multiple root nodes, with each root representing an effect tree that * affects different parts of the shading pipeline. The key is can only hold 2 or 3 roots: * 1. Color root node: produces the "src" color used in final blending with the "dst" color. * 2. Final blend node: defines the blend function combining src and dst colors. If this is a * FixedBlend snippet the final pipeline may be able to lift it to HW blending. * 3. Clipping: optional, produces analytic coverage from a clip shader or shape. * * Logically the root effects produce a src color and the src coverage (augmenting any other * coverage coming from the RenderStep). A single src shading node could be used instead of the * two for color and blending, but its structure would always be: * * [ BlendCompose [ [ color-root-node ] surface-color [ final-blend ] ] ] * * where "surface-color" would be a special snippet that produces the current dst color value. * To keep PaintParamsKeys memory cost lower, the BlendCompose and "surface-color" nodes are implied * when generating the SkSL and pipeline. */ class PaintParamsKey { public: // PaintParamsKey can only be created by using a PaintParamsKeyBuilder or by cloning the key // data from a Builder-owned key, but they can be passed around by value after that. constexpr PaintParamsKey(const PaintParamsKey&) = default; ~PaintParamsKey() = default; PaintParamsKey& operator=(const PaintParamsKey&) = default; static constexpr PaintParamsKey Invalid() { return PaintParamsKey(SkSpan()); } bool isValid() const { return !fData.empty(); } // Return a PaintParamsKey whose data is owned by the provided arena and is not attached to // a PaintParamsKeyBuilder. The caller must ensure that the SkArenaAlloc remains alive longer // than the returned key. PaintParamsKey clone(SkArenaAlloc*) const; // Converts the key into a forest of ShaderNode trees. If the key is valid this will return at // least one root node. If the key contains unknown shader snippet IDs, returns an empty span. // All shader nodes, and the returned span's backing data, are owned by the provided arena. // // A valid key will produce either 2 or 3 root nodes. The first root node represents how the // source color is computed. The second node defines the final blender between the calculated // source color and the current pixel's dst color. If provided, the third node calculates an // additional analytic coverage value to combine with the geometry's coverage. SkSpan getRootNodes(const ShaderCodeDictionary*, SkArenaAlloc*) const; // Converts the key to a structured list of snippet information for debugging or labeling // purposes. SkString toString(const ShaderCodeDictionary* dict, bool includeData) const; #ifdef SK_DEBUG void dump(const ShaderCodeDictionary*, UniquePaintParamsID) const; #endif bool operator==(const PaintParamsKey& that) const { return fData.size() == that.fData.size() && !memcmp(fData.data(), that.fData.data(), fData.size()); } bool operator!=(const PaintParamsKey& that) const { return !(*this == that); } struct Hash { uint32_t operator()(const PaintParamsKey& k) const { return SkChecksum::Hash32(k.fData.data(), k.fData.size_bytes()); } }; private: friend class PaintParamsKeyBuilder; // for the parented-data ctor constexpr PaintParamsKey(SkSpan span) : fData(span) {} // Returns null if the node or any of its children have an invalid snippet ID. Recursively // creates a node and all of its children, incrementing 'currentIndex' by the total number of // nodes created. const ShaderNode* createNode(const ShaderCodeDictionary*, int* currentIndex, SkArenaAlloc* arena) const; // The memory referenced in 'fData' is always owned by someone else. It either shares the span // from the Builder, or clone() puts the span in an arena. SkSpan fData; }; // 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* dict) { SkDEBUGCODE(fDict = dict;) } ~PaintParamsKeyBuilder() { SkASSERT(!fLocked); } void beginBlock(BuiltInCodeSnippetID id) { this->beginBlock(static_cast(id)); } void beginBlock(int32_t codeSnippetID) { SkASSERT(!fLocked); SkDEBUGCODE(this->pushStack(codeSnippetID);) fData.push_back(codeSnippetID); } // TODO: Have endBlock() be handled automatically with RAII, in which case we could have it // validate the snippet ID being popped off the stack frame. void endBlock() { SkDEBUGCODE(this->popStack();) } #ifdef SK_DEBUG // Check that the builder has been reset to its initial state prior to creating a new key. void checkReset(); #endif // Helper to add blocks that don't have children void addBlock(BuiltInCodeSnippetID id) { this->beginBlock(id); this->endBlock(); } void addData(SkSpan data) { // First push the data size followed by the actual data. SkDEBUGCODE(this->validateData(data.size())); fData.push_back(data.size()); fData.push_back_n(data.size(), data.begin()); } private: friend class AutoLockBuilderAsKey; // for lockAsKey() and unlock() // Returns a view of this builder as a PaintParamsKey. The Builder cannot be used until the // returned Key goes out of scope. PaintParamsKey lockAsKey() { SkASSERT(!fLocked); // lockAsKey() is not re-entrant SkASSERT(fStack.empty()); // All beginBlocks() had a matching endBlock() SkDEBUGCODE(fLocked = true;) return PaintParamsKey({fData.data(), fData.size()}); } // Invalidates any PaintParamsKey returned by lockAsKey() unless it has been cloned. void unlock() { SkASSERT(fLocked); fData.clear(); SkDEBUGCODE(fLocked = false;) SkDEBUGCODE(fStack.clear();) SkDEBUGCODE(this->checkReset();) } // The data array uses clear() on unlock so that it's underlying storage and repeated use of the // builder will hit a high-water mark and avoid lots of allocations when recording draws. skia_private::TArray fData; #ifdef SK_DEBUG void pushStack(int32_t codeSnippetID); void validateData(size_t dataSize); void popStack(); // Information about the current block being written struct StackFrame { int fCodeSnippetID; int fNumExpectedChildren; int fNumActualChildren = 0; int fDataSize = -1; }; const ShaderCodeDictionary* fDict; skia_private::TArray fStack; bool fLocked = false; #endif }; class AutoLockBuilderAsKey { public: AutoLockBuilderAsKey(PaintParamsKeyBuilder* builder) : fBuilder(builder) , fKey(builder->lockAsKey()) {} ~AutoLockBuilderAsKey() { fBuilder->unlock(); } // Use as a PaintParamsKey const PaintParamsKey& operator*() const { return fKey; } const PaintParamsKey* operator->() const { return &fKey; } private: PaintParamsKeyBuilder* fBuilder; PaintParamsKey fKey; }; } // namespace skgpu::graphite #endif // skgpu_graphite_PaintParamsKey_DEFINED