xref: /aosp_15_r20/external/skia/gm/beziereffects.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2013 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 // This test only works with the GPU backend.
9 
10 #include "gm/gm.h"
11 #include "include/core/SkBlendMode.h"
12 #include "include/core/SkCanvas.h"
13 #include "include/core/SkMatrix.h"
14 #include "include/core/SkPaint.h"
15 #include "include/core/SkPoint.h"
16 #include "include/core/SkPoint3.h"
17 #include "include/core/SkRect.h"
18 #include "include/core/SkRefCnt.h"
19 #include "include/core/SkScalar.h"
20 #include "include/core/SkSize.h"
21 #include "include/core/SkString.h"
22 #include "include/core/SkTypes.h"
23 #include "include/gpu/ganesh/GrRecordingContext.h"
24 #include "include/private/SkColorData.h"
25 #include "include/private/gpu/ganesh/GrTypesPriv.h"
26 #include "src/base/SkRandom.h"
27 #include "src/core/SkCanvasPriv.h"
28 #include "src/core/SkGeometry.h"
29 #include "src/core/SkPointPriv.h"
30 #include "src/gpu/ganesh/GrCanvas.h"
31 #include "src/gpu/ganesh/GrCaps.h"
32 #include "src/gpu/ganesh/GrDirectContextPriv.h"
33 #include "src/gpu/ganesh/GrGeometryProcessor.h"
34 #include "src/gpu/ganesh/GrMemoryPool.h"
35 #include "src/gpu/ganesh/GrOpFlushState.h"
36 #include "src/gpu/ganesh/GrOpsRenderPass.h"
37 #include "src/gpu/ganesh/GrPaint.h"
38 #include "src/gpu/ganesh/GrProcessorAnalysis.h"
39 #include "src/gpu/ganesh/GrProcessorSet.h"
40 #include "src/gpu/ganesh/GrProgramInfo.h"
41 #include "src/gpu/ganesh/GrRecordingContextPriv.h"
42 #include "src/gpu/ganesh/GrUserStencilSettings.h"
43 #include "src/gpu/ganesh/SurfaceDrawContext.h"
44 #include "src/gpu/ganesh/effects/GrBezierEffect.h"
45 #include "src/gpu/ganesh/effects/GrPorterDuffXferProcessor.h"
46 #include "src/gpu/ganesh/geometry/GrPathUtils.h"
47 #include "src/gpu/ganesh/ops/GrDrawOp.h"
48 #include "src/gpu/ganesh/ops/GrMeshDrawOp.h"
49 #include "src/gpu/ganesh/ops/GrOp.h"
50 #include "src/gpu/ganesh/ops/GrSimpleMeshDrawOpHelper.h"
51 
52 #include <memory>
53 #include <utility>
54 
55 class GrAppliedClip;
56 
57 namespace skiagm {
58 
59 class BezierTestOp : public GrMeshDrawOp {
60 public:
fixedFunctionFlags() const61     FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
62 
finalize(const GrCaps & caps,const GrAppliedClip * clip,GrClampType clampType)63     GrProcessorSet::Analysis finalize(
64             const GrCaps& caps, const GrAppliedClip* clip, GrClampType clampType) override {
65         return fProcessorSet.finalize(
66                 fColor, GrProcessorAnalysisCoverage::kSingleChannel, clip,
67                 &GrUserStencilSettings::kUnused, caps, clampType, &fColor);
68     }
69 
visitProxies(const GrVisitProxyFunc & func) const70     void visitProxies(const GrVisitProxyFunc& func) const override {
71         if (fProgramInfo) {
72             fProgramInfo->visitFPProxies(func);
73         } else {
74             fProcessorSet.visitProxies(func);
75         }
76     }
77 
78 protected:
BezierTestOp(const SkRect & rect,const SkPMColor4f & color,int32_t classID)79     BezierTestOp(const SkRect& rect, const SkPMColor4f& color, int32_t classID)
80             : INHERITED(classID)
81             , fRect(rect)
82             , fColor(color)
83             , fProcessorSet(SkBlendMode::kSrc) {
84         this->setBounds(rect, HasAABloat::kYes, IsHairline::kNo);
85     }
86 
87     virtual GrGeometryProcessor* makeGP(const GrCaps& caps, SkArenaAlloc* arena) = 0;
88 
programInfo()89     GrProgramInfo* programInfo() override { return fProgramInfo; }
90 
onCreateProgramInfo(const GrCaps * caps,SkArenaAlloc * arena,const GrSurfaceProxyView & writeView,bool usesMSAASurface,GrAppliedClip && appliedClip,const GrDstProxyView & dstProxyView,GrXferBarrierFlags renderPassXferBarriers,GrLoadOp colorLoadOp)91     void onCreateProgramInfo(const GrCaps* caps,
92                              SkArenaAlloc* arena,
93                              const GrSurfaceProxyView& writeView,
94                              bool usesMSAASurface,
95                              GrAppliedClip&& appliedClip,
96                              const GrDstProxyView& dstProxyView,
97                              GrXferBarrierFlags renderPassXferBarriers,
98                              GrLoadOp colorLoadOp) override {
99         auto gp = this->makeGP(*caps, arena);
100         if (!gp) {
101             return;
102         }
103 
104         GrPipeline::InputFlags flags = GrPipeline::InputFlags::kNone;
105 
106         fProgramInfo = GrSimpleMeshDrawOpHelper::CreateProgramInfo(caps, arena, writeView,
107                                                                    usesMSAASurface,
108                                                                    std::move(appliedClip),
109                                                                    dstProxyView, gp,
110                                                                    std::move(fProcessorSet),
111                                                                    GrPrimitiveType::kTriangles,
112                                                                    renderPassXferBarriers,
113                                                                    colorLoadOp,
114                                                                    flags);
115     }
116 
onExecute(GrOpFlushState * flushState,const SkRect & chainBounds)117     void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) final {
118         if (!fProgramInfo) {
119             this->createProgramInfo(flushState);
120         }
121 
122         if (!fProgramInfo) {
123             return;
124         }
125 
126         flushState->bindPipelineAndScissorClip(*fProgramInfo, chainBounds);
127         flushState->bindTextures(fProgramInfo->geomProc(), nullptr, fProgramInfo->pipeline());
128         flushState->drawMesh(*fMesh);
129     }
130 
rect() const131     const SkRect& rect() const { return fRect; }
color() const132     const SkPMColor4f& color() const { return fColor; }
133 
134 protected:
135     GrSimpleMesh*        fMesh = nullptr;  // filled in by the derived classes
136 
137 private:
138     SkRect               fRect;
139     SkPMColor4f          fColor;
140     GrProcessorSet       fProcessorSet;
141     GrProgramInfo*       fProgramInfo = nullptr;
142 
143     using INHERITED = GrMeshDrawOp;
144 };
145 
146 /**
147  * This GM directly exercises effects that draw Bezier curves in the GPU backend.
148  */
149 class BezierConicTestOp : public BezierTestOp {
150 public:
151     DEFINE_OP_CLASS_ID
152 
name() const153     const char* name() const final { return "BezierConicTestOp"; }
154 
Make(GrRecordingContext * context,const SkRect & rect,const SkPMColor4f & color,const SkMatrix & klm)155     static GrOp::Owner Make(GrRecordingContext* context,
156                             const SkRect& rect,
157                             const SkPMColor4f& color,
158                             const SkMatrix& klm) {
159         return GrOp::Make<BezierConicTestOp>(context, rect, color, klm);
160     }
161 
162 private:
163     friend class ::GrOp; // for ctor
164 
BezierConicTestOp(const SkRect & rect,const SkPMColor4f & color,const SkMatrix & klm)165     BezierConicTestOp(const SkRect& rect, const SkPMColor4f& color, const SkMatrix& klm)
166             : INHERITED(rect, color, ClassID())
167             , fKLM(klm) {}
168 
169     struct Vertex {
170         SkPoint fPosition;
171         float   fKLM[4]; // The last value is ignored. The effect expects a vec4f.
172     };
173 
makeGP(const GrCaps & caps,SkArenaAlloc * arena)174     GrGeometryProcessor* makeGP(const GrCaps& caps, SkArenaAlloc* arena) final {
175         auto tmp = GrConicEffect::Make(arena, this->color(), SkMatrix::I(), caps, SkMatrix::I(),
176                                        false);
177         if (!tmp) {
178             return nullptr;
179         }
180         SkASSERT(tmp->vertexStride() == sizeof(Vertex));
181         return tmp;
182     }
183 
onPrepareDraws(GrMeshDrawTarget * target)184     void onPrepareDraws(GrMeshDrawTarget* target) final {
185         QuadHelper helper(target, sizeof(Vertex), 1);
186         Vertex* verts = reinterpret_cast<Vertex*>(helper.vertices());
187         if (!verts) {
188             return;
189         }
190         SkRect rect = this->rect();
191         SkPointPriv::SetRectTriStrip(&verts[0].fPosition, rect, sizeof(Vertex));
192         for (int v = 0; v < 4; ++v) {
193             SkPoint3 pt3 = {verts[v].fPosition.x(), verts[v].fPosition.y(), 1.f};
194             fKLM.mapHomogeneousPoints((SkPoint3* ) verts[v].fKLM, &pt3, 1);
195         }
196 
197         fMesh = helper.mesh();
198     }
199 
200     SkMatrix fKLM;
201 
202     inline static constexpr int kVertsPerCubic = 4;
203     inline static constexpr int kIndicesPerCubic = 6;
204 
205     using INHERITED = BezierTestOp;
206 };
207 
208 
209 /**
210  * This GM directly exercises effects that draw Bezier curves in the GPU backend.
211  */
212 class BezierConicEffects : public GpuGM {
213 public:
BezierConicEffects()214     BezierConicEffects() {
215         this->setBGColor(0xFFFFFFFF);
216     }
217 
218 protected:
219     static const int kNumConics = 10;
220     static const int kCellWidth = 128;
221     static const int kCellHeight = 128;
222 
getName() const223     SkString getName() const override { return SkString("bezier_conic_effects"); }
224 
getISize()225     SkISize getISize() override { return SkISize::Make(kCellWidth, kNumConics * kCellHeight); }
226 
onDraw(GrRecordingContext * rContext,SkCanvas * canvas,SkString * errorMsg)227     DrawResult onDraw(GrRecordingContext* rContext, SkCanvas* canvas, SkString* errorMsg) override {
228         auto sdc = skgpu::ganesh::TopDeviceSurfaceDrawContext(canvas);
229         if (!sdc) {
230             *errorMsg = kErrorMsg_DrawSkippedGpuOnly;
231             return DrawResult::kSkip;
232         }
233 
234         const SkScalar w = kCellWidth, h = kCellHeight;
235         const SkPMColor4f kOpaqueBlack = SkPMColor4f::FromBytes_RGBA(0xff000000);
236 
237         const SkPoint baseControlPts[kNumConics][3] = {
238             { { 0.31f * w, 0.01f * h}, { 0.48f * w, 0.74f * h }, { 0.19f * w, 0.33f * h } },
239             { { 0.00f * w, 0.07f * h}, { 0.30f * w, 0.70f * h }, { 0.47f * w, 0.37f * h } },
240             { { 0.15f * w, 0.23f * h}, { 0.49f * w, 0.87f * h }, { 0.85f * w, 0.66f * h } },
241             { { 0.09f * w, 0.15f * h}, { 0.42f * w, 0.33f * h }, { 0.17f * w, 0.38f * h } },
242             { { 0.98f * w, 0.54f * h}, { 0.83f * w, 0.91f * h }, { 0.62f * w, 0.40f * h } },
243             { { 0.96f * w, 0.65f * h}, { 0.03f * w, 0.79f * h }, { 0.24f * w, 0.56f * h } },
244             { { 0.57f * w, 0.12f * h}, { 0.33f * w, 0.67f * h }, { 0.59f * w, 0.33f * h } },
245             { { 0.12f * w, 0.72f * h}, { 0.69f * w, 0.85f * h }, { 0.46f * w, 0.32f * h } },
246             { { 0.27f * w, 0.49f * h}, { 0.41f * w, 0.02f * h }, { 0.11f * w, 0.42f * h } },
247             { { 0.40f * w, 0.13f * h}, { 0.83f * w, 0.30f * h }, { 0.31f * w, 0.68f * h } },
248         };
249         const SkScalar weights[kNumConics] = { 0.62f, 0.01f, 0.95f, 1.48f, 0.37f,
250                                                0.66f, 0.15f, 0.14f, 0.61f, 1.4f };
251 
252         SkPaint ctrlPtPaint;
253         ctrlPtPaint.setColor(SK_ColorRED);
254 
255         SkPaint choppedPtPaint;
256         choppedPtPaint.setColor(~ctrlPtPaint.getColor() | 0xFF000000);
257 
258         SkPaint polyPaint;
259         polyPaint.setColor(0xffA0A0A0);
260         polyPaint.setStrokeWidth(0);
261         polyPaint.setStyle(SkPaint::kStroke_Style);
262 
263         SkPaint boundsPaint;
264         boundsPaint.setColor(0xff808080);
265         boundsPaint.setStrokeWidth(0);
266         boundsPaint.setStyle(SkPaint::kStroke_Style);
267 
268 
269         for (int row = 0; row < kNumConics; ++row) {
270             SkScalar x = 0;
271             SkScalar y = row * h;
272             SkPoint controlPts[] = {
273                 {x + baseControlPts[row][0].fX, y + baseControlPts[row][0].fY},
274                 {x + baseControlPts[row][1].fX, y + baseControlPts[row][1].fY},
275                 {x + baseControlPts[row][2].fX, y + baseControlPts[row][2].fY}
276             };
277 
278             for (int i = 0; i < 3; ++i) {
279                 canvas->drawCircle(controlPts[i], 6.f, ctrlPtPaint);
280             }
281 
282             canvas->drawPoints(SkCanvas::kPolygon_PointMode, 3, controlPts, polyPaint);
283 
284             SkConic dst[4];
285             SkMatrix klm;
286             int cnt = ChopConic(controlPts, dst, weights[row]);
287             GrPathUtils::getConicKLM(controlPts, weights[row], &klm);
288 
289             for (int c = 0; c < cnt; ++c) {
290                 SkPoint* pts = dst[c].fPts;
291                 for (int i = 0; i < 3; ++i) {
292                     canvas->drawCircle(pts[i], 3.f, choppedPtPaint);
293                 }
294 
295                 SkRect bounds;
296                 bounds.setBounds(pts, 3);
297 
298                 canvas->drawRect(bounds, boundsPaint);
299 
300                 GrOp::Owner op = BezierConicTestOp::Make(rContext, bounds,
301                                                          kOpaqueBlack, klm);
302                 sdc->addDrawOp(std::move(op));
303             }
304         }
305 
306         return DrawResult::kOk;
307     }
308 
309 private:
310     // Uses the max curvature function for quads to estimate
311     // where to chop the conic. If the max curvature is not
312     // found along the curve segment it will return 1 and
313     // dst[0] is the original conic. If it returns 2 the dst[0]
314     // and dst[1] are the two new conics.
SplitConic(const SkPoint src[3],SkConic dst[2],const SkScalar weight)315     static int SplitConic(const SkPoint src[3], SkConic dst[2], const SkScalar weight) {
316         SkScalar t = SkFindQuadMaxCurvature(src);
317         if (t == 0 || t == 1) {
318             if (dst) {
319                 dst[0].set(src, weight);
320             }
321             return 1;
322         } else {
323             if (dst) {
324                 SkConic conic;
325                 conic.set(src, weight);
326                 if (!conic.chopAt(t, dst)) {
327                     dst[0].set(src, weight);
328                     return 1;
329                 }
330             }
331             return 2;
332         }
333     }
334 
335     // Calls SplitConic on the entire conic and then once more on each subsection.
336     // Most cases will result in either 1 conic (chop point is not within t range)
337     // or 3 points (split once and then one subsection is split again).
ChopConic(const SkPoint src[3],SkConic dst[4],const SkScalar weight)338     static int ChopConic(const SkPoint src[3], SkConic dst[4], const SkScalar weight) {
339         SkConic dstTemp[2];
340         int conicCnt = SplitConic(src, dstTemp, weight);
341         if (2 == conicCnt) {
342             int conicCnt2 = SplitConic(dstTemp[0].fPts, dst, dstTemp[0].fW);
343             conicCnt = conicCnt2 + SplitConic(dstTemp[1].fPts, &dst[conicCnt2], dstTemp[1].fW);
344         } else {
345             dst[0] = dstTemp[0];
346         }
347         return conicCnt;
348     }
349 
350     using INHERITED = GM;
351 };
352 
353 //////////////////////////////////////////////////////////////////////////////
354 
355 class BezierQuadTestOp : public BezierTestOp {
356 public:
357     DEFINE_OP_CLASS_ID
name() const358     const char* name() const override { return "BezierQuadTestOp"; }
359 
Make(GrRecordingContext * context,const SkRect & rect,const SkPMColor4f & color,const GrPathUtils::QuadUVMatrix & devToUV)360     static GrOp::Owner Make(GrRecordingContext* context,
361                             const SkRect& rect,
362                             const SkPMColor4f& color,
363                             const GrPathUtils::QuadUVMatrix& devToUV) {
364         return GrOp::Make<BezierQuadTestOp>(context, rect, color, devToUV);
365     }
366 
367 private:
368     friend class ::GrOp; // for ctor
369 
BezierQuadTestOp(const SkRect & rect,const SkPMColor4f & color,const GrPathUtils::QuadUVMatrix & devToUV)370     BezierQuadTestOp(const SkRect& rect, const SkPMColor4f& color,
371                      const GrPathUtils::QuadUVMatrix& devToUV)
372             : INHERITED(rect, color, ClassID())
373             , fDevToUV(devToUV) {}
374 
375     struct Vertex {
376         SkPoint fPosition;
377         float   fKLM[4]; // The last value is ignored. The effect expects a vec4f.
378     };
379 
makeGP(const GrCaps & caps,SkArenaAlloc * arena)380     GrGeometryProcessor* makeGP(const GrCaps& caps, SkArenaAlloc* arena) final {
381         auto tmp = GrQuadEffect::Make(arena, this->color(), SkMatrix::I(), caps, SkMatrix::I(),
382                                       false);
383         if (!tmp) {
384             return nullptr;
385         }
386         SkASSERT(tmp->vertexStride() == sizeof(Vertex));
387         return tmp;
388     }
389 
onPrepareDraws(GrMeshDrawTarget * target)390     void onPrepareDraws(GrMeshDrawTarget* target) final {
391         QuadHelper helper(target, sizeof(Vertex), 1);
392         Vertex* verts = reinterpret_cast<Vertex*>(helper.vertices());
393         if (!verts) {
394             return;
395         }
396         SkRect rect = this->rect();
397         SkPointPriv::SetRectTriStrip(&verts[0].fPosition, rect, sizeof(Vertex));
398         fDevToUV.apply(verts, 4, sizeof(Vertex), sizeof(SkPoint));
399 
400         fMesh = helper.mesh();
401     }
402 
403     GrPathUtils::QuadUVMatrix fDevToUV;
404 
405     inline static constexpr int kVertsPerCubic = 4;
406     inline static constexpr int kIndicesPerCubic = 6;
407 
408     using INHERITED = BezierTestOp;
409 };
410 
411 /**
412  * This GM directly exercises effects that draw Bezier quad curves in the GPU backend.
413  */
414 class BezierQuadEffects : public GpuGM {
415 public:
BezierQuadEffects()416     BezierQuadEffects() {
417         this->setBGColor(0xFFFFFFFF);
418     }
419 
420 protected:
421     static const int kNumQuads = 5;
422     static const int kCellWidth = 128;
423     static const int kCellHeight = 128;
424 
getName() const425     SkString getName() const override { return SkString("bezier_quad_effects"); }
426 
getISize()427     SkISize getISize() override { return SkISize::Make(kCellWidth, kNumQuads * kCellHeight); }
428 
onDraw(GrRecordingContext * rContext,SkCanvas * canvas,SkString * errorMsg)429     DrawResult onDraw(GrRecordingContext* rContext, SkCanvas* canvas, SkString* errorMsg) override {
430         auto sdc = skgpu::ganesh::TopDeviceSurfaceDrawContext(canvas);
431         if (!sdc) {
432             *errorMsg = kErrorMsg_DrawSkippedGpuOnly;
433             return DrawResult::kSkip;
434         }
435 
436         const SkScalar w = kCellWidth, h = kCellHeight;
437         const SkPMColor4f kOpaqueBlack = SkPMColor4f::FromBytes_RGBA(0xff000000);
438 
439         const SkPoint baseControlPts[kNumQuads][3] = {
440             { { 0.31f * w, 0.01f * h}, { 0.48f * w, 0.74f * h }, { 0.19f * w, 0.33f * h } },
441             { { 0.00f * w, 0.07f * h}, { 0.30f * w, 0.70f * h }, { 0.47f * w, 0.37f * h } },
442             { { 0.15f * w, 0.23f * h}, { 0.49f * w, 0.87f * h }, { 0.85f * w, 0.66f * h } },
443             { { 0.09f * w, 0.15f * h}, { 0.42f * w, 0.33f * h }, { 0.17f * w, 0.38f * h } },
444             { { 0.98f * w, 0.54f * h}, { 0.83f * w, 0.91f * h }, { 0.62f * w, 0.40f * h } },
445         };
446 
447         SkPaint ctrlPtPaint;
448         ctrlPtPaint.setColor(SK_ColorRED);
449 
450         SkPaint choppedPtPaint;
451         choppedPtPaint.setColor(~ctrlPtPaint.getColor() | 0xFF000000);
452 
453         SkPaint polyPaint;
454         polyPaint.setColor(0xffA0A0A0);
455         polyPaint.setStrokeWidth(0);
456         polyPaint.setStyle(SkPaint::kStroke_Style);
457 
458         SkPaint boundsPaint;
459         boundsPaint.setColor(0xff808080);
460         boundsPaint.setStrokeWidth(0);
461         boundsPaint.setStyle(SkPaint::kStroke_Style);
462 
463         for (int row = 0; row < kNumQuads; ++row) {
464             SkScalar x = 0;
465             SkScalar y = row * h;
466             SkPoint controlPts[] = {
467                 {x + baseControlPts[row][0].fX, y + baseControlPts[row][0].fY},
468                 {x + baseControlPts[row][1].fX, y + baseControlPts[row][1].fY},
469                 {x + baseControlPts[row][2].fX, y + baseControlPts[row][2].fY}
470             };
471 
472             for (int i = 0; i < 3; ++i) {
473                 canvas->drawCircle(controlPts[i], 6.f, ctrlPtPaint);
474             }
475 
476             canvas->drawPoints(SkCanvas::kPolygon_PointMode, 3, controlPts, polyPaint);
477 
478             SkPoint chopped[5];
479             int cnt = SkChopQuadAtMaxCurvature(controlPts, chopped);
480 
481             for (int c = 0; c < cnt; ++c) {
482                 SkPoint* pts = chopped + 2 * c;
483 
484                 for (int i = 0; i < 3; ++i) {
485                     canvas->drawCircle(pts[i], 3.f, choppedPtPaint);
486                 }
487 
488                 SkRect bounds;
489                 bounds.setBounds(pts, 3);
490 
491                 canvas->drawRect(bounds, boundsPaint);
492 
493                 GrPathUtils::QuadUVMatrix DevToUV(pts);
494 
495                 GrOp::Owner op = BezierQuadTestOp::Make(rContext, bounds,
496                                                         kOpaqueBlack, DevToUV);
497                 sdc->addDrawOp(std::move(op));
498             }
499         }
500 
501         return DrawResult::kOk;
502     }
503 
504 private:
505     using INHERITED = GM;
506 };
507 
508 DEF_GM(return new BezierConicEffects;)
509 DEF_GM(return new BezierQuadEffects;)
510 }  // namespace skiagm
511