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#include "src/gpu/tessellate/PathWedgeTessellator.h"
9cb93a386Sopenharmony_ci
10cb93a386Sopenharmony_ci#include "src/gpu/tessellate/AffineMatrix.h"
11cb93a386Sopenharmony_ci#include "src/gpu/tessellate/PatchWriter.h"
12cb93a386Sopenharmony_ci#include "src/gpu/tessellate/PathCurveTessellator.h"
13cb93a386Sopenharmony_ci#include "src/gpu/tessellate/WangsFormula.h"
14cb93a386Sopenharmony_ci
15cb93a386Sopenharmony_ci#if SK_GPU_V1
16cb93a386Sopenharmony_ci#include "src/gpu/GrMeshDrawTarget.h"
17cb93a386Sopenharmony_ci#include "src/gpu/GrOpFlushState.h"
18cb93a386Sopenharmony_ci#include "src/gpu/GrResourceProvider.h"
19cb93a386Sopenharmony_ci#endif
20cb93a386Sopenharmony_ci
21cb93a386Sopenharmony_cinamespace skgpu {
22cb93a386Sopenharmony_ci
23cb93a386Sopenharmony_ciusing CubicPatch = PatchWriter::CubicPatch;
24cb93a386Sopenharmony_ciusing ConicPatch = PatchWriter::ConicPatch;
25cb93a386Sopenharmony_ci
26cb93a386Sopenharmony_cinamespace {
27cb93a386Sopenharmony_ci
28cb93a386Sopenharmony_ci// Parses out each contour in a path and tracks the midpoint. Example usage:
29cb93a386Sopenharmony_ci//
30cb93a386Sopenharmony_ci//   SkTPathContourParser parser;
31cb93a386Sopenharmony_ci//   while (parser.parseNextContour()) {
32cb93a386Sopenharmony_ci//       SkPoint midpoint = parser.currentMidpoint();
33cb93a386Sopenharmony_ci//       for (auto [verb, pts] : parser.currentContour()) {
34cb93a386Sopenharmony_ci//           ...
35cb93a386Sopenharmony_ci//       }
36cb93a386Sopenharmony_ci//   }
37cb93a386Sopenharmony_ci//
38cb93a386Sopenharmony_ciclass MidpointContourParser {
39cb93a386Sopenharmony_cipublic:
40cb93a386Sopenharmony_ci    MidpointContourParser(const SkPath& path)
41cb93a386Sopenharmony_ci            : fPath(path)
42cb93a386Sopenharmony_ci            , fVerbs(SkPathPriv::VerbData(fPath))
43cb93a386Sopenharmony_ci            , fNumRemainingVerbs(fPath.countVerbs())
44cb93a386Sopenharmony_ci            , fPoints(SkPathPriv::PointData(fPath))
45cb93a386Sopenharmony_ci            , fWeights(SkPathPriv::ConicWeightData(fPath)) {}
46cb93a386Sopenharmony_ci    // Advances the internal state to the next contour in the path. Returns false if there are no
47cb93a386Sopenharmony_ci    // more contours.
48cb93a386Sopenharmony_ci    bool parseNextContour() {
49cb93a386Sopenharmony_ci        bool hasGeometry = false;
50cb93a386Sopenharmony_ci        for (; fVerbsIdx < fNumRemainingVerbs; ++fVerbsIdx) {
51cb93a386Sopenharmony_ci            switch (fVerbs[fVerbsIdx]) {
52cb93a386Sopenharmony_ci                case SkPath::kMove_Verb:
53cb93a386Sopenharmony_ci                    if (!hasGeometry) {
54cb93a386Sopenharmony_ci                        fMidpoint = {0,0};
55cb93a386Sopenharmony_ci                        fMidpointWeight = 0;
56cb93a386Sopenharmony_ci                        this->advance();  // Resets fPtsIdx to 0 and advances fPoints.
57cb93a386Sopenharmony_ci                        fPtsIdx = 1;  // Increment fPtsIdx past the kMove.
58cb93a386Sopenharmony_ci                        continue;
59cb93a386Sopenharmony_ci                    }
60cb93a386Sopenharmony_ci                    if (fPoints[0] != fPoints[fPtsIdx - 1]) {
61cb93a386Sopenharmony_ci                        // There's an implicit close at the end. Add the start point to our mean.
62cb93a386Sopenharmony_ci                        fMidpoint += fPoints[0];
63cb93a386Sopenharmony_ci                        ++fMidpointWeight;
64cb93a386Sopenharmony_ci                    }
65cb93a386Sopenharmony_ci                    return true;
66cb93a386Sopenharmony_ci                default:
67cb93a386Sopenharmony_ci                    continue;
68cb93a386Sopenharmony_ci                case SkPath::kLine_Verb:
69cb93a386Sopenharmony_ci                    ++fPtsIdx;
70cb93a386Sopenharmony_ci                    break;
71cb93a386Sopenharmony_ci                case SkPath::kConic_Verb:
72cb93a386Sopenharmony_ci                    ++fWtsIdx;
73cb93a386Sopenharmony_ci                    [[fallthrough]];
74cb93a386Sopenharmony_ci                case SkPath::kQuad_Verb:
75cb93a386Sopenharmony_ci                    fPtsIdx += 2;
76cb93a386Sopenharmony_ci                    break;
77cb93a386Sopenharmony_ci                case SkPath::kCubic_Verb:
78cb93a386Sopenharmony_ci                    fPtsIdx += 3;
79cb93a386Sopenharmony_ci                    break;
80cb93a386Sopenharmony_ci            }
81cb93a386Sopenharmony_ci            fMidpoint += fPoints[fPtsIdx - 1];
82cb93a386Sopenharmony_ci            ++fMidpointWeight;
83cb93a386Sopenharmony_ci            hasGeometry = true;
84cb93a386Sopenharmony_ci        }
85cb93a386Sopenharmony_ci        if (hasGeometry && fPoints[0] != fPoints[fPtsIdx - 1]) {
86cb93a386Sopenharmony_ci            // There's an implicit close at the end. Add the start point to our mean.
87cb93a386Sopenharmony_ci            fMidpoint += fPoints[0];
88cb93a386Sopenharmony_ci            ++fMidpointWeight;
89cb93a386Sopenharmony_ci        }
90cb93a386Sopenharmony_ci        return hasGeometry;
91cb93a386Sopenharmony_ci    }
92cb93a386Sopenharmony_ci
93cb93a386Sopenharmony_ci    // Allows for iterating the current contour using a range-for loop.
94cb93a386Sopenharmony_ci    SkPathPriv::Iterate currentContour() {
95cb93a386Sopenharmony_ci        return SkPathPriv::Iterate(fVerbs, fVerbs + fVerbsIdx, fPoints, fWeights);
96cb93a386Sopenharmony_ci    }
97cb93a386Sopenharmony_ci
98cb93a386Sopenharmony_ci    SkPoint currentMidpoint() { return fMidpoint * (1.f / fMidpointWeight); }
99cb93a386Sopenharmony_ci
100cb93a386Sopenharmony_ciprivate:
101cb93a386Sopenharmony_ci    void advance() {
102cb93a386Sopenharmony_ci        fVerbs += fVerbsIdx;
103cb93a386Sopenharmony_ci        fNumRemainingVerbs -= fVerbsIdx;
104cb93a386Sopenharmony_ci        fVerbsIdx = 0;
105cb93a386Sopenharmony_ci        fPoints += fPtsIdx;
106cb93a386Sopenharmony_ci        fPtsIdx = 0;
107cb93a386Sopenharmony_ci        fWeights += fWtsIdx;
108cb93a386Sopenharmony_ci        fWtsIdx = 0;
109cb93a386Sopenharmony_ci    }
110cb93a386Sopenharmony_ci
111cb93a386Sopenharmony_ci    const SkPath& fPath;
112cb93a386Sopenharmony_ci
113cb93a386Sopenharmony_ci    const uint8_t* fVerbs;
114cb93a386Sopenharmony_ci    int fNumRemainingVerbs = 0;
115cb93a386Sopenharmony_ci    int fVerbsIdx = 0;
116cb93a386Sopenharmony_ci
117cb93a386Sopenharmony_ci    const SkPoint* fPoints;
118cb93a386Sopenharmony_ci    int fPtsIdx = 0;
119cb93a386Sopenharmony_ci
120cb93a386Sopenharmony_ci    const float* fWeights;
121cb93a386Sopenharmony_ci    int fWtsIdx = 0;
122cb93a386Sopenharmony_ci
123cb93a386Sopenharmony_ci    SkPoint fMidpoint;
124cb93a386Sopenharmony_ci    int fMidpointWeight;
125cb93a386Sopenharmony_ci};
126cb93a386Sopenharmony_ci
127cb93a386Sopenharmony_ci}  // namespace
128cb93a386Sopenharmony_ci
129cb93a386Sopenharmony_ciint PathWedgeTessellator::patchPreallocCount(int totalCombinedPathVerbCnt) const {
130cb93a386Sopenharmony_ci    // Over-allocate enough wedges for 1 in 4 to chop.
131cb93a386Sopenharmony_ci    int maxWedges = MaxCombinedFanEdgesInPathDrawList(totalCombinedPathVerbCnt);
132cb93a386Sopenharmony_ci    return (maxWedges * 5 + 3) / 4;  // i.e., ceil(maxWedges * 5/4)
133cb93a386Sopenharmony_ci}
134cb93a386Sopenharmony_ci
135cb93a386Sopenharmony_civoid PathWedgeTessellator::writePatches(PatchWriter& patchWriter,
136cb93a386Sopenharmony_ci                                        int maxTessellationSegments,
137cb93a386Sopenharmony_ci                                        const SkMatrix& shaderMatrix,
138cb93a386Sopenharmony_ci                                        const PathDrawList& pathDrawList) {
139cb93a386Sopenharmony_ci    float maxSegments_pow2 = pow2(maxTessellationSegments);
140cb93a386Sopenharmony_ci    float maxSegments_pow4 = pow2(maxSegments_pow2);
141cb93a386Sopenharmony_ci
142cb93a386Sopenharmony_ci    // If using fixed count, this is the number of segments we need to emit per instance. Always
143cb93a386Sopenharmony_ci    // emit at least 1 segment.
144cb93a386Sopenharmony_ci    float numFixedSegments_pow4 = 1;
145cb93a386Sopenharmony_ci
146cb93a386Sopenharmony_ci    for (auto [pathMatrix, path, color] : pathDrawList) {
147cb93a386Sopenharmony_ci        AffineMatrix m(pathMatrix);
148cb93a386Sopenharmony_ci        wangs_formula::VectorXform totalXform(SkMatrix::Concat(shaderMatrix, pathMatrix));
149cb93a386Sopenharmony_ci        if (fAttribs & PatchAttribs::kColor) {
150cb93a386Sopenharmony_ci            patchWriter.updateColorAttrib(color);
151cb93a386Sopenharmony_ci        }
152cb93a386Sopenharmony_ci        MidpointContourParser parser(path);
153cb93a386Sopenharmony_ci        while (parser.parseNextContour()) {
154cb93a386Sopenharmony_ci            patchWriter.updateFanPointAttrib(m.mapPoint(parser.currentMidpoint()));
155cb93a386Sopenharmony_ci            SkPoint lastPoint = {0, 0};
156cb93a386Sopenharmony_ci            SkPoint startPoint = {0, 0};
157cb93a386Sopenharmony_ci            for (auto [verb, pts, w] : parser.currentContour()) {
158cb93a386Sopenharmony_ci                switch (verb) {
159cb93a386Sopenharmony_ci                    case SkPathVerb::kMove: {
160cb93a386Sopenharmony_ci                        startPoint = lastPoint = pts[0];
161cb93a386Sopenharmony_ci                        break;
162cb93a386Sopenharmony_ci                    }
163cb93a386Sopenharmony_ci
164cb93a386Sopenharmony_ci                    case SkPathVerb::kLine: {
165cb93a386Sopenharmony_ci                        CubicPatch(patchWriter) << LineToCubic{m.map2Points(pts)};
166cb93a386Sopenharmony_ci                        lastPoint = pts[1];
167cb93a386Sopenharmony_ci                        break;
168cb93a386Sopenharmony_ci                    }
169cb93a386Sopenharmony_ci
170cb93a386Sopenharmony_ci                    case SkPathVerb::kQuad: {
171cb93a386Sopenharmony_ci                        auto [p0, p1] = m.map2Points(pts);
172cb93a386Sopenharmony_ci                        auto p2 = m.map1Point(pts+2);
173cb93a386Sopenharmony_ci                        float n4 = wangs_formula::quadratic_pow4(kTessellationPrecision,
174cb93a386Sopenharmony_ci                                                                 pts,
175cb93a386Sopenharmony_ci                                                                 totalXform);
176cb93a386Sopenharmony_ci                        if (n4 <= maxSegments_pow4) {
177cb93a386Sopenharmony_ci                            // This quad already fits in "maxTessellationSegments".
178cb93a386Sopenharmony_ci                            CubicPatch(patchWriter) << QuadToCubic{p0, p1, p2};
179cb93a386Sopenharmony_ci                        } else {
180cb93a386Sopenharmony_ci                            // Chop until each quad tessellation requires "maxSegments" or fewer.
181cb93a386Sopenharmony_ci                            int numPatches =
182cb93a386Sopenharmony_ci                                    SkScalarCeilToInt(wangs_formula::root4(n4/maxSegments_pow4));
183cb93a386Sopenharmony_ci                            patchWriter.chopAndWriteQuads(p0, p1, p2, numPatches);
184cb93a386Sopenharmony_ci                        }
185cb93a386Sopenharmony_ci                        numFixedSegments_pow4 = std::max(n4, numFixedSegments_pow4);
186cb93a386Sopenharmony_ci                        lastPoint = pts[2];
187cb93a386Sopenharmony_ci                        break;
188cb93a386Sopenharmony_ci                    }
189cb93a386Sopenharmony_ci
190cb93a386Sopenharmony_ci                    case SkPathVerb::kConic: {
191cb93a386Sopenharmony_ci                        auto [p0, p1] = m.map2Points(pts);
192cb93a386Sopenharmony_ci                        auto p2 = m.map1Point(pts+2);
193cb93a386Sopenharmony_ci                        float n2 = wangs_formula::conic_pow2(kTessellationPrecision,
194cb93a386Sopenharmony_ci                                                             pts,
195cb93a386Sopenharmony_ci                                                             *w,
196cb93a386Sopenharmony_ci                                                             totalXform);
197cb93a386Sopenharmony_ci                        if (n2 <= maxSegments_pow2) {
198cb93a386Sopenharmony_ci                            // This conic already fits in "maxTessellationSegments".
199cb93a386Sopenharmony_ci                            ConicPatch(patchWriter) << p0 << p1 << p2 << *w;
200cb93a386Sopenharmony_ci                        } else {
201cb93a386Sopenharmony_ci                            // Chop until each conic tessellation requires "maxSegments" or fewer.
202cb93a386Sopenharmony_ci                            int numPatches = SkScalarCeilToInt(sqrtf(n2/maxSegments_pow2));
203cb93a386Sopenharmony_ci                            patchWriter.chopAndWriteConics(p0, p1, p2, *w, numPatches);
204cb93a386Sopenharmony_ci                        }
205cb93a386Sopenharmony_ci                        numFixedSegments_pow4 = std::max(n2*n2, numFixedSegments_pow4);
206cb93a386Sopenharmony_ci                        lastPoint = pts[2];
207cb93a386Sopenharmony_ci                        break;
208cb93a386Sopenharmony_ci                    }
209cb93a386Sopenharmony_ci
210cb93a386Sopenharmony_ci                    case SkPathVerb::kCubic: {
211cb93a386Sopenharmony_ci                        auto [p0, p1] = m.map2Points(pts);
212cb93a386Sopenharmony_ci                        auto [p2, p3] = m.map2Points(pts+2);
213cb93a386Sopenharmony_ci                        float n4 = wangs_formula::cubic_pow4(kTessellationPrecision,
214cb93a386Sopenharmony_ci                                                             pts,
215cb93a386Sopenharmony_ci                                                             totalXform);
216cb93a386Sopenharmony_ci                        if (n4 <= maxSegments_pow4) {
217cb93a386Sopenharmony_ci                            // This cubic already fits in "maxTessellationSegments".
218cb93a386Sopenharmony_ci                            CubicPatch(patchWriter) << p0 << p1 << p2 << p3;
219cb93a386Sopenharmony_ci                        } else {
220cb93a386Sopenharmony_ci                            // Chop until each cubic tessellation requires "maxSegments" or fewer.
221cb93a386Sopenharmony_ci                            int numPatches =
222cb93a386Sopenharmony_ci                                    SkScalarCeilToInt(wangs_formula::root4(n4/maxSegments_pow4));
223cb93a386Sopenharmony_ci                            patchWriter.chopAndWriteCubics(p0, p1, p2, p3, numPatches);
224cb93a386Sopenharmony_ci                        }
225cb93a386Sopenharmony_ci                        numFixedSegments_pow4 = std::max(n4, numFixedSegments_pow4);
226cb93a386Sopenharmony_ci                        lastPoint = pts[3];
227cb93a386Sopenharmony_ci                        break;
228cb93a386Sopenharmony_ci                    }
229cb93a386Sopenharmony_ci
230cb93a386Sopenharmony_ci                    case SkPathVerb::kClose: {
231cb93a386Sopenharmony_ci                        break;  // Ignore. We can assume an implicit close at the end.
232cb93a386Sopenharmony_ci                    }
233cb93a386Sopenharmony_ci                }
234cb93a386Sopenharmony_ci            }
235cb93a386Sopenharmony_ci            if (lastPoint != startPoint) {
236cb93a386Sopenharmony_ci                SkPoint pts[2] = {lastPoint, startPoint};
237cb93a386Sopenharmony_ci                CubicPatch(patchWriter) << LineToCubic{m.map2Points(pts)};
238cb93a386Sopenharmony_ci            }
239cb93a386Sopenharmony_ci        }
240cb93a386Sopenharmony_ci    }
241cb93a386Sopenharmony_ci
242cb93a386Sopenharmony_ci    // log16(n^4) == log2(n).
243cb93a386Sopenharmony_ci    // We already chopped curves to make sure none needed a higher resolveLevel than
244cb93a386Sopenharmony_ci    // kMaxFixedResolveLevel.
245cb93a386Sopenharmony_ci    fFixedResolveLevel = SkTPin(wangs_formula::nextlog16(numFixedSegments_pow4),
246cb93a386Sopenharmony_ci                                fFixedResolveLevel,
247cb93a386Sopenharmony_ci                                int(kMaxFixedResolveLevel));
248cb93a386Sopenharmony_ci}
249cb93a386Sopenharmony_ci
250cb93a386Sopenharmony_civoid PathWedgeTessellator::WriteFixedVertexBuffer(VertexWriter vertexWriter, size_t bufferSize) {
251cb93a386Sopenharmony_ci    SkASSERT(bufferSize >= sizeof(SkPoint));
252cb93a386Sopenharmony_ci
253cb93a386Sopenharmony_ci    // Start out with the fan point. A negative resolve level indicates the fan point.
254cb93a386Sopenharmony_ci    vertexWriter << -1.f/*resolveLevel*/ << -1.f/*idx*/;
255cb93a386Sopenharmony_ci
256cb93a386Sopenharmony_ci    // The rest is the same as for curves.
257cb93a386Sopenharmony_ci    PathCurveTessellator::WriteFixedVertexBuffer(std::move(vertexWriter),
258cb93a386Sopenharmony_ci                                                 bufferSize - sizeof(SkPoint));
259cb93a386Sopenharmony_ci}
260cb93a386Sopenharmony_ci
261cb93a386Sopenharmony_civoid PathWedgeTessellator::WriteFixedIndexBuffer(VertexWriter vertexWriter, size_t bufferSize) {
262cb93a386Sopenharmony_ci    SkASSERT(bufferSize >= sizeof(uint16_t) * 3);
263cb93a386Sopenharmony_ci
264cb93a386Sopenharmony_ci    // Start out with the fan triangle.
265cb93a386Sopenharmony_ci    vertexWriter << (uint16_t)0 << (uint16_t)1 << (uint16_t)2;
266cb93a386Sopenharmony_ci
267cb93a386Sopenharmony_ci    // The rest is the same as for curves, with a baseIndex of 1.
268cb93a386Sopenharmony_ci    PathCurveTessellator::WriteFixedIndexBufferBaseIndex(std::move(vertexWriter),
269cb93a386Sopenharmony_ci                                                         bufferSize - sizeof(uint16_t) * 3,
270cb93a386Sopenharmony_ci                                                         1);
271cb93a386Sopenharmony_ci}
272cb93a386Sopenharmony_ci
273cb93a386Sopenharmony_ci#if SK_GPU_V1
274cb93a386Sopenharmony_ci
275cb93a386Sopenharmony_ciGR_DECLARE_STATIC_UNIQUE_KEY(gFixedVertexBufferKey);
276cb93a386Sopenharmony_ciGR_DECLARE_STATIC_UNIQUE_KEY(gFixedIndexBufferKey);
277cb93a386Sopenharmony_ci
278cb93a386Sopenharmony_civoid PathWedgeTessellator::prepareFixedCountBuffers(GrMeshDrawTarget* target) {
279cb93a386Sopenharmony_ci    GrResourceProvider* rp = target->resourceProvider();
280cb93a386Sopenharmony_ci
281cb93a386Sopenharmony_ci    GR_DEFINE_STATIC_UNIQUE_KEY(gFixedVertexBufferKey);
282cb93a386Sopenharmony_ci
283cb93a386Sopenharmony_ci    fFixedVertexBuffer = rp->findOrMakeStaticBuffer(GrGpuBufferType::kVertex,
284cb93a386Sopenharmony_ci                                                    FixedVertexBufferSize(kMaxFixedResolveLevel),
285cb93a386Sopenharmony_ci                                                    gFixedVertexBufferKey,
286cb93a386Sopenharmony_ci                                                    WriteFixedVertexBuffer);
287cb93a386Sopenharmony_ci
288cb93a386Sopenharmony_ci    GR_DEFINE_STATIC_UNIQUE_KEY(gFixedIndexBufferKey);
289cb93a386Sopenharmony_ci
290cb93a386Sopenharmony_ci    fFixedIndexBuffer = rp->findOrMakeStaticBuffer(GrGpuBufferType::kIndex,
291cb93a386Sopenharmony_ci                                                   FixedIndexBufferSize(kMaxFixedResolveLevel),
292cb93a386Sopenharmony_ci                                                   gFixedIndexBufferKey,
293cb93a386Sopenharmony_ci                                                   WriteFixedIndexBuffer);
294cb93a386Sopenharmony_ci}
295cb93a386Sopenharmony_ci
296cb93a386Sopenharmony_civoid PathWedgeTessellator::drawTessellated(GrOpFlushState* flushState) const {
297cb93a386Sopenharmony_ci    for (const GrVertexChunk& chunk : fVertexChunkArray) {
298cb93a386Sopenharmony_ci        flushState->bindBuffers(nullptr, nullptr, chunk.fBuffer);
299cb93a386Sopenharmony_ci        flushState->draw(chunk.fCount * 5, chunk.fBase * 5);
300cb93a386Sopenharmony_ci    }
301cb93a386Sopenharmony_ci}
302cb93a386Sopenharmony_ci
303cb93a386Sopenharmony_civoid PathWedgeTessellator::drawFixedCount(GrOpFlushState* flushState) const {
304cb93a386Sopenharmony_ci    if (!fFixedVertexBuffer || !fFixedIndexBuffer) {
305cb93a386Sopenharmony_ci        return;
306cb93a386Sopenharmony_ci    }
307cb93a386Sopenharmony_ci    // Emit 3 vertices per curve triangle, plus 3 more for the fan triangle.
308cb93a386Sopenharmony_ci    int fixedIndexCount = (NumCurveTrianglesAtResolveLevel(fFixedResolveLevel) + 1) * 3;
309cb93a386Sopenharmony_ci    for (const GrVertexChunk& chunk : fVertexChunkArray) {
310cb93a386Sopenharmony_ci        flushState->bindBuffers(fFixedIndexBuffer, chunk.fBuffer, fFixedVertexBuffer);
311cb93a386Sopenharmony_ci        flushState->drawIndexedInstanced(fixedIndexCount, 0, chunk.fCount, chunk.fBase, 0);
312cb93a386Sopenharmony_ci    }
313cb93a386Sopenharmony_ci}
314cb93a386Sopenharmony_ci
315cb93a386Sopenharmony_ci#endif
316cb93a386Sopenharmony_ci
317cb93a386Sopenharmony_ci}  // namespace skgpu
318