| /* | |
| * 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 "src/gpu/ganesh/ops/DrawMeshOp.h" | |
| #include "include/core/SkColor.h" | |
| #include "include/core/SkData.h" | |
| #include "include/core/SkMatrix.h" | |
| #include "include/core/SkMesh.h" | |
| #include "include/core/SkString.h" | |
| #include "include/core/SkVertices.h" | |
| #include "include/effects/SkRuntimeEffect.h" | |
| #include "include/private/base/SkAssert.h" | |
| #include "include/private/base/SkDebug.h" | |
| #include "include/private/base/SkPoint_impl.h" | |
| #include "include/private/base/SkSpan_impl.h" | |
| #include "include/private/base/SkTemplates.h" | |
| #include "include/private/base/SkTo.h" | |
| #include "include/private/base/SkTypeTraits.h" | |
| #include "include/private/gpu/ganesh/GrTypesPriv.h" | |
| #include "src/base/SkArenaAlloc.h" | |
| #include "src/core/SkColorData.h" | |
| #include "src/core/SkMeshPriv.h" | |
| #include "src/core/SkRuntimeEffectPriv.h" | |
| #include "src/core/SkSLTypeShared.h" | |
| #include "src/core/SkVerticesPriv.h" | |
| #include "src/gpu/BufferWriter.h" | |
| #include "src/gpu/KeyBuilder.h" | |
| #include "src/gpu/ganesh/GrAppliedClip.h" | |
| #include "src/gpu/ganesh/GrBuffer.h" | |
| #include "src/gpu/ganesh/GrCaps.h" | |
| #include "src/gpu/ganesh/GrColorSpaceXform.h" | |
| #include "src/gpu/ganesh/GrFragmentProcessor.h" | |
| #include "src/gpu/ganesh/GrGeometryProcessor.h" | |
| #include "src/gpu/ganesh/GrMeshBuffers.h" | |
| #include "src/gpu/ganesh/GrMeshDrawTarget.h" | |
| #include "src/gpu/ganesh/GrOpFlushState.h" | |
| #include "src/gpu/ganesh/GrPaint.h" | |
| #include "src/gpu/ganesh/GrProcessorAnalysis.h" | |
| #include "src/gpu/ganesh/GrProcessorSet.h" | |
| #include "src/gpu/ganesh/GrProgramInfo.h" | |
| #include "src/gpu/ganesh/GrShaderVar.h" | |
| #include "src/gpu/ganesh/GrSimpleMesh.h" | |
| #include "src/gpu/ganesh/GrSurfaceProxy.h" | |
| #include "src/gpu/ganesh/GrSurfaceProxyView.h" | |
| #include "src/gpu/ganesh/effects/GrTextureEffect.h" | |
| #include "src/gpu/ganesh/glsl/GrGLSLColorSpaceXformHelper.h" | |
| #include "src/gpu/ganesh/glsl/GrGLSLFragmentShaderBuilder.h" | |
| #include "src/gpu/ganesh/glsl/GrGLSLProgramBuilder.h" | |
| #include "src/gpu/ganesh/glsl/GrGLSLProgramDataManager.h" | |
| #include "src/gpu/ganesh/glsl/GrGLSLShaderBuilder.h" | |
| #include "src/gpu/ganesh/glsl/GrGLSLUniformHandler.h" | |
| #include "src/gpu/ganesh/glsl/GrGLSLVarying.h" | |
| #include "src/gpu/ganesh/glsl/GrGLSLVertexGeoBuilder.h" | |
| #include "src/gpu/ganesh/ops/GrDrawOp.h" | |
| #include "src/gpu/ganesh/ops/GrMeshDrawOp.h" | |
| #include "src/gpu/ganesh/ops/GrSimpleMeshDrawOpHelper.h" | |
| #include "src/sksl/SkSLString.h" | |
| #include "src/sksl/SkSLUtil.h" | |
| #include "src/sksl/codegen/SkSLPipelineStageCodeGenerator.h" | |
| #include "src/sksl/ir/SkSLProgram.h" | |
| #include "src/sksl/ir/SkSLType.h" | |
| #include "src/sksl/ir/SkSLVarDeclarations.h" | |
| #include "src/sksl/ir/SkSLVariable.h" | |
| #include <algorithm> | |
| #include <climits> | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <functional> | |
| #include <iterator> | |
| #include <new> | |
| #include <optional> | |
| #include <string> | |
| #include <string_view> | |
| #include <tuple> | |
| #include <utility> | |
| #include <vector> | |
| class GrDstProxyView; | |
| class GrGpuBuffer; | |
| enum class GrXferBarrierFlags; | |
| namespace SkSL { | |
| class Context; | |
| } | |
| struct GrShaderCaps; | |
| struct SkRect; | |
| using namespace skia_private; | |
| namespace { | |
| GrPrimitiveType primitive_type(SkMesh::Mode mode) { | |
| switch (mode) { | |
| case SkMesh::Mode::kTriangles: return GrPrimitiveType::kTriangles; | |
| case SkMesh::Mode::kTriangleStrip: return GrPrimitiveType::kTriangleStrip; | |
| } | |
| SkUNREACHABLE; | |
| } | |
| using MeshAttributeType = SkMeshSpecification::Attribute::Type; | |
| GrVertexAttribType attrib_type(MeshAttributeType type) { | |
| switch (type) { | |
| case MeshAttributeType::kFloat: return kFloat_GrVertexAttribType; | |
| case MeshAttributeType::kFloat2: return kFloat2_GrVertexAttribType; | |
| case MeshAttributeType::kFloat3: return kFloat3_GrVertexAttribType; | |
| case MeshAttributeType::kFloat4: return kFloat4_GrVertexAttribType; | |
| case MeshAttributeType::kUByte4_unorm: return kUByte4_norm_GrVertexAttribType; | |
| } | |
| SkUNREACHABLE; | |
| } | |
| class MeshGP : public GrGeometryProcessor { | |
| private: | |
| using ChildPtr = SkRuntimeEffect::ChildPtr; | |
| public: | |
| static GrGeometryProcessor* Make( | |
| SkArenaAlloc* arena, | |
| sk_sp<SkMeshSpecification> spec, | |
| sk_sp<GrColorSpaceXform> colorSpaceXform, | |
| const SkMatrix& viewMatrix, | |
| const std::optional<SkPMColor4f>& color, | |
| bool needsLocalCoords, | |
| sk_sp<const SkData> uniforms, | |
| SkSpan<std::unique_ptr<GrFragmentProcessor>> children) { | |
| return arena->make([&](void* ptr) { | |
| return new (ptr) MeshGP(std::move(spec), | |
| std::move(colorSpaceXform), | |
| viewMatrix, | |
| std::move(color), | |
| needsLocalCoords, | |
| std::move(uniforms), | |
| children); | |
| }); | |
| } | |
| const char* name() const override { return "MeshGP"; } | |
| void addToKey(const GrShaderCaps& caps, skgpu::KeyBuilder* b) const override { | |
| b->add32(SkMeshSpecificationPriv::Hash(*fSpec), "custom mesh spec hash"); | |
| b->add32(ProgramImpl::ComputeMatrixKey(caps, fViewMatrix), "view matrix key"); | |
| if (SkMeshSpecificationPriv::GetColorType(*fSpec) != | |
| SkMeshSpecificationPriv::ColorType::kNone) { | |
| b->add32(GrColorSpaceXform::XformKey(fColorSpaceXform.get()), "colorspace xform key"); | |
| } | |
| for (const std::unique_ptr<GrFragmentProcessor>& fp : fChildren) { | |
| if (fp) { | |
| fp->addToKey(caps, b); | |
| } else { | |
| b->addBool(false, "null effect"); | |
| } | |
| } | |
| } | |
| std::unique_ptr<ProgramImpl> makeProgramImpl(const GrShaderCaps&) const override { | |
| return std::make_unique<Impl>(); | |
| } | |
| private: | |
| class Impl : public ProgramImpl { | |
| public: | |
| void setData(const GrGLSLProgramDataManager& pdman, | |
| const GrShaderCaps& shaderCaps, | |
| const GrGeometryProcessor& geomProc) override { | |
| const auto& mgp = geomProc.cast<MeshGP>(); | |
| SetTransform(pdman, shaderCaps, fViewMatrixUniform, mgp.fViewMatrix, &fViewMatrix); | |
| // Set up uniforms for the color-space transform. | |
| fColorSpaceHelper.setData(pdman, mgp.fColorSpaceXform.get()); | |
| // Assign the paint color to a uniform. | |
| if (fColorUniform.isValid()) { | |
| pdman.set4fv(fColorUniform, 1, mgp.fColor.vec()); | |
| } | |
| // Update uniforms associated with the mesh vertex/fragment program. | |
| if (mgp.fUniforms) { | |
| pdman.setRuntimeEffectUniforms(mgp.fSpec->uniforms(), | |
| SkSpan(fSpecUniformHandles), | |
| mgp.fUniforms->data()); | |
| } | |
| // Recursively update uniforms associated with the mesh child FPs. | |
| for (size_t index = 0; index < mgp.fChildren.size(); ++index) { | |
| if (const GrFragmentProcessor* fp = mgp.fChildren[index].get()) { | |
| GrFragmentProcessor::ProgramImpl* impl = fChildImpls[index].get(); | |
| SkASSERT(impl); | |
| fp->visitWithImpls([&](const GrFragmentProcessor& fp, | |
| GrFragmentProcessor::ProgramImpl& impl) { | |
| impl.setData(pdman, fp); | |
| }, | |
| *impl); | |
| } | |
| } | |
| } | |
| private: | |
| class MeshCallbacks : public SkSL::PipelineStage::Callbacks { | |
| public: | |
| MeshCallbacks(Impl* self, | |
| const MeshGP& gp, | |
| GrGLSLShaderBuilder* builder, | |
| GrGLSLUniformHandler* uniformHandler, | |
| const char* mainName, | |
| const SkSL::Context& context) | |
| : fSelf(self) | |
| , fGP(gp) | |
| , fBuilder(builder) | |
| , fUniformHandler(uniformHandler) | |
| , fMainName(mainName) | |
| , fContext(context) {} | |
| std::string declareUniform(const SkSL::VarDeclaration* decl) override { | |
| const SkSL::Variable* var = decl->var(); | |
| if (var->type().isOpaque()) { | |
| // Nothing to do. The only opaque types we should see are children, and those | |
| // will be handled in the `sample` overloads below. | |
| SkASSERT(var->type().isEffectChild()); | |
| return std::string(var->name()); | |
| } | |
| const SkSL::Type* type = &var->type(); | |
| bool isArray = false; | |
| if (type->isArray()) { | |
| type = &type->componentType(); | |
| isArray = true; | |
| } | |
| SkSLType gpuType; | |
| SkAssertResult(SkSL::type_to_sksltype(fContext, *type, &gpuType)); | |
| SkString name(var->name()); | |
| const SkSpan<const SkMeshSpecification::Uniform> uniforms = fGP.fSpec->uniforms(); | |
| auto it = std::find_if(uniforms.begin(), | |
| uniforms.end(), | |
| [&name](SkMeshSpecification::Uniform uniform) { | |
| return uniform.name == std::string_view(name.c_str(), name.size()); | |
| }); | |
| SkASSERT(it != uniforms.end()); | |
| size_t handleIdx = std::distance(uniforms.begin(), it); | |
| UniformHandle* handle = &fSelf->fSpecUniformHandles[handleIdx]; | |
| if (handle->isValid()) { | |
| const GrShaderVar& uniformVar = fUniformHandler->getUniformVariable(*handle); | |
| return std::string(uniformVar.getName().c_str()); | |
| } | |
| const SkMeshSpecification::Uniform& uniform = *it; | |
| GrShaderFlags shaderFlags = kNone_GrShaderFlags; | |
| if (uniform.flags & SkMeshSpecification::Uniform::Flags::kVertex_Flag) { | |
| shaderFlags |= kVertex_GrShaderFlag; | |
| } | |
| if (uniform.flags & SkMeshSpecification::Uniform::Flags::kFragment_Flag) { | |
| shaderFlags |= kFragment_GrShaderFlag; | |
| } | |
| SkASSERT(shaderFlags != kNone_GrShaderFlags); | |
| const char* mangledName = nullptr; | |
| *handle = fUniformHandler->addUniformArray(&fGP, | |
| shaderFlags, | |
| gpuType, | |
| name.c_str(), | |
| isArray ? var->type().columns() : 0, | |
| &mangledName); | |
| return std::string(mangledName); | |
| } | |
| std::string getMangledName(const char* name) override { | |
| return std::string(fBuilder->getMangledFunctionName(name).c_str()); | |
| } | |
| std::string getMainName() override { return fMainName; } | |
| void defineFunction(const char* decl, const char* body, bool isMain) override { | |
| fBuilder->emitFunction(decl, body); | |
| } | |
| void declareFunction(const char* decl) override { | |
| fBuilder->emitFunctionPrototype(decl); | |
| } | |
| void defineStruct(const char* definition) override { | |
| fBuilder->definitionAppend(definition); | |
| } | |
| void declareGlobal(const char* declaration) override { | |
| fBuilder->definitionAppend(declaration); | |
| } | |
| std::string sampleShader(int index, std::string coords) override { | |
| const GrFragmentProcessor* fp = fGP.fChildren[index].get(); | |
| if (!fp) { | |
| // For a null shader, return transparent black. | |
| return "half4(0)"; | |
| } | |
| GrFragmentProcessor::ProgramImpl* impl = fSelf->fChildImpls[index].get(); | |
| SkASSERT(impl); | |
| return fBuilder->getProgramBuilder()->invokeFP(*fp, | |
| *impl, | |
| /*inputColor=*/"half4(0)", | |
| /*destColor=*/"half4(1)", | |
| coords.c_str()); | |
| } | |
| std::string sampleColorFilter(int index, std::string color) override { | |
| const GrFragmentProcessor* fp = fGP.fChildren[index].get(); | |
| if (!fp) { | |
| // For a null color filter, return the color as-is. | |
| return color; | |
| } | |
| GrFragmentProcessor::ProgramImpl* impl = fSelf->fChildImpls[index].get(); | |
| SkASSERT(impl); | |
| return fBuilder->getProgramBuilder()->invokeFP(*fp, | |
| *impl, | |
| color.c_str(), | |
| /*destColor=*/"half4(1)", | |
| /*coords=*/"float2(0)"); | |
| } | |
| std::string sampleBlender(int index, std::string src, std::string dst) override { | |
| const GrFragmentProcessor* fp = fGP.fChildren[index].get(); | |
| if (!fp) { | |
| // For a null blend, perform src-over. | |
| return SkSL::String::printf("blend_src_over(%s, %s)", src.c_str(), dst.c_str()); | |
| } | |
| GrFragmentProcessor::ProgramImpl* impl = fSelf->fChildImpls[index].get(); | |
| SkASSERT(impl); | |
| return fBuilder->getProgramBuilder()->invokeFP(*fp, | |
| *impl, | |
| src.c_str(), | |
| dst.c_str(), | |
| /*coords=*/"float2(0)"); | |
| } | |
| std::string toLinearSrgb(std::string color) override { | |
| SK_ABORT("Color transform intrinsics not allowed."); | |
| } | |
| std::string fromLinearSrgb(std::string Color) override { | |
| SK_ABORT("Color transform intrinsics not allowed."); | |
| } | |
| Impl* fSelf; | |
| const MeshGP& fGP; | |
| GrGLSLShaderBuilder* fBuilder; | |
| GrGLSLUniformHandler* fUniformHandler; | |
| const char* fMainName; | |
| const SkSL::Context& fContext; | |
| }; | |
| void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override { | |
| const MeshGP& mgp = args.fGeomProc.cast<MeshGP>(); | |
| GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder; | |
| GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; | |
| GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; | |
| GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; | |
| SkSpan<std::unique_ptr<GrFragmentProcessor>> children = mgp.fChildren; | |
| // Create Impls for any child fragment processors. | |
| fChildImpls.reserve_exact(children.size()); | |
| for (const std::unique_ptr<GrFragmentProcessor>& fp : children) { | |
| fChildImpls.push_back(fp ? fp->makeProgramImpl() : nullptr); | |
| } | |
| SkASSERT(fSpecUniformHandles.empty()); | |
| fSpecUniformHandles.reserve_exact(mgp.fSpec->uniforms().size()); | |
| fSpecUniformHandles.push_back_n(mgp.fSpec->uniforms().size()); | |
| SkMeshSpecificationPriv::ColorType meshColorType = | |
| SkMeshSpecificationPriv::GetColorType(*mgp.fSpec); | |
| int passthroughLCVaryingIndex = | |
| SkMeshSpecificationPriv::PassthroughLocalCoordsVaryingIndex(*mgp.fSpec); | |
| // If the user's fragment shader doesn't output color and we also don't need its local | |
| // coords then it isn't necessary to call it at all. We might not need its local coords | |
| // because local coords aren't required for the paint or because we detected a | |
| // passthrough varying returned from the user's FS. | |
| bool needUserFS = (passthroughLCVaryingIndex < 0 && mgp.fNeedsLocalCoords) || | |
| meshColorType != SkMeshSpecificationPriv::ColorType::kNone; | |
| if (!needUserFS && !mgp.fNeedsLocalCoords) { | |
| // Don't bother with it if we don't need it. | |
| passthroughLCVaryingIndex = -1; | |
| } | |
| SkSpan<const SkMeshSpecification::Varying> specVaryings = | |
| SkMeshSpecificationPriv::Varyings(*mgp.fSpec); | |
| ////// VS | |
| // emit attributes | |
| varyingHandler->emitAttributes(mgp); | |
| // Define the user's vert function. | |
| SkString userVertName = vertBuilder->getMangledFunctionName("custom_mesh_vs"); | |
| const SkSL::Program* customVS = SkMeshSpecificationPriv::VS(*mgp.fSpec); | |
| MeshCallbacks vsCallbacks(this, | |
| mgp, | |
| vertBuilder, | |
| uniformHandler, | |
| userVertName.c_str(), | |
| *customVS->fContext); | |
| SkSL::PipelineStage::ConvertProgram(*customVS, | |
| /*sampleCoords=*/"", | |
| /*inputColor=*/"", | |
| /*destColor=*/"", | |
| &vsCallbacks); | |
| // Copy the individual attributes into a struct | |
| vertBuilder->codeAppendf("%s attributes;", | |
| vsCallbacks.getMangledName("Attributes").c_str()); | |
| { | |
| size_t i = 0; | |
| SkASSERT(mgp.vertexAttributes().count() == (int)mgp.fSpec->attributes().size()); | |
| for (auto attr : mgp.vertexAttributes()) { | |
| vertBuilder->codeAppendf("attributes.%s = %s;", | |
| mgp.fSpec->attributes()[i++].name.c_str(), | |
| attr.name()); | |
| } | |
| } | |
| // Call the user's vert function. | |
| vertBuilder->codeAppendf("%s varyings = %s(attributes);", | |
| vsCallbacks.getMangledName("Varyings").c_str(), | |
| userVertName.c_str()); | |
| if (passthroughLCVaryingIndex >= 0 && | |
| SkMeshSpecificationPriv::VaryingIsDead(*mgp.fSpec, passthroughLCVaryingIndex)) { | |
| vertBuilder->codeAppendf("float2 local = varyings.%s\n;", | |
| specVaryings[passthroughLCVaryingIndex].name.c_str()); | |
| gpArgs->fLocalCoordVar = GrShaderVar("local", SkSLType::kFloat2); | |
| gpArgs->fLocalCoordShader = kVertex_GrShaderType; | |
| } | |
| // Unpack the "varyings" from the struct into individual real varyings if they are | |
| // required. | |
| struct RealVarying { | |
| size_t specIndex; | |
| GrGLSLVarying varying; | |
| }; | |
| STArray<SkMeshSpecification::kMaxVaryings, RealVarying> realVaryings; | |
| if (needUserFS) { | |
| for (size_t i = 0; i < specVaryings.size(); ++i) { | |
| const auto& v = specVaryings[i]; | |
| if (SkMeshSpecificationPriv::VaryingIsDead(*mgp.fSpec, i)) { | |
| continue; | |
| } | |
| RealVarying rv {i, SkMeshSpecificationPriv::VaryingTypeAsSLType(v.type)}; | |
| realVaryings.push_back(rv); | |
| varyingHandler->addVarying(v.name.c_str(), &realVaryings.back().varying); | |
| vertBuilder->codeAppendf("%s = varyings.%s;", | |
| realVaryings.back().varying.vsOut(), | |
| v.name.c_str()); | |
| if (passthroughLCVaryingIndex == SkToInt(i)) { | |
| SkASSERT(gpArgs->fLocalCoordVar.getType() == SkSLType::kVoid); | |
| gpArgs->fLocalCoordVar = realVaryings.back().varying.vsOutVar(); | |
| gpArgs->fLocalCoordShader = kVertex_GrShaderType; | |
| } | |
| } | |
| } | |
| vertBuilder->codeAppend("float2 pos = varyings.position;"); | |
| // Setup position | |
| WriteOutputPosition(vertBuilder, | |
| uniformHandler, | |
| *args.fShaderCaps, | |
| gpArgs, | |
| "pos", | |
| mgp.fViewMatrix, | |
| &fViewMatrixUniform); | |
| ////// FS | |
| int samplerIndex = 0; | |
| for (size_t fpIdx = 0; fpIdx < mgp.fChildren.size(); ++fpIdx) { | |
| if (const GrFragmentProcessor* fp = mgp.fChildren[fpIdx].get()) { | |
| GrFragmentProcessor::ProgramImpl* impl = fChildImpls[fpIdx].get(); | |
| SkASSERT(impl); | |
| // Hook up sampler handles to texture effects. This code needs to keep | |
| // consistent with the code that up sampler handles (in the MeshGP ctor). | |
| fp->visitWithImpls([&](const GrFragmentProcessor& fp, | |
| GrFragmentProcessor::ProgramImpl& impl) { | |
| if (fp.asTextureEffect()) { | |
| static_cast<GrTextureEffect::Impl&>(impl).setSamplerHandle( | |
| args.fTexSamplers[samplerIndex++]); | |
| } | |
| }, | |
| *impl); | |
| // Write functions associated with this FP. | |
| args.fFragBuilder->getProgramBuilder()->advanceStage(); | |
| args.fFragBuilder->getProgramBuilder()->writeFPFunction(*fp, *impl); | |
| } | |
| } | |
| // Define the user's frag function. | |
| fragBuilder->codeAppendf("half4 %s;", args.fOutputColor); | |
| fragBuilder->codeAppendf("const half4 %s = half4(1);", args.fOutputCoverage); | |
| SkString userFragName = fragBuilder->getMangledFunctionName("custom_mesh_fs"); | |
| const SkSL::Program* customFS = SkMeshSpecificationPriv::FS(*mgp.fSpec); | |
| MeshCallbacks fsCallbacks(this, | |
| mgp, | |
| fragBuilder, | |
| uniformHandler, | |
| userFragName.c_str(), | |
| *customFS->fContext); | |
| SkSL::PipelineStage::ConvertProgram(*customFS, | |
| /*sampleCoords=*/"", | |
| /*inputColor=*/"", | |
| /*destColor=*/"", | |
| &fsCallbacks); | |
| const char* uniformColorName = nullptr; | |
| if (mgp.fColor != SK_PMColor4fILLEGAL) { | |
| fColorUniform = uniformHandler->addUniform(nullptr, | |
| kFragment_GrShaderFlag, | |
| SkSLType::kHalf4, | |
| "color", | |
| &uniformColorName); | |
| } | |
| if (meshColorType == SkMeshSpecificationPriv::ColorType::kNone) { | |
| SkASSERT(uniformColorName); | |
| fragBuilder->codeAppendf("%s = %s;", args.fOutputColor, uniformColorName); | |
| } | |
| if (needUserFS) { | |
| // Pack the real varyings into a struct to call the user's frag code. | |
| fragBuilder->codeAppendf("%s varyings;", | |
| fsCallbacks.getMangledName("Varyings").c_str()); | |
| for (const auto& rv : realVaryings) { | |
| const auto& v = specVaryings[rv.specIndex]; | |
| fragBuilder->codeAppendf("varyings.%s = %s;", | |
| v.name.c_str(), | |
| rv.varying.vsOut()); | |
| } | |
| // Grab the return local coords from the user's FS code only if we actually need it. | |
| SkString local; | |
| if (gpArgs->fLocalCoordVar.getType() == SkSLType::kVoid && mgp.fNeedsLocalCoords) { | |
| gpArgs->fLocalCoordVar = GrShaderVar("local", SkSLType::kFloat2); | |
| gpArgs->fLocalCoordShader = kFragment_GrShaderType; | |
| local = "float2 local = "; | |
| } | |
| if (meshColorType == SkMeshSpecificationPriv::ColorType::kNone) { | |
| fragBuilder->codeAppendf("%s%s(varyings);", | |
| local.c_str(), | |
| userFragName.c_str()); | |
| } else { | |
| fColorSpaceHelper.emitCode(uniformHandler, | |
| mgp.fColorSpaceXform.get(), | |
| kFragment_GrShaderFlag); | |
| if (meshColorType == SkMeshSpecificationPriv::ColorType::kFloat4) { | |
| fragBuilder->codeAppendf("float4 color;"); | |
| } else { | |
| SkASSERT(meshColorType == SkMeshSpecificationPriv::ColorType::kHalf4); | |
| fragBuilder->codeAppendf("half4 color;"); | |
| } | |
| fragBuilder->codeAppendf("%s%s(varyings, color);", | |
| local.c_str(), | |
| userFragName.c_str()); | |
| // We ignore the user's color if analysis told us to emit a specific color. | |
| // The user color might be float4 and we expect a half4 in the colorspace | |
| // helper. | |
| const char* color = uniformColorName ? uniformColorName : "half4(color)"; | |
| SkString xformedColor; | |
| fragBuilder->appendColorGamutXform(&xformedColor, color, &fColorSpaceHelper); | |
| fragBuilder->codeAppendf("%s = %s;", args.fOutputColor, xformedColor.c_str()); | |
| } | |
| } | |
| SkASSERT(!mgp.fNeedsLocalCoords || | |
| gpArgs->fLocalCoordVar.getType() == SkSLType::kFloat2); | |
| } | |
| private: | |
| SkMatrix fViewMatrix = SkMatrix::InvalidMatrix(); | |
| STArray<2, std::unique_ptr<GrFragmentProcessor::ProgramImpl>> fChildImpls; | |
| UniformHandle fViewMatrixUniform; | |
| UniformHandle fColorUniform; | |
| STArray<8, UniformHandle> fSpecUniformHandles; | |
| GrGLSLColorSpaceXformHelper fColorSpaceHelper; | |
| }; | |
| MeshGP(sk_sp<SkMeshSpecification> spec, | |
| sk_sp<GrColorSpaceXform> colorSpaceXform, | |
| const SkMatrix& viewMatrix, | |
| const std::optional<SkPMColor4f>& color, | |
| bool needsLocalCoords, | |
| sk_sp<const SkData> uniforms, | |
| SkSpan<std::unique_ptr<GrFragmentProcessor>> children) | |
| : INHERITED(kVerticesGP_ClassID) | |
| , fSpec(std::move(spec)) | |
| , fUniforms(std::move(uniforms)) | |
| , fChildren(children) | |
| , fViewMatrix(viewMatrix) | |
| , fColorSpaceXform(std::move(colorSpaceXform)) | |
| , fNeedsLocalCoords(needsLocalCoords) { | |
| fColor = color.value_or(SK_PMColor4fILLEGAL); | |
| for (const auto& srcAttr : fSpec->attributes()) { | |
| fAttributes.emplace_back(srcAttr.name.c_str(), | |
| attrib_type(srcAttr.type), | |
| SkMeshSpecificationPriv::AttrTypeAsSLType(srcAttr.type), | |
| srcAttr.offset); | |
| } | |
| this->setVertexAttributes(fAttributes.data(), fAttributes.size(), fSpec->stride()); | |
| // We are relying here on the fact that `visitTextureEffects` and `visitWithImpls` walk the | |
| // FP tree in the same order. | |
| for (const std::unique_ptr<GrFragmentProcessor>& fp : fChildren) { | |
| if (fp) { | |
| fp->visitTextureEffects([&](const GrTextureEffect& te) { | |
| fTextureSamplers.push_back({te.samplerState(), | |
| te.view().proxy()->backendFormat(), | |
| te.view().swizzle()}); | |
| }); | |
| } | |
| } | |
| this->setTextureSamplerCnt(fTextureSamplers.size()); | |
| } | |
| const TextureSampler& onTextureSampler(int index) const override { | |
| return fTextureSamplers[index]; | |
| } | |
| sk_sp<SkMeshSpecification> fSpec; | |
| sk_sp<const SkData> fUniforms; | |
| SkSpan<std::unique_ptr<GrFragmentProcessor>> fChildren; // backed by a TArray in MeshOp | |
| TArray<TextureSampler> fTextureSamplers; | |
| std::vector<Attribute> fAttributes; | |
| SkMatrix fViewMatrix; | |
| SkPMColor4f fColor; | |
| sk_sp<GrColorSpaceXform> fColorSpaceXform; | |
| bool fNeedsLocalCoords; | |
| using INHERITED = GrGeometryProcessor; | |
| }; | |
| class MeshOp final : public GrMeshDrawOp { | |
| private: | |
| using Helper = GrSimpleMeshDrawOpHelper; | |
| using ChildPtr = SkRuntimeEffect::ChildPtr; | |
| public: | |
| DEFINE_OP_CLASS_ID | |
| MeshOp(GrProcessorSet*, | |
| const SkPMColor4f&, | |
| const SkMesh&, | |
| TArray<std::unique_ptr<GrFragmentProcessor>> children, | |
| GrAAType, | |
| sk_sp<GrColorSpaceXform>, | |
| const SkMatrix&); | |
| MeshOp(GrProcessorSet*, | |
| const SkPMColor4f&, | |
| sk_sp<SkVertices>, | |
| const GrPrimitiveType*, | |
| GrAAType, | |
| sk_sp<GrColorSpaceXform>, | |
| const SkMatrix&); | |
| const char* name() const override { return "MeshOp"; } | |
| void visitProxies(const GrVisitProxyFunc& func) const override { | |
| for (const std::unique_ptr<GrFragmentProcessor>& fp : fChildren) { | |
| if (fp) { | |
| fp->visitTextureEffects([&](const GrTextureEffect& te) { | |
| func(te.view().proxy(), te.view().mipmapped()); | |
| }); | |
| } | |
| } | |
| if (fProgramInfo) { | |
| fProgramInfo->visitFPProxies(func); | |
| } else { | |
| fHelper.visitProxies(func); | |
| } | |
| } | |
| FixedFunctionFlags fixedFunctionFlags() const override; | |
| GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*, GrClampType) override; | |
| private: | |
| GrProgramInfo* programInfo() override { return fProgramInfo; } | |
| void onCreateProgramInfo(const GrCaps*, | |
| SkArenaAlloc*, | |
| const GrSurfaceProxyView& writeView, | |
| bool usesMSAASurface, | |
| GrAppliedClip&&, | |
| const GrDstProxyView&, | |
| GrXferBarrierFlags renderPassXferBarriers, | |
| GrLoadOp colorLoadOp) override; | |
| void onPrepareDraws(GrMeshDrawTarget*) override; | |
| void onExecute(GrOpFlushState*, const SkRect& chainBounds) override; | |
| #if defined(GPU_TEST_UTILS) | |
| SkString onDumpInfo() const override; | |
| #endif | |
| GrGeometryProcessor* makeGP(SkArenaAlloc*); | |
| CombineResult onCombineIfPossible(GrOp* t, SkArenaAlloc*, const GrCaps&) override; | |
| /** | |
| * Built either from a SkMesh or a SkVertices. In the former case the data is owned | |
| * by Mesh and in the latter it is not. Meshes made from SkVertices can contain a SkMatrix | |
| * to enable CPU-based transformation but Meshes made from SkMesh cannot. | |
| */ | |
| class Mesh { | |
| public: | |
| Mesh() = delete; | |
| explicit Mesh(const SkMesh& mesh); | |
| Mesh(sk_sp<SkVertices>, const SkMatrix& viewMatrix); | |
| Mesh(const Mesh&) = delete; | |
| Mesh(Mesh&& m); | |
| Mesh& operator=(const Mesh&) = delete; | |
| Mesh& operator=(Mesh&&) = delete; // not used by SkSTArray but could be implemented. | |
| ~Mesh(); | |
| bool isFromVertices() const { return SkToBool(fVertices); } | |
| const SkVertices* vertices() const { | |
| SkASSERT(this->isFromVertices()); | |
| return fVertices.get(); | |
| } | |
| std::tuple<sk_sp<const GrGpuBuffer>, size_t> gpuVB() const { | |
| if (this->isFromVertices()) { | |
| return {}; | |
| } | |
| SkASSERT(fMeshData.vb); | |
| if (!fMeshData.vb->isGaneshBacked()) { | |
| // This is a signal to upload the vertices which weren't already uploaded | |
| // to the GPU (e.g. SkPicture containing a mesh). | |
| return {nullptr, 0}; | |
| } | |
| if (auto buf = static_cast<const SkMeshPriv::GaneshVertexBuffer*>(fMeshData.vb.get())) { | |
| return {buf->asGpuBuffer(), fMeshData.voffset}; | |
| } | |
| return {}; | |
| } | |
| std::tuple<sk_sp<const GrGpuBuffer>, size_t> gpuIB() const { | |
| if (this->isFromVertices() || !fMeshData.ib) { | |
| return {}; | |
| } | |
| if (!fMeshData.ib->isGaneshBacked()) { | |
| // This is a signal to upload the indices which weren't already uploaded | |
| // to the GPU (e.g. SkPicture containing a mesh). | |
| return {nullptr, 0}; | |
| } | |
| if (auto buf = static_cast<const SkMeshPriv::GaneshIndexBuffer*>(fMeshData.ib.get())) { | |
| return {buf->asGpuBuffer(), fMeshData.ioffset}; | |
| } | |
| return {}; | |
| } | |
| void writeVertices(skgpu::VertexWriter& writer, | |
| const SkMeshSpecification& spec, | |
| bool transform) const; | |
| int vertexCount() const { | |
| return this->isFromVertices() ? fVertices->priv().vertexCount() : fMeshData.vcount; | |
| } | |
| const uint16_t* indices() const { | |
| if (this->isFromVertices()) { | |
| return fVertices->priv().indices(); | |
| } | |
| if (!fMeshData.ib) { | |
| return nullptr; | |
| } | |
| auto data = fMeshData.ib->peek(); | |
| if (!data) { | |
| return nullptr; | |
| } | |
| return SkTAddOffset<const uint16_t>(data, fMeshData.ioffset); | |
| } | |
| int indexCount() const { | |
| return this->isFromVertices() ? fVertices->priv().indexCount() : fMeshData.icount; | |
| } | |
| using sk_is_trivially_relocatable = std::true_type; | |
| private: | |
| struct MeshData { | |
| sk_sp<const SkMeshPriv::VB> vb; | |
| sk_sp<const SkMeshPriv::IB> ib; | |
| size_t vcount = 0; | |
| size_t icount = 0; | |
| size_t voffset = 0; | |
| size_t ioffset = 0; | |
| static_assert(::sk_is_trivially_relocatable<decltype(vb)>::value); | |
| static_assert(::sk_is_trivially_relocatable<decltype(ib)>::value); | |
| using sk_is_trivially_relocatable = std::true_type; | |
| }; | |
| sk_sp<SkVertices> fVertices; | |
| union { | |
| SkMatrix fViewMatrix; | |
| MeshData fMeshData; | |
| }; | |
| static_assert(::sk_is_trivially_relocatable<decltype(fVertices)>::value); | |
| static_assert(::sk_is_trivially_relocatable<decltype(fViewMatrix)>::value); | |
| }; | |
| Helper fHelper; | |
| sk_sp<SkMeshSpecification> fSpecification; | |
| bool fIgnoreSpecColor = false; | |
| GrPrimitiveType fPrimitiveType; | |
| STArray<1, Mesh> fMeshes; | |
| sk_sp<GrColorSpaceXform> fColorSpaceXform; | |
| SkPMColor4f fColor; // Used if no color from spec or analysis overrides. | |
| SkMatrix fViewMatrix; | |
| sk_sp<const SkData> fUniforms; | |
| int fVertexCount; | |
| int fIndexCount; | |
| GrSimpleMesh* fMesh = nullptr; | |
| GrProgramInfo* fProgramInfo = nullptr; | |
| TArray<std::unique_ptr<GrFragmentProcessor>> fChildren; | |
| using INHERITED = GrMeshDrawOp; | |
| }; | |
| MeshOp::Mesh::Mesh(const SkMesh& mesh) { | |
| new (&fMeshData) MeshData(); | |
| SkASSERT(mesh.vertexBuffer()); | |
| fMeshData.vb = sk_ref_sp(static_cast<SkMeshPriv::VB*>(mesh.vertexBuffer())); | |
| if (mesh.indexBuffer()) { | |
| fMeshData.ib = sk_ref_sp(static_cast<SkMeshPriv::IB*>(mesh.indexBuffer())); | |
| } | |
| fMeshData.vcount = mesh.vertexCount(); | |
| fMeshData.voffset = mesh.vertexOffset(); | |
| fMeshData.icount = mesh.indexCount(); | |
| fMeshData.ioffset = mesh.indexOffset(); | |
| // The caller could modify CPU buffers after the draw so we must copy the data. | |
| if (fMeshData.vb->peek()) { | |
| auto data = SkTAddOffset<const void>(fMeshData.vb->peek(), fMeshData.voffset); | |
| size_t size = fMeshData.vcount * mesh.spec()->stride(); | |
| fMeshData.vb = SkMeshPriv::CpuVertexBuffer::Make(data, size); | |
| fMeshData.voffset = 0; | |
| } | |
| if (fMeshData.ib && fMeshData.ib->peek()) { | |
| auto data = SkTAddOffset<const void>(fMeshData.ib->peek(), fMeshData.ioffset); | |
| size_t size = fMeshData.icount * sizeof(uint16_t); | |
| fMeshData.ib = SkMeshPriv::CpuIndexBuffer::Make(data, size); | |
| fMeshData.ioffset = 0; | |
| } | |
| } | |
| MeshOp::Mesh::Mesh(sk_sp<SkVertices> vertices, const SkMatrix& viewMatrix) | |
| : fVertices(std::move(vertices)), fViewMatrix(viewMatrix) { | |
| SkASSERT(fVertices); | |
| } | |
| MeshOp::Mesh::Mesh(Mesh&& that) { | |
| fVertices = std::move(that.fVertices); | |
| if (fVertices) { | |
| fViewMatrix = that.fViewMatrix; | |
| // 'that' is now not-a-vertices. Make sure it can be safely destroyed. | |
| new (&that.fMeshData) MeshData(); | |
| } else { | |
| fMeshData = std::move(that.fMeshData); | |
| } | |
| } | |
| MeshOp::Mesh::~Mesh() { | |
| if (!this->isFromVertices()) { | |
| fMeshData.~MeshData(); | |
| } | |
| } | |
| void MeshOp::Mesh::writeVertices(skgpu::VertexWriter& writer, | |
| const SkMeshSpecification& spec, | |
| bool transform) const { | |
| SkASSERT(!transform || this->isFromVertices()); | |
| if (this->isFromVertices()) { | |
| int vertexCount = fVertices->priv().vertexCount(); | |
| for (int i = 0; i < vertexCount; ++i) { | |
| SkPoint pos = fVertices->priv().positions()[i]; | |
| if (transform) { | |
| SkASSERT(!fViewMatrix.hasPerspective()); | |
| pos = fViewMatrix.mapPoint(pos); | |
| } | |
| writer << pos; | |
| if (SkMeshSpecificationPriv::HasColors(spec)) { | |
| SkASSERT(fVertices->priv().hasColors()); | |
| writer << fVertices->priv().colors()[i]; | |
| } | |
| if (fVertices->priv().hasTexCoords()) { | |
| writer << fVertices->priv().texCoords()[i]; | |
| } | |
| } | |
| } else { | |
| const void* data = fMeshData.vb->peek(); | |
| if (data) { | |
| auto vdata = SkTAddOffset<const char>(data, fMeshData.voffset); | |
| writer << skgpu::VertexWriter::Array(vdata, spec.stride()*fMeshData.vcount); | |
| } | |
| } | |
| } | |
| MeshOp::MeshOp(GrProcessorSet* processorSet, | |
| const SkPMColor4f& color, | |
| const SkMesh& mesh, | |
| TArray<std::unique_ptr<GrFragmentProcessor>> children, | |
| GrAAType aaType, | |
| sk_sp<GrColorSpaceXform> colorSpaceXform, | |
| const SkMatrix& viewMatrix) | |
| : INHERITED(ClassID()) | |
| , fHelper(processorSet, aaType) | |
| , fPrimitiveType(primitive_type(mesh.mode())) | |
| , fColorSpaceXform(std::move(colorSpaceXform)) | |
| , fColor(color) | |
| , fViewMatrix(viewMatrix) { | |
| fMeshes.emplace_back(mesh); | |
| fSpecification = mesh.refSpec(); | |
| fUniforms = SkRuntimeEffectPriv::TransformUniforms( | |
| mesh.spec()->uniforms(), mesh.refUniforms(), mesh.spec()->colorSpace()); | |
| fChildren = std::move(children); | |
| fVertexCount = fMeshes.back().vertexCount(); | |
| fIndexCount = fMeshes.back().indexCount(); | |
| this->setTransformedBounds(mesh.bounds(), fViewMatrix, HasAABloat::kNo, IsHairline::kNo); | |
| } | |
| static SkMeshSpecification* make_vertices_spec(bool hasColors, bool hasTex) { | |
| using Attribute = SkMeshSpecification::Attribute; | |
| using Varying = SkMeshSpecification::Varying; | |
| std::vector<Attribute> attributes; | |
| attributes.reserve(3); | |
| attributes.push_back({Attribute::Type::kFloat2, 0, SkString{"pos"}}); | |
| size_t size = 8; | |
| std::vector<Varying> varyings; | |
| attributes.reserve(2); | |
| SkString vs("Varyings main(const Attributes a) {\nVaryings v;"); | |
| SkString fs("float2 "); | |
| if (hasColors) { | |
| attributes.push_back({Attribute::Type::kUByte4_unorm, size, SkString{"color"}}); | |
| varyings.push_back({Varying::Type::kHalf4, SkString{"color"}}); | |
| vs += "v.color = a.color;\n"; | |
| // Using float4 for the output color to work around skbug.com/40043854 | |
| fs += "main(const Varyings v, out float4 color) {\n" | |
| "color = float4(v.color.bgr*v.color.a, v.color.a);\n"; | |
| size += 4; | |
| } else { | |
| fs += "main(const Varyings v) {\n"; | |
| } | |
| if (hasTex) { | |
| attributes.push_back({Attribute::Type::kFloat2, size, SkString{"tex"}}); | |
| varyings.push_back({Varying::Type::kFloat2, SkString{"tex"}}); | |
| vs += "v.tex = a.tex;\n"; | |
| fs += "return v.tex;\n"; | |
| size += 8; | |
| } else { | |
| fs += "return v.position;\n"; | |
| } | |
| vs += "v.position = a.pos;\nreturn v;\n}"; | |
| fs += "}"; | |
| auto [spec, error] = SkMeshSpecification::Make(SkSpan(attributes), | |
| size, | |
| SkSpan(varyings), | |
| vs, | |
| fs); | |
| SkASSERT(spec); | |
| return spec.release(); | |
| } | |
| MeshOp::MeshOp(GrProcessorSet* processorSet, | |
| const SkPMColor4f& color, | |
| sk_sp<SkVertices> vertices, | |
| const GrPrimitiveType* overridePrimitiveType, | |
| GrAAType aaType, | |
| sk_sp<GrColorSpaceXform> colorSpaceXform, | |
| const SkMatrix& viewMatrix) | |
| : INHERITED(ClassID()) | |
| , fHelper(processorSet, aaType) | |
| , fColorSpaceXform(std::move(colorSpaceXform)) | |
| , fColor(color) | |
| , fViewMatrix(viewMatrix) { | |
| int attrs = (vertices->priv().hasColors() ? 0b01 : 0b00) | | |
| (vertices->priv().hasTexCoords() ? 0b10 : 0b00); | |
| switch (attrs) { | |
| case 0b00: { | |
| static const SkMeshSpecification* kSpec = make_vertices_spec(false, false); | |
| fSpecification = sk_ref_sp(kSpec); | |
| break; | |
| } | |
| case 0b01: { | |
| static const SkMeshSpecification* kSpec = make_vertices_spec(true, false); | |
| fSpecification = sk_ref_sp(kSpec); | |
| break; | |
| } | |
| case 0b10: { | |
| static const SkMeshSpecification* kSpec = make_vertices_spec(false, true); | |
| fSpecification = sk_ref_sp(kSpec); | |
| break; | |
| } | |
| case 0b11: { | |
| static const SkMeshSpecification* kSpec = make_vertices_spec(true, true); | |
| fSpecification = sk_ref_sp(kSpec); | |
| break; | |
| } | |
| } | |
| SkASSERT(fSpecification); | |
| if (overridePrimitiveType) { | |
| fPrimitiveType = *overridePrimitiveType; | |
| } else { | |
| switch (vertices->priv().mode()) { | |
| case SkVertices::kTriangles_VertexMode: | |
| fPrimitiveType = GrPrimitiveType::kTriangles; | |
| break; | |
| case SkVertices::kTriangleStrip_VertexMode: | |
| fPrimitiveType = GrPrimitiveType::kTriangleStrip; | |
| break; | |
| case SkVertices::kTriangleFan_VertexMode: | |
| SkUNREACHABLE; | |
| } | |
| } | |
| IsHairline isHairline = IsHairline::kNo; | |
| if (GrIsPrimTypeLines(fPrimitiveType) || fPrimitiveType == GrPrimitiveType::kPoints) { | |
| isHairline = IsHairline::kYes; | |
| } | |
| this->setTransformedBounds(vertices->bounds(), fViewMatrix, HasAABloat::kNo, isHairline); | |
| fMeshes.emplace_back(std::move(vertices), fViewMatrix); | |
| fVertexCount = fMeshes.back().vertexCount(); | |
| fIndexCount = fMeshes.back().indexCount(); | |
| } | |
| #if defined(GPU_TEST_UTILS) | |
| SkString MeshOp::onDumpInfo() const { return {}; } | |
| #endif | |
| GrDrawOp::FixedFunctionFlags MeshOp::fixedFunctionFlags() const { | |
| return fHelper.fixedFunctionFlags(); | |
| } | |
| GrProcessorSet::Analysis MeshOp::finalize(const GrCaps& caps, | |
| const GrAppliedClip* clip, | |
| GrClampType clampType) { | |
| GrProcessorAnalysisColor gpColor; | |
| gpColor.setToUnknown(); | |
| auto result = fHelper.finalizeProcessors(caps, | |
| clip, | |
| clampType, | |
| GrProcessorAnalysisCoverage::kNone, | |
| &gpColor); | |
| if (gpColor.isConstant(&fColor)) { | |
| fIgnoreSpecColor = true; | |
| } | |
| return result; | |
| } | |
| GrGeometryProcessor* MeshOp::makeGP(SkArenaAlloc* arena) { | |
| std::optional<SkPMColor4f> color; | |
| if (fIgnoreSpecColor || !SkMeshSpecificationPriv::HasColors(*fSpecification)) { | |
| color.emplace(fColor); | |
| } | |
| // Check if we're pre-transforming the vertices on the CPU. | |
| const SkMatrix& vm = fViewMatrix == SkMatrix::InvalidMatrix() ? SkMatrix::I() : fViewMatrix; | |
| return MeshGP::Make(arena, | |
| fSpecification, | |
| fColorSpaceXform, | |
| vm, | |
| color, | |
| fHelper.usesLocalCoords(), | |
| fUniforms, | |
| SkSpan(fChildren)); | |
| } | |
| void MeshOp::onCreateProgramInfo(const GrCaps* caps, | |
| SkArenaAlloc* arena, | |
| const GrSurfaceProxyView& writeView, | |
| bool usesMSAASurface, | |
| GrAppliedClip&& appliedClip, | |
| const GrDstProxyView& dstProxyView, | |
| GrXferBarrierFlags renderPassXferBarriers, | |
| GrLoadOp colorLoadOp) { | |
| fProgramInfo = fHelper.createProgramInfo(caps, | |
| arena, | |
| writeView, | |
| usesMSAASurface, | |
| std::move(appliedClip), | |
| dstProxyView, | |
| this->makeGP(arena), | |
| fPrimitiveType, | |
| renderPassXferBarriers, | |
| colorLoadOp); | |
| } | |
| void MeshOp::onPrepareDraws(GrMeshDrawTarget* target) { | |
| size_t vertexStride = fSpecification->stride(); | |
| sk_sp<const GrBuffer> vertexBuffer; | |
| int firstVertex; | |
| std::tie(vertexBuffer, firstVertex) = fMeshes[0].gpuVB(); | |
| if (!vertexBuffer) { | |
| skgpu::VertexWriter verts = target->makeVertexWriter(vertexStride, | |
| fVertexCount, | |
| &vertexBuffer, | |
| &firstVertex); | |
| if (!verts) { | |
| SkDebugf("Could not allocate vertices.\n"); | |
| return; | |
| } | |
| bool transform = fViewMatrix == SkMatrix::InvalidMatrix(); | |
| for (const auto& m : fMeshes) { | |
| m.writeVertices(verts, *fSpecification, transform); | |
| } | |
| } else { | |
| SkASSERT(fMeshes.size() == 1); | |
| SkASSERT(firstVertex % fSpecification->stride() == 0); | |
| firstVertex /= fSpecification->stride(); | |
| } | |
| sk_sp<const GrBuffer> indexBuffer; | |
| int firstIndex = 0; | |
| std::tie(indexBuffer, firstIndex) = fMeshes[0].gpuIB(); | |
| if (fIndexCount && !indexBuffer) { | |
| uint16_t* indices = nullptr; | |
| indices = target->makeIndexSpace(fIndexCount, &indexBuffer, &firstIndex); | |
| if (!indices) { | |
| SkDebugf("Could not allocate indices.\n"); | |
| return; | |
| } | |
| // We can just copy the first mesh's indices. Subsequent meshes need their indices adjusted. | |
| std::copy_n(fMeshes[0].indices(), fMeshes[0].indexCount(), indices); | |
| int voffset = fMeshes[0].vertexCount(); | |
| int ioffset = fMeshes[0].indexCount(); | |
| for (int m = 1; m < fMeshes.size(); ++m) { | |
| for (int i = 0; i < fMeshes[m].indexCount(); ++i) { | |
| indices[ioffset++] = fMeshes[m].indices()[i] + voffset; | |
| } | |
| voffset += fMeshes[m].vertexCount(); | |
| } | |
| SkASSERT(voffset == fVertexCount); | |
| SkASSERT(ioffset == fIndexCount); | |
| } else if (indexBuffer) { | |
| SkASSERT(fMeshes.size() == 1); | |
| SkASSERT(firstIndex % sizeof(uint16_t) == 0); | |
| firstIndex /= sizeof(uint16_t); | |
| } | |
| SkASSERT(!fMesh); | |
| fMesh = target->allocMesh(); | |
| if (indexBuffer) { | |
| fMesh->setIndexed(std::move(indexBuffer), | |
| fIndexCount, | |
| firstIndex, | |
| /*minIndexValue=*/0, | |
| fVertexCount - 1, | |
| GrPrimitiveRestart::kNo, | |
| std::move(vertexBuffer), | |
| firstVertex); | |
| } else { | |
| fMesh->set(std::move(vertexBuffer), fVertexCount, firstVertex); | |
| } | |
| } | |
| void MeshOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) { | |
| if (!fProgramInfo) { | |
| this->createProgramInfo(flushState); | |
| } | |
| if (!fProgramInfo || !fMesh) { | |
| return; | |
| } | |
| TArray<GrSurfaceProxy*> geomProcTextures; | |
| for (const std::unique_ptr<GrFragmentProcessor>& fp : fChildren) { | |
| if (fp) { | |
| fp->visitTextureEffects([&](const GrTextureEffect& te) { | |
| geomProcTextures.push_back(te.view().proxy()); | |
| }); | |
| } | |
| } | |
| flushState->bindPipelineAndScissorClip(*fProgramInfo, chainBounds); | |
| flushState->bindTextures(fProgramInfo->geomProc(), | |
| geomProcTextures.data(), | |
| fProgramInfo->pipeline()); | |
| flushState->drawMesh(*fMesh); | |
| } | |
| GrOp::CombineResult MeshOp::onCombineIfPossible(GrOp* t, SkArenaAlloc*, const GrCaps& caps) { | |
| auto that = t->cast<MeshOp>(); | |
| if (!fMeshes[0].isFromVertices() || !that->fMeshes[0].isFromVertices()) { | |
| // We *could* make this work when the vertex/index buffers are CPU-backed but that isn't an | |
| // important use case. | |
| return GrOp::CombineResult::kCannotCombine; | |
| } | |
| // Check for a combinable primitive type. | |
| if (!(fPrimitiveType == GrPrimitiveType::kTriangles || | |
| fPrimitiveType == GrPrimitiveType::kLines || | |
| fPrimitiveType == GrPrimitiveType::kPoints)) { | |
| return CombineResult::kCannotCombine; | |
| } | |
| if (fPrimitiveType != that->fPrimitiveType) { | |
| return CombineResult::kCannotCombine; | |
| } | |
| if (fVertexCount > INT32_MAX - that->fVertexCount) { | |
| return CombineResult::kCannotCombine; | |
| } | |
| if (SkToBool(fIndexCount) != SkToBool(that->fIndexCount)) { | |
| return CombineResult::kCannotCombine; | |
| } | |
| if (SkToBool(fIndexCount) && | |
| // Index count would overflow | |
| (fIndexCount > INT32_MAX - that->fIndexCount || | |
| // *or* combined vertex count would not be referenceable by uint16 indices | |
| fVertexCount > SkToInt(UINT16_MAX) - that->fVertexCount)) { | |
| return CombineResult::kCannotCombine; | |
| } | |
| if (SkMeshSpecificationPriv::Hash(*this->fSpecification) != | |
| SkMeshSpecificationPriv::Hash(*that->fSpecification)) { | |
| return CombineResult::kCannotCombine; | |
| } | |
| // Our specs made for vertices don't have uniforms. | |
| SkASSERT(fSpecification->uniforms().empty()); | |
| if (!SkMeshSpecificationPriv::HasColors(*fSpecification) && fColor != that->fColor) { | |
| return CombineResult::kCannotCombine; | |
| } | |
| if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) { | |
| return CombineResult::kCannotCombine; | |
| } | |
| if (fViewMatrix != that->fViewMatrix) { | |
| // If we use local coords and the local coords come from positions then we can't pre- | |
| // transform the positions on the CPU. | |
| if (fHelper.usesLocalCoords() && !fMeshes[0].vertices()->priv().hasTexCoords()) { | |
| return CombineResult::kCannotCombine; | |
| } | |
| // We only support two-component position attributes. This means we would not get | |
| // perspective-correct interpolation of attributes if we transform on the CPU. | |
| if ((this->fViewMatrix.isFinite() && this->fViewMatrix.hasPerspective()) || | |
| (that->fViewMatrix.isFinite() && that->fViewMatrix.hasPerspective())) { | |
| return CombineResult::kCannotCombine; | |
| } | |
| // This is how we record that we must CPU-transform the vertices. | |
| fViewMatrix = SkMatrix::InvalidMatrix(); | |
| } | |
| // NOTE: The source color space is part of the spec, and the destination gamut is determined by | |
| // the render target context. A mis-match should be impossible. | |
| SkASSERT(GrColorSpaceXform::Equals(fColorSpaceXform.get(), that->fColorSpaceXform.get())); | |
| fMeshes.move_back_n(that->fMeshes.size(), that->fMeshes.begin()); | |
| fVertexCount += that->fVertexCount; | |
| fIndexCount += that->fIndexCount; | |
| return CombineResult::kMerged; | |
| } | |
| } // anonymous namespace | |
| namespace skgpu::ganesh::DrawMeshOp { | |
| GrOp::Owner Make(GrRecordingContext* context, | |
| GrPaint&& paint, | |
| const SkMesh& mesh, | |
| TArray<std::unique_ptr<GrFragmentProcessor>> children, | |
| const SkMatrix& viewMatrix, | |
| GrAAType aaType, | |
| sk_sp<GrColorSpaceXform> colorSpaceXform) { | |
| return GrSimpleMeshDrawOpHelper::FactoryHelper<MeshOp>(context, | |
| std::move(paint), | |
| mesh, | |
| std::move(children), | |
| aaType, | |
| std::move(colorSpaceXform), | |
| viewMatrix); | |
| } | |
| GrOp::Owner Make(GrRecordingContext* context, | |
| GrPaint&& paint, | |
| sk_sp<SkVertices> vertices, | |
| const GrPrimitiveType* overridePrimitiveType, | |
| const SkMatrix& viewMatrix, | |
| GrAAType aaType, | |
| sk_sp<GrColorSpaceXform> colorSpaceXform) { | |
| return GrSimpleMeshDrawOpHelper::FactoryHelper<MeshOp>(context, | |
| std::move(paint), | |
| std::move(vertices), | |
| overridePrimitiveType, | |
| aaType, | |
| std::move(colorSpaceXform), | |
| viewMatrix); | |
| } | |
| } // namespace skgpu::ganesh::DrawMeshOp |