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 "BisectSlide.h"
9 #include "GMSlide.h"
10 #include "GrContext.h"
11 #include "GrContextPriv.h"
12 #include "ImageSlide.h"
13 #include "Resources.h"
14 #include "SKPSlide.h"
15 #include "SampleSlide.h"
16 #include "SkCanvas.h"
17 #include "SkColorSpacePriv.h"
18 #include "SkCommandLineFlags.h"
19 #include "SkCommonFlags.h"
20 #include "SkCommonFlagsGpu.h"
21 #include "SkEventTracingPriv.h"
22 #include "SkFontMgrPriv.h"
23 #include "SkGraphics.h"
24 #include "SkImagePriv.h"
25 #include "SkJSONWriter.h"
26 #include "SkMakeUnique.h"
27 #include "SkOSFile.h"
28 #include "SkOSPath.h"
29 #include "SkPaintFilterCanvas.h"
30 #include "SkPictureRecorder.h"
31 #include "SkScan.h"
32 #include "SkStream.h"
33 #include "SkSurface.h"
34 #include "SkTaskGroup.h"
35 #include "SkTestFontMgr.h"
36 #include "SkTo.h"
37 #include "SlideDir.h"
38 #include "SvgSlide.h"
39 #include "Viewer.h"
40 #include "ccpr/GrCoverageCountingPathRenderer.h"
41 
42 #include <stdlib.h>
43 #include <map>
44 
45 #include "imgui.h"
46 
47 #if defined(SK_ENABLE_SKOTTIE)
48     #include "SkottieSlide.h"
49 #endif
50 
51 #if !(defined(SK_BUILD_FOR_WIN) && defined(__clang__))
52     #include "NIMASlide.h"
53 #endif
54 
55 using namespace sk_app;
56 
57 static std::map<GpuPathRenderers, std::string> gPathRendererNames;
58 
Create(int argc,char ** argv,void * platformData)59 Application* Application::Create(int argc, char** argv, void* platformData) {
60     return new Viewer(argc, argv, platformData);
61 }
62 
63 static DEFINE_string(slide, "", "Start on this sample.");
64 static DEFINE_bool(list, false, "List samples?");
65 
66 #ifdef SK_VULKAN
67 #    define BACKENDS_STR "\"sw\", \"gl\", and \"vk\""
68 #else
69 #    define BACKENDS_STR "\"sw\" and \"gl\""
70 #endif
71 
72 static DEFINE_string2(backend, b, "sw", "Backend to use. Allowed values are " BACKENDS_STR ".");
73 
74 static DEFINE_int32(msaa, 1, "Number of subpixel samples. 0 for no HW antialiasing.");
75 
76 DEFINE_string(bisect, "", "Path to a .skp or .svg file to bisect.");
77 
78 DECLARE_int32(threads)
79 
80 DEFINE_string2(file, f, "", "Open a single file for viewing.");
81 
82 const char* kBackendTypeStrings[sk_app::Window::kBackendTypeCount] = {
83     "OpenGL",
84 #if SK_ANGLE && defined(SK_BUILD_FOR_WIN)
85     "ANGLE",
86 #endif
87 #ifdef SK_VULKAN
88     "Vulkan",
89 #endif
90     "Raster"
91 };
92 
get_backend_type(const char * str)93 static sk_app::Window::BackendType get_backend_type(const char* str) {
94 #ifdef SK_VULKAN
95     if (0 == strcmp(str, "vk")) {
96         return sk_app::Window::kVulkan_BackendType;
97     } else
98 #endif
99 #if SK_ANGLE && defined(SK_BUILD_FOR_WIN)
100     if (0 == strcmp(str, "angle")) {
101         return sk_app::Window::kANGLE_BackendType;
102     } else
103 #endif
104     if (0 == strcmp(str, "gl")) {
105         return sk_app::Window::kNativeGL_BackendType;
106     } else if (0 == strcmp(str, "sw")) {
107         return sk_app::Window::kRaster_BackendType;
108     } else {
109         SkDebugf("Unknown backend type, %s, defaulting to sw.", str);
110         return sk_app::Window::kRaster_BackendType;
111     }
112 }
113 
114 static SkColorSpacePrimaries gSrgbPrimaries = {
115     0.64f, 0.33f,
116     0.30f, 0.60f,
117     0.15f, 0.06f,
118     0.3127f, 0.3290f };
119 
120 static SkColorSpacePrimaries gAdobePrimaries = {
121     0.64f, 0.33f,
122     0.21f, 0.71f,
123     0.15f, 0.06f,
124     0.3127f, 0.3290f };
125 
126 static SkColorSpacePrimaries gP3Primaries = {
127     0.680f, 0.320f,
128     0.265f, 0.690f,
129     0.150f, 0.060f,
130     0.3127f, 0.3290f };
131 
132 static SkColorSpacePrimaries gRec2020Primaries = {
133     0.708f, 0.292f,
134     0.170f, 0.797f,
135     0.131f, 0.046f,
136     0.3127f, 0.3290f };
137 
138 struct NamedPrimaries {
139     const char* fName;
140     SkColorSpacePrimaries* fPrimaries;
141 } gNamedPrimaries[] = {
142     { "sRGB", &gSrgbPrimaries },
143     { "AdobeRGB", &gAdobePrimaries },
144     { "P3", &gP3Primaries },
145     { "Rec. 2020", &gRec2020Primaries },
146 };
147 
primaries_equal(const SkColorSpacePrimaries & a,const SkColorSpacePrimaries & b)148 static bool primaries_equal(const SkColorSpacePrimaries& a, const SkColorSpacePrimaries& b) {
149     return memcmp(&a, &b, sizeof(SkColorSpacePrimaries)) == 0;
150 }
151 
backend_type_for_window(Window::BackendType backendType)152 static Window::BackendType backend_type_for_window(Window::BackendType backendType) {
153     // In raster mode, we still use GL for the window.
154     // This lets us render the GUI faster (and correct).
155     return Window::kRaster_BackendType == backendType ? Window::kNativeGL_BackendType : backendType;
156 }
157 
158 const char* kName = "name";
159 const char* kValue = "value";
160 const char* kOptions = "options";
161 const char* kSlideStateName = "Slide";
162 const char* kBackendStateName = "Backend";
163 const char* kMSAAStateName = "MSAA";
164 const char* kPathRendererStateName = "Path renderer";
165 const char* kSoftkeyStateName = "Softkey";
166 const char* kSoftkeyHint = "Please select a softkey";
167 const char* kFpsStateName = "FPS";
168 const char* kON = "ON";
169 const char* kOFF = "OFF";
170 const char* kRefreshStateName = "Refresh";
171 
Viewer(int argc,char ** argv,void * platformData)172 Viewer::Viewer(int argc, char** argv, void* platformData)
173     : fCurrentSlide(-1)
174     , fRefresh(false)
175     , fSaveToSKP(false)
176     , fShowImGuiDebugWindow(false)
177     , fShowSlidePicker(false)
178     , fShowImGuiTestWindow(false)
179     , fShowZoomWindow(false)
180     , fZoomWindowFixed(false)
181     , fZoomWindowLocation{0.0f, 0.0f}
182     , fLastImage(nullptr)
183     , fZoomUI(false)
184     , fBackendType(sk_app::Window::kNativeGL_BackendType)
185     , fColorMode(ColorMode::kLegacy)
186     , fColorSpacePrimaries(gSrgbPrimaries)
187     // Our UI can only tweak gamma (currently), so start out gamma-only
188     , fColorSpaceTransferFn(SkNamedTransferFn::k2Dot2)
189     , fZoomLevel(0.0f)
190     , fRotation(0.0f)
191     , fOffset{0.5f, 0.5f}
192     , fGestureDevice(GestureDevice::kNone)
193     , fTiled(false)
194     , fDrawTileBoundaries(false)
195     , fTileScale{0.25f, 0.25f}
196     , fPerspectiveMode(kPerspective_Off)
197 {
198     SkGraphics::Init();
199 
200     gPathRendererNames[GpuPathRenderers::kAll] = "All Path Renderers";
201     gPathRendererNames[GpuPathRenderers::kStencilAndCover] = "NV_path_rendering";
202     gPathRendererNames[GpuPathRenderers::kSmall] = "Small paths (cached sdf or alpha masks)";
203     gPathRendererNames[GpuPathRenderers::kCoverageCounting] = "Coverage counting";
204     gPathRendererNames[GpuPathRenderers::kTessellating] = "Tessellating";
205     gPathRendererNames[GpuPathRenderers::kNone] = "Software masks";
206 
207     SkDebugf("Command line arguments: ");
208     for (int i = 1; i < argc; ++i) {
209         SkDebugf("%s ", argv[i]);
210     }
211     SkDebugf("\n");
212 
213     SkCommandLineFlags::Parse(argc, argv);
214 #ifdef SK_BUILD_FOR_ANDROID
215     SetResourcePath("/data/local/tmp/resources");
216 #endif
217 
218     if (!FLAGS_nativeFonts) {
219         gSkFontMgr_DefaultFactory = &sk_tool_utils::MakePortableFontMgr;
220     }
221 
222     initializeEventTracingForTools();
223     static SkTaskGroup::Enabler kTaskGroupEnabler(FLAGS_threads);
224 
225     fBackendType = get_backend_type(FLAGS_backend[0]);
226     fWindow = Window::CreateNativeWindow(platformData);
227 
228     DisplayParams displayParams;
229     displayParams.fMSAASampleCount = FLAGS_msaa;
230     SetCtxOptionsFromCommonFlags(&displayParams.fGrContextOptions);
231     fWindow->setRequestedDisplayParams(displayParams);
232 
233     // Configure timers
234     fStatsLayer.setActive(false);
235     fAnimateTimer = fStatsLayer.addTimer("Animate", SK_ColorMAGENTA, 0xffff66ff);
236     fPaintTimer = fStatsLayer.addTimer("Paint", SK_ColorGREEN);
237     fFlushTimer = fStatsLayer.addTimer("Flush", SK_ColorRED, 0xffff6666);
238 
239     // register callbacks
240     fCommands.attach(fWindow);
241     fWindow->pushLayer(this);
242     fWindow->pushLayer(&fStatsLayer);
243     fWindow->pushLayer(&fImGuiLayer);
244 
245     // add key-bindings
__anon567afbe30102() 246     fCommands.addCommand(' ', "GUI", "Toggle Debug GUI", [this]() {
247         this->fShowImGuiDebugWindow = !this->fShowImGuiDebugWindow;
248         fWindow->inval();
249     });
250     // Command to jump directly to the slide picker and give it focus
__anon567afbe30202() 251     fCommands.addCommand('/', "GUI", "Jump to slide picker", [this]() {
252         this->fShowImGuiDebugWindow = true;
253         this->fShowSlidePicker = true;
254         fWindow->inval();
255     });
256     // Alias that to Backspace, to match SampleApp
__anon567afbe30302() 257     fCommands.addCommand(Window::Key::kBack, "Backspace", "GUI", "Jump to slide picker", [this]() {
258         this->fShowImGuiDebugWindow = true;
259         this->fShowSlidePicker = true;
260         fWindow->inval();
261     });
__anon567afbe30402() 262     fCommands.addCommand('g', "GUI", "Toggle GUI Demo", [this]() {
263         this->fShowImGuiTestWindow = !this->fShowImGuiTestWindow;
264         fWindow->inval();
265     });
__anon567afbe30502() 266     fCommands.addCommand('z', "GUI", "Toggle zoom window", [this]() {
267         this->fShowZoomWindow = !this->fShowZoomWindow;
268         fWindow->inval();
269     });
__anon567afbe30602() 270     fCommands.addCommand('Z', "GUI", "Toggle zoom window state", [this]() {
271         this->fZoomWindowFixed = !this->fZoomWindowFixed;
272         fWindow->inval();
273     });
__anon567afbe30702() 274     fCommands.addCommand('s', "Overlays", "Toggle stats display", [this]() {
275         fStatsLayer.setActive(!fStatsLayer.getActive());
276         fWindow->inval();
277     });
__anon567afbe30802() 278     fCommands.addCommand('0', "Overlays", "Reset stats", [this]() {
279         fStatsLayer.resetMeasurements();
280         this->updateTitle();
281         fWindow->inval();
282     });
__anon567afbe30902() 283     fCommands.addCommand('c', "Modes", "Cycle color mode", [this]() {
284         switch (fColorMode) {
285             case ColorMode::kLegacy:
286                 this->setColorMode(ColorMode::kColorManaged8888);
287                 break;
288             case ColorMode::kColorManaged8888:
289                 this->setColorMode(ColorMode::kColorManagedF16);
290                 break;
291             case ColorMode::kColorManagedF16:
292                 this->setColorMode(ColorMode::kLegacy);
293                 break;
294         }
295     });
__anon567afbe30a02() 296     fCommands.addCommand(Window::Key::kRight, "Right", "Navigation", "Next slide", [this]() {
297         this->setCurrentSlide(fCurrentSlide < fSlides.count() - 1 ? fCurrentSlide + 1 : 0);
298     });
__anon567afbe30b02() 299     fCommands.addCommand(Window::Key::kLeft, "Left", "Navigation", "Previous slide", [this]() {
300         this->setCurrentSlide(fCurrentSlide > 0 ? fCurrentSlide - 1 : fSlides.count() - 1);
301     });
__anon567afbe30c02() 302     fCommands.addCommand(Window::Key::kUp, "Up", "Transform", "Zoom in", [this]() {
303         this->changeZoomLevel(1.f / 32.f);
304         fWindow->inval();
305     });
__anon567afbe30d02() 306     fCommands.addCommand(Window::Key::kDown, "Down", "Transform", "Zoom out", [this]() {
307         this->changeZoomLevel(-1.f / 32.f);
308         fWindow->inval();
309     });
__anon567afbe30e02() 310     fCommands.addCommand('d', "Modes", "Change rendering backend", [this]() {
311         sk_app::Window::BackendType newBackend = (sk_app::Window::BackendType)(
312                 (fBackendType + 1) % sk_app::Window::kBackendTypeCount);
313         // Switching to and from Vulkan is problematic on Linux so disabled for now
314 #if defined(SK_BUILD_FOR_UNIX) && defined(SK_VULKAN)
315         if (newBackend == sk_app::Window::kVulkan_BackendType) {
316             newBackend = (sk_app::Window::BackendType)((newBackend + 1) %
317                                                        sk_app::Window::kBackendTypeCount);
318         } else if (fBackendType == sk_app::Window::kVulkan_BackendType) {
319             newBackend = sk_app::Window::kVulkan_BackendType;
320         }
321 #endif
322         this->setBackend(newBackend);
323     });
__anon567afbe30f02() 324     fCommands.addCommand('K', "IO", "Save slide to SKP", [this]() {
325         fSaveToSKP = true;
326         fWindow->inval();
327     });
__anon567afbe31002() 328     fCommands.addCommand('G', "Modes", "Geometry", [this]() {
329         DisplayParams params = fWindow->getRequestedDisplayParams();
330         uint32_t flags = params.fSurfaceProps.flags();
331         if (!fPixelGeometryOverrides) {
332             fPixelGeometryOverrides = true;
333             params.fSurfaceProps = SkSurfaceProps(flags, kUnknown_SkPixelGeometry);
334         } else {
335             switch (params.fSurfaceProps.pixelGeometry()) {
336                 case kUnknown_SkPixelGeometry:
337                     params.fSurfaceProps = SkSurfaceProps(flags, kRGB_H_SkPixelGeometry);
338                     break;
339                 case kRGB_H_SkPixelGeometry:
340                     params.fSurfaceProps = SkSurfaceProps(flags, kBGR_H_SkPixelGeometry);
341                     break;
342                 case kBGR_H_SkPixelGeometry:
343                     params.fSurfaceProps = SkSurfaceProps(flags, kRGB_V_SkPixelGeometry);
344                     break;
345                 case kRGB_V_SkPixelGeometry:
346                     params.fSurfaceProps = SkSurfaceProps(flags, kBGR_V_SkPixelGeometry);
347                     break;
348                 case kBGR_V_SkPixelGeometry:
349                     params.fSurfaceProps = SkSurfaceProps(flags, SkSurfaceProps::kLegacyFontHost_InitType);
350                     fPixelGeometryOverrides = false;
351                     break;
352             }
353         }
354         fWindow->setRequestedDisplayParams(params);
355         this->updateTitle();
356         fWindow->inval();
357     });
__anon567afbe31102() 358     fCommands.addCommand('H', "Font", "Hinting mode", [this]() {
359         if (!fFontOverrides.fHinting) {
360             fFontOverrides.fHinting = true;
361             fFont.setHinting(kNo_SkFontHinting);
362         } else {
363             switch (fFont.getHinting()) {
364                 case kNo_SkFontHinting:
365                     fFont.setHinting(kSlight_SkFontHinting);
366                     break;
367                 case kSlight_SkFontHinting:
368                     fFont.setHinting(kNormal_SkFontHinting);
369                     break;
370                 case kNormal_SkFontHinting:
371                     fFont.setHinting(kFull_SkFontHinting);
372                     break;
373                 case kFull_SkFontHinting:
374                     fFont.setHinting(kNo_SkFontHinting);
375                     fFontOverrides.fHinting = false;
376                     break;
377             }
378         }
379         this->updateTitle();
380         fWindow->inval();
381     });
__anon567afbe31202() 382     fCommands.addCommand('A', "Paint", "Antialias Mode", [this]() {
383         if (!fPaintOverrides.fAntiAlias) {
384             fPaintOverrides.fAntiAliasState = SkPaintFields::AntiAliasState::Alias;
385             fPaintOverrides.fAntiAlias = true;
386             fPaint.setAntiAlias(false);
387             gSkUseAnalyticAA = gSkForceAnalyticAA = false;
388             gSkUseDeltaAA = gSkForceDeltaAA = false;
389         } else {
390             fPaint.setAntiAlias(true);
391             switch (fPaintOverrides.fAntiAliasState) {
392                 case SkPaintFields::AntiAliasState::Alias:
393                     fPaintOverrides.fAntiAliasState = SkPaintFields::AntiAliasState::Normal;
394                     gSkUseAnalyticAA = gSkForceAnalyticAA = false;
395                     gSkUseDeltaAA = gSkForceDeltaAA = false;
396                     break;
397                 case SkPaintFields::AntiAliasState::Normal:
398                     fPaintOverrides.fAntiAliasState = SkPaintFields::AntiAliasState::AnalyticAAEnabled;
399                     gSkUseAnalyticAA = true;
400                     gSkForceAnalyticAA = false;
401                     gSkUseDeltaAA = gSkForceDeltaAA = false;
402                     break;
403                 case SkPaintFields::AntiAliasState::AnalyticAAEnabled:
404                     fPaintOverrides.fAntiAliasState = SkPaintFields::AntiAliasState::AnalyticAAForced;
405                     gSkUseAnalyticAA = gSkForceAnalyticAA = true;
406                     gSkUseDeltaAA = gSkForceDeltaAA = false;
407                     break;
408                 case SkPaintFields::AntiAliasState::AnalyticAAForced:
409                     fPaintOverrides.fAntiAliasState = SkPaintFields::AntiAliasState::DeltaAAEnabled;
410                     gSkUseAnalyticAA = gSkForceAnalyticAA = false;
411                     gSkUseDeltaAA = true;
412                     gSkForceDeltaAA = false;
413                     break;
414                 case SkPaintFields::AntiAliasState::DeltaAAEnabled:
415                     fPaintOverrides.fAntiAliasState = SkPaintFields::AntiAliasState::DeltaAAForced;
416                     gSkUseAnalyticAA = gSkForceAnalyticAA = false;
417                     gSkUseDeltaAA = gSkForceDeltaAA = true;
418                     break;
419                 case SkPaintFields::AntiAliasState::DeltaAAForced:
420                     fPaintOverrides.fAntiAliasState = SkPaintFields::AntiAliasState::Alias;
421                     fPaintOverrides.fAntiAlias = false;
422                     gSkUseAnalyticAA = fPaintOverrides.fOriginalSkUseAnalyticAA;
423                     gSkForceAnalyticAA = fPaintOverrides.fOriginalSkForceAnalyticAA;
424                     gSkUseDeltaAA = fPaintOverrides.fOriginalSkUseDeltaAA;
425                     gSkForceDeltaAA = fPaintOverrides.fOriginalSkForceDeltaAA;
426                     break;
427             }
428         }
429         this->updateTitle();
430         fWindow->inval();
431     });
__anon567afbe31302() 432     fCommands.addCommand('D', "Modes", "DFT", [this]() {
433         DisplayParams params = fWindow->getRequestedDisplayParams();
434         uint32_t flags = params.fSurfaceProps.flags();
435         flags ^= SkSurfaceProps::kUseDeviceIndependentFonts_Flag;
436         params.fSurfaceProps = SkSurfaceProps(flags, params.fSurfaceProps.pixelGeometry());
437         fWindow->setRequestedDisplayParams(params);
438         this->updateTitle();
439         fWindow->inval();
440     });
__anon567afbe31402() 441     fCommands.addCommand('L', "Font", "Subpixel Antialias Mode", [this]() {
442         if (!fFontOverrides.fEdging) {
443             fFontOverrides.fEdging = true;
444             fFont.setEdging(SkFont::Edging::kAlias);
445         } else {
446             switch (fFont.getEdging()) {
447                 case SkFont::Edging::kAlias:
448                     fFont.setEdging(SkFont::Edging::kAntiAlias);
449                     break;
450                 case SkFont::Edging::kAntiAlias:
451                     fFont.setEdging(SkFont::Edging::kSubpixelAntiAlias);
452                     break;
453                 case SkFont::Edging::kSubpixelAntiAlias:
454                     fFont.setEdging(SkFont::Edging::kAlias);
455                     fFontOverrides.fEdging = false;
456                     break;
457             }
458         }
459         this->updateTitle();
460         fWindow->inval();
461     });
__anon567afbe31502() 462     fCommands.addCommand('S', "Font", "Subpixel Position Mode", [this]() {
463         if (!fFontOverrides.fSubpixel) {
464             fFontOverrides.fSubpixel = true;
465             fFont.setSubpixel(false);
466         } else {
467             if (!fFont.isSubpixel()) {
468                 fFont.setSubpixel(true);
469             } else {
470                 fFontOverrides.fSubpixel = false;
471             }
472         }
473         this->updateTitle();
474         fWindow->inval();
475     });
__anon567afbe31602() 476     fCommands.addCommand('p', "Transform", "Toggle Perspective Mode", [this]() {
477         fPerspectiveMode = (kPerspective_Real == fPerspectiveMode) ? kPerspective_Fake
478                                                                    : kPerspective_Real;
479         this->updateTitle();
480         fWindow->inval();
481     });
__anon567afbe31702() 482     fCommands.addCommand('P', "Transform", "Toggle Perspective", [this]() {
483         fPerspectiveMode = (kPerspective_Off == fPerspectiveMode) ? kPerspective_Real
484                                                                   : kPerspective_Off;
485         this->updateTitle();
486         fWindow->inval();
487     });
__anon567afbe31802() 488     fCommands.addCommand('a', "Transform", "Toggle Animation", [this]() {
489         fAnimTimer.togglePauseResume();
490     });
__anon567afbe31902() 491     fCommands.addCommand('u', "GUI", "Zoom UI", [this]() {
492         fZoomUI = !fZoomUI;
493         fStatsLayer.setDisplayScale(fZoomUI ? 2.0f : 1.0f);
494         fWindow->inval();
495     });
496 
497     // set up slides
498     this->initSlides();
499     if (FLAGS_list) {
500         this->listNames();
501     }
502 
503     fPerspectivePoints[0].set(0, 0);
504     fPerspectivePoints[1].set(1, 0);
505     fPerspectivePoints[2].set(0, 1);
506     fPerspectivePoints[3].set(1, 1);
507     fAnimTimer.run();
508 
509     auto gamutImage = GetResourceAsImage("images/gamut.png");
510     if (gamutImage) {
511         fImGuiGamutPaint.setShader(gamutImage->makeShader());
512     }
513     fImGuiGamutPaint.setColor(SK_ColorWHITE);
514     fImGuiGamutPaint.setFilterQuality(kLow_SkFilterQuality);
515 
516     fWindow->attach(backend_type_for_window(fBackendType));
517     this->setCurrentSlide(this->startupSlide());
518 }
519 
initSlides()520 void Viewer::initSlides() {
521     using SlideFactory = sk_sp<Slide>(*)(const SkString& name, const SkString& path);
522     static const struct {
523         const char*                            fExtension;
524         const char*                            fDirName;
525         const SkCommandLineFlags::StringArray& fFlags;
526         const SlideFactory                     fFactory;
527     } gExternalSlidesInfo[] = {
528         { ".skp", "skp-dir", FLAGS_skps,
529             [](const SkString& name, const SkString& path) -> sk_sp<Slide> {
530                 return sk_make_sp<SKPSlide>(name, path);}
531         },
532         { ".jpg", "jpg-dir", FLAGS_jpgs,
533             [](const SkString& name, const SkString& path) -> sk_sp<Slide> {
534                 return sk_make_sp<ImageSlide>(name, path);}
535         },
536 #if defined(SK_ENABLE_SKOTTIE)
537         { ".json", "skottie-dir", FLAGS_lotties,
538             [](const SkString& name, const SkString& path) -> sk_sp<Slide> {
539                 return sk_make_sp<SkottieSlide>(name, path);}
540         },
541 #endif
542 #if defined(SK_XML)
543         { ".svg", "svg-dir", FLAGS_svgs,
544             [](const SkString& name, const SkString& path) -> sk_sp<Slide> {
545                 return sk_make_sp<SvgSlide>(name, path);}
546         },
547 #endif
548 #if !(defined(SK_BUILD_FOR_WIN) && defined(__clang__))
549         { ".nima", "nima-dir", FLAGS_nimas,
550             [](const SkString& name, const SkString& path) -> sk_sp<Slide> {
551                 return sk_make_sp<NIMASlide>(name, path);}
552         },
553 #endif
554     };
555 
556     SkTArray<sk_sp<Slide>> dirSlides;
557 
558     const auto addSlide = [&](const SkString& name,
559                               const SkString& path,
560                               const SlideFactory& fact) {
561         if (SkCommandLineFlags::ShouldSkip(FLAGS_match,  name.c_str())) {
562             return;
563         }
564 
565         if (auto slide = fact(name, path)) {
566             dirSlides.push_back(slide);
567             fSlides.push_back(std::move(slide));
568         }
569     };
570 
571     if (!FLAGS_file.isEmpty()) {
572         // single file mode
573         const SkString file(FLAGS_file[0]);
574 
575         if (sk_exists(file.c_str(), kRead_SkFILE_Flag)) {
576             for (const auto& sinfo : gExternalSlidesInfo) {
577                 if (file.endsWith(sinfo.fExtension)) {
578                     addSlide(SkOSPath::Basename(file.c_str()), file, sinfo.fFactory);
579                     return;
580                 }
581             }
582 
583             fprintf(stderr, "Unsupported file type \"%s\"\n", file.c_str());
584         } else {
585             fprintf(stderr, "Cannot read \"%s\"\n", file.c_str());
586         }
587 
588         return;
589     }
590 
591     // Bisect slide.
592     if (!FLAGS_bisect.isEmpty()) {
593         sk_sp<BisectSlide> bisect = BisectSlide::Create(FLAGS_bisect[0]);
594         if (bisect && !SkCommandLineFlags::ShouldSkip(FLAGS_match, bisect->getName().c_str())) {
595             if (FLAGS_bisect.count() >= 2) {
596                 for (const char* ch = FLAGS_bisect[1]; *ch; ++ch) {
597                     bisect->onChar(*ch);
598                 }
599             }
600             fSlides.push_back(std::move(bisect));
601         }
602     }
603 
604     // GMs
605     int firstGM = fSlides.count();
606     for (skiagm::GMFactory gmFactory : skiagm::GMRegistry::Range()) {
607         std::unique_ptr<skiagm::GM> gm(gmFactory(nullptr));
608         if (!SkCommandLineFlags::ShouldSkip(FLAGS_match, gm->getName())) {
609             sk_sp<Slide> slide(new GMSlide(gm.release()));
610             fSlides.push_back(std::move(slide));
611         }
612     }
613     // reverse gms
614     int numGMs = fSlides.count() - firstGM;
615     for (int i = 0; i < numGMs/2; ++i) {
616         std::swap(fSlides[firstGM + i], fSlides[fSlides.count() - i - 1]);
617     }
618 
619     // samples
620     for (const SampleFactory factory : SampleRegistry::Range()) {
621         sk_sp<Slide> slide(new SampleSlide(factory));
622         if (!SkCommandLineFlags::ShouldSkip(FLAGS_match, slide->getName().c_str())) {
623             fSlides.push_back(slide);
624         }
625     }
626 
627     for (const auto& info : gExternalSlidesInfo) {
628         for (const auto& flag : info.fFlags) {
629             if (SkStrEndsWith(flag.c_str(), info.fExtension)) {
630                 // single file
631                 addSlide(SkOSPath::Basename(flag.c_str()), flag, info.fFactory);
632             } else {
633                 // directory
634                 SkOSFile::Iter it(flag.c_str(), info.fExtension);
635                 SkString name;
636                 while (it.next(&name)) {
637                     addSlide(name, SkOSPath::Join(flag.c_str(), name.c_str()), info.fFactory);
638                 }
639             }
640             if (!dirSlides.empty()) {
641                 fSlides.push_back(
642                     sk_make_sp<SlideDir>(SkStringPrintf("%s[%s]", info.fDirName, flag.c_str()),
643                                          std::move(dirSlides)));
644                 dirSlides.reset();  // NOLINT(bugprone-use-after-move)
645             }
646         }
647     }
648 }
649 
650 
~Viewer()651 Viewer::~Viewer() {
652     fWindow->detach();
653     delete fWindow;
654 }
655 
656 struct SkPaintTitleUpdater {
SkPaintTitleUpdaterSkPaintTitleUpdater657     SkPaintTitleUpdater(SkString* title) : fTitle(title), fCount(0) {}
appendSkPaintTitleUpdater658     void append(const char* s) {
659         if (fCount == 0) {
660             fTitle->append(" {");
661         } else {
662             fTitle->append(", ");
663         }
664         fTitle->append(s);
665         ++fCount;
666     }
doneSkPaintTitleUpdater667     void done() {
668         if (fCount > 0) {
669             fTitle->append("}");
670         }
671     }
672     SkString* fTitle;
673     int fCount;
674 };
675 
updateTitle()676 void Viewer::updateTitle() {
677     if (!fWindow) {
678         return;
679     }
680     if (fWindow->sampleCount() < 1) {
681         return; // Surface hasn't been created yet.
682     }
683 
684     SkString title("Viewer: ");
685     title.append(fSlides[fCurrentSlide]->getName());
686 
687     if (gSkUseDeltaAA) {
688         if (gSkForceDeltaAA) {
689             title.append(" <FDAA>");
690         } else {
691             title.append(" <DAA>");
692         }
693     } else if (gSkUseAnalyticAA) {
694         if (gSkForceAnalyticAA) {
695             title.append(" <FAAA>");
696         } else {
697             title.append(" <AAA>");
698         }
699     }
700 
701     SkPaintTitleUpdater paintTitle(&title);
702     auto paintFlag = [this, &paintTitle](bool SkPaintFields::* flag,
703                                          bool (SkPaint::* isFlag)() const,
704                                          const char* on, const char* off)
705     {
706         if (fPaintOverrides.*flag) {
707             paintTitle.append((fPaint.*isFlag)() ? on : off);
708         }
709     };
710 
711     auto fontFlag = [this, &paintTitle](bool SkFontFields::* flag, bool (SkFont::* isFlag)() const,
712                                         const char* on, const char* off)
713     {
714         if (fFontOverrides.*flag) {
715             paintTitle.append((fFont.*isFlag)() ? on : off);
716         }
717     };
718 
719     paintFlag(&SkPaintFields::fAntiAlias, &SkPaint::isAntiAlias, "Antialias", "Alias");
720     paintFlag(&SkPaintFields::fDither, &SkPaint::isDither, "DITHER", "No Dither");
721 
722     fontFlag(&SkFontFields::fForceAutoHinting, &SkFont::isForceAutoHinting,
723              "Force Autohint", "No Force Autohint");
724     fontFlag(&SkFontFields::fEmbolden, &SkFont::isEmbolden, "Fake Bold", "No Fake Bold");
725     fontFlag(&SkFontFields::fLinearMetrics, &SkFont::isLinearMetrics,
726              "Linear Metrics", "Non-Linear Metrics");
727     fontFlag(&SkFontFields::fEmbeddedBitmaps, &SkFont::isEmbeddedBitmaps,
728              "Bitmap Text", "No Bitmap Text");
729     fontFlag(&SkFontFields::fSubpixel, &SkFont::isSubpixel, "Subpixel Text", "Pixel Text");
730 
731     if (fFontOverrides.fEdging) {
732         switch (fFont.getEdging()) {
733             case SkFont::Edging::kAlias:
734                 paintTitle.append("Alias Text");
735                 break;
736             case SkFont::Edging::kAntiAlias:
737                 paintTitle.append("Antialias Text");
738                 break;
739             case SkFont::Edging::kSubpixelAntiAlias:
740                 paintTitle.append("Subpixel Antialias Text");
741                 break;
742         }
743     }
744 
745     if (fFontOverrides.fHinting) {
746         switch (fFont.getHinting()) {
747             case kNo_SkFontHinting:
748                 paintTitle.append("No Hinting");
749                 break;
750             case kSlight_SkFontHinting:
751                 paintTitle.append("Slight Hinting");
752                 break;
753             case kNormal_SkFontHinting:
754                 paintTitle.append("Normal Hinting");
755                 break;
756             case kFull_SkFontHinting:
757                 paintTitle.append("Full Hinting");
758                 break;
759         }
760     }
761     paintTitle.done();
762 
763     switch (fColorMode) {
764         case ColorMode::kLegacy:
765             title.append(" Legacy 8888");
766             break;
767         case ColorMode::kColorManaged8888:
768             title.append(" ColorManaged 8888");
769             break;
770         case ColorMode::kColorManagedF16:
771             title.append(" ColorManaged F16");
772             break;
773     }
774 
775     if (ColorMode::kLegacy != fColorMode) {
776         int curPrimaries = -1;
777         for (size_t i = 0; i < SK_ARRAY_COUNT(gNamedPrimaries); ++i) {
778             if (primaries_equal(*gNamedPrimaries[i].fPrimaries, fColorSpacePrimaries)) {
779                 curPrimaries = i;
780                 break;
781             }
782         }
783         title.appendf(" %s Gamma %f",
784                       curPrimaries >= 0 ? gNamedPrimaries[curPrimaries].fName : "Custom",
785                       fColorSpaceTransferFn.g);
786     }
787 
788     const DisplayParams& params = fWindow->getRequestedDisplayParams();
789     if (fPixelGeometryOverrides) {
790         switch (params.fSurfaceProps.pixelGeometry()) {
791             case kUnknown_SkPixelGeometry:
792                 title.append( " Flat");
793                 break;
794             case kRGB_H_SkPixelGeometry:
795                 title.append( " RGB");
796                 break;
797             case kBGR_H_SkPixelGeometry:
798                 title.append( " BGR");
799                 break;
800             case kRGB_V_SkPixelGeometry:
801                 title.append( " RGBV");
802                 break;
803             case kBGR_V_SkPixelGeometry:
804                 title.append( " BGRV");
805                 break;
806         }
807     }
808 
809     if (params.fSurfaceProps.isUseDeviceIndependentFonts()) {
810         title.append(" DFT");
811     }
812 
813     title.append(" [");
814     title.append(kBackendTypeStrings[fBackendType]);
815     int msaa = fWindow->sampleCount();
816     if (msaa > 1) {
817         title.appendf(" MSAA: %i", msaa);
818     }
819     title.append("]");
820 
821     GpuPathRenderers pr = fWindow->getRequestedDisplayParams().fGrContextOptions.fGpuPathRenderers;
822     if (GpuPathRenderers::kAll != pr) {
823         title.appendf(" [Path renderer: %s]", gPathRendererNames[pr].c_str());
824     }
825 
826     if (kPerspective_Real == fPerspectiveMode) {
827         title.append(" Perpsective (Real)");
828     } else if (kPerspective_Fake == fPerspectiveMode) {
829         title.append(" Perspective (Fake)");
830     }
831 
832     fWindow->setTitle(title.c_str());
833 }
834 
startupSlide() const835 int Viewer::startupSlide() const {
836 
837     if (!FLAGS_slide.isEmpty()) {
838         int count = fSlides.count();
839         for (int i = 0; i < count; i++) {
840             if (fSlides[i]->getName().equals(FLAGS_slide[0])) {
841                 return i;
842             }
843         }
844 
845         fprintf(stderr, "Unknown slide \"%s\"\n", FLAGS_slide[0]);
846         this->listNames();
847     }
848 
849     return 0;
850 }
851 
listNames() const852 void Viewer::listNames() const {
853     SkDebugf("All Slides:\n");
854     for (const auto& slide : fSlides) {
855         SkDebugf("    %s\n", slide->getName().c_str());
856     }
857 }
858 
setCurrentSlide(int slide)859 void Viewer::setCurrentSlide(int slide) {
860     SkASSERT(slide >= 0 && slide < fSlides.count());
861 
862     if (slide == fCurrentSlide) {
863         return;
864     }
865 
866     if (fCurrentSlide >= 0) {
867         fSlides[fCurrentSlide]->unload();
868     }
869 
870     fSlides[slide]->load(SkIntToScalar(fWindow->width()),
871                          SkIntToScalar(fWindow->height()));
872     fCurrentSlide = slide;
873     this->setupCurrentSlide();
874 }
875 
setupCurrentSlide()876 void Viewer::setupCurrentSlide() {
877     if (fCurrentSlide >= 0) {
878         // prepare dimensions for image slides
879         fGesture.resetTouchState();
880         fDefaultMatrix.reset();
881 
882         const SkISize slideSize = fSlides[fCurrentSlide]->getDimensions();
883         const SkRect slideBounds = SkRect::MakeIWH(slideSize.width(), slideSize.height());
884         const SkRect windowRect = SkRect::MakeIWH(fWindow->width(), fWindow->height());
885 
886         // Start with a matrix that scales the slide to the available screen space
887         if (fWindow->scaleContentToFit()) {
888             if (windowRect.width() > 0 && windowRect.height() > 0) {
889                 fDefaultMatrix.setRectToRect(slideBounds, windowRect, SkMatrix::kStart_ScaleToFit);
890             }
891         }
892 
893         // Prevent the user from dragging content so far outside the window they can't find it again
894         fGesture.setTransLimit(slideBounds, windowRect, this->computePreTouchMatrix());
895 
896         this->updateTitle();
897         this->updateUIState();
898 
899         fStatsLayer.resetMeasurements();
900 
901         fWindow->inval();
902     }
903 }
904 
905 #define MAX_ZOOM_LEVEL  8
906 #define MIN_ZOOM_LEVEL  -8
907 
changeZoomLevel(float delta)908 void Viewer::changeZoomLevel(float delta) {
909     fZoomLevel += delta;
910     fZoomLevel = SkScalarPin(fZoomLevel, MIN_ZOOM_LEVEL, MAX_ZOOM_LEVEL);
911     this->preTouchMatrixChanged();
912 }
913 
preTouchMatrixChanged()914 void Viewer::preTouchMatrixChanged() {
915     // Update the trans limit as the transform changes.
916     const SkISize slideSize = fSlides[fCurrentSlide]->getDimensions();
917     const SkRect slideBounds = SkRect::MakeIWH(slideSize.width(), slideSize.height());
918     const SkRect windowRect = SkRect::MakeIWH(fWindow->width(), fWindow->height());
919     fGesture.setTransLimit(slideBounds, windowRect, this->computePreTouchMatrix());
920 }
921 
computePerspectiveMatrix()922 SkMatrix Viewer::computePerspectiveMatrix() {
923     SkScalar w = fWindow->width(), h = fWindow->height();
924     SkPoint orthoPts[4] = { { 0, 0 }, { w, 0 }, { 0, h }, { w, h } };
925     SkPoint perspPts[4] = {
926         { fPerspectivePoints[0].fX * w, fPerspectivePoints[0].fY * h },
927         { fPerspectivePoints[1].fX * w, fPerspectivePoints[1].fY * h },
928         { fPerspectivePoints[2].fX * w, fPerspectivePoints[2].fY * h },
929         { fPerspectivePoints[3].fX * w, fPerspectivePoints[3].fY * h }
930     };
931     SkMatrix m;
932     m.setPolyToPoly(orthoPts, perspPts, 4);
933     return m;
934 }
935 
computePreTouchMatrix()936 SkMatrix Viewer::computePreTouchMatrix() {
937     SkMatrix m = fDefaultMatrix;
938     SkScalar zoomScale = (fZoomLevel < 0) ? SK_Scalar1 / (SK_Scalar1 - fZoomLevel)
939                                           : SK_Scalar1 + fZoomLevel;
940     m.preTranslate((fOffset.x() - 0.5f) * 2.0f, (fOffset.y() - 0.5f) * 2.0f);
941     m.preScale(zoomScale, zoomScale);
942 
943     const SkISize slideSize = fSlides[fCurrentSlide]->getDimensions();
944     m.preRotate(fRotation, slideSize.width() * 0.5f, slideSize.height() * 0.5f);
945 
946     if (kPerspective_Real == fPerspectiveMode) {
947         SkMatrix persp = this->computePerspectiveMatrix();
948         m.postConcat(persp);
949     }
950 
951     return m;
952 }
953 
computeMatrix()954 SkMatrix Viewer::computeMatrix() {
955     SkMatrix m = fGesture.localM();
956     m.preConcat(fGesture.globalM());
957     m.preConcat(this->computePreTouchMatrix());
958     return m;
959 }
960 
setBackend(sk_app::Window::BackendType backendType)961 void Viewer::setBackend(sk_app::Window::BackendType backendType) {
962     fBackendType = backendType;
963 
964     fWindow->detach();
965 
966 #if defined(SK_BUILD_FOR_WIN)
967     // Switching between OpenGL, Vulkan, and ANGLE in the same window is problematic at this point
968     // on Windows, so we just delete the window and recreate it.
969     DisplayParams params = fWindow->getRequestedDisplayParams();
970     delete fWindow;
971     fWindow = Window::CreateNativeWindow(nullptr);
972 
973     // re-register callbacks
974     fCommands.attach(fWindow);
975     fWindow->pushLayer(this);
976     fWindow->pushLayer(&fStatsLayer);
977     fWindow->pushLayer(&fImGuiLayer);
978 
979     // Don't allow the window to re-attach. If we're in MSAA mode, the params we grabbed above
980     // will still include our correct sample count. But the re-created fWindow will lose that
981     // information. On Windows, we need to re-create the window when changing sample count,
982     // so we'll incorrectly detect that situation, then re-initialize the window in GL mode,
983     // rendering this tear-down step pointless (and causing the Vulkan window context to fail
984     // as if we had never changed windows at all).
985     fWindow->setRequestedDisplayParams(params, false);
986 #endif
987 
988     fWindow->attach(backend_type_for_window(fBackendType));
989 }
990 
setColorMode(ColorMode colorMode)991 void Viewer::setColorMode(ColorMode colorMode) {
992     fColorMode = colorMode;
993     this->updateTitle();
994     fWindow->inval();
995 }
996 
997 class OveridePaintFilterCanvas : public SkPaintFilterCanvas {
998 public:
OveridePaintFilterCanvas(SkCanvas * canvas,SkPaint * paint,Viewer::SkPaintFields * pfields,SkFont * font,Viewer::SkFontFields * ffields)999     OveridePaintFilterCanvas(SkCanvas* canvas, SkPaint* paint, Viewer::SkPaintFields* pfields,
1000             SkFont* font, Viewer::SkFontFields* ffields)
1001         : SkPaintFilterCanvas(canvas), fPaint(paint), fPaintOverrides(pfields), fFont(font), fFontOverrides(ffields)
1002     { }
filterTextBlob(const SkPaint & paint,const SkTextBlob * blob,sk_sp<SkTextBlob> * cache)1003     const SkTextBlob* filterTextBlob(const SkPaint& paint, const SkTextBlob* blob,
1004                                      sk_sp<SkTextBlob>* cache) {
1005         bool blobWillChange = false;
1006         for (SkTextBlobRunIterator it(blob); !it.done(); it.next()) {
1007             SkTCopyOnFirstWrite<SkFont> filteredFont(it.font());
1008             bool shouldDraw = this->filterFont(&filteredFont);
1009             if (it.font() != *filteredFont || !shouldDraw) {
1010                 blobWillChange = true;
1011                 break;
1012             }
1013         }
1014         if (!blobWillChange) {
1015             return blob;
1016         }
1017 
1018         SkTextBlobBuilder builder;
1019         for (SkTextBlobRunIterator it(blob); !it.done(); it.next()) {
1020             SkTCopyOnFirstWrite<SkFont> filteredFont(it.font());
1021             bool shouldDraw = this->filterFont(&filteredFont);
1022             if (!shouldDraw) {
1023                 continue;
1024             }
1025 
1026             SkFont font = *filteredFont;
1027 
1028             const SkTextBlobBuilder::RunBuffer& runBuffer
1029                 = it.positioning() == SkTextBlobRunIterator::kDefault_Positioning
1030                     ? SkTextBlobBuilderPriv::AllocRunText(&builder, font,
1031                         it.offset().x(),it.offset().y(), it.glyphCount(), it.textSize(), SkString())
1032                 : it.positioning() == SkTextBlobRunIterator::kHorizontal_Positioning
1033                     ? SkTextBlobBuilderPriv::AllocRunTextPosH(&builder, font,
1034                         it.offset().y(), it.glyphCount(), it.textSize(), SkString())
1035                 : it.positioning() == SkTextBlobRunIterator::kFull_Positioning
1036                     ? SkTextBlobBuilderPriv::AllocRunTextPos(&builder, font,
1037                         it.glyphCount(), it.textSize(), SkString())
1038                 : (SkASSERT_RELEASE(false), SkTextBlobBuilder::RunBuffer());
1039             uint32_t glyphCount = it.glyphCount();
1040             if (it.glyphs()) {
1041                 size_t glyphSize = sizeof(decltype(*it.glyphs()));
1042                 memcpy(runBuffer.glyphs, it.glyphs(), glyphCount * glyphSize);
1043             }
1044             if (it.pos()) {
1045                 size_t posSize = sizeof(decltype(*it.pos()));
1046                 uint8_t positioning = it.positioning();
1047                 memcpy(runBuffer.pos, it.pos(), glyphCount * positioning * posSize);
1048             }
1049             if (it.text()) {
1050                 size_t textSize = sizeof(decltype(*it.text()));
1051                 uint32_t textCount = it.textSize();
1052                 memcpy(runBuffer.utf8text, it.text(), textCount * textSize);
1053             }
1054             if (it.clusters()) {
1055                 size_t clusterSize = sizeof(decltype(*it.clusters()));
1056                 memcpy(runBuffer.clusters, it.clusters(), glyphCount * clusterSize);
1057             }
1058         }
1059         *cache = builder.make();
1060         return cache->get();
1061     }
onDrawTextBlob(const SkTextBlob * blob,SkScalar x,SkScalar y,const SkPaint & paint)1062     void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
1063                         const SkPaint& paint) override {
1064         sk_sp<SkTextBlob> cache;
1065         this->SkPaintFilterCanvas::onDrawTextBlob(
1066             this->filterTextBlob(paint, blob, &cache), x, y, paint);
1067     }
filterFont(SkTCopyOnFirstWrite<SkFont> * font) const1068     bool filterFont(SkTCopyOnFirstWrite<SkFont>* font) const {
1069         if (fFontOverrides->fTextSize) {
1070             font->writable()->setSize(fFont->getSize());
1071         }
1072         if (fFontOverrides->fHinting) {
1073             font->writable()->setHinting(fFont->getHinting());
1074         }
1075         if (fFontOverrides->fEdging) {
1076             font->writable()->setEdging(fFont->getEdging());
1077         }
1078         if (fFontOverrides->fEmbolden) {
1079             font->writable()->setEmbolden(fFont->isEmbolden());
1080         }
1081         if (fFontOverrides->fLinearMetrics) {
1082             font->writable()->setLinearMetrics(fFont->isLinearMetrics());
1083         }
1084         if (fFontOverrides->fSubpixel) {
1085             font->writable()->setSubpixel(fFont->isSubpixel());
1086         }
1087         if (fFontOverrides->fEmbeddedBitmaps) {
1088             font->writable()->setEmbeddedBitmaps(fFont->isEmbeddedBitmaps());
1089         }
1090         if (fFontOverrides->fForceAutoHinting) {
1091             font->writable()->setForceAutoHinting(fFont->isForceAutoHinting());
1092         }
1093 
1094         return true;
1095     }
onFilter(SkTCopyOnFirstWrite<SkPaint> * paint,Type) const1096     bool onFilter(SkTCopyOnFirstWrite<SkPaint>* paint, Type) const override {
1097         if (*paint == nullptr) {
1098             return true;
1099         }
1100         if (fPaintOverrides->fAntiAlias) {
1101             paint->writable()->setAntiAlias(fPaint->isAntiAlias());
1102         }
1103         if (fPaintOverrides->fDither) {
1104             paint->writable()->setDither(fPaint->isDither());
1105         }
1106         return true;
1107     }
1108     SkPaint* fPaint;
1109     Viewer::SkPaintFields* fPaintOverrides;
1110     SkFont* fFont;
1111     Viewer::SkFontFields* fFontOverrides;
1112 };
1113 
drawSlide(SkCanvas * canvas)1114 void Viewer::drawSlide(SkCanvas* canvas) {
1115     SkAutoCanvasRestore autorestore(canvas, false);
1116 
1117     // By default, we render directly into the window's surface/canvas
1118     SkCanvas* slideCanvas = canvas;
1119     fLastImage.reset();
1120 
1121     // If we're in any of the color managed modes, construct the color space we're going to use
1122     sk_sp<SkColorSpace> colorSpace = nullptr;
1123     if (ColorMode::kLegacy != fColorMode) {
1124         skcms_Matrix3x3 toXYZ;
1125         SkAssertResult(fColorSpacePrimaries.toXYZD50(&toXYZ));
1126         colorSpace = SkColorSpace::MakeRGB(fColorSpaceTransferFn, toXYZ);
1127     }
1128 
1129     if (fSaveToSKP) {
1130         SkPictureRecorder recorder;
1131         SkCanvas* recorderCanvas = recorder.beginRecording(
1132                 SkRect::Make(fSlides[fCurrentSlide]->getDimensions()));
1133         fSlides[fCurrentSlide]->draw(recorderCanvas);
1134         sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture());
1135         SkFILEWStream stream("sample_app.skp");
1136         picture->serialize(&stream);
1137         fSaveToSKP = false;
1138     }
1139 
1140     // Grab some things we'll need to make surfaces (for tiling or general offscreen rendering)
1141     SkColorType colorType = (ColorMode::kColorManagedF16 == fColorMode) ? kRGBA_F16_SkColorType
1142                                                                         : kN32_SkColorType;
1143     SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType);
1144     canvas->getProps(&props);
1145 
1146     auto make_surface = [=](int w, int h) {
1147         SkImageInfo info = SkImageInfo::Make(w, h, colorType, kPremul_SkAlphaType, colorSpace);
1148         return Window::kRaster_BackendType == this->fBackendType
1149                 ? SkSurface::MakeRaster(info, &props)
1150                 : canvas->makeSurface(info, &props);
1151     };
1152 
1153     // We need to render offscreen if we're...
1154     // ... in fake perspective or zooming (so we have a snapped copy of the results)
1155     // ... in any raster mode, because the window surface is actually GL
1156     // ... in any color managed mode, because we always make the window surface with no color space
1157     sk_sp<SkSurface> offscreenSurface = nullptr;
1158     if (kPerspective_Fake == fPerspectiveMode ||
1159         fShowZoomWindow ||
1160         Window::kRaster_BackendType == fBackendType ||
1161         colorSpace != nullptr) {
1162 
1163         offscreenSurface = make_surface(fWindow->width(), fWindow->height());
1164         slideCanvas = offscreenSurface->getCanvas();
1165     }
1166 
1167     int count = slideCanvas->save();
1168     slideCanvas->clear(SK_ColorWHITE);
1169     // Time the painting logic of the slide
1170     fStatsLayer.beginTiming(fPaintTimer);
1171     if (fTiled) {
1172         int tileW = SkScalarCeilToInt(fWindow->width() * fTileScale.width());
1173         int tileH = SkScalarCeilToInt(fWindow->height() * fTileScale.height());
1174         sk_sp<SkSurface> tileSurface = make_surface(tileW, tileH);
1175         SkCanvas* tileCanvas = tileSurface->getCanvas();
1176         SkMatrix m = this->computeMatrix();
1177         for (int y = 0; y < fWindow->height(); y += tileH) {
1178             for (int x = 0; x < fWindow->width(); x += tileW) {
1179                 SkAutoCanvasRestore acr(tileCanvas, true);
1180                 tileCanvas->translate(-x, -y);
1181                 tileCanvas->clear(SK_ColorTRANSPARENT);
1182                 tileCanvas->concat(m);
1183                 OveridePaintFilterCanvas filterCanvas(tileCanvas, &fPaint, &fPaintOverrides,
1184                                                       &fFont, &fFontOverrides);
1185                 fSlides[fCurrentSlide]->draw(&filterCanvas);
1186                 tileSurface->draw(slideCanvas, x, y, nullptr);
1187             }
1188         }
1189 
1190         // Draw borders between tiles
1191         if (fDrawTileBoundaries) {
1192             SkPaint border;
1193             border.setColor(0x60FF00FF);
1194             border.setStyle(SkPaint::kStroke_Style);
1195             for (int y = 0; y < fWindow->height(); y += tileH) {
1196                 for (int x = 0; x < fWindow->width(); x += tileW) {
1197                     slideCanvas->drawRect(SkRect::MakeXYWH(x, y, tileW, tileH), border);
1198                 }
1199             }
1200         }
1201     } else {
1202         slideCanvas->concat(this->computeMatrix());
1203         if (kPerspective_Real == fPerspectiveMode) {
1204             slideCanvas->clipRect(SkRect::MakeWH(fWindow->width(), fWindow->height()));
1205         }
1206         OveridePaintFilterCanvas filterCanvas(slideCanvas, &fPaint, &fPaintOverrides, &fFont, &fFontOverrides);
1207         fSlides[fCurrentSlide]->draw(&filterCanvas);
1208     }
1209     fStatsLayer.endTiming(fPaintTimer);
1210     slideCanvas->restoreToCount(count);
1211 
1212     // Force a flush so we can time that, too
1213     fStatsLayer.beginTiming(fFlushTimer);
1214     slideCanvas->flush();
1215     fStatsLayer.endTiming(fFlushTimer);
1216 
1217     // If we rendered offscreen, snap an image and push the results to the window's canvas
1218     if (offscreenSurface) {
1219         fLastImage = offscreenSurface->makeImageSnapshot();
1220 
1221         SkPaint paint;
1222         paint.setBlendMode(SkBlendMode::kSrc);
1223         int prePerspectiveCount = canvas->save();
1224         if (kPerspective_Fake == fPerspectiveMode) {
1225             paint.setFilterQuality(kHigh_SkFilterQuality);
1226             canvas->clear(SK_ColorWHITE);
1227             canvas->concat(this->computePerspectiveMatrix());
1228         }
1229         canvas->drawImage(fLastImage, 0, 0, &paint);
1230         canvas->restoreToCount(prePerspectiveCount);
1231     }
1232 }
1233 
onBackendCreated()1234 void Viewer::onBackendCreated() {
1235     this->setupCurrentSlide();
1236     fWindow->show();
1237 }
1238 
onPaint(SkCanvas * canvas)1239 void Viewer::onPaint(SkCanvas* canvas) {
1240     this->drawSlide(canvas);
1241 
1242     fCommands.drawHelp(canvas);
1243 
1244     this->drawImGui();
1245 
1246     if (GrContext* ctx = fWindow->getGrContext()) {
1247         // Clean out cache items that haven't been used in more than 10 seconds.
1248         ctx->performDeferredCleanup(std::chrono::seconds(10));
1249     }
1250 }
1251 
onResize(int width,int height)1252 void Viewer::onResize(int width, int height) {
1253     if (fCurrentSlide >= 0) {
1254         fSlides[fCurrentSlide]->resize(width, height);
1255     }
1256 }
1257 
mapEvent(float x,float y)1258 SkPoint Viewer::mapEvent(float x, float y) {
1259     const auto m = this->computeMatrix();
1260     SkMatrix inv;
1261 
1262     SkAssertResult(m.invert(&inv));
1263 
1264     return inv.mapXY(x, y);
1265 }
1266 
onTouch(intptr_t owner,Window::InputState state,float x,float y)1267 bool Viewer::onTouch(intptr_t owner, Window::InputState state, float x, float y) {
1268     if (GestureDevice::kMouse == fGestureDevice) {
1269         return false;
1270     }
1271 
1272     const auto slidePt = this->mapEvent(x, y);
1273     if (fSlides[fCurrentSlide]->onMouse(slidePt.x(), slidePt.y(), state, 0)) {
1274         fWindow->inval();
1275         return true;
1276     }
1277 
1278     void* castedOwner = reinterpret_cast<void*>(owner);
1279     switch (state) {
1280         case Window::kUp_InputState: {
1281             fGesture.touchEnd(castedOwner);
1282 #if defined(SK_BUILD_FOR_IOS)
1283             // TODO: move IOS swipe detection higher up into the platform code
1284             SkPoint dir;
1285             if (fGesture.isFling(&dir)) {
1286                 // swiping left or right
1287                 if (SkTAbs(dir.fX) > SkTAbs(dir.fY)) {
1288                     if (dir.fX < 0) {
1289                         this->setCurrentSlide(fCurrentSlide < fSlides.count() - 1 ?
1290                                               fCurrentSlide + 1 : 0);
1291                     } else {
1292                         this->setCurrentSlide(fCurrentSlide > 0 ?
1293                                               fCurrentSlide - 1 : fSlides.count() - 1);
1294                     }
1295                 }
1296                 fGesture.reset();
1297             }
1298 #endif
1299             break;
1300         }
1301         case Window::kDown_InputState: {
1302             fGesture.touchBegin(castedOwner, x, y);
1303             break;
1304         }
1305         case Window::kMove_InputState: {
1306             fGesture.touchMoved(castedOwner, x, y);
1307             break;
1308         }
1309     }
1310     fGestureDevice = fGesture.isBeingTouched() ? GestureDevice::kTouch : GestureDevice::kNone;
1311     fWindow->inval();
1312     return true;
1313 }
1314 
onMouse(int x,int y,Window::InputState state,uint32_t modifiers)1315 bool Viewer::onMouse(int x, int y, Window::InputState state, uint32_t modifiers) {
1316     if (GestureDevice::kTouch == fGestureDevice) {
1317         return false;
1318     }
1319 
1320     const auto slidePt = this->mapEvent(x, y);
1321     if (fSlides[fCurrentSlide]->onMouse(slidePt.x(), slidePt.y(), state, modifiers)) {
1322         fWindow->inval();
1323         return true;
1324     }
1325 
1326     switch (state) {
1327         case Window::kUp_InputState: {
1328             fGesture.touchEnd(nullptr);
1329             break;
1330         }
1331         case Window::kDown_InputState: {
1332             fGesture.touchBegin(nullptr, x, y);
1333             break;
1334         }
1335         case Window::kMove_InputState: {
1336             fGesture.touchMoved(nullptr, x, y);
1337             break;
1338         }
1339     }
1340     fGestureDevice = fGesture.isBeingTouched() ? GestureDevice::kMouse : GestureDevice::kNone;
1341 
1342     if (state != Window::kMove_InputState || fGesture.isBeingTouched()) {
1343         fWindow->inval();
1344     }
1345     return true;
1346 }
1347 
ImGui_DragPrimary(const char * label,float * x,float * y,const ImVec2 & pos,const ImVec2 & size)1348 static ImVec2 ImGui_DragPrimary(const char* label, float* x, float* y,
1349                                 const ImVec2& pos, const ImVec2& size) {
1350     // Transform primaries ([0, 0] - [0.8, 0.9]) to screen coords (including Y-flip)
1351     ImVec2 center(pos.x + (*x / 0.8f) * size.x, pos.y + (1.0f - (*y / 0.9f)) * size.y);
1352 
1353     // Invisible 10x10 button
1354     ImGui::SetCursorScreenPos(ImVec2(center.x - 5, center.y - 5));
1355     ImGui::InvisibleButton(label, ImVec2(10, 10));
1356 
1357     if (ImGui::IsItemActive() && ImGui::IsMouseDragging()) {
1358         ImGuiIO& io = ImGui::GetIO();
1359         // Normalized mouse position, relative to our gamut box
1360         ImVec2 mousePosXY((io.MousePos.x - pos.x) / size.x, (io.MousePos.y - pos.y) / size.y);
1361         // Clamp to edge of box, convert back to primary scale
1362         *x = SkTPin(mousePosXY.x, 0.0f, 1.0f) * 0.8f;
1363         *y = SkTPin(1 - mousePosXY.y, 0.0f, 1.0f) * 0.9f;
1364     }
1365 
1366     if (ImGui::IsItemHovered()) {
1367         ImGui::SetTooltip("x: %.3f\ny: %.3f", *x, *y);
1368     }
1369 
1370     // Return screen coordinates for the caller. We could just return center here, but we'd have
1371     // one frame of lag during drag.
1372     return ImVec2(pos.x + (*x / 0.8f) * size.x, pos.y + (1.0f - (*y / 0.9f)) * size.y);
1373 }
1374 
ImGui_Primaries(SkColorSpacePrimaries * primaries,SkPaint * gamutPaint)1375 static void ImGui_Primaries(SkColorSpacePrimaries* primaries, SkPaint* gamutPaint) {
1376     ImDrawList* drawList = ImGui::GetWindowDrawList();
1377 
1378     // The gamut image covers a (0.8 x 0.9) shaped region, so fit our image/canvas to the available
1379     // width, and scale the height to maintain aspect ratio.
1380     float canvasWidth = SkTMax(ImGui::GetContentRegionAvailWidth(), 50.0f);
1381     ImVec2 size = ImVec2(canvasWidth, canvasWidth * (0.9f / 0.8f));
1382     ImVec2 pos = ImGui::GetCursorScreenPos();
1383 
1384     // Background image. Only draw a subset of the image, to avoid the regions less than zero.
1385     // Simplifes re-mapping math, clipping behavior, and increases resolution in the useful area.
1386     // Magic numbers are pixel locations of the origin and upper-right corner.
1387     drawList->AddImage(gamutPaint, pos, ImVec2(pos.x + size.x, pos.y + size.y),
1388                        ImVec2(242, 61), ImVec2(1897, 1922));
1389 
1390     // Primary markers
1391     ImVec2 r = ImGui_DragPrimary("R", &primaries->fRX, &primaries->fRY, pos, size);
1392     ImVec2 g = ImGui_DragPrimary("G", &primaries->fGX, &primaries->fGY, pos, size);
1393     ImVec2 b = ImGui_DragPrimary("B", &primaries->fBX, &primaries->fBY, pos, size);
1394     ImVec2 w = ImGui_DragPrimary("W", &primaries->fWX, &primaries->fWY, pos, size);
1395 
1396     // Gamut triangle
1397     drawList->AddCircle(r, 5.0f, 0xFF000040);
1398     drawList->AddCircle(g, 5.0f, 0xFF004000);
1399     drawList->AddCircle(b, 5.0f, 0xFF400000);
1400     drawList->AddCircle(w, 5.0f, 0xFFFFFFFF);
1401     drawList->AddTriangle(r, g, b, 0xFFFFFFFF);
1402 
1403     // Re-position cursor immediate after the diagram for subsequent controls
1404     ImGui::SetCursorScreenPos(ImVec2(pos.x, pos.y + size.y));
1405 }
1406 
ImGui_DragPoint(const char * label,SkPoint * p,const ImVec2 & pos,const ImVec2 & size,bool * dragging)1407 static ImVec2 ImGui_DragPoint(const char* label, SkPoint* p,
1408                               const ImVec2& pos, const ImVec2& size, bool* dragging) {
1409     // Transform points ([0, 0] - [1.0, 1.0]) to screen coords
1410     ImVec2 center(pos.x + p->fX * size.x, pos.y + p->fY * size.y);
1411 
1412     // Invisible 10x10 button
1413     ImGui::SetCursorScreenPos(ImVec2(center.x - 5, center.y - 5));
1414     ImGui::InvisibleButton(label, ImVec2(10, 10));
1415 
1416     if (ImGui::IsItemActive() && ImGui::IsMouseDragging()) {
1417         ImGuiIO& io = ImGui::GetIO();
1418         // Normalized mouse position, relative to our gamut box
1419         ImVec2 mousePosXY((io.MousePos.x - pos.x) / size.x, (io.MousePos.y - pos.y) / size.y);
1420         // Clamp to edge of box
1421         p->fX = SkTPin(mousePosXY.x, 0.0f, 1.0f);
1422         p->fY = SkTPin(mousePosXY.y, 0.0f, 1.0f);
1423         *dragging = true;
1424     }
1425 
1426     // Return screen coordinates for the caller. We could just return center here, but we'd have
1427     // one frame of lag during drag.
1428     return ImVec2(pos.x + p->fX * size.x, pos.y + p->fY * size.y);
1429 }
1430 
ImGui_DragLocation(SkPoint * pt)1431 static bool ImGui_DragLocation(SkPoint* pt) {
1432     ImDrawList* drawList = ImGui::GetWindowDrawList();
1433 
1434     // Fit our image/canvas to the available width, and scale the height to maintain aspect ratio.
1435     float canvasWidth = SkTMax(ImGui::GetContentRegionAvailWidth(), 50.0f);
1436     ImVec2 size = ImVec2(canvasWidth, canvasWidth);
1437     ImVec2 pos = ImGui::GetCursorScreenPos();
1438 
1439     // Background rectangle
1440     drawList->AddRectFilled(pos, ImVec2(pos.x + size.x, pos.y + size.y), IM_COL32(0, 0, 0, 128));
1441 
1442     // Location marker
1443     bool dragging = false;
1444     ImVec2 tl = ImGui_DragPoint("SL", pt + 0, pos, size, &dragging);
1445     drawList->AddCircle(tl, 5.0f, 0xFFFFFFFF);
1446 
1447     ImGui::SetCursorScreenPos(ImVec2(pos.x, pos.y + size.y));
1448     ImGui::Spacing();
1449 
1450     return dragging;
1451 }
1452 
ImGui_DragQuad(SkPoint * pts)1453 static bool ImGui_DragQuad(SkPoint* pts) {
1454     ImDrawList* drawList = ImGui::GetWindowDrawList();
1455 
1456     // Fit our image/canvas to the available width, and scale the height to maintain aspect ratio.
1457     float canvasWidth = SkTMax(ImGui::GetContentRegionAvailWidth(), 50.0f);
1458     ImVec2 size = ImVec2(canvasWidth, canvasWidth);
1459     ImVec2 pos = ImGui::GetCursorScreenPos();
1460 
1461     // Background rectangle
1462     drawList->AddRectFilled(pos, ImVec2(pos.x + size.x, pos.y + size.y), IM_COL32(0, 0, 0, 128));
1463 
1464     // Corner markers
1465     bool dragging = false;
1466     ImVec2 tl = ImGui_DragPoint("TL", pts + 0, pos, size, &dragging);
1467     ImVec2 tr = ImGui_DragPoint("TR", pts + 1, pos, size, &dragging);
1468     ImVec2 bl = ImGui_DragPoint("BL", pts + 2, pos, size, &dragging);
1469     ImVec2 br = ImGui_DragPoint("BR", pts + 3, pos, size, &dragging);
1470 
1471     // Draw markers and quad
1472     drawList->AddCircle(tl, 5.0f, 0xFFFFFFFF);
1473     drawList->AddCircle(tr, 5.0f, 0xFFFFFFFF);
1474     drawList->AddCircle(bl, 5.0f, 0xFFFFFFFF);
1475     drawList->AddCircle(br, 5.0f, 0xFFFFFFFF);
1476     drawList->AddLine(tl, tr, 0xFFFFFFFF);
1477     drawList->AddLine(tr, br, 0xFFFFFFFF);
1478     drawList->AddLine(br, bl, 0xFFFFFFFF);
1479     drawList->AddLine(bl, tl, 0xFFFFFFFF);
1480 
1481     ImGui::SetCursorScreenPos(ImVec2(pos.x, pos.y + size.y));
1482     ImGui::Spacing();
1483 
1484     return dragging;
1485 }
1486 
drawImGui()1487 void Viewer::drawImGui() {
1488     // Support drawing the ImGui demo window. Superfluous, but gives a good idea of what's possible
1489     if (fShowImGuiTestWindow) {
1490         ImGui::ShowDemoWindow(&fShowImGuiTestWindow);
1491     }
1492 
1493     if (fShowImGuiDebugWindow) {
1494         // We have some dynamic content that sizes to fill available size. If the scroll bar isn't
1495         // always visible, we can end up in a layout feedback loop.
1496         ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiCond_FirstUseEver);
1497         DisplayParams params = fWindow->getRequestedDisplayParams();
1498         bool paramsChanged = false;
1499         if (ImGui::Begin("Tools", &fShowImGuiDebugWindow,
1500                          ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
1501             if (ImGui::CollapsingHeader("Backend")) {
1502                 int newBackend = static_cast<int>(fBackendType);
1503                 ImGui::RadioButton("Raster", &newBackend, sk_app::Window::kRaster_BackendType);
1504                 ImGui::SameLine();
1505                 ImGui::RadioButton("OpenGL", &newBackend, sk_app::Window::kNativeGL_BackendType);
1506 #if SK_ANGLE && defined(SK_BUILD_FOR_WIN)
1507                 ImGui::SameLine();
1508                 ImGui::RadioButton("ANGLE", &newBackend, sk_app::Window::kANGLE_BackendType);
1509 #endif
1510 #if defined(SK_VULKAN)
1511                 ImGui::SameLine();
1512                 ImGui::RadioButton("Vulkan", &newBackend, sk_app::Window::kVulkan_BackendType);
1513 #endif
1514                 if (newBackend != fBackendType) {
1515                     fDeferredActions.push_back([=]() {
1516                         this->setBackend(static_cast<sk_app::Window::BackendType>(newBackend));
1517                     });
1518                 }
1519 
1520                 const GrContext* ctx = fWindow->getGrContext();
1521                 bool* wire = &params.fGrContextOptions.fWireframeMode;
1522                 if (ctx && ImGui::Checkbox("Wireframe Mode", wire)) {
1523                     paramsChanged = true;
1524                 }
1525 
1526                 if (ctx) {
1527                     int sampleCount = fWindow->sampleCount();
1528                     ImGui::Text("MSAA: "); ImGui::SameLine();
1529                     ImGui::RadioButton("1", &sampleCount, 1); ImGui::SameLine();
1530                     ImGui::RadioButton("4", &sampleCount, 4); ImGui::SameLine();
1531                     ImGui::RadioButton("8", &sampleCount, 8); ImGui::SameLine();
1532                     ImGui::RadioButton("16", &sampleCount, 16);
1533 
1534                     if (sampleCount != params.fMSAASampleCount) {
1535                         params.fMSAASampleCount = sampleCount;
1536                         paramsChanged = true;
1537                     }
1538                 }
1539 
1540                 int pixelGeometryIdx = 0;
1541                 if (fPixelGeometryOverrides) {
1542                     pixelGeometryIdx = params.fSurfaceProps.pixelGeometry() + 1;
1543                 }
1544                 if (ImGui::Combo("Pixel Geometry", &pixelGeometryIdx,
1545                                  "Default\0Flat\0RGB\0BGR\0RGBV\0BGRV\0\0"))
1546                 {
1547                     uint32_t flags = params.fSurfaceProps.flags();
1548                     if (pixelGeometryIdx == 0) {
1549                         fPixelGeometryOverrides = false;
1550                         params.fSurfaceProps = SkSurfaceProps(flags, SkSurfaceProps::kLegacyFontHost_InitType);
1551                     } else {
1552                         fPixelGeometryOverrides = true;
1553                         SkPixelGeometry pixelGeometry = SkTo<SkPixelGeometry>(pixelGeometryIdx - 1);
1554                         params.fSurfaceProps = SkSurfaceProps(flags, pixelGeometry);
1555                     }
1556                     paramsChanged = true;
1557                 }
1558 
1559                 bool useDFT = params.fSurfaceProps.isUseDeviceIndependentFonts();
1560                 if (ImGui::Checkbox("DFT", &useDFT)) {
1561                     uint32_t flags = params.fSurfaceProps.flags();
1562                     if (useDFT) {
1563                         flags |= SkSurfaceProps::kUseDeviceIndependentFonts_Flag;
1564                     } else {
1565                         flags &= ~SkSurfaceProps::kUseDeviceIndependentFonts_Flag;
1566                     }
1567                     SkPixelGeometry pixelGeometry = params.fSurfaceProps.pixelGeometry();
1568                     params.fSurfaceProps = SkSurfaceProps(flags, pixelGeometry);
1569                     paramsChanged = true;
1570                 }
1571 
1572                 if (ImGui::TreeNode("Path Renderers")) {
1573                     GpuPathRenderers prevPr = params.fGrContextOptions.fGpuPathRenderers;
1574                     auto prButton = [&](GpuPathRenderers x) {
1575                         if (ImGui::RadioButton(gPathRendererNames[x].c_str(), prevPr == x)) {
1576                             if (x != params.fGrContextOptions.fGpuPathRenderers) {
1577                                 params.fGrContextOptions.fGpuPathRenderers = x;
1578                                 paramsChanged = true;
1579                             }
1580                         }
1581                     };
1582 
1583                     if (!ctx) {
1584                         ImGui::RadioButton("Software", true);
1585                     } else if (fWindow->sampleCount() > 1) {
1586                         prButton(GpuPathRenderers::kAll);
1587                         if (ctx->contextPriv().caps()->shaderCaps()->pathRenderingSupport()) {
1588                             prButton(GpuPathRenderers::kStencilAndCover);
1589                         }
1590                         prButton(GpuPathRenderers::kTessellating);
1591                         prButton(GpuPathRenderers::kNone);
1592                     } else {
1593                         prButton(GpuPathRenderers::kAll);
1594                         if (GrCoverageCountingPathRenderer::IsSupported(
1595                                     *ctx->contextPriv().caps())) {
1596                             prButton(GpuPathRenderers::kCoverageCounting);
1597                         }
1598                         prButton(GpuPathRenderers::kSmall);
1599                         prButton(GpuPathRenderers::kTessellating);
1600                         prButton(GpuPathRenderers::kNone);
1601                     }
1602                     ImGui::TreePop();
1603                 }
1604             }
1605 
1606             if (ImGui::CollapsingHeader("Transform")) {
1607                 float zoom = fZoomLevel;
1608                 if (ImGui::SliderFloat("Zoom", &zoom, MIN_ZOOM_LEVEL, MAX_ZOOM_LEVEL)) {
1609                     fZoomLevel = zoom;
1610                     this->preTouchMatrixChanged();
1611                     paramsChanged = true;
1612                 }
1613                 float deg = fRotation;
1614                 if (ImGui::SliderFloat("Rotate", &deg, -30, 360, "%.3f deg")) {
1615                     fRotation = deg;
1616                     this->preTouchMatrixChanged();
1617                     paramsChanged = true;
1618                 }
1619                 if (ImGui::CollapsingHeader("Subpixel offset", ImGuiTreeNodeFlags_NoTreePushOnOpen)) {
1620                     if (ImGui_DragLocation(&fOffset)) {
1621                         this->preTouchMatrixChanged();
1622                         paramsChanged = true;
1623                     }
1624                 } else if (fOffset != SkVector{0.5f, 0.5f}) {
1625                     this->preTouchMatrixChanged();
1626                     paramsChanged = true;
1627                     fOffset = {0.5f, 0.5f};
1628                 }
1629                 if (ImGui::CollapsingHeader("Tiling")) {
1630                     ImGui::Checkbox("Enable", &fTiled);
1631                     ImGui::Checkbox("Draw Boundaries", &fDrawTileBoundaries);
1632                     ImGui::SliderFloat("Horizontal", &fTileScale.fWidth, 0.1f, 1.0f);
1633                     ImGui::SliderFloat("Vertical", &fTileScale.fHeight, 0.1f, 1.0f);
1634                 }
1635                 int perspectiveMode = static_cast<int>(fPerspectiveMode);
1636                 if (ImGui::Combo("Perspective", &perspectiveMode, "Off\0Real\0Fake\0\0")) {
1637                     fPerspectiveMode = static_cast<PerspectiveMode>(perspectiveMode);
1638                     this->preTouchMatrixChanged();
1639                     paramsChanged = true;
1640                 }
1641                 if (perspectiveMode != kPerspective_Off && ImGui_DragQuad(fPerspectivePoints)) {
1642                     this->preTouchMatrixChanged();
1643                     paramsChanged = true;
1644                 }
1645             }
1646 
1647             if (ImGui::CollapsingHeader("Paint")) {
1648                 int aliasIdx = 0;
1649                 if (fPaintOverrides.fAntiAlias) {
1650                     aliasIdx = SkTo<int>(fPaintOverrides.fAntiAliasState) + 1;
1651                 }
1652                 if (ImGui::Combo("Anti-Alias", &aliasIdx,
1653                                  "Default\0Alias\0Normal\0AnalyticAAEnabled\0AnalyticAAForced\0"
1654                                  "DeltaAAEnabled\0DeltaAAForced\0\0"))
1655                 {
1656                     gSkUseAnalyticAA = fPaintOverrides.fOriginalSkUseAnalyticAA;
1657                     gSkForceAnalyticAA = fPaintOverrides.fOriginalSkForceAnalyticAA;
1658                     gSkUseDeltaAA = fPaintOverrides.fOriginalSkUseDeltaAA;
1659                     gSkForceDeltaAA = fPaintOverrides.fOriginalSkForceDeltaAA;
1660                     if (aliasIdx == 0) {
1661                         fPaintOverrides.fAntiAliasState = SkPaintFields::AntiAliasState::Alias;
1662                         fPaintOverrides.fAntiAlias = false;
1663                     } else {
1664                         fPaintOverrides.fAntiAlias = true;
1665                         fPaintOverrides.fAntiAliasState = SkTo<SkPaintFields::AntiAliasState>(aliasIdx-1);
1666                         fPaint.setAntiAlias(aliasIdx > 1);
1667                         switch (fPaintOverrides.fAntiAliasState) {
1668                             case SkPaintFields::AntiAliasState::Alias:
1669                                 break;
1670                             case SkPaintFields::AntiAliasState::Normal:
1671                                 break;
1672                             case SkPaintFields::AntiAliasState::AnalyticAAEnabled:
1673                                 gSkUseAnalyticAA = true;
1674                                 gSkForceAnalyticAA = false;
1675                                 gSkUseDeltaAA = gSkForceDeltaAA = false;
1676                                 break;
1677                             case SkPaintFields::AntiAliasState::AnalyticAAForced:
1678                                 gSkUseAnalyticAA = gSkForceAnalyticAA = true;
1679                                 gSkUseDeltaAA = gSkForceDeltaAA = false;
1680                                 break;
1681                             case SkPaintFields::AntiAliasState::DeltaAAEnabled:
1682                                 gSkUseAnalyticAA = gSkForceAnalyticAA = false;
1683                                 gSkUseDeltaAA = true;
1684                                 gSkForceDeltaAA = false;
1685                                 break;
1686                             case SkPaintFields::AntiAliasState::DeltaAAForced:
1687                                 gSkUseAnalyticAA = gSkForceAnalyticAA = false;
1688                                 gSkUseDeltaAA = gSkForceDeltaAA = true;
1689                                 break;
1690                         }
1691                     }
1692                     paramsChanged = true;
1693                 }
1694 
1695                 auto paintFlag = [this, &paramsChanged](const char* label, const char* items,
1696                                                         bool SkPaintFields::* flag,
1697                                                         bool (SkPaint::* isFlag)() const,
1698                                                         void (SkPaint::* setFlag)(bool) )
1699                 {
1700                     int itemIndex = 0;
1701                     if (fPaintOverrides.*flag) {
1702                         itemIndex = (fPaint.*isFlag)() ? 2 : 1;
1703                     }
1704                     if (ImGui::Combo(label, &itemIndex, items)) {
1705                         if (itemIndex == 0) {
1706                             fPaintOverrides.*flag = false;
1707                         } else {
1708                             fPaintOverrides.*flag = true;
1709                             (fPaint.*setFlag)(itemIndex == 2);
1710                         }
1711                         paramsChanged = true;
1712                     }
1713                 };
1714 
1715                 paintFlag("Dither",
1716                           "Default\0No Dither\0Dither\0\0",
1717                           &SkPaintFields::fDither,
1718                           &SkPaint::isDither, &SkPaint::setDither);
1719             }
1720 
1721             if (ImGui::CollapsingHeader("Font")) {
1722                 int hintingIdx = 0;
1723                 if (fFontOverrides.fHinting) {
1724                     hintingIdx = SkTo<int>(fFont.getHinting()) + 1;
1725                 }
1726                 if (ImGui::Combo("Hinting", &hintingIdx,
1727                                  "Default\0None\0Slight\0Normal\0Full\0\0"))
1728                 {
1729                     if (hintingIdx == 0) {
1730                         fFontOverrides.fHinting = false;
1731                         fFont.setHinting(kNo_SkFontHinting);
1732                     } else {
1733                         fFont.setHinting(SkTo<SkFontHinting>(hintingIdx - 1));
1734                         fFontOverrides.fHinting = true;
1735                     }
1736                     paramsChanged = true;
1737                 }
1738 
1739                 auto fontFlag = [this, &paramsChanged](const char* label, const char* items,
1740                                                        bool SkFontFields::* flag,
1741                                                        bool (SkFont::* isFlag)() const,
1742                                                        void (SkFont::* setFlag)(bool) )
1743                 {
1744                     int itemIndex = 0;
1745                     if (fFontOverrides.*flag) {
1746                         itemIndex = (fFont.*isFlag)() ? 2 : 1;
1747                     }
1748                     if (ImGui::Combo(label, &itemIndex, items)) {
1749                         if (itemIndex == 0) {
1750                             fFontOverrides.*flag = false;
1751                         } else {
1752                             fFontOverrides.*flag = true;
1753                             (fFont.*setFlag)(itemIndex == 2);
1754                         }
1755                         paramsChanged = true;
1756                     }
1757                 };
1758 
1759                 fontFlag("Fake Bold Glyphs",
1760                          "Default\0No Fake Bold\0Fake Bold\0\0",
1761                          &SkFontFields::fEmbolden,
1762                          &SkFont::isEmbolden, &SkFont::setEmbolden);
1763 
1764                 fontFlag("Linear Text",
1765                          "Default\0No Linear Text\0Linear Text\0\0",
1766                          &SkFontFields::fLinearMetrics,
1767                          &SkFont::isLinearMetrics, &SkFont::setLinearMetrics);
1768 
1769                 fontFlag("Subpixel Position Glyphs",
1770                          "Default\0Pixel Text\0Subpixel Text\0\0",
1771                          &SkFontFields::fSubpixel,
1772                          &SkFont::isSubpixel, &SkFont::setSubpixel);
1773 
1774                 fontFlag("Embedded Bitmap Text",
1775                          "Default\0No Embedded Bitmaps\0Embedded Bitmaps\0\0",
1776                          &SkFontFields::fEmbeddedBitmaps,
1777                          &SkFont::isEmbeddedBitmaps, &SkFont::setEmbeddedBitmaps);
1778 
1779                 fontFlag("Force Auto-Hinting",
1780                          "Default\0No Force Auto-Hinting\0Force Auto-Hinting\0\0",
1781                          &SkFontFields::fForceAutoHinting,
1782                          &SkFont::isForceAutoHinting, &SkFont::setForceAutoHinting);
1783 
1784                 int edgingIdx = 0;
1785                 if (fFontOverrides.fEdging) {
1786                     edgingIdx = SkTo<int>(fFont.getEdging()) + 1;
1787                 }
1788                 if (ImGui::Combo("Edging", &edgingIdx,
1789                                  "Default\0Alias\0Antialias\0Subpixel Antialias\0\0"))
1790                 {
1791                     if (edgingIdx == 0) {
1792                         fFontOverrides.fEdging = false;
1793                         fFont.setEdging(SkFont::Edging::kAlias);
1794                     } else {
1795                         fFont.setEdging(SkTo<SkFont::Edging>(edgingIdx-1));
1796                         fFontOverrides.fEdging = true;
1797                     }
1798                     paramsChanged = true;
1799                 }
1800 
1801                 ImGui::Checkbox("Override TextSize", &fFontOverrides.fTextSize);
1802                 if (fFontOverrides.fTextSize) {
1803                     ImGui::DragFloat2("TextRange", fFontOverrides.fTextSizeRange,
1804                                       0.001f, -10.0f, 300.0f, "%.6f", 2.0f);
1805                     float textSize = fFont.getSize();
1806                     if (ImGui::DragFloat("TextSize", &textSize, 0.001f,
1807                                          fFontOverrides.fTextSizeRange[0],
1808                                          fFontOverrides.fTextSizeRange[1],
1809                                          "%.6f", 2.0f))
1810                     {
1811                         fFont.setSize(textSize);
1812                         this->preTouchMatrixChanged();
1813                         paramsChanged = true;
1814                     }
1815                 }
1816             }
1817 
1818             {
1819                 SkMetaData controls;
1820                 if (fSlides[fCurrentSlide]->onGetControls(&controls)) {
1821                     if (ImGui::CollapsingHeader("Current Slide")) {
1822                         SkMetaData::Iter iter(controls);
1823                         const char* name;
1824                         SkMetaData::Type type;
1825                         int count;
1826                         while ((name = iter.next(&type, &count)) != nullptr) {
1827                             if (type == SkMetaData::kScalar_Type) {
1828                                 float val[3];
1829                                 SkASSERT(count == 3);
1830                                 controls.findScalars(name, &count, val);
1831                                 if (ImGui::SliderFloat(name, &val[0], val[1], val[2])) {
1832                                     controls.setScalars(name, 3, val);
1833                                 }
1834                             }
1835                         }
1836                         fSlides[fCurrentSlide]->onSetControls(controls);
1837                     }
1838                 }
1839             }
1840 
1841             if (fShowSlidePicker) {
1842                 ImGui::SetNextTreeNodeOpen(true);
1843             }
1844             if (ImGui::CollapsingHeader("Slide")) {
1845                 static ImGuiTextFilter filter;
1846                 static ImVector<const char*> filteredSlideNames;
1847                 static ImVector<int> filteredSlideIndices;
1848 
1849                 if (fShowSlidePicker) {
1850                     ImGui::SetKeyboardFocusHere();
1851                     fShowSlidePicker = false;
1852                 }
1853 
1854                 filter.Draw();
1855                 filteredSlideNames.clear();
1856                 filteredSlideIndices.clear();
1857                 int filteredIndex = 0;
1858                 for (int i = 0; i < fSlides.count(); ++i) {
1859                     const char* slideName = fSlides[i]->getName().c_str();
1860                     if (filter.PassFilter(slideName) || i == fCurrentSlide) {
1861                         if (i == fCurrentSlide) {
1862                             filteredIndex = filteredSlideIndices.size();
1863                         }
1864                         filteredSlideNames.push_back(slideName);
1865                         filteredSlideIndices.push_back(i);
1866                     }
1867                 }
1868 
1869                 if (ImGui::ListBox("", &filteredIndex, filteredSlideNames.begin(),
1870                                    filteredSlideNames.size(), 20)) {
1871                     this->setCurrentSlide(filteredSlideIndices[filteredIndex]);
1872                 }
1873             }
1874 
1875             if (ImGui::CollapsingHeader("Color Mode")) {
1876                 ColorMode newMode = fColorMode;
1877                 auto cmButton = [&](ColorMode mode, const char* label) {
1878                     if (ImGui::RadioButton(label, mode == fColorMode)) {
1879                         newMode = mode;
1880                     }
1881                 };
1882 
1883                 cmButton(ColorMode::kLegacy, "Legacy 8888");
1884                 cmButton(ColorMode::kColorManaged8888, "Color Managed 8888");
1885                 cmButton(ColorMode::kColorManagedF16, "Color Managed F16");
1886 
1887                 if (newMode != fColorMode) {
1888                     this->setColorMode(newMode);
1889                 }
1890 
1891                 // Pick from common gamuts:
1892                 int primariesIdx = 4; // Default: Custom
1893                 for (size_t i = 0; i < SK_ARRAY_COUNT(gNamedPrimaries); ++i) {
1894                     if (primaries_equal(*gNamedPrimaries[i].fPrimaries, fColorSpacePrimaries)) {
1895                         primariesIdx = i;
1896                         break;
1897                     }
1898                 }
1899 
1900                 // Let user adjust the gamma
1901                 ImGui::SliderFloat("Gamma", &fColorSpaceTransferFn.g, 0.5f, 3.5f);
1902 
1903                 if (ImGui::Combo("Primaries", &primariesIdx,
1904                                  "sRGB\0AdobeRGB\0P3\0Rec. 2020\0Custom\0\0")) {
1905                     if (primariesIdx >= 0 && primariesIdx <= 3) {
1906                         fColorSpacePrimaries = *gNamedPrimaries[primariesIdx].fPrimaries;
1907                     }
1908                 }
1909 
1910                 // Allow direct editing of gamut
1911                 ImGui_Primaries(&fColorSpacePrimaries, &fImGuiGamutPaint);
1912             }
1913 
1914             if (ImGui::CollapsingHeader("Animation")) {
1915                 bool isPaused = fAnimTimer.isPaused();
1916                 if (ImGui::Checkbox("Pause", &isPaused)) {
1917                     fAnimTimer.togglePauseResume();
1918                 }
1919 
1920                 float speed = fAnimTimer.getSpeed();
1921                 if (ImGui::DragFloat("Speed", &speed, 0.1f)) {
1922                     fAnimTimer.setSpeed(speed);
1923                 }
1924             }
1925         }
1926         if (paramsChanged) {
1927             fDeferredActions.push_back([=]() {
1928                 fWindow->setRequestedDisplayParams(params);
1929                 fWindow->inval();
1930                 this->updateTitle();
1931             });
1932         }
1933         ImGui::End();
1934     }
1935 
1936     if (fShowZoomWindow && fLastImage) {
1937         ImGui::SetNextWindowSize(ImVec2(200, 200), ImGuiCond_FirstUseEver);
1938         if (ImGui::Begin("Zoom", &fShowZoomWindow)) {
1939             static int zoomFactor = 8;
1940             if (ImGui::Button("<<")) {
1941                 zoomFactor = SkTMax(zoomFactor / 2, 4);
1942             }
1943             ImGui::SameLine(); ImGui::Text("%2d", zoomFactor); ImGui::SameLine();
1944             if (ImGui::Button(">>")) {
1945                 zoomFactor = SkTMin(zoomFactor * 2, 32);
1946             }
1947 
1948             if (!fZoomWindowFixed) {
1949                 ImVec2 mousePos = ImGui::GetMousePos();
1950                 fZoomWindowLocation = SkPoint::Make(mousePos.x, mousePos.y);
1951             }
1952             SkScalar x = fZoomWindowLocation.x();
1953             SkScalar y = fZoomWindowLocation.y();
1954             int xInt = SkScalarRoundToInt(x);
1955             int yInt = SkScalarRoundToInt(y);
1956             ImVec2 avail = ImGui::GetContentRegionAvail();
1957 
1958             uint32_t pixel = 0;
1959             SkImageInfo info = SkImageInfo::MakeN32Premul(1, 1);
1960             if (fLastImage->readPixels(info, &pixel, info.minRowBytes(), xInt, yInt)) {
1961                 ImGui::SameLine();
1962                 ImGui::Text("(X, Y): %d, %d RGBA: %x %x %x %x",
1963                             xInt, yInt,
1964                             SkGetPackedR32(pixel), SkGetPackedG32(pixel),
1965                             SkGetPackedB32(pixel), SkGetPackedA32(pixel));
1966             }
1967 
1968             fImGuiLayer.skiaWidget(avail, [=](SkCanvas* c) {
1969                 // Translate so the region of the image that's under the mouse cursor is centered
1970                 // in the zoom canvas:
1971                 c->scale(zoomFactor, zoomFactor);
1972                 c->translate(avail.x * 0.5f / zoomFactor - x - 0.5f,
1973                              avail.y * 0.5f / zoomFactor - y - 0.5f);
1974                 c->drawImage(this->fLastImage, 0, 0);
1975 
1976                 SkPaint outline;
1977                 outline.setStyle(SkPaint::kStroke_Style);
1978                 c->drawRect(SkRect::MakeXYWH(x, y, 1, 1), outline);
1979             });
1980         }
1981 
1982         ImGui::End();
1983     }
1984 }
1985 
onIdle()1986 void Viewer::onIdle() {
1987     for (int i = 0; i < fDeferredActions.count(); ++i) {
1988         fDeferredActions[i]();
1989     }
1990     fDeferredActions.reset();
1991 
1992     fStatsLayer.beginTiming(fAnimateTimer);
1993     fAnimTimer.updateTime();
1994     bool animateWantsInval = fSlides[fCurrentSlide]->animate(fAnimTimer);
1995     fStatsLayer.endTiming(fAnimateTimer);
1996 
1997     ImGuiIO& io = ImGui::GetIO();
1998     // ImGui always has at least one "active" window, which is the default "Debug" window. It may
1999     // not be visible, though. So we need to redraw if there is at least one visible window, or
2000     // more than one active window. Newly created windows are active but not visible for one frame
2001     // while they determine their layout and sizing.
2002     if (animateWantsInval || fStatsLayer.getActive() || fRefresh ||
2003         io.MetricsActiveWindows > 1 || io.MetricsRenderWindows > 0) {
2004         fWindow->inval();
2005     }
2006 }
2007 
2008 template <typename OptionsFunc>
WriteStateObject(SkJSONWriter & writer,const char * name,const char * value,OptionsFunc && optionsFunc)2009 static void WriteStateObject(SkJSONWriter& writer, const char* name, const char* value,
2010                              OptionsFunc&& optionsFunc) {
2011     writer.beginObject();
2012     {
2013         writer.appendString(kName , name);
2014         writer.appendString(kValue, value);
2015 
2016         writer.beginArray(kOptions);
2017         {
2018             optionsFunc(writer);
2019         }
2020         writer.endArray();
2021     }
2022     writer.endObject();
2023 }
2024 
2025 
updateUIState()2026 void Viewer::updateUIState() {
2027     if (!fWindow) {
2028         return;
2029     }
2030     if (fWindow->sampleCount() < 1) {
2031         return; // Surface hasn't been created yet.
2032     }
2033 
2034     SkDynamicMemoryWStream memStream;
2035     SkJSONWriter writer(&memStream);
2036     writer.beginArray();
2037 
2038     // Slide state
2039     WriteStateObject(writer, kSlideStateName, fSlides[fCurrentSlide]->getName().c_str(),
2040         [this](SkJSONWriter& writer) {
2041             for(const auto& slide : fSlides) {
2042                 writer.appendString(slide->getName().c_str());
2043             }
2044         });
2045 
2046     // Backend state
2047     WriteStateObject(writer, kBackendStateName, kBackendTypeStrings[fBackendType],
2048         [](SkJSONWriter& writer) {
2049             for (const auto& str : kBackendTypeStrings) {
2050                 writer.appendString(str);
2051             }
2052         });
2053 
2054     // MSAA state
2055     const auto countString = SkStringPrintf("%d", fWindow->sampleCount());
2056     WriteStateObject(writer, kMSAAStateName, countString.c_str(),
2057         [this](SkJSONWriter& writer) {
2058             writer.appendS32(0);
2059 
2060             if (sk_app::Window::kRaster_BackendType == fBackendType) {
2061                 return;
2062             }
2063 
2064             for (int msaa : {4, 8, 16}) {
2065                 writer.appendS32(msaa);
2066             }
2067         });
2068 
2069     // Path renderer state
2070     GpuPathRenderers pr = fWindow->getRequestedDisplayParams().fGrContextOptions.fGpuPathRenderers;
2071     WriteStateObject(writer, kPathRendererStateName, gPathRendererNames[pr].c_str(),
2072         [this](SkJSONWriter& writer) {
2073             const GrContext* ctx = fWindow->getGrContext();
2074             if (!ctx) {
2075                 writer.appendString("Software");
2076             } else {
2077                 const auto* caps = ctx->contextPriv().caps();
2078 
2079                 writer.appendString(gPathRendererNames[GpuPathRenderers::kAll].c_str());
2080                 if (fWindow->sampleCount() > 1) {
2081                     if (caps->shaderCaps()->pathRenderingSupport()) {
2082                         writer.appendString(
2083                             gPathRendererNames[GpuPathRenderers::kStencilAndCover].c_str());
2084                     }
2085                 } else {
2086                     if(GrCoverageCountingPathRenderer::IsSupported(*caps)) {
2087                         writer.appendString(
2088                             gPathRendererNames[GpuPathRenderers::kCoverageCounting].c_str());
2089                     }
2090                     writer.appendString(gPathRendererNames[GpuPathRenderers::kSmall].c_str());
2091                 }
2092                     writer.appendString(
2093                         gPathRendererNames[GpuPathRenderers::kTessellating].c_str());
2094                     writer.appendString(gPathRendererNames[GpuPathRenderers::kNone].c_str());
2095             }
2096         });
2097 
2098     // Softkey state
2099     WriteStateObject(writer, kSoftkeyStateName, kSoftkeyHint,
2100         [this](SkJSONWriter& writer) {
2101             writer.appendString(kSoftkeyHint);
2102             for (const auto& softkey : fCommands.getCommandsAsSoftkeys()) {
2103                 writer.appendString(softkey.c_str());
2104             }
2105         });
2106 
2107     writer.endArray();
2108     writer.flush();
2109 
2110     auto data = memStream.detachAsData();
2111 
2112     // TODO: would be cool to avoid this copy
2113     const SkString cstring(static_cast<const char*>(data->data()), data->size());
2114 
2115     fWindow->setUIState(cstring.c_str());
2116 }
2117 
onUIStateChanged(const SkString & stateName,const SkString & stateValue)2118 void Viewer::onUIStateChanged(const SkString& stateName, const SkString& stateValue) {
2119     // For those who will add more features to handle the state change in this function:
2120     // After the change, please call updateUIState no notify the frontend (e.g., Android app).
2121     // For example, after slide change, updateUIState is called inside setupCurrentSlide;
2122     // after backend change, updateUIState is called in this function.
2123     if (stateName.equals(kSlideStateName)) {
2124         for (int i = 0; i < fSlides.count(); ++i) {
2125             if (fSlides[i]->getName().equals(stateValue)) {
2126                 this->setCurrentSlide(i);
2127                 return;
2128             }
2129         }
2130 
2131         SkDebugf("Slide not found: %s", stateValue.c_str());
2132     } else if (stateName.equals(kBackendStateName)) {
2133         for (int i = 0; i < sk_app::Window::kBackendTypeCount; i++) {
2134             if (stateValue.equals(kBackendTypeStrings[i])) {
2135                 if (fBackendType != i) {
2136                     fBackendType = (sk_app::Window::BackendType)i;
2137                     fWindow->detach();
2138                     fWindow->attach(backend_type_for_window(fBackendType));
2139                 }
2140                 break;
2141             }
2142         }
2143     } else if (stateName.equals(kMSAAStateName)) {
2144         DisplayParams params = fWindow->getRequestedDisplayParams();
2145         int sampleCount = atoi(stateValue.c_str());
2146         if (sampleCount != params.fMSAASampleCount) {
2147             params.fMSAASampleCount = sampleCount;
2148             fWindow->setRequestedDisplayParams(params);
2149             fWindow->inval();
2150             this->updateTitle();
2151             this->updateUIState();
2152         }
2153     } else if (stateName.equals(kPathRendererStateName)) {
2154         DisplayParams params = fWindow->getRequestedDisplayParams();
2155         for (const auto& pair : gPathRendererNames) {
2156             if (pair.second == stateValue.c_str()) {
2157                 if (params.fGrContextOptions.fGpuPathRenderers != pair.first) {
2158                     params.fGrContextOptions.fGpuPathRenderers = pair.first;
2159                     fWindow->setRequestedDisplayParams(params);
2160                     fWindow->inval();
2161                     this->updateTitle();
2162                     this->updateUIState();
2163                 }
2164                 break;
2165             }
2166         }
2167     } else if (stateName.equals(kSoftkeyStateName)) {
2168         if (!stateValue.equals(kSoftkeyHint)) {
2169             fCommands.onSoftkey(stateValue);
2170             this->updateUIState(); // This is still needed to reset the value to kSoftkeyHint
2171         }
2172     } else if (stateName.equals(kRefreshStateName)) {
2173         // This state is actually NOT in the UI state.
2174         // We use this to allow Android to quickly set bool fRefresh.
2175         fRefresh = stateValue.equals(kON);
2176     } else {
2177         SkDebugf("Unknown stateName: %s", stateName.c_str());
2178     }
2179 }
2180 
onKey(sk_app::Window::Key key,sk_app::Window::InputState state,uint32_t modifiers)2181 bool Viewer::onKey(sk_app::Window::Key key, sk_app::Window::InputState state, uint32_t modifiers) {
2182     return fCommands.onKey(key, state, modifiers);
2183 }
2184 
onChar(SkUnichar c,uint32_t modifiers)2185 bool Viewer::onChar(SkUnichar c, uint32_t modifiers) {
2186     if (fSlides[fCurrentSlide]->onChar(c)) {
2187         fWindow->inval();
2188         return true;
2189     } else {
2190         return fCommands.onChar(c, modifiers);
2191     }
2192 }
2193