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