1cb93a386Sopenharmony_ci/*
2cb93a386Sopenharmony_ci * Copyright 2021 Google LLC.
3cb93a386Sopenharmony_ci *
4cb93a386Sopenharmony_ci * Use of this source code is governed by a BSD-style license that can be
5cb93a386Sopenharmony_ci * found in the LICENSE file.
6cb93a386Sopenharmony_ci */
7cb93a386Sopenharmony_ci
8cb93a386Sopenharmony_ci#ifndef tessellate_StrokeTessellator_DEFINED
9cb93a386Sopenharmony_ci#define tessellate_StrokeTessellator_DEFINED
10cb93a386Sopenharmony_ci
11cb93a386Sopenharmony_ci#include "include/core/SkPath.h"
12cb93a386Sopenharmony_ci#include "include/core/SkStrokeRec.h"
13cb93a386Sopenharmony_ci#include "include/private/SkColorData.h"
14cb93a386Sopenharmony_ci#include "src/gpu/tessellate/Tessellation.h"
15cb93a386Sopenharmony_ci
16cb93a386Sopenharmony_ciclass GrMeshDrawTarget;
17cb93a386Sopenharmony_ciclass GrOpFlushState;
18cb93a386Sopenharmony_ci
19cb93a386Sopenharmony_cinamespace skgpu {
20cb93a386Sopenharmony_ci
21cb93a386Sopenharmony_ci// Prepares GPU data for, and then draws a stroke's tessellated geometry.
22cb93a386Sopenharmony_ciclass StrokeTessellator {
23cb93a386Sopenharmony_cipublic:
24cb93a386Sopenharmony_ci    struct PathStrokeList {
25cb93a386Sopenharmony_ci        PathStrokeList(const SkPath& path, const SkStrokeRec& stroke, const SkPMColor4f& color)
26cb93a386Sopenharmony_ci                : fPath(path), fStroke(stroke), fColor(color) {}
27cb93a386Sopenharmony_ci        SkPath fPath;
28cb93a386Sopenharmony_ci        SkStrokeRec fStroke;
29cb93a386Sopenharmony_ci        SkPMColor4f fColor;
30cb93a386Sopenharmony_ci        PathStrokeList* fNext = nullptr;
31cb93a386Sopenharmony_ci    };
32cb93a386Sopenharmony_ci
33cb93a386Sopenharmony_ci    StrokeTessellator(PatchAttribs attribs) : fAttribs(attribs) {}
34cb93a386Sopenharmony_ci
35cb93a386Sopenharmony_ci    // Called before draw(). Prepares GPU buffers containing the geometry to tessellate.
36cb93a386Sopenharmony_ci    //
37cb93a386Sopenharmony_ci    // Returns the fixed number of edges the tessellator will draw per patch, if using fixed-count
38cb93a386Sopenharmony_ci    // rendering, otherwise 0.
39cb93a386Sopenharmony_ci    virtual int prepare(GrMeshDrawTarget*,
40cb93a386Sopenharmony_ci                        const SkMatrix& shaderMatrix,
41cb93a386Sopenharmony_ci                        std::array<float,2> matrixMinMaxScales,
42cb93a386Sopenharmony_ci                        PathStrokeList*,
43cb93a386Sopenharmony_ci                        int totalCombinedVerbCnt) = 0;
44cb93a386Sopenharmony_ci
45cb93a386Sopenharmony_ci#if SK_GPU_V1
46cb93a386Sopenharmony_ci    // Issues draw calls for the tessellated stroke. The caller is responsible for creating and
47cb93a386Sopenharmony_ci    // binding a pipeline that uses this class's shader() before calling draw().
48cb93a386Sopenharmony_ci    virtual void draw(GrOpFlushState*) const = 0;
49cb93a386Sopenharmony_ci#endif
50cb93a386Sopenharmony_ci
51cb93a386Sopenharmony_ci    virtual ~StrokeTessellator() {}
52cb93a386Sopenharmony_ci
53cb93a386Sopenharmony_ciprotected:
54cb93a386Sopenharmony_ci    PatchAttribs fAttribs;
55cb93a386Sopenharmony_ci};
56cb93a386Sopenharmony_ci
57cb93a386Sopenharmony_ci// These tolerances decide the number of parametric and radial segments the tessellator will
58cb93a386Sopenharmony_ci// linearize strokes into. These decisions are made in (pre-viewMatrix) local path space.
59cb93a386Sopenharmony_cistruct StrokeTolerances {
60cb93a386Sopenharmony_ci    // Decides the number of parametric segments the tessellator adds for each curve. (Uniform
61cb93a386Sopenharmony_ci    // steps in parametric space.) The tessellator will add enough parametric segments so that,
62cb93a386Sopenharmony_ci    // once transformed into device space, they never deviate by more than
63cb93a386Sopenharmony_ci    // 1/kTessellationPrecision pixels from the true curve.
64cb93a386Sopenharmony_ci    constexpr static float CalcParametricPrecision(float matrixMaxScale) {
65cb93a386Sopenharmony_ci        return matrixMaxScale * kTessellationPrecision;
66cb93a386Sopenharmony_ci    }
67cb93a386Sopenharmony_ci    // Decides the number of radial segments the tessellator adds for each curve. (Uniform steps
68cb93a386Sopenharmony_ci    // in tangent angle.) The tessellator will add this number of radial segments for each
69cb93a386Sopenharmony_ci    // radian of rotation in local path space.
70cb93a386Sopenharmony_ci    static float CalcNumRadialSegmentsPerRadian(float parametricPrecision,
71cb93a386Sopenharmony_ci                                                float strokeWidth) {
72cb93a386Sopenharmony_ci        return .5f / acosf(std::max(1 - 2 / (parametricPrecision * strokeWidth), -1.f));
73cb93a386Sopenharmony_ci    }
74cb93a386Sopenharmony_ci    template<int N> static vec<N> ApproxNumRadialSegmentsPerRadian(float parametricPrecision,
75cb93a386Sopenharmony_ci                                                                   vec<N> strokeWidths) {
76cb93a386Sopenharmony_ci        vec<N> cosTheta = skvx::max(1 - 2 / (parametricPrecision * strokeWidths), -1);
77cb93a386Sopenharmony_ci        // Subtract SKVX_APPROX_ACOS_MAX_ERROR so we never account for too few segments.
78cb93a386Sopenharmony_ci        return .5f / (skvx::approx_acos(cosTheta) - SKVX_APPROX_ACOS_MAX_ERROR);
79cb93a386Sopenharmony_ci    }
80cb93a386Sopenharmony_ci    // Returns the equivalent stroke width in (pre-viewMatrix) local path space that the
81cb93a386Sopenharmony_ci    // tessellator will use when rendering this stroke. This only differs from the actual stroke
82cb93a386Sopenharmony_ci    // width for hairlines.
83cb93a386Sopenharmony_ci    static float GetLocalStrokeWidth(const float matrixMinMaxScales[2], float strokeWidth) {
84cb93a386Sopenharmony_ci        SkASSERT(strokeWidth >= 0);
85cb93a386Sopenharmony_ci        float localStrokeWidth = strokeWidth;
86cb93a386Sopenharmony_ci        if (localStrokeWidth == 0) {  // Is the stroke a hairline?
87cb93a386Sopenharmony_ci            float matrixMinScale = matrixMinMaxScales[0];
88cb93a386Sopenharmony_ci            float matrixMaxScale = matrixMinMaxScales[1];
89cb93a386Sopenharmony_ci            // If the stroke is hairline then the tessellator will operate in post-transform
90cb93a386Sopenharmony_ci            // space instead. But for the sake of CPU methods that need to conservatively
91cb93a386Sopenharmony_ci            // approximate the number of segments to emit, we use
92cb93a386Sopenharmony_ci            // localStrokeWidth ~= 1/matrixMinScale.
93cb93a386Sopenharmony_ci            float approxScale = matrixMinScale;
94cb93a386Sopenharmony_ci            // If the matrix has strong skew, don't let the scale shoot off to infinity. (This
95cb93a386Sopenharmony_ci            // does not affect the tessellator; only the CPU methods that approximate the number
96cb93a386Sopenharmony_ci            // of segments to emit.)
97cb93a386Sopenharmony_ci            approxScale = std::max(matrixMinScale, matrixMaxScale * .25f);
98cb93a386Sopenharmony_ci            localStrokeWidth = 1/approxScale;
99cb93a386Sopenharmony_ci            if (localStrokeWidth == 0) {
100cb93a386Sopenharmony_ci                // We just can't accidentally return zero from this method because zero means
101cb93a386Sopenharmony_ci                // "hairline". Otherwise return whatever we calculated above.
102cb93a386Sopenharmony_ci                localStrokeWidth = SK_ScalarNearlyZero;
103cb93a386Sopenharmony_ci            }
104cb93a386Sopenharmony_ci        }
105cb93a386Sopenharmony_ci        return localStrokeWidth;
106cb93a386Sopenharmony_ci    }
107cb93a386Sopenharmony_ci    static StrokeTolerances Make(const float matrixMinMaxScales[2], float strokeWidth) {
108cb93a386Sopenharmony_ci        return MakeNonHairline(matrixMinMaxScales[1],
109cb93a386Sopenharmony_ci                               GetLocalStrokeWidth(matrixMinMaxScales, strokeWidth));
110cb93a386Sopenharmony_ci    }
111cb93a386Sopenharmony_ci    static StrokeTolerances MakeNonHairline(float matrixMaxScale, float strokeWidth) {
112cb93a386Sopenharmony_ci        SkASSERT(strokeWidth > 0);
113cb93a386Sopenharmony_ci        float parametricPrecision = CalcParametricPrecision(matrixMaxScale);
114cb93a386Sopenharmony_ci        return {parametricPrecision,
115cb93a386Sopenharmony_ci                CalcNumRadialSegmentsPerRadian(parametricPrecision, strokeWidth)};
116cb93a386Sopenharmony_ci    }
117cb93a386Sopenharmony_ci    float fParametricPrecision;
118cb93a386Sopenharmony_ci    float fNumRadialSegmentsPerRadian;
119cb93a386Sopenharmony_ci};
120cb93a386Sopenharmony_ci
121cb93a386Sopenharmony_ci// Calculates and buffers up future values for "numRadialSegmentsPerRadian" using SIMD.
122cb93a386Sopenharmony_ciclass alignas(sizeof(float) * 4) StrokeToleranceBuffer {
123cb93a386Sopenharmony_cipublic:
124cb93a386Sopenharmony_ci    using PathStrokeList = StrokeTessellator::PathStrokeList;
125cb93a386Sopenharmony_ci
126cb93a386Sopenharmony_ci    StrokeToleranceBuffer(float parametricPrecision) : fParametricPrecision(parametricPrecision) {}
127cb93a386Sopenharmony_ci
128cb93a386Sopenharmony_ci    float fetchRadialSegmentsPerRadian(PathStrokeList* head) {
129cb93a386Sopenharmony_ci        // StrokeTessellateOp::onCombineIfPossible does not allow hairlines to become dynamic. If
130cb93a386Sopenharmony_ci        // this changes, we will need to call StrokeTolerances::GetLocalStrokeWidth() for each
131cb93a386Sopenharmony_ci        // stroke.
132cb93a386Sopenharmony_ci        SkASSERT(!head->fStroke.isHairlineStyle());
133cb93a386Sopenharmony_ci        if (fBufferIdx == 4) {
134cb93a386Sopenharmony_ci            // We ran out of values. Peek ahead and buffer up 4 more.
135cb93a386Sopenharmony_ci            PathStrokeList* peekAhead = head;
136cb93a386Sopenharmony_ci            int i = 0;
137cb93a386Sopenharmony_ci            do {
138cb93a386Sopenharmony_ci                fStrokeWidths[i++] = peekAhead->fStroke.getWidth();
139cb93a386Sopenharmony_ci            } while ((peekAhead = peekAhead->fNext) && i < 4);
140cb93a386Sopenharmony_ci            auto tol = StrokeTolerances::ApproxNumRadialSegmentsPerRadian(fParametricPrecision,
141cb93a386Sopenharmony_ci                                                                          fStrokeWidths);
142cb93a386Sopenharmony_ci            tol.store(fNumRadialSegmentsPerRadian);
143cb93a386Sopenharmony_ci            fBufferIdx = 0;
144cb93a386Sopenharmony_ci        }
145cb93a386Sopenharmony_ci        SkASSERT(0 <= fBufferIdx && fBufferIdx < 4);
146cb93a386Sopenharmony_ci        SkASSERT(fStrokeWidths[fBufferIdx] == head->fStroke.getWidth());
147cb93a386Sopenharmony_ci        return fNumRadialSegmentsPerRadian[fBufferIdx++];
148cb93a386Sopenharmony_ci    }
149cb93a386Sopenharmony_ci
150cb93a386Sopenharmony_ciprivate:
151cb93a386Sopenharmony_ci    float4 fStrokeWidths{};  // Must be first for alignment purposes.
152cb93a386Sopenharmony_ci    float fNumRadialSegmentsPerRadian[4];
153cb93a386Sopenharmony_ci    const float fParametricPrecision;
154cb93a386Sopenharmony_ci    int fBufferIdx = 4;  // Initialize the buffer as "empty";
155cb93a386Sopenharmony_ci};
156cb93a386Sopenharmony_ci
157cb93a386Sopenharmony_ci}  // namespace skgpu
158cb93a386Sopenharmony_ci
159cb93a386Sopenharmony_ci#endif  // tessellate_StrokeTessellator_DEFINED
160