|  | /* | 
|  | * 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/gpu/graphite/PaintParamsKey.h" | 
|  |  | 
|  | #include "src/base/SkArenaAlloc.h" | 
|  | #include "src/base/SkAutoMalloc.h" | 
|  | #include "src/base/SkBase64.h" | 
|  | #include "src/base/SkStringView.h" | 
|  | #include "src/gpu/graphite/Caps.h" | 
|  | #include "src/gpu/graphite/KeyHelpers.h" | 
|  | #include "src/gpu/graphite/Log.h" | 
|  | #include "src/gpu/graphite/ShaderCodeDictionary.h" | 
|  |  | 
|  | using namespace skia_private; | 
|  |  | 
|  | namespace skgpu::graphite { | 
|  |  | 
|  | //-------------------------------------------------------------------------------------------------- | 
|  | // PaintParamsKeyBuilder | 
|  |  | 
|  | #ifdef SK_DEBUG | 
|  |  | 
|  | void PaintParamsKeyBuilder::checkReset() { | 
|  | SkASSERT(!fLocked); | 
|  | SkASSERT(fData.empty()); | 
|  | SkASSERT(fStack.empty()); | 
|  | } | 
|  |  | 
|  | void PaintParamsKeyBuilder::pushStack(int32_t codeSnippetID) { | 
|  | SkASSERT(fDict->isValidID(codeSnippetID)); | 
|  |  | 
|  | if (!fStack.empty()) { | 
|  | fStack.back().fNumActualChildren++; | 
|  | SkASSERT(fStack.back().fNumActualChildren <= fStack.back().fNumExpectedChildren); | 
|  | } | 
|  |  | 
|  | const ShaderSnippet* snippet = fDict->getEntry(codeSnippetID); | 
|  | fStack.push_back({codeSnippetID, snippet->fNumChildren}); | 
|  | } | 
|  |  | 
|  | void PaintParamsKeyBuilder::validateData(size_t dataSize) { | 
|  | SkASSERT(!fStack.empty()); // addData() called within code snippet block | 
|  | // Check that addData() is only called for snippets that support it and is only called once | 
|  | const ShaderSnippet* snippet = fDict->getEntry(fStack.back().fCodeSnippetID); | 
|  | SkASSERT(snippet->storesSamplerDescData()); | 
|  | SkASSERT(fStack.back().fDataSize < 0); | 
|  |  | 
|  | fStack.back().fDataSize = SkTo<int>(dataSize); | 
|  | } | 
|  |  | 
|  | void PaintParamsKeyBuilder::popStack() { | 
|  | SkASSERT(!fStack.empty()); | 
|  | SkASSERT(fStack.back().fNumActualChildren == fStack.back().fNumExpectedChildren); | 
|  | const bool expectsData = fDict->getEntry(fStack.back().fCodeSnippetID)->storesSamplerDescData(); | 
|  | const bool hasData = fStack.back().fDataSize >= 0; | 
|  | SkASSERT(expectsData == hasData); | 
|  | fStack.pop_back(); | 
|  | } | 
|  |  | 
|  | #endif // SK_DEBUG | 
|  |  | 
|  | //-------------------------------------------------------------------------------------------------- | 
|  | // PaintParamsKey | 
|  |  | 
|  | PaintParamsKey PaintParamsKey::clone(SkArenaAlloc* arena) const { | 
|  | uint32_t* newData = arena->makeArrayDefault<uint32_t>(fData.size()); | 
|  | memcpy(newData, fData.data(), fData.size_bytes()); | 
|  | return PaintParamsKey({newData, fData.size()}); | 
|  | } | 
|  |  | 
|  | ShaderNode* PaintParamsKey::createNode(const ShaderCodeDictionary* dict, | 
|  | int* currentIndex, | 
|  | SkArenaAlloc* arena) const { | 
|  | SkASSERT(*currentIndex < SkTo<int>(fData.size())); | 
|  | const int32_t index = (*currentIndex)++; | 
|  | const int32_t id = fData[index]; | 
|  |  | 
|  | const ShaderSnippet* entry = dict->getEntry(id); | 
|  | if (!entry) { | 
|  | SKGPU_LOG_E("Unknown snippet ID in key: %d", id); | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | SkSpan<const uint32_t> dataSpan = {}; | 
|  | if (entry->storesSamplerDescData()) { | 
|  | // If a snippet stores data, then the subsequent paint key index signifies the length of | 
|  | // its data. Determine this data length and iterate currentIndex past it. | 
|  | const int storedDataLengthIdx = (*currentIndex)++; | 
|  | SkASSERT(storedDataLengthIdx < SkTo<int>(fData.size())); | 
|  | const int dataLength = fData[storedDataLengthIdx]; | 
|  | SkASSERT(storedDataLengthIdx + dataLength < SkTo<int>(fData.size())); | 
|  |  | 
|  | // Gather the data contents (length can now be inferred by the consumers of the data) to | 
|  | // pass into ShaderNode creation. Iterate the paint key index past the data indices. | 
|  | dataSpan = fData.subspan(storedDataLengthIdx + 1, dataLength); | 
|  | *currentIndex += dataLength; | 
|  | } | 
|  |  | 
|  | ShaderNode** childArray = arena->makeArray<ShaderNode*>(entry->fNumChildren); | 
|  | for (int i = 0; i < entry->fNumChildren; ++i) { | 
|  | ShaderNode* child = this->createNode(dict, currentIndex, arena); | 
|  | if (!child) { | 
|  | return nullptr; | 
|  | } | 
|  | childArray[i] = child; | 
|  | } | 
|  |  | 
|  | return arena->make<ShaderNode>(entry, | 
|  | SkSpan(childArray, entry->fNumChildren), | 
|  | id, | 
|  | index, | 
|  | dataSpan); | 
|  | } | 
|  |  | 
|  | // Traverse a ShaderNode tree, attempting to lift any coordinate modification expressions. | 
|  | // Returns whether any of the given nodes need local coordinate inputs after lifting. | 
|  | bool lift_coord_expressions(SkSpan<ShaderNode*> nodes, int* availableVaryings) { | 
|  | bool anyNeedLocalCoords = false; | 
|  |  | 
|  | for (ShaderNode* node : nodes) { | 
|  | bool curNeedsLocalCoords = | 
|  | SkToBool(node->requiredFlags() & SnippetRequirementFlags::kLocalCoords); | 
|  |  | 
|  | // Lift expressions from nodes whose liftable expressions are on coordinate inputs. | 
|  | if (*availableVaryings > 0 && curNeedsLocalCoords && | 
|  | node->entry()->fLiftableExpressionType == | 
|  | ShaderSnippet::LiftableExpressionType::kLocalCoords) { | 
|  | --*availableVaryings; | 
|  |  | 
|  | #if !defined(SK_USE_LEGACY_UNIFORM_LIFTING_GRAPHITE) | 
|  | // We can potentially lift the nested expressions under here as well. | 
|  | const bool childNeedsOurCoords = | 
|  | lift_coord_expressions(node->children(), availableVaryings); | 
|  | // If no child needs our lifted coords, we can omit them from the fragment shader | 
|  | // entirely, and only use them in the vertex shader for calculating other coords. | 
|  | if (!childNeedsOurCoords) { | 
|  | node->setOmitExpressionFlag(); | 
|  | } else { | 
|  | node->setLiftExpressionFlag(); | 
|  | } | 
|  | #else | 
|  | node->setLiftExpressionFlag(); | 
|  | #endif | 
|  | // Since we lifted the coordinate expression here, this node no longer needs a local | 
|  | // coords argument. | 
|  | curNeedsLocalCoords = false; | 
|  | node->unsetLocalCoordsFlag(); | 
|  |  | 
|  | #if !defined(SK_USE_LEGACY_UNIFORM_LIFTING_GRAPHITE) | 
|  | // If the node passes through its local coords to its children, we check if those perform | 
|  | // modifications that can be lifted. | 
|  | } else if (*availableVaryings > 0 && | 
|  | node->requiredFlags() & SnippetRequirementFlags::kPassthroughLocalCoords) { | 
|  | // Assume that this node doesn't need local coordinates unless its actual shader snippet | 
|  | // entry does, or one of its children does even after accounting for lifting. | 
|  | const bool entryNeedsLocalCoords = node->entry()->needsLocalCoords(); | 
|  | const bool childNeedsLocalCoords = lift_coord_expressions(node->children(), | 
|  | availableVaryings); | 
|  | curNeedsLocalCoords = entryNeedsLocalCoords || childNeedsLocalCoords; | 
|  | if (!curNeedsLocalCoords) { | 
|  | node->unsetLocalCoordsFlag(); | 
|  | } | 
|  | #endif | 
|  | } | 
|  |  | 
|  | anyNeedLocalCoords |= curNeedsLocalCoords; | 
|  | } | 
|  |  | 
|  | return anyNeedLocalCoords; | 
|  | } | 
|  |  | 
|  | // Traverse a list of ShaderNodes, attempting to lift any expressions that resolve to a color. | 
|  | // For now, this does not recurse into ShaderNodes' lists of children. In practice we only lift | 
|  | // solid color expressions, and we only care to lift such expressions if there is no other fragment | 
|  | // shader work (i.e., if the solid color expression is a root node in a shader's ShaderNode tree). | 
|  | // If there is other fragment shader work, we'll likely be accessing other fragment shader uniforms, | 
|  | // the color value will likely be cached, and lifting may not be worth the extra varying. | 
|  | void lift_color_expressions(SkSpan<ShaderNode*> nodes, int* availableVaryings) { | 
|  | #if !defined(SK_USE_LEGACY_UNIFORM_LIFTING_GRAPHITE) | 
|  | for (ShaderNode* node : nodes) { | 
|  | if (*availableVaryings > 0 && | 
|  | node->entry()->fLiftableExpressionType == | 
|  | ShaderSnippet::LiftableExpressionType::kPriorStageOutput) { | 
|  | --*availableVaryings; | 
|  | node->setLiftExpressionFlag(); | 
|  | } | 
|  | } | 
|  | #endif | 
|  | } | 
|  |  | 
|  | SkSpan<const ShaderNode*> PaintParamsKey::getRootNodes(const Caps* caps, | 
|  | const ShaderCodeDictionary* dict, | 
|  | SkArenaAlloc* arena, | 
|  | int availableVaryings) const { | 
|  | // TODO: Once the PaintParamsKey creation is organized to represent a single tree starting at | 
|  | // the final blend, there will only be a single root node and this can be simplified. | 
|  | // For now, we don't know how many roots there are, so collect them into a local array before | 
|  | // copying into the arena. | 
|  | const int keySize = SkTo<int>(fData.size()); | 
|  |  | 
|  | // Normal PaintParams creation will have up to 7 roots for the different stages. | 
|  | STArray<7, ShaderNode*> roots; | 
|  | int currentIndex = 0; | 
|  | while (currentIndex < keySize) { | 
|  | ShaderNode* root = this->createNode(dict, ¤tIndex, arena); | 
|  | if (!root) { | 
|  | return {}; // a bad key | 
|  | } | 
|  | roots.push_back(root); | 
|  | } | 
|  |  | 
|  | // See what expressions we can lift to the vertex shader. | 
|  | const bool hasClipNode = roots.size() > 2; | 
|  | SkSpan<ShaderNode*> liftableNodes(roots.data(), hasClipNode ? 2 : roots.size()); | 
|  | lift_coord_expressions(liftableNodes, &availableVaryings); | 
|  | // Don't lift constant expressions if we're using regular UBOs, since lifting is likely only | 
|  | // beneficial if we're avoiding a storage buffer access. | 
|  | if (caps->storageBufferSupport()) { | 
|  | lift_color_expressions(liftableNodes, &availableVaryings); | 
|  | } | 
|  |  | 
|  | // Copy the accumulated roots into a span stored in the arena | 
|  | const ShaderNode** rootSpan = arena->makeArray<const ShaderNode*>(roots.size()); | 
|  | memcpy(rootSpan, roots.data(), roots.size_bytes()); | 
|  | return SkSpan(rootSpan, roots.size()); | 
|  | } | 
|  |  | 
|  | static int key_to_string(SkString* str, | 
|  | const ShaderCodeDictionary* dict, | 
|  | SkSpan<const uint32_t> keyData, | 
|  | int currentIndex, | 
|  | int indent) { | 
|  | SkASSERT(currentIndex < SkTo<int>(keyData.size())); | 
|  |  | 
|  | const bool multiline = indent >= 0; | 
|  | if (multiline) { | 
|  | // Format for multi-line printing | 
|  | str->appendf("%*c", 2 * indent, ' '); | 
|  | } | 
|  |  | 
|  | uint32_t id = keyData[currentIndex++]; | 
|  | auto entry = dict->getEntry(id); | 
|  | if (!entry) { | 
|  | str->append("UnknownCodeSnippetID:"); | 
|  | str->appendS32(id); | 
|  | str->append(" "); | 
|  | return currentIndex; | 
|  | } | 
|  |  | 
|  | str->append(entry->fName); | 
|  |  | 
|  | if (entry->storesSamplerDescData()) { | 
|  | SkASSERT(currentIndex + 1 < SkTo<int>(keyData.size())); | 
|  |  | 
|  | // If an entry stores data, then the next key value reports the quantity of key indices that | 
|  | // are used to house the data for this snippet. This way, we know how many indices to | 
|  | // iterate over in order to capture the snippet's data before we may encounter another | 
|  | // snippet ID. | 
|  | // For example: | 
|  | // [snippetId using 2 indices worth of data] [2] [dataValue0] [dataValue1] [next snippet ID] | 
|  | const int dataIndexCount = keyData[currentIndex++]; | 
|  | SkASSERT(currentIndex + dataIndexCount < SkTo<int>(keyData.size())); | 
|  |  | 
|  | // We shorten the string for the common case of no extra data. | 
|  | if (dataIndexCount == 0) { | 
|  | str->append("(0)"); | 
|  | } else { | 
|  | str->append("("); | 
|  | str->appendU32(dataIndexCount); | 
|  | str->append(": "); | 
|  | // Encode data in base64 to shorten it | 
|  | const size_t srcDataSize = dataIndexCount * sizeof(uint32_t); // size in bytes | 
|  | SkAutoMalloc encodedData{SkBase64::EncodedSize(srcDataSize)}; | 
|  | char* dst = static_cast<char*>(encodedData.get()); | 
|  | size_t encodedLen = SkBase64::Encode(&keyData[currentIndex], srcDataSize, dst); | 
|  | str->append(dst, encodedLen); | 
|  | str->append(")"); | 
|  | } | 
|  | // Increment current index past the indices which contain data | 
|  | currentIndex += dataIndexCount; | 
|  | } | 
|  |  | 
|  | if (entry->fNumChildren > 0) { | 
|  | if (multiline) { | 
|  | str->append(":\n"); | 
|  | indent++; | 
|  | } else { | 
|  | str->append(" [ "); | 
|  | } | 
|  |  | 
|  | for (int i = 0; i < entry->fNumChildren; ++i) { | 
|  | currentIndex = key_to_string(str, dict, keyData, currentIndex, indent); | 
|  | } | 
|  |  | 
|  | if (!multiline) { | 
|  | str->append("]"); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!multiline) { | 
|  | str->append(" "); | 
|  | } else if (entry->fNumChildren == 0) { | 
|  | str->append("\n"); | 
|  | } | 
|  | return currentIndex; | 
|  | } | 
|  |  | 
|  | SkString PaintParamsKey::toString(const ShaderCodeDictionary* dict) const { | 
|  | SkString str; | 
|  | const int keySize = SkTo<int>(fData.size()); | 
|  | for (int currentIndex = 0; currentIndex < keySize;) { | 
|  | currentIndex = key_to_string(&str, dict, fData, currentIndex, /*indent=*/-1); | 
|  | } | 
|  | return str.isEmpty() ? SkString("(empty)") : str; | 
|  | } | 
|  |  | 
|  | #ifdef SK_DEBUG | 
|  |  | 
|  | void PaintParamsKey::dump(const ShaderCodeDictionary* dict, UniquePaintParamsID id) const { | 
|  | const int keySize = SkTo<int>(fData.size()); | 
|  |  | 
|  | SkDebugf("--------------------------------------\n"); | 
|  | SkDebugf("PaintParamsKey %u (keySize: %d): ", id.asUInt(), keySize); | 
|  | const uint32_t* data = fData.data(); | 
|  | for (int i = 0; i < keySize; ++i) { | 
|  | SkDebugf("%x ", data[i]); | 
|  | } | 
|  | SkDebugf("\n"); | 
|  |  | 
|  | int currentIndex = 0; | 
|  | while (currentIndex < keySize) { | 
|  | SkString nodeStr; | 
|  | currentIndex = key_to_string(&nodeStr, dict, fData, currentIndex, /*indent=*/1); | 
|  | SkDebugf("%s", nodeStr.c_str()); | 
|  | } | 
|  | } | 
|  |  | 
|  | #endif // SK_DEBUG | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // check a single block and, recursively, all its children | 
|  | [[nodiscard]] bool is_block_valid(const ShaderCodeDictionary* dict, | 
|  | SkSpan<const uint32_t> keyData, | 
|  | int* currentIndex) { | 
|  | if (*currentIndex >= SkTo<int>(keyData.size())) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | uint32_t id = keyData[(*currentIndex)++]; | 
|  | if (id >= kBuiltInCodeSnippetIDCount && | 
|  | !SkKnownRuntimeEffects::IsSkiaKnownRuntimeEffect(id) && | 
|  | !dict->isUserDefinedKnownRuntimeEffect(id)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | auto entry = dict->getEntry(id); | 
|  | if (!entry) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (entry->storesSamplerDescData()) { | 
|  | if (*currentIndex >= SkTo<int>(keyData.size())) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | const int dataLength = keyData[(*currentIndex)++]; | 
|  |  | 
|  | if (*currentIndex + dataLength > SkTo<int>(keyData.size())) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | *currentIndex += dataLength; | 
|  | } | 
|  |  | 
|  | if (entry->fNumChildren > 0) { | 
|  | for (int i = 0; i < entry->fNumChildren; ++i) { | 
|  | if (!is_block_valid(dict, keyData, currentIndex)) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | } // anonymous namespace | 
|  |  | 
|  |  | 
|  | bool PaintParamsKey::isSerializable(const ShaderCodeDictionary* dict) const { | 
|  | const int keySize = SkTo<int>(fData.size()); | 
|  |  | 
|  | int currentIndex = 0; | 
|  | while (currentIndex < keySize) { | 
|  | if (!is_block_valid(dict, fData, ¤tIndex)) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | } // namespace skgpu::graphite |