| /* | |
| * 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 |