xref: /aosp_15_r20/external/skia/tools/viewer/MeshGradientSlide.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2024 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 #include "include/core/SkCanvas.h"
9 #include "include/core/SkColor.h"
10 #include "include/core/SkCubicMap.h"
11 #include "include/core/SkFont.h"
12 #include "include/core/SkPicture.h"
13 #include "include/core/SkPictureRecorder.h"
14 #include "include/core/SkPoint.h"
15 #include "include/core/SkRefCnt.h"
16 #include "include/core/SkShader.h"
17 #include "include/core/SkString.h"
18 #include "include/core/SkVertices.h"
19 #include "include/effects/SkRuntimeEffect.h"
20 #include "include/private/base/SkDebug.h"
21 #include "src/base/SkRandom.h"
22 #include "tools/fonts/FontToolUtils.h"
23 #include "tools/viewer/Slide.h"
24 
25 #include <cmath>
26 #include <cstddef>
27 #include <limits>
28 #include <vector>
29 
30 #include "delaunator.hpp"
31 #include "imgui.h"
32 
33 namespace {
34 
triangulate_pts(const std::vector<SkPoint> & pts,const std::vector<SkColor> & colors)35 sk_sp<SkVertices> triangulate_pts(const std::vector<SkPoint>& pts, const std::vector<SkColor>& colors) {
36     // put points in the format delaunator wants
37     std::vector<double> coords;
38     for (size_t i = 0; i < pts.size(); ++i) {
39         coords.push_back(pts[i].x());
40         coords.push_back(pts[i].y());
41     }
42 
43     // triangulation happens here
44     delaunator::Delaunator d(coords);
45 
46     // SkVertices parameters
47     std::vector<SkPoint> vertices;
48     std::vector<uint16_t> indices;
49 
50     // populate vertices & colors
51     for(std::size_t i = 0; i < d.coords.size(); i+=2) {
52         vertices.push_back(SkPoint::Make(d.coords[i], d.coords[i+1]));
53     }
54 
55     // populate triangle indices
56     for(std::size_t i = 0; i < d.triangles.size(); i+=3) {
57         indices.push_back(d.triangles[i]);
58         indices.push_back(d.triangles[i+1]);
59         indices.push_back(d.triangles[i+2]);
60     }
61 
62     return SkVertices::MakeCopy(SkVertices::kTriangles_VertexMode, vertices.size(), vertices.data(), nullptr, colors.data(), indices.size(), indices.data());
63 }
64 
makeGradientShader(int w,int h,std::vector<SkPoint> & vertices,std::vector<SkColor> & colors)65 sk_sp<SkShader> makeGradientShader(int w, int h, std::vector<SkPoint>& vertices, std::vector<SkColor>& colors) {
66     vertices.push_back(SkPoint::Make(0.f, 0.f));
67     vertices.push_back(SkPoint::Make(w, 0.f));
68     vertices.push_back(SkPoint::Make(0.f, h));
69     vertices.push_back(SkPoint::Make(w, h));
70 
71     colors.push_back(SK_ColorTRANSPARENT);
72     colors.push_back(SK_ColorTRANSPARENT);
73     colors.push_back(SK_ColorTRANSPARENT);
74     colors.push_back(SK_ColorTRANSPARENT);
75 
76     sk_sp<SkVertices> sk_vertices = triangulate_pts(vertices, colors);
77 
78     // record with a picture
79     SkRect tile = SkRect::MakeWH(w, h);
80     SkPictureRecorder recorder;
81     SkCanvas* c = recorder.beginRecording(tile);
82 
83     SkPaint p;
84     p.setColor(SK_ColorWHITE);
85     c->drawVertices(sk_vertices, SkBlendMode::kModulate, p);
86     sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture());
87 
88     return picture->makeShader(SkTileMode::kDecal, SkTileMode::kDecal, SkFilterMode::kNearest);
89 }
90 
91 class GradientRenderer : public SkRefCnt {
92 public:
93     virtual void draw(SkCanvas*) const = 0;
94 
95     virtual sk_sp<SkShader> asShader() const = 0;
96 
97     virtual void updateVertices(SkSpan<const SkPoint> vert_pos,
98                                 SkSpan<const SkColor4f> vert_colors) = 0;
99 };
100 
101 class SkSlRenderer : public GradientRenderer {
102 public:
draw(SkCanvas * canvas) const103     void draw(SkCanvas* canvas) const override {
104         SkPaint paint;
105         paint.setShader(fShader);
106         canvas->drawRect(SkRect::MakeWH(1, 1), paint);
107     }
108 
asShader() const109     sk_sp<SkShader> asShader() const override { return fShader; }
110 
updateVertices(SkSpan<const SkPoint> vert_pos,SkSpan<const SkColor4f> vert_colors)111     void updateVertices(SkSpan<const SkPoint> vert_pos,
112                         SkSpan<const SkColor4f> vert_colors) override {
113         SkASSERT(vert_pos.size() == vert_colors.size());
114         const auto vert_count = vert_pos.size();
115 
116         if (!vert_count) {
117             return;
118         }
119 
120         // Effect compilation is expensive, so we cache and only recompile when the count changes.
121         if (vert_count != fCachedCount) {
122             this->buildEffect(vert_count);
123             fCachedCount = vert_count;
124         }
125 
126         SkRuntimeEffectBuilder builder(fEffect);
127         builder.uniform("u_vertcolors").set(vert_colors.data(), vert_colors.size());
128         builder.uniform("u_vertpos")   .set(vert_pos.data()   , vert_pos.size());
129 
130         fShader = builder.makeShader();
131     }
132     virtual void buildEffect(size_t vert_count) = 0;
133 
134 protected:
135     sk_sp<SkRuntimeEffect> fEffect;
136     sk_sp<SkShader>        fShader;
137 
138     size_t                 fCachedCount = 0;
139 };
140 
141 class AEGradientRenderer final : public SkSlRenderer {
142 public:
buildEffect(size_t vert_count)143     void buildEffect(size_t vert_count) override {
144         static constexpr char gAEGradientSkSL[] =
145             "uniform half4  u_vertcolors[%zu];"
146             "uniform float2 u_vertpos[%zu];"
147 
148             "half4 main(float2 xy) {"
149                 "half4 c = half4(0);"
150                 "float w_acc = 0;"
151 
152                 "for (int i = 0; i < %zu; ++i) {"
153                     "float d = distance(xy, u_vertpos[i]);"
154                     "float w = 1 / (d * d);"
155 
156                     "c += u_vertcolors[i] * w;"
157                     "w_acc += w;"
158                 "}"
159 
160                 "return c / w_acc;"
161             "}";
162 
163         const auto res = SkRuntimeEffect::MakeForShader(
164                             SkStringPrintf(gAEGradientSkSL, vert_count, vert_count, vert_count));
165         if (!res.effect) {
166             SkDEBUGF("%s\n", res.errorText.c_str());
167         }
168 
169         fEffect = res.effect;
170 
171         SkASSERT(fEffect);
172     }
173 };
174 
175 class LinearGradientRenderer final : public SkSlRenderer {
176 public:
buildEffect(size_t vert_count)177     void buildEffect(size_t vert_count) override {
178         static constexpr char sksl[] =
179             "uniform half4  u_vertcolors[%zu];"
180             "uniform float2 u_vertpos[%zu];"
181 
182             "half4 main(float2 xy) {"
183                 "float v[%zu];"
184                 "for (int i = 0; i < %zu; i++) {"
185                     "v[i] = 1.;"
186                 "}"
187 
188                 "for (int i = 0; i < %zu; ++i) {"
189                     "for (int j = 0; j < %zu; ++j) {"
190                         "vec2 delta;"
191                         "delta.x = u_vertpos[j].x - u_vertpos[i].x;"
192                         "delta.y = u_vertpos[j].y - u_vertpos[i].y;"
193 
194                         "mat3 m = mat3 ("
195                             "delta.x, delta.y, 0.,"                 // 1st column
196                             "-delta.y, delta.x, 0.,"                // 2nd column
197                             "u_vertpos[i].x, u_vertpos[i].y, 1."    // 3rd column
198                         ");"
199                         "mat3 m_inv = inverse(m);"
200 
201                         "vec3 p_h = vec3(xy.x, xy.y, 1.);"
202                         "vec3 u = m_inv*p_h;"
203                         "float t = u.x;"
204 
205                         "if (t < 0) {"
206                             "v[j] = 0;"
207                         "} else if (t > 1) {"
208                             "v[i] = 0;"
209                         "} else {"
210                             "v[i] *= 1-t;"
211                             "v[j] *= t;"
212                         "}"
213                     "}"
214                 "}"
215 
216                 "half4 c = half4(0);"
217                 "float w_acc = 0;"
218                 "for (int i = 0; i < %zu; i++) {"
219                     "c += u_vertcolors[i] * v[i];"
220                     "w_acc += v[i];"
221                 "}"
222 
223                 "return c / w_acc;"
224             "}";
225 
226         const auto res = SkRuntimeEffect::MakeForShader(
227                             SkStringPrintf(sksl, vert_count, vert_count, vert_count, vert_count, vert_count, vert_count, vert_count));
228         if (!res.effect) {
229             SkDEBUGF("%s\n", res.errorText.c_str());
230         }
231 
232         fEffect = res.effect;
233 
234         SkASSERT(fEffect);
235     }
236 };
237 
238 class IllGradientRenderer final : public SkSlRenderer {
239 public:
buildEffect(size_t vert_count)240     void buildEffect(size_t vert_count) override {
241         static constexpr char sksl[] =
242             "uniform half4  u_vertcolors[%zu];"
243             "uniform float2 u_vertpos[%zu];"
244 
245             "half4 main(float2 xy) {"
246                 "float d[%zu];"
247                 "for (int i = 0; i < %zu; i++) {"
248                     "d[i] = 0.;"
249                 "}"
250 
251                 "for (int i = 0; i < %zu; ++i) {"
252                     "for (int j = 0; j < %zu; ++j) {"
253                         "vec2 delta;"
254                         "delta.x = u_vertpos[j].x - u_vertpos[i].x;"
255                         "delta.y = u_vertpos[j].y - u_vertpos[i].y;"
256 
257                         "mat3 m = mat3 ("
258                             "delta.x, delta.y, 0.,"                 // 1st column
259                             "-delta.y, delta.x, 0.,"                // 2nd column
260                             "u_vertpos[i].x, u_vertpos[i].y, 1."    // 3rd column
261                         ");"
262                         "mat3 m_inv = inverse(m);"
263 
264                         "vec3 p_h = vec3(xy.x, xy.y, 1.);"
265                         "vec3 u = m_inv*p_h;"
266                         "float t = u.x;"
267 
268                         "float s = length(delta);"
269                         "if (t < 0) {"
270                             "d[i] += s*abs(u.y);"
271                             "d[j] += s*distance(vec2(u.x, u.y), vec2(1., 0.));"
272                         "} else if (t > 1) {"
273                             "d[j] += s*abs(u.y);"
274                             "d[i] += s*distance(vec2(u.x, u.y), vec2(0., 0.));"
275                         "} else {"
276                             "d[i] += s*distance(vec2(u.x, u.y), vec2(0., 0.));"
277                             "d[j] += s*distance(vec2(u.x, u.y), vec2(1., 0.));"
278                         "}"
279                     "}"
280                 "}"
281 
282                 "half4 c = half4(0);"
283                 "float w_acc = 0;"
284                 "for (int i = 0; i < %zu; i++) {"
285                     "float w = 1 / (d[i] * d[i]);"
286                     "c += u_vertcolors[i] * w;"
287                     "w_acc += w;"
288                 "}"
289 
290                 "return c / w_acc;"
291             "}";
292 
293         const auto res = SkRuntimeEffect::MakeForShader(
294                             SkStringPrintf(sksl, vert_count, vert_count, vert_count, vert_count, vert_count, vert_count, vert_count));
295         if (!res.effect) {
296             SkDEBUGF("%s\n", res.errorText.c_str());
297         }
298 
299         fEffect = res.effect;
300 
301         SkASSERT(fEffect);
302     }
303 };
304 
305 class TriangulatedGradientRenderer final : public GradientRenderer {
306 public:
draw(SkCanvas * canvas) const307     void draw(SkCanvas* canvas) const override {
308         SkPaint paint;
309         paint.setShader(fShader);
310         canvas->drawRect(SkRect::MakeWH(1, 1), paint);
311     }
312 
asShader() const313     sk_sp<SkShader> asShader() const override { return fShader; }
314 
updateVertices(SkSpan<const SkPoint> vert_pos,SkSpan<const SkColor4f> vert_colors)315     void updateVertices(SkSpan<const SkPoint> vert_pos,
316                         SkSpan<const SkColor4f> vert_colors) override {
317         SkASSERT(vert_pos.size() == vert_colors.size());
318         const auto vert_count = vert_pos.size();
319 
320         if (!vert_count) {
321             return;
322         }
323 
324         std::vector<SkPoint> pos;
325         for (auto& p : vert_pos) {
326             pos.push_back(p);
327         }
328         std::vector<SkColor> colors;
329         for (auto& c : vert_colors) {
330             colors.push_back(c.toSkColor());
331         }
332 
333         fShader = makeGradientShader(1, 1, pos, colors);
334     }
335 
336 private:
337     sk_sp<SkShader> fShader;
338 };
339 
340 static constexpr struct RendererChoice {
341     const char* fName;
342     sk_sp<GradientRenderer>(*fFactory)();
343 } gGradientRenderers[] = {
344     {
345         "AfterEffects Gradient",
__anonc8458bdc0202() 346         []() -> sk_sp<GradientRenderer> { return sk_make_sp<AEGradientRenderer>(); }
347     },
348     {
349         "n-Linear gradient",
__anonc8458bdc0302() 350         []() -> sk_sp<GradientRenderer> { return sk_make_sp<LinearGradientRenderer>(); }
351     },
352     {
353         "Illustrator (attempt) gradient",
__anonc8458bdc0402() 354         []() -> sk_sp<GradientRenderer> { return sk_make_sp<IllGradientRenderer>(); }
355     },
356     {
357         "Triangulated gradient",
__anonc8458bdc0502() 358         []() -> sk_sp<GradientRenderer> { return sk_make_sp<TriangulatedGradientRenderer>(); }
359     }
360 };
361 
lerp(float a,float b,float t)362 float lerp(float a, float b, float t) {
363     return a + (b - a)*t;
364 }
365 
366 static constexpr struct VertexAnimator {
367     const char* fName;
368     void (*fAanimate)(SkSpan<const SkPoint> uvs, SkSpan<SkPoint> pos, float t);
369 } gVertexAnimators[] = {
370     {
371         "Wigglynator",
__anonc8458bdc0602(SkSpan<const SkPoint> uvs, SkSpan<SkPoint> pos, float t) 372         [](SkSpan<const SkPoint> uvs, SkSpan<SkPoint> pos, float t) {
373             const float radius = t*0.2f/(std::sqrt(uvs.size()) - 1);
374             for (size_t i = 0; i < uvs.size(); ++i) {
375                 const float phase = i*SK_FloatPI*0.31f,
376                             angle = phase + t*SK_FloatPI*2;
377                 pos[i] = uvs[i] + SkVector{
378                     radius*std::cos(angle),
379                     radius*std::sin(angle),
380                 };
381             }
382         },
383     },
384     {
385         "Squircillator",
386         // Pull all vertices towards center, proportionally, such that the outer square edge
387         // is mapped to a circle for t == 1.
__anonc8458bdc0702(SkSpan<const SkPoint> uvs, SkSpan<SkPoint> pos, float t) 388         [](SkSpan<const SkPoint> uvs, SkSpan<SkPoint> pos, float t) {
389             for (size_t i = 0; i < uvs.size(); ++i) {
390                 // remap to [-.5,.5]
391                 const auto uv = (uvs[i] - SkPoint{0.5,0.5});
392                 // can't allow len to collapse to zero, lest bad things happen at {0.5, 0.5}
393                 const auto len = std::max(uv.length(), std::numeric_limits<float>::min());
394 
395                 // Distance from center to outer edge for the line pasing through uv.
396                 const auto d = len*0.5f/std::max(std::abs(uv.fX), std::abs(uv.fY));
397                 // Scale needed to pull the outer edge to the r=0.5 circle at t == 1.
398                 const auto s = lerp(1, (0.5f / d), t);
399 
400                 pos[i] = uv*s + SkPoint{0.5, 0.5};
401             }
402         },
403     },
404     {
405         "Twirlinator",
406         // Rotate vertices proportional to their distance to center.
__anonc8458bdc0802(SkSpan<const SkPoint> uvs, SkSpan<SkPoint> pos, float t) 407         [](SkSpan<const SkPoint> uvs, SkSpan<SkPoint> pos, float t) {
408             static constexpr float kMaxRotate = SK_FloatPI*4;
409 
410             for (size_t i = 0; i < uvs.size(); ++i) {
411                 // remap to [-.5,.5]
412                 const auto uv = (uvs[i] - SkPoint{0.5,0.5});
413                 const auto angle = kMaxRotate * t * uv.length();
414 
415                 pos[i] = SkMatrix::RotateRad(angle).mapPoint(uv) + SkPoint{0.5, 0.5};
416             }
417         },
418     },
419     {
420         "Cylinderator",
421         // Simulate a cylinder rolling sideways across the 1x1 uv space.
__anonc8458bdc0902(SkSpan<const SkPoint> uvs, SkSpan<SkPoint> pos, float t) 422         [](SkSpan<const SkPoint> uvs, SkSpan<SkPoint> pos, float t) {
423             static constexpr float kCylRadius = .2f;
424 
425             const auto cyl_pos = t;
426 
427             for (size_t i = 0; i < uvs.size(); ++i) {
428                 if (uvs[i].fX <= cyl_pos) {
429                     pos[i] = uvs[i];
430                     continue;
431                 }
432 
433                 const auto arc_len = uvs[i].fX - cyl_pos,
434                            arc_ang = arc_len/kCylRadius;
435 
436                 pos[i] = SkPoint{
437                     cyl_pos + std::sin(arc_ang)*kCylRadius,
438                     uvs[i].fY,
439                 };
440             }
441         },
442     },
443     {
444         "None",
__anonc8458bdc0a02(SkSpan<const SkPoint> uvs, SkSpan<SkPoint> pos, float t) 445         [](SkSpan<const SkPoint> uvs, SkSpan<SkPoint> pos, float t) {
446             memcpy(pos.data(), uvs.data(), uvs.size() * sizeof(SkPoint));
447         },
448     },
449 };
450 
451 class MeshGradientSlide final : public Slide {
452 public:
MeshGradientSlide()453     MeshGradientSlide()
454         : fCurrentRenderer(gGradientRenderers[0].fFactory())
455         , fFont(ToolUtils::DefaultPortableTypeface(), .75f)
456         , fTimeMapper({0.5f, 0}, {0.5f, 1})
457         , fVertedCountTimeMapper({0, 0.5f}, {0.5f, 1})
458     {
459             fName = "MeshGradient";
460     }
461 
load(SkScalar w,SkScalar h)462     void load(SkScalar w, SkScalar h) override {
463         fSize = {w, h};
464     }
465 
resize(SkScalar w,SkScalar h)466     void resize(SkScalar w, SkScalar h) override { fSize = {w, h}; }
467 
draw(SkCanvas * canvas)468     void draw(SkCanvas* canvas) override {
469         SkAutoCanvasRestore acr(canvas, true);
470 
471         static constexpr float kMeshViewFract = 0.85f;
472         const float mesh_size = std::min(fSize.fWidth, fSize.fHeight) * kMeshViewFract;
473 
474         canvas->translate((fSize.fWidth  - mesh_size) * 0.5f,
475                           (fSize.fHeight - mesh_size) * 0.5f);
476         canvas->scale(mesh_size, mesh_size);
477 
478         if (fShaderFill) {
479             SkPaint paint;
480             paint.setAntiAlias(true);
481             paint.setShader(fCurrentRenderer->asShader());
482             canvas->drawString("SK", 0, 0.75f, fFont, paint);
483         } else {
484             fCurrentRenderer->draw(canvas);
485         }
486 
487         this->drawControls();
488     }
489 
animate(double nanos)490     bool animate(double nanos) override {
491         // Mesh count animation
492         {
493             if (!fVertexCountTimeBase) {
494                 fVertexCountTimeBase = nanos;
495             }
496 
497             static constexpr float kSizeAdjustSeconds = 2;
498             const float t = (nanos - fVertexCountTimeBase) * 0.000000001 / kSizeAdjustSeconds;
499 
500             this->updateMesh(lerp(fVertexCountFrom,
501                                   fVertexCountTo,
502                                   fVertedCountTimeMapper.computeYFromX(t)));
503         }
504 
505         // Vertex animation
506         {
507             if (!fTimeBase) {
508                 fTimeBase = nanos;
509             }
510 
511             const float t =
512                 std::abs((std::fmod((nanos - fTimeBase) * 0.000000001 * fAnimationSpeed, 2) - 1));
513 
514             fCurrentAnimator->fAanimate(fVertUVs, fVertPos, fTimeMapper.computeYFromX(t));
515 
516             fCurrentRenderer->updateVertices(fVertPos, fVertColors);
517         }
518 
519         return true;
520     }
521 
522 private:
updateMesh(size_t new_count)523     void updateMesh(size_t new_count) {
524         // These look better than rng when the count is low.
525         static constexpr struct {
526             SkPoint   fUv;
527             SkColor4f fColor;
528         } gFixedVertices[] = {
529             {{ .25f, .25f}, {1, 0, 0, 1}},
530             {{ .75f, .75f}, {0, 1, 0, 1}},
531             {{ .75f, .25f}, {0, 0, 1, 1}},
532             {{ .25f, .75f}, {1, 1, 0, 1}},
533             {{ .50f, .50f}, {0, 1, 1, 1}},
534         };
535 
536         SkASSERT(fVertUVs.size() == fVertPos.size());
537         SkASSERT(fVertUVs.size() == fVertColors.size());
538         const size_t current_count = fVertUVs.size();
539 
540         fVertUVs.resize(new_count);
541         fVertPos.resize(new_count);
542         fVertColors.resize(new_count);
543 
544         for (size_t i = current_count; i < new_count; ++i) {
545             const SkPoint uv = i < std::size(gFixedVertices)
546                 ? gFixedVertices[i].fUv
547                 : SkPoint{ fRNG.nextF(), fRNG.nextF() };
548             const SkColor4f color = i < std::size(gFixedVertices)
549                 ? gFixedVertices[i].fColor
550                 : SkColor4f{ fRNG.nextF(), fRNG.nextF(), fRNG.nextF(), 1.f };
551 
552             fVertUVs[i] = fVertPos[i] = uv;
553             fVertColors[i] = color;
554         }
555 
556         if (new_count < current_count) {
557             // Reset the RNG state
558             fRNG.setSeed(0);
559             static constexpr size_t kRandsPerVertex = 5;
560             const size_t rand_vertex_count =
561                 std::max(new_count, std::size(gFixedVertices)) - std::size(gFixedVertices);
562             for (size_t i = 0; i < rand_vertex_count * kRandsPerVertex; ++i) { fRNG.nextF(); }
563         }
564     }
565 
drawControls()566     void drawControls() {
567         ImGui::Begin("Mesh Options");
568 
569         if (ImGui::BeginCombo("Gradient Type", fCurrentRendererChoice->fName)) {
570             for (const auto& renderer : gGradientRenderers) {
571                 const auto is_selected = (fCurrentRendererChoice->fName == renderer.fName);
572                 if (ImGui::Selectable(renderer.fName) && !is_selected) {
573                     fCurrentRendererChoice = &renderer;
574                     fCurrentRenderer = renderer.fFactory();
575                     fCurrentRenderer->updateVertices(fVertPos, fVertColors);
576                 }
577                 if (is_selected) {
578                     ImGui::SetItemDefaultFocus();
579                 }
580             }
581             ImGui::EndCombo();
582         }
583 
584         if (ImGui::BeginCombo("Animator", fCurrentAnimator->fName)) {
585             for (const auto& anim : gVertexAnimators) {
586                 const auto is_selected = (fCurrentAnimator->fName == anim.fName);
587                 if (ImGui::Selectable(anim.fName) && !is_selected) {
588                     fCurrentAnimator = &anim;
589                     fTimeBase = 0;
590                 }
591                 if (is_selected) {
592                     ImGui::SetItemDefaultFocus();
593                 }
594             }
595             ImGui::EndCombo();
596         }
597 
598         if (ImGui::SliderInt("Vertex Count", &fVertexCountTo, 3, 64)) {
599             fVertexCountTimeBase = 0;
600             fVertexCountFrom = fVertUVs.size();
601         }
602 
603         ImGui::SliderFloat("Speed", &fAnimationSpeed, 0.25, 4, "%.2f");
604 
605         ImGui::Checkbox("Shader Fill", &fShaderFill);
606 
607         ImGui::End();
608     }
609 
610     SkSize                  fSize;
611     std::vector<SkPoint>    fVertUVs;
612     std::vector<SkPoint>    fVertPos;    // fVertUVs after animation
613     std::vector<SkColor4f>  fVertColors;
614 
615     SkRandom                fRNG;
616 
617     sk_sp<GradientRenderer> fCurrentRenderer;
618 
619     const SkFont            fFont;
620 
621     // Animation state
622     const SkCubicMap        fTimeMapper,
623                             fVertedCountTimeMapper;
624     double                  fTimeBase               = 0,
625                             fVertexCountTimeBase    = 0;
626     int                     fVertexCountFrom        = 0,
627                             fVertexCountTo          = 5;
628 
629     // UI stuff
630     const RendererChoice*   fCurrentRendererChoice  = &gGradientRenderers[0];
631     const VertexAnimator*   fCurrentAnimator        = &gVertexAnimators[0];
632     float                   fAnimationSpeed         = 1.f;
633     bool                    fShaderFill             = false;
634 };
635 
636 }  // anonymous namespace
637 
638 DEF_SLIDE(return new MeshGradientSlide{};)
639