| /* | |
| * 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/render/TessellateStrokesRenderStep.h" | |
| #include "include/core/SkM44.h" | |
| #include "include/private/SkSLString.h" | |
| #include "src/base/SkVx.h" | |
| #include "src/core/SkGeometry.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/render/CommonDepthStencilSettings.h" | |
| #include "src/gpu/graphite/render/DynamicInstancesPatchAllocator.h" | |
| #include "src/gpu/tessellate/FixedCountBufferUtils.h" | |
| #include "src/gpu/tessellate/PatchWriter.h" | |
| #include "src/gpu/tessellate/StrokeIterator.h" | |
| namespace skgpu::graphite { | |
| namespace { | |
| using namespace skgpu::tess; | |
| // Always use dynamic stroke params and join control points, track the join control point in | |
| // PatchWriter and replicate line end points (match Ganesh's shader behavior). | |
| // | |
| // No explicit curve type on platforms that support infinity. | |
| // No color or wide color attribs, since it might always be part of the PaintParams | |
| // or we'll add a color-only fast path to RenderStep later. | |
| static constexpr PatchAttribs kAttribs = PatchAttribs::kJoinControlPoint | | |
| PatchAttribs::kStrokeParams | | |
| PatchAttribs::kPaintDepth | | |
| PatchAttribs::kSsboIndex; | |
| static constexpr PatchAttribs kAttribsWithCurveType = kAttribs | PatchAttribs::kExplicitCurveType; | |
| using Writer = PatchWriter<DynamicInstancesPatchAllocator<FixedCountStrokes>, | |
| Required<PatchAttribs::kJoinControlPoint>, | |
| Required<PatchAttribs::kStrokeParams>, | |
| Required<PatchAttribs::kPaintDepth>, | |
| Required<PatchAttribs::kSsboIndex>, | |
| Optional<PatchAttribs::kExplicitCurveType>, | |
| ReplicateLineEndPoints, | |
| TrackJoinControlPoints>; | |
| // The order of the attribute declarations must match the order used by | |
| // PatchWriter::emitPatchAttribs, i.e.: | |
| // join << fanPoint << stroke << color << depth << curveType << ssboIndex | |
| static constexpr Attribute kBaseAttributes[] = { | |
| {"p01", VertexAttribType::kFloat4, SkSLType::kFloat4}, | |
| {"p23", VertexAttribType::kFloat4, SkSLType::kFloat4}, | |
| {"prevPoint", VertexAttribType::kFloat2, SkSLType::kFloat2}, | |
| {"stroke", VertexAttribType::kFloat2, SkSLType::kFloat2}, | |
| {"depth", VertexAttribType::kFloat, SkSLType::kFloat}, | |
| {"ssboIndex", VertexAttribType::kInt, SkSLType::kInt}}; | |
| static constexpr Attribute kAttributesWithCurveType[] = { | |
| {"p01", VertexAttribType::kFloat4, SkSLType::kFloat4}, | |
| {"p23", VertexAttribType::kFloat4, SkSLType::kFloat4}, | |
| {"prevPoint", VertexAttribType::kFloat2, SkSLType::kFloat2}, | |
| {"stroke", VertexAttribType::kFloat2, SkSLType::kFloat2}, | |
| {"depth", VertexAttribType::kFloat, SkSLType::kFloat}, | |
| {"curveType", VertexAttribType::kFloat, SkSLType::kFloat}, | |
| {"ssboIndex", VertexAttribType::kInt, SkSLType::kInt}}; | |
| static constexpr SkSpan<const Attribute> kAttributes[2] = {kAttributesWithCurveType, | |
| kBaseAttributes}; | |
| } // namespace | |
| TessellateStrokesRenderStep::TessellateStrokesRenderStep(bool infinitySupport) | |
| : RenderStep("TessellateStrokeRenderStep", | |
| "", | |
| Flags::kRequiresMSAA | Flags::kPerformsShading, | |
| /*uniforms=*/{{"affineMatrix", SkSLType::kFloat4}, | |
| {"translate", SkSLType::kFloat2}, | |
| {"maxScale", SkSLType::kFloat}}, | |
| PrimitiveType::kTriangleStrip, | |
| kDirectDepthGreaterPass, | |
| /*vertexAttrs=*/ {}, | |
| /*instanceAttrs=*/kAttributes[infinitySupport]) | |
| , fInfinitySupport(infinitySupport) {} | |
| TessellateStrokesRenderStep::~TessellateStrokesRenderStep() {} | |
| std::string TessellateStrokesRenderStep::vertexSkSL() const { | |
| // TODO: Assumes vertex ID support for now, max edges must equal | |
| // skgpu::tess::FixedCountStrokes::kMaxEdges -> (2^14 - 1) -> 16383 | |
| return SkSL::String::printf( | |
| R"( | |
| float edgeID = float(sk_VertexID >> 1); | |
| if ((sk_VertexID & 1) != 0) { | |
| edgeID = -edgeID; | |
| } | |
| float2x2 affine = float2x2(affineMatrix.xy, affineMatrix.zw); | |
| float4 devAndLocalCoords = tessellate_stroked_curve( | |
| edgeID, 16383, affine, translate, maxScale, p01, p23, prevPoint, | |
| stroke, %s); | |
| float4 devPosition = float4(devAndLocalCoords.xy, depth, 1.0); | |
| stepLocalCoords = devAndLocalCoords.zw; | |
| )", | |
| fInfinitySupport ? "curve_type_using_inf_support(p23)" : "curveType"); | |
| } | |
| void TessellateStrokesRenderStep::writeVertices(DrawWriter* dw, | |
| const DrawParams& params, | |
| int ssboIndex) const { | |
| SkPath path = params.geometry().shape().asPath(); // TODO: Iterate the Shape directly | |
| int patchReserveCount = FixedCountStrokes::PreallocCount(path.countVerbs()); | |
| // Stroke tessellation does not use fixed indices or vertex data, and only needs the vertex ID | |
| static const BindBufferInfo kNullBinding = {}; | |
| // TODO: All HW that Graphite will run on should support instancing ith sk_VertexID, but when | |
| // we support Vulkan+Swiftshader, we will need the vertex buffer ID fallback unless Swiftshader | |
| // has figured out how to support vertex IDs before then. | |
| Writer writer{fInfinitySupport ? kAttribs : kAttribsWithCurveType, | |
| *dw, | |
| kNullBinding, | |
| kNullBinding, | |
| patchReserveCount}; | |
| writer.updatePaintDepthAttrib(params.order().depthAsFloat()); | |
| writer.updateSsboIndexAttrib(ssboIndex); | |
| // The vector xform approximates how the control points are transformed by the shader to | |
| // more accurately compute how many *parametric* segments are needed. | |
| // getMaxScale() returns -1 if it can't compute a scale factor (e.g. perspective), taking the | |
| // absolute value automatically converts that to an identity scale factor for our purposes. | |
| writer.setShaderTransform(wangs_formula::VectorXform{params.transform().matrix()}, | |
| params.transform().maxScaleFactor()); | |
| SkASSERT(params.isStroke()); | |
| writer.updateStrokeParamsAttrib({params.strokeStyle().halfWidth(), | |
| params.strokeStyle().joinLimit()}); | |
| // TODO: If PatchWriter can handle adding caps to its deferred patches, and we can convert | |
| // hairlines to use round caps instead of square, then StrokeIterator can be deleted entirely. | |
| // Besides being simpler, PatchWriter already has what it needs from the shader matrix and | |
| // stroke params, so we don't have to re-extract them here. | |
| SkMatrix shaderMatrix = params.transform(); | |
| SkStrokeRec stroke{SkStrokeRec::kHairline_InitStyle}; | |
| stroke.setStrokeStyle(params.strokeStyle().width()); | |
| stroke.setStrokeParams(params.strokeStyle().cap(), | |
| params.strokeStyle().join(), | |
| params.strokeStyle().miterLimit()); | |
| StrokeIterator strokeIter(path, &stroke, &shaderMatrix); | |
| while (strokeIter.next()) { | |
| using Verb = StrokeIterator::Verb; | |
| const SkPoint* p = strokeIter.pts(); | |
| int numChops; | |
| // TODO: The cusp detection logic should be moved into PatchWriter and shared between | |
| // this and StrokeTessellator.cpp, but that will require updating a lot of SkGeometry to | |
| // operate on float2 (skvx) instead of the legacy SkNx or SkPoint. | |
| switch (strokeIter.verb()) { | |
| case Verb::kContourFinished: | |
| writer.writeDeferredStrokePatch(); | |
| break; | |
| case Verb::kCircle: | |
| // Round cap or else an empty stroke that is specified to be drawn as a circle. | |
| writer.writeCircle(p[0]); | |
| [[fallthrough]]; | |
| case Verb::kMoveWithinContour: | |
| // A regular kMove invalidates the previous control point; the stroke iterator | |
| // tells us a new value to use. | |
| writer.updateJoinControlPointAttrib(p[0]); | |
| break; | |
| case Verb::kLine: | |
| writer.writeLine(p[0], p[1]); | |
| break; | |
| case Verb::kQuad: | |
| if (ConicHasCusp(p)) { | |
| // The cusp is always at the midtandent. | |
| SkPoint cusp = SkEvalQuadAt(p, SkFindQuadMidTangent(p)); | |
| writer.writeCircle(cusp); | |
| // A quad can only have a cusp if it's flat with a 180-degree turnaround. | |
| writer.writeLine(p[0], cusp); | |
| writer.writeLine(cusp, p[2]); | |
| } else { | |
| writer.writeQuadratic(p); | |
| } | |
| break; | |
| case Verb::kConic: | |
| if (ConicHasCusp(p)) { | |
| // The cusp is always at the midtandent. | |
| SkConic conic(p, strokeIter.w()); | |
| SkPoint cusp = conic.evalAt(conic.findMidTangent()); | |
| writer.writeCircle(cusp); | |
| // A conic can only have a cusp if it's flat with a 180-degree turnaround. | |
| writer.writeLine(p[0], cusp); | |
| writer.writeLine(cusp, p[2]); | |
| } else { | |
| writer.writeConic(p, strokeIter.w()); | |
| } | |
| break; | |
| case Verb::kCubic: | |
| SkPoint chops[10]; | |
| float T[2]; | |
| bool areCusps; | |
| numChops = FindCubicConvex180Chops(p, T, &areCusps); | |
| if (numChops == 0) { | |
| writer.writeCubic(p); | |
| } else if (numChops == 1) { | |
| SkChopCubicAt(p, chops, T[0]); | |
| if (areCusps) { | |
| writer.writeCircle(chops[3]); | |
| // In a perfect world, these 3 points would be be equal after chopping | |
| // on a cusp. | |
| chops[2] = chops[4] = chops[3]; | |
| } | |
| writer.writeCubic(chops); | |
| writer.writeCubic(chops + 3); | |
| } else { | |
| SkASSERT(numChops == 2); | |
| SkChopCubicAt(p, chops, T[0], T[1]); | |
| if (areCusps) { | |
| writer.writeCircle(chops[3]); | |
| writer.writeCircle(chops[6]); | |
| // Two cusps are only possible if it's a flat line with two 180-degree | |
| // turnarounds. | |
| writer.writeLine(chops[0], chops[3]); | |
| writer.writeLine(chops[3], chops[6]); | |
| writer.writeLine(chops[6], chops[9]); | |
| } else { | |
| writer.writeCubic(chops); | |
| writer.writeCubic(chops + 3); | |
| writer.writeCubic(chops + 6); | |
| } | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| void TessellateStrokesRenderStep::writeUniformsAndTextures(const DrawParams& params, | |
| PipelineDataGatherer* gatherer) const { | |
| SkASSERT(params.transform().type() < Transform::Type::kProjection); // TODO: Implement perspective | |
| SkDEBUGCODE(UniformExpectationsValidator uev(gatherer, this->uniforms());) | |
| // affineMatrix = float4 (2x2 of transform), translate = float2, maxScale = float | |
| // Column-major 2x2 of the transform. | |
| SkV4 upper = {params.transform().matrix().rc(0, 0), params.transform().matrix().rc(1, 0), | |
| params.transform().matrix().rc(0, 1), params.transform().matrix().rc(1, 1)}; | |
| gatherer->write(upper); | |
| gatherer->write(SkPoint{params.transform().matrix().rc(0, 3), | |
| params.transform().matrix().rc(1, 3)}); | |
| gatherer->write(params.transform().maxScaleFactor()); | |
| } | |
| } // namespace skgpu::graphite |