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