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