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 "tools/viewer/Viewer.h"
9
10 #include "include/core/SkCanvas.h"
11 #include "include/core/SkData.h"
12 #include "include/core/SkGraphics.h"
13 #include "include/core/SkPictureRecorder.h"
14 #include "include/core/SkStream.h"
15 #include "include/core/SkSurface.h"
16 #include "include/gpu/GrDirectContext.h"
17 #include "include/private/SkTPin.h"
18 #include "include/private/SkTo.h"
19 #include "include/utils/SkPaintFilterCanvas.h"
20 #include "src/core/SkColorSpacePriv.h"
21 #include "src/core/SkImagePriv.h"
22 #include "src/core/SkMD5.h"
23 #include "src/core/SkOSFile.h"
24 #include "src/core/SkReadBuffer.h"
25 #include "src/core/SkScan.h"
26 #include "src/core/SkSurfacePriv.h"
27 #include "src/core/SkTSort.h"
28 #include "src/core/SkTaskGroup.h"
29 #include "src/core/SkTextBlobPriv.h"
30 #include "src/core/SkVMBlitter.h"
31 #include "src/gpu/GrDirectContextPriv.h"
32 #include "src/gpu/GrGpu.h"
33 #include "src/gpu/GrPersistentCacheUtils.h"
34 #include "src/gpu/GrShaderUtils.h"
35 #include "src/image/SkImage_Base.h"
36 #include "src/sksl/SkSLCompiler.h"
37 #include "src/utils/SkJSONWriter.h"
38 #include "src/utils/SkOSPath.h"
39 #include "tools/Resources.h"
40 #include "tools/RuntimeBlendUtils.h"
41 #include "tools/ToolUtils.h"
42 #include "tools/flags/CommandLineFlags.h"
43 #include "tools/flags/CommonFlags.h"
44 #include "tools/trace/EventTracingPriv.h"
45 #include "tools/viewer/BisectSlide.h"
46 #include "tools/viewer/GMSlide.h"
47 #include "tools/viewer/ImageSlide.h"
48 #include "tools/viewer/MSKPSlide.h"
49 #include "tools/viewer/ParticlesSlide.h"
50 #include "tools/viewer/SKPSlide.h"
51 #include "tools/viewer/SampleSlide.h"
52 #include "tools/viewer/SkSLSlide.h"
53 #include "tools/viewer/SlideDir.h"
54 #include "tools/viewer/SvgSlide.h"
55
56 #if SK_GPU_V1
57 #include "src/gpu/ops/AtlasPathRenderer.h"
58 #include "src/gpu/ops/TessellationPathRenderer.h"
59 #endif
60
61 #include <cstdlib>
62 #include <map>
63
64 #include "imgui.h"
65 #include "misc/cpp/imgui_stdlib.h" // For ImGui support of std::string
66
67 #ifdef SK_VULKAN
68 #include "spirv-tools/libspirv.hpp"
69 #endif
70
71 #if defined(SK_ENABLE_SKOTTIE)
72 #include "tools/viewer/SkottieSlide.h"
73 #endif
74 #if defined(SK_ENABLE_SKRIVE)
75 #include "tools/viewer/SkRiveSlide.h"
76 #endif
77
78 class CapturingShaderErrorHandler : public GrContextOptions::ShaderErrorHandler {
79 public:
compileError(const char * shader,const char * errors)80 void compileError(const char* shader, const char* errors) override {
81 fShaders.push_back(SkString(shader));
82 fErrors.push_back(SkString(errors));
83 }
84
reset()85 void reset() {
86 fShaders.reset();
87 fErrors.reset();
88 }
89
90 SkTArray<SkString> fShaders;
91 SkTArray<SkString> fErrors;
92 };
93
94 static CapturingShaderErrorHandler gShaderErrorHandler;
95
ShaderErrorHandler()96 GrContextOptions::ShaderErrorHandler* Viewer::ShaderErrorHandler() { return &gShaderErrorHandler; }
97
98 using namespace sk_app;
99 using SkSL::Compiler;
100 using OverrideFlag = SkSL::Compiler::OverrideFlag;
101
102 static std::map<GpuPathRenderers, std::string> gPathRendererNames;
103
Create(int argc,char ** argv,void * platformData)104 Application* Application::Create(int argc, char** argv, void* platformData) {
105 return new Viewer(argc, argv, platformData);
106 }
107
108 static DEFINE_string(slide, "", "Start on this sample.");
109 static DEFINE_bool(list, false, "List samples?");
110
111 #ifdef SK_GL
112 #define GL_BACKEND_STR ", \"gl\""
113 #else
114 #define GL_BACKEND_STR
115 #endif
116 #ifdef SK_VULKAN
117 #define VK_BACKEND_STR ", \"vk\""
118 #else
119 #define VK_BACKEND_STR
120 #endif
121 #ifdef SK_METAL
122 #define MTL_BACKEND_STR ", \"mtl\""
123 #else
124 #define MTL_BACKEND_STR
125 #endif
126 #ifdef SK_DIRECT3D
127 #define D3D_BACKEND_STR ", \"d3d\""
128 #else
129 #define D3D_BACKEND_STR
130 #endif
131 #ifdef SK_DAWN
132 #define DAWN_BACKEND_STR ", \"dawn\""
133 #else
134 #define DAWN_BACKEND_STR
135 #endif
136 #define BACKENDS_STR_EVALUATOR(sw, gl, vk, mtl, d3d, dawn) sw gl vk mtl d3d dawn
137 #define BACKENDS_STR BACKENDS_STR_EVALUATOR( \
138 "\"sw\"", GL_BACKEND_STR, VK_BACKEND_STR, MTL_BACKEND_STR, D3D_BACKEND_STR, DAWN_BACKEND_STR)
139
140 static DEFINE_string2(backend, b, "sw", "Backend to use. Allowed values are " BACKENDS_STR ".");
141
142 static DEFINE_int(msaa, 1, "Number of subpixel samples. 0 for no HW antialiasing.");
143 static DEFINE_bool(dmsaa, false, "Use internal MSAA to render to non-MSAA surfaces?");
144
145 static DEFINE_string(bisect, "", "Path to a .skp or .svg file to bisect.");
146
147 static DEFINE_string2(file, f, "", "Open a single file for viewing.");
148
149 static DEFINE_string2(match, m, nullptr,
150 "[~][^]substring[$] [...] of name to run.\n"
151 "Multiple matches may be separated by spaces.\n"
152 "~ causes a matching name to always be skipped\n"
153 "^ requires the start of the name to match\n"
154 "$ requires the end of the name to match\n"
155 "^ and $ requires an exact match\n"
156 "If a name does not match any list entry,\n"
157 "it is skipped unless some list entry starts with ~");
158
159 #if defined(SK_BUILD_FOR_ANDROID)
160 # define PATH_PREFIX "/data/local/tmp/"
161 #else
162 # define PATH_PREFIX ""
163 #endif
164
165 static DEFINE_string(jpgs , PATH_PREFIX "jpgs" , "Directory to read jpgs from.");
166 static DEFINE_string(skps , PATH_PREFIX "skps" , "Directory to read skps from.");
167 static DEFINE_string(mskps , PATH_PREFIX "mskps" , "Directory to read mskps from.");
168 static DEFINE_string(lotties, PATH_PREFIX "lotties", "Directory to read (Bodymovin) jsons from.");
169 static DEFINE_string(rives , PATH_PREFIX "rives" , "Directory to read Rive (Flare) files from.");
170 #undef PATH_PREFIX
171
172 static DEFINE_string(svgs, "", "Directory to read SVGs from, or a single SVG file.");
173
174 static DEFINE_int_2(threads, j, -1,
175 "Run threadsafe tests on a threadpool with this many extra threads, "
176 "defaulting to one extra thread per core.");
177
178 static DEFINE_bool(redraw, false, "Toggle continuous redraw.");
179
180 static DEFINE_bool(offscreen, false, "Force rendering to an offscreen surface.");
181 static DEFINE_bool(skvm, false, "Force skvm blitters for raster.");
182 static DEFINE_bool(jit, true, "JIT SkVM?");
183 static DEFINE_bool(dylib, false, "JIT via dylib (much slower compile but easier to debug/profile)");
184 static DEFINE_bool(stats, false, "Display stats overlay on startup.");
185 static DEFINE_bool(binaryarchive, false, "Enable MTLBinaryArchive use (if available).");
186
187 #ifndef SK_GL
188 static_assert(false, "viewer requires GL backend for raster.")
189 #endif
190
191 const char* kBackendTypeStrings[sk_app::Window::kBackendTypeCount] = {
192 "OpenGL",
193 #if SK_ANGLE && defined(SK_BUILD_FOR_WIN)
194 "ANGLE",
195 #endif
196 #ifdef SK_DAWN
197 "Dawn",
198 #endif
199 #ifdef SK_VULKAN
200 "Vulkan",
201 #endif
202 #ifdef SK_METAL
203 "Metal",
204 #ifdef SK_GRAPHITE_ENABLED
205 "Metal (Graphite)",
206 #endif
207 #endif
208 #ifdef SK_DIRECT3D
209 "Direct3D",
210 #endif
211 "Raster"
212 };
213
get_backend_type(const char * str)214 static sk_app::Window::BackendType get_backend_type(const char* str) {
215 #ifdef SK_DAWN
216 if (0 == strcmp(str, "dawn")) {
217 return sk_app::Window::kDawn_BackendType;
218 } else
219 #endif
220 #ifdef SK_VULKAN
221 if (0 == strcmp(str, "vk")) {
222 return sk_app::Window::kVulkan_BackendType;
223 } else
224 #endif
225 #if SK_ANGLE && defined(SK_BUILD_FOR_WIN)
226 if (0 == strcmp(str, "angle")) {
227 return sk_app::Window::kANGLE_BackendType;
228 } else
229 #endif
230 #ifdef SK_METAL
231 if (0 == strcmp(str, "mtl")) {
232 return sk_app::Window::kMetal_BackendType;
233 } else
234 #ifdef SK_GRAPHITE_ENABLED
235 if (0 == strcmp(str, "grmtl")) {
236 return sk_app::Window::kGraphiteMetal_BackendType;
237 } else
238 #endif
239 #endif
240 #ifdef SK_DIRECT3D
241 if (0 == strcmp(str, "d3d")) {
242 return sk_app::Window::kDirect3D_BackendType;
243 } else
244 #endif
245
246 if (0 == strcmp(str, "gl")) {
247 return sk_app::Window::kNativeGL_BackendType;
248 } else if (0 == strcmp(str, "sw")) {
249 return sk_app::Window::kRaster_BackendType;
250 } else {
251 SkDebugf("Unknown backend type, %s, defaulting to sw.", str);
252 return sk_app::Window::kRaster_BackendType;
253 }
254 }
255
256 static SkColorSpacePrimaries gSrgbPrimaries = {
257 0.64f, 0.33f,
258 0.30f, 0.60f,
259 0.15f, 0.06f,
260 0.3127f, 0.3290f };
261
262 static SkColorSpacePrimaries gAdobePrimaries = {
263 0.64f, 0.33f,
264 0.21f, 0.71f,
265 0.15f, 0.06f,
266 0.3127f, 0.3290f };
267
268 static SkColorSpacePrimaries gP3Primaries = {
269 0.680f, 0.320f,
270 0.265f, 0.690f,
271 0.150f, 0.060f,
272 0.3127f, 0.3290f };
273
274 static SkColorSpacePrimaries gRec2020Primaries = {
275 0.708f, 0.292f,
276 0.170f, 0.797f,
277 0.131f, 0.046f,
278 0.3127f, 0.3290f };
279
280 struct NamedPrimaries {
281 const char* fName;
282 SkColorSpacePrimaries* fPrimaries;
283 } gNamedPrimaries[] = {
284 { "sRGB", &gSrgbPrimaries },
285 { "AdobeRGB", &gAdobePrimaries },
286 { "P3", &gP3Primaries },
287 { "Rec. 2020", &gRec2020Primaries },
288 };
289
primaries_equal(const SkColorSpacePrimaries & a,const SkColorSpacePrimaries & b)290 static bool primaries_equal(const SkColorSpacePrimaries& a, const SkColorSpacePrimaries& b) {
291 return memcmp(&a, &b, sizeof(SkColorSpacePrimaries)) == 0;
292 }
293
backend_type_for_window(Window::BackendType backendType)294 static Window::BackendType backend_type_for_window(Window::BackendType backendType) {
295 // In raster mode, we still use GL for the window.
296 // This lets us render the GUI faster (and correct).
297 return Window::kRaster_BackendType == backendType ? Window::kNativeGL_BackendType : backendType;
298 }
299
300 class NullSlide : public Slide {
getDimensions() const301 SkISize getDimensions() const override {
302 return SkISize::Make(640, 480);
303 }
304
draw(SkCanvas * canvas)305 void draw(SkCanvas* canvas) override {
306 canvas->clear(0xffff11ff);
307 }
308 };
309
310 static const char kName[] = "name";
311 static const char kValue[] = "value";
312 static const char kOptions[] = "options";
313 static const char kSlideStateName[] = "Slide";
314 static const char kBackendStateName[] = "Backend";
315 static const char kMSAAStateName[] = "MSAA";
316 static const char kPathRendererStateName[] = "Path renderer";
317 static const char kSoftkeyStateName[] = "Softkey";
318 static const char kSoftkeyHint[] = "Please select a softkey";
319 static const char kON[] = "ON";
320 static const char kRefreshStateName[] = "Refresh";
321
322 extern bool gUseSkVMBlitter;
323 extern bool gSkVMAllowJIT;
324 extern bool gSkVMJITViaDylib;
325
Viewer(int argc,char ** argv,void * platformData)326 Viewer::Viewer(int argc, char** argv, void* platformData)
327 : fCurrentSlide(-1)
328 , fRefresh(false)
329 , fSaveToSKP(false)
330 , fShowSlideDimensions(false)
331 , fShowImGuiDebugWindow(false)
332 , fShowSlidePicker(false)
333 , fShowImGuiTestWindow(false)
334 , fShowZoomWindow(false)
335 , fZoomWindowFixed(false)
336 , fZoomWindowLocation{0.0f, 0.0f}
337 , fLastImage(nullptr)
338 , fZoomUI(false)
339 , fBackendType(sk_app::Window::kNativeGL_BackendType)
340 , fColorMode(ColorMode::kLegacy)
341 , fColorSpacePrimaries(gSrgbPrimaries)
342 // Our UI can only tweak gamma (currently), so start out gamma-only
343 , fColorSpaceTransferFn(SkNamedTransferFn::k2Dot2)
344 , fApplyBackingScale(true)
345 , fZoomLevel(0.0f)
346 , fRotation(0.0f)
347 , fOffset{0.5f, 0.5f}
348 , fGestureDevice(GestureDevice::kNone)
349 , fTiled(false)
350 , fDrawTileBoundaries(false)
351 , fTileScale{0.25f, 0.25f}
352 , fPerspectiveMode(kPerspective_Off)
353 {
354 SkGraphics::Init();
355
356 gPathRendererNames[GpuPathRenderers::kDefault] = "Default Path Renderers";
357 gPathRendererNames[GpuPathRenderers::kAtlas] = "Atlas (tessellation)";
358 gPathRendererNames[GpuPathRenderers::kTessellation] = "Tessellation";
359 gPathRendererNames[GpuPathRenderers::kSmall] = "Small paths (cached sdf or alpha masks)";
360 gPathRendererNames[GpuPathRenderers::kTriangulating] = "Triangulating";
361 gPathRendererNames[GpuPathRenderers::kNone] = "Software masks";
362
363 SkDebugf("Command line arguments: ");
364 for (int i = 1; i < argc; ++i) {
365 SkDebugf("%s ", argv[i]);
366 }
367 SkDebugf("\n");
368
369 CommandLineFlags::Parse(argc, argv);
370 #ifdef SK_BUILD_FOR_ANDROID
371 SetResourcePath("/data/local/tmp/resources");
372 #endif
373
374 gUseSkVMBlitter = FLAGS_skvm;
375 gSkVMAllowJIT = FLAGS_jit;
376 gSkVMJITViaDylib = FLAGS_dylib;
377
378 CommonFlags::SetDefaultFontMgr();
379
380 initializeEventTracingForTools();
381 static SkTaskGroup::Enabler kTaskGroupEnabler(FLAGS_threads);
382
383 fBackendType = get_backend_type(FLAGS_backend[0]);
384 fWindow = Window::CreateNativeWindow(platformData);
385
386 DisplayParams displayParams;
387 displayParams.fMSAASampleCount = FLAGS_msaa;
388 displayParams.fEnableBinaryArchive = FLAGS_binaryarchive;
389 CommonFlags::SetCtxOptions(&displayParams.fGrContextOptions);
390 displayParams.fGrContextOptions.fPersistentCache = &fPersistentCache;
391 displayParams.fGrContextOptions.fShaderCacheStrategy =
392 GrContextOptions::ShaderCacheStrategy::kSkSL;
393 displayParams.fGrContextOptions.fShaderErrorHandler = &gShaderErrorHandler;
394 displayParams.fGrContextOptions.fSuppressPrints = true;
395 if (FLAGS_dmsaa) {
396 displayParams.fSurfaceProps = SkSurfaceProps(
397 displayParams.fSurfaceProps.flags() | SkSurfaceProps::kDynamicMSAA_Flag,
398 displayParams.fSurfaceProps.pixelGeometry());
399 }
400 fWindow->setRequestedDisplayParams(displayParams);
401 fDisplay = fWindow->getRequestedDisplayParams();
402 fRefresh = FLAGS_redraw;
403
404 fImGuiLayer.setScaleFactor(fWindow->scaleFactor());
405 fStatsLayer.setDisplayScale((fZoomUI ? 2.0f : 1.0f) * fWindow->scaleFactor());
406
407 // Configure timers
408 fStatsLayer.setActive(FLAGS_stats);
409 fAnimateTimer = fStatsLayer.addTimer("Animate", SK_ColorMAGENTA, 0xffff66ff);
410 fPaintTimer = fStatsLayer.addTimer("Paint", SK_ColorGREEN);
411 fFlushTimer = fStatsLayer.addTimer("Flush", SK_ColorRED, 0xffff6666);
412
413 // register callbacks
414 fCommands.attach(fWindow);
415 fWindow->pushLayer(this);
416 fWindow->pushLayer(&fStatsLayer);
417 fWindow->pushLayer(&fImGuiLayer);
418
419 // add key-bindings
__anon9a3958fe0102() 420 fCommands.addCommand(' ', "GUI", "Toggle Debug GUI", [this]() {
421 this->fShowImGuiDebugWindow = !this->fShowImGuiDebugWindow;
422 fWindow->inval();
423 });
424 // Command to jump directly to the slide picker and give it focus
__anon9a3958fe0202() 425 fCommands.addCommand('/', "GUI", "Jump to slide picker", [this]() {
426 this->fShowImGuiDebugWindow = true;
427 this->fShowSlidePicker = true;
428 fWindow->inval();
429 });
430 // Alias that to Backspace, to match SampleApp
__anon9a3958fe0302() 431 fCommands.addCommand(skui::Key::kBack, "Backspace", "GUI", "Jump to slide picker", [this]() {
432 this->fShowImGuiDebugWindow = true;
433 this->fShowSlidePicker = true;
434 fWindow->inval();
435 });
__anon9a3958fe0402() 436 fCommands.addCommand('g', "GUI", "Toggle GUI Demo", [this]() {
437 this->fShowImGuiTestWindow = !this->fShowImGuiTestWindow;
438 fWindow->inval();
439 });
__anon9a3958fe0502() 440 fCommands.addCommand('z', "GUI", "Toggle zoom window", [this]() {
441 this->fShowZoomWindow = !this->fShowZoomWindow;
442 fWindow->inval();
443 });
__anon9a3958fe0602() 444 fCommands.addCommand('Z', "GUI", "Toggle zoom window state", [this]() {
445 this->fZoomWindowFixed = !this->fZoomWindowFixed;
446 fWindow->inval();
447 });
__anon9a3958fe0702() 448 fCommands.addCommand('v', "Swapchain", "Toggle vsync on/off", [this]() {
449 DisplayParams params = fWindow->getRequestedDisplayParams();
450 params.fDisableVsync = !params.fDisableVsync;
451 fWindow->setRequestedDisplayParams(params);
452 this->updateTitle();
453 fWindow->inval();
454 });
__anon9a3958fe0802() 455 fCommands.addCommand('V', "Swapchain", "Toggle delayed acquire on/off (Metal only)", [this]() {
456 DisplayParams params = fWindow->getRequestedDisplayParams();
457 params.fDelayDrawableAcquisition = !params.fDelayDrawableAcquisition;
458 fWindow->setRequestedDisplayParams(params);
459 this->updateTitle();
460 fWindow->inval();
461 });
__anon9a3958fe0902() 462 fCommands.addCommand('r', "Redraw", "Toggle redraw", [this]() {
463 fRefresh = !fRefresh;
464 fWindow->inval();
465 });
__anon9a3958fe0a02() 466 fCommands.addCommand('s', "Overlays", "Toggle stats display", [this]() {
467 fStatsLayer.setActive(!fStatsLayer.getActive());
468 fWindow->inval();
469 });
__anon9a3958fe0b02() 470 fCommands.addCommand('0', "Overlays", "Reset stats", [this]() {
471 fStatsLayer.resetMeasurements();
472 this->updateTitle();
473 fWindow->inval();
474 });
__anon9a3958fe0c02() 475 fCommands.addCommand('c', "Modes", "Cycle color mode", [this]() {
476 switch (fColorMode) {
477 case ColorMode::kLegacy:
478 this->setColorMode(ColorMode::kColorManaged8888);
479 break;
480 case ColorMode::kColorManaged8888:
481 this->setColorMode(ColorMode::kColorManagedF16);
482 break;
483 case ColorMode::kColorManagedF16:
484 this->setColorMode(ColorMode::kColorManagedF16Norm);
485 break;
486 case ColorMode::kColorManagedF16Norm:
487 this->setColorMode(ColorMode::kLegacy);
488 break;
489 }
490 });
__anon9a3958fe0d02() 491 fCommands.addCommand('w', "Modes", "Toggle wireframe", [this]() {
492 DisplayParams params = fWindow->getRequestedDisplayParams();
493 params.fGrContextOptions.fWireframeMode = !params.fGrContextOptions.fWireframeMode;
494 fWindow->setRequestedDisplayParams(params);
495 fWindow->inval();
496 });
__anon9a3958fe0e02() 497 fCommands.addCommand('w', "Modes", "Toggle reduced shaders", [this]() {
498 DisplayParams params = fWindow->getRequestedDisplayParams();
499 params.fGrContextOptions.fReducedShaderVariations =
500 !params.fGrContextOptions.fReducedShaderVariations;
501 fWindow->setRequestedDisplayParams(params);
502 fWindow->inval();
503 });
__anon9a3958fe0f02() 504 fCommands.addCommand(skui::Key::kRight, "Right", "Navigation", "Next slide", [this]() {
505 this->setCurrentSlide(fCurrentSlide < fSlides.count() - 1 ? fCurrentSlide + 1 : 0);
506 });
__anon9a3958fe1002() 507 fCommands.addCommand(skui::Key::kLeft, "Left", "Navigation", "Previous slide", [this]() {
508 this->setCurrentSlide(fCurrentSlide > 0 ? fCurrentSlide - 1 : fSlides.count() - 1);
509 });
__anon9a3958fe1102() 510 fCommands.addCommand(skui::Key::kUp, "Up", "Transform", "Zoom in", [this]() {
511 this->changeZoomLevel(1.f / 32.f);
512 fWindow->inval();
513 });
__anon9a3958fe1202() 514 fCommands.addCommand(skui::Key::kDown, "Down", "Transform", "Zoom out", [this]() {
515 this->changeZoomLevel(-1.f / 32.f);
516 fWindow->inval();
517 });
__anon9a3958fe1302() 518 fCommands.addCommand('d', "Modes", "Change rendering backend", [this]() {
519 sk_app::Window::BackendType newBackend = (sk_app::Window::BackendType)(
520 (fBackendType + 1) % sk_app::Window::kBackendTypeCount);
521 // Switching to and from Vulkan is problematic on Linux so disabled for now
522 #if defined(SK_BUILD_FOR_UNIX) && defined(SK_VULKAN)
523 if (newBackend == sk_app::Window::kVulkan_BackendType) {
524 newBackend = (sk_app::Window::BackendType)((newBackend + 1) %
525 sk_app::Window::kBackendTypeCount);
526 } else if (fBackendType == sk_app::Window::kVulkan_BackendType) {
527 newBackend = sk_app::Window::kVulkan_BackendType;
528 }
529 #endif
530 this->setBackend(newBackend);
531 });
__anon9a3958fe1402() 532 fCommands.addCommand('K', "IO", "Save slide to SKP", [this]() {
533 fSaveToSKP = true;
534 fWindow->inval();
535 });
__anon9a3958fe1502() 536 fCommands.addCommand('&', "Overlays", "Show slide dimensios", [this]() {
537 fShowSlideDimensions = !fShowSlideDimensions;
538 fWindow->inval();
539 });
__anon9a3958fe1602() 540 fCommands.addCommand('G', "Modes", "Geometry", [this]() {
541 DisplayParams params = fWindow->getRequestedDisplayParams();
542 uint32_t flags = params.fSurfaceProps.flags();
543 SkPixelGeometry defaultPixelGeometry = fDisplay.fSurfaceProps.pixelGeometry();
544 if (!fDisplayOverrides.fSurfaceProps.fPixelGeometry) {
545 fDisplayOverrides.fSurfaceProps.fPixelGeometry = true;
546 params.fSurfaceProps = SkSurfaceProps(flags, kUnknown_SkPixelGeometry);
547 } else {
548 switch (params.fSurfaceProps.pixelGeometry()) {
549 case kUnknown_SkPixelGeometry:
550 params.fSurfaceProps = SkSurfaceProps(flags, kRGB_H_SkPixelGeometry);
551 break;
552 case kRGB_H_SkPixelGeometry:
553 params.fSurfaceProps = SkSurfaceProps(flags, kBGR_H_SkPixelGeometry);
554 break;
555 case kBGR_H_SkPixelGeometry:
556 params.fSurfaceProps = SkSurfaceProps(flags, kRGB_V_SkPixelGeometry);
557 break;
558 case kRGB_V_SkPixelGeometry:
559 params.fSurfaceProps = SkSurfaceProps(flags, kBGR_V_SkPixelGeometry);
560 break;
561 case kBGR_V_SkPixelGeometry:
562 params.fSurfaceProps = SkSurfaceProps(flags, defaultPixelGeometry);
563 fDisplayOverrides.fSurfaceProps.fPixelGeometry = false;
564 break;
565 }
566 }
567 fWindow->setRequestedDisplayParams(params);
568 this->updateTitle();
569 fWindow->inval();
570 });
__anon9a3958fe1702() 571 fCommands.addCommand('H', "Font", "Hinting mode", [this]() {
572 if (!fFontOverrides.fHinting) {
573 fFontOverrides.fHinting = true;
574 fFont.setHinting(SkFontHinting::kNone);
575 } else {
576 switch (fFont.getHinting()) {
577 case SkFontHinting::kNone:
578 fFont.setHinting(SkFontHinting::kSlight);
579 break;
580 case SkFontHinting::kSlight:
581 fFont.setHinting(SkFontHinting::kNormal);
582 break;
583 case SkFontHinting::kNormal:
584 fFont.setHinting(SkFontHinting::kFull);
585 break;
586 case SkFontHinting::kFull:
587 fFont.setHinting(SkFontHinting::kNone);
588 fFontOverrides.fHinting = false;
589 break;
590 }
591 }
592 this->updateTitle();
593 fWindow->inval();
594 });
__anon9a3958fe1802() 595 fCommands.addCommand('A', "Paint", "Antialias Mode", [this]() {
596 if (!fPaintOverrides.fAntiAlias) {
597 fPaintOverrides.fAntiAliasState = SkPaintFields::AntiAliasState::Alias;
598 fPaintOverrides.fAntiAlias = true;
599 fPaint.setAntiAlias(false);
600 gSkUseAnalyticAA = gSkForceAnalyticAA = false;
601 } else {
602 fPaint.setAntiAlias(true);
603 switch (fPaintOverrides.fAntiAliasState) {
604 case SkPaintFields::AntiAliasState::Alias:
605 fPaintOverrides.fAntiAliasState = SkPaintFields::AntiAliasState::Normal;
606 gSkUseAnalyticAA = gSkForceAnalyticAA = false;
607 break;
608 case SkPaintFields::AntiAliasState::Normal:
609 fPaintOverrides.fAntiAliasState = SkPaintFields::AntiAliasState::AnalyticAAEnabled;
610 gSkUseAnalyticAA = true;
611 gSkForceAnalyticAA = false;
612 break;
613 case SkPaintFields::AntiAliasState::AnalyticAAEnabled:
614 fPaintOverrides.fAntiAliasState = SkPaintFields::AntiAliasState::AnalyticAAForced;
615 gSkUseAnalyticAA = gSkForceAnalyticAA = true;
616 break;
617 case SkPaintFields::AntiAliasState::AnalyticAAForced:
618 fPaintOverrides.fAntiAliasState = SkPaintFields::AntiAliasState::Alias;
619 fPaintOverrides.fAntiAlias = false;
620 gSkUseAnalyticAA = fPaintOverrides.fOriginalSkUseAnalyticAA;
621 gSkForceAnalyticAA = fPaintOverrides.fOriginalSkForceAnalyticAA;
622 break;
623 }
624 }
625 this->updateTitle();
626 fWindow->inval();
627 });
__anon9a3958fe1902() 628 fCommands.addCommand('D', "Modes", "DFT", [this]() {
629 DisplayParams params = fWindow->getRequestedDisplayParams();
630 uint32_t flags = params.fSurfaceProps.flags();
631 flags ^= SkSurfaceProps::kUseDeviceIndependentFonts_Flag;
632 params.fSurfaceProps = SkSurfaceProps(flags, params.fSurfaceProps.pixelGeometry());
633 fWindow->setRequestedDisplayParams(params);
634 this->updateTitle();
635 fWindow->inval();
636 });
__anon9a3958fe1a02() 637 fCommands.addCommand('L', "Font", "Subpixel Antialias Mode", [this]() {
638 if (!fFontOverrides.fEdging) {
639 fFontOverrides.fEdging = true;
640 fFont.setEdging(SkFont::Edging::kAlias);
641 } else {
642 switch (fFont.getEdging()) {
643 case SkFont::Edging::kAlias:
644 fFont.setEdging(SkFont::Edging::kAntiAlias);
645 break;
646 case SkFont::Edging::kAntiAlias:
647 fFont.setEdging(SkFont::Edging::kSubpixelAntiAlias);
648 break;
649 case SkFont::Edging::kSubpixelAntiAlias:
650 fFont.setEdging(SkFont::Edging::kAlias);
651 fFontOverrides.fEdging = false;
652 break;
653 }
654 }
655 this->updateTitle();
656 fWindow->inval();
657 });
__anon9a3958fe1b02() 658 fCommands.addCommand('S', "Font", "Subpixel Position Mode", [this]() {
659 if (!fFontOverrides.fSubpixel) {
660 fFontOverrides.fSubpixel = true;
661 fFont.setSubpixel(false);
662 } else {
663 if (!fFont.isSubpixel()) {
664 fFont.setSubpixel(true);
665 } else {
666 fFontOverrides.fSubpixel = false;
667 }
668 }
669 this->updateTitle();
670 fWindow->inval();
671 });
__anon9a3958fe1c02() 672 fCommands.addCommand('B', "Font", "Baseline Snapping", [this]() {
673 if (!fFontOverrides.fBaselineSnap) {
674 fFontOverrides.fBaselineSnap = true;
675 fFont.setBaselineSnap(false);
676 } else {
677 if (!fFont.isBaselineSnap()) {
678 fFont.setBaselineSnap(true);
679 } else {
680 fFontOverrides.fBaselineSnap = false;
681 }
682 }
683 this->updateTitle();
684 fWindow->inval();
685 });
__anon9a3958fe1d02() 686 fCommands.addCommand('p', "Transform", "Toggle Perspective Mode", [this]() {
687 fPerspectiveMode = (kPerspective_Real == fPerspectiveMode) ? kPerspective_Fake
688 : kPerspective_Real;
689 this->updateTitle();
690 fWindow->inval();
691 });
__anon9a3958fe1e02() 692 fCommands.addCommand('P', "Transform", "Toggle Perspective", [this]() {
693 fPerspectiveMode = (kPerspective_Off == fPerspectiveMode) ? kPerspective_Real
694 : kPerspective_Off;
695 this->updateTitle();
696 fWindow->inval();
697 });
__anon9a3958fe1f02() 698 fCommands.addCommand('a', "Transform", "Toggle Animation", [this]() {
699 fAnimTimer.togglePauseResume();
700 });
__anon9a3958fe2002() 701 fCommands.addCommand('u', "GUI", "Zoom UI", [this]() {
702 fZoomUI = !fZoomUI;
703 fStatsLayer.setDisplayScale((fZoomUI ? 2.0f : 1.0f) * fWindow->scaleFactor());
704 fWindow->inval();
705 });
__anon9a3958fe2102() 706 fCommands.addCommand('$', "ViaSerialize", "Toggle ViaSerialize", [this]() {
707 fDrawViaSerialize = !fDrawViaSerialize;
708 this->updateTitle();
709 fWindow->inval();
710 });
__anon9a3958fe2202() 711 fCommands.addCommand('!', "SkVM", "Toggle SkVM blitter", [this]() {
712 gUseSkVMBlitter = !gUseSkVMBlitter;
713 this->updateTitle();
714 fWindow->inval();
715 });
__anon9a3958fe2302() 716 fCommands.addCommand('@', "SkVM", "Toggle SkVM JIT", [this]() {
717 gSkVMAllowJIT = !gSkVMAllowJIT;
718 this->updateTitle();
719 fWindow->inval();
720 });
721
722 // set up slides
723 this->initSlides();
724 if (FLAGS_list) {
725 this->listNames();
726 }
727
728 fPerspectivePoints[0].set(0, 0);
729 fPerspectivePoints[1].set(1, 0);
730 fPerspectivePoints[2].set(0, 1);
731 fPerspectivePoints[3].set(1, 1);
732 fAnimTimer.run();
733
734 auto gamutImage = GetResourceAsImage("images/gamut.png");
735 if (gamutImage) {
736 fImGuiGamutPaint.setShader(gamutImage->makeShader(SkSamplingOptions(SkFilterMode::kLinear)));
737 }
738 fImGuiGamutPaint.setColor(SK_ColorWHITE);
739
740 fWindow->attach(backend_type_for_window(fBackendType));
741 this->setCurrentSlide(this->startupSlide());
742 }
743
initSlides()744 void Viewer::initSlides() {
745 using SlideFactory = sk_sp<Slide>(*)(const SkString& name, const SkString& path);
746 static const struct {
747 const char* fExtension;
748 const char* fDirName;
749 const CommandLineFlags::StringArray& fFlags;
750 const SlideFactory fFactory;
751 } gExternalSlidesInfo[] = {
752 { ".mskp", "mskp-dir", FLAGS_mskps,
753 [](const SkString& name, const SkString& path) -> sk_sp<Slide> {
754 return sk_make_sp<MSKPSlide>(name, path);}
755 },
756 { ".skp", "skp-dir", FLAGS_skps,
757 [](const SkString& name, const SkString& path) -> sk_sp<Slide> {
758 return sk_make_sp<SKPSlide>(name, path);}
759 },
760 { ".jpg", "jpg-dir", FLAGS_jpgs,
761 [](const SkString& name, const SkString& path) -> sk_sp<Slide> {
762 return sk_make_sp<ImageSlide>(name, path);}
763 },
764 #if defined(SK_ENABLE_SKOTTIE)
765 { ".json", "skottie-dir", FLAGS_lotties,
766 [](const SkString& name, const SkString& path) -> sk_sp<Slide> {
767 return sk_make_sp<SkottieSlide>(name, path);}
768 },
769 #endif
770 #if defined(SK_ENABLE_SKRIVE)
771 { ".flr", "skrive-dir", FLAGS_rives,
772 [](const SkString& name, const SkString& path) -> sk_sp<Slide> {
773 return sk_make_sp<SkRiveSlide>(name, path);}
774 },
775 #endif
776 #if defined(SK_ENABLE_SVG)
777 { ".svg", "svg-dir", FLAGS_svgs,
778 [](const SkString& name, const SkString& path) -> sk_sp<Slide> {
779 return sk_make_sp<SvgSlide>(name, path);}
780 },
781 #endif
782 };
783
784 SkTArray<sk_sp<Slide>> dirSlides;
785
786 const auto addSlide =
787 [&](const SkString& name, const SkString& path, const SlideFactory& fact) {
788 if (CommandLineFlags::ShouldSkip(FLAGS_match, name.c_str())) {
789 return;
790 }
791
792 if (auto slide = fact(name, path)) {
793 dirSlides.push_back(slide);
794 fSlides.push_back(std::move(slide));
795 }
796 };
797
798 if (!FLAGS_file.isEmpty()) {
799 // single file mode
800 const SkString file(FLAGS_file[0]);
801
802 if (sk_exists(file.c_str(), kRead_SkFILE_Flag)) {
803 for (const auto& sinfo : gExternalSlidesInfo) {
804 if (file.endsWith(sinfo.fExtension)) {
805 addSlide(SkOSPath::Basename(file.c_str()), file, sinfo.fFactory);
806 return;
807 }
808 }
809
810 fprintf(stderr, "Unsupported file type \"%s\"\n", file.c_str());
811 } else {
812 fprintf(stderr, "Cannot read \"%s\"\n", file.c_str());
813 }
814
815 return;
816 }
817
818 // Bisect slide.
819 if (!FLAGS_bisect.isEmpty()) {
820 sk_sp<BisectSlide> bisect = BisectSlide::Create(FLAGS_bisect[0]);
821 if (bisect && !CommandLineFlags::ShouldSkip(FLAGS_match, bisect->getName().c_str())) {
822 if (FLAGS_bisect.count() >= 2) {
823 for (const char* ch = FLAGS_bisect[1]; *ch; ++ch) {
824 bisect->onChar(*ch);
825 }
826 }
827 fSlides.push_back(std::move(bisect));
828 }
829 }
830
831 // GMs
832 int firstGM = fSlides.count();
833 for (skiagm::GMFactory gmFactory : skiagm::GMRegistry::Range()) {
834 std::unique_ptr<skiagm::GM> gm = gmFactory();
835 if (!CommandLineFlags::ShouldSkip(FLAGS_match, gm->getName())) {
836 sk_sp<Slide> slide(new GMSlide(std::move(gm)));
837 fSlides.push_back(std::move(slide));
838 }
839 }
840 // reverse gms
841 int numGMs = fSlides.count() - firstGM;
842 for (int i = 0; i < numGMs/2; ++i) {
843 std::swap(fSlides[firstGM + i], fSlides[fSlides.count() - i - 1]);
844 }
845
846 // samples
847 for (const SampleFactory factory : SampleRegistry::Range()) {
848 sk_sp<Slide> slide(new SampleSlide(factory));
849 if (!CommandLineFlags::ShouldSkip(FLAGS_match, slide->getName().c_str())) {
850 fSlides.push_back(slide);
851 }
852 }
853
854 // Particle demo
855 {
856 // TODO: Convert this to a sample
857 sk_sp<Slide> slide(new ParticlesSlide());
858 if (!CommandLineFlags::ShouldSkip(FLAGS_match, slide->getName().c_str())) {
859 fSlides.push_back(std::move(slide));
860 }
861 }
862
863 // Runtime shader editor
864 {
865 sk_sp<Slide> slide(new SkSLSlide());
866 if (!CommandLineFlags::ShouldSkip(FLAGS_match, slide->getName().c_str())) {
867 fSlides.push_back(std::move(slide));
868 }
869 }
870
871 for (const auto& info : gExternalSlidesInfo) {
872 for (const auto& flag : info.fFlags) {
873 if (SkStrEndsWith(flag.c_str(), info.fExtension)) {
874 // single file
875 addSlide(SkOSPath::Basename(flag.c_str()), flag, info.fFactory);
876 } else {
877 // directory
878 SkString name;
879 SkTArray<SkString> sortedFilenames;
880 SkOSFile::Iter it(flag.c_str(), info.fExtension);
881 while (it.next(&name)) {
882 sortedFilenames.push_back(name);
883 }
884 if (sortedFilenames.count()) {
885 SkTQSort(sortedFilenames.begin(), sortedFilenames.end(),
886 [](const SkString& a, const SkString& b) {
887 return strcmp(a.c_str(), b.c_str()) < 0;
888 });
889 }
890 for (const SkString& filename : sortedFilenames) {
891 addSlide(filename, SkOSPath::Join(flag.c_str(), filename.c_str()),
892 info.fFactory);
893 }
894 }
895 if (!dirSlides.empty()) {
896 fSlides.push_back(
897 sk_make_sp<SlideDir>(SkStringPrintf("%s[%s]", info.fDirName, flag.c_str()),
898 std::move(dirSlides)));
899 dirSlides.reset(); // NOLINT(bugprone-use-after-move)
900 }
901 }
902 }
903
904 if (!fSlides.count()) {
905 sk_sp<Slide> slide(new NullSlide());
906 fSlides.push_back(std::move(slide));
907 }
908 }
909
910
~Viewer()911 Viewer::~Viewer() {
912 for(auto& slide : fSlides) {
913 slide->gpuTeardown();
914 }
915
916 fWindow->detach();
917 delete fWindow;
918 }
919
920 struct SkPaintTitleUpdater {
SkPaintTitleUpdaterSkPaintTitleUpdater921 SkPaintTitleUpdater(SkString* title) : fTitle(title), fCount(0) {}
appendSkPaintTitleUpdater922 void append(const char* s) {
923 if (fCount == 0) {
924 fTitle->append(" {");
925 } else {
926 fTitle->append(", ");
927 }
928 fTitle->append(s);
929 ++fCount;
930 }
doneSkPaintTitleUpdater931 void done() {
932 if (fCount > 0) {
933 fTitle->append("}");
934 }
935 }
936 SkString* fTitle;
937 int fCount;
938 };
939
updateTitle()940 void Viewer::updateTitle() {
941 if (!fWindow) {
942 return;
943 }
944 if (fWindow->sampleCount() < 1) {
945 return; // Surface hasn't been created yet.
946 }
947
948 SkString title("Viewer: ");
949 title.append(fSlides[fCurrentSlide]->getName());
950
951 if (gSkUseAnalyticAA) {
952 if (gSkForceAnalyticAA) {
953 title.append(" <FAAA>");
954 } else {
955 title.append(" <AAA>");
956 }
957 }
958 if (fDrawViaSerialize) {
959 title.append(" <serialize>");
960 }
961 if (gUseSkVMBlitter) {
962 title.append(" <SkVMBlitter>");
963 }
964 if (!gSkVMAllowJIT) {
965 title.append(" <SkVM interpreter>");
966 }
967
968 SkPaintTitleUpdater paintTitle(&title);
969 auto paintFlag = [this, &paintTitle](bool SkPaintFields::* flag,
970 bool (SkPaint::* isFlag)() const,
971 const char* on, const char* off)
972 {
973 if (fPaintOverrides.*flag) {
974 paintTitle.append((fPaint.*isFlag)() ? on : off);
975 }
976 };
977
978 auto fontFlag = [this, &paintTitle](bool SkFontFields::* flag, bool (SkFont::* isFlag)() const,
979 const char* on, const char* off)
980 {
981 if (fFontOverrides.*flag) {
982 paintTitle.append((fFont.*isFlag)() ? on : off);
983 }
984 };
985
986 paintFlag(&SkPaintFields::fAntiAlias, &SkPaint::isAntiAlias, "Antialias", "Alias");
987 paintFlag(&SkPaintFields::fDither, &SkPaint::isDither, "DITHER", "No Dither");
988
989 fontFlag(&SkFontFields::fForceAutoHinting, &SkFont::isForceAutoHinting,
990 "Force Autohint", "No Force Autohint");
991 fontFlag(&SkFontFields::fEmbolden, &SkFont::isEmbolden, "Fake Bold", "No Fake Bold");
992 fontFlag(&SkFontFields::fBaselineSnap, &SkFont::isBaselineSnap, "BaseSnap", "No BaseSnap");
993 fontFlag(&SkFontFields::fLinearMetrics, &SkFont::isLinearMetrics,
994 "Linear Metrics", "Non-Linear Metrics");
995 fontFlag(&SkFontFields::fEmbeddedBitmaps, &SkFont::isEmbeddedBitmaps,
996 "Bitmap Text", "No Bitmap Text");
997 fontFlag(&SkFontFields::fSubpixel, &SkFont::isSubpixel, "Subpixel Text", "Pixel Text");
998
999 if (fFontOverrides.fEdging) {
1000 switch (fFont.getEdging()) {
1001 case SkFont::Edging::kAlias:
1002 paintTitle.append("Alias Text");
1003 break;
1004 case SkFont::Edging::kAntiAlias:
1005 paintTitle.append("Antialias Text");
1006 break;
1007 case SkFont::Edging::kSubpixelAntiAlias:
1008 paintTitle.append("Subpixel Antialias Text");
1009 break;
1010 }
1011 }
1012
1013 if (fFontOverrides.fHinting) {
1014 switch (fFont.getHinting()) {
1015 case SkFontHinting::kNone:
1016 paintTitle.append("No Hinting");
1017 break;
1018 case SkFontHinting::kSlight:
1019 paintTitle.append("Slight Hinting");
1020 break;
1021 case SkFontHinting::kNormal:
1022 paintTitle.append("Normal Hinting");
1023 break;
1024 case SkFontHinting::kFull:
1025 paintTitle.append("Full Hinting");
1026 break;
1027 }
1028 }
1029 paintTitle.done();
1030
1031 switch (fColorMode) {
1032 case ColorMode::kLegacy:
1033 title.append(" Legacy 8888");
1034 break;
1035 case ColorMode::kColorManaged8888:
1036 title.append(" ColorManaged 8888");
1037 break;
1038 case ColorMode::kColorManagedF16:
1039 title.append(" ColorManaged F16");
1040 break;
1041 case ColorMode::kColorManagedF16Norm:
1042 title.append(" ColorManaged F16 Norm");
1043 break;
1044 }
1045
1046 if (ColorMode::kLegacy != fColorMode) {
1047 int curPrimaries = -1;
1048 for (size_t i = 0; i < SK_ARRAY_COUNT(gNamedPrimaries); ++i) {
1049 if (primaries_equal(*gNamedPrimaries[i].fPrimaries, fColorSpacePrimaries)) {
1050 curPrimaries = i;
1051 break;
1052 }
1053 }
1054 title.appendf(" %s Gamma %f",
1055 curPrimaries >= 0 ? gNamedPrimaries[curPrimaries].fName : "Custom",
1056 fColorSpaceTransferFn.g);
1057 }
1058
1059 const DisplayParams& params = fWindow->getRequestedDisplayParams();
1060 if (fDisplayOverrides.fSurfaceProps.fPixelGeometry) {
1061 switch (params.fSurfaceProps.pixelGeometry()) {
1062 case kUnknown_SkPixelGeometry:
1063 title.append( " Flat");
1064 break;
1065 case kRGB_H_SkPixelGeometry:
1066 title.append( " RGB");
1067 break;
1068 case kBGR_H_SkPixelGeometry:
1069 title.append( " BGR");
1070 break;
1071 case kRGB_V_SkPixelGeometry:
1072 title.append( " RGBV");
1073 break;
1074 case kBGR_V_SkPixelGeometry:
1075 title.append( " BGRV");
1076 break;
1077 }
1078 }
1079
1080 if (params.fSurfaceProps.isUseDeviceIndependentFonts()) {
1081 title.append(" DFT");
1082 }
1083
1084 title.append(" [");
1085 title.append(kBackendTypeStrings[fBackendType]);
1086 int msaa = fWindow->sampleCount();
1087 if (msaa > 1) {
1088 title.appendf(" MSAA: %i", msaa);
1089 }
1090 title.append("]");
1091
1092 GpuPathRenderers pr = fWindow->getRequestedDisplayParams().fGrContextOptions.fGpuPathRenderers;
1093 if (GpuPathRenderers::kDefault != pr) {
1094 title.appendf(" [Path renderer: %s]", gPathRendererNames[pr].c_str());
1095 }
1096
1097 if (kPerspective_Real == fPerspectiveMode) {
1098 title.append(" Perpsective (Real)");
1099 } else if (kPerspective_Fake == fPerspectiveMode) {
1100 title.append(" Perspective (Fake)");
1101 }
1102
1103 fWindow->setTitle(title.c_str());
1104 }
1105
startupSlide() const1106 int Viewer::startupSlide() const {
1107
1108 if (!FLAGS_slide.isEmpty()) {
1109 int count = fSlides.count();
1110 for (int i = 0; i < count; i++) {
1111 if (fSlides[i]->getName().equals(FLAGS_slide[0])) {
1112 return i;
1113 }
1114 }
1115
1116 fprintf(stderr, "Unknown slide \"%s\"\n", FLAGS_slide[0]);
1117 this->listNames();
1118 }
1119
1120 return 0;
1121 }
1122
listNames() const1123 void Viewer::listNames() const {
1124 SkDebugf("All Slides:\n");
1125 for (const auto& slide : fSlides) {
1126 SkDebugf(" %s\n", slide->getName().c_str());
1127 }
1128 }
1129
setCurrentSlide(int slide)1130 void Viewer::setCurrentSlide(int slide) {
1131 SkASSERT(slide >= 0 && slide < fSlides.count());
1132
1133 if (slide == fCurrentSlide) {
1134 return;
1135 }
1136
1137 if (fCurrentSlide >= 0) {
1138 fSlides[fCurrentSlide]->unload();
1139 }
1140
1141 SkScalar scaleFactor = 1.0;
1142 if (fApplyBackingScale) {
1143 scaleFactor = fWindow->scaleFactor();
1144 }
1145 fSlides[slide]->load(SkIntToScalar(fWindow->width()) / scaleFactor,
1146 SkIntToScalar(fWindow->height()) / scaleFactor);
1147 fCurrentSlide = slide;
1148 this->setupCurrentSlide();
1149 }
1150
setupCurrentSlide()1151 void Viewer::setupCurrentSlide() {
1152 if (fCurrentSlide >= 0) {
1153 // prepare dimensions for image slides
1154 fGesture.resetTouchState();
1155 fDefaultMatrix.reset();
1156
1157 const SkISize slideSize = fSlides[fCurrentSlide]->getDimensions();
1158 const SkRect slideBounds = SkRect::MakeIWH(slideSize.width(), slideSize.height());
1159 const SkRect windowRect = SkRect::MakeIWH(fWindow->width(), fWindow->height());
1160
1161 // Start with a matrix that scales the slide to the available screen space
1162 if (fWindow->scaleContentToFit()) {
1163 if (windowRect.width() > 0 && windowRect.height() > 0) {
1164 fDefaultMatrix = SkMatrix::RectToRect(slideBounds, windowRect,
1165 SkMatrix::kStart_ScaleToFit);
1166 }
1167 }
1168
1169 // Prevent the user from dragging content so far outside the window they can't find it again
1170 fGesture.setTransLimit(slideBounds, windowRect, this->computePreTouchMatrix());
1171
1172 this->updateTitle();
1173 this->updateUIState();
1174
1175 fStatsLayer.resetMeasurements();
1176
1177 fWindow->inval();
1178 }
1179 }
1180
1181 #define MAX_ZOOM_LEVEL 8.0f
1182 #define MIN_ZOOM_LEVEL -8.0f
1183
changeZoomLevel(float delta)1184 void Viewer::changeZoomLevel(float delta) {
1185 fZoomLevel += delta;
1186 fZoomLevel = SkTPin(fZoomLevel, MIN_ZOOM_LEVEL, MAX_ZOOM_LEVEL);
1187 this->preTouchMatrixChanged();
1188 }
1189
preTouchMatrixChanged()1190 void Viewer::preTouchMatrixChanged() {
1191 // Update the trans limit as the transform changes.
1192 const SkISize slideSize = fSlides[fCurrentSlide]->getDimensions();
1193 const SkRect slideBounds = SkRect::MakeIWH(slideSize.width(), slideSize.height());
1194 const SkRect windowRect = SkRect::MakeIWH(fWindow->width(), fWindow->height());
1195 fGesture.setTransLimit(slideBounds, windowRect, this->computePreTouchMatrix());
1196 }
1197
computePerspectiveMatrix()1198 SkMatrix Viewer::computePerspectiveMatrix() {
1199 SkScalar w = fWindow->width(), h = fWindow->height();
1200 SkPoint orthoPts[4] = { { 0, 0 }, { w, 0 }, { 0, h }, { w, h } };
1201 SkPoint perspPts[4] = {
1202 { fPerspectivePoints[0].fX * w, fPerspectivePoints[0].fY * h },
1203 { fPerspectivePoints[1].fX * w, fPerspectivePoints[1].fY * h },
1204 { fPerspectivePoints[2].fX * w, fPerspectivePoints[2].fY * h },
1205 { fPerspectivePoints[3].fX * w, fPerspectivePoints[3].fY * h }
1206 };
1207 SkMatrix m;
1208 m.setPolyToPoly(orthoPts, perspPts, 4);
1209 return m;
1210 }
1211
computePreTouchMatrix()1212 SkMatrix Viewer::computePreTouchMatrix() {
1213 SkMatrix m = fDefaultMatrix;
1214
1215 SkScalar zoomScale = exp(fZoomLevel);
1216 if (fApplyBackingScale) {
1217 zoomScale *= fWindow->scaleFactor();
1218 }
1219 m.preTranslate((fOffset.x() - 0.5f) * 2.0f, (fOffset.y() - 0.5f) * 2.0f);
1220 m.preScale(zoomScale, zoomScale);
1221
1222 const SkISize slideSize = fSlides[fCurrentSlide]->getDimensions();
1223 m.preRotate(fRotation, slideSize.width() * 0.5f, slideSize.height() * 0.5f);
1224
1225 if (kPerspective_Real == fPerspectiveMode) {
1226 SkMatrix persp = this->computePerspectiveMatrix();
1227 m.postConcat(persp);
1228 }
1229
1230 return m;
1231 }
1232
computeMatrix()1233 SkMatrix Viewer::computeMatrix() {
1234 SkMatrix m = fGesture.localM();
1235 m.preConcat(fGesture.globalM());
1236 m.preConcat(this->computePreTouchMatrix());
1237 return m;
1238 }
1239
setBackend(sk_app::Window::BackendType backendType)1240 void Viewer::setBackend(sk_app::Window::BackendType backendType) {
1241 fPersistentCache.reset();
1242 fCachedShaders.reset();
1243 fBackendType = backendType;
1244
1245 // The active context is going away in 'detach'
1246 for(auto& slide : fSlides) {
1247 slide->gpuTeardown();
1248 }
1249
1250 fWindow->detach();
1251
1252 #if defined(SK_BUILD_FOR_WIN)
1253 // Switching between OpenGL, Vulkan, and ANGLE in the same window is problematic at this point
1254 // on Windows, so we just delete the window and recreate it.
1255 DisplayParams params = fWindow->getRequestedDisplayParams();
1256 delete fWindow;
1257 fWindow = Window::CreateNativeWindow(nullptr);
1258
1259 // re-register callbacks
1260 fCommands.attach(fWindow);
1261 fWindow->pushLayer(this);
1262 fWindow->pushLayer(&fStatsLayer);
1263 fWindow->pushLayer(&fImGuiLayer);
1264
1265 // Don't allow the window to re-attach. If we're in MSAA mode, the params we grabbed above
1266 // will still include our correct sample count. But the re-created fWindow will lose that
1267 // information. On Windows, we need to re-create the window when changing sample count,
1268 // so we'll incorrectly detect that situation, then re-initialize the window in GL mode,
1269 // rendering this tear-down step pointless (and causing the Vulkan window context to fail
1270 // as if we had never changed windows at all).
1271 fWindow->setRequestedDisplayParams(params, false);
1272 #endif
1273
1274 fWindow->attach(backend_type_for_window(fBackendType));
1275 }
1276
setColorMode(ColorMode colorMode)1277 void Viewer::setColorMode(ColorMode colorMode) {
1278 fColorMode = colorMode;
1279 this->updateTitle();
1280 fWindow->inval();
1281 }
1282
1283 class OveridePaintFilterCanvas : public SkPaintFilterCanvas {
1284 public:
OveridePaintFilterCanvas(SkCanvas * canvas,SkPaint * paint,Viewer::SkPaintFields * pfields,SkFont * font,Viewer::SkFontFields * ffields)1285 OveridePaintFilterCanvas(SkCanvas* canvas,
1286 SkPaint* paint, Viewer::SkPaintFields* pfields,
1287 SkFont* font, Viewer::SkFontFields* ffields)
1288 : SkPaintFilterCanvas(canvas)
1289 , fPaint(paint)
1290 , fPaintOverrides(pfields)
1291 , fFont(font)
1292 , fFontOverrides(ffields) {
1293 }
1294
filterTextBlob(const SkPaint & paint,const SkTextBlob * blob,sk_sp<SkTextBlob> * cache)1295 const SkTextBlob* filterTextBlob(const SkPaint& paint,
1296 const SkTextBlob* blob,
1297 sk_sp<SkTextBlob>* cache) {
1298 bool blobWillChange = false;
1299 for (SkTextBlobRunIterator it(blob); !it.done(); it.next()) {
1300 SkTCopyOnFirstWrite<SkFont> filteredFont(it.font());
1301 bool shouldDraw = this->filterFont(&filteredFont);
1302 if (it.font() != *filteredFont || !shouldDraw) {
1303 blobWillChange = true;
1304 break;
1305 }
1306 }
1307 if (!blobWillChange) {
1308 return blob;
1309 }
1310
1311 SkTextBlobBuilder builder;
1312 for (SkTextBlobRunIterator it(blob); !it.done(); it.next()) {
1313 SkTCopyOnFirstWrite<SkFont> filteredFont(it.font());
1314 bool shouldDraw = this->filterFont(&filteredFont);
1315 if (!shouldDraw) {
1316 continue;
1317 }
1318
1319 SkFont font = *filteredFont;
1320
1321 const SkTextBlobBuilder::RunBuffer& runBuffer
1322 = it.positioning() == SkTextBlobRunIterator::kDefault_Positioning
1323 ? builder.allocRunText(font, it.glyphCount(), it.offset().x(),it.offset().y(),
1324 it.textSize())
1325 : it.positioning() == SkTextBlobRunIterator::kHorizontal_Positioning
1326 ? builder.allocRunTextPosH(font, it.glyphCount(), it.offset().y(),
1327 it.textSize())
1328 : it.positioning() == SkTextBlobRunIterator::kFull_Positioning
1329 ? builder.allocRunTextPos(font, it.glyphCount(), it.textSize())
1330 : it.positioning() == SkTextBlobRunIterator::kRSXform_Positioning
1331 ? builder.allocRunTextRSXform(font, it.glyphCount(), it.textSize())
1332 : (SkASSERT_RELEASE(false), SkTextBlobBuilder::RunBuffer());
1333 uint32_t glyphCount = it.glyphCount();
1334 if (it.glyphs()) {
1335 size_t glyphSize = sizeof(decltype(*it.glyphs()));
1336 memcpy(runBuffer.glyphs, it.glyphs(), glyphCount * glyphSize);
1337 }
1338 if (it.pos()) {
1339 size_t posSize = sizeof(decltype(*it.pos()));
1340 unsigned posPerGlyph = it.scalarsPerGlyph();
1341 memcpy(runBuffer.pos, it.pos(), glyphCount * posPerGlyph * posSize);
1342 }
1343 if (it.text()) {
1344 size_t textSize = sizeof(decltype(*it.text()));
1345 uint32_t textCount = it.textSize();
1346 memcpy(runBuffer.utf8text, it.text(), textCount * textSize);
1347 }
1348 if (it.clusters()) {
1349 size_t clusterSize = sizeof(decltype(*it.clusters()));
1350 memcpy(runBuffer.clusters, it.clusters(), glyphCount * clusterSize);
1351 }
1352 }
1353 *cache = builder.make();
1354 return cache->get();
1355 }
onDrawTextBlob(const SkTextBlob * blob,SkScalar x,SkScalar y,const SkPaint & paint)1356 void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
1357 const SkPaint& paint) override {
1358 sk_sp<SkTextBlob> cache;
1359 this->SkPaintFilterCanvas::onDrawTextBlob(
1360 this->filterTextBlob(paint, blob, &cache), x, y, paint);
1361 }
filterFont(SkTCopyOnFirstWrite<SkFont> * font) const1362 bool filterFont(SkTCopyOnFirstWrite<SkFont>* font) const {
1363 if (fFontOverrides->fTypeface) {
1364 font->writable()->setTypeface(fFont->refTypeface());
1365 }
1366 if (fFontOverrides->fSize) {
1367 font->writable()->setSize(fFont->getSize());
1368 }
1369 if (fFontOverrides->fScaleX) {
1370 font->writable()->setScaleX(fFont->getScaleX());
1371 }
1372 if (fFontOverrides->fSkewX) {
1373 font->writable()->setSkewX(fFont->getSkewX());
1374 }
1375 if (fFontOverrides->fHinting) {
1376 font->writable()->setHinting(fFont->getHinting());
1377 }
1378 if (fFontOverrides->fEdging) {
1379 font->writable()->setEdging(fFont->getEdging());
1380 }
1381 if (fFontOverrides->fSubpixel) {
1382 font->writable()->setSubpixel(fFont->isSubpixel());
1383 }
1384 if (fFontOverrides->fForceAutoHinting) {
1385 font->writable()->setForceAutoHinting(fFont->isForceAutoHinting());
1386 }
1387 if (fFontOverrides->fEmbeddedBitmaps) {
1388 font->writable()->setEmbeddedBitmaps(fFont->isEmbeddedBitmaps());
1389 }
1390 if (fFontOverrides->fLinearMetrics) {
1391 font->writable()->setLinearMetrics(fFont->isLinearMetrics());
1392 }
1393 if (fFontOverrides->fEmbolden) {
1394 font->writable()->setEmbolden(fFont->isEmbolden());
1395 }
1396 if (fFontOverrides->fBaselineSnap) {
1397 font->writable()->setBaselineSnap(fFont->isBaselineSnap());
1398 }
1399
1400 return true; // we, currently, never elide a draw
1401 }
1402
onFilter(SkPaint & paint) const1403 bool onFilter(SkPaint& paint) const override {
1404 if (fPaintOverrides->fPathEffect) {
1405 paint.setPathEffect(fPaint->refPathEffect());
1406 }
1407 if (fPaintOverrides->fShader) {
1408 paint.setShader(fPaint->refShader());
1409 }
1410 if (fPaintOverrides->fMaskFilter) {
1411 paint.setMaskFilter(fPaint->refMaskFilter());
1412 }
1413 if (fPaintOverrides->fColorFilter) {
1414 paint.setColorFilter(fPaint->refColorFilter());
1415 }
1416 if (fPaintOverrides->fImageFilter) {
1417 paint.setImageFilter(fPaint->refImageFilter());
1418 }
1419 if (fPaintOverrides->fColor) {
1420 paint.setColor4f(fPaint->getColor4f());
1421 }
1422 if (fPaintOverrides->fStrokeWidth) {
1423 paint.setStrokeWidth(fPaint->getStrokeWidth());
1424 }
1425 if (fPaintOverrides->fMiterLimit) {
1426 paint.setStrokeMiter(fPaint->getStrokeMiter());
1427 }
1428 if (fPaintOverrides->fBlendMode) {
1429 paint.setBlendMode(fPaint->getBlendMode_or(SkBlendMode::kSrc));
1430 }
1431 if (fPaintOverrides->fAntiAlias) {
1432 paint.setAntiAlias(fPaint->isAntiAlias());
1433 }
1434 if (fPaintOverrides->fDither) {
1435 paint.setDither(fPaint->isDither());
1436 }
1437 if (fPaintOverrides->fForceRuntimeBlend) {
1438 if (skstd::optional<SkBlendMode> mode = paint.asBlendMode()) {
1439 paint.setBlender(GetRuntimeBlendForBlendMode(*mode));
1440 }
1441 }
1442 if (fPaintOverrides->fCapType) {
1443 paint.setStrokeCap(fPaint->getStrokeCap());
1444 }
1445 if (fPaintOverrides->fJoinType) {
1446 paint.setStrokeJoin(fPaint->getStrokeJoin());
1447 }
1448 if (fPaintOverrides->fStyle) {
1449 paint.setStyle(fPaint->getStyle());
1450 }
1451 return true; // we, currently, never elide a draw
1452 }
1453 SkPaint* fPaint;
1454 Viewer::SkPaintFields* fPaintOverrides;
1455 SkFont* fFont;
1456 Viewer::SkFontFields* fFontOverrides;
1457 };
1458
drawSlide(SkSurface * surface)1459 void Viewer::drawSlide(SkSurface* surface) {
1460 if (fCurrentSlide < 0) {
1461 return;
1462 }
1463
1464 SkAutoCanvasRestore autorestore(surface->getCanvas(), false);
1465
1466 // By default, we render directly into the window's surface/canvas
1467 SkSurface* slideSurface = surface;
1468 SkCanvas* slideCanvas = surface->getCanvas();
1469 fLastImage.reset();
1470
1471 // If we're in any of the color managed modes, construct the color space we're going to use
1472 sk_sp<SkColorSpace> colorSpace = nullptr;
1473 if (ColorMode::kLegacy != fColorMode) {
1474 skcms_Matrix3x3 toXYZ;
1475 SkAssertResult(fColorSpacePrimaries.toXYZD50(&toXYZ));
1476 colorSpace = SkColorSpace::MakeRGB(fColorSpaceTransferFn, toXYZ);
1477 }
1478
1479 if (fSaveToSKP) {
1480 SkPictureRecorder recorder;
1481 SkCanvas* recorderCanvas = recorder.beginRecording(
1482 SkRect::Make(fSlides[fCurrentSlide]->getDimensions()));
1483 fSlides[fCurrentSlide]->draw(recorderCanvas);
1484 sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture());
1485 SkFILEWStream stream("sample_app.skp");
1486 picture->serialize(&stream);
1487 fSaveToSKP = false;
1488 }
1489
1490 // Grab some things we'll need to make surfaces (for tiling or general offscreen rendering)
1491 SkColorType colorType;
1492 switch (fColorMode) {
1493 case ColorMode::kLegacy:
1494 case ColorMode::kColorManaged8888:
1495 colorType = kN32_SkColorType;
1496 break;
1497 case ColorMode::kColorManagedF16:
1498 colorType = kRGBA_F16_SkColorType;
1499 break;
1500 case ColorMode::kColorManagedF16Norm:
1501 colorType = kRGBA_F16Norm_SkColorType;
1502 break;
1503 }
1504
1505 auto make_surface = [=](int w, int h) {
1506 SkSurfaceProps props(fWindow->getRequestedDisplayParams().fSurfaceProps);
1507 slideCanvas->getProps(&props);
1508
1509 SkImageInfo info = SkImageInfo::Make(w, h, colorType, kPremul_SkAlphaType, colorSpace);
1510 return Window::kRaster_BackendType == this->fBackendType
1511 ? SkSurface::MakeRaster(info, &props)
1512 : slideCanvas->makeSurface(info, &props);
1513 };
1514
1515 // We need to render offscreen if we're...
1516 // ... in fake perspective or zooming (so we have a snapped copy of the results)
1517 // ... in any raster mode, because the window surface is actually GL
1518 // ... in any color managed mode, because we always make the window surface with no color space
1519 // ... or if the user explicitly requested offscreen rendering
1520 sk_sp<SkSurface> offscreenSurface = nullptr;
1521 if (kPerspective_Fake == fPerspectiveMode ||
1522 fShowZoomWindow ||
1523 Window::kRaster_BackendType == fBackendType ||
1524 colorSpace != nullptr ||
1525 FLAGS_offscreen) {
1526
1527 offscreenSurface = make_surface(fWindow->width(), fWindow->height());
1528 slideSurface = offscreenSurface.get();
1529 slideCanvas = offscreenSurface->getCanvas();
1530 }
1531
1532 SkPictureRecorder recorder;
1533 SkCanvas* recorderRestoreCanvas = nullptr;
1534 if (fDrawViaSerialize) {
1535 recorderRestoreCanvas = slideCanvas;
1536 slideCanvas = recorder.beginRecording(
1537 SkRect::Make(fSlides[fCurrentSlide]->getDimensions()));
1538 }
1539
1540 int count = slideCanvas->save();
1541 slideCanvas->clear(SK_ColorWHITE);
1542 // Time the painting logic of the slide
1543 fStatsLayer.beginTiming(fPaintTimer);
1544 if (fTiled) {
1545 int tileW = SkScalarCeilToInt(fWindow->width() * fTileScale.width());
1546 int tileH = SkScalarCeilToInt(fWindow->height() * fTileScale.height());
1547 for (int y = 0; y < fWindow->height(); y += tileH) {
1548 for (int x = 0; x < fWindow->width(); x += tileW) {
1549 SkAutoCanvasRestore acr(slideCanvas, true);
1550 slideCanvas->clipRect(SkRect::MakeXYWH(x, y, tileW, tileH));
1551 fSlides[fCurrentSlide]->draw(slideCanvas);
1552 }
1553 }
1554
1555 // Draw borders between tiles
1556 if (fDrawTileBoundaries) {
1557 SkPaint border;
1558 border.setColor(0x60FF00FF);
1559 border.setStyle(SkPaint::kStroke_Style);
1560 for (int y = 0; y < fWindow->height(); y += tileH) {
1561 for (int x = 0; x < fWindow->width(); x += tileW) {
1562 slideCanvas->drawRect(SkRect::MakeXYWH(x, y, tileW, tileH), border);
1563 }
1564 }
1565 }
1566 } else {
1567 slideCanvas->concat(this->computeMatrix());
1568 if (kPerspective_Real == fPerspectiveMode) {
1569 slideCanvas->clipRect(SkRect::MakeWH(fWindow->width(), fWindow->height()));
1570 }
1571 if (fPaintOverrides.overridesSomething() || fFontOverrides.overridesSomething()) {
1572 OveridePaintFilterCanvas filterCanvas(slideCanvas,
1573 &fPaint, &fPaintOverrides,
1574 &fFont, &fFontOverrides);
1575 fSlides[fCurrentSlide]->draw(&filterCanvas);
1576 } else {
1577 fSlides[fCurrentSlide]->draw(slideCanvas);
1578 }
1579 }
1580 fStatsLayer.endTiming(fPaintTimer);
1581 slideCanvas->restoreToCount(count);
1582
1583 if (recorderRestoreCanvas) {
1584 sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture());
1585 auto data = picture->serialize();
1586 slideCanvas = recorderRestoreCanvas;
1587 slideCanvas->drawPicture(SkPicture::MakeFromData(data.get()));
1588 }
1589
1590 // Force a flush so we can time that, too
1591 fStatsLayer.beginTiming(fFlushTimer);
1592 slideSurface->flushAndSubmit();
1593 fStatsLayer.endTiming(fFlushTimer);
1594
1595 // If we rendered offscreen, snap an image and push the results to the window's canvas
1596 if (offscreenSurface) {
1597 fLastImage = offscreenSurface->makeImageSnapshot();
1598
1599 SkCanvas* canvas = surface->getCanvas();
1600 SkPaint paint;
1601 paint.setBlendMode(SkBlendMode::kSrc);
1602 SkSamplingOptions sampling;
1603 int prePerspectiveCount = canvas->save();
1604 if (kPerspective_Fake == fPerspectiveMode) {
1605 sampling = SkSamplingOptions({1.0f/3, 1.0f/3});
1606 canvas->clear(SK_ColorWHITE);
1607 canvas->concat(this->computePerspectiveMatrix());
1608 }
1609 canvas->drawImage(fLastImage, 0, 0, sampling, &paint);
1610 canvas->restoreToCount(prePerspectiveCount);
1611 }
1612
1613 if (fShowSlideDimensions) {
1614 SkCanvas* canvas = surface->getCanvas();
1615 SkAutoCanvasRestore acr(canvas, true);
1616 canvas->concat(this->computeMatrix());
1617 SkRect r = SkRect::Make(fSlides[fCurrentSlide]->getDimensions());
1618 SkPaint paint;
1619 paint.setColor(0x40FFFF00);
1620 canvas->drawRect(r, paint);
1621 }
1622 }
1623
onBackendCreated()1624 void Viewer::onBackendCreated() {
1625 this->setupCurrentSlide();
1626 fWindow->show();
1627 }
1628
onPaint(SkSurface * surface)1629 void Viewer::onPaint(SkSurface* surface) {
1630 this->drawSlide(surface);
1631
1632 fCommands.drawHelp(surface->getCanvas());
1633
1634 this->drawImGui();
1635
1636 fLastImage.reset();
1637
1638 if (auto direct = fWindow->directContext()) {
1639 // Clean out cache items that haven't been used in more than 10 seconds.
1640 direct->performDeferredCleanup(std::chrono::seconds(10));
1641 }
1642 }
1643
onResize(int width,int height)1644 void Viewer::onResize(int width, int height) {
1645 if (fCurrentSlide >= 0) {
1646 SkScalar scaleFactor = 1.0;
1647 if (fApplyBackingScale) {
1648 scaleFactor = fWindow->scaleFactor();
1649 }
1650 fSlides[fCurrentSlide]->resize(width / scaleFactor, height / scaleFactor);
1651 }
1652 }
1653
mapEvent(float x,float y)1654 SkPoint Viewer::mapEvent(float x, float y) {
1655 const auto m = this->computeMatrix();
1656 SkMatrix inv;
1657
1658 SkAssertResult(m.invert(&inv));
1659
1660 return inv.mapXY(x, y);
1661 }
1662
onTouch(intptr_t owner,skui::InputState state,float x,float y)1663 bool Viewer::onTouch(intptr_t owner, skui::InputState state, float x, float y) {
1664 if (GestureDevice::kMouse == fGestureDevice) {
1665 return false;
1666 }
1667
1668 const auto slidePt = this->mapEvent(x, y);
1669 if (fSlides[fCurrentSlide]->onMouse(slidePt.x(), slidePt.y(), state, skui::ModifierKey::kNone)) {
1670 fWindow->inval();
1671 return true;
1672 }
1673
1674 void* castedOwner = reinterpret_cast<void*>(owner);
1675 switch (state) {
1676 case skui::InputState::kUp: {
1677 fGesture.touchEnd(castedOwner);
1678 #if defined(SK_BUILD_FOR_IOS)
1679 // TODO: move IOS swipe detection higher up into the platform code
1680 SkPoint dir;
1681 if (fGesture.isFling(&dir)) {
1682 // swiping left or right
1683 if (SkTAbs(dir.fX) > SkTAbs(dir.fY)) {
1684 if (dir.fX < 0) {
1685 this->setCurrentSlide(fCurrentSlide < fSlides.count() - 1 ?
1686 fCurrentSlide + 1 : 0);
1687 } else {
1688 this->setCurrentSlide(fCurrentSlide > 0 ?
1689 fCurrentSlide - 1 : fSlides.count() - 1);
1690 }
1691 }
1692 fGesture.reset();
1693 }
1694 #endif
1695 break;
1696 }
1697 case skui::InputState::kDown: {
1698 fGesture.touchBegin(castedOwner, x, y);
1699 break;
1700 }
1701 case skui::InputState::kMove: {
1702 fGesture.touchMoved(castedOwner, x, y);
1703 break;
1704 }
1705 default: {
1706 // kLeft and kRight are only for swipes
1707 SkASSERT(false);
1708 break;
1709 }
1710 }
1711 fGestureDevice = fGesture.isBeingTouched() ? GestureDevice::kTouch : GestureDevice::kNone;
1712 fWindow->inval();
1713 return true;
1714 }
1715
onMouse(int x,int y,skui::InputState state,skui::ModifierKey modifiers)1716 bool Viewer::onMouse(int x, int y, skui::InputState state, skui::ModifierKey modifiers) {
1717 if (GestureDevice::kTouch == fGestureDevice) {
1718 return false;
1719 }
1720
1721 const auto slidePt = this->mapEvent(x, y);
1722 if (fSlides[fCurrentSlide]->onMouse(slidePt.x(), slidePt.y(), state, modifiers)) {
1723 fWindow->inval();
1724 return true;
1725 }
1726
1727 switch (state) {
1728 case skui::InputState::kUp: {
1729 fGesture.touchEnd(nullptr);
1730 break;
1731 }
1732 case skui::InputState::kDown: {
1733 fGesture.touchBegin(nullptr, x, y);
1734 break;
1735 }
1736 case skui::InputState::kMove: {
1737 fGesture.touchMoved(nullptr, x, y);
1738 break;
1739 }
1740 default: {
1741 SkASSERT(false); // shouldn't see kRight or kLeft here
1742 break;
1743 }
1744 }
1745 fGestureDevice = fGesture.isBeingTouched() ? GestureDevice::kMouse : GestureDevice::kNone;
1746
1747 if (state != skui::InputState::kMove || fGesture.isBeingTouched()) {
1748 fWindow->inval();
1749 }
1750 return true;
1751 }
1752
onFling(skui::InputState state)1753 bool Viewer::onFling(skui::InputState state) {
1754 if (skui::InputState::kRight == state) {
1755 this->setCurrentSlide(fCurrentSlide > 0 ? fCurrentSlide - 1 : fSlides.count() - 1);
1756 return true;
1757 } else if (skui::InputState::kLeft == state) {
1758 this->setCurrentSlide(fCurrentSlide < fSlides.count() - 1 ? fCurrentSlide + 1 : 0);
1759 return true;
1760 }
1761 return false;
1762 }
1763
onPinch(skui::InputState state,float scale,float x,float y)1764 bool Viewer::onPinch(skui::InputState state, float scale, float x, float y) {
1765 switch (state) {
1766 case skui::InputState::kDown:
1767 fGesture.startZoom();
1768 return true;
1769 break;
1770 case skui::InputState::kMove:
1771 fGesture.updateZoom(scale, x, y, x, y);
1772 return true;
1773 break;
1774 case skui::InputState::kUp:
1775 fGesture.endZoom();
1776 return true;
1777 break;
1778 default:
1779 SkASSERT(false);
1780 break;
1781 }
1782
1783 return false;
1784 }
1785
ImGui_Primaries(SkColorSpacePrimaries * primaries,SkPaint * gamutPaint)1786 static void ImGui_Primaries(SkColorSpacePrimaries* primaries, SkPaint* gamutPaint) {
1787 // The gamut image covers a (0.8 x 0.9) shaped region
1788 ImGui::DragCanvas dc(primaries, { 0.0f, 0.9f }, { 0.8f, 0.0f });
1789
1790 // Background image. Only draw a subset of the image, to avoid the regions less than zero.
1791 // Simplifes re-mapping math, clipping behavior, and increases resolution in the useful area.
1792 // Magic numbers are pixel locations of the origin and upper-right corner.
1793 dc.fDrawList->AddImage(gamutPaint, dc.fPos,
1794 ImVec2(dc.fPos.x + dc.fSize.x, dc.fPos.y + dc.fSize.y),
1795 ImVec2(242, 61), ImVec2(1897, 1922));
1796
1797 dc.dragPoint((SkPoint*)(&primaries->fRX), true, 0xFF000040);
1798 dc.dragPoint((SkPoint*)(&primaries->fGX), true, 0xFF004000);
1799 dc.dragPoint((SkPoint*)(&primaries->fBX), true, 0xFF400000);
1800 dc.dragPoint((SkPoint*)(&primaries->fWX), true);
1801 dc.fDrawList->AddPolyline(dc.fScreenPoints.begin(), 3, 0xFFFFFFFF, true, 1.5f);
1802 }
1803
ImGui_DragLocation(SkPoint * pt)1804 static bool ImGui_DragLocation(SkPoint* pt) {
1805 ImGui::DragCanvas dc(pt);
1806 dc.fillColor(IM_COL32(0, 0, 0, 128));
1807 dc.dragPoint(pt);
1808 return dc.fDragging;
1809 }
1810
ImGui_DragQuad(SkPoint * pts)1811 static bool ImGui_DragQuad(SkPoint* pts) {
1812 ImGui::DragCanvas dc(pts);
1813 dc.fillColor(IM_COL32(0, 0, 0, 128));
1814
1815 for (int i = 0; i < 4; ++i) {
1816 dc.dragPoint(pts + i);
1817 }
1818
1819 dc.fDrawList->AddLine(dc.fScreenPoints[0], dc.fScreenPoints[1], 0xFFFFFFFF);
1820 dc.fDrawList->AddLine(dc.fScreenPoints[1], dc.fScreenPoints[3], 0xFFFFFFFF);
1821 dc.fDrawList->AddLine(dc.fScreenPoints[3], dc.fScreenPoints[2], 0xFFFFFFFF);
1822 dc.fDrawList->AddLine(dc.fScreenPoints[2], dc.fScreenPoints[0], 0xFFFFFFFF);
1823
1824 return dc.fDragging;
1825 }
1826
build_sksl_highlight_shader()1827 static SkSL::String build_sksl_highlight_shader() {
1828 return SkSL::String("out half4 sk_FragColor;\n"
1829 "void main() { sk_FragColor = half4(1, 0, 1, 0.5); }");
1830 }
1831
build_metal_highlight_shader(const SkSL::String & inShader)1832 static SkSL::String build_metal_highlight_shader(const SkSL::String& inShader) {
1833 // Metal fragment shaders need a lot of non-trivial boilerplate that we don't want to recompute
1834 // here. So keep all shader code, but right before `return *_out;`, swap out the sk_FragColor.
1835 size_t pos = inShader.rfind("return *_out;\n");
1836 if (pos == std::string::npos) {
1837 return inShader;
1838 }
1839
1840 SkSL::String replacementShader = inShader;
1841 replacementShader.insert(pos, "_out->sk_FragColor = float4(1.0, 0.0, 1.0, 0.5); ");
1842 return replacementShader;
1843 }
1844
build_glsl_highlight_shader(const GrShaderCaps & shaderCaps)1845 static SkSL::String build_glsl_highlight_shader(const GrShaderCaps& shaderCaps) {
1846 const char* versionDecl = shaderCaps.versionDeclString();
1847 SkSL::String highlight = versionDecl ? versionDecl : "";
1848 if (shaderCaps.usesPrecisionModifiers()) {
1849 highlight.append("precision mediump float;\n");
1850 }
1851 highlight.appendf("out vec4 sk_FragColor;\n"
1852 "void main() { sk_FragColor = vec4(1, 0, 1, 0.5); }");
1853 return highlight;
1854 }
1855
build_skvm_highlight_program(SkColorType ct,int nargs)1856 static skvm::Program build_skvm_highlight_program(SkColorType ct, int nargs) {
1857 // Code here is heavily tied to (and inspired by) SkVMBlitter::BuildProgram
1858 skvm::Builder b;
1859
1860 // All VM blitters start with two arguments (uniforms, dst surface)
1861 SkASSERT(nargs >= 2);
1862 (void)b.uniform();
1863 skvm::Ptr dst_ptr = b.varying(SkColorTypeBytesPerPixel(ct));
1864
1865 // Depending on coverage and shader, there can be additional arguments.
1866 // Make sure that we append the right number, so that we don't assert when
1867 // the CPU backend tries to run this program.
1868 for (int i = 2; i < nargs; ++i) {
1869 (void)b.uniform();
1870 }
1871
1872 skvm::Color magenta = {b.splat(1.0f), b.splat(0.0f), b.splat(1.0f), b.splat(0.5f)};
1873 skvm::PixelFormat dstFormat = skvm::SkColorType_to_PixelFormat(ct);
1874 store(dstFormat, dst_ptr, magenta);
1875
1876 return b.done();
1877 }
1878
drawImGui()1879 void Viewer::drawImGui() {
1880 // Support drawing the ImGui demo window. Superfluous, but gives a good idea of what's possible
1881 if (fShowImGuiTestWindow) {
1882 ImGui::ShowDemoWindow(&fShowImGuiTestWindow);
1883 }
1884
1885 if (fShowImGuiDebugWindow) {
1886 // We have some dynamic content that sizes to fill available size. If the scroll bar isn't
1887 // always visible, we can end up in a layout feedback loop.
1888 ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiCond_FirstUseEver);
1889 DisplayParams params = fWindow->getRequestedDisplayParams();
1890 bool displayParamsChanged = false; // heavy-weight, might recreate entire context
1891 bool uiParamsChanged = false; // light weight, just triggers window invalidation
1892 auto ctx = fWindow->directContext();
1893
1894 if (ImGui::Begin("Tools", &fShowImGuiDebugWindow,
1895 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
1896 if (ImGui::CollapsingHeader("Backend")) {
1897 int newBackend = static_cast<int>(fBackendType);
1898 ImGui::RadioButton("Raster", &newBackend, sk_app::Window::kRaster_BackendType);
1899 ImGui::SameLine();
1900 ImGui::RadioButton("OpenGL", &newBackend, sk_app::Window::kNativeGL_BackendType);
1901 #if SK_ANGLE && defined(SK_BUILD_FOR_WIN)
1902 ImGui::SameLine();
1903 ImGui::RadioButton("ANGLE", &newBackend, sk_app::Window::kANGLE_BackendType);
1904 #endif
1905 #if defined(SK_DAWN)
1906 ImGui::SameLine();
1907 ImGui::RadioButton("Dawn", &newBackend, sk_app::Window::kDawn_BackendType);
1908 #endif
1909 #if defined(SK_VULKAN) && !defined(SK_BUILD_FOR_MAC)
1910 ImGui::SameLine();
1911 ImGui::RadioButton("Vulkan", &newBackend, sk_app::Window::kVulkan_BackendType);
1912 #endif
1913 #if defined(SK_METAL)
1914 ImGui::SameLine();
1915 ImGui::RadioButton("Metal", &newBackend, sk_app::Window::kMetal_BackendType);
1916 #if defined(SK_GRAPHITE_ENABLED)
1917 ImGui::SameLine();
1918 ImGui::RadioButton("Metal (Graphite)", &newBackend,
1919 sk_app::Window::kGraphiteMetal_BackendType);
1920 #endif
1921 #endif
1922 #if defined(SK_DIRECT3D)
1923 ImGui::SameLine();
1924 ImGui::RadioButton("Direct3D", &newBackend, sk_app::Window::kDirect3D_BackendType);
1925 #endif
1926 if (newBackend != fBackendType) {
1927 fDeferredActions.push_back([=]() {
1928 this->setBackend(static_cast<sk_app::Window::BackendType>(newBackend));
1929 });
1930 }
1931
1932 bool* wire = ¶ms.fGrContextOptions.fWireframeMode;
1933 if (ctx && ImGui::Checkbox("Wireframe Mode", wire)) {
1934 displayParamsChanged = true;
1935 }
1936
1937 bool* reducedShaders = ¶ms.fGrContextOptions.fReducedShaderVariations;
1938 if (ctx && ImGui::Checkbox("Reduced shaders", reducedShaders)) {
1939 displayParamsChanged = true;
1940 }
1941
1942 if (ctx) {
1943 // Determine the context's max sample count for MSAA radio buttons.
1944 int sampleCount = fWindow->sampleCount();
1945 int maxMSAA = (fBackendType != sk_app::Window::kRaster_BackendType) ?
1946 ctx->maxSurfaceSampleCountForColorType(kRGBA_8888_SkColorType) :
1947 1;
1948
1949 // Only display the MSAA radio buttons when there are options above 1x MSAA.
1950 if (maxMSAA >= 4) {
1951 ImGui::Text("MSAA: ");
1952
1953 for (int curMSAA = 1; curMSAA <= maxMSAA; curMSAA *= 2) {
1954 // 2x MSAA works, but doesn't offer much of a visual improvement, so we
1955 // don't show it in the list.
1956 if (curMSAA == 2) {
1957 continue;
1958 }
1959 ImGui::SameLine();
1960 ImGui::RadioButton(SkStringPrintf("%d", curMSAA).c_str(),
1961 &sampleCount, curMSAA);
1962 }
1963 }
1964
1965 if (sampleCount != params.fMSAASampleCount) {
1966 params.fMSAASampleCount = sampleCount;
1967 displayParamsChanged = true;
1968 }
1969 }
1970
1971 int pixelGeometryIdx = 0;
1972 if (fDisplayOverrides.fSurfaceProps.fPixelGeometry) {
1973 pixelGeometryIdx = params.fSurfaceProps.pixelGeometry() + 1;
1974 }
1975 if (ImGui::Combo("Pixel Geometry", &pixelGeometryIdx,
1976 "Default\0Flat\0RGB\0BGR\0RGBV\0BGRV\0\0"))
1977 {
1978 uint32_t flags = params.fSurfaceProps.flags();
1979 if (pixelGeometryIdx == 0) {
1980 fDisplayOverrides.fSurfaceProps.fPixelGeometry = false;
1981 SkPixelGeometry pixelGeometry = fDisplay.fSurfaceProps.pixelGeometry();
1982 params.fSurfaceProps = SkSurfaceProps(flags, pixelGeometry);
1983 } else {
1984 fDisplayOverrides.fSurfaceProps.fPixelGeometry = true;
1985 SkPixelGeometry pixelGeometry = SkTo<SkPixelGeometry>(pixelGeometryIdx - 1);
1986 params.fSurfaceProps = SkSurfaceProps(flags, pixelGeometry);
1987 }
1988 displayParamsChanged = true;
1989 }
1990
1991 bool useDFT = params.fSurfaceProps.isUseDeviceIndependentFonts();
1992 if (ImGui::Checkbox("DFT", &useDFT)) {
1993 uint32_t flags = params.fSurfaceProps.flags();
1994 if (useDFT) {
1995 flags |= SkSurfaceProps::kUseDeviceIndependentFonts_Flag;
1996 } else {
1997 flags &= ~SkSurfaceProps::kUseDeviceIndependentFonts_Flag;
1998 }
1999 SkPixelGeometry pixelGeometry = params.fSurfaceProps.pixelGeometry();
2000 params.fSurfaceProps = SkSurfaceProps(flags, pixelGeometry);
2001 displayParamsChanged = true;
2002 }
2003
2004 if (ImGui::TreeNode("Path Renderers")) {
2005 GpuPathRenderers prevPr = params.fGrContextOptions.fGpuPathRenderers;
2006 auto prButton = [&](GpuPathRenderers x) {
2007 if (ImGui::RadioButton(gPathRendererNames[x].c_str(), prevPr == x)) {
2008 if (x != params.fGrContextOptions.fGpuPathRenderers) {
2009 params.fGrContextOptions.fGpuPathRenderers = x;
2010 displayParamsChanged = true;
2011 }
2012 }
2013 };
2014
2015 if (!ctx) {
2016 ImGui::RadioButton("Software", true);
2017 } else {
2018 prButton(GpuPathRenderers::kDefault);
2019 #if SK_GPU_V1
2020 if (fWindow->sampleCount() > 1 || FLAGS_dmsaa) {
2021 const auto* caps = ctx->priv().caps();
2022 if (skgpu::v1::AtlasPathRenderer::IsSupported(ctx)) {
2023 prButton(GpuPathRenderers::kAtlas);
2024 }
2025 if (skgpu::v1::TessellationPathRenderer::IsSupported(*caps)) {
2026 prButton(GpuPathRenderers::kTessellation);
2027 }
2028 }
2029 #endif
2030 if (1 == fWindow->sampleCount()) {
2031 prButton(GpuPathRenderers::kSmall);
2032 }
2033 prButton(GpuPathRenderers::kTriangulating);
2034 prButton(GpuPathRenderers::kNone);
2035 }
2036 ImGui::TreePop();
2037 }
2038 }
2039
2040 if (ImGui::CollapsingHeader("Tiling")) {
2041 ImGui::Checkbox("Enable", &fTiled);
2042 ImGui::Checkbox("Draw Boundaries", &fDrawTileBoundaries);
2043 ImGui::SliderFloat("Horizontal", &fTileScale.fWidth, 0.1f, 1.0f);
2044 ImGui::SliderFloat("Vertical", &fTileScale.fHeight, 0.1f, 1.0f);
2045 }
2046
2047 if (ImGui::CollapsingHeader("Transform")) {
2048 if (ImGui::Checkbox("Apply Backing Scale", &fApplyBackingScale)) {
2049 this->preTouchMatrixChanged();
2050 this->onResize(fWindow->width(), fWindow->height());
2051 // This changes how we manipulate the canvas transform, it's not changing the
2052 // window's actual parameters.
2053 uiParamsChanged = true;
2054 }
2055
2056 float zoom = fZoomLevel;
2057 if (ImGui::SliderFloat("Zoom", &zoom, MIN_ZOOM_LEVEL, MAX_ZOOM_LEVEL)) {
2058 fZoomLevel = zoom;
2059 this->preTouchMatrixChanged();
2060 uiParamsChanged = true;
2061 }
2062 float deg = fRotation;
2063 if (ImGui::SliderFloat("Rotate", °, -30, 360, "%.3f deg")) {
2064 fRotation = deg;
2065 this->preTouchMatrixChanged();
2066 uiParamsChanged = true;
2067 }
2068 if (ImGui::CollapsingHeader("Subpixel offset", ImGuiTreeNodeFlags_NoTreePushOnOpen)) {
2069 if (ImGui_DragLocation(&fOffset)) {
2070 this->preTouchMatrixChanged();
2071 uiParamsChanged = true;
2072 }
2073 } else if (fOffset != SkVector{0.5f, 0.5f}) {
2074 this->preTouchMatrixChanged();
2075 uiParamsChanged = true;
2076 fOffset = {0.5f, 0.5f};
2077 }
2078 int perspectiveMode = static_cast<int>(fPerspectiveMode);
2079 if (ImGui::Combo("Perspective", &perspectiveMode, "Off\0Real\0Fake\0\0")) {
2080 fPerspectiveMode = static_cast<PerspectiveMode>(perspectiveMode);
2081 this->preTouchMatrixChanged();
2082 uiParamsChanged = true;
2083 }
2084 if (perspectiveMode != kPerspective_Off && ImGui_DragQuad(fPerspectivePoints)) {
2085 this->preTouchMatrixChanged();
2086 uiParamsChanged = true;
2087 }
2088 }
2089
2090 if (ImGui::CollapsingHeader("Paint")) {
2091 int aliasIdx = 0;
2092 if (fPaintOverrides.fAntiAlias) {
2093 aliasIdx = SkTo<int>(fPaintOverrides.fAntiAliasState) + 1;
2094 }
2095 if (ImGui::Combo("Anti-Alias", &aliasIdx,
2096 "Default\0Alias\0Normal\0AnalyticAAEnabled\0AnalyticAAForced\0\0"))
2097 {
2098 gSkUseAnalyticAA = fPaintOverrides.fOriginalSkUseAnalyticAA;
2099 gSkForceAnalyticAA = fPaintOverrides.fOriginalSkForceAnalyticAA;
2100 if (aliasIdx == 0) {
2101 fPaintOverrides.fAntiAliasState = SkPaintFields::AntiAliasState::Alias;
2102 fPaintOverrides.fAntiAlias = false;
2103 } else {
2104 fPaintOverrides.fAntiAlias = true;
2105 fPaintOverrides.fAntiAliasState = SkTo<SkPaintFields::AntiAliasState>(aliasIdx-1);
2106 fPaint.setAntiAlias(aliasIdx > 1);
2107 switch (fPaintOverrides.fAntiAliasState) {
2108 case SkPaintFields::AntiAliasState::Alias:
2109 break;
2110 case SkPaintFields::AntiAliasState::Normal:
2111 break;
2112 case SkPaintFields::AntiAliasState::AnalyticAAEnabled:
2113 gSkUseAnalyticAA = true;
2114 gSkForceAnalyticAA = false;
2115 break;
2116 case SkPaintFields::AntiAliasState::AnalyticAAForced:
2117 gSkUseAnalyticAA = gSkForceAnalyticAA = true;
2118 break;
2119 }
2120 }
2121 uiParamsChanged = true;
2122 }
2123
2124 auto paintFlag = [this, &uiParamsChanged](const char* label, const char* items,
2125 bool SkPaintFields::* flag,
2126 bool (SkPaint::* isFlag)() const,
2127 void (SkPaint::* setFlag)(bool) )
2128 {
2129 int itemIndex = 0;
2130 if (fPaintOverrides.*flag) {
2131 itemIndex = (fPaint.*isFlag)() ? 2 : 1;
2132 }
2133 if (ImGui::Combo(label, &itemIndex, items)) {
2134 if (itemIndex == 0) {
2135 fPaintOverrides.*flag = false;
2136 } else {
2137 fPaintOverrides.*flag = true;
2138 (fPaint.*setFlag)(itemIndex == 2);
2139 }
2140 uiParamsChanged = true;
2141 }
2142 };
2143
2144 paintFlag("Dither",
2145 "Default\0No Dither\0Dither\0\0",
2146 &SkPaintFields::fDither,
2147 &SkPaint::isDither, &SkPaint::setDither);
2148
2149 int styleIdx = 0;
2150 if (fPaintOverrides.fStyle) {
2151 styleIdx = SkTo<int>(fPaint.getStyle()) + 1;
2152 }
2153 if (ImGui::Combo("Style", &styleIdx,
2154 "Default\0Fill\0Stroke\0Stroke and Fill\0\0"))
2155 {
2156 if (styleIdx == 0) {
2157 fPaintOverrides.fStyle = false;
2158 fPaint.setStyle(SkPaint::kFill_Style);
2159 } else {
2160 fPaint.setStyle(SkTo<SkPaint::Style>(styleIdx - 1));
2161 fPaintOverrides.fStyle = true;
2162 }
2163 uiParamsChanged = true;
2164 }
2165
2166 ImGui::Checkbox("Force Runtime Blends", &fPaintOverrides.fForceRuntimeBlend);
2167
2168 ImGui::Checkbox("Override Stroke Width", &fPaintOverrides.fStrokeWidth);
2169 if (fPaintOverrides.fStrokeWidth) {
2170 float width = fPaint.getStrokeWidth();
2171 if (ImGui::SliderFloat("Stroke Width", &width, 0, 20)) {
2172 fPaint.setStrokeWidth(width);
2173 uiParamsChanged = true;
2174 }
2175 }
2176
2177 ImGui::Checkbox("Override Miter Limit", &fPaintOverrides.fMiterLimit);
2178 if (fPaintOverrides.fMiterLimit) {
2179 float miterLimit = fPaint.getStrokeMiter();
2180 if (ImGui::SliderFloat("Miter Limit", &miterLimit, 0, 20)) {
2181 fPaint.setStrokeMiter(miterLimit);
2182 uiParamsChanged = true;
2183 }
2184 }
2185
2186 int capIdx = 0;
2187 if (fPaintOverrides.fCapType) {
2188 capIdx = SkTo<int>(fPaint.getStrokeCap()) + 1;
2189 }
2190 if (ImGui::Combo("Cap Type", &capIdx,
2191 "Default\0Butt\0Round\0Square\0\0"))
2192 {
2193 if (capIdx == 0) {
2194 fPaintOverrides.fCapType = false;
2195 fPaint.setStrokeCap(SkPaint::kDefault_Cap);
2196 } else {
2197 fPaint.setStrokeCap(SkTo<SkPaint::Cap>(capIdx - 1));
2198 fPaintOverrides.fCapType = true;
2199 }
2200 uiParamsChanged = true;
2201 }
2202
2203 int joinIdx = 0;
2204 if (fPaintOverrides.fJoinType) {
2205 joinIdx = SkTo<int>(fPaint.getStrokeJoin()) + 1;
2206 }
2207 if (ImGui::Combo("Join Type", &joinIdx,
2208 "Default\0Miter\0Round\0Bevel\0\0"))
2209 {
2210 if (joinIdx == 0) {
2211 fPaintOverrides.fJoinType = false;
2212 fPaint.setStrokeJoin(SkPaint::kDefault_Join);
2213 } else {
2214 fPaint.setStrokeJoin(SkTo<SkPaint::Join>(joinIdx - 1));
2215 fPaintOverrides.fJoinType = true;
2216 }
2217 uiParamsChanged = true;
2218 }
2219 }
2220
2221 if (ImGui::CollapsingHeader("Font")) {
2222 int hintingIdx = 0;
2223 if (fFontOverrides.fHinting) {
2224 hintingIdx = SkTo<int>(fFont.getHinting()) + 1;
2225 }
2226 if (ImGui::Combo("Hinting", &hintingIdx,
2227 "Default\0None\0Slight\0Normal\0Full\0\0"))
2228 {
2229 if (hintingIdx == 0) {
2230 fFontOverrides.fHinting = false;
2231 fFont.setHinting(SkFontHinting::kNone);
2232 } else {
2233 fFont.setHinting(SkTo<SkFontHinting>(hintingIdx - 1));
2234 fFontOverrides.fHinting = true;
2235 }
2236 uiParamsChanged = true;
2237 }
2238
2239 auto fontFlag = [this, &uiParamsChanged](const char* label, const char* items,
2240 bool SkFontFields::* flag,
2241 bool (SkFont::* isFlag)() const,
2242 void (SkFont::* setFlag)(bool) )
2243 {
2244 int itemIndex = 0;
2245 if (fFontOverrides.*flag) {
2246 itemIndex = (fFont.*isFlag)() ? 2 : 1;
2247 }
2248 if (ImGui::Combo(label, &itemIndex, items)) {
2249 if (itemIndex == 0) {
2250 fFontOverrides.*flag = false;
2251 } else {
2252 fFontOverrides.*flag = true;
2253 (fFont.*setFlag)(itemIndex == 2);
2254 }
2255 uiParamsChanged = true;
2256 }
2257 };
2258
2259 fontFlag("Fake Bold Glyphs",
2260 "Default\0No Fake Bold\0Fake Bold\0\0",
2261 &SkFontFields::fEmbolden,
2262 &SkFont::isEmbolden, &SkFont::setEmbolden);
2263
2264 fontFlag("Baseline Snapping",
2265 "Default\0No Baseline Snapping\0Baseline Snapping\0\0",
2266 &SkFontFields::fBaselineSnap,
2267 &SkFont::isBaselineSnap, &SkFont::setBaselineSnap);
2268
2269 fontFlag("Linear Text",
2270 "Default\0No Linear Text\0Linear Text\0\0",
2271 &SkFontFields::fLinearMetrics,
2272 &SkFont::isLinearMetrics, &SkFont::setLinearMetrics);
2273
2274 fontFlag("Subpixel Position Glyphs",
2275 "Default\0Pixel Text\0Subpixel Text\0\0",
2276 &SkFontFields::fSubpixel,
2277 &SkFont::isSubpixel, &SkFont::setSubpixel);
2278
2279 fontFlag("Embedded Bitmap Text",
2280 "Default\0No Embedded Bitmaps\0Embedded Bitmaps\0\0",
2281 &SkFontFields::fEmbeddedBitmaps,
2282 &SkFont::isEmbeddedBitmaps, &SkFont::setEmbeddedBitmaps);
2283
2284 fontFlag("Force Auto-Hinting",
2285 "Default\0No Force Auto-Hinting\0Force Auto-Hinting\0\0",
2286 &SkFontFields::fForceAutoHinting,
2287 &SkFont::isForceAutoHinting, &SkFont::setForceAutoHinting);
2288
2289 int edgingIdx = 0;
2290 if (fFontOverrides.fEdging) {
2291 edgingIdx = SkTo<int>(fFont.getEdging()) + 1;
2292 }
2293 if (ImGui::Combo("Edging", &edgingIdx,
2294 "Default\0Alias\0Antialias\0Subpixel Antialias\0\0"))
2295 {
2296 if (edgingIdx == 0) {
2297 fFontOverrides.fEdging = false;
2298 fFont.setEdging(SkFont::Edging::kAlias);
2299 } else {
2300 fFont.setEdging(SkTo<SkFont::Edging>(edgingIdx-1));
2301 fFontOverrides.fEdging = true;
2302 }
2303 uiParamsChanged = true;
2304 }
2305
2306 ImGui::Checkbox("Override Size", &fFontOverrides.fSize);
2307 if (fFontOverrides.fSize) {
2308 ImGui::DragFloat2("TextRange", fFontOverrides.fSizeRange,
2309 0.001f, -10.0f, 300.0f, "%.6f", 2.0f);
2310 float textSize = fFont.getSize();
2311 if (ImGui::DragFloat("TextSize", &textSize, 0.001f,
2312 fFontOverrides.fSizeRange[0],
2313 fFontOverrides.fSizeRange[1],
2314 "%.6f", 2.0f))
2315 {
2316 fFont.setSize(textSize);
2317 uiParamsChanged = true;
2318 }
2319 }
2320
2321 ImGui::Checkbox("Override ScaleX", &fFontOverrides.fScaleX);
2322 if (fFontOverrides.fScaleX) {
2323 float scaleX = fFont.getScaleX();
2324 if (ImGui::SliderFloat("ScaleX", &scaleX, MIN_ZOOM_LEVEL, MAX_ZOOM_LEVEL)) {
2325 fFont.setScaleX(scaleX);
2326 uiParamsChanged = true;
2327 }
2328 }
2329
2330 ImGui::Checkbox("Override SkewX", &fFontOverrides.fSkewX);
2331 if (fFontOverrides.fSkewX) {
2332 float skewX = fFont.getSkewX();
2333 if (ImGui::SliderFloat("SkewX", &skewX, MIN_ZOOM_LEVEL, MAX_ZOOM_LEVEL)) {
2334 fFont.setSkewX(skewX);
2335 uiParamsChanged = true;
2336 }
2337 }
2338 }
2339
2340 {
2341 SkMetaData controls;
2342 if (fSlides[fCurrentSlide]->onGetControls(&controls)) {
2343 if (ImGui::CollapsingHeader("Current Slide")) {
2344 SkMetaData::Iter iter(controls);
2345 const char* name;
2346 SkMetaData::Type type;
2347 int count;
2348 while ((name = iter.next(&type, &count)) != nullptr) {
2349 if (type == SkMetaData::kScalar_Type) {
2350 float val[3];
2351 SkASSERT(count == 3);
2352 controls.findScalars(name, &count, val);
2353 if (ImGui::SliderFloat(name, &val[0], val[1], val[2])) {
2354 controls.setScalars(name, 3, val);
2355 }
2356 } else if (type == SkMetaData::kBool_Type) {
2357 bool val;
2358 SkASSERT(count == 1);
2359 controls.findBool(name, &val);
2360 if (ImGui::Checkbox(name, &val)) {
2361 controls.setBool(name, val);
2362 }
2363 }
2364 }
2365 fSlides[fCurrentSlide]->onSetControls(controls);
2366 }
2367 }
2368 }
2369
2370 if (fShowSlidePicker) {
2371 ImGui::SetNextTreeNodeOpen(true);
2372 }
2373 if (ImGui::CollapsingHeader("Slide")) {
2374 static ImGuiTextFilter filter;
2375 static ImVector<const char*> filteredSlideNames;
2376 static ImVector<int> filteredSlideIndices;
2377
2378 if (fShowSlidePicker) {
2379 ImGui::SetKeyboardFocusHere();
2380 fShowSlidePicker = false;
2381 }
2382
2383 filter.Draw();
2384 filteredSlideNames.clear();
2385 filteredSlideIndices.clear();
2386 int filteredIndex = 0;
2387 for (int i = 0; i < fSlides.count(); ++i) {
2388 const char* slideName = fSlides[i]->getName().c_str();
2389 if (filter.PassFilter(slideName) || i == fCurrentSlide) {
2390 if (i == fCurrentSlide) {
2391 filteredIndex = filteredSlideIndices.size();
2392 }
2393 filteredSlideNames.push_back(slideName);
2394 filteredSlideIndices.push_back(i);
2395 }
2396 }
2397
2398 if (ImGui::ListBox("", &filteredIndex, filteredSlideNames.begin(),
2399 filteredSlideNames.size(), 20)) {
2400 this->setCurrentSlide(filteredSlideIndices[filteredIndex]);
2401 }
2402 }
2403
2404 if (ImGui::CollapsingHeader("Color Mode")) {
2405 ColorMode newMode = fColorMode;
2406 auto cmButton = [&](ColorMode mode, const char* label) {
2407 if (ImGui::RadioButton(label, mode == fColorMode)) {
2408 newMode = mode;
2409 }
2410 };
2411
2412 cmButton(ColorMode::kLegacy, "Legacy 8888");
2413 cmButton(ColorMode::kColorManaged8888, "Color Managed 8888");
2414 cmButton(ColorMode::kColorManagedF16, "Color Managed F16");
2415 cmButton(ColorMode::kColorManagedF16Norm, "Color Managed F16 Norm");
2416
2417 if (newMode != fColorMode) {
2418 this->setColorMode(newMode);
2419 }
2420
2421 // Pick from common gamuts:
2422 int primariesIdx = 4; // Default: Custom
2423 for (size_t i = 0; i < SK_ARRAY_COUNT(gNamedPrimaries); ++i) {
2424 if (primaries_equal(*gNamedPrimaries[i].fPrimaries, fColorSpacePrimaries)) {
2425 primariesIdx = i;
2426 break;
2427 }
2428 }
2429
2430 // Let user adjust the gamma
2431 ImGui::SliderFloat("Gamma", &fColorSpaceTransferFn.g, 0.5f, 3.5f);
2432
2433 if (ImGui::Combo("Primaries", &primariesIdx,
2434 "sRGB\0AdobeRGB\0P3\0Rec. 2020\0Custom\0\0")) {
2435 if (primariesIdx >= 0 && primariesIdx <= 3) {
2436 fColorSpacePrimaries = *gNamedPrimaries[primariesIdx].fPrimaries;
2437 }
2438 }
2439
2440 if (ImGui::Button("Spin")) {
2441 float rx = fColorSpacePrimaries.fRX,
2442 ry = fColorSpacePrimaries.fRY;
2443 fColorSpacePrimaries.fRX = fColorSpacePrimaries.fGX;
2444 fColorSpacePrimaries.fRY = fColorSpacePrimaries.fGY;
2445 fColorSpacePrimaries.fGX = fColorSpacePrimaries.fBX;
2446 fColorSpacePrimaries.fGY = fColorSpacePrimaries.fBY;
2447 fColorSpacePrimaries.fBX = rx;
2448 fColorSpacePrimaries.fBY = ry;
2449 }
2450
2451 // Allow direct editing of gamut
2452 ImGui_Primaries(&fColorSpacePrimaries, &fImGuiGamutPaint);
2453 }
2454
2455 if (ImGui::CollapsingHeader("Animation")) {
2456 bool isPaused = AnimTimer::kPaused_State == fAnimTimer.state();
2457 if (ImGui::Checkbox("Pause", &isPaused)) {
2458 fAnimTimer.togglePauseResume();
2459 }
2460
2461 float speed = fAnimTimer.getSpeed();
2462 if (ImGui::DragFloat("Speed", &speed, 0.1f)) {
2463 fAnimTimer.setSpeed(speed);
2464 }
2465 }
2466
2467 if (ImGui::CollapsingHeader("Shaders")) {
2468 bool sksl = params.fGrContextOptions.fShaderCacheStrategy ==
2469 GrContextOptions::ShaderCacheStrategy::kSkSL;
2470
2471 #if defined(SK_VULKAN)
2472 const bool isVulkan = fBackendType == sk_app::Window::kVulkan_BackendType;
2473 #else
2474 const bool isVulkan = false;
2475 #endif
2476
2477 // To re-load shaders from the currently active programs, we flush all
2478 // caches on one frame, then set a flag to poll the cache on the next frame.
2479 static bool gLoadPending = false;
2480 if (gLoadPending) {
2481 auto collectShaders = [this](sk_sp<const SkData> key, sk_sp<SkData> data,
2482 const SkString& description, int hitCount) {
2483 CachedShader& entry(fCachedShaders.push_back());
2484 entry.fKey = key;
2485 SkMD5 hash;
2486 hash.write(key->bytes(), key->size());
2487 SkMD5::Digest digest = hash.finish();
2488 for (int i = 0; i < 16; ++i) {
2489 entry.fKeyString.appendf("%02x", digest.data[i]);
2490 }
2491 entry.fKeyDescription = description;
2492
2493 SkReadBuffer reader(data->data(), data->size());
2494 entry.fShaderType = GrPersistentCacheUtils::GetType(&reader);
2495 GrPersistentCacheUtils::UnpackCachedShaders(&reader, entry.fShader,
2496 entry.fInputs,
2497 kGrShaderTypeCount);
2498 };
2499 fCachedShaders.reset();
2500 fPersistentCache.foreach(collectShaders);
2501 gLoadPending = false;
2502
2503 #if defined(SK_VULKAN)
2504 if (isVulkan && !sksl) {
2505 spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_0);
2506 for (auto& entry : fCachedShaders) {
2507 for (int i = 0; i < kGrShaderTypeCount; ++i) {
2508 const SkSL::String& spirv(entry.fShader[i]);
2509 std::string disasm;
2510 tools.Disassemble((const uint32_t*)spirv.c_str(), spirv.size() / 4,
2511 &disasm);
2512 entry.fShader[i].assign(disasm);
2513 }
2514 }
2515 }
2516 #endif
2517 }
2518
2519 // Defer actually doing the View/Apply logic so that we can trigger an Apply when we
2520 // start or finish hovering on a tree node in the list below:
2521 bool doView = ImGui::Button("View"); ImGui::SameLine();
2522 bool doApply = ImGui::Button("Apply Changes"); ImGui::SameLine();
2523 bool doDump = ImGui::Button("Dump SkSL to resources/sksl/");
2524
2525 int newOptLevel = fOptLevel;
2526 ImGui::RadioButton("SkSL", &newOptLevel, kShaderOptLevel_Source);
2527 ImGui::SameLine();
2528 ImGui::RadioButton("Compile", &newOptLevel, kShaderOptLevel_Compile);
2529 ImGui::SameLine();
2530 ImGui::RadioButton("Optimize", &newOptLevel, kShaderOptLevel_Optimize);
2531 ImGui::SameLine();
2532 ImGui::RadioButton("Inline", &newOptLevel, kShaderOptLevel_Inline);
2533
2534 // If we are changing the compile mode, we want to reset the cache and redo
2535 // everything.
2536 if (doDump || newOptLevel != fOptLevel) {
2537 sksl = doDump || (newOptLevel == kShaderOptLevel_Source);
2538 fOptLevel = (ShaderOptLevel)newOptLevel;
2539 switch (fOptLevel) {
2540 case kShaderOptLevel_Source:
2541 Compiler::EnableOptimizer(OverrideFlag::kDefault);
2542 Compiler::EnableInliner(OverrideFlag::kDefault);
2543 break;
2544 case kShaderOptLevel_Compile:
2545 Compiler::EnableOptimizer(OverrideFlag::kOff);
2546 Compiler::EnableInliner(OverrideFlag::kOff);
2547 break;
2548 case kShaderOptLevel_Optimize:
2549 Compiler::EnableOptimizer(OverrideFlag::kOn);
2550 Compiler::EnableInliner(OverrideFlag::kOff);
2551 break;
2552 case kShaderOptLevel_Inline:
2553 Compiler::EnableOptimizer(OverrideFlag::kOn);
2554 Compiler::EnableInliner(OverrideFlag::kOn);
2555 break;
2556 }
2557
2558 params.fGrContextOptions.fShaderCacheStrategy =
2559 sksl ? GrContextOptions::ShaderCacheStrategy::kSkSL
2560 : GrContextOptions::ShaderCacheStrategy::kBackendSource;
2561 displayParamsChanged = true;
2562 doView = true;
2563
2564 fDeferredActions.push_back([=]() {
2565 // Reset the cache.
2566 fPersistentCache.reset();
2567 // Dump the cache once we have drawn a frame with it.
2568 if (doDump) {
2569 fDeferredActions.push_back([this]() {
2570 this->dumpShadersToResources();
2571 });
2572 }
2573 });
2574 }
2575
2576 ImGui::BeginChild("##ScrollingRegion");
2577 for (auto& entry : fCachedShaders) {
2578 bool inTreeNode = ImGui::TreeNode(entry.fKeyString.c_str());
2579 bool hovered = ImGui::IsItemHovered();
2580 if (hovered != entry.fHovered) {
2581 // Force an Apply to patch the highlight shader in/out
2582 entry.fHovered = hovered;
2583 doApply = true;
2584 }
2585 if (inTreeNode) {
2586 auto stringBox = [](const char* label, std::string* str) {
2587 // Full width, and not too much space for each shader
2588 int lines = std::count(str->begin(), str->end(), '\n') + 2;
2589 ImVec2 boxSize(-1.0f, ImGui::GetTextLineHeight() * std::min(lines, 30));
2590 ImGui::InputTextMultiline(label, str, boxSize);
2591 };
2592 if (ImGui::TreeNode("Key")) {
2593 ImGui::TextWrapped("%s", entry.fKeyDescription.c_str());
2594 ImGui::TreePop();
2595 }
2596 stringBox("##VP", &entry.fShader[kVertex_GrShaderType]);
2597 stringBox("##FP", &entry.fShader[kFragment_GrShaderType]);
2598 ImGui::TreePop();
2599 }
2600 }
2601 ImGui::EndChild();
2602
2603 if (doView) {
2604 fPersistentCache.reset();
2605 ctx->priv().getGpu()->resetShaderCacheForTesting();
2606 gLoadPending = true;
2607 }
2608
2609 // We don't support updating SPIRV shaders. We could re-assemble them (with edits),
2610 // but I'm not sure anyone wants to do that.
2611 if (isVulkan && !sksl) {
2612 doApply = false;
2613 }
2614 if (doApply) {
2615 fPersistentCache.reset();
2616 ctx->priv().getGpu()->resetShaderCacheForTesting();
2617 for (auto& entry : fCachedShaders) {
2618 SkSL::String backup = entry.fShader[kFragment_GrShaderType];
2619 if (entry.fHovered) {
2620 // The hovered item (if any) gets a special shader to make it
2621 // identifiable.
2622 SkSL::String& fragShader = entry.fShader[kFragment_GrShaderType];
2623 switch (entry.fShaderType) {
2624 case SkSetFourByteTag('S', 'K', 'S', 'L'): {
2625 fragShader = build_sksl_highlight_shader();
2626 break;
2627 }
2628 case SkSetFourByteTag('G', 'L', 'S', 'L'): {
2629 fragShader = build_glsl_highlight_shader(
2630 *ctx->priv().caps()->shaderCaps());
2631 break;
2632 }
2633 case SkSetFourByteTag('M', 'S', 'L', ' '): {
2634 fragShader = build_metal_highlight_shader(fragShader);
2635 break;
2636 }
2637 }
2638 }
2639
2640 auto data = GrPersistentCacheUtils::PackCachedShaders(entry.fShaderType,
2641 entry.fShader,
2642 entry.fInputs,
2643 kGrShaderTypeCount);
2644 fPersistentCache.store(*entry.fKey, *data, entry.fKeyDescription);
2645
2646 entry.fShader[kFragment_GrShaderType] = backup;
2647 }
2648 }
2649 }
2650
2651 if (ImGui::CollapsingHeader("SkVM")) {
2652 auto* cache = SkVMBlitter::TryAcquireProgramCache();
2653 SkASSERT(cache);
2654
2655 if (ImGui::Button("Clear")) {
2656 cache->reset();
2657 fDisassemblyCache.reset();
2658 }
2659
2660 // First, go through the cache and restore the original program if we were hovering
2661 if (!fHoveredProgram.empty()) {
2662 auto restoreHoveredProgram = [this](const SkVMBlitter::Key* key,
2663 skvm::Program* program) {
2664 if (*key == fHoveredKey) {
2665 *program = std::move(fHoveredProgram);
2666 fHoveredProgram = {};
2667 }
2668 };
2669 cache->foreach(restoreHoveredProgram);
2670 }
2671
2672 // Now iterate again, and dump any expanded program. If any program is hovered,
2673 // patch it, and remember the original (so it can be restored next frame).
2674 auto showVMEntry = [this](const SkVMBlitter::Key* key, skvm::Program* program) {
2675 SkString keyString = SkVMBlitter::DebugName(*key);
2676 bool inTreeNode = ImGui::TreeNode(keyString.c_str());
2677 bool hovered = ImGui::IsItemHovered();
2678
2679 if (inTreeNode) {
2680 auto stringBox = [](const char* label, std::string* str) {
2681 int lines = std::count(str->begin(), str->end(), '\n') + 2;
2682 ImVec2 boxSize(-1.0f, ImGui::GetTextLineHeight() * std::min(lines, 30));
2683 ImGui::InputTextMultiline(label, str, boxSize);
2684 };
2685
2686 SkDynamicMemoryWStream stream;
2687 program->dump(&stream);
2688 auto dumpData = stream.detachAsData();
2689 std::string dumpString((const char*)dumpData->data(), dumpData->size());
2690 stringBox("##VM", &dumpString);
2691
2692 #if defined(SKVM_JIT)
2693 std::string* asmString = fDisassemblyCache.find(*key);
2694 if (!asmString) {
2695 program->disassemble(&stream);
2696 auto asmData = stream.detachAsData();
2697 asmString = fDisassemblyCache.set(
2698 *key,
2699 std::string((const char*)asmData->data(), asmData->size()));
2700 }
2701 stringBox("##ASM", asmString);
2702 #endif
2703
2704 ImGui::TreePop();
2705 }
2706 if (hovered) {
2707 // Generate a new blitter that just draws magenta
2708 skvm::Program highlightProgram = build_skvm_highlight_program(
2709 static_cast<SkColorType>(key->colorType), program->nargs());
2710
2711 fHoveredKey = *key;
2712 fHoveredProgram = std::move(*program);
2713 *program = std::move(highlightProgram);
2714 }
2715 };
2716 cache->foreach(showVMEntry);
2717
2718 SkVMBlitter::ReleaseProgramCache();
2719 }
2720 }
2721 if (displayParamsChanged || uiParamsChanged) {
2722 fDeferredActions.push_back([=]() {
2723 if (displayParamsChanged) {
2724 fWindow->setRequestedDisplayParams(params);
2725 }
2726 fWindow->inval();
2727 this->updateTitle();
2728 });
2729 }
2730 ImGui::End();
2731 }
2732
2733 if (gShaderErrorHandler.fErrors.count()) {
2734 ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiCond_FirstUseEver);
2735 ImGui::Begin("Shader Errors", nullptr, ImGuiWindowFlags_NoFocusOnAppearing);
2736 for (int i = 0; i < gShaderErrorHandler.fErrors.count(); ++i) {
2737 ImGui::TextWrapped("%s", gShaderErrorHandler.fErrors[i].c_str());
2738 SkSL::String sksl(gShaderErrorHandler.fShaders[i].c_str());
2739 GrShaderUtils::VisitLineByLine(sksl, [](int lineNumber, const char* lineText) {
2740 ImGui::TextWrapped("%4i\t%s\n", lineNumber, lineText);
2741 });
2742 }
2743 ImGui::End();
2744 gShaderErrorHandler.reset();
2745 }
2746
2747 if (fShowZoomWindow && fLastImage) {
2748 ImGui::SetNextWindowSize(ImVec2(200, 200), ImGuiCond_FirstUseEver);
2749 if (ImGui::Begin("Zoom", &fShowZoomWindow)) {
2750 static int zoomFactor = 8;
2751 if (ImGui::Button("<<")) {
2752 zoomFactor = std::max(zoomFactor / 2, 4);
2753 }
2754 ImGui::SameLine(); ImGui::Text("%2d", zoomFactor); ImGui::SameLine();
2755 if (ImGui::Button(">>")) {
2756 zoomFactor = std::min(zoomFactor * 2, 32);
2757 }
2758
2759 if (!fZoomWindowFixed) {
2760 ImVec2 mousePos = ImGui::GetMousePos();
2761 fZoomWindowLocation = SkPoint::Make(mousePos.x, mousePos.y);
2762 }
2763 SkScalar x = fZoomWindowLocation.x();
2764 SkScalar y = fZoomWindowLocation.y();
2765 int xInt = SkScalarRoundToInt(x);
2766 int yInt = SkScalarRoundToInt(y);
2767 ImVec2 avail = ImGui::GetContentRegionAvail();
2768
2769 uint32_t pixel = 0;
2770 SkImageInfo info = SkImageInfo::MakeN32Premul(1, 1);
2771 auto dContext = fWindow->directContext();
2772 if (fLastImage->readPixels(dContext, info, &pixel, info.minRowBytes(), xInt, yInt)) {
2773 ImGui::SameLine();
2774 ImGui::Text("(X, Y): %d, %d RGBA: %X %X %X %X",
2775 xInt, yInt,
2776 SkGetPackedR32(pixel), SkGetPackedG32(pixel),
2777 SkGetPackedB32(pixel), SkGetPackedA32(pixel));
2778 }
2779
2780 fImGuiLayer.skiaWidget(avail, [=, lastImage = fLastImage](SkCanvas* c) {
2781 // Translate so the region of the image that's under the mouse cursor is centered
2782 // in the zoom canvas:
2783 c->scale(zoomFactor, zoomFactor);
2784 c->translate(avail.x * 0.5f / zoomFactor - x - 0.5f,
2785 avail.y * 0.5f / zoomFactor - y - 0.5f);
2786 c->drawImage(lastImage, 0, 0);
2787
2788 SkPaint outline;
2789 outline.setStyle(SkPaint::kStroke_Style);
2790 c->drawRect(SkRect::MakeXYWH(x, y, 1, 1), outline);
2791 });
2792 }
2793
2794 ImGui::End();
2795 }
2796 }
2797
dumpShadersToResources()2798 void Viewer::dumpShadersToResources() {
2799 // Sort the list of cached shaders so we can maintain some minimal level of consistency.
2800 // It doesn't really matter, but it will keep files from switching places unpredictably.
2801 std::vector<const CachedShader*> shaders;
2802 shaders.reserve(fCachedShaders.size());
2803 for (const CachedShader& shader : fCachedShaders) {
2804 shaders.push_back(&shader);
2805 }
2806
2807 std::sort(shaders.begin(), shaders.end(), [](const CachedShader* a, const CachedShader* b) {
2808 return std::tie(a->fShader[kFragment_GrShaderType], a->fShader[kVertex_GrShaderType]) <
2809 std::tie(b->fShader[kFragment_GrShaderType], b->fShader[kVertex_GrShaderType]);
2810 });
2811
2812 // Make the resources/sksl/SlideName/ directory.
2813 SkString directory = SkStringPrintf("%ssksl/%s",
2814 GetResourcePath().c_str(),
2815 fSlides[fCurrentSlide]->getName().c_str());
2816 if (!sk_mkdir(directory.c_str())) {
2817 SkDEBUGFAILF("Unable to create directory '%s'", directory.c_str());
2818 return;
2819 }
2820
2821 int index = 0;
2822 for (const auto& entry : shaders) {
2823 SkString vertPath = SkStringPrintf("%s/Vertex_%02d.vert", directory.c_str(), index);
2824 FILE* vertFile = sk_fopen(vertPath.c_str(), kWrite_SkFILE_Flag);
2825 if (vertFile) {
2826 const SkSL::String& vertText = entry->fShader[kVertex_GrShaderType];
2827 SkAssertResult(sk_fwrite(vertText.c_str(), vertText.size(), vertFile));
2828 sk_fclose(vertFile);
2829 } else {
2830 SkDEBUGFAILF("Unable to write shader to path '%s'", vertPath.c_str());
2831 }
2832
2833 SkString fragPath = SkStringPrintf("%s/Fragment_%02d.frag", directory.c_str(), index);
2834 FILE* fragFile = sk_fopen(fragPath.c_str(), kWrite_SkFILE_Flag);
2835 if (fragFile) {
2836 const SkSL::String& fragText = entry->fShader[kFragment_GrShaderType];
2837 SkAssertResult(sk_fwrite(fragText.c_str(), fragText.size(), fragFile));
2838 sk_fclose(fragFile);
2839 } else {
2840 SkDEBUGFAILF("Unable to write shader to path '%s'", fragPath.c_str());
2841 }
2842
2843 ++index;
2844 }
2845 }
2846
onIdle()2847 void Viewer::onIdle() {
2848 SkTArray<std::function<void()>> actionsToRun;
2849 actionsToRun.swap(fDeferredActions);
2850
2851 for (const auto& fn : actionsToRun) {
2852 fn();
2853 }
2854
2855 fStatsLayer.beginTiming(fAnimateTimer);
2856 fAnimTimer.updateTime();
2857 bool animateWantsInval = fSlides[fCurrentSlide]->animate(fAnimTimer.nanos());
2858 fStatsLayer.endTiming(fAnimateTimer);
2859
2860 ImGuiIO& io = ImGui::GetIO();
2861 // ImGui always has at least one "active" window, which is the default "Debug" window. It may
2862 // not be visible, though. So we need to redraw if there is at least one visible window, or
2863 // more than one active window. Newly created windows are active but not visible for one frame
2864 // while they determine their layout and sizing.
2865 if (animateWantsInval || fStatsLayer.getActive() || fRefresh ||
2866 io.MetricsActiveWindows > 1 || io.MetricsRenderWindows > 0) {
2867 fWindow->inval();
2868 }
2869 }
2870
2871 template <typename OptionsFunc>
WriteStateObject(SkJSONWriter & writer,const char * name,const char * value,OptionsFunc && optionsFunc)2872 static void WriteStateObject(SkJSONWriter& writer, const char* name, const char* value,
2873 OptionsFunc&& optionsFunc) {
2874 writer.beginObject();
2875 {
2876 writer.appendString(kName , name);
2877 writer.appendString(kValue, value);
2878
2879 writer.beginArray(kOptions);
2880 {
2881 optionsFunc(writer);
2882 }
2883 writer.endArray();
2884 }
2885 writer.endObject();
2886 }
2887
2888
updateUIState()2889 void Viewer::updateUIState() {
2890 if (!fWindow) {
2891 return;
2892 }
2893 if (fWindow->sampleCount() < 1) {
2894 return; // Surface hasn't been created yet.
2895 }
2896
2897 SkDynamicMemoryWStream memStream;
2898 SkJSONWriter writer(&memStream);
2899 writer.beginArray();
2900
2901 // Slide state
2902 WriteStateObject(writer, kSlideStateName, fSlides[fCurrentSlide]->getName().c_str(),
2903 [this](SkJSONWriter& writer) {
2904 for(const auto& slide : fSlides) {
2905 writer.appendString(slide->getName().c_str());
2906 }
2907 });
2908
2909 // Backend state
2910 WriteStateObject(writer, kBackendStateName, kBackendTypeStrings[fBackendType],
2911 [](SkJSONWriter& writer) {
2912 for (const auto& str : kBackendTypeStrings) {
2913 writer.appendString(str);
2914 }
2915 });
2916
2917 // MSAA state
2918 const auto countString = SkStringPrintf("%d", fWindow->sampleCount());
2919 WriteStateObject(writer, kMSAAStateName, countString.c_str(),
2920 [this](SkJSONWriter& writer) {
2921 writer.appendS32(0);
2922
2923 if (sk_app::Window::kRaster_BackendType == fBackendType) {
2924 return;
2925 }
2926
2927 for (int msaa : {4, 8, 16}) {
2928 writer.appendS32(msaa);
2929 }
2930 });
2931
2932 // Path renderer state
2933 GpuPathRenderers pr = fWindow->getRequestedDisplayParams().fGrContextOptions.fGpuPathRenderers;
2934 WriteStateObject(writer, kPathRendererStateName, gPathRendererNames[pr].c_str(),
2935 [this](SkJSONWriter& writer) {
2936 auto ctx = fWindow->directContext();
2937 if (!ctx) {
2938 writer.appendString("Software");
2939 } else {
2940 writer.appendString(gPathRendererNames[GpuPathRenderers::kDefault].c_str());
2941 #if SK_GPU_V1
2942 if (fWindow->sampleCount() > 1 || FLAGS_dmsaa) {
2943 const auto* caps = ctx->priv().caps();
2944 if (skgpu::v1::AtlasPathRenderer::IsSupported(ctx)) {
2945 writer.appendString(
2946 gPathRendererNames[GpuPathRenderers::kAtlas].c_str());
2947 }
2948 if (skgpu::v1::TessellationPathRenderer::IsSupported(*caps)) {
2949 writer.appendString(
2950 gPathRendererNames[GpuPathRenderers::kTessellation].c_str());
2951 }
2952 }
2953 #endif
2954 if (1 == fWindow->sampleCount()) {
2955 writer.appendString(gPathRendererNames[GpuPathRenderers::kSmall].c_str());
2956 }
2957 writer.appendString(gPathRendererNames[GpuPathRenderers::kTriangulating].c_str());
2958 writer.appendString(gPathRendererNames[GpuPathRenderers::kNone].c_str());
2959 }
2960 });
2961
2962 // Softkey state
2963 WriteStateObject(writer, kSoftkeyStateName, kSoftkeyHint,
2964 [this](SkJSONWriter& writer) {
2965 writer.appendString(kSoftkeyHint);
2966 for (const auto& softkey : fCommands.getCommandsAsSoftkeys()) {
2967 writer.appendString(softkey.c_str());
2968 }
2969 });
2970
2971 writer.endArray();
2972 writer.flush();
2973
2974 auto data = memStream.detachAsData();
2975
2976 // TODO: would be cool to avoid this copy
2977 const SkString cstring(static_cast<const char*>(data->data()), data->size());
2978
2979 fWindow->setUIState(cstring.c_str());
2980 }
2981
onUIStateChanged(const SkString & stateName,const SkString & stateValue)2982 void Viewer::onUIStateChanged(const SkString& stateName, const SkString& stateValue) {
2983 // For those who will add more features to handle the state change in this function:
2984 // After the change, please call updateUIState no notify the frontend (e.g., Android app).
2985 // For example, after slide change, updateUIState is called inside setupCurrentSlide;
2986 // after backend change, updateUIState is called in this function.
2987 if (stateName.equals(kSlideStateName)) {
2988 for (int i = 0; i < fSlides.count(); ++i) {
2989 if (fSlides[i]->getName().equals(stateValue)) {
2990 this->setCurrentSlide(i);
2991 return;
2992 }
2993 }
2994
2995 SkDebugf("Slide not found: %s", stateValue.c_str());
2996 } else if (stateName.equals(kBackendStateName)) {
2997 for (int i = 0; i < sk_app::Window::kBackendTypeCount; i++) {
2998 if (stateValue.equals(kBackendTypeStrings[i])) {
2999 if (fBackendType != i) {
3000 fBackendType = (sk_app::Window::BackendType)i;
3001 for(auto& slide : fSlides) {
3002 slide->gpuTeardown();
3003 }
3004 fWindow->detach();
3005 fWindow->attach(backend_type_for_window(fBackendType));
3006 }
3007 break;
3008 }
3009 }
3010 } else if (stateName.equals(kMSAAStateName)) {
3011 DisplayParams params = fWindow->getRequestedDisplayParams();
3012 int sampleCount = atoi(stateValue.c_str());
3013 if (sampleCount != params.fMSAASampleCount) {
3014 params.fMSAASampleCount = sampleCount;
3015 fWindow->setRequestedDisplayParams(params);
3016 fWindow->inval();
3017 this->updateTitle();
3018 this->updateUIState();
3019 }
3020 } else if (stateName.equals(kPathRendererStateName)) {
3021 DisplayParams params = fWindow->getRequestedDisplayParams();
3022 for (const auto& pair : gPathRendererNames) {
3023 if (pair.second == stateValue.c_str()) {
3024 if (params.fGrContextOptions.fGpuPathRenderers != pair.first) {
3025 params.fGrContextOptions.fGpuPathRenderers = pair.first;
3026 fWindow->setRequestedDisplayParams(params);
3027 fWindow->inval();
3028 this->updateTitle();
3029 this->updateUIState();
3030 }
3031 break;
3032 }
3033 }
3034 } else if (stateName.equals(kSoftkeyStateName)) {
3035 if (!stateValue.equals(kSoftkeyHint)) {
3036 fCommands.onSoftkey(stateValue);
3037 this->updateUIState(); // This is still needed to reset the value to kSoftkeyHint
3038 }
3039 } else if (stateName.equals(kRefreshStateName)) {
3040 // This state is actually NOT in the UI state.
3041 // We use this to allow Android to quickly set bool fRefresh.
3042 fRefresh = stateValue.equals(kON);
3043 } else {
3044 SkDebugf("Unknown stateName: %s", stateName.c_str());
3045 }
3046 }
3047
onKey(skui::Key key,skui::InputState state,skui::ModifierKey modifiers)3048 bool Viewer::onKey(skui::Key key, skui::InputState state, skui::ModifierKey modifiers) {
3049 return fCommands.onKey(key, state, modifiers);
3050 }
3051
onChar(SkUnichar c,skui::ModifierKey modifiers)3052 bool Viewer::onChar(SkUnichar c, skui::ModifierKey modifiers) {
3053 if (fSlides[fCurrentSlide]->onChar(c)) {
3054 fWindow->inval();
3055 return true;
3056 } else {
3057 return fCommands.onChar(c, modifiers);
3058 }
3059 }
3060