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 = ¶ms.fGrContextOptions.fEnableInstancedRendering;
1027 if (ctx && ImGui::Checkbox("Instanced Rendering", inst)) {
1028 paramsChanged = true;
1029 }
1030 bool* wire = ¶ms.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