| /* | |
| * 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 "include/private/SkAttributes.h" | |
| #include "include/private/SkLog.h" | |
| #include "src/core/SkArenaAlloc.h" | |
| #include "src/core/SkAutoMalloc.h" | |
| #include "src/core/SkBase64.h" | |
| #include "src/core/SkStringView.h" | |
| #include "src/gpu/graphite/Caps.h" | |
| #include "src/gpu/graphite/KeyHelpers.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()); | |
| SkASSERT(!fHasError); | |
| } | |
| void PaintParamsKeyBuilder::pushStack(int32_t codeSnippetID) { | |
| SkASSERT(!fLocked); | |
| SkASSERT(fDict->isValidID(codeSnippetID)); | |
| // If the kError ID is pushed, fHasError must have been set already. | |
| SkASSERT(codeSnippetID != (int) BuiltInCodeSnippetID::kError || fHasError); | |
| 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) { | |
| // Check that the size of the embedded data fits our self-imposed limit. | |
| SkASSERT(dataSize <= PaintParamsKey::kEmbeddedDataSizeLimit); | |
| SkASSERT(!fLocked); | |
| 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(!fLocked); | |
| 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(); | |
| } | |
| void PaintParamsKeyBuilder::validateReplacement(int32_t oldCodeSnippetID, | |
| int32_t newCodeSnippetID) { | |
| // Rules for allowed replacements: | |
| // 1. BuiltInCodeSnippetIDs only | |
| // 2. Have the same number of children (and 0 if allowChildren is false) | |
| // 3. Have the same uniform and texture signature | |
| // 4. No extra data | |
| // | |
| // This ensures that replacing on a block by block basis produces a valid ShaderNode tree | |
| // after modification, and that extracted uniforms and textures for the original key can be | |
| // used with the new key. | |
| SkASSERT(oldCodeSnippetID >= 0 && oldCodeSnippetID < kBuiltInCodeSnippetIDCount); | |
| SkASSERT(newCodeSnippetID >= 0 && newCodeSnippetID < kBuiltInCodeSnippetIDCount); | |
| const ShaderSnippet* oldSnippet = fDict->getEntry(oldCodeSnippetID); | |
| const ShaderSnippet* newSnippet = fDict->getEntry(newCodeSnippetID); | |
| SkASSERT(oldSnippet->fNumChildren == newSnippet->fNumChildren); | |
| // Same signature, not caring about the actual variable names. | |
| SkASSERT(oldSnippet->fUniforms.size() == newSnippet->fUniforms.size()); | |
| for (int u = 0; u < oldSnippet->fUniforms.size(); ++u) { | |
| const Uniform& oldU = oldSnippet->fUniforms[u]; | |
| const Uniform& newU = newSnippet->fUniforms[u]; | |
| SkASSERT(oldU.type() == newU.type()); | |
| SkASSERT(oldU.count() == newU.count()); | |
| SkASSERT(oldU.isPaintColor() == newU.isPaintColor()); | |
| } | |
| SkASSERT(oldSnippet->fTexturesAndSamplers.size() == newSnippet->fTexturesAndSamplers.size()); | |
| SkASSERT(!oldSnippet->storesSamplerDescData()); | |
| SkASSERT(!newSnippet->storesSamplerDescData()); | |
| } | |
| #endif // SK_DEBUG | |
| //-------------------------------------------------------------------------------------------------- | |
| // PaintParamsKey | |
| PaintParamsKey PaintParamsKey::clone(SkArenaAlloc* arena) const { | |
| int32_t* newData = arena->makeArrayDefault<int32_t>(fData.size()); | |
| SK_UNSAFE_TODO(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]; | |
| SkASSERT(id >= 0); // Otherwise we're calling createNode on a data length somehow | |
| const ShaderSnippet* entry = dict->getEntry(id); | |
| if (!entry) { | |
| SKIA_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 = EncodeDataSize(fData[storedDataLengthIdx]); | |
| // Building ShaderNodes is assumed to be done for keys built by PaintParamsKeyBuilder or | |
| // already validated for deserialization, so we consider them trusted. | |
| SkASSERT(dataLength >= 0 && dataLength <= kEmbeddedDataSizeLimit); | |
| 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. | |
| // For the embedded data, we re-interpret the values as uint32_t's since that was what was | |
| // written by addData(). | |
| SkSpan<const int32_t> signedDataSpan = fData.subspan(storedDataLengthIdx + 1, dataLength); | |
| dataSpan = {reinterpret_cast<const uint32_t*>(signedDataSpan.data()), 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; | |
| } | |
| SK_UNSAFE_TODO(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 | |
| } | |
| RootNodesInfo PaintParamsKey::getRootNodes(const Caps* caps, | |
| const ShaderCodeDictionary* dict, | |
| const RuntimeEffectDictionary* rteDict, | |
| SkArenaAlloc* arena, | |
| int availableVaryings, | |
| bool canLiftCoords) 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()); | |
| RootNodesInfo rootsInfo; | |
| // Normal PaintParams creation will have up to 4 roots for the different stages. | |
| STArray<4, ShaderNode*> roots; | |
| STArray<2, ShaderNode*> liftableRoots; | |
| int currentIndex = 0; | |
| while (currentIndex < keySize) { | |
| int32_t blockMarker = fData[currentIndex++]; | |
| if (blockMarker >= 0) { | |
| return {}; // a bad key | |
| } | |
| RootBlockType type = static_cast<RootBlockType>(blockMarker); | |
| ShaderNode* root = this->createNode(dict, ¤tIndex, arena); | |
| if (!root) { | |
| return {}; // a bad key | |
| } | |
| roots.push_back(root); | |
| switch (type) { | |
| case RootBlockType::kSrcColor: | |
| SkASSERT(!rootsInfo.fSrcColor); | |
| rootsInfo.fSrcColor = root; | |
| liftableRoots.push_back(root); | |
| break; | |
| case RootBlockType::kFinalBlend: | |
| SkASSERT(!rootsInfo.fFinalBlend); | |
| rootsInfo.fFinalBlend = root; | |
| liftableRoots.push_back(root); | |
| break; | |
| case RootBlockType::kClip: | |
| SkASSERT(!rootsInfo.fClip); | |
| rootsInfo.fClip = root; | |
| break; | |
| case RootBlockType::kMeshShader: | |
| SkASSERT(!rootsInfo.fMeshShader); | |
| rootsInfo.fMeshShader = root; | |
| break; | |
| default: | |
| SkUNREACHABLE; | |
| } | |
| } | |
| if (rootsInfo.fMeshShader) { | |
| rootsInfo.fMeshSpec = rteDict->findMeshSpec(rootsInfo.fMeshShader->codeSnippetId()); | |
| if (!rootsInfo.fMeshSpec) { | |
| // Couldn't find the SkMeshSpecification for the mesh shader snippet so the key is bad. | |
| return {}; | |
| } | |
| } | |
| // See what expressions we can lift to the vertex shader. | |
| if (canLiftCoords) { | |
| lift_coord_expressions(liftableRoots, &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(liftableRoots, &availableVaryings); | |
| } | |
| // Copy the accumulated roots into a span stored in the arena | |
| const ShaderNode** rootSpan = arena->makeArray<const ShaderNode*>(roots.size()); | |
| SK_UNSAFE_TODO(memcpy(rootSpan, roots.data(), roots.size_bytes())); | |
| rootsInfo.fRoots = SkSpan(rootSpan, roots.size()); | |
| return rootsInfo; | |
| } | |
| static void append_as_base64(SkString* str, SkSpan<const int32_t> data) { | |
| str->append("("); | |
| str->appendU32(data.size()); | |
| str->append(": "); | |
| // Encode data in base64 to shorten it | |
| const size_t srcDataSize = data.size() * sizeof(uint32_t); // size in bytes | |
| SkAutoMalloc encodedData{SkBase64::EncodedSize(srcDataSize)}; | |
| char* dst = static_cast<char*>(encodedData.get()); | |
| size_t encodedLen = SkBase64::Encode(data.data(), srcDataSize, dst); | |
| str->append(dst, encodedLen); | |
| str->append(")"); | |
| } | |
| static int key_to_string(const Caps* caps, | |
| SkString* str, | |
| const ShaderCodeDictionary* dict, | |
| SkSpan<const int32_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, ' '); | |
| } | |
| int32_t id = keyData[currentIndex++]; | |
| // Skip the root block headers. | |
| if (id < 0) { | |
| if (multiline) { | |
| switch (static_cast<RootBlockType>(id)) { | |
| case RootBlockType::kSrcColor: | |
| str->append("[RootSrcColor] "); | |
| break; | |
| case RootBlockType::kFinalBlend: | |
| str->append("[RootFinalBlend] "); | |
| break; | |
| case RootBlockType::kClip: | |
| str->append("[RootClip] "); | |
| break; | |
| case RootBlockType::kMeshShader: | |
| str->append("[RootMeshShader] "); | |
| break; | |
| default: | |
| SkUNREACHABLE; | |
| } | |
| } | |
| id = keyData[currentIndex++]; | |
| } | |
| auto entry = dict->getEntry(id); | |
| if (!entry) { | |
| str->append("Unknown("); | |
| str->appendS32(id); | |
| str->append(")"); | |
| return currentIndex; | |
| } | |
| // Single lined Composes get shortened to just a plus between its two children, e.g. Compose [ A | |
| // B ] becomes A+B. We don't do a similar prettification for Blend [ A B C ] to (A B)+C because | |
| // that's not quite as readable and they aren't nearly as common within keys. To make sure | |
| // chains of Composes are not ambiguous, we only consolidate cases where the inner node is not | |
| // Compose, e.g. Compose [ A Compose [ B C ]] => A+B+C but [ Compose [ Compose [ A B ] C ] does | |
| // not become A+B+C | |
| static constexpr int32_t kComposeID = (int32_t) BuiltInCodeSnippetID::kCompose; | |
| SkASSERT(dict->getEntry(kComposeID)->fNumChildren == 2); | |
| const bool prettyCompose = | |
| // single-lined Compose block | |
| id == kComposeID && !multiline && | |
| // that doesn't have an inner Compose child | |
| currentIndex < SkTo<int>(keyData.size()) && keyData[currentIndex] != kComposeID; | |
| if (prettyCompose) { | |
| SkASSERT(entry->fNumChildren == 2); // ordered [inner, outer] | |
| currentIndex = key_to_string(caps, str, dict, keyData, currentIndex, indent); | |
| str->append("+"); | |
| return key_to_string(caps, str, dict, keyData, currentIndex, indent); | |
| } | |
| 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 = PaintParamsKey::EncodeDataSize(keyData[currentIndex++]); | |
| // Printing keys is assumed to be done for keys that were built by PaintParamsKeyBuilder or | |
| // already validated for deserialization, so we consider them trusted. | |
| SkASSERT(dataIndexCount >= 0 && dataIndexCount <= PaintParamsKey::kEmbeddedDataSizeLimit); | |
| SkASSERT(currentIndex + dataIndexCount < SkTo<int>(keyData.size())); | |
| bool descriptiveFormAppended = false; | |
| if (dataIndexCount == 0) { | |
| // We shorten the string for the common case of no extra data. | |
| str->append("(0)"); | |
| descriptiveFormAppended = true; | |
| } else { | |
| SkASSERTF(dataIndexCount == 2 || dataIndexCount == 3, "count %d", dataIndexCount); | |
| // Attempt to append the sampler data as human-readable YCbCr information | |
| SamplerDesc s(keyData[currentIndex], | |
| keyData[currentIndex+1], | |
| /* extFormatMSB= */ dataIndexCount == 3 ? keyData[currentIndex+2] : 0); | |
| if (s.isImmutable()) { | |
| std::string tmp = caps->toString(s.immutableSamplerInfo()); | |
| if (!tmp.empty()) { | |
| str->append("("); | |
| str->append(tmp); | |
| str->append(")"); | |
| descriptiveFormAppended = true; | |
| } | |
| } | |
| } | |
| if (!descriptiveFormAppended) { | |
| append_as_base64(str, { &keyData[currentIndex], dataIndexCount }); | |
| } | |
| // 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) { | |
| if (i > 0) { | |
| str->append(", "); | |
| } | |
| currentIndex = key_to_string(caps, str, dict, keyData, currentIndex, indent); | |
| } | |
| if (!multiline) { | |
| str->append("]"); | |
| } | |
| } | |
| if (multiline && entry->fNumChildren == 0) { | |
| str->append("\n"); | |
| } | |
| return currentIndex; | |
| } | |
| SkString PaintParamsKey::toString(const Caps* caps, | |
| const ShaderCodeDictionary* dict) const { | |
| SkString str; | |
| const int keySize = SkTo<int>(fData.size()); | |
| for (int currentIndex = 0; currentIndex < keySize;) { | |
| currentIndex = key_to_string(caps, &str, dict, fData, currentIndex, /*indent=*/-1); | |
| str.append(" "); | |
| } | |
| return str.isEmpty() ? SkString("(empty)") : str; | |
| } | |
| #ifdef SK_DEBUG | |
| void PaintParamsKey::dump(const Caps* caps, | |
| const ShaderCodeDictionary* dict, | |
| UniquePaintParamsID id) const { | |
| const int keySize = SkTo<int>(fData.size()); | |
| SkDebugf("--------------------------------------\n"); | |
| SkDebugf("PaintParamsKey %u (keySize: %d): ", id.asUInt(), keySize); | |
| const int32_t* data = fData.data(); | |
| for (int i = 0; i < keySize; ++i) { | |
| SkDebugf("%x ", SK_UNSAFE_TODO((uint32_t)data[i])); | |
| } | |
| SkDebugf("\n"); | |
| int currentIndex = 0; | |
| while (currentIndex < keySize) { | |
| SkString nodeStr; | |
| currentIndex = key_to_string(caps, &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 int32_t> keyData, | |
| int* currentIndex) { | |
| if (*currentIndex >= SkTo<int>(keyData.size())) { | |
| return false; | |
| } | |
| int32_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; | |
| } | |
| int dataLength = keyData[(*currentIndex)++]; | |
| // This `dataLength` is untrusted, so check that it doesn't overflow EncodeDataSize | |
| // and that matches expectations of a valid length (i.e. it started out negative and is | |
| // now positive and less than the key data limit). | |
| if (dataLength >= 0 || | |
| dataLength < PaintParamsKey::EncodeDataSize(PaintParamsKey::kEmbeddedDataSizeLimit)) { | |
| // Would not produce a valid size after decoding | |
| return false; | |
| } | |
| dataLength = PaintParamsKey::EncodeDataSize(dataLength); | |
| SkASSERT(dataLength >= 0 && dataLength <= PaintParamsKey::kEmbeddedDataSizeLimit); | |
| 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) { | |
| // Ensure root nodes have their headers set properly, if not the key is malformed. | |
| if (fData[currentIndex++] >= 0) { | |
| return false; | |
| } | |
| if (!is_block_valid(dict, fData, ¤tIndex)) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| } // namespace skgpu::graphite |