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