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