| /* | |
| * Copyright 2026 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/render/MeshRenderStep.h" | |
| #include "src/gpu/graphite/PaintParamsKey.h" | |
| #include "include/private/SkAssert.h" | |
| #include "include/private/SkDebug.h" | |
| #include "src/core/SkMeshPriv.h" | |
| #include "src/core/SkRuntimeEffectPriv.h" | |
| #include "src/core/SkSLTypeShared.h" | |
| #include "src/core/SkVertState.h" | |
| #include "src/gpu/BufferWriter.h" | |
| #include "src/gpu/graphite/DrawParams.h" | |
| #include "src/gpu/graphite/DrawTypes.h" | |
| #include "src/gpu/graphite/DrawWriter.h" | |
| #include "src/gpu/graphite/PipelineData.h" | |
| #include "src/gpu/graphite/ShaderCodeDictionary.h" | |
| #include "src/gpu/graphite/geom/Geometry.h" | |
| #include "src/gpu/graphite/geom/Transform.h" | |
| #include "src/sksl/SkSLString.h" | |
| #include "src/gpu/graphite/render/CommonDepthStencilSettings.h" | |
| #include <cstdint> | |
| namespace skgpu::graphite { | |
| static constexpr char kMeshFSLocalCoordsName[] = "meshLocalCoordsOverride"; | |
| static constexpr std::initializer_list<Uniform> kStepUniforms = | |
| {{"depth", SkSLType::kFloat}, {"localToDevice", SkSLType::kFloat4x4}}; | |
| static SkVertices::VertexMode vertex_mode(SkMesh::Mode mode) { | |
| if (mode == SkMesh::Mode::kTriangles) { | |
| return SkVertices::VertexMode::kTriangles_VertexMode; | |
| } else { | |
| SkASSERT(mode == SkMesh::Mode::kTriangleStrip); | |
| return SkVertices::VertexMode::kTriangleStrip_VertexMode; | |
| } | |
| } | |
| // NOTE: Mesh geometry can emit triangles that self intersect, but it *wants* those to produce | |
| // double blending, so we always add Flags::kAllowSelfIntersection. | |
| MeshRenderStep::MeshRenderStep(Layout layout) | |
| : RenderStep(layout, | |
| RenderStep::RenderStepID::kMesh, | |
| Flags::kPerformsShading | Flags::kAppendVertices | |
| | Flags::kEmitsPrimitiveColor | |
| | Flags::kAllowsSelfIntersection, | |
| kStepUniforms, | |
| PrimitiveType::kTriangles, | |
| /*stencilSettings=*/{}, | |
| /*staticAttrs=*/{}, | |
| /*appendAttrs=*/{{"ssboIndex", VertexAttribType::kUInt, SkSLType::kUInt}}, | |
| /*storageUniforms=*/{}) {} | |
| MeshRenderStep::~MeshRenderStep() {} | |
| std::string MeshRenderStep::vertexSkSL(const RootNodesInfo& roots) const { | |
| const SkMeshSpecification* spec = roots.fMeshSpec; | |
| SkASSERT(spec); | |
| // Any attributes and varyings defined by the SkMeshSpecification will be emitted within | |
| // `ShaderInfo::generateVertexSkSL` with the same names as defined in the mesh specification, | |
| // we mangle the varyings' names to ensure there is not a name conflict between an attribute | |
| // and varying. | |
| std::string attrs = "Attributes attributes;\n"; | |
| for (const auto& attr : spec->attributes()) { | |
| attrs += SkSL::String::printf("attributes.%s = %s;\n", | |
| attr.name.c_str(), attr.name.c_str()); | |
| } | |
| std::string varyingAssignments; | |
| for (const auto& v : SkMeshSpecificationPriv::Varyings(*spec)) { | |
| varyingAssignments += SkSL::String::printf("%s%s = varyings.%s;\n", | |
| v.name.c_str(), kMeshVaryingMangleSuffix, | |
| v.name.c_str()); | |
| } | |
| return SkSL::String::printf("%s\n" | |
| "Varyings varyings = %s(attributes);\n" | |
| "float4 devPosition" | |
| " = localToDevice * float4(varyings.position, depth, 1.0);\n" | |
| "stepLocalCoords = varyings.position;\n" | |
| "%s", attrs.c_str(), kMeshVSMainName, | |
| varyingAssignments.c_str()); | |
| } | |
| std::string MeshRenderStep::fragmentColorSkSL(const RootNodesInfo& roots) const { | |
| const SkMeshSpecification* spec = roots.fMeshSpec; | |
| SkASSERT(spec); | |
| // Varyings defined by the SkMeshSpecification will be emitted within | |
| // `ShaderInfo::generateFragmentSkSL` following the same varying name mangling. | |
| std::string s = "Varyings varyings;\n"; | |
| for (const auto& v : SkMeshSpecificationPriv::Varyings(*spec)) { | |
| s += SkSL::String::printf("varyings.%s = %s%s;\n", | |
| v.name.c_str(), v.name.c_str(), | |
| kMeshVaryingMangleSuffix); | |
| } | |
| bool needsColorConversion = SkMeshSpecificationPriv::GetColorType(*spec) | |
| == SkMeshSpecificationPriv::ColorType::kFloat4; | |
| std::string outColorName = "primitiveColor"; | |
| if (needsColorConversion) { | |
| outColorName = "primitiveColorFloat4"; | |
| s += "float4 " + outColorName + ";\n"; | |
| } | |
| // Check if the mesh FS should have a primitive color output parameter. | |
| bool hasColorOutput = SkMeshSpecificationPriv::HasColors(*spec); | |
| std::string methodCall = hasColorOutput ? SkSL::String::printf("%s(varyings, %s)", | |
| kMeshFSMainName, | |
| outColorName.c_str()) | |
| : SkSL::String::printf("%s(varyings)", | |
| kMeshFSMainName); | |
| s += SkSL::String::printf("float2 %s = %s;\n", | |
| kMeshFSLocalCoordsName, | |
| methodCall.c_str()); | |
| if (needsColorConversion) { | |
| s += "primitiveColor = half4(" + outColorName + ");\n"; | |
| } | |
| return s; | |
| } | |
| const char* MeshRenderStep::fragmentColorSkSLLocalCoordsVariable() const { | |
| return kMeshFSLocalCoordsName; | |
| } | |
| void MeshRenderStep::writeVertices(DrawWriter* writer, | |
| StorageContext* /*storageContext*/, | |
| const DrawParams& params, | |
| uint32_t ssboIndex) const { | |
| const SkMesh& mesh = params.geometry().mesh(); | |
| const SkMeshSpecification* spec = mesh.spec(); | |
| SkASSERT(mesh.vertexBuffer()); // SkMesh::isValid() should catch this in Device::drawMesh(). | |
| auto* cpuVertexBuffer = static_cast<const SkMeshPriv::CpuVertexBuffer*>(mesh.vertexBuffer()); | |
| size_t vertexStride = spec->stride(); | |
| size_t vertexCount = mesh.vertexCount(); | |
| const uint8_t* vertexData = static_cast<const uint8_t*>( | |
| cpuVertexBuffer->peek()) + mesh.vertexOffset(); | |
| const uint16_t* indices = nullptr; | |
| size_t indexCount = mesh.indexCount(); | |
| if (mesh.indexBuffer()) { | |
| auto* cpuIndexBuffer = static_cast<const SkMeshPriv::CpuIndexBuffer*>(mesh.indexBuffer()); | |
| indices = reinterpret_cast<const uint16_t*>(static_cast<const uint8_t*>( | |
| cpuIndexBuffer->peek()) + mesh.indexOffset()); | |
| } | |
| DrawWriter::Vertices verts(*writer); | |
| verts.reserve(indices ? indexCount : vertexCount); | |
| VertState state(vertexCount, indices, indexCount); | |
| VertState::Proc vertProc = state.chooseProc(vertex_mode(mesh.mode())); | |
| while (vertProc(&state)) { | |
| VertexWriter vertWriter = verts.append(3); | |
| for (uint32_t i = 0; i < 3; ++i) { | |
| uint32_t vertIndex = i == 0 ? state.f0 : (i == 1 ? state.f1 : state.f2); | |
| const uint8_t* vertexDataBase = vertexData + vertIndex * vertexStride; | |
| vertWriter << ssboIndex; | |
| for (const SkMeshSpecification::Attribute& attr : spec->attributes()) { | |
| vertWriter << VertexWriter::Array( | |
| vertexDataBase + attr.offset, | |
| SkMeshSpecificationPriv::AttrTypeByteSize(attr.type)); | |
| } | |
| } | |
| } | |
| } | |
| void MeshRenderStep::writeUniformsAndTextures(const DrawParams& params, | |
| PipelineDataGatherer* gatherer) const { | |
| const SkMesh& mesh = params.geometry().mesh(); | |
| const SkMeshSpecification* spec = mesh.spec(); | |
| #if defined(SK_DEBUG) | |
| skia_private::TArray<Uniform> uniforms(kStepUniforms); | |
| uniforms.reserve_exact(this->numUniforms() + spec->uniforms().size()); | |
| skia_private::TArray<std::string> uniformNames; | |
| uniformNames.reserve_exact(spec->uniforms().size()); | |
| for (const auto& u : spec->uniforms()) { | |
| uniformNames.push_back(std::string(u.name)); | |
| uniforms.push_back(Uniform(uniformNames.back().c_str(), | |
| ShaderCodeDictionary::UniformTypeToSkSLType(u), | |
| u.isArray() ? u.count : Uniform::kNonArray)); | |
| } | |
| gatherer->checkRewind(); | |
| UniformExpectationsValidator uev(gatherer, uniforms); | |
| #endif | |
| // Write step uniforms. | |
| gatherer->write(params.order().depthAsFloat()); | |
| gatherer->write(params.transform().matrix()); | |
| // Write SkMeshSpecification uniforms. | |
| sk_sp<const SkData> transformedUniforms = SkRuntimeEffectPriv::TransformUniforms( | |
| spec->uniforms(), mesh.refUniforms(), SkMeshSpecificationPriv::ColorSpace(*spec)); | |
| for (const SkMeshSpecification::Uniform& u: spec->uniforms()) { | |
| // Since we are doing pointer arithmetic, make sure we are operating on bytes. | |
| static_assert(std::is_same_v<decltype(transformedUniforms->bytes()), const uint8_t*>); | |
| // We only need the type information for writing the uniform so we can leave the name null. | |
| gatherer->write(Uniform(nullptr, | |
| ShaderCodeDictionary::UniformTypeToSkSLType(u), | |
| u.isArray() ? u.count : Uniform::kNonArray), | |
| transformedUniforms->bytes() + u.offset); | |
| } | |
| } | |
| size_t MeshRenderStep::appendDataStride(const DrawParams& params) const { | |
| const SkMeshSpecification* spec = params.geometry().mesh().spec(); | |
| size_t stride = this->RenderStep::appendDataStride(params); | |
| for (const auto& attr : spec->attributes()) { | |
| stride += Attribute::MakeFromSkMeshAttribute(attr).sizeAlign4(); | |
| } | |
| return stride; | |
| } | |
| } // namespace skgpu::graphite |