| /* | |
| * 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 <limits> | |
| #include <cstring> // 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; | |
| constexpr PaintParamsKey(SkSpan<const uint32_t> span) : fData(span) {} | |
| ~PaintParamsKey() = default; | |
| PaintParamsKey& operator=(const PaintParamsKey&) = default; | |
| static constexpr PaintParamsKey Invalid() { return PaintParamsKey(SkSpan<const uint32_t>()); } | |
| 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. | |
| // | |
| // Before returning the ShaderNode trees, this method decides which ShaderNode expressions to | |
| // lift to the vertex shader, depending on how many varyings are available. | |
| SkSpan<const ShaderNode*> getRootNodes(const Caps*, | |
| const ShaderCodeDictionary*, | |
| SkArenaAlloc*, | |
| int availableVaryings) const; | |
| // Converts the key to a structured list of snippet information for debugging or labeling | |
| // purposes. | |
| SkString toString(const ShaderCodeDictionary* dict) 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()); | |
| } | |
| }; | |
| SkSpan<const uint32_t> data() const { return fData; } | |
| // Checks that a given key is viable for serialization and, also, that a deserialized | |
| // key is, at least, correctly formed. Other than that all the sizes make sense, this method | |
| // also checks that only Skia-internal shader code snippets appear in the key. | |
| [[nodiscard]] bool isSerializable(const ShaderCodeDictionary*) const; | |
| private: | |
| friend class PaintParamsKeyBuilder; // for the parented-data ctor | |
| // 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. | |
| 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<const uint32_t> 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<int32_t>(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<const uint32_t> 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<uint32_t> 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<StackFrame> 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 |