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