• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2 * Copyright 2016 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 "tools/viewer/Viewer.h"
9 
10 #include "include/core/SkCanvas.h"
11 #include "include/core/SkData.h"
12 #include "include/core/SkGraphics.h"
13 #include "include/core/SkPictureRecorder.h"
14 #include "include/core/SkStream.h"
15 #include "include/core/SkSurface.h"
16 #include "include/gpu/GrDirectContext.h"
17 #include "include/private/SkTPin.h"
18 #include "include/private/SkTo.h"
19 #include "include/utils/SkPaintFilterCanvas.h"
20 #include "src/core/SkColorSpacePriv.h"
21 #include "src/core/SkImagePriv.h"
22 #include "src/core/SkMD5.h"
23 #include "src/core/SkOSFile.h"
24 #include "src/core/SkReadBuffer.h"
25 #include "src/core/SkScan.h"
26 #include "src/core/SkSurfacePriv.h"
27 #include "src/core/SkTSort.h"
28 #include "src/core/SkTaskGroup.h"
29 #include "src/core/SkTextBlobPriv.h"
30 #include "src/core/SkVMBlitter.h"
31 #include "src/gpu/GrDirectContextPriv.h"
32 #include "src/gpu/GrGpu.h"
33 #include "src/gpu/GrPersistentCacheUtils.h"
34 #include "src/gpu/GrShaderUtils.h"
35 #include "src/image/SkImage_Base.h"
36 #include "src/sksl/SkSLCompiler.h"
37 #include "src/utils/SkJSONWriter.h"
38 #include "src/utils/SkOSPath.h"
39 #include "tools/Resources.h"
40 #include "tools/RuntimeBlendUtils.h"
41 #include "tools/ToolUtils.h"
42 #include "tools/flags/CommandLineFlags.h"
43 #include "tools/flags/CommonFlags.h"
44 #include "tools/trace/EventTracingPriv.h"
45 #include "tools/viewer/BisectSlide.h"
46 #include "tools/viewer/GMSlide.h"
47 #include "tools/viewer/ImageSlide.h"
48 #include "tools/viewer/MSKPSlide.h"
49 #include "tools/viewer/ParticlesSlide.h"
50 #include "tools/viewer/SKPSlide.h"
51 #include "tools/viewer/SampleSlide.h"
52 #include "tools/viewer/SkSLSlide.h"
53 #include "tools/viewer/SlideDir.h"
54 #include "tools/viewer/SvgSlide.h"
55 
56 #if SK_GPU_V1
57 #include "src/gpu/ops/AtlasPathRenderer.h"
58 #include "src/gpu/ops/TessellationPathRenderer.h"
59 #endif
60 
61 #include <cstdlib>
62 #include <map>
63 
64 #include "imgui.h"
65 #include "misc/cpp/imgui_stdlib.h"  // For ImGui support of std::string
66 
67 #ifdef SK_VULKAN
68 #include "spirv-tools/libspirv.hpp"
69 #endif
70 
71 #if defined(SK_ENABLE_SKOTTIE)
72     #include "tools/viewer/SkottieSlide.h"
73 #endif
74 #if defined(SK_ENABLE_SKRIVE)
75     #include "tools/viewer/SkRiveSlide.h"
76 #endif
77 
78 class CapturingShaderErrorHandler : public GrContextOptions::ShaderErrorHandler {
79 public:
compileError(const char * shader,const char * errors)80     void compileError(const char* shader, const char* errors) override {
81         fShaders.push_back(SkString(shader));
82         fErrors.push_back(SkString(errors));
83     }
84 
reset()85     void reset() {
86         fShaders.reset();
87         fErrors.reset();
88     }
89 
90     SkTArray<SkString> fShaders;
91     SkTArray<SkString> fErrors;
92 };
93 
94 static CapturingShaderErrorHandler gShaderErrorHandler;
95 
ShaderErrorHandler()96 GrContextOptions::ShaderErrorHandler* Viewer::ShaderErrorHandler() { return &gShaderErrorHandler; }
97 
98 using namespace sk_app;
99 using SkSL::Compiler;
100 using OverrideFlag = SkSL::Compiler::OverrideFlag;
101 
102 static std::map<GpuPathRenderers, std::string> gPathRendererNames;
103 
Create(int argc,char ** argv,void * platformData)104 Application* Application::Create(int argc, char** argv, void* platformData) {
105     return new Viewer(argc, argv, platformData);
106 }
107 
108 static DEFINE_string(slide, "", "Start on this sample.");
109 static DEFINE_bool(list, false, "List samples?");
110 
111 #ifdef SK_GL
112 #define GL_BACKEND_STR ", \"gl\""
113 #else
114 #define GL_BACKEND_STR
115 #endif
116 #ifdef SK_VULKAN
117 #define VK_BACKEND_STR ", \"vk\""
118 #else
119 #define VK_BACKEND_STR
120 #endif
121 #ifdef SK_METAL
122 #define MTL_BACKEND_STR ", \"mtl\""
123 #else
124 #define MTL_BACKEND_STR
125 #endif
126 #ifdef SK_DIRECT3D
127 #define D3D_BACKEND_STR ", \"d3d\""
128 #else
129 #define D3D_BACKEND_STR
130 #endif
131 #ifdef SK_DAWN
132 #define DAWN_BACKEND_STR ", \"dawn\""
133 #else
134 #define DAWN_BACKEND_STR
135 #endif
136 #define BACKENDS_STR_EVALUATOR(sw, gl, vk, mtl, d3d, dawn) sw gl vk mtl d3d dawn
137 #define BACKENDS_STR BACKENDS_STR_EVALUATOR( \
138     "\"sw\"", GL_BACKEND_STR, VK_BACKEND_STR, MTL_BACKEND_STR, D3D_BACKEND_STR, DAWN_BACKEND_STR)
139 
140 static DEFINE_string2(backend, b, "sw", "Backend to use. Allowed values are " BACKENDS_STR ".");
141 
142 static DEFINE_int(msaa, 1, "Number of subpixel samples. 0 for no HW antialiasing.");
143 static DEFINE_bool(dmsaa, false, "Use internal MSAA to render to non-MSAA surfaces?");
144 
145 static DEFINE_string(bisect, "", "Path to a .skp or .svg file to bisect.");
146 
147 static DEFINE_string2(file, f, "", "Open a single file for viewing.");
148 
149 static DEFINE_string2(match, m, nullptr,
150                "[~][^]substring[$] [...] of name to run.\n"
151                "Multiple matches may be separated by spaces.\n"
152                "~ causes a matching name to always be skipped\n"
153                "^ requires the start of the name to match\n"
154                "$ requires the end of the name to match\n"
155                "^ and $ requires an exact match\n"
156                "If a name does not match any list entry,\n"
157                "it is skipped unless some list entry starts with ~");
158 
159 #if defined(SK_BUILD_FOR_ANDROID)
160 #   define PATH_PREFIX "/data/local/tmp/"
161 #else
162 #   define PATH_PREFIX ""
163 #endif
164 
165 static DEFINE_string(jpgs   , PATH_PREFIX "jpgs"   , "Directory to read jpgs from.");
166 static DEFINE_string(skps   , PATH_PREFIX "skps"   , "Directory to read skps from.");
167 static DEFINE_string(mskps  , PATH_PREFIX "mskps"  , "Directory to read mskps from.");
168 static DEFINE_string(lotties, PATH_PREFIX "lotties", "Directory to read (Bodymovin) jsons from.");
169 static DEFINE_string(rives  , PATH_PREFIX "rives"  , "Directory to read Rive (Flare) files from.");
170 #undef PATH_PREFIX
171 
172 static DEFINE_string(svgs, "", "Directory to read SVGs from, or a single SVG file.");
173 
174 static DEFINE_int_2(threads, j, -1,
175                "Run threadsafe tests on a threadpool with this many extra threads, "
176                "defaulting to one extra thread per core.");
177 
178 static DEFINE_bool(redraw, false, "Toggle continuous redraw.");
179 
180 static DEFINE_bool(offscreen, false, "Force rendering to an offscreen surface.");
181 static DEFINE_bool(skvm, false, "Force skvm blitters for raster.");
182 static DEFINE_bool(jit, true, "JIT SkVM?");
183 static DEFINE_bool(dylib, false, "JIT via dylib (much slower compile but easier to debug/profile)");
184 static DEFINE_bool(stats, false, "Display stats overlay on startup.");
185 static DEFINE_bool(binaryarchive, false, "Enable MTLBinaryArchive use (if available).");
186 
187 #ifndef SK_GL
188 static_assert(false, "viewer requires GL backend for raster.")
189 #endif
190 
191 const char* kBackendTypeStrings[sk_app::Window::kBackendTypeCount] = {
192     "OpenGL",
193 #if SK_ANGLE && defined(SK_BUILD_FOR_WIN)
194     "ANGLE",
195 #endif
196 #ifdef SK_DAWN
197     "Dawn",
198 #endif
199 #ifdef SK_VULKAN
200     "Vulkan",
201 #endif
202 #ifdef SK_METAL
203     "Metal",
204 #ifdef SK_GRAPHITE_ENABLED
205     "Metal (Graphite)",
206 #endif
207 #endif
208 #ifdef SK_DIRECT3D
209     "Direct3D",
210 #endif
211     "Raster"
212 };
213 
get_backend_type(const char * str)214 static sk_app::Window::BackendType get_backend_type(const char* str) {
215 #ifdef SK_DAWN
216     if (0 == strcmp(str, "dawn")) {
217         return sk_app::Window::kDawn_BackendType;
218     } else
219 #endif
220 #ifdef SK_VULKAN
221     if (0 == strcmp(str, "vk")) {
222         return sk_app::Window::kVulkan_BackendType;
223     } else
224 #endif
225 #if SK_ANGLE && defined(SK_BUILD_FOR_WIN)
226     if (0 == strcmp(str, "angle")) {
227         return sk_app::Window::kANGLE_BackendType;
228     } else
229 #endif
230 #ifdef SK_METAL
231     if (0 == strcmp(str, "mtl")) {
232         return sk_app::Window::kMetal_BackendType;
233     } else
234 #ifdef SK_GRAPHITE_ENABLED
235     if (0 == strcmp(str, "grmtl")) {
236         return sk_app::Window::kGraphiteMetal_BackendType;
237     } else
238 #endif
239 #endif
240 #ifdef SK_DIRECT3D
241     if (0 == strcmp(str, "d3d")) {
242         return sk_app::Window::kDirect3D_BackendType;
243     } else
244 #endif
245 
246     if (0 == strcmp(str, "gl")) {
247         return sk_app::Window::kNativeGL_BackendType;
248     } else if (0 == strcmp(str, "sw")) {
249         return sk_app::Window::kRaster_BackendType;
250     } else {
251         SkDebugf("Unknown backend type, %s, defaulting to sw.", str);
252         return sk_app::Window::kRaster_BackendType;
253     }
254 }
255 
256 static SkColorSpacePrimaries gSrgbPrimaries = {
257     0.64f, 0.33f,
258     0.30f, 0.60f,
259     0.15f, 0.06f,
260     0.3127f, 0.3290f };
261 
262 static SkColorSpacePrimaries gAdobePrimaries = {
263     0.64f, 0.33f,
264     0.21f, 0.71f,
265     0.15f, 0.06f,
266     0.3127f, 0.3290f };
267 
268 static SkColorSpacePrimaries gP3Primaries = {
269     0.680f, 0.320f,
270     0.265f, 0.690f,
271     0.150f, 0.060f,
272     0.3127f, 0.3290f };
273 
274 static SkColorSpacePrimaries gRec2020Primaries = {
275     0.708f, 0.292f,
276     0.170f, 0.797f,
277     0.131f, 0.046f,
278     0.3127f, 0.3290f };
279 
280 struct NamedPrimaries {
281     const char* fName;
282     SkColorSpacePrimaries* fPrimaries;
283 } gNamedPrimaries[] = {
284     { "sRGB", &gSrgbPrimaries },
285     { "AdobeRGB", &gAdobePrimaries },
286     { "P3", &gP3Primaries },
287     { "Rec. 2020", &gRec2020Primaries },
288 };
289 
primaries_equal(const SkColorSpacePrimaries & a,const SkColorSpacePrimaries & b)290 static bool primaries_equal(const SkColorSpacePrimaries& a, const SkColorSpacePrimaries& b) {
291     return memcmp(&a, &b, sizeof(SkColorSpacePrimaries)) == 0;
292 }
293 
backend_type_for_window(Window::BackendType backendType)294 static Window::BackendType backend_type_for_window(Window::BackendType backendType) {
295     // In raster mode, we still use GL for the window.
296     // This lets us render the GUI faster (and correct).
297     return Window::kRaster_BackendType == backendType ? Window::kNativeGL_BackendType : backendType;
298 }
299 
300 class NullSlide : public Slide {
getDimensions() const301     SkISize getDimensions() const override {
302         return SkISize::Make(640, 480);
303     }
304 
draw(SkCanvas * canvas)305     void draw(SkCanvas* canvas) override {
306         canvas->clear(0xffff11ff);
307     }
308 };
309 
310 static const char kName[] = "name";
311 static const char kValue[] = "value";
312 static const char kOptions[] = "options";
313 static const char kSlideStateName[] = "Slide";
314 static const char kBackendStateName[] = "Backend";
315 static const char kMSAAStateName[] = "MSAA";
316 static const char kPathRendererStateName[] = "Path renderer";
317 static const char kSoftkeyStateName[] = "Softkey";
318 static const char kSoftkeyHint[] = "Please select a softkey";
319 static const char kON[] = "ON";
320 static const char kRefreshStateName[] = "Refresh";
321 
322 extern bool gUseSkVMBlitter;
323 extern bool gSkVMAllowJIT;
324 extern bool gSkVMJITViaDylib;
325 
Viewer(int argc,char ** argv,void * platformData)326 Viewer::Viewer(int argc, char** argv, void* platformData)
327     : fCurrentSlide(-1)
328     , fRefresh(false)
329     , fSaveToSKP(false)
330     , fShowSlideDimensions(false)
331     , fShowImGuiDebugWindow(false)
332     , fShowSlidePicker(false)
333     , fShowImGuiTestWindow(false)
334     , fShowZoomWindow(false)
335     , fZoomWindowFixed(false)
336     , fZoomWindowLocation{0.0f, 0.0f}
337     , fLastImage(nullptr)
338     , fZoomUI(false)
339     , fBackendType(sk_app::Window::kNativeGL_BackendType)
340     , fColorMode(ColorMode::kLegacy)
341     , fColorSpacePrimaries(gSrgbPrimaries)
342     // Our UI can only tweak gamma (currently), so start out gamma-only
343     , fColorSpaceTransferFn(SkNamedTransferFn::k2Dot2)
344     , fApplyBackingScale(true)
345     , fZoomLevel(0.0f)
346     , fRotation(0.0f)
347     , fOffset{0.5f, 0.5f}
348     , fGestureDevice(GestureDevice::kNone)
349     , fTiled(false)
350     , fDrawTileBoundaries(false)
351     , fTileScale{0.25f, 0.25f}
352     , fPerspectiveMode(kPerspective_Off)
353 {
354     SkGraphics::Init();
355 
356     gPathRendererNames[GpuPathRenderers::kDefault] = "Default Path Renderers";
357     gPathRendererNames[GpuPathRenderers::kAtlas] = "Atlas (tessellation)";
358     gPathRendererNames[GpuPathRenderers::kTessellation] = "Tessellation";
359     gPathRendererNames[GpuPathRenderers::kSmall] = "Small paths (cached sdf or alpha masks)";
360     gPathRendererNames[GpuPathRenderers::kTriangulating] = "Triangulating";
361     gPathRendererNames[GpuPathRenderers::kNone] = "Software masks";
362 
363     SkDebugf("Command line arguments: ");
364     for (int i = 1; i < argc; ++i) {
365         SkDebugf("%s ", argv[i]);
366     }
367     SkDebugf("\n");
368 
369     CommandLineFlags::Parse(argc, argv);
370 #ifdef SK_BUILD_FOR_ANDROID
371     SetResourcePath("/data/local/tmp/resources");
372 #endif
373 
374     gUseSkVMBlitter = FLAGS_skvm;
375     gSkVMAllowJIT = FLAGS_jit;
376     gSkVMJITViaDylib = FLAGS_dylib;
377 
378     CommonFlags::SetDefaultFontMgr();
379 
380     initializeEventTracingForTools();
381     static SkTaskGroup::Enabler kTaskGroupEnabler(FLAGS_threads);
382 
383     fBackendType = get_backend_type(FLAGS_backend[0]);
384     fWindow = Window::CreateNativeWindow(platformData);
385 
386     DisplayParams displayParams;
387     displayParams.fMSAASampleCount = FLAGS_msaa;
388     displayParams.fEnableBinaryArchive = FLAGS_binaryarchive;
389     CommonFlags::SetCtxOptions(&displayParams.fGrContextOptions);
390     displayParams.fGrContextOptions.fPersistentCache = &fPersistentCache;
391     displayParams.fGrContextOptions.fShaderCacheStrategy =
392             GrContextOptions::ShaderCacheStrategy::kSkSL;
393     displayParams.fGrContextOptions.fShaderErrorHandler = &gShaderErrorHandler;
394     displayParams.fGrContextOptions.fSuppressPrints = true;
395     if (FLAGS_dmsaa) {
396         displayParams.fSurfaceProps = SkSurfaceProps(
397                 displayParams.fSurfaceProps.flags() | SkSurfaceProps::kDynamicMSAA_Flag,
398                 displayParams.fSurfaceProps.pixelGeometry());
399     }
400     fWindow->setRequestedDisplayParams(displayParams);
401     fDisplay = fWindow->getRequestedDisplayParams();
402     fRefresh = FLAGS_redraw;
403 
404     fImGuiLayer.setScaleFactor(fWindow->scaleFactor());
405     fStatsLayer.setDisplayScale((fZoomUI ? 2.0f : 1.0f) * fWindow->scaleFactor());
406 
407     // Configure timers
408     fStatsLayer.setActive(FLAGS_stats);
409     fAnimateTimer = fStatsLayer.addTimer("Animate", SK_ColorMAGENTA, 0xffff66ff);
410     fPaintTimer = fStatsLayer.addTimer("Paint", SK_ColorGREEN);
411     fFlushTimer = fStatsLayer.addTimer("Flush", SK_ColorRED, 0xffff6666);
412 
413     // register callbacks
414     fCommands.attach(fWindow);
415     fWindow->pushLayer(this);
416     fWindow->pushLayer(&fStatsLayer);
417     fWindow->pushLayer(&fImGuiLayer);
418 
419     // add key-bindings
__anon9a3958fe0102() 420     fCommands.addCommand(' ', "GUI", "Toggle Debug GUI", [this]() {
421         this->fShowImGuiDebugWindow = !this->fShowImGuiDebugWindow;
422         fWindow->inval();
423     });
424     // Command to jump directly to the slide picker and give it focus
__anon9a3958fe0202() 425     fCommands.addCommand('/', "GUI", "Jump to slide picker", [this]() {
426         this->fShowImGuiDebugWindow = true;
427         this->fShowSlidePicker = true;
428         fWindow->inval();
429     });
430     // Alias that to Backspace, to match SampleApp
__anon9a3958fe0302() 431     fCommands.addCommand(skui::Key::kBack, "Backspace", "GUI", "Jump to slide picker", [this]() {
432         this->fShowImGuiDebugWindow = true;
433         this->fShowSlidePicker = true;
434         fWindow->inval();
435     });
__anon9a3958fe0402() 436     fCommands.addCommand('g', "GUI", "Toggle GUI Demo", [this]() {
437         this->fShowImGuiTestWindow = !this->fShowImGuiTestWindow;
438         fWindow->inval();
439     });
__anon9a3958fe0502() 440     fCommands.addCommand('z', "GUI", "Toggle zoom window", [this]() {
441         this->fShowZoomWindow = !this->fShowZoomWindow;
442         fWindow->inval();
443     });
__anon9a3958fe0602() 444     fCommands.addCommand('Z', "GUI", "Toggle zoom window state", [this]() {
445         this->fZoomWindowFixed = !this->fZoomWindowFixed;
446         fWindow->inval();
447     });
__anon9a3958fe0702() 448     fCommands.addCommand('v', "Swapchain", "Toggle vsync on/off", [this]() {
449         DisplayParams params = fWindow->getRequestedDisplayParams();
450         params.fDisableVsync = !params.fDisableVsync;
451         fWindow->setRequestedDisplayParams(params);
452         this->updateTitle();
453         fWindow->inval();
454     });
__anon9a3958fe0802() 455     fCommands.addCommand('V', "Swapchain", "Toggle delayed acquire on/off (Metal only)", [this]() {
456         DisplayParams params = fWindow->getRequestedDisplayParams();
457         params.fDelayDrawableAcquisition = !params.fDelayDrawableAcquisition;
458         fWindow->setRequestedDisplayParams(params);
459         this->updateTitle();
460         fWindow->inval();
461     });
__anon9a3958fe0902() 462     fCommands.addCommand('r', "Redraw", "Toggle redraw", [this]() {
463         fRefresh = !fRefresh;
464         fWindow->inval();
465     });
__anon9a3958fe0a02() 466     fCommands.addCommand('s', "Overlays", "Toggle stats display", [this]() {
467         fStatsLayer.setActive(!fStatsLayer.getActive());
468         fWindow->inval();
469     });
__anon9a3958fe0b02() 470     fCommands.addCommand('0', "Overlays", "Reset stats", [this]() {
471         fStatsLayer.resetMeasurements();
472         this->updateTitle();
473         fWindow->inval();
474     });
__anon9a3958fe0c02() 475     fCommands.addCommand('c', "Modes", "Cycle color mode", [this]() {
476         switch (fColorMode) {
477             case ColorMode::kLegacy:
478                 this->setColorMode(ColorMode::kColorManaged8888);
479                 break;
480             case ColorMode::kColorManaged8888:
481                 this->setColorMode(ColorMode::kColorManagedF16);
482                 break;
483             case ColorMode::kColorManagedF16:
484                 this->setColorMode(ColorMode::kColorManagedF16Norm);
485                 break;
486             case ColorMode::kColorManagedF16Norm:
487                 this->setColorMode(ColorMode::kLegacy);
488                 break;
489         }
490     });
__anon9a3958fe0d02() 491     fCommands.addCommand('w', "Modes", "Toggle wireframe", [this]() {
492         DisplayParams params = fWindow->getRequestedDisplayParams();
493         params.fGrContextOptions.fWireframeMode = !params.fGrContextOptions.fWireframeMode;
494         fWindow->setRequestedDisplayParams(params);
495         fWindow->inval();
496     });
__anon9a3958fe0e02() 497     fCommands.addCommand('w', "Modes", "Toggle reduced shaders", [this]() {
498       DisplayParams params = fWindow->getRequestedDisplayParams();
499       params.fGrContextOptions.fReducedShaderVariations =
500               !params.fGrContextOptions.fReducedShaderVariations;
501       fWindow->setRequestedDisplayParams(params);
502       fWindow->inval();
503     });
__anon9a3958fe0f02() 504     fCommands.addCommand(skui::Key::kRight, "Right", "Navigation", "Next slide", [this]() {
505         this->setCurrentSlide(fCurrentSlide < fSlides.count() - 1 ? fCurrentSlide + 1 : 0);
506     });
__anon9a3958fe1002() 507     fCommands.addCommand(skui::Key::kLeft, "Left", "Navigation", "Previous slide", [this]() {
508         this->setCurrentSlide(fCurrentSlide > 0 ? fCurrentSlide - 1 : fSlides.count() - 1);
509     });
__anon9a3958fe1102() 510     fCommands.addCommand(skui::Key::kUp, "Up", "Transform", "Zoom in", [this]() {
511         this->changeZoomLevel(1.f / 32.f);
512         fWindow->inval();
513     });
__anon9a3958fe1202() 514     fCommands.addCommand(skui::Key::kDown, "Down", "Transform", "Zoom out", [this]() {
515         this->changeZoomLevel(-1.f / 32.f);
516         fWindow->inval();
517     });
__anon9a3958fe1302() 518     fCommands.addCommand('d', "Modes", "Change rendering backend", [this]() {
519         sk_app::Window::BackendType newBackend = (sk_app::Window::BackendType)(
520                 (fBackendType + 1) % sk_app::Window::kBackendTypeCount);
521         // Switching to and from Vulkan is problematic on Linux so disabled for now
522 #if defined(SK_BUILD_FOR_UNIX) && defined(SK_VULKAN)
523         if (newBackend == sk_app::Window::kVulkan_BackendType) {
524             newBackend = (sk_app::Window::BackendType)((newBackend + 1) %
525                                                        sk_app::Window::kBackendTypeCount);
526         } else if (fBackendType == sk_app::Window::kVulkan_BackendType) {
527             newBackend = sk_app::Window::kVulkan_BackendType;
528         }
529 #endif
530         this->setBackend(newBackend);
531     });
__anon9a3958fe1402() 532     fCommands.addCommand('K', "IO", "Save slide to SKP", [this]() {
533         fSaveToSKP = true;
534         fWindow->inval();
535     });
__anon9a3958fe1502() 536     fCommands.addCommand('&', "Overlays", "Show slide dimensios", [this]() {
537         fShowSlideDimensions = !fShowSlideDimensions;
538         fWindow->inval();
539     });
__anon9a3958fe1602() 540     fCommands.addCommand('G', "Modes", "Geometry", [this]() {
541         DisplayParams params = fWindow->getRequestedDisplayParams();
542         uint32_t flags = params.fSurfaceProps.flags();
543         SkPixelGeometry defaultPixelGeometry = fDisplay.fSurfaceProps.pixelGeometry();
544         if (!fDisplayOverrides.fSurfaceProps.fPixelGeometry) {
545             fDisplayOverrides.fSurfaceProps.fPixelGeometry = true;
546             params.fSurfaceProps = SkSurfaceProps(flags, kUnknown_SkPixelGeometry);
547         } else {
548             switch (params.fSurfaceProps.pixelGeometry()) {
549                 case kUnknown_SkPixelGeometry:
550                     params.fSurfaceProps = SkSurfaceProps(flags, kRGB_H_SkPixelGeometry);
551                     break;
552                 case kRGB_H_SkPixelGeometry:
553                     params.fSurfaceProps = SkSurfaceProps(flags, kBGR_H_SkPixelGeometry);
554                     break;
555                 case kBGR_H_SkPixelGeometry:
556                     params.fSurfaceProps = SkSurfaceProps(flags, kRGB_V_SkPixelGeometry);
557                     break;
558                 case kRGB_V_SkPixelGeometry:
559                     params.fSurfaceProps = SkSurfaceProps(flags, kBGR_V_SkPixelGeometry);
560                     break;
561                 case kBGR_V_SkPixelGeometry:
562                     params.fSurfaceProps = SkSurfaceProps(flags, defaultPixelGeometry);
563                     fDisplayOverrides.fSurfaceProps.fPixelGeometry = false;
564                     break;
565             }
566         }
567         fWindow->setRequestedDisplayParams(params);
568         this->updateTitle();
569         fWindow->inval();
570     });
__anon9a3958fe1702() 571     fCommands.addCommand('H', "Font", "Hinting mode", [this]() {
572         if (!fFontOverrides.fHinting) {
573             fFontOverrides.fHinting = true;
574             fFont.setHinting(SkFontHinting::kNone);
575         } else {
576             switch (fFont.getHinting()) {
577                 case SkFontHinting::kNone:
578                     fFont.setHinting(SkFontHinting::kSlight);
579                     break;
580                 case SkFontHinting::kSlight:
581                     fFont.setHinting(SkFontHinting::kNormal);
582                     break;
583                 case SkFontHinting::kNormal:
584                     fFont.setHinting(SkFontHinting::kFull);
585                     break;
586                 case SkFontHinting::kFull:
587                     fFont.setHinting(SkFontHinting::kNone);
588                     fFontOverrides.fHinting = false;
589                     break;
590             }
591         }
592         this->updateTitle();
593         fWindow->inval();
594     });
__anon9a3958fe1802() 595     fCommands.addCommand('A', "Paint", "Antialias Mode", [this]() {
596         if (!fPaintOverrides.fAntiAlias) {
597             fPaintOverrides.fAntiAliasState = SkPaintFields::AntiAliasState::Alias;
598             fPaintOverrides.fAntiAlias = true;
599             fPaint.setAntiAlias(false);
600             gSkUseAnalyticAA = gSkForceAnalyticAA = false;
601         } else {
602             fPaint.setAntiAlias(true);
603             switch (fPaintOverrides.fAntiAliasState) {
604                 case SkPaintFields::AntiAliasState::Alias:
605                     fPaintOverrides.fAntiAliasState = SkPaintFields::AntiAliasState::Normal;
606                     gSkUseAnalyticAA = gSkForceAnalyticAA = false;
607                     break;
608                 case SkPaintFields::AntiAliasState::Normal:
609                     fPaintOverrides.fAntiAliasState = SkPaintFields::AntiAliasState::AnalyticAAEnabled;
610                     gSkUseAnalyticAA = true;
611                     gSkForceAnalyticAA = false;
612                     break;
613                 case SkPaintFields::AntiAliasState::AnalyticAAEnabled:
614                     fPaintOverrides.fAntiAliasState = SkPaintFields::AntiAliasState::AnalyticAAForced;
615                     gSkUseAnalyticAA = gSkForceAnalyticAA = true;
616                     break;
617                 case SkPaintFields::AntiAliasState::AnalyticAAForced:
618                     fPaintOverrides.fAntiAliasState = SkPaintFields::AntiAliasState::Alias;
619                     fPaintOverrides.fAntiAlias = false;
620                     gSkUseAnalyticAA = fPaintOverrides.fOriginalSkUseAnalyticAA;
621                     gSkForceAnalyticAA = fPaintOverrides.fOriginalSkForceAnalyticAA;
622                     break;
623             }
624         }
625         this->updateTitle();
626         fWindow->inval();
627     });
__anon9a3958fe1902() 628     fCommands.addCommand('D', "Modes", "DFT", [this]() {
629         DisplayParams params = fWindow->getRequestedDisplayParams();
630         uint32_t flags = params.fSurfaceProps.flags();
631         flags ^= SkSurfaceProps::kUseDeviceIndependentFonts_Flag;
632         params.fSurfaceProps = SkSurfaceProps(flags, params.fSurfaceProps.pixelGeometry());
633         fWindow->setRequestedDisplayParams(params);
634         this->updateTitle();
635         fWindow->inval();
636     });
__anon9a3958fe1a02() 637     fCommands.addCommand('L', "Font", "Subpixel Antialias Mode", [this]() {
638         if (!fFontOverrides.fEdging) {
639             fFontOverrides.fEdging = true;
640             fFont.setEdging(SkFont::Edging::kAlias);
641         } else {
642             switch (fFont.getEdging()) {
643                 case SkFont::Edging::kAlias:
644                     fFont.setEdging(SkFont::Edging::kAntiAlias);
645                     break;
646                 case SkFont::Edging::kAntiAlias:
647                     fFont.setEdging(SkFont::Edging::kSubpixelAntiAlias);
648                     break;
649                 case SkFont::Edging::kSubpixelAntiAlias:
650                     fFont.setEdging(SkFont::Edging::kAlias);
651                     fFontOverrides.fEdging = false;
652                     break;
653             }
654         }
655         this->updateTitle();
656         fWindow->inval();
657     });
__anon9a3958fe1b02() 658     fCommands.addCommand('S', "Font", "Subpixel Position Mode", [this]() {
659         if (!fFontOverrides.fSubpixel) {
660             fFontOverrides.fSubpixel = true;
661             fFont.setSubpixel(false);
662         } else {
663             if (!fFont.isSubpixel()) {
664                 fFont.setSubpixel(true);
665             } else {
666                 fFontOverrides.fSubpixel = false;
667             }
668         }
669         this->updateTitle();
670         fWindow->inval();
671     });
__anon9a3958fe1c02() 672     fCommands.addCommand('B', "Font", "Baseline Snapping", [this]() {
673         if (!fFontOverrides.fBaselineSnap) {
674             fFontOverrides.fBaselineSnap = true;
675             fFont.setBaselineSnap(false);
676         } else {
677             if (!fFont.isBaselineSnap()) {
678                 fFont.setBaselineSnap(true);
679             } else {
680                 fFontOverrides.fBaselineSnap = false;
681             }
682         }
683         this->updateTitle();
684         fWindow->inval();
685     });
__anon9a3958fe1d02() 686     fCommands.addCommand('p', "Transform", "Toggle Perspective Mode", [this]() {
687         fPerspectiveMode = (kPerspective_Real == fPerspectiveMode) ? kPerspective_Fake
688                                                                    : kPerspective_Real;
689         this->updateTitle();
690         fWindow->inval();
691     });
__anon9a3958fe1e02() 692     fCommands.addCommand('P', "Transform", "Toggle Perspective", [this]() {
693         fPerspectiveMode = (kPerspective_Off == fPerspectiveMode) ? kPerspective_Real
694                                                                   : kPerspective_Off;
695         this->updateTitle();
696         fWindow->inval();
697     });
__anon9a3958fe1f02() 698     fCommands.addCommand('a', "Transform", "Toggle Animation", [this]() {
699         fAnimTimer.togglePauseResume();
700     });
__anon9a3958fe2002() 701     fCommands.addCommand('u', "GUI", "Zoom UI", [this]() {
702         fZoomUI = !fZoomUI;
703         fStatsLayer.setDisplayScale((fZoomUI ? 2.0f : 1.0f) * fWindow->scaleFactor());
704         fWindow->inval();
705     });
__anon9a3958fe2102() 706     fCommands.addCommand('$', "ViaSerialize", "Toggle ViaSerialize", [this]() {
707         fDrawViaSerialize = !fDrawViaSerialize;
708         this->updateTitle();
709         fWindow->inval();
710     });
__anon9a3958fe2202() 711     fCommands.addCommand('!', "SkVM", "Toggle SkVM blitter", [this]() {
712         gUseSkVMBlitter = !gUseSkVMBlitter;
713         this->updateTitle();
714         fWindow->inval();
715     });
__anon9a3958fe2302() 716     fCommands.addCommand('@', "SkVM", "Toggle SkVM JIT", [this]() {
717         gSkVMAllowJIT = !gSkVMAllowJIT;
718         this->updateTitle();
719         fWindow->inval();
720     });
721 
722     // set up slides
723     this->initSlides();
724     if (FLAGS_list) {
725         this->listNames();
726     }
727 
728     fPerspectivePoints[0].set(0, 0);
729     fPerspectivePoints[1].set(1, 0);
730     fPerspectivePoints[2].set(0, 1);
731     fPerspectivePoints[3].set(1, 1);
732     fAnimTimer.run();
733 
734     auto gamutImage = GetResourceAsImage("images/gamut.png");
735     if (gamutImage) {
736         fImGuiGamutPaint.setShader(gamutImage->makeShader(SkSamplingOptions(SkFilterMode::kLinear)));
737     }
738     fImGuiGamutPaint.setColor(SK_ColorWHITE);
739 
740     fWindow->attach(backend_type_for_window(fBackendType));
741     this->setCurrentSlide(this->startupSlide());
742 }
743 
initSlides()744 void Viewer::initSlides() {
745     using SlideFactory = sk_sp<Slide>(*)(const SkString& name, const SkString& path);
746     static const struct {
747         const char*                            fExtension;
748         const char*                            fDirName;
749         const CommandLineFlags::StringArray&   fFlags;
750         const SlideFactory                     fFactory;
751     } gExternalSlidesInfo[] = {
752         { ".mskp", "mskp-dir", FLAGS_mskps,
753           [](const SkString& name, const SkString& path) -> sk_sp<Slide> {
754             return sk_make_sp<MSKPSlide>(name, path);}
755         },
756         { ".skp", "skp-dir", FLAGS_skps,
757             [](const SkString& name, const SkString& path) -> sk_sp<Slide> {
758                 return sk_make_sp<SKPSlide>(name, path);}
759         },
760         { ".jpg", "jpg-dir", FLAGS_jpgs,
761             [](const SkString& name, const SkString& path) -> sk_sp<Slide> {
762                 return sk_make_sp<ImageSlide>(name, path);}
763         },
764 #if defined(SK_ENABLE_SKOTTIE)
765         { ".json", "skottie-dir", FLAGS_lotties,
766             [](const SkString& name, const SkString& path) -> sk_sp<Slide> {
767                 return sk_make_sp<SkottieSlide>(name, path);}
768         },
769 #endif
770     #if defined(SK_ENABLE_SKRIVE)
771             { ".flr", "skrive-dir", FLAGS_rives,
772                 [](const SkString& name, const SkString& path) -> sk_sp<Slide> {
773                     return sk_make_sp<SkRiveSlide>(name, path);}
774             },
775     #endif
776 #if defined(SK_ENABLE_SVG)
777         { ".svg", "svg-dir", FLAGS_svgs,
778             [](const SkString& name, const SkString& path) -> sk_sp<Slide> {
779                 return sk_make_sp<SvgSlide>(name, path);}
780         },
781 #endif
782     };
783 
784     SkTArray<sk_sp<Slide>> dirSlides;
785 
786     const auto addSlide =
787             [&](const SkString& name, const SkString& path, const SlideFactory& fact) {
788                 if (CommandLineFlags::ShouldSkip(FLAGS_match, name.c_str())) {
789                     return;
790                 }
791 
792                 if (auto slide = fact(name, path)) {
793                     dirSlides.push_back(slide);
794                     fSlides.push_back(std::move(slide));
795                 }
796             };
797 
798     if (!FLAGS_file.isEmpty()) {
799         // single file mode
800         const SkString file(FLAGS_file[0]);
801 
802         if (sk_exists(file.c_str(), kRead_SkFILE_Flag)) {
803             for (const auto& sinfo : gExternalSlidesInfo) {
804                 if (file.endsWith(sinfo.fExtension)) {
805                     addSlide(SkOSPath::Basename(file.c_str()), file, sinfo.fFactory);
806                     return;
807                 }
808             }
809 
810             fprintf(stderr, "Unsupported file type \"%s\"\n", file.c_str());
811         } else {
812             fprintf(stderr, "Cannot read \"%s\"\n", file.c_str());
813         }
814 
815         return;
816     }
817 
818     // Bisect slide.
819     if (!FLAGS_bisect.isEmpty()) {
820         sk_sp<BisectSlide> bisect = BisectSlide::Create(FLAGS_bisect[0]);
821         if (bisect && !CommandLineFlags::ShouldSkip(FLAGS_match, bisect->getName().c_str())) {
822             if (FLAGS_bisect.count() >= 2) {
823                 for (const char* ch = FLAGS_bisect[1]; *ch; ++ch) {
824                     bisect->onChar(*ch);
825                 }
826             }
827             fSlides.push_back(std::move(bisect));
828         }
829     }
830 
831     // GMs
832     int firstGM = fSlides.count();
833     for (skiagm::GMFactory gmFactory : skiagm::GMRegistry::Range()) {
834         std::unique_ptr<skiagm::GM> gm = gmFactory();
835         if (!CommandLineFlags::ShouldSkip(FLAGS_match, gm->getName())) {
836             sk_sp<Slide> slide(new GMSlide(std::move(gm)));
837             fSlides.push_back(std::move(slide));
838         }
839     }
840     // reverse gms
841     int numGMs = fSlides.count() - firstGM;
842     for (int i = 0; i < numGMs/2; ++i) {
843         std::swap(fSlides[firstGM + i], fSlides[fSlides.count() - i - 1]);
844     }
845 
846     // samples
847     for (const SampleFactory factory : SampleRegistry::Range()) {
848         sk_sp<Slide> slide(new SampleSlide(factory));
849         if (!CommandLineFlags::ShouldSkip(FLAGS_match, slide->getName().c_str())) {
850             fSlides.push_back(slide);
851         }
852     }
853 
854     // Particle demo
855     {
856         // TODO: Convert this to a sample
857         sk_sp<Slide> slide(new ParticlesSlide());
858         if (!CommandLineFlags::ShouldSkip(FLAGS_match, slide->getName().c_str())) {
859             fSlides.push_back(std::move(slide));
860         }
861     }
862 
863     // Runtime shader editor
864     {
865         sk_sp<Slide> slide(new SkSLSlide());
866         if (!CommandLineFlags::ShouldSkip(FLAGS_match, slide->getName().c_str())) {
867             fSlides.push_back(std::move(slide));
868         }
869     }
870 
871     for (const auto& info : gExternalSlidesInfo) {
872         for (const auto& flag : info.fFlags) {
873             if (SkStrEndsWith(flag.c_str(), info.fExtension)) {
874                 // single file
875                 addSlide(SkOSPath::Basename(flag.c_str()), flag, info.fFactory);
876             } else {
877                 // directory
878                 SkString name;
879                 SkTArray<SkString> sortedFilenames;
880                 SkOSFile::Iter it(flag.c_str(), info.fExtension);
881                 while (it.next(&name)) {
882                     sortedFilenames.push_back(name);
883                 }
884                 if (sortedFilenames.count()) {
885                     SkTQSort(sortedFilenames.begin(), sortedFilenames.end(),
886                              [](const SkString& a, const SkString& b) {
887                                  return strcmp(a.c_str(), b.c_str()) < 0;
888                              });
889                 }
890                 for (const SkString& filename : sortedFilenames) {
891                     addSlide(filename, SkOSPath::Join(flag.c_str(), filename.c_str()),
892                              info.fFactory);
893                 }
894             }
895             if (!dirSlides.empty()) {
896                 fSlides.push_back(
897                     sk_make_sp<SlideDir>(SkStringPrintf("%s[%s]", info.fDirName, flag.c_str()),
898                                          std::move(dirSlides)));
899                 dirSlides.reset();  // NOLINT(bugprone-use-after-move)
900             }
901         }
902     }
903 
904     if (!fSlides.count()) {
905         sk_sp<Slide> slide(new NullSlide());
906         fSlides.push_back(std::move(slide));
907     }
908 }
909 
910 
~Viewer()911 Viewer::~Viewer() {
912     for(auto& slide : fSlides) {
913         slide->gpuTeardown();
914     }
915 
916     fWindow->detach();
917     delete fWindow;
918 }
919 
920 struct SkPaintTitleUpdater {
SkPaintTitleUpdaterSkPaintTitleUpdater921     SkPaintTitleUpdater(SkString* title) : fTitle(title), fCount(0) {}
appendSkPaintTitleUpdater922     void append(const char* s) {
923         if (fCount == 0) {
924             fTitle->append(" {");
925         } else {
926             fTitle->append(", ");
927         }
928         fTitle->append(s);
929         ++fCount;
930     }
doneSkPaintTitleUpdater931     void done() {
932         if (fCount > 0) {
933             fTitle->append("}");
934         }
935     }
936     SkString* fTitle;
937     int fCount;
938 };
939 
updateTitle()940 void Viewer::updateTitle() {
941     if (!fWindow) {
942         return;
943     }
944     if (fWindow->sampleCount() < 1) {
945         return; // Surface hasn't been created yet.
946     }
947 
948     SkString title("Viewer: ");
949     title.append(fSlides[fCurrentSlide]->getName());
950 
951     if (gSkUseAnalyticAA) {
952         if (gSkForceAnalyticAA) {
953             title.append(" <FAAA>");
954         } else {
955             title.append(" <AAA>");
956         }
957     }
958     if (fDrawViaSerialize) {
959         title.append(" <serialize>");
960     }
961     if (gUseSkVMBlitter) {
962         title.append(" <SkVMBlitter>");
963     }
964     if (!gSkVMAllowJIT) {
965         title.append(" <SkVM interpreter>");
966     }
967 
968     SkPaintTitleUpdater paintTitle(&title);
969     auto paintFlag = [this, &paintTitle](bool SkPaintFields::* flag,
970                                          bool (SkPaint::* isFlag)() const,
971                                          const char* on, const char* off)
972     {
973         if (fPaintOverrides.*flag) {
974             paintTitle.append((fPaint.*isFlag)() ? on : off);
975         }
976     };
977 
978     auto fontFlag = [this, &paintTitle](bool SkFontFields::* flag, bool (SkFont::* isFlag)() const,
979                                         const char* on, const char* off)
980     {
981         if (fFontOverrides.*flag) {
982             paintTitle.append((fFont.*isFlag)() ? on : off);
983         }
984     };
985 
986     paintFlag(&SkPaintFields::fAntiAlias, &SkPaint::isAntiAlias, "Antialias", "Alias");
987     paintFlag(&SkPaintFields::fDither, &SkPaint::isDither, "DITHER", "No Dither");
988 
989     fontFlag(&SkFontFields::fForceAutoHinting, &SkFont::isForceAutoHinting,
990              "Force Autohint", "No Force Autohint");
991     fontFlag(&SkFontFields::fEmbolden, &SkFont::isEmbolden, "Fake Bold", "No Fake Bold");
992     fontFlag(&SkFontFields::fBaselineSnap, &SkFont::isBaselineSnap, "BaseSnap", "No BaseSnap");
993     fontFlag(&SkFontFields::fLinearMetrics, &SkFont::isLinearMetrics,
994              "Linear Metrics", "Non-Linear Metrics");
995     fontFlag(&SkFontFields::fEmbeddedBitmaps, &SkFont::isEmbeddedBitmaps,
996              "Bitmap Text", "No Bitmap Text");
997     fontFlag(&SkFontFields::fSubpixel, &SkFont::isSubpixel, "Subpixel Text", "Pixel Text");
998 
999     if (fFontOverrides.fEdging) {
1000         switch (fFont.getEdging()) {
1001             case SkFont::Edging::kAlias:
1002                 paintTitle.append("Alias Text");
1003                 break;
1004             case SkFont::Edging::kAntiAlias:
1005                 paintTitle.append("Antialias Text");
1006                 break;
1007             case SkFont::Edging::kSubpixelAntiAlias:
1008                 paintTitle.append("Subpixel Antialias Text");
1009                 break;
1010         }
1011     }
1012 
1013     if (fFontOverrides.fHinting) {
1014         switch (fFont.getHinting()) {
1015             case SkFontHinting::kNone:
1016                 paintTitle.append("No Hinting");
1017                 break;
1018             case SkFontHinting::kSlight:
1019                 paintTitle.append("Slight Hinting");
1020                 break;
1021             case SkFontHinting::kNormal:
1022                 paintTitle.append("Normal Hinting");
1023                 break;
1024             case SkFontHinting::kFull:
1025                 paintTitle.append("Full Hinting");
1026                 break;
1027         }
1028     }
1029     paintTitle.done();
1030 
1031     switch (fColorMode) {
1032         case ColorMode::kLegacy:
1033             title.append(" Legacy 8888");
1034             break;
1035         case ColorMode::kColorManaged8888:
1036             title.append(" ColorManaged 8888");
1037             break;
1038         case ColorMode::kColorManagedF16:
1039             title.append(" ColorManaged F16");
1040             break;
1041         case ColorMode::kColorManagedF16Norm:
1042             title.append(" ColorManaged F16 Norm");
1043             break;
1044     }
1045 
1046     if (ColorMode::kLegacy != fColorMode) {
1047         int curPrimaries = -1;
1048         for (size_t i = 0; i < SK_ARRAY_COUNT(gNamedPrimaries); ++i) {
1049             if (primaries_equal(*gNamedPrimaries[i].fPrimaries, fColorSpacePrimaries)) {
1050                 curPrimaries = i;
1051                 break;
1052             }
1053         }
1054         title.appendf(" %s Gamma %f",
1055                       curPrimaries >= 0 ? gNamedPrimaries[curPrimaries].fName : "Custom",
1056                       fColorSpaceTransferFn.g);
1057     }
1058 
1059     const DisplayParams& params = fWindow->getRequestedDisplayParams();
1060     if (fDisplayOverrides.fSurfaceProps.fPixelGeometry) {
1061         switch (params.fSurfaceProps.pixelGeometry()) {
1062             case kUnknown_SkPixelGeometry:
1063                 title.append( " Flat");
1064                 break;
1065             case kRGB_H_SkPixelGeometry:
1066                 title.append( " RGB");
1067                 break;
1068             case kBGR_H_SkPixelGeometry:
1069                 title.append( " BGR");
1070                 break;
1071             case kRGB_V_SkPixelGeometry:
1072                 title.append( " RGBV");
1073                 break;
1074             case kBGR_V_SkPixelGeometry:
1075                 title.append( " BGRV");
1076                 break;
1077         }
1078     }
1079 
1080     if (params.fSurfaceProps.isUseDeviceIndependentFonts()) {
1081         title.append(" DFT");
1082     }
1083 
1084     title.append(" [");
1085     title.append(kBackendTypeStrings[fBackendType]);
1086     int msaa = fWindow->sampleCount();
1087     if (msaa > 1) {
1088         title.appendf(" MSAA: %i", msaa);
1089     }
1090     title.append("]");
1091 
1092     GpuPathRenderers pr = fWindow->getRequestedDisplayParams().fGrContextOptions.fGpuPathRenderers;
1093     if (GpuPathRenderers::kDefault != pr) {
1094         title.appendf(" [Path renderer: %s]", gPathRendererNames[pr].c_str());
1095     }
1096 
1097     if (kPerspective_Real == fPerspectiveMode) {
1098         title.append(" Perpsective (Real)");
1099     } else if (kPerspective_Fake == fPerspectiveMode) {
1100         title.append(" Perspective (Fake)");
1101     }
1102 
1103     fWindow->setTitle(title.c_str());
1104 }
1105 
startupSlide() const1106 int Viewer::startupSlide() const {
1107 
1108     if (!FLAGS_slide.isEmpty()) {
1109         int count = fSlides.count();
1110         for (int i = 0; i < count; i++) {
1111             if (fSlides[i]->getName().equals(FLAGS_slide[0])) {
1112                 return i;
1113             }
1114         }
1115 
1116         fprintf(stderr, "Unknown slide \"%s\"\n", FLAGS_slide[0]);
1117         this->listNames();
1118     }
1119 
1120     return 0;
1121 }
1122 
listNames() const1123 void Viewer::listNames() const {
1124     SkDebugf("All Slides:\n");
1125     for (const auto& slide : fSlides) {
1126         SkDebugf("    %s\n", slide->getName().c_str());
1127     }
1128 }
1129 
setCurrentSlide(int slide)1130 void Viewer::setCurrentSlide(int slide) {
1131     SkASSERT(slide >= 0 && slide < fSlides.count());
1132 
1133     if (slide == fCurrentSlide) {
1134         return;
1135     }
1136 
1137     if (fCurrentSlide >= 0) {
1138         fSlides[fCurrentSlide]->unload();
1139     }
1140 
1141     SkScalar scaleFactor = 1.0;
1142     if (fApplyBackingScale) {
1143         scaleFactor = fWindow->scaleFactor();
1144     }
1145     fSlides[slide]->load(SkIntToScalar(fWindow->width()) / scaleFactor,
1146                          SkIntToScalar(fWindow->height()) / scaleFactor);
1147     fCurrentSlide = slide;
1148     this->setupCurrentSlide();
1149 }
1150 
setupCurrentSlide()1151 void Viewer::setupCurrentSlide() {
1152     if (fCurrentSlide >= 0) {
1153         // prepare dimensions for image slides
1154         fGesture.resetTouchState();
1155         fDefaultMatrix.reset();
1156 
1157         const SkISize slideSize = fSlides[fCurrentSlide]->getDimensions();
1158         const SkRect slideBounds = SkRect::MakeIWH(slideSize.width(), slideSize.height());
1159         const SkRect windowRect = SkRect::MakeIWH(fWindow->width(), fWindow->height());
1160 
1161         // Start with a matrix that scales the slide to the available screen space
1162         if (fWindow->scaleContentToFit()) {
1163             if (windowRect.width() > 0 && windowRect.height() > 0) {
1164                 fDefaultMatrix = SkMatrix::RectToRect(slideBounds, windowRect,
1165                                                       SkMatrix::kStart_ScaleToFit);
1166             }
1167         }
1168 
1169         // Prevent the user from dragging content so far outside the window they can't find it again
1170         fGesture.setTransLimit(slideBounds, windowRect, this->computePreTouchMatrix());
1171 
1172         this->updateTitle();
1173         this->updateUIState();
1174 
1175         fStatsLayer.resetMeasurements();
1176 
1177         fWindow->inval();
1178     }
1179 }
1180 
1181 #define MAX_ZOOM_LEVEL  8.0f
1182 #define MIN_ZOOM_LEVEL  -8.0f
1183 
changeZoomLevel(float delta)1184 void Viewer::changeZoomLevel(float delta) {
1185     fZoomLevel += delta;
1186     fZoomLevel = SkTPin(fZoomLevel, MIN_ZOOM_LEVEL, MAX_ZOOM_LEVEL);
1187     this->preTouchMatrixChanged();
1188 }
1189 
preTouchMatrixChanged()1190 void Viewer::preTouchMatrixChanged() {
1191     // Update the trans limit as the transform changes.
1192     const SkISize slideSize = fSlides[fCurrentSlide]->getDimensions();
1193     const SkRect slideBounds = SkRect::MakeIWH(slideSize.width(), slideSize.height());
1194     const SkRect windowRect = SkRect::MakeIWH(fWindow->width(), fWindow->height());
1195     fGesture.setTransLimit(slideBounds, windowRect, this->computePreTouchMatrix());
1196 }
1197 
computePerspectiveMatrix()1198 SkMatrix Viewer::computePerspectiveMatrix() {
1199     SkScalar w = fWindow->width(), h = fWindow->height();
1200     SkPoint orthoPts[4] = { { 0, 0 }, { w, 0 }, { 0, h }, { w, h } };
1201     SkPoint perspPts[4] = {
1202         { fPerspectivePoints[0].fX * w, fPerspectivePoints[0].fY * h },
1203         { fPerspectivePoints[1].fX * w, fPerspectivePoints[1].fY * h },
1204         { fPerspectivePoints[2].fX * w, fPerspectivePoints[2].fY * h },
1205         { fPerspectivePoints[3].fX * w, fPerspectivePoints[3].fY * h }
1206     };
1207     SkMatrix m;
1208     m.setPolyToPoly(orthoPts, perspPts, 4);
1209     return m;
1210 }
1211 
computePreTouchMatrix()1212 SkMatrix Viewer::computePreTouchMatrix() {
1213     SkMatrix m = fDefaultMatrix;
1214 
1215     SkScalar zoomScale = exp(fZoomLevel);
1216     if (fApplyBackingScale) {
1217         zoomScale *= fWindow->scaleFactor();
1218     }
1219     m.preTranslate((fOffset.x() - 0.5f) * 2.0f, (fOffset.y() - 0.5f) * 2.0f);
1220     m.preScale(zoomScale, zoomScale);
1221 
1222     const SkISize slideSize = fSlides[fCurrentSlide]->getDimensions();
1223     m.preRotate(fRotation, slideSize.width() * 0.5f, slideSize.height() * 0.5f);
1224 
1225     if (kPerspective_Real == fPerspectiveMode) {
1226         SkMatrix persp = this->computePerspectiveMatrix();
1227         m.postConcat(persp);
1228     }
1229 
1230     return m;
1231 }
1232 
computeMatrix()1233 SkMatrix Viewer::computeMatrix() {
1234     SkMatrix m = fGesture.localM();
1235     m.preConcat(fGesture.globalM());
1236     m.preConcat(this->computePreTouchMatrix());
1237     return m;
1238 }
1239 
setBackend(sk_app::Window::BackendType backendType)1240 void Viewer::setBackend(sk_app::Window::BackendType backendType) {
1241     fPersistentCache.reset();
1242     fCachedShaders.reset();
1243     fBackendType = backendType;
1244 
1245     // The active context is going away in 'detach'
1246     for(auto& slide : fSlides) {
1247         slide->gpuTeardown();
1248     }
1249 
1250     fWindow->detach();
1251 
1252 #if defined(SK_BUILD_FOR_WIN)
1253     // Switching between OpenGL, Vulkan, and ANGLE in the same window is problematic at this point
1254     // on Windows, so we just delete the window and recreate it.
1255     DisplayParams params = fWindow->getRequestedDisplayParams();
1256     delete fWindow;
1257     fWindow = Window::CreateNativeWindow(nullptr);
1258 
1259     // re-register callbacks
1260     fCommands.attach(fWindow);
1261     fWindow->pushLayer(this);
1262     fWindow->pushLayer(&fStatsLayer);
1263     fWindow->pushLayer(&fImGuiLayer);
1264 
1265     // Don't allow the window to re-attach. If we're in MSAA mode, the params we grabbed above
1266     // will still include our correct sample count. But the re-created fWindow will lose that
1267     // information. On Windows, we need to re-create the window when changing sample count,
1268     // so we'll incorrectly detect that situation, then re-initialize the window in GL mode,
1269     // rendering this tear-down step pointless (and causing the Vulkan window context to fail
1270     // as if we had never changed windows at all).
1271     fWindow->setRequestedDisplayParams(params, false);
1272 #endif
1273 
1274     fWindow->attach(backend_type_for_window(fBackendType));
1275 }
1276 
setColorMode(ColorMode colorMode)1277 void Viewer::setColorMode(ColorMode colorMode) {
1278     fColorMode = colorMode;
1279     this->updateTitle();
1280     fWindow->inval();
1281 }
1282 
1283 class OveridePaintFilterCanvas : public SkPaintFilterCanvas {
1284 public:
OveridePaintFilterCanvas(SkCanvas * canvas,SkPaint * paint,Viewer::SkPaintFields * pfields,SkFont * font,Viewer::SkFontFields * ffields)1285     OveridePaintFilterCanvas(SkCanvas* canvas,
1286                              SkPaint* paint, Viewer::SkPaintFields* pfields,
1287                              SkFont* font, Viewer::SkFontFields* ffields)
1288         : SkPaintFilterCanvas(canvas)
1289         , fPaint(paint)
1290         , fPaintOverrides(pfields)
1291         , fFont(font)
1292         , fFontOverrides(ffields) {
1293     }
1294 
filterTextBlob(const SkPaint & paint,const SkTextBlob * blob,sk_sp<SkTextBlob> * cache)1295     const SkTextBlob* filterTextBlob(const SkPaint& paint,
1296                                      const SkTextBlob* blob,
1297                                      sk_sp<SkTextBlob>* cache) {
1298         bool blobWillChange = false;
1299         for (SkTextBlobRunIterator it(blob); !it.done(); it.next()) {
1300             SkTCopyOnFirstWrite<SkFont> filteredFont(it.font());
1301             bool shouldDraw = this->filterFont(&filteredFont);
1302             if (it.font() != *filteredFont || !shouldDraw) {
1303                 blobWillChange = true;
1304                 break;
1305             }
1306         }
1307         if (!blobWillChange) {
1308             return blob;
1309         }
1310 
1311         SkTextBlobBuilder builder;
1312         for (SkTextBlobRunIterator it(blob); !it.done(); it.next()) {
1313             SkTCopyOnFirstWrite<SkFont> filteredFont(it.font());
1314             bool shouldDraw = this->filterFont(&filteredFont);
1315             if (!shouldDraw) {
1316                 continue;
1317             }
1318 
1319             SkFont font = *filteredFont;
1320 
1321             const SkTextBlobBuilder::RunBuffer& runBuffer
1322                 = it.positioning() == SkTextBlobRunIterator::kDefault_Positioning
1323                     ? builder.allocRunText(font, it.glyphCount(), it.offset().x(),it.offset().y(),
1324                                            it.textSize())
1325                 : it.positioning() == SkTextBlobRunIterator::kHorizontal_Positioning
1326                     ? builder.allocRunTextPosH(font, it.glyphCount(), it.offset().y(),
1327                                                it.textSize())
1328                 : it.positioning() == SkTextBlobRunIterator::kFull_Positioning
1329                     ? builder.allocRunTextPos(font, it.glyphCount(), it.textSize())
1330                 : it.positioning() == SkTextBlobRunIterator::kRSXform_Positioning
1331                     ? builder.allocRunTextRSXform(font, it.glyphCount(), it.textSize())
1332                 : (SkASSERT_RELEASE(false), SkTextBlobBuilder::RunBuffer());
1333             uint32_t glyphCount = it.glyphCount();
1334             if (it.glyphs()) {
1335                 size_t glyphSize = sizeof(decltype(*it.glyphs()));
1336                 memcpy(runBuffer.glyphs, it.glyphs(), glyphCount * glyphSize);
1337             }
1338             if (it.pos()) {
1339                 size_t posSize = sizeof(decltype(*it.pos()));
1340                 unsigned posPerGlyph = it.scalarsPerGlyph();
1341                 memcpy(runBuffer.pos, it.pos(), glyphCount * posPerGlyph * posSize);
1342             }
1343             if (it.text()) {
1344                 size_t textSize = sizeof(decltype(*it.text()));
1345                 uint32_t textCount = it.textSize();
1346                 memcpy(runBuffer.utf8text, it.text(), textCount * textSize);
1347             }
1348             if (it.clusters()) {
1349                 size_t clusterSize = sizeof(decltype(*it.clusters()));
1350                 memcpy(runBuffer.clusters, it.clusters(), glyphCount * clusterSize);
1351             }
1352         }
1353         *cache = builder.make();
1354         return cache->get();
1355     }
onDrawTextBlob(const SkTextBlob * blob,SkScalar x,SkScalar y,const SkPaint & paint)1356     void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
1357                         const SkPaint& paint) override {
1358         sk_sp<SkTextBlob> cache;
1359         this->SkPaintFilterCanvas::onDrawTextBlob(
1360             this->filterTextBlob(paint, blob, &cache), x, y, paint);
1361     }
filterFont(SkTCopyOnFirstWrite<SkFont> * font) const1362     bool filterFont(SkTCopyOnFirstWrite<SkFont>* font) const {
1363         if (fFontOverrides->fTypeface) {
1364             font->writable()->setTypeface(fFont->refTypeface());
1365         }
1366         if (fFontOverrides->fSize) {
1367             font->writable()->setSize(fFont->getSize());
1368         }
1369         if (fFontOverrides->fScaleX) {
1370             font->writable()->setScaleX(fFont->getScaleX());
1371         }
1372         if (fFontOverrides->fSkewX) {
1373             font->writable()->setSkewX(fFont->getSkewX());
1374         }
1375         if (fFontOverrides->fHinting) {
1376             font->writable()->setHinting(fFont->getHinting());
1377         }
1378         if (fFontOverrides->fEdging) {
1379             font->writable()->setEdging(fFont->getEdging());
1380         }
1381         if (fFontOverrides->fSubpixel) {
1382             font->writable()->setSubpixel(fFont->isSubpixel());
1383         }
1384         if (fFontOverrides->fForceAutoHinting) {
1385             font->writable()->setForceAutoHinting(fFont->isForceAutoHinting());
1386         }
1387         if (fFontOverrides->fEmbeddedBitmaps) {
1388             font->writable()->setEmbeddedBitmaps(fFont->isEmbeddedBitmaps());
1389         }
1390         if (fFontOverrides->fLinearMetrics) {
1391             font->writable()->setLinearMetrics(fFont->isLinearMetrics());
1392         }
1393         if (fFontOverrides->fEmbolden) {
1394             font->writable()->setEmbolden(fFont->isEmbolden());
1395         }
1396         if (fFontOverrides->fBaselineSnap) {
1397             font->writable()->setBaselineSnap(fFont->isBaselineSnap());
1398         }
1399 
1400         return true; // we, currently, never elide a draw
1401     }
1402 
onFilter(SkPaint & paint) const1403     bool onFilter(SkPaint& paint) const override {
1404         if (fPaintOverrides->fPathEffect) {
1405             paint.setPathEffect(fPaint->refPathEffect());
1406         }
1407         if (fPaintOverrides->fShader) {
1408             paint.setShader(fPaint->refShader());
1409         }
1410         if (fPaintOverrides->fMaskFilter) {
1411             paint.setMaskFilter(fPaint->refMaskFilter());
1412         }
1413         if (fPaintOverrides->fColorFilter) {
1414             paint.setColorFilter(fPaint->refColorFilter());
1415         }
1416         if (fPaintOverrides->fImageFilter) {
1417             paint.setImageFilter(fPaint->refImageFilter());
1418         }
1419         if (fPaintOverrides->fColor) {
1420             paint.setColor4f(fPaint->getColor4f());
1421         }
1422         if (fPaintOverrides->fStrokeWidth) {
1423             paint.setStrokeWidth(fPaint->getStrokeWidth());
1424         }
1425         if (fPaintOverrides->fMiterLimit) {
1426             paint.setStrokeMiter(fPaint->getStrokeMiter());
1427         }
1428         if (fPaintOverrides->fBlendMode) {
1429             paint.setBlendMode(fPaint->getBlendMode_or(SkBlendMode::kSrc));
1430         }
1431         if (fPaintOverrides->fAntiAlias) {
1432             paint.setAntiAlias(fPaint->isAntiAlias());
1433         }
1434         if (fPaintOverrides->fDither) {
1435             paint.setDither(fPaint->isDither());
1436         }
1437         if (fPaintOverrides->fForceRuntimeBlend) {
1438             if (skstd::optional<SkBlendMode> mode = paint.asBlendMode()) {
1439                 paint.setBlender(GetRuntimeBlendForBlendMode(*mode));
1440             }
1441         }
1442         if (fPaintOverrides->fCapType) {
1443             paint.setStrokeCap(fPaint->getStrokeCap());
1444         }
1445         if (fPaintOverrides->fJoinType) {
1446             paint.setStrokeJoin(fPaint->getStrokeJoin());
1447         }
1448         if (fPaintOverrides->fStyle) {
1449             paint.setStyle(fPaint->getStyle());
1450         }
1451         return true; // we, currently, never elide a draw
1452     }
1453     SkPaint* fPaint;
1454     Viewer::SkPaintFields* fPaintOverrides;
1455     SkFont* fFont;
1456     Viewer::SkFontFields* fFontOverrides;
1457 };
1458 
drawSlide(SkSurface * surface)1459 void Viewer::drawSlide(SkSurface* surface) {
1460     if (fCurrentSlide < 0) {
1461         return;
1462     }
1463 
1464     SkAutoCanvasRestore autorestore(surface->getCanvas(), false);
1465 
1466     // By default, we render directly into the window's surface/canvas
1467     SkSurface* slideSurface = surface;
1468     SkCanvas* slideCanvas = surface->getCanvas();
1469     fLastImage.reset();
1470 
1471     // If we're in any of the color managed modes, construct the color space we're going to use
1472     sk_sp<SkColorSpace> colorSpace = nullptr;
1473     if (ColorMode::kLegacy != fColorMode) {
1474         skcms_Matrix3x3 toXYZ;
1475         SkAssertResult(fColorSpacePrimaries.toXYZD50(&toXYZ));
1476         colorSpace = SkColorSpace::MakeRGB(fColorSpaceTransferFn, toXYZ);
1477     }
1478 
1479     if (fSaveToSKP) {
1480         SkPictureRecorder recorder;
1481         SkCanvas* recorderCanvas = recorder.beginRecording(
1482                 SkRect::Make(fSlides[fCurrentSlide]->getDimensions()));
1483         fSlides[fCurrentSlide]->draw(recorderCanvas);
1484         sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture());
1485         SkFILEWStream stream("sample_app.skp");
1486         picture->serialize(&stream);
1487         fSaveToSKP = false;
1488     }
1489 
1490     // Grab some things we'll need to make surfaces (for tiling or general offscreen rendering)
1491     SkColorType colorType;
1492     switch (fColorMode) {
1493         case ColorMode::kLegacy:
1494         case ColorMode::kColorManaged8888:
1495             colorType = kN32_SkColorType;
1496             break;
1497         case ColorMode::kColorManagedF16:
1498             colorType = kRGBA_F16_SkColorType;
1499             break;
1500         case ColorMode::kColorManagedF16Norm:
1501             colorType = kRGBA_F16Norm_SkColorType;
1502             break;
1503     }
1504 
1505     auto make_surface = [=](int w, int h) {
1506         SkSurfaceProps props(fWindow->getRequestedDisplayParams().fSurfaceProps);
1507         slideCanvas->getProps(&props);
1508 
1509         SkImageInfo info = SkImageInfo::Make(w, h, colorType, kPremul_SkAlphaType, colorSpace);
1510         return Window::kRaster_BackendType == this->fBackendType
1511                 ? SkSurface::MakeRaster(info, &props)
1512                 : slideCanvas->makeSurface(info, &props);
1513     };
1514 
1515     // We need to render offscreen if we're...
1516     // ... in fake perspective or zooming (so we have a snapped copy of the results)
1517     // ... in any raster mode, because the window surface is actually GL
1518     // ... in any color managed mode, because we always make the window surface with no color space
1519     // ... or if the user explicitly requested offscreen rendering
1520     sk_sp<SkSurface> offscreenSurface = nullptr;
1521     if (kPerspective_Fake == fPerspectiveMode ||
1522         fShowZoomWindow ||
1523         Window::kRaster_BackendType == fBackendType ||
1524         colorSpace != nullptr ||
1525         FLAGS_offscreen) {
1526 
1527         offscreenSurface = make_surface(fWindow->width(), fWindow->height());
1528         slideSurface = offscreenSurface.get();
1529         slideCanvas = offscreenSurface->getCanvas();
1530     }
1531 
1532     SkPictureRecorder recorder;
1533     SkCanvas* recorderRestoreCanvas = nullptr;
1534     if (fDrawViaSerialize) {
1535         recorderRestoreCanvas = slideCanvas;
1536         slideCanvas = recorder.beginRecording(
1537                 SkRect::Make(fSlides[fCurrentSlide]->getDimensions()));
1538     }
1539 
1540     int count = slideCanvas->save();
1541     slideCanvas->clear(SK_ColorWHITE);
1542     // Time the painting logic of the slide
1543     fStatsLayer.beginTiming(fPaintTimer);
1544     if (fTiled) {
1545         int tileW = SkScalarCeilToInt(fWindow->width() * fTileScale.width());
1546         int tileH = SkScalarCeilToInt(fWindow->height() * fTileScale.height());
1547         for (int y = 0; y < fWindow->height(); y += tileH) {
1548             for (int x = 0; x < fWindow->width(); x += tileW) {
1549                 SkAutoCanvasRestore acr(slideCanvas, true);
1550                 slideCanvas->clipRect(SkRect::MakeXYWH(x, y, tileW, tileH));
1551                 fSlides[fCurrentSlide]->draw(slideCanvas);
1552             }
1553         }
1554 
1555         // Draw borders between tiles
1556         if (fDrawTileBoundaries) {
1557             SkPaint border;
1558             border.setColor(0x60FF00FF);
1559             border.setStyle(SkPaint::kStroke_Style);
1560             for (int y = 0; y < fWindow->height(); y += tileH) {
1561                 for (int x = 0; x < fWindow->width(); x += tileW) {
1562                     slideCanvas->drawRect(SkRect::MakeXYWH(x, y, tileW, tileH), border);
1563                 }
1564             }
1565         }
1566     } else {
1567         slideCanvas->concat(this->computeMatrix());
1568         if (kPerspective_Real == fPerspectiveMode) {
1569             slideCanvas->clipRect(SkRect::MakeWH(fWindow->width(), fWindow->height()));
1570         }
1571         if (fPaintOverrides.overridesSomething() || fFontOverrides.overridesSomething()) {
1572             OveridePaintFilterCanvas filterCanvas(slideCanvas,
1573                                                   &fPaint, &fPaintOverrides,
1574                                                   &fFont, &fFontOverrides);
1575             fSlides[fCurrentSlide]->draw(&filterCanvas);
1576         } else {
1577             fSlides[fCurrentSlide]->draw(slideCanvas);
1578         }
1579     }
1580     fStatsLayer.endTiming(fPaintTimer);
1581     slideCanvas->restoreToCount(count);
1582 
1583     if (recorderRestoreCanvas) {
1584         sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture());
1585         auto data = picture->serialize();
1586         slideCanvas = recorderRestoreCanvas;
1587         slideCanvas->drawPicture(SkPicture::MakeFromData(data.get()));
1588     }
1589 
1590     // Force a flush so we can time that, too
1591     fStatsLayer.beginTiming(fFlushTimer);
1592     slideSurface->flushAndSubmit();
1593     fStatsLayer.endTiming(fFlushTimer);
1594 
1595     // If we rendered offscreen, snap an image and push the results to the window's canvas
1596     if (offscreenSurface) {
1597         fLastImage = offscreenSurface->makeImageSnapshot();
1598 
1599         SkCanvas* canvas = surface->getCanvas();
1600         SkPaint paint;
1601         paint.setBlendMode(SkBlendMode::kSrc);
1602         SkSamplingOptions sampling;
1603         int prePerspectiveCount = canvas->save();
1604         if (kPerspective_Fake == fPerspectiveMode) {
1605             sampling = SkSamplingOptions({1.0f/3, 1.0f/3});
1606             canvas->clear(SK_ColorWHITE);
1607             canvas->concat(this->computePerspectiveMatrix());
1608         }
1609         canvas->drawImage(fLastImage, 0, 0, sampling, &paint);
1610         canvas->restoreToCount(prePerspectiveCount);
1611     }
1612 
1613     if (fShowSlideDimensions) {
1614         SkCanvas* canvas = surface->getCanvas();
1615         SkAutoCanvasRestore acr(canvas, true);
1616         canvas->concat(this->computeMatrix());
1617         SkRect r = SkRect::Make(fSlides[fCurrentSlide]->getDimensions());
1618         SkPaint paint;
1619         paint.setColor(0x40FFFF00);
1620         canvas->drawRect(r, paint);
1621     }
1622 }
1623 
onBackendCreated()1624 void Viewer::onBackendCreated() {
1625     this->setupCurrentSlide();
1626     fWindow->show();
1627 }
1628 
onPaint(SkSurface * surface)1629 void Viewer::onPaint(SkSurface* surface) {
1630     this->drawSlide(surface);
1631 
1632     fCommands.drawHelp(surface->getCanvas());
1633 
1634     this->drawImGui();
1635 
1636     fLastImage.reset();
1637 
1638     if (auto direct = fWindow->directContext()) {
1639         // Clean out cache items that haven't been used in more than 10 seconds.
1640         direct->performDeferredCleanup(std::chrono::seconds(10));
1641     }
1642 }
1643 
onResize(int width,int height)1644 void Viewer::onResize(int width, int height) {
1645     if (fCurrentSlide >= 0) {
1646         SkScalar scaleFactor = 1.0;
1647         if (fApplyBackingScale) {
1648             scaleFactor = fWindow->scaleFactor();
1649         }
1650         fSlides[fCurrentSlide]->resize(width / scaleFactor, height / scaleFactor);
1651     }
1652 }
1653 
mapEvent(float x,float y)1654 SkPoint Viewer::mapEvent(float x, float y) {
1655     const auto m = this->computeMatrix();
1656     SkMatrix inv;
1657 
1658     SkAssertResult(m.invert(&inv));
1659 
1660     return inv.mapXY(x, y);
1661 }
1662 
onTouch(intptr_t owner,skui::InputState state,float x,float y)1663 bool Viewer::onTouch(intptr_t owner, skui::InputState state, float x, float y) {
1664     if (GestureDevice::kMouse == fGestureDevice) {
1665         return false;
1666     }
1667 
1668     const auto slidePt = this->mapEvent(x, y);
1669     if (fSlides[fCurrentSlide]->onMouse(slidePt.x(), slidePt.y(), state, skui::ModifierKey::kNone)) {
1670         fWindow->inval();
1671         return true;
1672     }
1673 
1674     void* castedOwner = reinterpret_cast<void*>(owner);
1675     switch (state) {
1676         case skui::InputState::kUp: {
1677             fGesture.touchEnd(castedOwner);
1678 #if defined(SK_BUILD_FOR_IOS)
1679             // TODO: move IOS swipe detection higher up into the platform code
1680             SkPoint dir;
1681             if (fGesture.isFling(&dir)) {
1682                 // swiping left or right
1683                 if (SkTAbs(dir.fX) > SkTAbs(dir.fY)) {
1684                     if (dir.fX < 0) {
1685                         this->setCurrentSlide(fCurrentSlide < fSlides.count() - 1 ?
1686                                               fCurrentSlide + 1 : 0);
1687                     } else {
1688                         this->setCurrentSlide(fCurrentSlide > 0 ?
1689                                               fCurrentSlide - 1 : fSlides.count() - 1);
1690                     }
1691                 }
1692                 fGesture.reset();
1693             }
1694 #endif
1695             break;
1696         }
1697         case skui::InputState::kDown: {
1698             fGesture.touchBegin(castedOwner, x, y);
1699             break;
1700         }
1701         case skui::InputState::kMove: {
1702             fGesture.touchMoved(castedOwner, x, y);
1703             break;
1704         }
1705         default: {
1706             // kLeft and kRight are only for swipes
1707             SkASSERT(false);
1708             break;
1709         }
1710     }
1711     fGestureDevice = fGesture.isBeingTouched() ? GestureDevice::kTouch : GestureDevice::kNone;
1712     fWindow->inval();
1713     return true;
1714 }
1715 
onMouse(int x,int y,skui::InputState state,skui::ModifierKey modifiers)1716 bool Viewer::onMouse(int x, int y, skui::InputState state, skui::ModifierKey modifiers) {
1717     if (GestureDevice::kTouch == fGestureDevice) {
1718         return false;
1719     }
1720 
1721     const auto slidePt = this->mapEvent(x, y);
1722     if (fSlides[fCurrentSlide]->onMouse(slidePt.x(), slidePt.y(), state, modifiers)) {
1723         fWindow->inval();
1724         return true;
1725     }
1726 
1727     switch (state) {
1728         case skui::InputState::kUp: {
1729             fGesture.touchEnd(nullptr);
1730             break;
1731         }
1732         case skui::InputState::kDown: {
1733             fGesture.touchBegin(nullptr, x, y);
1734             break;
1735         }
1736         case skui::InputState::kMove: {
1737             fGesture.touchMoved(nullptr, x, y);
1738             break;
1739         }
1740         default: {
1741             SkASSERT(false); // shouldn't see kRight or kLeft here
1742             break;
1743         }
1744     }
1745     fGestureDevice = fGesture.isBeingTouched() ? GestureDevice::kMouse : GestureDevice::kNone;
1746 
1747     if (state != skui::InputState::kMove || fGesture.isBeingTouched()) {
1748         fWindow->inval();
1749     }
1750     return true;
1751 }
1752 
onFling(skui::InputState state)1753 bool Viewer::onFling(skui::InputState state) {
1754     if (skui::InputState::kRight == state) {
1755         this->setCurrentSlide(fCurrentSlide > 0 ? fCurrentSlide - 1 : fSlides.count() - 1);
1756         return true;
1757     } else if (skui::InputState::kLeft == state) {
1758         this->setCurrentSlide(fCurrentSlide < fSlides.count() - 1 ? fCurrentSlide + 1 : 0);
1759         return true;
1760     }
1761     return false;
1762 }
1763 
onPinch(skui::InputState state,float scale,float x,float y)1764 bool Viewer::onPinch(skui::InputState state, float scale, float x, float y) {
1765     switch (state) {
1766         case skui::InputState::kDown:
1767             fGesture.startZoom();
1768             return true;
1769             break;
1770         case skui::InputState::kMove:
1771             fGesture.updateZoom(scale, x, y, x, y);
1772             return true;
1773             break;
1774         case skui::InputState::kUp:
1775             fGesture.endZoom();
1776             return true;
1777             break;
1778         default:
1779             SkASSERT(false);
1780             break;
1781     }
1782 
1783     return false;
1784 }
1785 
ImGui_Primaries(SkColorSpacePrimaries * primaries,SkPaint * gamutPaint)1786 static void ImGui_Primaries(SkColorSpacePrimaries* primaries, SkPaint* gamutPaint) {
1787     // The gamut image covers a (0.8 x 0.9) shaped region
1788     ImGui::DragCanvas dc(primaries, { 0.0f, 0.9f }, { 0.8f, 0.0f });
1789 
1790     // Background image. Only draw a subset of the image, to avoid the regions less than zero.
1791     // Simplifes re-mapping math, clipping behavior, and increases resolution in the useful area.
1792     // Magic numbers are pixel locations of the origin and upper-right corner.
1793     dc.fDrawList->AddImage(gamutPaint, dc.fPos,
1794                            ImVec2(dc.fPos.x + dc.fSize.x, dc.fPos.y + dc.fSize.y),
1795                            ImVec2(242, 61), ImVec2(1897, 1922));
1796 
1797     dc.dragPoint((SkPoint*)(&primaries->fRX), true, 0xFF000040);
1798     dc.dragPoint((SkPoint*)(&primaries->fGX), true, 0xFF004000);
1799     dc.dragPoint((SkPoint*)(&primaries->fBX), true, 0xFF400000);
1800     dc.dragPoint((SkPoint*)(&primaries->fWX), true);
1801     dc.fDrawList->AddPolyline(dc.fScreenPoints.begin(), 3, 0xFFFFFFFF, true, 1.5f);
1802 }
1803 
ImGui_DragLocation(SkPoint * pt)1804 static bool ImGui_DragLocation(SkPoint* pt) {
1805     ImGui::DragCanvas dc(pt);
1806     dc.fillColor(IM_COL32(0, 0, 0, 128));
1807     dc.dragPoint(pt);
1808     return dc.fDragging;
1809 }
1810 
ImGui_DragQuad(SkPoint * pts)1811 static bool ImGui_DragQuad(SkPoint* pts) {
1812     ImGui::DragCanvas dc(pts);
1813     dc.fillColor(IM_COL32(0, 0, 0, 128));
1814 
1815     for (int i = 0; i < 4; ++i) {
1816         dc.dragPoint(pts + i);
1817     }
1818 
1819     dc.fDrawList->AddLine(dc.fScreenPoints[0], dc.fScreenPoints[1], 0xFFFFFFFF);
1820     dc.fDrawList->AddLine(dc.fScreenPoints[1], dc.fScreenPoints[3], 0xFFFFFFFF);
1821     dc.fDrawList->AddLine(dc.fScreenPoints[3], dc.fScreenPoints[2], 0xFFFFFFFF);
1822     dc.fDrawList->AddLine(dc.fScreenPoints[2], dc.fScreenPoints[0], 0xFFFFFFFF);
1823 
1824     return dc.fDragging;
1825 }
1826 
build_sksl_highlight_shader()1827 static SkSL::String build_sksl_highlight_shader() {
1828     return SkSL::String("out half4 sk_FragColor;\n"
1829                         "void main() { sk_FragColor = half4(1, 0, 1, 0.5); }");
1830 }
1831 
build_metal_highlight_shader(const SkSL::String & inShader)1832 static SkSL::String build_metal_highlight_shader(const SkSL::String& inShader) {
1833     // Metal fragment shaders need a lot of non-trivial boilerplate that we don't want to recompute
1834     // here. So keep all shader code, but right before `return *_out;`, swap out the sk_FragColor.
1835     size_t pos = inShader.rfind("return *_out;\n");
1836     if (pos == std::string::npos) {
1837         return inShader;
1838     }
1839 
1840     SkSL::String replacementShader = inShader;
1841     replacementShader.insert(pos, "_out->sk_FragColor = float4(1.0, 0.0, 1.0, 0.5); ");
1842     return replacementShader;
1843 }
1844 
build_glsl_highlight_shader(const GrShaderCaps & shaderCaps)1845 static SkSL::String build_glsl_highlight_shader(const GrShaderCaps& shaderCaps) {
1846     const char* versionDecl = shaderCaps.versionDeclString();
1847     SkSL::String highlight = versionDecl ? versionDecl : "";
1848     if (shaderCaps.usesPrecisionModifiers()) {
1849         highlight.append("precision mediump float;\n");
1850     }
1851     highlight.appendf("out vec4 sk_FragColor;\n"
1852                       "void main() { sk_FragColor = vec4(1, 0, 1, 0.5); }");
1853     return highlight;
1854 }
1855 
build_skvm_highlight_program(SkColorType ct,int nargs)1856 static skvm::Program build_skvm_highlight_program(SkColorType ct, int nargs) {
1857     // Code here is heavily tied to (and inspired by) SkVMBlitter::BuildProgram
1858     skvm::Builder b;
1859 
1860     // All VM blitters start with two arguments (uniforms, dst surface)
1861     SkASSERT(nargs >= 2);
1862     (void)b.uniform();
1863     skvm::Ptr dst_ptr = b.varying(SkColorTypeBytesPerPixel(ct));
1864 
1865     // Depending on coverage and shader, there can be additional arguments.
1866     // Make sure that we append the right number, so that we don't assert when
1867     // the CPU backend tries to run this program.
1868     for (int i = 2; i < nargs; ++i) {
1869         (void)b.uniform();
1870     }
1871 
1872     skvm::Color magenta = {b.splat(1.0f), b.splat(0.0f), b.splat(1.0f), b.splat(0.5f)};
1873     skvm::PixelFormat dstFormat = skvm::SkColorType_to_PixelFormat(ct);
1874     store(dstFormat, dst_ptr, magenta);
1875 
1876     return b.done();
1877 }
1878 
drawImGui()1879 void Viewer::drawImGui() {
1880     // Support drawing the ImGui demo window. Superfluous, but gives a good idea of what's possible
1881     if (fShowImGuiTestWindow) {
1882         ImGui::ShowDemoWindow(&fShowImGuiTestWindow);
1883     }
1884 
1885     if (fShowImGuiDebugWindow) {
1886         // We have some dynamic content that sizes to fill available size. If the scroll bar isn't
1887         // always visible, we can end up in a layout feedback loop.
1888         ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiCond_FirstUseEver);
1889         DisplayParams params = fWindow->getRequestedDisplayParams();
1890         bool displayParamsChanged = false; // heavy-weight, might recreate entire context
1891         bool uiParamsChanged = false;      // light weight, just triggers window invalidation
1892         auto ctx = fWindow->directContext();
1893 
1894         if (ImGui::Begin("Tools", &fShowImGuiDebugWindow,
1895                          ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
1896             if (ImGui::CollapsingHeader("Backend")) {
1897                 int newBackend = static_cast<int>(fBackendType);
1898                 ImGui::RadioButton("Raster", &newBackend, sk_app::Window::kRaster_BackendType);
1899                 ImGui::SameLine();
1900                 ImGui::RadioButton("OpenGL", &newBackend, sk_app::Window::kNativeGL_BackendType);
1901 #if SK_ANGLE && defined(SK_BUILD_FOR_WIN)
1902                 ImGui::SameLine();
1903                 ImGui::RadioButton("ANGLE", &newBackend, sk_app::Window::kANGLE_BackendType);
1904 #endif
1905 #if defined(SK_DAWN)
1906                 ImGui::SameLine();
1907                 ImGui::RadioButton("Dawn", &newBackend, sk_app::Window::kDawn_BackendType);
1908 #endif
1909 #if defined(SK_VULKAN) && !defined(SK_BUILD_FOR_MAC)
1910                 ImGui::SameLine();
1911                 ImGui::RadioButton("Vulkan", &newBackend, sk_app::Window::kVulkan_BackendType);
1912 #endif
1913 #if defined(SK_METAL)
1914                 ImGui::SameLine();
1915                 ImGui::RadioButton("Metal", &newBackend, sk_app::Window::kMetal_BackendType);
1916 #if defined(SK_GRAPHITE_ENABLED)
1917                 ImGui::SameLine();
1918                 ImGui::RadioButton("Metal (Graphite)", &newBackend,
1919                                    sk_app::Window::kGraphiteMetal_BackendType);
1920 #endif
1921 #endif
1922 #if defined(SK_DIRECT3D)
1923                 ImGui::SameLine();
1924                 ImGui::RadioButton("Direct3D", &newBackend, sk_app::Window::kDirect3D_BackendType);
1925 #endif
1926                 if (newBackend != fBackendType) {
1927                     fDeferredActions.push_back([=]() {
1928                         this->setBackend(static_cast<sk_app::Window::BackendType>(newBackend));
1929                     });
1930                 }
1931 
1932                 bool* wire = &params.fGrContextOptions.fWireframeMode;
1933                 if (ctx && ImGui::Checkbox("Wireframe Mode", wire)) {
1934                     displayParamsChanged = true;
1935                 }
1936 
1937                 bool* reducedShaders = &params.fGrContextOptions.fReducedShaderVariations;
1938                 if (ctx && ImGui::Checkbox("Reduced shaders", reducedShaders)) {
1939                     displayParamsChanged = true;
1940                 }
1941 
1942                 if (ctx) {
1943                     // Determine the context's max sample count for MSAA radio buttons.
1944                     int sampleCount = fWindow->sampleCount();
1945                     int maxMSAA = (fBackendType != sk_app::Window::kRaster_BackendType) ?
1946                             ctx->maxSurfaceSampleCountForColorType(kRGBA_8888_SkColorType) :
1947                             1;
1948 
1949                     // Only display the MSAA radio buttons when there are options above 1x MSAA.
1950                     if (maxMSAA >= 4) {
1951                         ImGui::Text("MSAA: ");
1952 
1953                         for (int curMSAA = 1; curMSAA <= maxMSAA; curMSAA *= 2) {
1954                             // 2x MSAA works, but doesn't offer much of a visual improvement, so we
1955                             // don't show it in the list.
1956                             if (curMSAA == 2) {
1957                                 continue;
1958                             }
1959                             ImGui::SameLine();
1960                             ImGui::RadioButton(SkStringPrintf("%d", curMSAA).c_str(),
1961                                                &sampleCount, curMSAA);
1962                         }
1963                     }
1964 
1965                     if (sampleCount != params.fMSAASampleCount) {
1966                         params.fMSAASampleCount = sampleCount;
1967                         displayParamsChanged = true;
1968                     }
1969                 }
1970 
1971                 int pixelGeometryIdx = 0;
1972                 if (fDisplayOverrides.fSurfaceProps.fPixelGeometry) {
1973                     pixelGeometryIdx = params.fSurfaceProps.pixelGeometry() + 1;
1974                 }
1975                 if (ImGui::Combo("Pixel Geometry", &pixelGeometryIdx,
1976                                  "Default\0Flat\0RGB\0BGR\0RGBV\0BGRV\0\0"))
1977                 {
1978                     uint32_t flags = params.fSurfaceProps.flags();
1979                     if (pixelGeometryIdx == 0) {
1980                         fDisplayOverrides.fSurfaceProps.fPixelGeometry = false;
1981                         SkPixelGeometry pixelGeometry = fDisplay.fSurfaceProps.pixelGeometry();
1982                         params.fSurfaceProps = SkSurfaceProps(flags, pixelGeometry);
1983                     } else {
1984                         fDisplayOverrides.fSurfaceProps.fPixelGeometry = true;
1985                         SkPixelGeometry pixelGeometry = SkTo<SkPixelGeometry>(pixelGeometryIdx - 1);
1986                         params.fSurfaceProps = SkSurfaceProps(flags, pixelGeometry);
1987                     }
1988                     displayParamsChanged = true;
1989                 }
1990 
1991                 bool useDFT = params.fSurfaceProps.isUseDeviceIndependentFonts();
1992                 if (ImGui::Checkbox("DFT", &useDFT)) {
1993                     uint32_t flags = params.fSurfaceProps.flags();
1994                     if (useDFT) {
1995                         flags |= SkSurfaceProps::kUseDeviceIndependentFonts_Flag;
1996                     } else {
1997                         flags &= ~SkSurfaceProps::kUseDeviceIndependentFonts_Flag;
1998                     }
1999                     SkPixelGeometry pixelGeometry = params.fSurfaceProps.pixelGeometry();
2000                     params.fSurfaceProps = SkSurfaceProps(flags, pixelGeometry);
2001                     displayParamsChanged = true;
2002                 }
2003 
2004                 if (ImGui::TreeNode("Path Renderers")) {
2005                     GpuPathRenderers prevPr = params.fGrContextOptions.fGpuPathRenderers;
2006                     auto prButton = [&](GpuPathRenderers x) {
2007                         if (ImGui::RadioButton(gPathRendererNames[x].c_str(), prevPr == x)) {
2008                             if (x != params.fGrContextOptions.fGpuPathRenderers) {
2009                                 params.fGrContextOptions.fGpuPathRenderers = x;
2010                                 displayParamsChanged = true;
2011                             }
2012                         }
2013                     };
2014 
2015                     if (!ctx) {
2016                         ImGui::RadioButton("Software", true);
2017                     } else {
2018                         prButton(GpuPathRenderers::kDefault);
2019 #if SK_GPU_V1
2020                         if (fWindow->sampleCount() > 1 || FLAGS_dmsaa) {
2021                             const auto* caps = ctx->priv().caps();
2022                             if (skgpu::v1::AtlasPathRenderer::IsSupported(ctx)) {
2023                                 prButton(GpuPathRenderers::kAtlas);
2024                             }
2025                             if (skgpu::v1::TessellationPathRenderer::IsSupported(*caps)) {
2026                                 prButton(GpuPathRenderers::kTessellation);
2027                             }
2028                         }
2029 #endif
2030                         if (1 == fWindow->sampleCount()) {
2031                             prButton(GpuPathRenderers::kSmall);
2032                         }
2033                         prButton(GpuPathRenderers::kTriangulating);
2034                         prButton(GpuPathRenderers::kNone);
2035                     }
2036                     ImGui::TreePop();
2037                 }
2038             }
2039 
2040             if (ImGui::CollapsingHeader("Tiling")) {
2041                 ImGui::Checkbox("Enable", &fTiled);
2042                 ImGui::Checkbox("Draw Boundaries", &fDrawTileBoundaries);
2043                 ImGui::SliderFloat("Horizontal", &fTileScale.fWidth, 0.1f, 1.0f);
2044                 ImGui::SliderFloat("Vertical", &fTileScale.fHeight, 0.1f, 1.0f);
2045             }
2046 
2047             if (ImGui::CollapsingHeader("Transform")) {
2048                 if (ImGui::Checkbox("Apply Backing Scale", &fApplyBackingScale)) {
2049                     this->preTouchMatrixChanged();
2050                     this->onResize(fWindow->width(), fWindow->height());
2051                     // This changes how we manipulate the canvas transform, it's not changing the
2052                     // window's actual parameters.
2053                     uiParamsChanged = true;
2054                 }
2055 
2056                 float zoom = fZoomLevel;
2057                 if (ImGui::SliderFloat("Zoom", &zoom, MIN_ZOOM_LEVEL, MAX_ZOOM_LEVEL)) {
2058                     fZoomLevel = zoom;
2059                     this->preTouchMatrixChanged();
2060                     uiParamsChanged = true;
2061                 }
2062                 float deg = fRotation;
2063                 if (ImGui::SliderFloat("Rotate", &deg, -30, 360, "%.3f deg")) {
2064                     fRotation = deg;
2065                     this->preTouchMatrixChanged();
2066                     uiParamsChanged = true;
2067                 }
2068                 if (ImGui::CollapsingHeader("Subpixel offset", ImGuiTreeNodeFlags_NoTreePushOnOpen)) {
2069                     if (ImGui_DragLocation(&fOffset)) {
2070                         this->preTouchMatrixChanged();
2071                         uiParamsChanged = true;
2072                     }
2073                 } else if (fOffset != SkVector{0.5f, 0.5f}) {
2074                     this->preTouchMatrixChanged();
2075                     uiParamsChanged = true;
2076                     fOffset = {0.5f, 0.5f};
2077                 }
2078                 int perspectiveMode = static_cast<int>(fPerspectiveMode);
2079                 if (ImGui::Combo("Perspective", &perspectiveMode, "Off\0Real\0Fake\0\0")) {
2080                     fPerspectiveMode = static_cast<PerspectiveMode>(perspectiveMode);
2081                     this->preTouchMatrixChanged();
2082                     uiParamsChanged = true;
2083                 }
2084                 if (perspectiveMode != kPerspective_Off && ImGui_DragQuad(fPerspectivePoints)) {
2085                     this->preTouchMatrixChanged();
2086                     uiParamsChanged = true;
2087                 }
2088             }
2089 
2090             if (ImGui::CollapsingHeader("Paint")) {
2091                 int aliasIdx = 0;
2092                 if (fPaintOverrides.fAntiAlias) {
2093                     aliasIdx = SkTo<int>(fPaintOverrides.fAntiAliasState) + 1;
2094                 }
2095                 if (ImGui::Combo("Anti-Alias", &aliasIdx,
2096                                  "Default\0Alias\0Normal\0AnalyticAAEnabled\0AnalyticAAForced\0\0"))
2097                 {
2098                     gSkUseAnalyticAA = fPaintOverrides.fOriginalSkUseAnalyticAA;
2099                     gSkForceAnalyticAA = fPaintOverrides.fOriginalSkForceAnalyticAA;
2100                     if (aliasIdx == 0) {
2101                         fPaintOverrides.fAntiAliasState = SkPaintFields::AntiAliasState::Alias;
2102                         fPaintOverrides.fAntiAlias = false;
2103                     } else {
2104                         fPaintOverrides.fAntiAlias = true;
2105                         fPaintOverrides.fAntiAliasState = SkTo<SkPaintFields::AntiAliasState>(aliasIdx-1);
2106                         fPaint.setAntiAlias(aliasIdx > 1);
2107                         switch (fPaintOverrides.fAntiAliasState) {
2108                             case SkPaintFields::AntiAliasState::Alias:
2109                                 break;
2110                             case SkPaintFields::AntiAliasState::Normal:
2111                                 break;
2112                             case SkPaintFields::AntiAliasState::AnalyticAAEnabled:
2113                                 gSkUseAnalyticAA = true;
2114                                 gSkForceAnalyticAA = false;
2115                                 break;
2116                             case SkPaintFields::AntiAliasState::AnalyticAAForced:
2117                                 gSkUseAnalyticAA = gSkForceAnalyticAA = true;
2118                                 break;
2119                         }
2120                     }
2121                     uiParamsChanged = true;
2122                 }
2123 
2124                 auto paintFlag = [this, &uiParamsChanged](const char* label, const char* items,
2125                                                           bool SkPaintFields::* flag,
2126                                                           bool (SkPaint::* isFlag)() const,
2127                                                           void (SkPaint::* setFlag)(bool) )
2128                 {
2129                     int itemIndex = 0;
2130                     if (fPaintOverrides.*flag) {
2131                         itemIndex = (fPaint.*isFlag)() ? 2 : 1;
2132                     }
2133                     if (ImGui::Combo(label, &itemIndex, items)) {
2134                         if (itemIndex == 0) {
2135                             fPaintOverrides.*flag = false;
2136                         } else {
2137                             fPaintOverrides.*flag = true;
2138                             (fPaint.*setFlag)(itemIndex == 2);
2139                         }
2140                         uiParamsChanged = true;
2141                     }
2142                 };
2143 
2144                 paintFlag("Dither",
2145                           "Default\0No Dither\0Dither\0\0",
2146                           &SkPaintFields::fDither,
2147                           &SkPaint::isDither, &SkPaint::setDither);
2148 
2149                 int styleIdx = 0;
2150                 if (fPaintOverrides.fStyle) {
2151                     styleIdx = SkTo<int>(fPaint.getStyle()) + 1;
2152                 }
2153                 if (ImGui::Combo("Style", &styleIdx,
2154                                  "Default\0Fill\0Stroke\0Stroke and Fill\0\0"))
2155                 {
2156                     if (styleIdx == 0) {
2157                         fPaintOverrides.fStyle = false;
2158                         fPaint.setStyle(SkPaint::kFill_Style);
2159                     } else {
2160                         fPaint.setStyle(SkTo<SkPaint::Style>(styleIdx - 1));
2161                         fPaintOverrides.fStyle = true;
2162                     }
2163                     uiParamsChanged = true;
2164                 }
2165 
2166                 ImGui::Checkbox("Force Runtime Blends", &fPaintOverrides.fForceRuntimeBlend);
2167 
2168                 ImGui::Checkbox("Override Stroke Width", &fPaintOverrides.fStrokeWidth);
2169                 if (fPaintOverrides.fStrokeWidth) {
2170                     float width = fPaint.getStrokeWidth();
2171                     if (ImGui::SliderFloat("Stroke Width", &width, 0, 20)) {
2172                         fPaint.setStrokeWidth(width);
2173                         uiParamsChanged = true;
2174                     }
2175                 }
2176 
2177                 ImGui::Checkbox("Override Miter Limit", &fPaintOverrides.fMiterLimit);
2178                 if (fPaintOverrides.fMiterLimit) {
2179                     float miterLimit = fPaint.getStrokeMiter();
2180                     if (ImGui::SliderFloat("Miter Limit", &miterLimit, 0, 20)) {
2181                         fPaint.setStrokeMiter(miterLimit);
2182                         uiParamsChanged = true;
2183                     }
2184                 }
2185 
2186                 int capIdx = 0;
2187                 if (fPaintOverrides.fCapType) {
2188                     capIdx = SkTo<int>(fPaint.getStrokeCap()) + 1;
2189                 }
2190                 if (ImGui::Combo("Cap Type", &capIdx,
2191                                  "Default\0Butt\0Round\0Square\0\0"))
2192                 {
2193                     if (capIdx == 0) {
2194                         fPaintOverrides.fCapType = false;
2195                         fPaint.setStrokeCap(SkPaint::kDefault_Cap);
2196                     } else {
2197                         fPaint.setStrokeCap(SkTo<SkPaint::Cap>(capIdx - 1));
2198                         fPaintOverrides.fCapType = true;
2199                     }
2200                     uiParamsChanged = true;
2201                 }
2202 
2203                 int joinIdx = 0;
2204                 if (fPaintOverrides.fJoinType) {
2205                     joinIdx = SkTo<int>(fPaint.getStrokeJoin()) + 1;
2206                 }
2207                 if (ImGui::Combo("Join Type", &joinIdx,
2208                                  "Default\0Miter\0Round\0Bevel\0\0"))
2209                 {
2210                     if (joinIdx == 0) {
2211                         fPaintOverrides.fJoinType = false;
2212                         fPaint.setStrokeJoin(SkPaint::kDefault_Join);
2213                     } else {
2214                         fPaint.setStrokeJoin(SkTo<SkPaint::Join>(joinIdx - 1));
2215                         fPaintOverrides.fJoinType = true;
2216                     }
2217                     uiParamsChanged = true;
2218                 }
2219             }
2220 
2221             if (ImGui::CollapsingHeader("Font")) {
2222                 int hintingIdx = 0;
2223                 if (fFontOverrides.fHinting) {
2224                     hintingIdx = SkTo<int>(fFont.getHinting()) + 1;
2225                 }
2226                 if (ImGui::Combo("Hinting", &hintingIdx,
2227                                  "Default\0None\0Slight\0Normal\0Full\0\0"))
2228                 {
2229                     if (hintingIdx == 0) {
2230                         fFontOverrides.fHinting = false;
2231                         fFont.setHinting(SkFontHinting::kNone);
2232                     } else {
2233                         fFont.setHinting(SkTo<SkFontHinting>(hintingIdx - 1));
2234                         fFontOverrides.fHinting = true;
2235                     }
2236                     uiParamsChanged = true;
2237                 }
2238 
2239                 auto fontFlag = [this, &uiParamsChanged](const char* label, const char* items,
2240                                                         bool SkFontFields::* flag,
2241                                                         bool (SkFont::* isFlag)() const,
2242                                                         void (SkFont::* setFlag)(bool) )
2243                 {
2244                     int itemIndex = 0;
2245                     if (fFontOverrides.*flag) {
2246                         itemIndex = (fFont.*isFlag)() ? 2 : 1;
2247                     }
2248                     if (ImGui::Combo(label, &itemIndex, items)) {
2249                         if (itemIndex == 0) {
2250                             fFontOverrides.*flag = false;
2251                         } else {
2252                             fFontOverrides.*flag = true;
2253                             (fFont.*setFlag)(itemIndex == 2);
2254                         }
2255                         uiParamsChanged = true;
2256                     }
2257                 };
2258 
2259                 fontFlag("Fake Bold Glyphs",
2260                          "Default\0No Fake Bold\0Fake Bold\0\0",
2261                          &SkFontFields::fEmbolden,
2262                          &SkFont::isEmbolden, &SkFont::setEmbolden);
2263 
2264                 fontFlag("Baseline Snapping",
2265                          "Default\0No Baseline Snapping\0Baseline Snapping\0\0",
2266                          &SkFontFields::fBaselineSnap,
2267                          &SkFont::isBaselineSnap, &SkFont::setBaselineSnap);
2268 
2269                 fontFlag("Linear Text",
2270                          "Default\0No Linear Text\0Linear Text\0\0",
2271                          &SkFontFields::fLinearMetrics,
2272                          &SkFont::isLinearMetrics, &SkFont::setLinearMetrics);
2273 
2274                 fontFlag("Subpixel Position Glyphs",
2275                          "Default\0Pixel Text\0Subpixel Text\0\0",
2276                          &SkFontFields::fSubpixel,
2277                          &SkFont::isSubpixel, &SkFont::setSubpixel);
2278 
2279                 fontFlag("Embedded Bitmap Text",
2280                          "Default\0No Embedded Bitmaps\0Embedded Bitmaps\0\0",
2281                          &SkFontFields::fEmbeddedBitmaps,
2282                          &SkFont::isEmbeddedBitmaps, &SkFont::setEmbeddedBitmaps);
2283 
2284                 fontFlag("Force Auto-Hinting",
2285                          "Default\0No Force Auto-Hinting\0Force Auto-Hinting\0\0",
2286                          &SkFontFields::fForceAutoHinting,
2287                          &SkFont::isForceAutoHinting, &SkFont::setForceAutoHinting);
2288 
2289                 int edgingIdx = 0;
2290                 if (fFontOverrides.fEdging) {
2291                     edgingIdx = SkTo<int>(fFont.getEdging()) + 1;
2292                 }
2293                 if (ImGui::Combo("Edging", &edgingIdx,
2294                                  "Default\0Alias\0Antialias\0Subpixel Antialias\0\0"))
2295                 {
2296                     if (edgingIdx == 0) {
2297                         fFontOverrides.fEdging = false;
2298                         fFont.setEdging(SkFont::Edging::kAlias);
2299                     } else {
2300                         fFont.setEdging(SkTo<SkFont::Edging>(edgingIdx-1));
2301                         fFontOverrides.fEdging = true;
2302                     }
2303                     uiParamsChanged = true;
2304                 }
2305 
2306                 ImGui::Checkbox("Override Size", &fFontOverrides.fSize);
2307                 if (fFontOverrides.fSize) {
2308                     ImGui::DragFloat2("TextRange", fFontOverrides.fSizeRange,
2309                                       0.001f, -10.0f, 300.0f, "%.6f", 2.0f);
2310                     float textSize = fFont.getSize();
2311                     if (ImGui::DragFloat("TextSize", &textSize, 0.001f,
2312                                          fFontOverrides.fSizeRange[0],
2313                                          fFontOverrides.fSizeRange[1],
2314                                          "%.6f", 2.0f))
2315                     {
2316                         fFont.setSize(textSize);
2317                         uiParamsChanged = true;
2318                     }
2319                 }
2320 
2321                 ImGui::Checkbox("Override ScaleX", &fFontOverrides.fScaleX);
2322                 if (fFontOverrides.fScaleX) {
2323                     float scaleX = fFont.getScaleX();
2324                     if (ImGui::SliderFloat("ScaleX", &scaleX, MIN_ZOOM_LEVEL, MAX_ZOOM_LEVEL)) {
2325                         fFont.setScaleX(scaleX);
2326                         uiParamsChanged = true;
2327                     }
2328                 }
2329 
2330                 ImGui::Checkbox("Override SkewX", &fFontOverrides.fSkewX);
2331                 if (fFontOverrides.fSkewX) {
2332                     float skewX = fFont.getSkewX();
2333                     if (ImGui::SliderFloat("SkewX", &skewX, MIN_ZOOM_LEVEL, MAX_ZOOM_LEVEL)) {
2334                         fFont.setSkewX(skewX);
2335                         uiParamsChanged = true;
2336                     }
2337                 }
2338             }
2339 
2340             {
2341                 SkMetaData controls;
2342                 if (fSlides[fCurrentSlide]->onGetControls(&controls)) {
2343                     if (ImGui::CollapsingHeader("Current Slide")) {
2344                         SkMetaData::Iter iter(controls);
2345                         const char* name;
2346                         SkMetaData::Type type;
2347                         int count;
2348                         while ((name = iter.next(&type, &count)) != nullptr) {
2349                             if (type == SkMetaData::kScalar_Type) {
2350                                 float val[3];
2351                                 SkASSERT(count == 3);
2352                                 controls.findScalars(name, &count, val);
2353                                 if (ImGui::SliderFloat(name, &val[0], val[1], val[2])) {
2354                                     controls.setScalars(name, 3, val);
2355                                 }
2356                             } else if (type == SkMetaData::kBool_Type) {
2357                                 bool val;
2358                                 SkASSERT(count == 1);
2359                                 controls.findBool(name, &val);
2360                                 if (ImGui::Checkbox(name, &val)) {
2361                                     controls.setBool(name, val);
2362                                 }
2363                             }
2364                         }
2365                         fSlides[fCurrentSlide]->onSetControls(controls);
2366                     }
2367                 }
2368             }
2369 
2370             if (fShowSlidePicker) {
2371                 ImGui::SetNextTreeNodeOpen(true);
2372             }
2373             if (ImGui::CollapsingHeader("Slide")) {
2374                 static ImGuiTextFilter filter;
2375                 static ImVector<const char*> filteredSlideNames;
2376                 static ImVector<int> filteredSlideIndices;
2377 
2378                 if (fShowSlidePicker) {
2379                     ImGui::SetKeyboardFocusHere();
2380                     fShowSlidePicker = false;
2381                 }
2382 
2383                 filter.Draw();
2384                 filteredSlideNames.clear();
2385                 filteredSlideIndices.clear();
2386                 int filteredIndex = 0;
2387                 for (int i = 0; i < fSlides.count(); ++i) {
2388                     const char* slideName = fSlides[i]->getName().c_str();
2389                     if (filter.PassFilter(slideName) || i == fCurrentSlide) {
2390                         if (i == fCurrentSlide) {
2391                             filteredIndex = filteredSlideIndices.size();
2392                         }
2393                         filteredSlideNames.push_back(slideName);
2394                         filteredSlideIndices.push_back(i);
2395                     }
2396                 }
2397 
2398                 if (ImGui::ListBox("", &filteredIndex, filteredSlideNames.begin(),
2399                                    filteredSlideNames.size(), 20)) {
2400                     this->setCurrentSlide(filteredSlideIndices[filteredIndex]);
2401                 }
2402             }
2403 
2404             if (ImGui::CollapsingHeader("Color Mode")) {
2405                 ColorMode newMode = fColorMode;
2406                 auto cmButton = [&](ColorMode mode, const char* label) {
2407                     if (ImGui::RadioButton(label, mode == fColorMode)) {
2408                         newMode = mode;
2409                     }
2410                 };
2411 
2412                 cmButton(ColorMode::kLegacy, "Legacy 8888");
2413                 cmButton(ColorMode::kColorManaged8888, "Color Managed 8888");
2414                 cmButton(ColorMode::kColorManagedF16, "Color Managed F16");
2415                 cmButton(ColorMode::kColorManagedF16Norm, "Color Managed F16 Norm");
2416 
2417                 if (newMode != fColorMode) {
2418                     this->setColorMode(newMode);
2419                 }
2420 
2421                 // Pick from common gamuts:
2422                 int primariesIdx = 4; // Default: Custom
2423                 for (size_t i = 0; i < SK_ARRAY_COUNT(gNamedPrimaries); ++i) {
2424                     if (primaries_equal(*gNamedPrimaries[i].fPrimaries, fColorSpacePrimaries)) {
2425                         primariesIdx = i;
2426                         break;
2427                     }
2428                 }
2429 
2430                 // Let user adjust the gamma
2431                 ImGui::SliderFloat("Gamma", &fColorSpaceTransferFn.g, 0.5f, 3.5f);
2432 
2433                 if (ImGui::Combo("Primaries", &primariesIdx,
2434                                  "sRGB\0AdobeRGB\0P3\0Rec. 2020\0Custom\0\0")) {
2435                     if (primariesIdx >= 0 && primariesIdx <= 3) {
2436                         fColorSpacePrimaries = *gNamedPrimaries[primariesIdx].fPrimaries;
2437                     }
2438                 }
2439 
2440                 if (ImGui::Button("Spin")) {
2441                     float rx = fColorSpacePrimaries.fRX,
2442                           ry = fColorSpacePrimaries.fRY;
2443                     fColorSpacePrimaries.fRX = fColorSpacePrimaries.fGX;
2444                     fColorSpacePrimaries.fRY = fColorSpacePrimaries.fGY;
2445                     fColorSpacePrimaries.fGX = fColorSpacePrimaries.fBX;
2446                     fColorSpacePrimaries.fGY = fColorSpacePrimaries.fBY;
2447                     fColorSpacePrimaries.fBX = rx;
2448                     fColorSpacePrimaries.fBY = ry;
2449                 }
2450 
2451                 // Allow direct editing of gamut
2452                 ImGui_Primaries(&fColorSpacePrimaries, &fImGuiGamutPaint);
2453             }
2454 
2455             if (ImGui::CollapsingHeader("Animation")) {
2456                 bool isPaused = AnimTimer::kPaused_State == fAnimTimer.state();
2457                 if (ImGui::Checkbox("Pause", &isPaused)) {
2458                     fAnimTimer.togglePauseResume();
2459                 }
2460 
2461                 float speed = fAnimTimer.getSpeed();
2462                 if (ImGui::DragFloat("Speed", &speed, 0.1f)) {
2463                     fAnimTimer.setSpeed(speed);
2464                 }
2465             }
2466 
2467             if (ImGui::CollapsingHeader("Shaders")) {
2468                 bool sksl = params.fGrContextOptions.fShaderCacheStrategy ==
2469                             GrContextOptions::ShaderCacheStrategy::kSkSL;
2470 
2471 #if defined(SK_VULKAN)
2472                 const bool isVulkan = fBackendType == sk_app::Window::kVulkan_BackendType;
2473 #else
2474                 const bool isVulkan = false;
2475 #endif
2476 
2477                 // To re-load shaders from the currently active programs, we flush all
2478                 // caches on one frame, then set a flag to poll the cache on the next frame.
2479                 static bool gLoadPending = false;
2480                 if (gLoadPending) {
2481                     auto collectShaders = [this](sk_sp<const SkData> key, sk_sp<SkData> data,
2482                                                  const SkString& description, int hitCount) {
2483                         CachedShader& entry(fCachedShaders.push_back());
2484                         entry.fKey = key;
2485                         SkMD5 hash;
2486                         hash.write(key->bytes(), key->size());
2487                         SkMD5::Digest digest = hash.finish();
2488                         for (int i = 0; i < 16; ++i) {
2489                             entry.fKeyString.appendf("%02x", digest.data[i]);
2490                         }
2491                         entry.fKeyDescription = description;
2492 
2493                         SkReadBuffer reader(data->data(), data->size());
2494                         entry.fShaderType = GrPersistentCacheUtils::GetType(&reader);
2495                         GrPersistentCacheUtils::UnpackCachedShaders(&reader, entry.fShader,
2496                                                                     entry.fInputs,
2497                                                                     kGrShaderTypeCount);
2498                     };
2499                     fCachedShaders.reset();
2500                     fPersistentCache.foreach(collectShaders);
2501                     gLoadPending = false;
2502 
2503 #if defined(SK_VULKAN)
2504                     if (isVulkan && !sksl) {
2505                         spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_0);
2506                         for (auto& entry : fCachedShaders) {
2507                             for (int i = 0; i < kGrShaderTypeCount; ++i) {
2508                                 const SkSL::String& spirv(entry.fShader[i]);
2509                                 std::string disasm;
2510                                 tools.Disassemble((const uint32_t*)spirv.c_str(), spirv.size() / 4,
2511                                                   &disasm);
2512                                 entry.fShader[i].assign(disasm);
2513                             }
2514                         }
2515                     }
2516 #endif
2517                 }
2518 
2519                 // Defer actually doing the View/Apply logic so that we can trigger an Apply when we
2520                 // start or finish hovering on a tree node in the list below:
2521                 bool doView      = ImGui::Button("View"); ImGui::SameLine();
2522                 bool doApply     = ImGui::Button("Apply Changes"); ImGui::SameLine();
2523                 bool doDump      = ImGui::Button("Dump SkSL to resources/sksl/");
2524 
2525                 int newOptLevel = fOptLevel;
2526                 ImGui::RadioButton("SkSL", &newOptLevel, kShaderOptLevel_Source);
2527                 ImGui::SameLine();
2528                 ImGui::RadioButton("Compile", &newOptLevel, kShaderOptLevel_Compile);
2529                 ImGui::SameLine();
2530                 ImGui::RadioButton("Optimize", &newOptLevel, kShaderOptLevel_Optimize);
2531                 ImGui::SameLine();
2532                 ImGui::RadioButton("Inline", &newOptLevel, kShaderOptLevel_Inline);
2533 
2534                 // If we are changing the compile mode, we want to reset the cache and redo
2535                 // everything.
2536                 if (doDump || newOptLevel != fOptLevel) {
2537                     sksl = doDump || (newOptLevel == kShaderOptLevel_Source);
2538                     fOptLevel = (ShaderOptLevel)newOptLevel;
2539                     switch (fOptLevel) {
2540                         case kShaderOptLevel_Source:
2541                             Compiler::EnableOptimizer(OverrideFlag::kDefault);
2542                             Compiler::EnableInliner(OverrideFlag::kDefault);
2543                             break;
2544                         case kShaderOptLevel_Compile:
2545                             Compiler::EnableOptimizer(OverrideFlag::kOff);
2546                             Compiler::EnableInliner(OverrideFlag::kOff);
2547                             break;
2548                         case kShaderOptLevel_Optimize:
2549                             Compiler::EnableOptimizer(OverrideFlag::kOn);
2550                             Compiler::EnableInliner(OverrideFlag::kOff);
2551                             break;
2552                         case kShaderOptLevel_Inline:
2553                             Compiler::EnableOptimizer(OverrideFlag::kOn);
2554                             Compiler::EnableInliner(OverrideFlag::kOn);
2555                             break;
2556                     }
2557 
2558                     params.fGrContextOptions.fShaderCacheStrategy =
2559                             sksl ? GrContextOptions::ShaderCacheStrategy::kSkSL
2560                                  : GrContextOptions::ShaderCacheStrategy::kBackendSource;
2561                     displayParamsChanged = true;
2562                     doView = true;
2563 
2564                     fDeferredActions.push_back([=]() {
2565                         // Reset the cache.
2566                         fPersistentCache.reset();
2567                         // Dump the cache once we have drawn a frame with it.
2568                         if (doDump) {
2569                             fDeferredActions.push_back([this]() {
2570                                 this->dumpShadersToResources();
2571                             });
2572                         }
2573                     });
2574                 }
2575 
2576                 ImGui::BeginChild("##ScrollingRegion");
2577                 for (auto& entry : fCachedShaders) {
2578                     bool inTreeNode = ImGui::TreeNode(entry.fKeyString.c_str());
2579                     bool hovered = ImGui::IsItemHovered();
2580                     if (hovered != entry.fHovered) {
2581                         // Force an Apply to patch the highlight shader in/out
2582                         entry.fHovered = hovered;
2583                         doApply = true;
2584                     }
2585                     if (inTreeNode) {
2586                         auto stringBox = [](const char* label, std::string* str) {
2587                             // Full width, and not too much space for each shader
2588                             int lines = std::count(str->begin(), str->end(), '\n') + 2;
2589                             ImVec2 boxSize(-1.0f, ImGui::GetTextLineHeight() * std::min(lines, 30));
2590                             ImGui::InputTextMultiline(label, str, boxSize);
2591                         };
2592                         if (ImGui::TreeNode("Key")) {
2593                             ImGui::TextWrapped("%s", entry.fKeyDescription.c_str());
2594                             ImGui::TreePop();
2595                         }
2596                         stringBox("##VP", &entry.fShader[kVertex_GrShaderType]);
2597                         stringBox("##FP", &entry.fShader[kFragment_GrShaderType]);
2598                         ImGui::TreePop();
2599                     }
2600                 }
2601                 ImGui::EndChild();
2602 
2603                 if (doView) {
2604                     fPersistentCache.reset();
2605                     ctx->priv().getGpu()->resetShaderCacheForTesting();
2606                     gLoadPending = true;
2607                 }
2608 
2609                 // We don't support updating SPIRV shaders. We could re-assemble them (with edits),
2610                 // but I'm not sure anyone wants to do that.
2611                 if (isVulkan && !sksl) {
2612                     doApply = false;
2613                 }
2614                 if (doApply) {
2615                     fPersistentCache.reset();
2616                     ctx->priv().getGpu()->resetShaderCacheForTesting();
2617                     for (auto& entry : fCachedShaders) {
2618                         SkSL::String backup = entry.fShader[kFragment_GrShaderType];
2619                         if (entry.fHovered) {
2620                             // The hovered item (if any) gets a special shader to make it
2621                             // identifiable.
2622                             SkSL::String& fragShader = entry.fShader[kFragment_GrShaderType];
2623                             switch (entry.fShaderType) {
2624                                 case SkSetFourByteTag('S', 'K', 'S', 'L'): {
2625                                     fragShader = build_sksl_highlight_shader();
2626                                     break;
2627                                 }
2628                                 case SkSetFourByteTag('G', 'L', 'S', 'L'): {
2629                                     fragShader = build_glsl_highlight_shader(
2630                                         *ctx->priv().caps()->shaderCaps());
2631                                     break;
2632                                 }
2633                                 case SkSetFourByteTag('M', 'S', 'L', ' '): {
2634                                     fragShader = build_metal_highlight_shader(fragShader);
2635                                     break;
2636                                 }
2637                             }
2638                         }
2639 
2640                         auto data = GrPersistentCacheUtils::PackCachedShaders(entry.fShaderType,
2641                                                                               entry.fShader,
2642                                                                               entry.fInputs,
2643                                                                               kGrShaderTypeCount);
2644                         fPersistentCache.store(*entry.fKey, *data, entry.fKeyDescription);
2645 
2646                         entry.fShader[kFragment_GrShaderType] = backup;
2647                     }
2648                 }
2649             }
2650 
2651             if (ImGui::CollapsingHeader("SkVM")) {
2652                 auto* cache = SkVMBlitter::TryAcquireProgramCache();
2653                 SkASSERT(cache);
2654 
2655                 if (ImGui::Button("Clear")) {
2656                     cache->reset();
2657                     fDisassemblyCache.reset();
2658                 }
2659 
2660                 // First, go through the cache and restore the original program if we were hovering
2661                 if (!fHoveredProgram.empty()) {
2662                     auto restoreHoveredProgram = [this](const SkVMBlitter::Key* key,
2663                                                         skvm::Program* program) {
2664                         if (*key == fHoveredKey) {
2665                             *program = std::move(fHoveredProgram);
2666                             fHoveredProgram = {};
2667                         }
2668                     };
2669                     cache->foreach(restoreHoveredProgram);
2670                 }
2671 
2672                 // Now iterate again, and dump any expanded program. If any program is hovered,
2673                 // patch it, and remember the original (so it can be restored next frame).
2674                 auto showVMEntry = [this](const SkVMBlitter::Key* key, skvm::Program* program) {
2675                     SkString keyString = SkVMBlitter::DebugName(*key);
2676                     bool inTreeNode = ImGui::TreeNode(keyString.c_str());
2677                     bool hovered = ImGui::IsItemHovered();
2678 
2679                     if (inTreeNode) {
2680                         auto stringBox = [](const char* label, std::string* str) {
2681                             int lines = std::count(str->begin(), str->end(), '\n') + 2;
2682                             ImVec2 boxSize(-1.0f, ImGui::GetTextLineHeight() * std::min(lines, 30));
2683                             ImGui::InputTextMultiline(label, str, boxSize);
2684                         };
2685 
2686                         SkDynamicMemoryWStream stream;
2687                         program->dump(&stream);
2688                         auto dumpData = stream.detachAsData();
2689                         std::string dumpString((const char*)dumpData->data(), dumpData->size());
2690                         stringBox("##VM", &dumpString);
2691 
2692 #if defined(SKVM_JIT)
2693                         std::string* asmString = fDisassemblyCache.find(*key);
2694                         if (!asmString) {
2695                             program->disassemble(&stream);
2696                             auto asmData = stream.detachAsData();
2697                             asmString = fDisassemblyCache.set(
2698                                     *key,
2699                                     std::string((const char*)asmData->data(), asmData->size()));
2700                         }
2701                         stringBox("##ASM", asmString);
2702 #endif
2703 
2704                         ImGui::TreePop();
2705                     }
2706                     if (hovered) {
2707                         // Generate a new blitter that just draws magenta
2708                         skvm::Program highlightProgram = build_skvm_highlight_program(
2709                                 static_cast<SkColorType>(key->colorType), program->nargs());
2710 
2711                         fHoveredKey = *key;
2712                         fHoveredProgram = std::move(*program);
2713                         *program = std::move(highlightProgram);
2714                     }
2715                 };
2716                 cache->foreach(showVMEntry);
2717 
2718                 SkVMBlitter::ReleaseProgramCache();
2719             }
2720         }
2721         if (displayParamsChanged || uiParamsChanged) {
2722             fDeferredActions.push_back([=]() {
2723                 if (displayParamsChanged) {
2724                     fWindow->setRequestedDisplayParams(params);
2725                 }
2726                 fWindow->inval();
2727                 this->updateTitle();
2728             });
2729         }
2730         ImGui::End();
2731     }
2732 
2733     if (gShaderErrorHandler.fErrors.count()) {
2734         ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiCond_FirstUseEver);
2735         ImGui::Begin("Shader Errors", nullptr, ImGuiWindowFlags_NoFocusOnAppearing);
2736         for (int i = 0; i < gShaderErrorHandler.fErrors.count(); ++i) {
2737             ImGui::TextWrapped("%s", gShaderErrorHandler.fErrors[i].c_str());
2738             SkSL::String sksl(gShaderErrorHandler.fShaders[i].c_str());
2739             GrShaderUtils::VisitLineByLine(sksl, [](int lineNumber, const char* lineText) {
2740                 ImGui::TextWrapped("%4i\t%s\n", lineNumber, lineText);
2741             });
2742         }
2743         ImGui::End();
2744         gShaderErrorHandler.reset();
2745     }
2746 
2747     if (fShowZoomWindow && fLastImage) {
2748         ImGui::SetNextWindowSize(ImVec2(200, 200), ImGuiCond_FirstUseEver);
2749         if (ImGui::Begin("Zoom", &fShowZoomWindow)) {
2750             static int zoomFactor = 8;
2751             if (ImGui::Button("<<")) {
2752                 zoomFactor = std::max(zoomFactor / 2, 4);
2753             }
2754             ImGui::SameLine(); ImGui::Text("%2d", zoomFactor); ImGui::SameLine();
2755             if (ImGui::Button(">>")) {
2756                 zoomFactor = std::min(zoomFactor * 2, 32);
2757             }
2758 
2759             if (!fZoomWindowFixed) {
2760                 ImVec2 mousePos = ImGui::GetMousePos();
2761                 fZoomWindowLocation = SkPoint::Make(mousePos.x, mousePos.y);
2762             }
2763             SkScalar x = fZoomWindowLocation.x();
2764             SkScalar y = fZoomWindowLocation.y();
2765             int xInt = SkScalarRoundToInt(x);
2766             int yInt = SkScalarRoundToInt(y);
2767             ImVec2 avail = ImGui::GetContentRegionAvail();
2768 
2769             uint32_t pixel = 0;
2770             SkImageInfo info = SkImageInfo::MakeN32Premul(1, 1);
2771             auto dContext = fWindow->directContext();
2772             if (fLastImage->readPixels(dContext, info, &pixel, info.minRowBytes(), xInt, yInt)) {
2773                 ImGui::SameLine();
2774                 ImGui::Text("(X, Y): %d, %d RGBA: %X %X %X %X",
2775                             xInt, yInt,
2776                             SkGetPackedR32(pixel), SkGetPackedG32(pixel),
2777                             SkGetPackedB32(pixel), SkGetPackedA32(pixel));
2778             }
2779 
2780             fImGuiLayer.skiaWidget(avail, [=, lastImage = fLastImage](SkCanvas* c) {
2781                 // Translate so the region of the image that's under the mouse cursor is centered
2782                 // in the zoom canvas:
2783                 c->scale(zoomFactor, zoomFactor);
2784                 c->translate(avail.x * 0.5f / zoomFactor - x - 0.5f,
2785                              avail.y * 0.5f / zoomFactor - y - 0.5f);
2786                 c->drawImage(lastImage, 0, 0);
2787 
2788                 SkPaint outline;
2789                 outline.setStyle(SkPaint::kStroke_Style);
2790                 c->drawRect(SkRect::MakeXYWH(x, y, 1, 1), outline);
2791             });
2792         }
2793 
2794         ImGui::End();
2795     }
2796 }
2797 
dumpShadersToResources()2798 void Viewer::dumpShadersToResources() {
2799     // Sort the list of cached shaders so we can maintain some minimal level of consistency.
2800     // It doesn't really matter, but it will keep files from switching places unpredictably.
2801     std::vector<const CachedShader*> shaders;
2802     shaders.reserve(fCachedShaders.size());
2803     for (const CachedShader& shader : fCachedShaders) {
2804         shaders.push_back(&shader);
2805     }
2806 
2807     std::sort(shaders.begin(), shaders.end(), [](const CachedShader* a, const CachedShader* b) {
2808         return std::tie(a->fShader[kFragment_GrShaderType], a->fShader[kVertex_GrShaderType]) <
2809                std::tie(b->fShader[kFragment_GrShaderType], b->fShader[kVertex_GrShaderType]);
2810     });
2811 
2812     // Make the resources/sksl/SlideName/ directory.
2813     SkString directory = SkStringPrintf("%ssksl/%s",
2814                                         GetResourcePath().c_str(),
2815                                         fSlides[fCurrentSlide]->getName().c_str());
2816     if (!sk_mkdir(directory.c_str())) {
2817         SkDEBUGFAILF("Unable to create directory '%s'", directory.c_str());
2818         return;
2819     }
2820 
2821     int index = 0;
2822     for (const auto& entry : shaders) {
2823         SkString vertPath = SkStringPrintf("%s/Vertex_%02d.vert", directory.c_str(), index);
2824         FILE* vertFile = sk_fopen(vertPath.c_str(), kWrite_SkFILE_Flag);
2825         if (vertFile) {
2826             const SkSL::String& vertText = entry->fShader[kVertex_GrShaderType];
2827             SkAssertResult(sk_fwrite(vertText.c_str(), vertText.size(), vertFile));
2828             sk_fclose(vertFile);
2829         } else {
2830             SkDEBUGFAILF("Unable to write shader to path '%s'", vertPath.c_str());
2831         }
2832 
2833         SkString fragPath = SkStringPrintf("%s/Fragment_%02d.frag", directory.c_str(), index);
2834         FILE* fragFile = sk_fopen(fragPath.c_str(), kWrite_SkFILE_Flag);
2835         if (fragFile) {
2836             const SkSL::String& fragText = entry->fShader[kFragment_GrShaderType];
2837             SkAssertResult(sk_fwrite(fragText.c_str(), fragText.size(), fragFile));
2838             sk_fclose(fragFile);
2839         } else {
2840             SkDEBUGFAILF("Unable to write shader to path '%s'", fragPath.c_str());
2841         }
2842 
2843         ++index;
2844     }
2845 }
2846 
onIdle()2847 void Viewer::onIdle() {
2848     SkTArray<std::function<void()>> actionsToRun;
2849     actionsToRun.swap(fDeferredActions);
2850 
2851     for (const auto& fn : actionsToRun) {
2852         fn();
2853     }
2854 
2855     fStatsLayer.beginTiming(fAnimateTimer);
2856     fAnimTimer.updateTime();
2857     bool animateWantsInval = fSlides[fCurrentSlide]->animate(fAnimTimer.nanos());
2858     fStatsLayer.endTiming(fAnimateTimer);
2859 
2860     ImGuiIO& io = ImGui::GetIO();
2861     // ImGui always has at least one "active" window, which is the default "Debug" window. It may
2862     // not be visible, though. So we need to redraw if there is at least one visible window, or
2863     // more than one active window. Newly created windows are active but not visible for one frame
2864     // while they determine their layout and sizing.
2865     if (animateWantsInval || fStatsLayer.getActive() || fRefresh ||
2866         io.MetricsActiveWindows > 1 || io.MetricsRenderWindows > 0) {
2867         fWindow->inval();
2868     }
2869 }
2870 
2871 template <typename OptionsFunc>
WriteStateObject(SkJSONWriter & writer,const char * name,const char * value,OptionsFunc && optionsFunc)2872 static void WriteStateObject(SkJSONWriter& writer, const char* name, const char* value,
2873                              OptionsFunc&& optionsFunc) {
2874     writer.beginObject();
2875     {
2876         writer.appendString(kName , name);
2877         writer.appendString(kValue, value);
2878 
2879         writer.beginArray(kOptions);
2880         {
2881             optionsFunc(writer);
2882         }
2883         writer.endArray();
2884     }
2885     writer.endObject();
2886 }
2887 
2888 
updateUIState()2889 void Viewer::updateUIState() {
2890     if (!fWindow) {
2891         return;
2892     }
2893     if (fWindow->sampleCount() < 1) {
2894         return; // Surface hasn't been created yet.
2895     }
2896 
2897     SkDynamicMemoryWStream memStream;
2898     SkJSONWriter writer(&memStream);
2899     writer.beginArray();
2900 
2901     // Slide state
2902     WriteStateObject(writer, kSlideStateName, fSlides[fCurrentSlide]->getName().c_str(),
2903         [this](SkJSONWriter& writer) {
2904             for(const auto& slide : fSlides) {
2905                 writer.appendString(slide->getName().c_str());
2906             }
2907         });
2908 
2909     // Backend state
2910     WriteStateObject(writer, kBackendStateName, kBackendTypeStrings[fBackendType],
2911         [](SkJSONWriter& writer) {
2912             for (const auto& str : kBackendTypeStrings) {
2913                 writer.appendString(str);
2914             }
2915         });
2916 
2917     // MSAA state
2918     const auto countString = SkStringPrintf("%d", fWindow->sampleCount());
2919     WriteStateObject(writer, kMSAAStateName, countString.c_str(),
2920         [this](SkJSONWriter& writer) {
2921             writer.appendS32(0);
2922 
2923             if (sk_app::Window::kRaster_BackendType == fBackendType) {
2924                 return;
2925             }
2926 
2927             for (int msaa : {4, 8, 16}) {
2928                 writer.appendS32(msaa);
2929             }
2930         });
2931 
2932     // Path renderer state
2933     GpuPathRenderers pr = fWindow->getRequestedDisplayParams().fGrContextOptions.fGpuPathRenderers;
2934     WriteStateObject(writer, kPathRendererStateName, gPathRendererNames[pr].c_str(),
2935         [this](SkJSONWriter& writer) {
2936             auto ctx = fWindow->directContext();
2937             if (!ctx) {
2938                 writer.appendString("Software");
2939             } else {
2940                 writer.appendString(gPathRendererNames[GpuPathRenderers::kDefault].c_str());
2941 #if SK_GPU_V1
2942                 if (fWindow->sampleCount() > 1 || FLAGS_dmsaa) {
2943                     const auto* caps = ctx->priv().caps();
2944                     if (skgpu::v1::AtlasPathRenderer::IsSupported(ctx)) {
2945                         writer.appendString(
2946                                 gPathRendererNames[GpuPathRenderers::kAtlas].c_str());
2947                     }
2948                     if (skgpu::v1::TessellationPathRenderer::IsSupported(*caps)) {
2949                         writer.appendString(
2950                                 gPathRendererNames[GpuPathRenderers::kTessellation].c_str());
2951                     }
2952                 }
2953 #endif
2954                 if (1 == fWindow->sampleCount()) {
2955                     writer.appendString(gPathRendererNames[GpuPathRenderers::kSmall].c_str());
2956                 }
2957                 writer.appendString(gPathRendererNames[GpuPathRenderers::kTriangulating].c_str());
2958                 writer.appendString(gPathRendererNames[GpuPathRenderers::kNone].c_str());
2959             }
2960         });
2961 
2962     // Softkey state
2963     WriteStateObject(writer, kSoftkeyStateName, kSoftkeyHint,
2964         [this](SkJSONWriter& writer) {
2965             writer.appendString(kSoftkeyHint);
2966             for (const auto& softkey : fCommands.getCommandsAsSoftkeys()) {
2967                 writer.appendString(softkey.c_str());
2968             }
2969         });
2970 
2971     writer.endArray();
2972     writer.flush();
2973 
2974     auto data = memStream.detachAsData();
2975 
2976     // TODO: would be cool to avoid this copy
2977     const SkString cstring(static_cast<const char*>(data->data()), data->size());
2978 
2979     fWindow->setUIState(cstring.c_str());
2980 }
2981 
onUIStateChanged(const SkString & stateName,const SkString & stateValue)2982 void Viewer::onUIStateChanged(const SkString& stateName, const SkString& stateValue) {
2983     // For those who will add more features to handle the state change in this function:
2984     // After the change, please call updateUIState no notify the frontend (e.g., Android app).
2985     // For example, after slide change, updateUIState is called inside setupCurrentSlide;
2986     // after backend change, updateUIState is called in this function.
2987     if (stateName.equals(kSlideStateName)) {
2988         for (int i = 0; i < fSlides.count(); ++i) {
2989             if (fSlides[i]->getName().equals(stateValue)) {
2990                 this->setCurrentSlide(i);
2991                 return;
2992             }
2993         }
2994 
2995         SkDebugf("Slide not found: %s", stateValue.c_str());
2996     } else if (stateName.equals(kBackendStateName)) {
2997         for (int i = 0; i < sk_app::Window::kBackendTypeCount; i++) {
2998             if (stateValue.equals(kBackendTypeStrings[i])) {
2999                 if (fBackendType != i) {
3000                     fBackendType = (sk_app::Window::BackendType)i;
3001                     for(auto& slide : fSlides) {
3002                         slide->gpuTeardown();
3003                     }
3004                     fWindow->detach();
3005                     fWindow->attach(backend_type_for_window(fBackendType));
3006                 }
3007                 break;
3008             }
3009         }
3010     } else if (stateName.equals(kMSAAStateName)) {
3011         DisplayParams params = fWindow->getRequestedDisplayParams();
3012         int sampleCount = atoi(stateValue.c_str());
3013         if (sampleCount != params.fMSAASampleCount) {
3014             params.fMSAASampleCount = sampleCount;
3015             fWindow->setRequestedDisplayParams(params);
3016             fWindow->inval();
3017             this->updateTitle();
3018             this->updateUIState();
3019         }
3020     } else if (stateName.equals(kPathRendererStateName)) {
3021         DisplayParams params = fWindow->getRequestedDisplayParams();
3022         for (const auto& pair : gPathRendererNames) {
3023             if (pair.second == stateValue.c_str()) {
3024                 if (params.fGrContextOptions.fGpuPathRenderers != pair.first) {
3025                     params.fGrContextOptions.fGpuPathRenderers = pair.first;
3026                     fWindow->setRequestedDisplayParams(params);
3027                     fWindow->inval();
3028                     this->updateTitle();
3029                     this->updateUIState();
3030                 }
3031                 break;
3032             }
3033         }
3034     } else if (stateName.equals(kSoftkeyStateName)) {
3035         if (!stateValue.equals(kSoftkeyHint)) {
3036             fCommands.onSoftkey(stateValue);
3037             this->updateUIState(); // This is still needed to reset the value to kSoftkeyHint
3038         }
3039     } else if (stateName.equals(kRefreshStateName)) {
3040         // This state is actually NOT in the UI state.
3041         // We use this to allow Android to quickly set bool fRefresh.
3042         fRefresh = stateValue.equals(kON);
3043     } else {
3044         SkDebugf("Unknown stateName: %s", stateName.c_str());
3045     }
3046 }
3047 
onKey(skui::Key key,skui::InputState state,skui::ModifierKey modifiers)3048 bool Viewer::onKey(skui::Key key, skui::InputState state, skui::ModifierKey modifiers) {
3049     return fCommands.onKey(key, state, modifiers);
3050 }
3051 
onChar(SkUnichar c,skui::ModifierKey modifiers)3052 bool Viewer::onChar(SkUnichar c, skui::ModifierKey modifiers) {
3053     if (fSlides[fCurrentSlide]->onChar(c)) {
3054         fWindow->inval();
3055         return true;
3056     } else {
3057         return fCommands.onChar(c, modifiers);
3058     }
3059 }
3060