| /* |
| * Copyright 2021 Google LLC |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "include/core/SkMesh.h" |
| |
| #ifdef SK_ENABLE_SKSL |
| #include "include/core/SkColorSpace.h" |
| #include "include/core/SkData.h" |
| #include "include/private/SkOpts_spi.h" |
| #include "include/private/SkSLProgramElement.h" |
| #include "include/private/SkSLProgramKind.h" |
| #include "include/private/base/SkMath.h" |
| #include "src/base/SkSafeMath.h" |
| #include "src/core/SkMeshPriv.h" |
| #include "src/core/SkRuntimeEffectPriv.h" |
| #include "src/sksl/SkSLAnalysis.h" |
| #include "src/sksl/SkSLBuiltinTypes.h" |
| #include "src/sksl/SkSLCompiler.h" |
| #include "src/sksl/SkSLProgramSettings.h" |
| #include "src/sksl/SkSLUtil.h" |
| #include "src/sksl/analysis/SkSLProgramVisitor.h" |
| #include "src/sksl/ir/SkSLFieldAccess.h" |
| #include "src/sksl/ir/SkSLFunctionDeclaration.h" |
| #include "src/sksl/ir/SkSLFunctionDefinition.h" |
| #include "src/sksl/ir/SkSLProgram.h" |
| #include "src/sksl/ir/SkSLReturnStatement.h" |
| #include "src/sksl/ir/SkSLStructDefinition.h" |
| #include "src/sksl/ir/SkSLType.h" |
| #include "src/sksl/ir/SkSLVarDeclarations.h" |
| #include "src/sksl/ir/SkSLVariable.h" |
| #include "src/sksl/ir/SkSLVariableReference.h" |
| |
| #if defined(SK_GANESH) |
| #include "src/gpu/ganesh/GrGpu.h" |
| #include "src/gpu/ganesh/GrStagingBufferManager.h" |
| #endif // defined(SK_GANESH) |
| |
| #include <locale> |
| #include <string> |
| #include <tuple> |
| #include <type_traits> |
| #include <utility> |
| |
| using Attribute = SkMeshSpecification::Attribute; |
| using Varying = SkMeshSpecification::Varying; |
| |
| using IndexBuffer = SkMesh::IndexBuffer; |
| using VertexBuffer = SkMesh::VertexBuffer; |
| |
| #define RETURN_FAILURE(...) return Result{nullptr, SkStringPrintf(__VA_ARGS__)} |
| |
| #define RETURN_ERROR(...) return std::make_tuple(false, SkStringPrintf(__VA_ARGS__)) |
| |
| #define RETURN_SUCCESS return std::make_tuple(true, SkString{}) |
| |
| using Uniform = SkMeshSpecification::Uniform; |
| |
| static std::vector<Uniform>::iterator find_uniform(std::vector<Uniform>& uniforms, |
| std::string_view name) { |
| return std::find_if(uniforms.begin(), uniforms.end(), |
| [name](const SkMeshSpecification::Uniform& u) { return u.name == name; }); |
| } |
| |
| static std::tuple<bool, SkString> |
| gather_uniforms_and_check_for_main(const SkSL::Program& program, |
| std::vector<Uniform>* uniforms, |
| SkMeshSpecification::Uniform::Flags stage, |
| size_t* offset) { |
| bool foundMain = false; |
| for (const SkSL::ProgramElement* elem : program.elements()) { |
| if (elem->is<SkSL::FunctionDefinition>()) { |
| const SkSL::FunctionDefinition& defn = elem->as<SkSL::FunctionDefinition>(); |
| const SkSL::FunctionDeclaration& decl = defn.declaration(); |
| if (decl.isMain()) { |
| foundMain = true; |
| } |
| } else if (elem->is<SkSL::GlobalVarDeclaration>()) { |
| const SkSL::GlobalVarDeclaration& global = elem->as<SkSL::GlobalVarDeclaration>(); |
| const SkSL::VarDeclaration& varDecl = global.declaration()->as<SkSL::VarDeclaration>(); |
| const SkSL::Variable& var = *varDecl.var(); |
| if (var.modifiers().fFlags & SkSL::Modifiers::kUniform_Flag) { |
| auto iter = find_uniform(*uniforms, var.name()); |
| const auto& context = *program.fContext; |
| if (iter == uniforms->end()) { |
| uniforms->push_back(SkRuntimeEffectPriv::VarAsUniform(var, context, offset)); |
| uniforms->back().flags |= stage; |
| } else { |
| // Check that the two declarations are equivalent |
| size_t ignoredOffset = 0; |
| auto uniform = SkRuntimeEffectPriv::VarAsUniform(var, context, &ignoredOffset); |
| if (uniform.isArray() != iter->isArray() || |
| uniform.type != iter->type || |
| uniform.count != iter->count) { |
| return {false, SkStringPrintf("Uniform %.*s declared with different types" |
| " in vertex and fragment shaders.", |
| (int)iter->name.size(), iter->name.data())}; |
| } |
| if (uniform.isColor() != iter->isColor()) { |
| return {false, SkStringPrintf("Uniform %.*s declared with different color" |
| " layout in vertex and fragment shaders.", |
| (int)iter->name.size(), iter->name.data())}; |
| } |
| (*iter).flags |= stage; |
| } |
| } |
| } |
| } |
| if (!foundMain) { |
| return {false, SkString("No main function found.")}; |
| } |
| return {true, {}}; |
| } |
| |
| using ColorType = SkMeshSpecificationPriv::ColorType; |
| |
| ColorType get_fs_color_type(const SkSL::Program& fsProgram) { |
| for (const SkSL::ProgramElement* elem : fsProgram.elements()) { |
| if (elem->is<SkSL::FunctionDefinition>()) { |
| const SkSL::FunctionDefinition& defn = elem->as<SkSL::FunctionDefinition>(); |
| const SkSL::FunctionDeclaration& decl = defn.declaration(); |
| if (decl.isMain()) { |
| SkASSERT(decl.parameters().size() == 1 || decl.parameters().size() == 2); |
| if (decl.parameters().size() == 1) { |
| return ColorType::kNone; |
| } |
| const SkSL::Type& paramType = decl.parameters()[1]->type(); |
| SkASSERT(paramType.matches(*fsProgram.fContext->fTypes.fHalf4) || |
| paramType.matches(*fsProgram.fContext->fTypes.fFloat4)); |
| return paramType.matches(*fsProgram.fContext->fTypes.fHalf4) ? ColorType::kHalf4 |
| : ColorType::kFloat4; |
| } |
| } |
| } |
| SkUNREACHABLE; |
| } |
| |
| // This is a non-exhaustive check for the validity of a variable name. The SkSL compiler will |
| // actually process the name. We're just guarding against having multiple tokens embedded in the |
| // name before we put it into a struct definition. |
| static bool check_name(const SkString& name) { |
| if (name.isEmpty()) { |
| return false; |
| } |
| for (size_t i = 0; i < name.size(); ++i) { |
| if (name[i] != '_' && !std::isalnum(name[i], std::locale::classic())) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| static size_t attribute_type_size(Attribute::Type type) { |
| switch (type) { |
| case Attribute::Type::kFloat: return 4; |
| case Attribute::Type::kFloat2: return 2*4; |
| case Attribute::Type::kFloat3: return 3*4; |
| case Attribute::Type::kFloat4: return 4*4; |
| case Attribute::Type::kUByte4_unorm: return 4; |
| } |
| SkUNREACHABLE; |
| } |
| |
| static const char* attribute_type_string(Attribute::Type type) { |
| switch (type) { |
| case Attribute::Type::kFloat: return "float"; |
| case Attribute::Type::kFloat2: return "float2"; |
| case Attribute::Type::kFloat3: return "float3"; |
| case Attribute::Type::kFloat4: return "float4"; |
| case Attribute::Type::kUByte4_unorm: return "half4"; |
| } |
| SkUNREACHABLE; |
| } |
| |
| static const char* varying_type_string(Varying::Type type) { |
| switch (type) { |
| case Varying::Type::kFloat: return "float"; |
| case Varying::Type::kFloat2: return "float2"; |
| case Varying::Type::kFloat3: return "float3"; |
| case Varying::Type::kFloat4: return "float4"; |
| case Varying::Type::kHalf: return "half"; |
| case Varying::Type::kHalf2: return "half2"; |
| case Varying::Type::kHalf3: return "half3"; |
| case Varying::Type::kHalf4: return "half4"; |
| } |
| SkUNREACHABLE; |
| } |
| |
| std::tuple<bool, SkString> |
| check_vertex_offsets_and_stride(SkSpan<const Attribute> attributes, |
| size_t stride) { |
| // Vulkan 1.0 has a minimum maximum attribute count of 2048. |
| static_assert(SkMeshSpecification::kMaxStride <= 2048); |
| // ES 2 has a max of 8. |
| static_assert(SkMeshSpecification::kMaxAttributes <= 8); |
| // Four bytes alignment is required by Metal. |
| static_assert(SkMeshSpecification::kStrideAlignment >= 4); |
| static_assert(SkMeshSpecification::kOffsetAlignment >= 4); |
| // ES2 has a minimum maximum of 8. We may need one for a broken gl_FragCoord workaround and |
| // one for local coords. |
| static_assert(SkMeshSpecification::kMaxVaryings <= 6); |
| |
| if (attributes.empty()) { |
| RETURN_ERROR("At least 1 attribute is required."); |
| } |
| if (attributes.size() > SkMeshSpecification::kMaxAttributes) { |
| RETURN_ERROR("A maximum of %zu attributes is allowed.", |
| SkMeshSpecification::kMaxAttributes); |
| } |
| static_assert(SkIsPow2(SkMeshSpecification::kStrideAlignment)); |
| if (stride == 0 || stride & (SkMeshSpecification::kStrideAlignment - 1)) { |
| RETURN_ERROR("Vertex stride must be a non-zero multiple of %zu.", |
| SkMeshSpecification::kStrideAlignment); |
| } |
| if (stride > SkMeshSpecification::kMaxStride) { |
| RETURN_ERROR("Stride cannot exceed %zu.", SkMeshSpecification::kMaxStride); |
| } |
| for (const auto& a : attributes) { |
| if (a.offset & (SkMeshSpecification::kOffsetAlignment - 1)) { |
| RETURN_ERROR("Attribute offset must be a multiple of %zu.", |
| SkMeshSpecification::kOffsetAlignment); |
| } |
| // This equivalent to vertexAttributeAccessBeyondStride==VK_FALSE in |
| // VK_KHR_portability_subset. First check is to avoid overflow in second check. |
| if (a.offset >= stride || a.offset + attribute_type_size(a.type) > stride) { |
| RETURN_ERROR("Attribute offset plus size cannot exceed stride."); |
| } |
| } |
| RETURN_SUCCESS; |
| } |
| |
| int check_for_passthrough_local_coords_and_dead_varyings(const SkSL::Program& fsProgram, |
| uint32_t* deadVaryingMask) { |
| SkASSERT(deadVaryingMask); |
| |
| using namespace SkSL; |
| static constexpr int kFailed = -2; |
| |
| class Visitor final : public SkSL::ProgramVisitor { |
| public: |
| Visitor(const Context& context) : fContext(context) {} |
| |
| void visit(const Program& program) { ProgramVisitor::visit(program); } |
| |
| int passthroughFieldIndex() const { return fPassthroughFieldIndex; } |
| |
| uint32_t fieldUseMask() const { return fFieldUseMask; } |
| |
| protected: |
| bool visitProgramElement(const ProgramElement& p) override { |
| if (p.is<StructDefinition>()) { |
| const auto& def = p.as<StructDefinition>(); |
| if (def.type().name() == "Varyings") { |
| fVaryingsType = &def.type(); |
| } |
| // No reason to keep looking at this type definition. |
| return false; |
| } |
| if (p.is<FunctionDefinition>() && p.as<FunctionDefinition>().declaration().isMain()) { |
| SkASSERT(!fVaryings); |
| fVaryings = p.as<FunctionDefinition>().declaration().parameters()[0]; |
| |
| SkASSERT(fVaryingsType && fVaryingsType->matches(fVaryings->type())); |
| |
| fInMain = true; |
| bool result = ProgramVisitor::visitProgramElement(p); |
| fInMain = false; |
| return result; |
| } |
| return ProgramVisitor::visitProgramElement(p); |
| } |
| |
| bool visitStatement(const Statement& s) override { |
| if (!fInMain) { |
| return ProgramVisitor::visitStatement(s); |
| } |
| // We should only get here if are in main and therefore found the varyings parameter. |
| SkASSERT(fVaryings); |
| SkASSERT(fVaryingsType); |
| |
| if (fPassthroughFieldIndex == kFailed) { |
| // We've already determined there are return statements that aren't passthrough |
| // or return different fields. |
| return ProgramVisitor::visitStatement(s); |
| } |
| if (!s.is<ReturnStatement>()) { |
| return ProgramVisitor::visitStatement(s); |
| } |
| |
| // We just detect simple cases like "return varyings.foo;" |
| const auto& rs = s.as<ReturnStatement>(); |
| SkASSERT(rs.expression()); |
| if (!rs.expression()->is<FieldAccess>()) { |
| this->passthroughFailed(); |
| return ProgramVisitor::visitStatement(s); |
| } |
| const auto& fa = rs.expression()->as<FieldAccess>(); |
| if (!fa.base()->is<VariableReference>()) { |
| this->passthroughFailed(); |
| return ProgramVisitor::visitStatement(s); |
| } |
| const auto& baseRef = fa.base()->as<VariableReference>(); |
| if (baseRef.variable() != fVaryings) { |
| this->passthroughFailed(); |
| return ProgramVisitor::visitStatement(s); |
| } |
| if (fPassthroughFieldIndex >= 0) { |
| // We already found an OK return statement. Check if this one returns the same |
| // field. |
| if (fa.fieldIndex() != fPassthroughFieldIndex) { |
| this->passthroughFailed(); |
| return ProgramVisitor::visitStatement(s); |
| } |
| // We don't call our base class here because we don't want to hit visitExpression |
| // and mark the returned field as used. |
| return false; |
| } |
| const Type::Field& field = fVaryings->type().fields()[fa.fieldIndex()]; |
| if (!field.fType->matches(*fContext.fTypes.fFloat2)) { |
| this->passthroughFailed(); |
| return ProgramVisitor::visitStatement(s); |
| } |
| fPassthroughFieldIndex = fa.fieldIndex(); |
| // We don't call our base class here because we don't want to hit visitExpression and |
| // mark the returned field as used. |
| return false; |
| } |
| |
| bool visitExpression(const Expression& e) override { |
| // Anything before the Varyings struct is defined doesn't matter. |
| if (!fVaryingsType) { |
| return false; |
| } |
| if (!e.is<FieldAccess>()) { |
| return ProgramVisitor::visitExpression(e); |
| } |
| const auto& fa = e.as<FieldAccess>(); |
| if (!fa.base()->type().matches(*fVaryingsType)) { |
| return ProgramVisitor::visitExpression(e); |
| } |
| fFieldUseMask |= 1 << fa.fieldIndex(); |
| return false; |
| } |
| |
| private: |
| void passthroughFailed() { |
| if (fPassthroughFieldIndex >= 0) { |
| fFieldUseMask |= 1 << fPassthroughFieldIndex; |
| } |
| fPassthroughFieldIndex = kFailed; |
| } |
| |
| const Context& fContext; |
| const Type* fVaryingsType = nullptr; |
| const Variable* fVaryings = nullptr; |
| int fPassthroughFieldIndex = -1; |
| bool fInMain = false; |
| uint32_t fFieldUseMask = 0; |
| }; |
| |
| Visitor v(*fsProgram.fContext); |
| v.visit(fsProgram); |
| *deadVaryingMask = ~v.fieldUseMask(); |
| return v.passthroughFieldIndex(); |
| } |
| |
| SkMeshSpecification::Result SkMeshSpecification::Make(SkSpan<const Attribute> attributes, |
| size_t vertexStride, |
| SkSpan<const Varying> varyings, |
| const SkString& vs, |
| const SkString& fs) { |
| return Make(attributes, |
| vertexStride, |
| varyings, |
| vs, |
| fs, |
| SkColorSpace::MakeSRGB(), |
| kPremul_SkAlphaType); |
| } |
| |
| SkMeshSpecification::Result SkMeshSpecification::Make(SkSpan<const Attribute> attributes, |
| size_t vertexStride, |
| SkSpan<const Varying> varyings, |
| const SkString& vs, |
| const SkString& fs, |
| sk_sp<SkColorSpace> cs) { |
| return Make(attributes, vertexStride, varyings, vs, fs, std::move(cs), kPremul_SkAlphaType); |
| } |
| |
| SkMeshSpecification::Result SkMeshSpecification::Make(SkSpan<const Attribute> attributes, |
| size_t vertexStride, |
| SkSpan<const Varying> varyings, |
| const SkString& vs, |
| const SkString& fs, |
| sk_sp<SkColorSpace> cs, |
| SkAlphaType at) { |
| SkString attributesStruct("struct Attributes {\n"); |
| for (const auto& a : attributes) { |
| attributesStruct.appendf(" %s %s;\n", attribute_type_string(a.type), a.name.c_str()); |
| } |
| attributesStruct.append("};\n"); |
| |
| bool userProvidedPositionVarying = false; |
| for (const auto& v : varyings) { |
| if (v.name.equals("position")) { |
| if (v.type != Varying::Type::kFloat2) { |
| return {nullptr, SkString("Varying \"position\" must have type float2.")}; |
| } |
| userProvidedPositionVarying = true; |
| } |
| } |
| |
| SkSTArray<kMaxVaryings, Varying> tempVaryings; |
| if (!userProvidedPositionVarying) { |
| // Even though we check the # of varyings in MakeFromSourceWithStructs we check here, too, |
| // to avoid overflow with + 1. |
| if (varyings.size() > kMaxVaryings - 1) { |
| RETURN_FAILURE("A maximum of %zu varyings is allowed.", kMaxVaryings); |
| } |
| for (const auto& v : varyings) { |
| tempVaryings.push_back(v); |
| } |
| tempVaryings.push_back(Varying{Varying::Type::kFloat2, SkString("position")}); |
| varyings = tempVaryings; |
| } |
| |
| SkString varyingStruct("struct Varyings {\n"); |
| for (const auto& v : varyings) { |
| varyingStruct.appendf(" %s %s;\n", varying_type_string(v.type), v.name.c_str()); |
| } |
| varyingStruct.append("};\n"); |
| |
| SkString fullVS; |
| fullVS.append(varyingStruct.c_str()); |
| fullVS.append(attributesStruct.c_str()); |
| fullVS.append(vs.c_str()); |
| |
| SkString fullFS; |
| fullFS.append(varyingStruct.c_str()); |
| fullFS.append(fs.c_str()); |
| |
| return MakeFromSourceWithStructs(attributes, |
| vertexStride, |
| varyings, |
| fullVS, |
| fullFS, |
| std::move(cs), |
| at); |
| } |
| |
| SkMeshSpecification::Result SkMeshSpecification::MakeFromSourceWithStructs( |
| SkSpan<const Attribute> attributes, |
| size_t stride, |
| SkSpan<const Varying> varyings, |
| const SkString& vs, |
| const SkString& fs, |
| sk_sp<SkColorSpace> cs, |
| SkAlphaType at) { |
| if (auto [ok, error] = check_vertex_offsets_and_stride(attributes, stride); !ok) { |
| return {nullptr, error}; |
| } |
| |
| for (const auto& a : attributes) { |
| if (!check_name(a.name)) { |
| RETURN_FAILURE("\"%s\" is not a valid attribute name.", a.name.c_str()); |
| } |
| } |
| |
| if (varyings.size() > kMaxVaryings) { |
| RETURN_FAILURE("A maximum of %zu varyings is allowed.", kMaxVaryings); |
| } |
| |
| for (const auto& v : varyings) { |
| if (!check_name(v.name)) { |
| return {nullptr, SkStringPrintf("\"%s\" is not a valid varying name.", v.name.c_str())}; |
| } |
| } |
| |
| std::vector<Uniform> uniforms; |
| size_t offset = 0; |
| |
| SkSL::Compiler compiler(SkSL::ShaderCapsFactory::Standalone()); |
| |
| // Disable memory pooling; this might slow down compilation slightly, but it will ensure that a |
| // long-lived mesh specification doesn't waste memory. |
| SkSL::ProgramSettings settings; |
| settings.fUseMemoryPool = false; |
| |
| // TODO(skia:11209): Add SkCapabilities to the API, check against required version. |
| std::unique_ptr<SkSL::Program> vsProgram = compiler.convertProgram( |
| SkSL::ProgramKind::kMeshVertex, |
| std::string(vs.c_str()), |
| settings); |
| if (!vsProgram) { |
| RETURN_FAILURE("VS: %s", compiler.errorText().c_str()); |
| } |
| |
| if (auto [result, error] = gather_uniforms_and_check_for_main( |
| *vsProgram, |
| &uniforms, |
| SkMeshSpecification::Uniform::Flags::kVertex_Flag, |
| &offset); |
| !result) { |
| return {nullptr, std::move(error)}; |
| } |
| |
| if (SkSL::Analysis::CallsColorTransformIntrinsics(*vsProgram)) { |
| RETURN_FAILURE("Color transform intrinsics are not permitted in custom mesh shaders"); |
| } |
| |
| std::unique_ptr<SkSL::Program> fsProgram = compiler.convertProgram( |
| SkSL::ProgramKind::kMeshFragment, |
| std::string(fs.c_str()), |
| settings); |
| |
| if (!fsProgram) { |
| RETURN_FAILURE("FS: %s", compiler.errorText().c_str()); |
| } |
| |
| if (auto [result, error] = gather_uniforms_and_check_for_main( |
| *fsProgram, |
| &uniforms, |
| SkMeshSpecification::Uniform::Flags::kFragment_Flag, |
| &offset); |
| !result) { |
| return {nullptr, std::move(error)}; |
| } |
| |
| if (SkSL::Analysis::CallsColorTransformIntrinsics(*fsProgram)) { |
| RETURN_FAILURE("Color transform intrinsics are not permitted in custom mesh shaders"); |
| } |
| |
| ColorType ct = get_fs_color_type(*fsProgram); |
| |
| if (ct == ColorType::kNone) { |
| cs = nullptr; |
| at = kPremul_SkAlphaType; |
| } else { |
| if (!cs) { |
| return {nullptr, SkString{"Must provide a color space if FS returns a color."}}; |
| } |
| if (at == kUnknown_SkAlphaType) { |
| return {nullptr, SkString{"Must provide a valid alpha type if FS returns a color."}}; |
| } |
| } |
| |
| uint32_t deadVaryingMask; |
| int passthroughLocalCoordsVaryingIndex = |
| check_for_passthrough_local_coords_and_dead_varyings(*fsProgram, &deadVaryingMask); |
| |
| if (passthroughLocalCoordsVaryingIndex >= 0) { |
| SkASSERT(varyings[passthroughLocalCoordsVaryingIndex].type == Varying::Type::kFloat2); |
| } |
| |
| return {sk_sp<SkMeshSpecification>(new SkMeshSpecification(attributes, |
| stride, |
| varyings, |
| passthroughLocalCoordsVaryingIndex, |
| deadVaryingMask, |
| std::move(uniforms), |
| std::move(vsProgram), |
| std::move(fsProgram), |
| ct, |
| std::move(cs), |
| at)), |
| /*error=*/{}}; |
| } |
| |
| SkMeshSpecification::~SkMeshSpecification() = default; |
| |
| SkMeshSpecification::SkMeshSpecification( |
| SkSpan<const Attribute> attributes, |
| size_t stride, |
| SkSpan<const Varying> varyings, |
| int passthroughLocalCoordsVaryingIndex, |
| uint32_t deadVaryingMask, |
| std::vector<Uniform> uniforms, |
| std::unique_ptr<const SkSL::Program> vs, |
| std::unique_ptr<const SkSL::Program> fs, |
| ColorType ct, |
| sk_sp<SkColorSpace> cs, |
| SkAlphaType at) |
| : fAttributes(attributes.begin(), attributes.end()) |
| , fVaryings(varyings.begin(), varyings.end()) |
| , fUniforms(std::move(uniforms)) |
| , fVS(std::move(vs)) |
| , fFS(std::move(fs)) |
| , fStride(stride) |
| , fPassthroughLocalCoordsVaryingIndex(passthroughLocalCoordsVaryingIndex) |
| , fDeadVaryingMask(deadVaryingMask) |
| , fColorType(ct) |
| , fColorSpace(std::move(cs)) |
| , fAlphaType(at) { |
| fHash = SkOpts::hash_fn(fVS->fSource->c_str(), fVS->fSource->size(), 0); |
| fHash = SkOpts::hash_fn(fFS->fSource->c_str(), fFS->fSource->size(), fHash); |
| |
| // The attributes and varyings SkSL struct declarations are included in the program source. |
| // However, the attribute offsets and types need to be included, the latter because the SkSL |
| // struct definition has the GPU type but not the CPU data format. |
| for (const auto& a : fAttributes) { |
| fHash = SkOpts::hash_fn(&a.offset, sizeof(a.offset), fHash); |
| fHash = SkOpts::hash_fn(&a.type, sizeof(a.type), fHash); |
| } |
| |
| fHash = SkOpts::hash_fn(&stride, sizeof(stride), fHash); |
| |
| uint64_t csHash = fColorSpace ? fColorSpace->hash() : 0; |
| fHash = SkOpts::hash_fn(&csHash, sizeof(csHash), fHash); |
| |
| auto atInt = static_cast<uint32_t>(fAlphaType); |
| fHash = SkOpts::hash_fn(&atInt, sizeof(atInt), fHash); |
| } |
| |
| size_t SkMeshSpecification::uniformSize() const { |
| return fUniforms.empty() ? 0 |
| : SkAlign4(fUniforms.back().offset + fUniforms.back().sizeInBytes()); |
| } |
| |
| const Uniform* SkMeshSpecification::findUniform(std::string_view name) const { |
| auto iter = std::find_if(fUniforms.begin(), fUniforms.end(), [name] (const Uniform& u) { |
| return u.name == name; |
| }); |
| return iter == fUniforms.end() ? nullptr : &(*iter); |
| } |
| |
| const Attribute* SkMeshSpecification::findAttribute(std::string_view name) const { |
| auto iter = std::find_if(fAttributes.begin(), fAttributes.end(), [name](const Attribute& a) { |
| return name.compare(a.name.c_str()) == 0; |
| }); |
| return iter == fAttributes.end() ? nullptr : &(*iter); |
| } |
| |
| const Varying* SkMeshSpecification::findVarying(std::string_view name) const { |
| auto iter = std::find_if(fVaryings.begin(), fVaryings.end(), [name](const Varying& v) { |
| return name.compare(v.name.c_str()) == 0; |
| }); |
| return iter == fVaryings.end() ? nullptr : &(*iter); |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| SkMesh::SkMesh() = default; |
| SkMesh::~SkMesh() = default; |
| |
| SkMesh::SkMesh(const SkMesh&) = default; |
| SkMesh::SkMesh(SkMesh&&) = default; |
| |
| SkMesh& SkMesh::operator=(const SkMesh&) = default; |
| SkMesh& SkMesh::operator=(SkMesh&&) = default; |
| |
| sk_sp<IndexBuffer> SkMesh::MakeIndexBuffer(GrDirectContext* dc, const void* data, size_t size) { |
| if (!dc) { |
| return SkMeshPriv::CpuIndexBuffer::Make(data, size); |
| } |
| #if defined(SK_GANESH) |
| return SkMeshPriv::GpuIndexBuffer::Make(dc, data, size); |
| #else |
| return nullptr; |
| #endif |
| } |
| |
| sk_sp<IndexBuffer> SkMesh::CopyIndexBuffer(GrDirectContext* dc, sk_sp<IndexBuffer> src) { |
| if (!src) { |
| return nullptr; |
| } |
| auto* ib = static_cast<SkMeshPriv::IB*>(src.get()); |
| const void* data = ib->peek(); |
| if (!data) { |
| return nullptr; |
| } |
| return MakeIndexBuffer(dc, data, ib->size()); |
| } |
| |
| sk_sp<VertexBuffer> SkMesh::MakeVertexBuffer(GrDirectContext* dc, const void* data, size_t size) { |
| if (!dc) { |
| return SkMeshPriv::CpuVertexBuffer::Make(data, size); |
| } |
| #if defined(SK_GANESH) |
| return SkMeshPriv::GpuVertexBuffer::Make(dc, data, size); |
| #else |
| return nullptr; |
| #endif |
| } |
| |
| sk_sp<VertexBuffer> SkMesh::CopyVertexBuffer(GrDirectContext* dc, sk_sp<VertexBuffer> src) { |
| if (!src) { |
| return nullptr; |
| } |
| auto* vb = static_cast<SkMeshPriv::VB*>(src.get()); |
| const void* data = vb->peek(); |
| if (!data) { |
| return nullptr; |
| } |
| return MakeVertexBuffer(dc, data, vb->size()); |
| } |
| |
| SkMesh::Result SkMesh::Make(sk_sp<SkMeshSpecification> spec, |
| Mode mode, |
| sk_sp<VertexBuffer> vb, |
| size_t vertexCount, |
| size_t vertexOffset, |
| sk_sp<const SkData> uniforms, |
| const SkRect& bounds) { |
| SkMesh mesh; |
| mesh.fSpec = std::move(spec); |
| mesh.fMode = mode; |
| mesh.fVB = std::move(vb); |
| mesh.fUniforms = std::move(uniforms); |
| mesh.fVCount = vertexCount; |
| mesh.fVOffset = vertexOffset; |
| mesh.fBounds = bounds; |
| auto [valid, msg] = mesh.validate(); |
| if (!valid) { |
| mesh = {}; |
| } |
| return {std::move(mesh), std::move(msg)}; |
| } |
| |
| SkMesh::Result SkMesh::MakeIndexed(sk_sp<SkMeshSpecification> spec, |
| Mode mode, |
| sk_sp<VertexBuffer> vb, |
| size_t vertexCount, |
| size_t vertexOffset, |
| sk_sp<IndexBuffer> ib, |
| size_t indexCount, |
| size_t indexOffset, |
| sk_sp<const SkData> uniforms, |
| const SkRect& bounds) { |
| if (!ib) { |
| // We check this before calling validate to disambiguate from a non-indexed mesh where |
| // IB is expected to be null. |
| return {{}, SkString{"An index buffer is required."}}; |
| } |
| SkMesh mesh; |
| mesh.fSpec = std::move(spec); |
| mesh.fMode = mode; |
| mesh.fVB = std::move(vb); |
| mesh.fVCount = vertexCount; |
| mesh.fVOffset = vertexOffset; |
| mesh.fIB = std::move(ib); |
| mesh.fUniforms = std::move(uniforms); |
| mesh.fICount = indexCount; |
| mesh.fIOffset = indexOffset; |
| mesh.fBounds = bounds; |
| auto [valid, msg] = mesh.validate(); |
| if (!valid) { |
| mesh = {}; |
| } |
| return {std::move(mesh), std::move(msg)}; |
| } |
| |
| bool SkMesh::isValid() const { |
| bool valid = SkToBool(fSpec); |
| SkASSERT(valid == std::get<0>(this->validate())); |
| return valid; |
| } |
| |
| static size_t min_vcount_for_mode(SkMesh::Mode mode) { |
| switch (mode) { |
| case SkMesh::Mode::kTriangles: return 3; |
| case SkMesh::Mode::kTriangleStrip: return 3; |
| } |
| SkUNREACHABLE; |
| } |
| |
| std::tuple<bool, SkString> SkMesh::validate() const { |
| #define FAIL_MESH_VALIDATE(...) return std::make_tuple(false, SkStringPrintf(__VA_ARGS__)) |
| if (!fSpec) { |
| FAIL_MESH_VALIDATE("SkMeshSpecification is required."); |
| } |
| |
| if (!fVB) { |
| FAIL_MESH_VALIDATE("A vertex buffer is required."); |
| } |
| |
| auto vb = static_cast<SkMeshPriv::VB*>(fVB.get()); |
| auto ib = static_cast<SkMeshPriv::IB*>(fIB.get()); |
| |
| SkSafeMath sm; |
| size_t vsize = sm.mul(fSpec->stride(), fVCount); |
| if (sm.add(vsize, fVOffset) > vb->size()) { |
| FAIL_MESH_VALIDATE("The vertex buffer offset and vertex count reads beyond the end of the" |
| " vertex buffer."); |
| } |
| |
| if (fVOffset%fSpec->stride() != 0) { |
| FAIL_MESH_VALIDATE("The vertex offset (%zu) must be a multiple of the vertex stride (%zu).", |
| fVOffset, |
| fSpec->stride()); |
| } |
| |
| if (size_t uniformSize = fSpec->uniformSize()) { |
| if (!fUniforms || fUniforms->size() < uniformSize) { |
| FAIL_MESH_VALIDATE("The uniform data is %zu bytes but must be at least %zu.", |
| fUniforms->size(), |
| uniformSize); |
| } |
| } |
| |
| auto modeToStr = [](Mode m) { |
| switch (m) { |
| case Mode::kTriangles: return "triangles"; |
| case Mode::kTriangleStrip: return "triangle-strip"; |
| } |
| SkUNREACHABLE; |
| }; |
| if (ib) { |
| if (fICount < min_vcount_for_mode(fMode)) { |
| FAIL_MESH_VALIDATE("%s mode requires at least %zu indices but index count is %zu.", |
| modeToStr(fMode), |
| min_vcount_for_mode(fMode), |
| fICount); |
| } |
| size_t isize = sm.mul(sizeof(uint16_t), fICount); |
| if (sm.add(isize, fIOffset) > ib->size()) { |
| FAIL_MESH_VALIDATE("The index buffer offset and index count reads beyond the end of the" |
| " index buffer."); |
| |
| } |
| // If we allow 32 bit indices then this should enforce 4 byte alignment in that case. |
| if (!SkIsAlign2(fIOffset)) { |
| FAIL_MESH_VALIDATE("The index offset must be a multiple of 2."); |
| } |
| } else { |
| if (fVCount < min_vcount_for_mode(fMode)) { |
| FAIL_MESH_VALIDATE("%s mode requires at least %zu vertices but vertex count is %zu.", |
| modeToStr(fMode), |
| min_vcount_for_mode(fMode), |
| fICount); |
| } |
| SkASSERT(!fICount); |
| SkASSERT(!fIOffset); |
| } |
| |
| if (!sm.ok()) { |
| FAIL_MESH_VALIDATE("Overflow"); |
| } |
| #undef FAIL_MESH_VALIDATE |
| return {true, {}}; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| static inline bool check_update(const void* data, size_t offset, size_t size, size_t bufferSize) { |
| SkSafeMath sm; |
| return data && |
| size && |
| SkIsAlign4(offset) && |
| SkIsAlign4(size) && |
| sm.add(offset, size) <= bufferSize && |
| sm.ok(); |
| } |
| |
| bool SkMesh::IndexBuffer::update(GrDirectContext* dc, |
| const void* data, |
| size_t offset, |
| size_t size) { |
| return check_update(data, offset, size, this->size()) && this->onUpdate(dc, data, offset, size); |
| } |
| |
| bool SkMesh::VertexBuffer::update(GrDirectContext* dc, |
| const void* data, |
| size_t offset, |
| size_t size) { |
| return check_update(data, offset, size, this->size()) && this->onUpdate(dc, data, offset, size); |
| } |
| |
| #if defined(SK_GANESH) |
| bool SkMeshPriv::UpdateGpuBuffer(GrDirectContext* dc, |
| sk_sp<GrGpuBuffer> buffer, |
| const void* data, |
| size_t offset, |
| size_t size) { |
| if (!dc || dc != buffer->getContext()) { |
| return false; |
| } |
| SkASSERT(!dc->abandoned()); // If dc is abandoned then buffer->getContext() should be null. |
| |
| if (!dc->priv().caps()->transferFromBufferToBufferSupport()) { |
| auto ownedData = SkData::MakeWithCopy(data, size); |
| dc->priv().drawingManager()->newBufferUpdateTask(std::move(ownedData), |
| std::move(buffer), |
| offset); |
| return true; |
| } |
| |
| sk_sp<GrGpuBuffer> tempBuffer; |
| size_t tempOffset = 0; |
| if (auto* sbm = dc->priv().getGpu()->stagingBufferManager()) { |
| auto alignment = dc->priv().caps()->transferFromBufferToBufferAlignment(); |
| auto [sliceBuffer, sliceOffset, ptr] = sbm->allocateStagingBufferSlice(size, alignment); |
| if (sliceBuffer) { |
| std::memcpy(ptr, data, size); |
| tempBuffer.reset(SkRef(sliceBuffer)); |
| tempOffset = sliceOffset; |
| } |
| } |
| |
| if (!tempBuffer) { |
| tempBuffer = dc->priv().resourceProvider()->createBuffer(size, |
| GrGpuBufferType::kXferCpuToGpu, |
| kDynamic_GrAccessPattern, |
| GrResourceProvider::ZeroInit::kNo); |
| if (!tempBuffer) { |
| return false; |
| } |
| if (!tempBuffer->updateData(data, 0, size, /*preserve=*/false)) { |
| return false; |
| } |
| } |
| |
| dc->priv().drawingManager()->newBufferTransferTask(std::move(tempBuffer), |
| tempOffset, |
| std::move(buffer), |
| offset, |
| size); |
| |
| return true; |
| } |
| #endif // defined(SK_GANESH) |
| |
| #endif // SK_ENABLE_SKSL |