• 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 "Viewer.h"
9 
10 #include "GMSlide.h"
11 #include "ImageSlide.h"
12 #include "Resources.h"
13 #include "SampleSlide.h"
14 #include "SKPSlide.h"
15 
16 #include "GrContext.h"
17 #include "SkATrace.h"
18 #include "SkCanvas.h"
19 #include "SkColorSpace_Base.h"
20 #include "SkColorSpaceXformCanvas.h"
21 #include "SkCommandLineFlags.h"
22 #include "SkCommonFlagsPathRenderer.h"
23 #include "SkDashPathEffect.h"
24 #include "SkGraphics.h"
25 #include "SkImagePriv.h"
26 #include "SkMetaData.h"
27 #include "SkOnce.h"
28 #include "SkOSFile.h"
29 #include "SkOSPath.h"
30 #include "SkRandom.h"
31 #include "SkStream.h"
32 #include "SkSurface.h"
33 #include "SkSwizzle.h"
34 #include "SkTaskGroup.h"
35 #include "SkTime.h"
36 #include "SkVertices.h"
37 
38 #include "imgui.h"
39 
40 #include "ccpr/GrCoverageCountingPathRenderer.h"
41 
42 #include <stdlib.h>
43 #include <map>
44 
45 using namespace sk_app;
46 
47 using GpuPathRenderers = GrContextOptions::GpuPathRenderers;
48 static std::map<GpuPathRenderers, std::string> gPathRendererNames;
49 
Create(int argc,char ** argv,void * platformData)50 Application* Application::Create(int argc, char** argv, void* platformData) {
51     return new Viewer(argc, argv, platformData);
52 }
53 
on_backend_created_func(void * userData)54 static void on_backend_created_func(void* userData) {
55     Viewer* vv = reinterpret_cast<Viewer*>(userData);
56 
57     return vv->onBackendCreated();
58 }
59 
on_paint_handler(SkCanvas * canvas,void * userData)60 static void on_paint_handler(SkCanvas* canvas, void* userData) {
61     Viewer* vv = reinterpret_cast<Viewer*>(userData);
62 
63     return vv->onPaint(canvas);
64 }
65 
on_touch_handler(intptr_t owner,Window::InputState state,float x,float y,void * userData)66 static bool on_touch_handler(intptr_t owner, Window::InputState state, float x, float y, void* userData)
67 {
68     Viewer* viewer = reinterpret_cast<Viewer*>(userData);
69 
70     return viewer->onTouch(owner, state, x, y);
71 }
72 
on_ui_state_changed_handler(const SkString & stateName,const SkString & stateValue,void * userData)73 static void on_ui_state_changed_handler(const SkString& stateName, const SkString& stateValue, void* userData) {
74     Viewer* viewer = reinterpret_cast<Viewer*>(userData);
75 
76     return viewer->onUIStateChanged(stateName, stateValue);
77 }
78 
on_mouse_handler(int x,int y,Window::InputState state,uint32_t modifiers,void * userData)79 static bool on_mouse_handler(int x, int y, Window::InputState state, uint32_t modifiers,
80                              void* userData) {
81     ImGuiIO& io = ImGui::GetIO();
82     io.MousePos.x = static_cast<float>(x);
83     io.MousePos.y = static_cast<float>(y);
84     if (Window::kDown_InputState == state) {
85         io.MouseDown[0] = true;
86     } else if (Window::kUp_InputState == state) {
87         io.MouseDown[0] = false;
88     }
89     if (io.WantCaptureMouse) {
90         return true;
91     } else {
92         Viewer* viewer = reinterpret_cast<Viewer*>(userData);
93         return viewer->onMouse(x, y, state, modifiers);
94     }
95 }
96 
on_mouse_wheel_handler(float delta,uint32_t modifiers,void * userData)97 static bool on_mouse_wheel_handler(float delta, uint32_t modifiers, void* userData) {
98     ImGuiIO& io = ImGui::GetIO();
99     io.MouseWheel += delta;
100     return true;
101 }
102 
on_key_handler(Window::Key key,Window::InputState state,uint32_t modifiers,void * userData)103 static bool on_key_handler(Window::Key key, Window::InputState state, uint32_t modifiers,
104                            void* userData) {
105     ImGuiIO& io = ImGui::GetIO();
106     io.KeysDown[static_cast<int>(key)] = (Window::kDown_InputState == state);
107 
108     if (io.WantCaptureKeyboard) {
109         return true;
110     } else {
111         Viewer* viewer = reinterpret_cast<Viewer*>(userData);
112         return viewer->onKey(key, state, modifiers);
113     }
114 }
115 
on_char_handler(SkUnichar c,uint32_t modifiers,void * userData)116 static bool on_char_handler(SkUnichar c, uint32_t modifiers, void* userData) {
117     ImGuiIO& io = ImGui::GetIO();
118     if (io.WantTextInput) {
119         if (c > 0 && c < 0x10000) {
120             io.AddInputCharacter(c);
121         }
122         return true;
123     } else {
124         Viewer* viewer = reinterpret_cast<Viewer*>(userData);
125         return viewer->onChar(c, modifiers);
126     }
127 }
128 
129 static DEFINE_bool2(fullscreen, f, true, "Run fullscreen.");
130 
131 static DEFINE_string2(match, m, nullptr,
132                "[~][^]substring[$] [...] of bench name to run.\n"
133                "Multiple matches may be separated by spaces.\n"
134                "~ causes a matching bench to always be skipped\n"
135                "^ requires the start of the bench to match\n"
136                "$ requires the end of the bench to match\n"
137                "^ and $ requires an exact match\n"
138                "If a bench does not match any list entry,\n"
139                "it is skipped unless some list entry starts with ~");
140 
141 DEFINE_string(slide, "", "Start on this sample.");
142 DEFINE_bool(list, false, "List samples?");
143 
144 #ifdef SK_VULKAN
145 #    define BACKENDS_STR "\"sw\", \"gl\", and \"vk\""
146 #else
147 #    define BACKENDS_STR "\"sw\" and \"gl\""
148 #endif
149 
150 #ifdef SK_BUILD_FOR_ANDROID
151 static DEFINE_string(skps, "/data/local/tmp/skps", "Directory to read skps from.");
152 static DEFINE_string(jpgs, "/data/local/tmp/resources", "Directory to read jpgs from.");
153 #else
154 static DEFINE_string(skps, "skps", "Directory to read skps from.");
155 static DEFINE_string(jpgs, "jpgs", "Directory to read jpgs from.");
156 #endif
157 
158 static DEFINE_string2(backend, b, "sw", "Backend to use. Allowed values are " BACKENDS_STR ".");
159 
160 static DEFINE_bool(atrace, false, "Enable support for using ATrace. ATrace is only supported on Android.");
161 
162 DEFINE_int32(msaa, 0, "Number of subpixel samples. 0 for no HW antialiasing.");
163 DEFINE_pathrenderer_flag;
164 
165 DEFINE_bool(instancedRendering, false, "Enable instanced rendering on GPU backends.");
166 
167 const char *kBackendTypeStrings[sk_app::Window::kBackendTypeCount] = {
168     "OpenGL",
169 #ifdef SK_VULKAN
170     "Vulkan",
171 #endif
172     "Raster"
173 };
174 
get_backend_type(const char * str)175 static sk_app::Window::BackendType get_backend_type(const char* str) {
176 #ifdef SK_VULKAN
177     if (0 == strcmp(str, "vk")) {
178         return sk_app::Window::kVulkan_BackendType;
179     } else
180 #endif
181     if (0 == strcmp(str, "gl")) {
182         return sk_app::Window::kNativeGL_BackendType;
183     } else if (0 == strcmp(str, "sw")) {
184         return sk_app::Window::kRaster_BackendType;
185     } else {
186         SkDebugf("Unknown backend type, %s, defaulting to sw.", str);
187         return sk_app::Window::kRaster_BackendType;
188     }
189 }
190 
191 static SkColorSpacePrimaries gSrgbPrimaries = {
192     0.64f, 0.33f,
193     0.30f, 0.60f,
194     0.15f, 0.06f,
195     0.3127f, 0.3290f };
196 
197 static SkColorSpacePrimaries gAdobePrimaries = {
198     0.64f, 0.33f,
199     0.21f, 0.71f,
200     0.15f, 0.06f,
201     0.3127f, 0.3290f };
202 
203 static SkColorSpacePrimaries gP3Primaries = {
204     0.680f, 0.320f,
205     0.265f, 0.690f,
206     0.150f, 0.060f,
207     0.3127f, 0.3290f };
208 
209 static SkColorSpacePrimaries gRec2020Primaries = {
210     0.708f, 0.292f,
211     0.170f, 0.797f,
212     0.131f, 0.046f,
213     0.3127f, 0.3290f };
214 
215 struct NamedPrimaries {
216     const char* fName;
217     SkColorSpacePrimaries* fPrimaries;
218 } gNamedPrimaries[] = {
219     { "sRGB", &gSrgbPrimaries },
220     { "AdobeRGB", &gAdobePrimaries },
221     { "P3", &gP3Primaries },
222     { "Rec. 2020", &gRec2020Primaries },
223 };
224 
primaries_equal(const SkColorSpacePrimaries & a,const SkColorSpacePrimaries & b)225 static bool primaries_equal(const SkColorSpacePrimaries& a, const SkColorSpacePrimaries& b) {
226     return memcmp(&a, &b, sizeof(SkColorSpacePrimaries)) == 0;
227 }
228 
229 const char* kName = "name";
230 const char* kValue = "value";
231 const char* kOptions = "options";
232 const char* kSlideStateName = "Slide";
233 const char* kBackendStateName = "Backend";
234 const char* kMSAAStateName = "MSAA";
235 const char* kPathRendererStateName = "Path renderer";
236 const char* kInstancedRenderingStateName = "Instanced rendering";
237 const char* kSoftkeyStateName = "Softkey";
238 const char* kSoftkeyHint = "Please select a softkey";
239 const char* kFpsStateName = "FPS";
240 const char* kON = "ON";
241 const char* kOFF = "OFF";
242 const char* kRefreshStateName = "Refresh";
243 
Viewer(int argc,char ** argv,void * platformData)244 Viewer::Viewer(int argc, char** argv, void* platformData)
245     : fCurrentMeasurement(0)
246     , fDisplayStats(false)
247     , fRefresh(false)
248     , fShowImGuiDebugWindow(false)
249     , fShowImGuiTestWindow(false)
250     , fShowZoomWindow(false)
251     , fLastImage(nullptr)
252     , fBackendType(sk_app::Window::kNativeGL_BackendType)
253     , fColorMode(ColorMode::kLegacy)
254     , fColorSpacePrimaries(gSrgbPrimaries)
255     , fZoomLevel(0.0f)
256     , fGestureDevice(GestureDevice::kNone)
257 {
258     static SkTaskGroup::Enabler kTaskGroupEnabler;
259     SkGraphics::Init();
260 
261     static SkOnce initPathRendererNames;
262     initPathRendererNames([]() {
263         gPathRendererNames[GpuPathRenderers::kAll] = "Default Ganesh Behavior (best path renderer)";
264         gPathRendererNames[GpuPathRenderers::kStencilAndCover] = "NV_path_rendering";
265         gPathRendererNames[GpuPathRenderers::kMSAA] = "Sample shading";
266         gPathRendererNames[GpuPathRenderers::kSmall] = "Small paths (cached sdf or alpha masks)";
267         gPathRendererNames[GpuPathRenderers::kCoverageCounting] = "Coverage counting";
268         gPathRendererNames[GpuPathRenderers::kTessellating] = "Tessellating";
269         gPathRendererNames[GpuPathRenderers::kDefault] = "Original Ganesh path renderer";
270         gPathRendererNames[GpuPathRenderers::kNone] = "Software masks";
271     });
272 
273     memset(fPaintTimes, 0, sizeof(fPaintTimes));
274     memset(fFlushTimes, 0, sizeof(fFlushTimes));
275     memset(fAnimateTimes, 0, sizeof(fAnimateTimes));
276 
277     SkDebugf("Command line arguments: ");
278     for (int i = 1; i < argc; ++i) {
279         SkDebugf("%s ", argv[i]);
280     }
281     SkDebugf("\n");
282 
283     SkCommandLineFlags::Parse(argc, argv);
284 #ifdef SK_BUILD_FOR_ANDROID
285     SetResourcePath("/data/local/tmp/resources");
286 #endif
287 
288     if (FLAGS_atrace) {
289         SkAssertResult(SkEventTracer::SetInstance(new SkATrace()));
290     }
291 
292     fBackendType = get_backend_type(FLAGS_backend[0]);
293     fWindow = Window::CreateNativeWindow(platformData);
294 
295     DisplayParams displayParams;
296     displayParams.fMSAASampleCount = FLAGS_msaa;
297     displayParams.fGrContextOptions.fEnableInstancedRendering = FLAGS_instancedRendering;
298     displayParams.fGrContextOptions.fGpuPathRenderers = CollectGpuPathRenderersFromFlags();
299     fWindow->setRequestedDisplayParams(displayParams);
300 
301     // register callbacks
302     fCommands.attach(fWindow);
303     fWindow->registerBackendCreatedFunc(on_backend_created_func, this);
304     fWindow->registerPaintFunc(on_paint_handler, this);
305     fWindow->registerTouchFunc(on_touch_handler, this);
306     fWindow->registerUIStateChangedFunc(on_ui_state_changed_handler, this);
307     fWindow->registerMouseFunc(on_mouse_handler, this);
308     fWindow->registerMouseWheelFunc(on_mouse_wheel_handler, this);
309     fWindow->registerKeyFunc(on_key_handler, this);
310     fWindow->registerCharFunc(on_char_handler, this);
311 
312     // add key-bindings
313     fCommands.addCommand(' ', "GUI", "Toggle Debug GUI", [this]() {
314         this->fShowImGuiDebugWindow = !this->fShowImGuiDebugWindow;
315         fWindow->inval();
316     });
317     fCommands.addCommand('g', "GUI", "Toggle GUI Demo", [this]() {
318         this->fShowImGuiTestWindow = !this->fShowImGuiTestWindow;
319         fWindow->inval();
320     });
321     fCommands.addCommand('z', "GUI", "Toggle zoom window", [this]() {
322         this->fShowZoomWindow = !this->fShowZoomWindow;
323         fWindow->inval();
324     });
325     fCommands.addCommand('s', "Overlays", "Toggle stats display", [this]() {
326         this->fDisplayStats = !this->fDisplayStats;
327         fWindow->inval();
328     });
329     fCommands.addCommand('c', "Modes", "Cycle color mode", [this]() {
330         switch (fColorMode) {
331             case ColorMode::kLegacy:
332                 this->setColorMode(ColorMode::kColorManagedSRGB8888_NonLinearBlending);
333                 break;
334             case ColorMode::kColorManagedSRGB8888_NonLinearBlending:
335                 this->setColorMode(ColorMode::kColorManagedSRGB8888);
336                 break;
337             case ColorMode::kColorManagedSRGB8888:
338                 this->setColorMode(ColorMode::kColorManagedLinearF16);
339                 break;
340             case ColorMode::kColorManagedLinearF16:
341                 this->setColorMode(ColorMode::kLegacy);
342                 break;
343         }
344     });
345     fCommands.addCommand(Window::Key::kRight, "Right", "Navigation", "Next slide", [this]() {
346         int previousSlide = fCurrentSlide;
347         fCurrentSlide++;
348         if (fCurrentSlide >= fSlides.count()) {
349             fCurrentSlide = 0;
350         }
351         this->setupCurrentSlide(previousSlide);
352     });
353     fCommands.addCommand(Window::Key::kLeft, "Left", "Navigation", "Previous slide", [this]() {
354         int previousSlide = fCurrentSlide;
355         fCurrentSlide--;
356         if (fCurrentSlide < 0) {
357             fCurrentSlide = fSlides.count() - 1;
358         }
359         this->setupCurrentSlide(previousSlide);
360     });
361     fCommands.addCommand(Window::Key::kUp, "Up", "Transform", "Zoom in", [this]() {
362         this->changeZoomLevel(1.f / 32.f);
363         fWindow->inval();
364     });
365     fCommands.addCommand(Window::Key::kDown, "Down", "Transform", "Zoom out", [this]() {
366         this->changeZoomLevel(-1.f / 32.f);
367         fWindow->inval();
368     });
369     fCommands.addCommand('d', "Modes", "Change rendering backend", [this]() {
370         sk_app::Window::BackendType newBackend = fBackendType;
371 #if defined(SK_BUILD_FOR_WIN) || defined(SK_BUILD_FOR_MAC)
372         if (sk_app::Window::kRaster_BackendType == fBackendType) {
373             newBackend = sk_app::Window::kNativeGL_BackendType;
374 #ifdef SK_VULKAN
375         } else if (sk_app::Window::kNativeGL_BackendType == fBackendType) {
376             newBackend = sk_app::Window::kVulkan_BackendType;
377 #endif
378         } else {
379             newBackend = sk_app::Window::kRaster_BackendType;
380         }
381 #elif defined(SK_BUILD_FOR_UNIX)
382         // Switching to and from Vulkan is problematic on Linux so disabled for now
383         if (sk_app::Window::kRaster_BackendType == fBackendType) {
384             newBackend = sk_app::Window::kNativeGL_BackendType;
385         } else if (sk_app::Window::kNativeGL_BackendType == fBackendType) {
386             newBackend = sk_app::Window::kRaster_BackendType;
387         }
388 #endif
389 
390         this->setBackend(newBackend);
391     });
392 
393     // set up slides
394     this->initSlides();
395     this->setStartupSlide();
396     if (FLAGS_list) {
397         this->listNames();
398     }
399 
400     fAnimTimer.run();
401 
402     // ImGui initialization:
403     ImGuiIO& io = ImGui::GetIO();
404     io.DisplaySize.x = static_cast<float>(fWindow->width());
405     io.DisplaySize.y = static_cast<float>(fWindow->height());
406 
407     // Keymap...
408     io.KeyMap[ImGuiKey_Tab] = (int)Window::Key::kTab;
409     io.KeyMap[ImGuiKey_LeftArrow] = (int)Window::Key::kLeft;
410     io.KeyMap[ImGuiKey_RightArrow] = (int)Window::Key::kRight;
411     io.KeyMap[ImGuiKey_UpArrow] = (int)Window::Key::kUp;
412     io.KeyMap[ImGuiKey_DownArrow] = (int)Window::Key::kDown;
413     io.KeyMap[ImGuiKey_PageUp] = (int)Window::Key::kPageUp;
414     io.KeyMap[ImGuiKey_PageDown] = (int)Window::Key::kPageDown;
415     io.KeyMap[ImGuiKey_Home] = (int)Window::Key::kHome;
416     io.KeyMap[ImGuiKey_End] = (int)Window::Key::kEnd;
417     io.KeyMap[ImGuiKey_Delete] = (int)Window::Key::kDelete;
418     io.KeyMap[ImGuiKey_Backspace] = (int)Window::Key::kBack;
419     io.KeyMap[ImGuiKey_Enter] = (int)Window::Key::kOK;
420     io.KeyMap[ImGuiKey_Escape] = (int)Window::Key::kEscape;
421     io.KeyMap[ImGuiKey_A] = (int)Window::Key::kA;
422     io.KeyMap[ImGuiKey_C] = (int)Window::Key::kC;
423     io.KeyMap[ImGuiKey_V] = (int)Window::Key::kV;
424     io.KeyMap[ImGuiKey_X] = (int)Window::Key::kX;
425     io.KeyMap[ImGuiKey_Y] = (int)Window::Key::kY;
426     io.KeyMap[ImGuiKey_Z] = (int)Window::Key::kZ;
427 
428     int w, h;
429     unsigned char* pixels;
430     io.Fonts->GetTexDataAsAlpha8(&pixels, &w, &h);
431     SkImageInfo info = SkImageInfo::MakeA8(w, h);
432     SkPixmap pmap(info, pixels, info.minRowBytes());
433     SkMatrix localMatrix = SkMatrix::MakeScale(1.0f / w, 1.0f / h);
434     auto fontImage = SkImage::MakeFromRaster(pmap, nullptr, nullptr);
435     auto fontShader = fontImage->makeShader(&localMatrix);
436     fImGuiFontPaint.setShader(fontShader);
437     fImGuiFontPaint.setColor(SK_ColorWHITE);
438     fImGuiFontPaint.setFilterQuality(kLow_SkFilterQuality);
439     io.Fonts->TexID = &fImGuiFontPaint;
440 
441     auto gamutImage = GetResourceAsImage("gamut.png");
442     if (gamutImage) {
443         fImGuiGamutPaint.setShader(gamutImage->makeShader());
444     }
445     fImGuiGamutPaint.setColor(SK_ColorWHITE);
446     fImGuiGamutPaint.setFilterQuality(kLow_SkFilterQuality);
447 
448     fWindow->attach(fBackendType);
449 }
450 
initSlides()451 void Viewer::initSlides() {
452     fAllSlideNames = Json::Value(Json::arrayValue);
453 
454     const skiagm::GMRegistry* gms(skiagm::GMRegistry::Head());
455     while (gms) {
456         std::unique_ptr<skiagm::GM> gm(gms->factory()(nullptr));
457 
458         if (!SkCommandLineFlags::ShouldSkip(FLAGS_match, gm->getName())) {
459             sk_sp<Slide> slide(new GMSlide(gm.release()));
460             fSlides.push_back(slide);
461         }
462 
463         gms = gms->next();
464     }
465 
466     // reverse array
467     for (int i = 0; i < fSlides.count()/2; ++i) {
468         sk_sp<Slide> temp = fSlides[i];
469         fSlides[i] = fSlides[fSlides.count() - i - 1];
470         fSlides[fSlides.count() - i - 1] = temp;
471     }
472 
473     // samples
474     const SkViewRegister* reg = SkViewRegister::Head();
475     while (reg) {
476         sk_sp<Slide> slide(new SampleSlide(reg->factory()));
477         if (!SkCommandLineFlags::ShouldSkip(FLAGS_match, slide->getName().c_str())) {
478             fSlides.push_back(slide);
479         }
480         reg = reg->next();
481     }
482 
483     // SKPs
484     for (int i = 0; i < FLAGS_skps.count(); i++) {
485         if (SkStrEndsWith(FLAGS_skps[i], ".skp")) {
486             if (SkCommandLineFlags::ShouldSkip(FLAGS_match, FLAGS_skps[i])) {
487                 continue;
488             }
489 
490             SkString path(FLAGS_skps[i]);
491             sk_sp<SKPSlide> slide(new SKPSlide(SkOSPath::Basename(path.c_str()), path));
492             if (slide) {
493                 fSlides.push_back(slide);
494             }
495         } else {
496             SkOSFile::Iter it(FLAGS_skps[i], ".skp");
497             SkString skpName;
498             while (it.next(&skpName)) {
499                 if (SkCommandLineFlags::ShouldSkip(FLAGS_match, skpName.c_str())) {
500                     continue;
501                 }
502 
503                 SkString path = SkOSPath::Join(FLAGS_skps[i], skpName.c_str());
504                 sk_sp<SKPSlide> slide(new SKPSlide(skpName, path));
505                 if (slide) {
506                     fSlides.push_back(slide);
507                 }
508             }
509         }
510     }
511 
512     // JPGs
513     for (int i = 0; i < FLAGS_jpgs.count(); i++) {
514         SkOSFile::Iter it(FLAGS_jpgs[i], ".jpg");
515         SkString jpgName;
516         while (it.next(&jpgName)) {
517             if (SkCommandLineFlags::ShouldSkip(FLAGS_match, jpgName.c_str())) {
518                 continue;
519             }
520 
521             SkString path = SkOSPath::Join(FLAGS_jpgs[i], jpgName.c_str());
522             sk_sp<ImageSlide> slide(new ImageSlide(jpgName, path));
523             if (slide) {
524                 fSlides.push_back(slide);
525             }
526         }
527     }
528 }
529 
530 
~Viewer()531 Viewer::~Viewer() {
532     fWindow->detach();
533     delete fWindow;
534 }
535 
updateTitle()536 void Viewer::updateTitle() {
537     if (!fWindow) {
538         return;
539     }
540     if (fWindow->sampleCount() < 0) {
541         return; // Surface hasn't been created yet.
542     }
543 
544     SkString title("Viewer: ");
545     title.append(fSlides[fCurrentSlide]->getName());
546 
547     switch (fColorMode) {
548         case ColorMode::kLegacy:
549             title.append(" Legacy 8888");
550             break;
551         case ColorMode::kColorManagedSRGB8888_NonLinearBlending:
552             title.append(" ColorManaged 8888 (Nonlinear blending)");
553             break;
554         case ColorMode::kColorManagedSRGB8888:
555             title.append(" ColorManaged 8888");
556             break;
557         case ColorMode::kColorManagedLinearF16:
558             title.append(" ColorManaged F16");
559             break;
560     }
561 
562     if (ColorMode::kLegacy != fColorMode) {
563         int curPrimaries = -1;
564         for (size_t i = 0; i < SK_ARRAY_COUNT(gNamedPrimaries); ++i) {
565             if (primaries_equal(*gNamedPrimaries[i].fPrimaries, fColorSpacePrimaries)) {
566                 curPrimaries = i;
567                 break;
568             }
569         }
570         title.appendf(" %s", curPrimaries >= 0 ? gNamedPrimaries[curPrimaries].fName : "Custom");
571     }
572 
573     title.append(" [");
574     title.append(kBackendTypeStrings[fBackendType]);
575     if (int msaa = fWindow->sampleCount()) {
576         title.appendf(" MSAA: %i", msaa);
577     }
578     title.append("]");
579 
580     GpuPathRenderers pr = fWindow->getRequestedDisplayParams().fGrContextOptions.fGpuPathRenderers;
581     if (GpuPathRenderers::kAll != pr) {
582         title.appendf(" [Path renderer: %s]", gPathRendererNames[pr].c_str());
583     }
584 
585     fWindow->setTitle(title.c_str());
586 }
587 
setStartupSlide()588 void Viewer::setStartupSlide() {
589 
590     if (!FLAGS_slide.isEmpty()) {
591         int count = fSlides.count();
592         for (int i = 0; i < count; i++) {
593             if (fSlides[i]->getName().equals(FLAGS_slide[0])) {
594                 fCurrentSlide = i;
595                 return;
596             }
597         }
598 
599         fprintf(stderr, "Unknown slide \"%s\"\n", FLAGS_slide[0]);
600         this->listNames();
601     }
602 
603     fCurrentSlide = 0;
604 }
605 
listNames()606 void Viewer::listNames() {
607     int count = fSlides.count();
608     SkDebugf("All Slides:\n");
609     for (int i = 0; i < count; i++) {
610         SkDebugf("    %s\n", fSlides[i]->getName().c_str());
611     }
612 }
613 
setupCurrentSlide(int previousSlide)614 void Viewer::setupCurrentSlide(int previousSlide) {
615     if (fCurrentSlide == previousSlide) {
616         return; // no change; do nothing
617     }
618     // prepare dimensions for image slides
619     fSlides[fCurrentSlide]->load(SkIntToScalar(fWindow->width()), SkIntToScalar(fWindow->height()));
620 
621     fGesture.reset();
622     fDefaultMatrix.reset();
623 
624     const SkISize slideSize = fSlides[fCurrentSlide]->getDimensions();
625     const SkRect slideBounds = SkRect::MakeIWH(slideSize.width(), slideSize.height());
626     const SkRect windowRect = SkRect::MakeIWH(fWindow->width(), fWindow->height());
627 
628     // Start with a matrix that scales the slide to the available screen space
629     if (fWindow->scaleContentToFit()) {
630         if (windowRect.width() > 0 && windowRect.height() > 0) {
631             fDefaultMatrix.setRectToRect(slideBounds, windowRect, SkMatrix::kStart_ScaleToFit);
632         }
633     }
634 
635     // Prevent the user from dragging content so far outside the window they can't find it again
636     fGesture.setTransLimit(slideBounds, windowRect, fDefaultMatrix);
637 
638     this->updateTitle();
639     this->updateUIState();
640     if (previousSlide >= 0) {
641         fSlides[previousSlide]->unload();
642     }
643     fWindow->inval();
644 }
645 
646 #define MAX_ZOOM_LEVEL  8
647 #define MIN_ZOOM_LEVEL  -8
648 
changeZoomLevel(float delta)649 void Viewer::changeZoomLevel(float delta) {
650     fZoomLevel += delta;
651     fZoomLevel = SkScalarPin(fZoomLevel, MIN_ZOOM_LEVEL, MAX_ZOOM_LEVEL);
652 }
653 
computeMatrix()654 SkMatrix Viewer::computeMatrix() {
655     SkMatrix m;
656 
657     SkScalar zoomScale = (fZoomLevel < 0) ? SK_Scalar1 / (SK_Scalar1 - fZoomLevel)
658                                           : SK_Scalar1 + fZoomLevel;
659     m = fGesture.localM();
660     m.preConcat(fGesture.globalM());
661     m.preConcat(fDefaultMatrix);
662     m.preScale(zoomScale, zoomScale);
663 
664     return m;
665 }
666 
setBackend(sk_app::Window::BackendType backendType)667 void Viewer::setBackend(sk_app::Window::BackendType backendType) {
668     fBackendType = backendType;
669 
670     fWindow->detach();
671 
672 #if defined(SK_BUILD_FOR_WIN) && defined(SK_VULKAN)
673     // Switching from OpenGL to Vulkan (or vice-versa on some systems) in the same window is
674     // problematic at this point on Windows, so we just delete the window and recreate it.
675     if (sk_app::Window::kVulkan_BackendType == fBackendType ||
676             sk_app::Window::kNativeGL_BackendType == fBackendType) {
677         DisplayParams params = fWindow->getRequestedDisplayParams();
678         delete fWindow;
679         fWindow = Window::CreateNativeWindow(nullptr);
680 
681         // re-register callbacks
682         fCommands.attach(fWindow);
683         fWindow->registerBackendCreatedFunc(on_backend_created_func, this);
684         fWindow->registerPaintFunc(on_paint_handler, this);
685         fWindow->registerTouchFunc(on_touch_handler, this);
686         fWindow->registerUIStateChangedFunc(on_ui_state_changed_handler, this);
687         fWindow->registerMouseFunc(on_mouse_handler, this);
688         fWindow->registerMouseWheelFunc(on_mouse_wheel_handler, this);
689         fWindow->registerKeyFunc(on_key_handler, this);
690         fWindow->registerCharFunc(on_char_handler, this);
691         // Don't allow the window to re-attach. If we're in MSAA mode, the params we grabbed above
692         // will still include our correct sample count. But the re-created fWindow will lose that
693         // information. On Windows, we need to re-create the window when changing sample count,
694         // so we'll incorrectly detect that situation, then re-initialize the window in GL mode,
695         // rendering this tear-down step pointless (and causing the Vulkan window context to fail
696         // as if we had never changed windows at all).
697         fWindow->setRequestedDisplayParams(params, false);
698     }
699 #endif
700 
701     fWindow->attach(fBackendType);
702 }
703 
setColorMode(ColorMode colorMode)704 void Viewer::setColorMode(ColorMode colorMode) {
705     fColorMode = colorMode;
706 
707     // When we're in color managed mode, we tag our window surface as sRGB. If we've switched into
708     // or out of legacy/nonlinear mode, we need to update our window configuration.
709     DisplayParams params = fWindow->getRequestedDisplayParams();
710     bool wasInLegacy = !SkToBool(params.fColorSpace);
711     bool wantLegacy = (ColorMode::kLegacy == fColorMode) ||
712                       (ColorMode::kColorManagedSRGB8888_NonLinearBlending == fColorMode);
713     if (wasInLegacy != wantLegacy) {
714         params.fColorSpace = wantLegacy ? nullptr : SkColorSpace::MakeSRGB();
715         fWindow->setRequestedDisplayParams(params);
716     }
717 
718     this->updateTitle();
719     fWindow->inval();
720 }
721 
drawSlide(SkCanvas * canvas)722 void Viewer::drawSlide(SkCanvas* canvas) {
723     SkAutoCanvasRestore autorestore(canvas, false);
724 
725     // By default, we render directly into the window's surface/canvas
726     SkCanvas* slideCanvas = canvas;
727     fLastImage.reset();
728 
729     // If we're in any of the color managed modes, construct the color space we're going to use
730     sk_sp<SkColorSpace> cs = nullptr;
731     if (ColorMode::kLegacy != fColorMode) {
732         auto transferFn = (ColorMode::kColorManagedLinearF16 == fColorMode)
733             ? SkColorSpace::kLinear_RenderTargetGamma : SkColorSpace::kSRGB_RenderTargetGamma;
734         SkMatrix44 toXYZ;
735         SkAssertResult(fColorSpacePrimaries.toXYZD50(&toXYZ));
736         cs = SkColorSpace::MakeRGB(transferFn, toXYZ);
737     }
738 
739     // If we're in F16, or we're zooming, or we're in color correct 8888 and the gamut isn't sRGB,
740     // we need to render offscreen
741     sk_sp<SkSurface> offscreenSurface = nullptr;
742     if (ColorMode::kColorManagedLinearF16 == fColorMode ||
743         fShowZoomWindow ||
744         (ColorMode::kColorManagedSRGB8888 == fColorMode &&
745          !primaries_equal(fColorSpacePrimaries, gSrgbPrimaries))) {
746 
747         SkColorType colorType = (ColorMode::kColorManagedLinearF16 == fColorMode)
748             ? kRGBA_F16_SkColorType : kN32_SkColorType;
749         // In nonlinear blending mode, we actually use a legacy off-screen canvas, and wrap it
750         // with a special canvas (below) that has the color space attached
751         sk_sp<SkColorSpace> offscreenColorSpace =
752             (ColorMode::kColorManagedSRGB8888_NonLinearBlending == fColorMode) ? nullptr : cs;
753         SkImageInfo info = SkImageInfo::Make(fWindow->width(), fWindow->height(), colorType,
754                                              kPremul_SkAlphaType, std::move(offscreenColorSpace));
755         offscreenSurface = canvas->makeSurface(info);
756         slideCanvas = offscreenSurface->getCanvas();
757     }
758 
759     std::unique_ptr<SkCanvas> xformCanvas = nullptr;
760     if (ColorMode::kColorManagedSRGB8888_NonLinearBlending == fColorMode) {
761         xformCanvas = SkCreateColorSpaceXformCanvas(slideCanvas, cs);
762         slideCanvas = xformCanvas.get();
763     }
764 
765     int count = slideCanvas->save();
766     slideCanvas->clear(SK_ColorWHITE);
767     slideCanvas->concat(computeMatrix());
768     // Time the painting logic of the slide
769     double startTime = SkTime::GetMSecs();
770     fSlides[fCurrentSlide]->draw(slideCanvas);
771     fPaintTimes[fCurrentMeasurement] = SkTime::GetMSecs() - startTime;
772     slideCanvas->restoreToCount(count);
773 
774     // Force a flush so we can time that, too
775     startTime = SkTime::GetMSecs();
776     slideCanvas->flush();
777     fFlushTimes[fCurrentMeasurement] = SkTime::GetMSecs() - startTime;
778 
779     // If we rendered offscreen, snap an image and push the results to the window's canvas
780     if (offscreenSurface) {
781         fLastImage = offscreenSurface->makeImageSnapshot();
782 
783         // Tag the image with the sRGB gamut, so no further color space conversion happens
784         sk_sp<SkColorSpace> srgb = (ColorMode::kColorManagedLinearF16 == fColorMode)
785             ? SkColorSpace::MakeSRGBLinear() : SkColorSpace::MakeSRGB();
786         auto retaggedImage = SkImageMakeRasterCopyAndAssignColorSpace(fLastImage.get(), srgb.get());
787         SkPaint paint;
788         paint.setBlendMode(SkBlendMode::kSrc);
789         canvas->drawImage(retaggedImage, 0, 0, &paint);
790     }
791 }
792 
onBackendCreated()793 void Viewer::onBackendCreated() {
794     this->updateTitle();
795     this->updateUIState();
796     this->setupCurrentSlide(-1);
797     fWindow->show();
798     fWindow->inval();
799 }
800 
onPaint(SkCanvas * canvas)801 void Viewer::onPaint(SkCanvas* canvas) {
802     // Update ImGui input
803     ImGuiIO& io = ImGui::GetIO();
804     io.DeltaTime = 1.0f / 60.0f;
805     io.DisplaySize.x = static_cast<float>(fWindow->width());
806     io.DisplaySize.y = static_cast<float>(fWindow->height());
807 
808     io.KeyAlt = io.KeysDown[static_cast<int>(Window::Key::kOption)];
809     io.KeyCtrl = io.KeysDown[static_cast<int>(Window::Key::kCtrl)];
810     io.KeyShift = io.KeysDown[static_cast<int>(Window::Key::kShift)];
811 
812     ImGui::NewFrame();
813 
814     drawSlide(canvas);
815 
816     // Advance our timing bookkeeping
817     fCurrentMeasurement = (fCurrentMeasurement + 1) & (kMeasurementCount - 1);
818     SkASSERT(fCurrentMeasurement < kMeasurementCount);
819 
820     // Draw any overlays or UI that we don't want timed
821     if (fDisplayStats) {
822         drawStats(canvas);
823     }
824     fCommands.drawHelp(canvas);
825 
826     drawImGui(canvas);
827 
828     // Update the FPS
829     updateUIState();
830 }
831 
onTouch(intptr_t owner,Window::InputState state,float x,float y)832 bool Viewer::onTouch(intptr_t owner, Window::InputState state, float x, float y) {
833     if (GestureDevice::kMouse == fGestureDevice) {
834         return false;
835     }
836     void* castedOwner = reinterpret_cast<void*>(owner);
837     switch (state) {
838         case Window::kUp_InputState: {
839             fGesture.touchEnd(castedOwner);
840             break;
841         }
842         case Window::kDown_InputState: {
843             fGesture.touchBegin(castedOwner, x, y);
844             break;
845         }
846         case Window::kMove_InputState: {
847             fGesture.touchMoved(castedOwner, x, y);
848             break;
849         }
850     }
851     fGestureDevice = fGesture.isBeingTouched() ? GestureDevice::kTouch : GestureDevice::kNone;
852     fWindow->inval();
853     return true;
854 }
855 
onMouse(float x,float y,Window::InputState state,uint32_t modifiers)856 bool Viewer::onMouse(float x, float y, Window::InputState state, uint32_t modifiers) {
857     if (GestureDevice::kTouch == fGestureDevice) {
858         return false;
859     }
860     switch (state) {
861         case Window::kUp_InputState: {
862             fGesture.touchEnd(nullptr);
863             break;
864         }
865         case Window::kDown_InputState: {
866             fGesture.touchBegin(nullptr, x, y);
867             break;
868         }
869         case Window::kMove_InputState: {
870             fGesture.touchMoved(nullptr, x, y);
871             break;
872         }
873     }
874     fGestureDevice = fGesture.isBeingTouched() ? GestureDevice::kMouse : GestureDevice::kNone;
875     fWindow->inval();
876     return true;
877 }
878 
drawStats(SkCanvas * canvas)879 void Viewer::drawStats(SkCanvas* canvas) {
880     static const float kPixelPerMS = 2.0f;
881     static const int kDisplayWidth = 130;
882     static const int kDisplayHeight = 100;
883     static const int kDisplayPadding = 10;
884     static const int kGraphPadding = 3;
885     static const SkScalar kBaseMS = 1000.f / 60.f;  // ms/frame to hit 60 fps
886 
887     SkISize canvasSize = canvas->getBaseLayerSize();
888     SkRect rect = SkRect::MakeXYWH(SkIntToScalar(canvasSize.fWidth-kDisplayWidth-kDisplayPadding),
889                                    SkIntToScalar(kDisplayPadding),
890                                    SkIntToScalar(kDisplayWidth), SkIntToScalar(kDisplayHeight));
891     SkPaint paint;
892     canvas->save();
893 
894     canvas->clipRect(rect);
895     paint.setColor(SK_ColorBLACK);
896     canvas->drawRect(rect, paint);
897     // draw the 16ms line
898     paint.setColor(SK_ColorLTGRAY);
899     canvas->drawLine(rect.fLeft, rect.fBottom - kBaseMS*kPixelPerMS,
900                      rect.fRight, rect.fBottom - kBaseMS*kPixelPerMS, paint);
901     paint.setColor(SK_ColorRED);
902     paint.setStyle(SkPaint::kStroke_Style);
903     canvas->drawRect(rect, paint);
904 
905     int x = SkScalarTruncToInt(rect.fLeft) + kGraphPadding;
906     const int xStep = 2;
907     int i = fCurrentMeasurement;
908     do {
909         // Round to nearest values
910         int animateHeight = (int)(fAnimateTimes[i] * kPixelPerMS + 0.5);
911         int paintHeight = (int)(fPaintTimes[i] * kPixelPerMS + 0.5);
912         int flushHeight = (int)(fFlushTimes[i] * kPixelPerMS + 0.5);
913         int startY = SkScalarTruncToInt(rect.fBottom);
914         int endY = startY - flushHeight;
915         paint.setColor(SK_ColorRED);
916         canvas->drawLine(SkIntToScalar(x), SkIntToScalar(startY),
917                          SkIntToScalar(x), SkIntToScalar(endY), paint);
918         startY = endY;
919         endY = startY - paintHeight;
920         paint.setColor(SK_ColorGREEN);
921         canvas->drawLine(SkIntToScalar(x), SkIntToScalar(startY),
922                          SkIntToScalar(x), SkIntToScalar(endY), paint);
923         startY = endY;
924         endY = startY - animateHeight;
925         paint.setColor(SK_ColorMAGENTA);
926         canvas->drawLine(SkIntToScalar(x), SkIntToScalar(startY),
927                          SkIntToScalar(x), SkIntToScalar(endY), paint);
928         i++;
929         i &= (kMeasurementCount - 1);  // fast mod
930         x += xStep;
931     } while (i != fCurrentMeasurement);
932 
933     canvas->restore();
934 }
935 
ImGui_DragPrimary(const char * label,float * x,float * y,const ImVec2 & pos,const ImVec2 & size)936 static ImVec2 ImGui_DragPrimary(const char* label, float* x, float* y,
937                                 const ImVec2& pos, const ImVec2& size) {
938     // Transform primaries ([0, 0] - [0.8, 0.9]) to screen coords (including Y-flip)
939     ImVec2 center(pos.x + (*x / 0.8f) * size.x, pos.y + (1.0f - (*y / 0.9f)) * size.y);
940 
941     // Invisible 10x10 button
942     ImGui::SetCursorScreenPos(ImVec2(center.x - 5, center.y - 5));
943     ImGui::InvisibleButton(label, ImVec2(10, 10));
944 
945     if (ImGui::IsItemActive() && ImGui::IsMouseDragging()) {
946         ImGuiIO& io = ImGui::GetIO();
947         // Normalized mouse position, relative to our gamut box
948         ImVec2 mousePosXY((io.MousePos.x - pos.x) / size.x, (io.MousePos.y - pos.y) / size.y);
949         // Clamp to edge of box, convert back to primary scale
950         *x = SkTPin(mousePosXY.x, 0.0f, 1.0f) * 0.8f;
951         *y = SkTPin(1 - mousePosXY.y, 0.0f, 1.0f) * 0.9f;
952     }
953 
954     if (ImGui::IsItemHovered()) {
955         ImGui::SetTooltip("x: %.3f\ny: %.3f", *x, *y);
956     }
957 
958     // Return screen coordinates for the caller. We could just return center here, but we'd have
959     // one frame of lag during drag.
960     return ImVec2(pos.x + (*x / 0.8f) * size.x, pos.y + (1.0f - (*y / 0.9f)) * size.y);
961 }
962 
ImGui_Primaries(SkColorSpacePrimaries * primaries,SkPaint * gamutPaint)963 static void ImGui_Primaries(SkColorSpacePrimaries* primaries, SkPaint* gamutPaint) {
964     ImDrawList* drawList = ImGui::GetWindowDrawList();
965 
966     // The gamut image covers a (0.8 x 0.9) shaped region, so fit our image/canvas to the available
967     // width, and scale the height to maintain aspect ratio.
968     float canvasWidth = SkTMax(ImGui::GetContentRegionAvailWidth(), 50.0f);
969     ImVec2 size = ImVec2(canvasWidth, canvasWidth * (0.9f / 0.8f));
970     ImVec2 pos = ImGui::GetCursorScreenPos();
971 
972     // Background image. Only draw a subset of the image, to avoid the regions less than zero.
973     // Simplifes re-mapping math, clipping behavior, and increases resolution in the useful area.
974     // Magic numbers are pixel locations of the origin and upper-right corner.
975     drawList->AddImage(gamutPaint, pos, ImVec2(pos.x + size.x, pos.y + size.y),
976                        ImVec2(242, 61), ImVec2(1897, 1922));
977     ImVec2 endPos = ImGui::GetCursorPos();
978 
979     // Primary markers
980     ImVec2 r = ImGui_DragPrimary("R", &primaries->fRX, &primaries->fRY, pos, size);
981     ImVec2 g = ImGui_DragPrimary("G", &primaries->fGX, &primaries->fGY, pos, size);
982     ImVec2 b = ImGui_DragPrimary("B", &primaries->fBX, &primaries->fBY, pos, size);
983     ImVec2 w = ImGui_DragPrimary("W", &primaries->fWX, &primaries->fWY, pos, size);
984 
985     // Gamut triangle
986     drawList->AddCircle(r, 5.0f, 0xFF000040);
987     drawList->AddCircle(g, 5.0f, 0xFF004000);
988     drawList->AddCircle(b, 5.0f, 0xFF400000);
989     drawList->AddCircle(w, 5.0f, 0xFFFFFFFF);
990     drawList->AddTriangle(r, g, b, 0xFFFFFFFF);
991 
992     // Re-position cursor immediate after the diagram for subsequent controls
993     ImGui::SetCursorPos(endPos);
994 }
995 
drawImGui(SkCanvas * canvas)996 void Viewer::drawImGui(SkCanvas* canvas) {
997     // Support drawing the ImGui demo window. Superfluous, but gives a good idea of what's possible
998     if (fShowImGuiTestWindow) {
999         ImGui::ShowTestWindow(&fShowImGuiTestWindow);
1000     }
1001 
1002     if (fShowImGuiDebugWindow) {
1003         // We have some dynamic content that sizes to fill available size. If the scroll bar isn't
1004         // always visible, we can end up in a layout feedback loop.
1005         ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiSetCond_FirstUseEver);
1006         DisplayParams params = fWindow->getRequestedDisplayParams();
1007         bool paramsChanged = false;
1008         if (ImGui::Begin("Tools", &fShowImGuiDebugWindow,
1009                          ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
1010             if (ImGui::CollapsingHeader("Backend")) {
1011                 int newBackend = static_cast<int>(fBackendType);
1012                 ImGui::RadioButton("Raster", &newBackend, sk_app::Window::kRaster_BackendType);
1013                 ImGui::SameLine();
1014                 ImGui::RadioButton("OpenGL", &newBackend, sk_app::Window::kNativeGL_BackendType);
1015 #if defined(SK_VULKAN)
1016                 ImGui::SameLine();
1017                 ImGui::RadioButton("Vulkan", &newBackend, sk_app::Window::kVulkan_BackendType);
1018 #endif
1019                 if (newBackend != fBackendType) {
1020                     fDeferredActions.push_back([=]() {
1021                         this->setBackend(static_cast<sk_app::Window::BackendType>(newBackend));
1022                     });
1023                 }
1024 
1025                 const GrContext* ctx = fWindow->getGrContext();
1026                 bool* inst = &params.fGrContextOptions.fEnableInstancedRendering;
1027                 if (ctx && ImGui::Checkbox("Instanced Rendering", inst)) {
1028                     paramsChanged = true;
1029                 }
1030                 bool* wire = &params.fGrContextOptions.fWireframeMode;
1031                 if (ctx && ImGui::Checkbox("Wireframe Mode", wire)) {
1032                     paramsChanged = true;
1033                 }
1034 
1035                 if (ctx) {
1036                     int sampleCount = fWindow->sampleCount();
1037                     ImGui::Text("MSAA: "); ImGui::SameLine();
1038                     ImGui::RadioButton("0", &sampleCount, 0); ImGui::SameLine();
1039                     ImGui::RadioButton("4", &sampleCount, 4); ImGui::SameLine();
1040                     ImGui::RadioButton("8", &sampleCount, 8); ImGui::SameLine();
1041                     ImGui::RadioButton("16", &sampleCount, 16);
1042 
1043                     if (sampleCount != params.fMSAASampleCount) {
1044                         params.fMSAASampleCount = sampleCount;
1045                         paramsChanged = true;
1046                     }
1047                 }
1048 
1049                 if (ImGui::TreeNode("Path Renderers")) {
1050                     GpuPathRenderers prevPr = params.fGrContextOptions.fGpuPathRenderers;
1051                     auto prButton = [&](GpuPathRenderers x) {
1052                         if (ImGui::RadioButton(gPathRendererNames[x].c_str(), prevPr == x)) {
1053                             if (x != params.fGrContextOptions.fGpuPathRenderers) {
1054                                 params.fGrContextOptions.fGpuPathRenderers = x;
1055                                 paramsChanged = true;
1056                             }
1057                         }
1058                     };
1059 
1060                     if (!ctx) {
1061                         ImGui::RadioButton("Software", true);
1062                     } else if (fWindow->sampleCount()) {
1063                         prButton(GpuPathRenderers::kAll);
1064                         if (ctx->caps()->shaderCaps()->pathRenderingSupport()) {
1065                             prButton(GpuPathRenderers::kStencilAndCover);
1066                         }
1067                         if (ctx->caps()->sampleShadingSupport()) {
1068                             prButton(GpuPathRenderers::kMSAA);
1069                         }
1070                         prButton(GpuPathRenderers::kTessellating);
1071                         prButton(GpuPathRenderers::kDefault);
1072                         prButton(GpuPathRenderers::kNone);
1073                     } else {
1074                         prButton(GpuPathRenderers::kAll);
1075                         if (GrCoverageCountingPathRenderer::IsSupported(*ctx->caps())) {
1076                             prButton(GpuPathRenderers::kCoverageCounting);
1077                         }
1078                         prButton(GpuPathRenderers::kSmall);
1079                         prButton(GpuPathRenderers::kTessellating);
1080                         prButton(GpuPathRenderers::kNone);
1081                     }
1082                     ImGui::TreePop();
1083                 }
1084             }
1085 
1086             if (ImGui::CollapsingHeader("Slide")) {
1087                 static ImGuiTextFilter filter;
1088                 filter.Draw();
1089                 int previousSlide = fCurrentSlide;
1090                 fCurrentSlide = 0;
1091                 for (auto slide : fSlides) {
1092                     if (filter.PassFilter(slide->getName().c_str())) {
1093                         ImGui::BulletText("%s", slide->getName().c_str());
1094                         if (ImGui::IsItemClicked()) {
1095                             setupCurrentSlide(previousSlide);
1096                             break;
1097                         }
1098                     }
1099                     ++fCurrentSlide;
1100                 }
1101                 if (fCurrentSlide >= fSlides.count()) {
1102                     fCurrentSlide = previousSlide;
1103                 }
1104             }
1105 
1106             if (ImGui::CollapsingHeader("Color Mode")) {
1107                 ColorMode newMode = fColorMode;
1108                 auto cmButton = [&](ColorMode mode, const char* label) {
1109                     if (ImGui::RadioButton(label, mode == fColorMode)) {
1110                         newMode = mode;
1111                     }
1112                 };
1113 
1114                 cmButton(ColorMode::kLegacy, "Legacy 8888");
1115                 cmButton(ColorMode::kColorManagedSRGB8888_NonLinearBlending,
1116                          "Color Managed 8888 (Nonlinear blending)");
1117                 cmButton(ColorMode::kColorManagedSRGB8888, "Color Managed 8888");
1118                 cmButton(ColorMode::kColorManagedLinearF16, "Color Managed F16");
1119 
1120                 if (newMode != fColorMode) {
1121                     // It isn't safe to switch color mode now (in the middle of painting). We might
1122                     // tear down the back-end, etc... Defer this change until the next onIdle.
1123                     fDeferredActions.push_back([=]() {
1124                         this->setColorMode(newMode);
1125                     });
1126                 }
1127 
1128                 // Pick from common gamuts:
1129                 int primariesIdx = 4; // Default: Custom
1130                 for (size_t i = 0; i < SK_ARRAY_COUNT(gNamedPrimaries); ++i) {
1131                     if (primaries_equal(*gNamedPrimaries[i].fPrimaries, fColorSpacePrimaries)) {
1132                         primariesIdx = i;
1133                         break;
1134                     }
1135                 }
1136 
1137                 if (ImGui::Combo("Primaries", &primariesIdx,
1138                                  "sRGB\0AdobeRGB\0P3\0Rec. 2020\0Custom\0\0")) {
1139                     if (primariesIdx >= 0 && primariesIdx <= 3) {
1140                         fColorSpacePrimaries = *gNamedPrimaries[primariesIdx].fPrimaries;
1141                     }
1142                 }
1143 
1144                 // Allow direct editing of gamut
1145                 ImGui_Primaries(&fColorSpacePrimaries, &fImGuiGamutPaint);
1146             }
1147         }
1148         if (paramsChanged) {
1149             fDeferredActions.push_back([=]() {
1150                 fWindow->setRequestedDisplayParams(params);
1151                 fWindow->inval();
1152                 this->updateTitle();
1153             });
1154         }
1155         ImGui::End();
1156     }
1157 
1158     SkPaint zoomImagePaint;
1159     if (fShowZoomWindow && fLastImage) {
1160         if (ImGui::Begin("Zoom", &fShowZoomWindow, ImVec2(200, 200))) {
1161             static int zoomFactor = 4;
1162             ImGui::SliderInt("Scale", &zoomFactor, 1, 16);
1163 
1164             zoomImagePaint.setShader(fLastImage->makeShader());
1165             zoomImagePaint.setColor(SK_ColorWHITE);
1166 
1167             // Zoom by shrinking the corner UVs towards the mouse cursor
1168             ImVec2 mousePos = ImGui::GetMousePos();
1169             ImVec2 avail = ImGui::GetContentRegionAvail();
1170 
1171             ImVec2 zoomHalfExtents = ImVec2((avail.x * 0.5f) / zoomFactor,
1172                                             (avail.y * 0.5f) / zoomFactor);
1173             ImGui::Image(&zoomImagePaint, avail,
1174                          ImVec2(mousePos.x - zoomHalfExtents.x, mousePos.y - zoomHalfExtents.y),
1175                          ImVec2(mousePos.x + zoomHalfExtents.x, mousePos.y + zoomHalfExtents.y));
1176         }
1177 
1178         ImGui::End();
1179     }
1180 
1181     // This causes ImGui to rebuild vertex/index data based on all immediate-mode commands
1182     // (widgets, etc...) that have been issued
1183     ImGui::Render();
1184 
1185     // Then we fetch the most recent data, and convert it so we can render with Skia
1186     const ImDrawData* drawData = ImGui::GetDrawData();
1187     SkTDArray<SkPoint> pos;
1188     SkTDArray<SkPoint> uv;
1189     SkTDArray<SkColor> color;
1190 
1191     for (int i = 0; i < drawData->CmdListsCount; ++i) {
1192         const ImDrawList* drawList = drawData->CmdLists[i];
1193 
1194         // De-interleave all vertex data (sigh), convert to Skia types
1195         pos.rewind(); uv.rewind(); color.rewind();
1196         for (int i = 0; i < drawList->VtxBuffer.size(); ++i) {
1197             const ImDrawVert& vert = drawList->VtxBuffer[i];
1198             pos.push(SkPoint::Make(vert.pos.x, vert.pos.y));
1199             uv.push(SkPoint::Make(vert.uv.x, vert.uv.y));
1200             color.push(vert.col);
1201         }
1202         // ImGui colors are RGBA
1203         SkSwapRB(color.begin(), color.begin(), color.count());
1204 
1205         int indexOffset = 0;
1206 
1207         // Draw everything with canvas.drawVertices...
1208         for (int j = 0; j < drawList->CmdBuffer.size(); ++j) {
1209             const ImDrawCmd* drawCmd = &drawList->CmdBuffer[j];
1210 
1211             // TODO: Find min/max index for each draw, so we know how many vertices (sigh)
1212             if (drawCmd->UserCallback) {
1213                 drawCmd->UserCallback(drawList, drawCmd);
1214             } else {
1215                 SkPaint* paint = static_cast<SkPaint*>(drawCmd->TextureId);
1216                 SkASSERT(paint);
1217 
1218                 canvas->save();
1219                 canvas->clipRect(SkRect::MakeLTRB(drawCmd->ClipRect.x, drawCmd->ClipRect.y,
1220                                                   drawCmd->ClipRect.z, drawCmd->ClipRect.w));
1221                 canvas->drawVertices(SkVertices::MakeCopy(SkVertices::kTriangles_VertexMode,
1222                                                           drawList->VtxBuffer.size(), pos.begin(),
1223                                                           uv.begin(), color.begin(),
1224                                                           drawCmd->ElemCount,
1225                                                           drawList->IdxBuffer.begin() + indexOffset),
1226                                      SkBlendMode::kModulate, *paint);
1227                 indexOffset += drawCmd->ElemCount;
1228                 canvas->restore();
1229             }
1230         }
1231     }
1232 }
1233 
onIdle()1234 void Viewer::onIdle() {
1235     for (int i = 0; i < fDeferredActions.count(); ++i) {
1236         fDeferredActions[i]();
1237     }
1238     fDeferredActions.reset();
1239 
1240     double startTime = SkTime::GetMSecs();
1241     fAnimTimer.updateTime();
1242     bool animateWantsInval = fSlides[fCurrentSlide]->animate(fAnimTimer);
1243     fAnimateTimes[fCurrentMeasurement] = SkTime::GetMSecs() - startTime;
1244 
1245     ImGuiIO& io = ImGui::GetIO();
1246     if (animateWantsInval || fDisplayStats || fRefresh || io.MetricsActiveWindows) {
1247         fWindow->inval();
1248     }
1249 }
1250 
updateUIState()1251 void Viewer::updateUIState() {
1252     if (!fWindow) {
1253         return;
1254     }
1255     if (fWindow->sampleCount() < 0) {
1256         return; // Surface hasn't been created yet.
1257     }
1258 
1259     // Slide state
1260     Json::Value slideState(Json::objectValue);
1261     slideState[kName] = kSlideStateName;
1262     slideState[kValue] = fSlides[fCurrentSlide]->getName().c_str();
1263     if (fAllSlideNames.size() == 0) {
1264         for(auto slide : fSlides) {
1265             fAllSlideNames.append(Json::Value(slide->getName().c_str()));
1266         }
1267     }
1268     slideState[kOptions] = fAllSlideNames;
1269 
1270     // Backend state
1271     Json::Value backendState(Json::objectValue);
1272     backendState[kName] = kBackendStateName;
1273     backendState[kValue] = kBackendTypeStrings[fBackendType];
1274     backendState[kOptions] = Json::Value(Json::arrayValue);
1275     for (auto str : kBackendTypeStrings) {
1276         backendState[kOptions].append(Json::Value(str));
1277     }
1278 
1279     // MSAA state
1280     Json::Value msaaState(Json::objectValue);
1281     msaaState[kName] = kMSAAStateName;
1282     msaaState[kValue] = fWindow->sampleCount();
1283     msaaState[kOptions] = Json::Value(Json::arrayValue);
1284     if (sk_app::Window::kRaster_BackendType == fBackendType) {
1285         msaaState[kOptions].append(Json::Value(0));
1286     } else {
1287         for (int msaa : {0, 4, 8, 16}) {
1288             msaaState[kOptions].append(Json::Value(msaa));
1289         }
1290     }
1291 
1292     // Path renderer state
1293     GpuPathRenderers pr = fWindow->getRequestedDisplayParams().fGrContextOptions.fGpuPathRenderers;
1294     Json::Value prState(Json::objectValue);
1295     prState[kName] = kPathRendererStateName;
1296     prState[kValue] = gPathRendererNames[pr];
1297     prState[kOptions] = Json::Value(Json::arrayValue);
1298     const GrContext* ctx = fWindow->getGrContext();
1299     if (!ctx) {
1300         prState[kOptions].append("Software");
1301     } else if (fWindow->sampleCount()) {
1302         prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kAll]);
1303         if (ctx->caps()->shaderCaps()->pathRenderingSupport()) {
1304             prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kStencilAndCover]);
1305         }
1306         if (ctx->caps()->sampleShadingSupport()) {
1307             prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kMSAA]);
1308         }
1309         prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kTessellating]);
1310         prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kDefault]);
1311         prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kNone]);
1312     } else {
1313         prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kAll]);
1314         if (GrCoverageCountingPathRenderer::IsSupported(*ctx->caps())) {
1315             prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kCoverageCounting]);
1316         }
1317         prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kSmall]);
1318         prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kTessellating]);
1319         prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kNone]);
1320     }
1321 
1322     // Instanced rendering state
1323     Json::Value instState(Json::objectValue);
1324     instState[kName] = kInstancedRenderingStateName;
1325     if (ctx) {
1326         if (fWindow->getRequestedDisplayParams().fGrContextOptions.fEnableInstancedRendering) {
1327             instState[kValue] = kON;
1328         } else {
1329             instState[kValue] = kOFF;
1330         }
1331         instState[kOptions] = Json::Value(Json::arrayValue);
1332         instState[kOptions].append(kOFF);
1333         instState[kOptions].append(kON);
1334     }
1335 
1336     // Softkey state
1337     Json::Value softkeyState(Json::objectValue);
1338     softkeyState[kName] = kSoftkeyStateName;
1339     softkeyState[kValue] = kSoftkeyHint;
1340     softkeyState[kOptions] = Json::Value(Json::arrayValue);
1341     softkeyState[kOptions].append(kSoftkeyHint);
1342     for (const auto& softkey : fCommands.getCommandsAsSoftkeys()) {
1343         softkeyState[kOptions].append(Json::Value(softkey.c_str()));
1344     }
1345 
1346     // FPS state
1347     Json::Value fpsState(Json::objectValue);
1348     fpsState[kName] = kFpsStateName;
1349     int idx = (fCurrentMeasurement + (kMeasurementCount - 1)) & (kMeasurementCount - 1);
1350     fpsState[kValue] = SkStringPrintf("%8.3lf ms\n\nA %8.3lf\nP %8.3lf\nF%8.3lf",
1351                                       fAnimateTimes[idx] + fPaintTimes[idx] + fFlushTimes[idx],
1352                                       fAnimateTimes[idx],
1353                                       fPaintTimes[idx],
1354                                       fFlushTimes[idx]).c_str();
1355     fpsState[kOptions] = Json::Value(Json::arrayValue);
1356 
1357     Json::Value state(Json::arrayValue);
1358     state.append(slideState);
1359     state.append(backendState);
1360     state.append(msaaState);
1361     state.append(prState);
1362     state.append(instState);
1363     state.append(softkeyState);
1364     state.append(fpsState);
1365 
1366     fWindow->setUIState(state);
1367 }
1368 
onUIStateChanged(const SkString & stateName,const SkString & stateValue)1369 void Viewer::onUIStateChanged(const SkString& stateName, const SkString& stateValue) {
1370     // For those who will add more features to handle the state change in this function:
1371     // After the change, please call updateUIState no notify the frontend (e.g., Android app).
1372     // For example, after slide change, updateUIState is called inside setupCurrentSlide;
1373     // after backend change, updateUIState is called in this function.
1374     if (stateName.equals(kSlideStateName)) {
1375         int previousSlide = fCurrentSlide;
1376         fCurrentSlide = 0;
1377         for(auto slide : fSlides) {
1378             if (slide->getName().equals(stateValue)) {
1379                 this->setupCurrentSlide(previousSlide);
1380                 break;
1381             }
1382             fCurrentSlide++;
1383         }
1384         if (fCurrentSlide >= fSlides.count()) {
1385             fCurrentSlide = previousSlide;
1386             SkDebugf("Slide not found: %s", stateValue.c_str());
1387         }
1388     } else if (stateName.equals(kBackendStateName)) {
1389         for (int i = 0; i < sk_app::Window::kBackendTypeCount; i++) {
1390             if (stateValue.equals(kBackendTypeStrings[i])) {
1391                 if (fBackendType != i) {
1392                     fBackendType = (sk_app::Window::BackendType)i;
1393                     fWindow->detach();
1394                     fWindow->attach(fBackendType);
1395                 }
1396                 break;
1397             }
1398         }
1399     } else if (stateName.equals(kMSAAStateName)) {
1400         DisplayParams params = fWindow->getRequestedDisplayParams();
1401         int sampleCount = atoi(stateValue.c_str());
1402         if (sampleCount != params.fMSAASampleCount) {
1403             params.fMSAASampleCount = sampleCount;
1404             fWindow->setRequestedDisplayParams(params);
1405             fWindow->inval();
1406             this->updateTitle();
1407             this->updateUIState();
1408         }
1409     } else if (stateName.equals(kPathRendererStateName)) {
1410         DisplayParams params = fWindow->getRequestedDisplayParams();
1411         for (const auto& pair : gPathRendererNames) {
1412             if (pair.second == stateValue.c_str()) {
1413                 if (params.fGrContextOptions.fGpuPathRenderers != pair.first) {
1414                     params.fGrContextOptions.fGpuPathRenderers = pair.first;
1415                     fWindow->setRequestedDisplayParams(params);
1416                     fWindow->inval();
1417                     this->updateTitle();
1418                     this->updateUIState();
1419                 }
1420                 break;
1421             }
1422         }
1423     } else if (stateName.equals(kInstancedRenderingStateName)) {
1424         DisplayParams params = fWindow->getRequestedDisplayParams();
1425         bool value = !strcmp(stateValue.c_str(), kON);
1426         if (params.fGrContextOptions.fEnableInstancedRendering != value) {
1427             params.fGrContextOptions.fEnableInstancedRendering = value;
1428             fWindow->setRequestedDisplayParams(params);
1429             fWindow->inval();
1430             this->updateTitle();
1431             this->updateUIState();
1432         }
1433     } else if (stateName.equals(kSoftkeyStateName)) {
1434         if (!stateValue.equals(kSoftkeyHint)) {
1435             fCommands.onSoftkey(stateValue);
1436             this->updateUIState(); // This is still needed to reset the value to kSoftkeyHint
1437         }
1438     } else if (stateName.equals(kRefreshStateName)) {
1439         // This state is actually NOT in the UI state.
1440         // We use this to allow Android to quickly set bool fRefresh.
1441         fRefresh = stateValue.equals(kON);
1442     } else {
1443         SkDebugf("Unknown stateName: %s", stateName.c_str());
1444     }
1445 }
1446 
onKey(sk_app::Window::Key key,sk_app::Window::InputState state,uint32_t modifiers)1447 bool Viewer::onKey(sk_app::Window::Key key, sk_app::Window::InputState state, uint32_t modifiers) {
1448     return fCommands.onKey(key, state, modifiers);
1449 }
1450 
onChar(SkUnichar c,uint32_t modifiers)1451 bool Viewer::onChar(SkUnichar c, uint32_t modifiers) {
1452     if (fSlides[fCurrentSlide]->onChar(c)) {
1453         fWindow->inval();
1454         return true;
1455     }
1456 
1457     return fCommands.onChar(c, modifiers);
1458 }
1459