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 "Viewer.h" 9 10 #include "GMSlide.h" 11 #include "ImageSlide.h" 12 #include "Resources.h" 13 #include "SampleSlide.h" 14 #include "SkottieSlide.h" 15 #include "SKPSlide.h" 16 17 #include "GrContext.h" 18 #include "SkCanvas.h" 19 #include "SkColorSpacePriv.h" 20 #include "SkColorSpaceXformCanvas.h" 21 #include "SkCommandLineFlags.h" 22 #include "SkCommonFlagsGpu.h" 23 #include "SkEventTracingPriv.h" 24 #include "SkGraphics.h" 25 #include "SkImagePriv.h" 26 #include "SkOSFile.h" 27 #include "SkOSPath.h" 28 #include "SkPictureRecorder.h" 29 #include "SkScan.h" 30 #include "SkStream.h" 31 #include "SkSurface.h" 32 #include "SkTaskGroup.h" 33 #include "SkThreadedBMPDevice.h" 34 35 #include "imgui.h" 36 37 #include "ccpr/GrCoverageCountingPathRenderer.h" 38 39 #include <stdlib.h> 40 #include <map> 41 42 using namespace sk_app; 43 44 static std::map<GpuPathRenderers, std::string> gPathRendererNames; 45 46 Application* Application::Create(int argc, char** argv, void* platformData) { 47 return new Viewer(argc, argv, platformData); 48 } 49 50 static DEFINE_string2(match, m, nullptr, 51 "[~][^]substring[$] [...] of bench name to run.\n" 52 "Multiple matches may be separated by spaces.\n" 53 "~ causes a matching bench to always be skipped\n" 54 "^ requires the start of the bench to match\n" 55 "$ requires the end of the bench to match\n" 56 "^ and $ requires an exact match\n" 57 "If a bench does not match any list entry,\n" 58 "it is skipped unless some list entry starts with ~"); 59 60 static DEFINE_string(slide, "", "Start on this sample."); 61 static DEFINE_bool(list, false, "List samples?"); 62 63 #ifdef SK_VULKAN 64 # define BACKENDS_STR "\"sw\", \"gl\", and \"vk\"" 65 #else 66 # define BACKENDS_STR "\"sw\" and \"gl\"" 67 #endif 68 69 #ifdef SK_BUILD_FOR_ANDROID 70 static DEFINE_string(skps, "/data/local/tmp/skps", "Directory to read skps from."); 71 static DEFINE_string(jpgs, "/data/local/tmp/resources", "Directory to read jpgs from."); 72 static DEFINE_string(jsons, "/data/local/tmp/jsons", "Directory to read (Bodymovin) jsons from."); 73 #else 74 static DEFINE_string(skps, "skps", "Directory to read skps from."); 75 static DEFINE_string(jpgs, "jpgs", "Directory to read jpgs from."); 76 static DEFINE_string(jsons, "jsons", "Directory to read (Bodymovin) jsons from."); 77 #endif 78 79 static DEFINE_string2(backend, b, "sw", "Backend to use. Allowed values are " BACKENDS_STR "."); 80 81 static DEFINE_int32(msaa, 1, "Number of subpixel samples. 0 for no HW antialiasing."); 82 83 DECLARE_int32(threads) 84 85 const char* kBackendTypeStrings[sk_app::Window::kBackendTypeCount] = { 86 "OpenGL", 87 #if SK_ANGLE && defined(SK_BUILD_FOR_WIN) 88 "ANGLE", 89 #endif 90 #ifdef SK_VULKAN 91 "Vulkan", 92 #endif 93 "Raster" 94 }; 95 96 static sk_app::Window::BackendType get_backend_type(const char* str) { 97 #ifdef SK_VULKAN 98 if (0 == strcmp(str, "vk")) { 99 return sk_app::Window::kVulkan_BackendType; 100 } else 101 #endif 102 #if SK_ANGLE && defined(SK_BUILD_FOR_WIN) 103 if (0 == strcmp(str, "angle")) { 104 return sk_app::Window::kANGLE_BackendType; 105 } else 106 #endif 107 if (0 == strcmp(str, "gl")) { 108 return sk_app::Window::kNativeGL_BackendType; 109 } else if (0 == strcmp(str, "sw")) { 110 return sk_app::Window::kRaster_BackendType; 111 } else { 112 SkDebugf("Unknown backend type, %s, defaulting to sw.", str); 113 return sk_app::Window::kRaster_BackendType; 114 } 115 } 116 117 static SkColorSpacePrimaries gSrgbPrimaries = { 118 0.64f, 0.33f, 119 0.30f, 0.60f, 120 0.15f, 0.06f, 121 0.3127f, 0.3290f }; 122 123 static SkColorSpacePrimaries gAdobePrimaries = { 124 0.64f, 0.33f, 125 0.21f, 0.71f, 126 0.15f, 0.06f, 127 0.3127f, 0.3290f }; 128 129 static SkColorSpacePrimaries gP3Primaries = { 130 0.680f, 0.320f, 131 0.265f, 0.690f, 132 0.150f, 0.060f, 133 0.3127f, 0.3290f }; 134 135 static SkColorSpacePrimaries gRec2020Primaries = { 136 0.708f, 0.292f, 137 0.170f, 0.797f, 138 0.131f, 0.046f, 139 0.3127f, 0.3290f }; 140 141 struct NamedPrimaries { 142 const char* fName; 143 SkColorSpacePrimaries* fPrimaries; 144 } gNamedPrimaries[] = { 145 { "sRGB", &gSrgbPrimaries }, 146 { "AdobeRGB", &gAdobePrimaries }, 147 { "P3", &gP3Primaries }, 148 { "Rec. 2020", &gRec2020Primaries }, 149 }; 150 151 static bool primaries_equal(const SkColorSpacePrimaries& a, const SkColorSpacePrimaries& b) { 152 return memcmp(&a, &b, sizeof(SkColorSpacePrimaries)) == 0; 153 } 154 155 static Window::BackendType backend_type_for_window(Window::BackendType backendType) { 156 // In raster mode, we still use GL for the window. 157 // This lets us render the GUI faster (and correct). 158 return Window::kRaster_BackendType == backendType ? Window::kNativeGL_BackendType : backendType; 159 } 160 161 const char* kName = "name"; 162 const char* kValue = "value"; 163 const char* kOptions = "options"; 164 const char* kSlideStateName = "Slide"; 165 const char* kBackendStateName = "Backend"; 166 const char* kMSAAStateName = "MSAA"; 167 const char* kPathRendererStateName = "Path renderer"; 168 const char* kSoftkeyStateName = "Softkey"; 169 const char* kSoftkeyHint = "Please select a softkey"; 170 const char* kFpsStateName = "FPS"; 171 const char* kON = "ON"; 172 const char* kOFF = "OFF"; 173 const char* kRefreshStateName = "Refresh"; 174 175 Viewer::Viewer(int argc, char** argv, void* platformData) 176 : fCurrentSlide(-1) 177 , fRefresh(false) 178 , fSaveToSKP(false) 179 , fShowImGuiDebugWindow(false) 180 , fShowSlidePicker(false) 181 , fShowImGuiTestWindow(false) 182 , fShowZoomWindow(false) 183 , fLastImage(nullptr) 184 , fBackendType(sk_app::Window::kNativeGL_BackendType) 185 , fColorMode(ColorMode::kLegacy) 186 , fColorSpacePrimaries(gSrgbPrimaries) 187 // Our UI can only tweak gamma (currently), so start out gamma-only 188 , fColorSpaceTransferFn(g2Dot2_TransferFn) 189 , fZoomLevel(0.0f) 190 , fGestureDevice(GestureDevice::kNone) 191 , fTileCnt(0) 192 , fThreadCnt(0) 193 { 194 SkGraphics::Init(); 195 196 gPathRendererNames[GpuPathRenderers::kAll] = "All Path Renderers"; 197 gPathRendererNames[GpuPathRenderers::kDefault] = 198 "Default Ganesh Behavior (best path renderer, not including CCPR)"; 199 gPathRendererNames[GpuPathRenderers::kStencilAndCover] = "NV_path_rendering"; 200 gPathRendererNames[GpuPathRenderers::kMSAA] = "Sample shading"; 201 gPathRendererNames[GpuPathRenderers::kSmall] = "Small paths (cached sdf or alpha masks)"; 202 gPathRendererNames[GpuPathRenderers::kCoverageCounting] = "Coverage counting"; 203 gPathRendererNames[GpuPathRenderers::kTessellating] = "Tessellating"; 204 gPathRendererNames[GpuPathRenderers::kNone] = "Software masks"; 205 206 SkDebugf("Command line arguments: "); 207 for (int i = 1; i < argc; ++i) { 208 SkDebugf("%s ", argv[i]); 209 } 210 SkDebugf("\n"); 211 212 SkCommandLineFlags::Parse(argc, argv); 213 #ifdef SK_BUILD_FOR_ANDROID 214 SetResourcePath("/data/local/tmp/resources"); 215 #endif 216 217 initializeEventTracingForTools(); 218 static SkTaskGroup::Enabler kTaskGroupEnabler(FLAGS_threads); 219 220 fBackendType = get_backend_type(FLAGS_backend[0]); 221 fWindow = Window::CreateNativeWindow(platformData); 222 223 DisplayParams displayParams; 224 displayParams.fMSAASampleCount = FLAGS_msaa; 225 SetCtxOptionsFromCommonFlags(&displayParams.fGrContextOptions); 226 fWindow->setRequestedDisplayParams(displayParams); 227 228 // Configure timers 229 fStatsLayer.setActive(false); 230 fAnimateTimer = fStatsLayer.addTimer("Animate", SK_ColorMAGENTA, 0xffff66ff); 231 fPaintTimer = fStatsLayer.addTimer("Paint", SK_ColorGREEN); 232 fFlushTimer = fStatsLayer.addTimer("Flush", SK_ColorRED, 0xffff6666); 233 234 // register callbacks 235 fCommands.attach(fWindow); 236 fWindow->pushLayer(this); 237 fWindow->pushLayer(&fStatsLayer); 238 fWindow->pushLayer(&fImGuiLayer); 239 240 // add key-bindings 241 fCommands.addCommand(' ', "GUI", "Toggle Debug GUI", [this]() { 242 this->fShowImGuiDebugWindow = !this->fShowImGuiDebugWindow; 243 fWindow->inval(); 244 }); 245 // Command to jump directly to the slide picker and give it focus 246 fCommands.addCommand('/', "GUI", "Jump to slide picker", [this]() { 247 this->fShowImGuiDebugWindow = true; 248 this->fShowSlidePicker = true; 249 fWindow->inval(); 250 }); 251 // Alias that to Backspace, to match SampleApp 252 fCommands.addCommand(Window::Key::kBack, "Backspace", "GUI", "Jump to slide picker", [this]() { 253 this->fShowImGuiDebugWindow = true; 254 this->fShowSlidePicker = true; 255 fWindow->inval(); 256 }); 257 fCommands.addCommand('g', "GUI", "Toggle GUI Demo", [this]() { 258 this->fShowImGuiTestWindow = !this->fShowImGuiTestWindow; 259 fWindow->inval(); 260 }); 261 fCommands.addCommand('z', "GUI", "Toggle zoom window", [this]() { 262 this->fShowZoomWindow = !this->fShowZoomWindow; 263 fWindow->inval(); 264 }); 265 fCommands.addCommand('s', "Overlays", "Toggle stats display", [this]() { 266 fStatsLayer.setActive(!fStatsLayer.getActive()); 267 fWindow->inval(); 268 }); 269 fCommands.addCommand('0', "Overlays", "Reset stats", [this]() { 270 fStatsLayer.resetMeasurements(); 271 this->updateTitle(); 272 fWindow->inval(); 273 }); 274 fCommands.addCommand('c', "Modes", "Cycle color mode", [this]() { 275 switch (fColorMode) { 276 case ColorMode::kLegacy: 277 this->setColorMode(ColorMode::kColorManagedSRGB8888_NonLinearBlending); 278 break; 279 case ColorMode::kColorManagedSRGB8888_NonLinearBlending: 280 this->setColorMode(ColorMode::kColorManagedSRGB8888); 281 break; 282 case ColorMode::kColorManagedSRGB8888: 283 this->setColorMode(ColorMode::kColorManagedLinearF16); 284 break; 285 case ColorMode::kColorManagedLinearF16: 286 this->setColorMode(ColorMode::kLegacy); 287 break; 288 } 289 }); 290 fCommands.addCommand(Window::Key::kRight, "Right", "Navigation", "Next slide", [this]() { 291 this->setCurrentSlide(fCurrentSlide < fSlides.count() - 1 ? fCurrentSlide + 1 : 0); 292 }); 293 fCommands.addCommand(Window::Key::kLeft, "Left", "Navigation", "Previous slide", [this]() { 294 this->setCurrentSlide(fCurrentSlide > 0 ? fCurrentSlide - 1 : fSlides.count() - 1); 295 }); 296 fCommands.addCommand(Window::Key::kUp, "Up", "Transform", "Zoom in", [this]() { 297 this->changeZoomLevel(1.f / 32.f); 298 fWindow->inval(); 299 }); 300 fCommands.addCommand(Window::Key::kDown, "Down", "Transform", "Zoom out", [this]() { 301 this->changeZoomLevel(-1.f / 32.f); 302 fWindow->inval(); 303 }); 304 fCommands.addCommand('d', "Modes", "Change rendering backend", [this]() { 305 sk_app::Window::BackendType newBackend = (sk_app::Window::BackendType)( 306 (fBackendType + 1) % sk_app::Window::kBackendTypeCount); 307 // Switching to and from Vulkan is problematic on Linux so disabled for now 308 #if defined(SK_BUILD_FOR_UNIX) && defined(SK_VULKAN) 309 if (newBackend == sk_app::Window::kVulkan_BackendType) { 310 newBackend = (sk_app::Window::BackendType)((newBackend + 1) % 311 sk_app::Window::kBackendTypeCount); 312 } else if (fBackendType == sk_app::Window::kVulkan_BackendType) { 313 newBackend = sk_app::Window::kVulkan_BackendType; 314 } 315 #endif 316 this->setBackend(newBackend); 317 }); 318 319 fCommands.addCommand('A', "AA", "Toggle analytic AA", [this]() { 320 if (!gSkUseAnalyticAA) { 321 gSkUseAnalyticAA = true; 322 } else if (!gSkForceAnalyticAA) { 323 gSkForceAnalyticAA = true; 324 } else { 325 gSkUseAnalyticAA = gSkForceAnalyticAA = false; 326 } 327 this->updateTitle(); 328 fWindow->inval(); 329 }); 330 fCommands.addCommand('D', "AA", "Toggle delta AA", [this]() { 331 if (!gSkUseDeltaAA) { 332 gSkUseDeltaAA = true; 333 } else if (!gSkForceDeltaAA) { 334 gSkForceDeltaAA = true; 335 } else { 336 gSkUseDeltaAA = gSkForceDeltaAA = false; 337 } 338 this->updateTitle(); 339 fWindow->inval(); 340 }); 341 342 fCommands.addCommand('+', "Threaded Backend", "Increase tile count", [this]() { 343 fTileCnt++; 344 if (fThreadCnt == 0) { 345 this->resetExecutor(); 346 } 347 this->updateTitle(); 348 fWindow->inval(); 349 }); 350 fCommands.addCommand('-', "Threaded Backend", "Decrease tile count", [this]() { 351 fTileCnt = SkTMax(0, fTileCnt - 1); 352 if (fThreadCnt == 0) { 353 this->resetExecutor(); 354 } 355 this->updateTitle(); 356 fWindow->inval(); 357 }); 358 fCommands.addCommand('>', "Threaded Backend", "Increase thread count", [this]() { 359 if (fTileCnt == 0) { 360 return; 361 } 362 fThreadCnt = (fThreadCnt + 1) % fTileCnt; 363 this->resetExecutor(); 364 this->updateTitle(); 365 fWindow->inval(); 366 }); 367 fCommands.addCommand('<', "Threaded Backend", "Decrease thread count", [this]() { 368 if (fTileCnt == 0) { 369 return; 370 } 371 fThreadCnt = (fThreadCnt + fTileCnt - 1) % fTileCnt; 372 this->resetExecutor(); 373 this->updateTitle(); 374 fWindow->inval(); 375 }); 376 fCommands.addCommand('K', "IO", "Save slide to SKP", [this]() { 377 fSaveToSKP = true; 378 fWindow->inval(); 379 }); 380 381 // set up slides 382 this->initSlides(); 383 if (FLAGS_list) { 384 this->listNames(); 385 } 386 387 fAnimTimer.run(); 388 389 auto gamutImage = GetResourceAsImage("images/gamut.png"); 390 if (gamutImage) { 391 fImGuiGamutPaint.setShader(gamutImage->makeShader()); 392 } 393 fImGuiGamutPaint.setColor(SK_ColorWHITE); 394 fImGuiGamutPaint.setFilterQuality(kLow_SkFilterQuality); 395 396 fWindow->attach(backend_type_for_window(fBackendType)); 397 this->setCurrentSlide(this->startupSlide()); 398 } 399 400 void Viewer::initSlides() { 401 fAllSlideNames = Json::Value(Json::arrayValue); 402 403 const skiagm::GMRegistry* gms(skiagm::GMRegistry::Head()); 404 while (gms) { 405 std::unique_ptr<skiagm::GM> gm(gms->factory()(nullptr)); 406 407 if (!SkCommandLineFlags::ShouldSkip(FLAGS_match, gm->getName())) { 408 sk_sp<Slide> slide(new GMSlide(gm.release())); 409 fSlides.push_back(slide); 410 } 411 412 gms = gms->next(); 413 } 414 415 // reverse array 416 for (int i = 0; i < fSlides.count()/2; ++i) { 417 sk_sp<Slide> temp = fSlides[i]; 418 fSlides[i] = fSlides[fSlides.count() - i - 1]; 419 fSlides[fSlides.count() - i - 1] = temp; 420 } 421 422 // samples 423 const SkViewRegister* reg = SkViewRegister::Head(); 424 while (reg) { 425 sk_sp<Slide> slide(new SampleSlide(reg->factory())); 426 if (!SkCommandLineFlags::ShouldSkip(FLAGS_match, slide->getName().c_str())) { 427 fSlides.push_back(slide); 428 } 429 reg = reg->next(); 430 } 431 432 // SKPs 433 for (int i = 0; i < FLAGS_skps.count(); i++) { 434 if (SkStrEndsWith(FLAGS_skps[i], ".skp")) { 435 if (SkCommandLineFlags::ShouldSkip(FLAGS_match, FLAGS_skps[i])) { 436 continue; 437 } 438 439 SkString path(FLAGS_skps[i]); 440 sk_sp<SKPSlide> slide(new SKPSlide(SkOSPath::Basename(path.c_str()), path)); 441 if (slide) { 442 fSlides.push_back(slide); 443 } 444 } else { 445 SkOSFile::Iter it(FLAGS_skps[i], ".skp"); 446 SkString skpName; 447 while (it.next(&skpName)) { 448 if (SkCommandLineFlags::ShouldSkip(FLAGS_match, skpName.c_str())) { 449 continue; 450 } 451 452 SkString path = SkOSPath::Join(FLAGS_skps[i], skpName.c_str()); 453 sk_sp<SKPSlide> slide(new SKPSlide(skpName, path)); 454 if (slide) { 455 fSlides.push_back(slide); 456 } 457 } 458 } 459 } 460 461 // JPGs 462 for (int i = 0; i < FLAGS_jpgs.count(); i++) { 463 SkOSFile::Iter it(FLAGS_jpgs[i], ".jpg"); 464 SkString jpgName; 465 while (it.next(&jpgName)) { 466 if (SkCommandLineFlags::ShouldSkip(FLAGS_match, jpgName.c_str())) { 467 continue; 468 } 469 470 SkString path = SkOSPath::Join(FLAGS_jpgs[i], jpgName.c_str()); 471 sk_sp<ImageSlide> slide(new ImageSlide(jpgName, path)); 472 if (slide) { 473 fSlides.push_back(slide); 474 } 475 } 476 } 477 478 // JSONs 479 for (const auto& json : FLAGS_jsons) { 480 fSlides.push_back(sk_make_sp<SkottieSlide2>(json)); 481 482 SkOSFile::Iter it(json.c_str(), ".json"); 483 SkString jsonName; 484 while (it.next(&jsonName)) { 485 if (SkCommandLineFlags::ShouldSkip(FLAGS_match, jsonName.c_str())) { 486 continue; 487 } 488 fSlides.push_back(sk_make_sp<SkottieSlide>(jsonName, SkOSPath::Join(json.c_str(), 489 jsonName.c_str()))); 490 } 491 } 492 } 493 494 495 Viewer::~Viewer() { 496 fWindow->detach(); 497 delete fWindow; 498 } 499 500 void Viewer::updateTitle() { 501 if (!fWindow) { 502 return; 503 } 504 if (fWindow->sampleCount() < 1) { 505 return; // Surface hasn't been created yet. 506 } 507 508 SkString title("Viewer: "); 509 title.append(fSlides[fCurrentSlide]->getName()); 510 511 if (gSkUseDeltaAA) { 512 if (gSkForceDeltaAA) { 513 title.append(" <FDAA>"); 514 } else { 515 title.append(" <DAA>"); 516 } 517 } else if (gSkUseAnalyticAA) { 518 if (gSkForceAnalyticAA) { 519 title.append(" <FAAA>"); 520 } else { 521 title.append(" <AAA>"); 522 } 523 } 524 525 if (fTileCnt > 0) { 526 title.appendf(" T%d", fTileCnt); 527 if (fThreadCnt > 0) { 528 title.appendf("/%d", fThreadCnt); 529 } 530 } 531 532 switch (fColorMode) { 533 case ColorMode::kLegacy: 534 title.append(" Legacy 8888"); 535 break; 536 case ColorMode::kColorManagedSRGB8888_NonLinearBlending: 537 title.append(" ColorManaged 8888 (Nonlinear blending)"); 538 break; 539 case ColorMode::kColorManagedSRGB8888: 540 title.append(" ColorManaged 8888"); 541 break; 542 case ColorMode::kColorManagedLinearF16: 543 title.append(" ColorManaged F16"); 544 break; 545 } 546 547 if (ColorMode::kLegacy != fColorMode) { 548 int curPrimaries = -1; 549 for (size_t i = 0; i < SK_ARRAY_COUNT(gNamedPrimaries); ++i) { 550 if (primaries_equal(*gNamedPrimaries[i].fPrimaries, fColorSpacePrimaries)) { 551 curPrimaries = i; 552 break; 553 } 554 } 555 title.appendf(" %s", curPrimaries >= 0 ? gNamedPrimaries[curPrimaries].fName : "Custom"); 556 557 if (ColorMode::kColorManagedSRGB8888_NonLinearBlending == fColorMode) { 558 title.appendf(" Gamma %f", fColorSpaceTransferFn.fG); 559 } 560 } 561 562 title.append(" ["); 563 title.append(kBackendTypeStrings[fBackendType]); 564 int msaa = fWindow->sampleCount(); 565 if (msaa > 1) { 566 title.appendf(" MSAA: %i", msaa); 567 } 568 title.append("]"); 569 570 GpuPathRenderers pr = fWindow->getRequestedDisplayParams().fGrContextOptions.fGpuPathRenderers; 571 if (GpuPathRenderers::kDefault != pr) { 572 title.appendf(" [Path renderer: %s]", gPathRendererNames[pr].c_str()); 573 } 574 575 fWindow->setTitle(title.c_str()); 576 } 577 578 int Viewer::startupSlide() const { 579 580 if (!FLAGS_slide.isEmpty()) { 581 int count = fSlides.count(); 582 for (int i = 0; i < count; i++) { 583 if (fSlides[i]->getName().equals(FLAGS_slide[0])) { 584 return i; 585 } 586 } 587 588 fprintf(stderr, "Unknown slide \"%s\"\n", FLAGS_slide[0]); 589 this->listNames(); 590 } 591 592 return 0; 593 } 594 595 void Viewer::listNames() const { 596 SkDebugf("All Slides:\n"); 597 for (const auto& slide : fSlides) { 598 SkDebugf(" %s\n", slide->getName().c_str()); 599 } 600 } 601 602 void Viewer::setCurrentSlide(int slide) { 603 SkASSERT(slide >= 0 && slide < fSlides.count()); 604 605 if (slide == fCurrentSlide) { 606 return; 607 } 608 609 if (fCurrentSlide >= 0) { 610 fSlides[fCurrentSlide]->unload(); 611 } 612 613 fSlides[slide]->load(SkIntToScalar(fWindow->width()), 614 SkIntToScalar(fWindow->height())); 615 fCurrentSlide = slide; 616 this->setupCurrentSlide(); 617 } 618 619 void Viewer::setupCurrentSlide() { 620 if (fCurrentSlide >= 0) { 621 // prepare dimensions for image slides 622 fGesture.resetTouchState(); 623 fDefaultMatrix.reset(); 624 625 const SkISize slideSize = fSlides[fCurrentSlide]->getDimensions(); 626 const SkRect slideBounds = SkRect::MakeIWH(slideSize.width(), slideSize.height()); 627 const SkRect windowRect = SkRect::MakeIWH(fWindow->width(), fWindow->height()); 628 629 // Start with a matrix that scales the slide to the available screen space 630 if (fWindow->scaleContentToFit()) { 631 if (windowRect.width() > 0 && windowRect.height() > 0) { 632 fDefaultMatrix.setRectToRect(slideBounds, windowRect, SkMatrix::kStart_ScaleToFit); 633 } 634 } 635 636 // Prevent the user from dragging content so far outside the window they can't find it again 637 fGesture.setTransLimit(slideBounds, windowRect, fDefaultMatrix); 638 639 this->updateTitle(); 640 this->updateUIState(); 641 642 fStatsLayer.resetMeasurements(); 643 644 fWindow->inval(); 645 } 646 } 647 648 #define MAX_ZOOM_LEVEL 8 649 #define MIN_ZOOM_LEVEL -8 650 651 void Viewer::changeZoomLevel(float delta) { 652 fZoomLevel += delta; 653 fZoomLevel = SkScalarPin(fZoomLevel, MIN_ZOOM_LEVEL, MAX_ZOOM_LEVEL); 654 } 655 656 SkMatrix Viewer::computeMatrix() { 657 SkMatrix m; 658 659 SkScalar zoomScale = (fZoomLevel < 0) ? SK_Scalar1 / (SK_Scalar1 - fZoomLevel) 660 : SK_Scalar1 + fZoomLevel; 661 m = fGesture.localM(); 662 m.preConcat(fGesture.globalM()); 663 m.preConcat(fDefaultMatrix); 664 m.preScale(zoomScale, zoomScale); 665 666 return m; 667 } 668 669 void Viewer::setBackend(sk_app::Window::BackendType backendType) { 670 fBackendType = backendType; 671 672 fWindow->detach(); 673 674 #if defined(SK_BUILD_FOR_WIN) 675 // Switching between OpenGL, Vulkan, and ANGLE in the same window is problematic at this point 676 // on Windows, so we just delete the window and recreate it. 677 DisplayParams params = fWindow->getRequestedDisplayParams(); 678 delete fWindow; 679 fWindow = Window::CreateNativeWindow(nullptr); 680 681 // re-register callbacks 682 fCommands.attach(fWindow); 683 fWindow->pushLayer(this); 684 fWindow->pushLayer(&fStatsLayer); 685 fWindow->pushLayer(&fImGuiLayer); 686 687 // Don't allow the window to re-attach. If we're in MSAA mode, the params we grabbed above 688 // will still include our correct sample count. But the re-created fWindow will lose that 689 // information. On Windows, we need to re-create the window when changing sample count, 690 // so we'll incorrectly detect that situation, then re-initialize the window in GL mode, 691 // rendering this tear-down step pointless (and causing the Vulkan window context to fail 692 // as if we had never changed windows at all). 693 fWindow->setRequestedDisplayParams(params, false); 694 #endif 695 696 fWindow->attach(backend_type_for_window(fBackendType)); 697 } 698 699 void Viewer::setColorMode(ColorMode colorMode) { 700 fColorMode = colorMode; 701 702 // When we're in color managed mode, we tag our window surface as sRGB. If we've switched into 703 // or out of legacy/nonlinear mode, we need to update our window configuration. 704 DisplayParams params = fWindow->getRequestedDisplayParams(); 705 bool wasInLegacy = !SkToBool(params.fColorSpace); 706 bool wantLegacy = (ColorMode::kLegacy == fColorMode) || 707 (ColorMode::kColorManagedSRGB8888_NonLinearBlending == fColorMode); 708 if (wasInLegacy != wantLegacy) { 709 params.fColorSpace = wantLegacy ? nullptr : SkColorSpace::MakeSRGB(); 710 fWindow->setRequestedDisplayParams(params); 711 } 712 713 this->updateTitle(); 714 fWindow->inval(); 715 } 716 717 void Viewer::drawSlide(SkCanvas* canvas) { 718 SkAutoCanvasRestore autorestore(canvas, false); 719 720 // By default, we render directly into the window's surface/canvas 721 SkCanvas* slideCanvas = canvas; 722 fLastImage.reset(); 723 724 // If we're in any of the color managed modes, construct the color space we're going to use 725 sk_sp<SkColorSpace> cs = nullptr; 726 if (ColorMode::kLegacy != fColorMode) { 727 auto transferFn = (ColorMode::kColorManagedLinearF16 == fColorMode) 728 ? SkColorSpace::kLinear_RenderTargetGamma : SkColorSpace::kSRGB_RenderTargetGamma; 729 SkMatrix44 toXYZ(SkMatrix44::kIdentity_Constructor); 730 SkAssertResult(fColorSpacePrimaries.toXYZD50(&toXYZ)); 731 if (ColorMode::kColorManagedSRGB8888_NonLinearBlending == fColorMode) { 732 cs = SkColorSpace::MakeRGB(fColorSpaceTransferFn, toXYZ); 733 } else { 734 cs = SkColorSpace::MakeRGB(transferFn, toXYZ); 735 } 736 } 737 738 if (fSaveToSKP) { 739 SkPictureRecorder recorder; 740 SkCanvas* recorderCanvas = recorder.beginRecording( 741 SkRect::Make(fSlides[fCurrentSlide]->getDimensions())); 742 // In xform-canvas mode, record the transformed output 743 std::unique_ptr<SkCanvas> xformCanvas = nullptr; 744 if (ColorMode::kColorManagedSRGB8888_NonLinearBlending == fColorMode) { 745 xformCanvas = SkCreateColorSpaceXformCanvas(recorderCanvas, cs); 746 recorderCanvas = xformCanvas.get(); 747 } 748 fSlides[fCurrentSlide]->draw(recorderCanvas); 749 sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture()); 750 SkFILEWStream stream("sample_app.skp"); 751 picture->serialize(&stream); 752 fSaveToSKP = false; 753 } 754 755 // If we're in F16, or we're zooming, or we're in color correct 8888 and the gamut isn't sRGB, 756 // we need to render offscreen. We also need to render offscreen if we're in any raster mode, 757 // because the window surface is actually GL. 758 sk_sp<SkSurface> offscreenSurface = nullptr; 759 std::unique_ptr<SkThreadedBMPDevice> threadedDevice; 760 std::unique_ptr<SkCanvas> threadedCanvas; 761 if (Window::kRaster_BackendType == fBackendType || 762 ColorMode::kColorManagedLinearF16 == fColorMode || 763 fShowZoomWindow || 764 (ColorMode::kColorManagedSRGB8888 == fColorMode && 765 !primaries_equal(fColorSpacePrimaries, gSrgbPrimaries))) { 766 767 SkColorType colorType = (ColorMode::kColorManagedLinearF16 == fColorMode) 768 ? kRGBA_F16_SkColorType : kN32_SkColorType; 769 // In nonlinear blending mode, we actually use a legacy off-screen canvas, and wrap it 770 // with a special canvas (below) that has the color space attached 771 sk_sp<SkColorSpace> offscreenColorSpace = 772 (ColorMode::kColorManagedSRGB8888_NonLinearBlending == fColorMode) ? nullptr : cs; 773 SkImageInfo info = SkImageInfo::Make(fWindow->width(), fWindow->height(), colorType, 774 kPremul_SkAlphaType, std::move(offscreenColorSpace)); 775 offscreenSurface = Window::kRaster_BackendType == fBackendType ? SkSurface::MakeRaster(info) 776 : canvas->makeSurface(info); 777 SkPixmap offscreenPixmap; 778 if (fTileCnt > 0 && offscreenSurface->peekPixels(&offscreenPixmap)) { 779 SkBitmap offscreenBitmap; 780 offscreenBitmap.installPixels(offscreenPixmap); 781 threadedDevice.reset(new SkThreadedBMPDevice(offscreenBitmap, fTileCnt, 782 fThreadCnt, fExecutor.get())); 783 threadedCanvas.reset(new SkCanvas(threadedDevice.get())); 784 slideCanvas = threadedCanvas.get(); 785 } else { 786 slideCanvas = offscreenSurface->getCanvas(); 787 } 788 } 789 790 std::unique_ptr<SkCanvas> xformCanvas = nullptr; 791 if (ColorMode::kColorManagedSRGB8888_NonLinearBlending == fColorMode) { 792 xformCanvas = SkCreateColorSpaceXformCanvas(slideCanvas, cs); 793 slideCanvas = xformCanvas.get(); 794 } 795 796 int count = slideCanvas->save(); 797 slideCanvas->clear(SK_ColorWHITE); 798 slideCanvas->concat(computeMatrix()); 799 // Time the painting logic of the slide 800 fStatsLayer.beginTiming(fPaintTimer); 801 fSlides[fCurrentSlide]->draw(slideCanvas); 802 fStatsLayer.endTiming(fPaintTimer); 803 slideCanvas->restoreToCount(count); 804 805 // Force a flush so we can time that, too 806 fStatsLayer.beginTiming(fFlushTimer); 807 slideCanvas->flush(); 808 fStatsLayer.endTiming(fFlushTimer); 809 810 // If we rendered offscreen, snap an image and push the results to the window's canvas 811 if (offscreenSurface) { 812 fLastImage = offscreenSurface->makeImageSnapshot(); 813 814 // Tag the image with the sRGB gamut, so no further color space conversion happens 815 sk_sp<SkColorSpace> srgb = (ColorMode::kColorManagedLinearF16 == fColorMode) 816 ? SkColorSpace::MakeSRGBLinear() : SkColorSpace::MakeSRGB(); 817 auto retaggedImage = SkImageMakeRasterCopyAndAssignColorSpace(fLastImage.get(), srgb.get()); 818 SkPaint paint; 819 paint.setBlendMode(SkBlendMode::kSrc); 820 canvas->drawImage(retaggedImage, 0, 0, &paint); 821 } 822 } 823 824 void Viewer::onBackendCreated() { 825 this->setupCurrentSlide(); 826 fWindow->show(); 827 } 828 829 void Viewer::onPaint(SkCanvas* canvas) { 830 this->drawSlide(canvas); 831 832 fCommands.drawHelp(canvas); 833 834 this->drawImGui(); 835 836 // Update the FPS 837 this->updateUIState(); 838 } 839 840 bool Viewer::onTouch(intptr_t owner, Window::InputState state, float x, float y) { 841 if (GestureDevice::kMouse == fGestureDevice) { 842 return false; 843 } 844 void* castedOwner = reinterpret_cast<void*>(owner); 845 switch (state) { 846 case Window::kUp_InputState: { 847 fGesture.touchEnd(castedOwner); 848 break; 849 } 850 case Window::kDown_InputState: { 851 fGesture.touchBegin(castedOwner, x, y); 852 break; 853 } 854 case Window::kMove_InputState: { 855 fGesture.touchMoved(castedOwner, x, y); 856 break; 857 } 858 } 859 fGestureDevice = fGesture.isBeingTouched() ? GestureDevice::kTouch : GestureDevice::kNone; 860 fWindow->inval(); 861 return true; 862 } 863 864 bool Viewer::onMouse(int x, int y, Window::InputState state, uint32_t modifiers) { 865 if (GestureDevice::kTouch == fGestureDevice) { 866 return false; 867 } 868 869 if (fSlides[fCurrentSlide]->onMouse(x, y, state, modifiers)) { 870 fWindow->inval(); 871 return true; 872 } 873 874 switch (state) { 875 case Window::kUp_InputState: { 876 fGesture.touchEnd(nullptr); 877 break; 878 } 879 case Window::kDown_InputState: { 880 fGesture.touchBegin(nullptr, x, y); 881 break; 882 } 883 case Window::kMove_InputState: { 884 fGesture.touchMoved(nullptr, x, y); 885 break; 886 } 887 } 888 fGestureDevice = fGesture.isBeingTouched() ? GestureDevice::kMouse : GestureDevice::kNone; 889 890 if (state != Window::kMove_InputState || fGesture.isBeingTouched()) { 891 fWindow->inval(); 892 } 893 return true; 894 } 895 896 static ImVec2 ImGui_DragPrimary(const char* label, float* x, float* y, 897 const ImVec2& pos, const ImVec2& size) { 898 // Transform primaries ([0, 0] - [0.8, 0.9]) to screen coords (including Y-flip) 899 ImVec2 center(pos.x + (*x / 0.8f) * size.x, pos.y + (1.0f - (*y / 0.9f)) * size.y); 900 901 // Invisible 10x10 button 902 ImGui::SetCursorScreenPos(ImVec2(center.x - 5, center.y - 5)); 903 ImGui::InvisibleButton(label, ImVec2(10, 10)); 904 905 if (ImGui::IsItemActive() && ImGui::IsMouseDragging()) { 906 ImGuiIO& io = ImGui::GetIO(); 907 // Normalized mouse position, relative to our gamut box 908 ImVec2 mousePosXY((io.MousePos.x - pos.x) / size.x, (io.MousePos.y - pos.y) / size.y); 909 // Clamp to edge of box, convert back to primary scale 910 *x = SkTPin(mousePosXY.x, 0.0f, 1.0f) * 0.8f; 911 *y = SkTPin(1 - mousePosXY.y, 0.0f, 1.0f) * 0.9f; 912 } 913 914 if (ImGui::IsItemHovered()) { 915 ImGui::SetTooltip("x: %.3f\ny: %.3f", *x, *y); 916 } 917 918 // Return screen coordinates for the caller. We could just return center here, but we'd have 919 // one frame of lag during drag. 920 return ImVec2(pos.x + (*x / 0.8f) * size.x, pos.y + (1.0f - (*y / 0.9f)) * size.y); 921 } 922 923 static void ImGui_Primaries(SkColorSpacePrimaries* primaries, SkPaint* gamutPaint) { 924 ImDrawList* drawList = ImGui::GetWindowDrawList(); 925 926 // The gamut image covers a (0.8 x 0.9) shaped region, so fit our image/canvas to the available 927 // width, and scale the height to maintain aspect ratio. 928 float canvasWidth = SkTMax(ImGui::GetContentRegionAvailWidth(), 50.0f); 929 ImVec2 size = ImVec2(canvasWidth, canvasWidth * (0.9f / 0.8f)); 930 ImVec2 pos = ImGui::GetCursorScreenPos(); 931 932 // Background image. Only draw a subset of the image, to avoid the regions less than zero. 933 // Simplifes re-mapping math, clipping behavior, and increases resolution in the useful area. 934 // Magic numbers are pixel locations of the origin and upper-right corner. 935 drawList->AddImage(gamutPaint, pos, ImVec2(pos.x + size.x, pos.y + size.y), 936 ImVec2(242, 61), ImVec2(1897, 1922)); 937 ImVec2 endPos = ImGui::GetCursorPos(); 938 939 // Primary markers 940 ImVec2 r = ImGui_DragPrimary("R", &primaries->fRX, &primaries->fRY, pos, size); 941 ImVec2 g = ImGui_DragPrimary("G", &primaries->fGX, &primaries->fGY, pos, size); 942 ImVec2 b = ImGui_DragPrimary("B", &primaries->fBX, &primaries->fBY, pos, size); 943 ImVec2 w = ImGui_DragPrimary("W", &primaries->fWX, &primaries->fWY, pos, size); 944 945 // Gamut triangle 946 drawList->AddCircle(r, 5.0f, 0xFF000040); 947 drawList->AddCircle(g, 5.0f, 0xFF004000); 948 drawList->AddCircle(b, 5.0f, 0xFF400000); 949 drawList->AddCircle(w, 5.0f, 0xFFFFFFFF); 950 drawList->AddTriangle(r, g, b, 0xFFFFFFFF); 951 952 // Re-position cursor immediate after the diagram for subsequent controls 953 ImGui::SetCursorPos(endPos); 954 } 955 956 void Viewer::drawImGui() { 957 // Support drawing the ImGui demo window. Superfluous, but gives a good idea of what's possible 958 if (fShowImGuiTestWindow) { 959 ImGui::ShowTestWindow(&fShowImGuiTestWindow); 960 } 961 962 if (fShowImGuiDebugWindow) { 963 // We have some dynamic content that sizes to fill available size. If the scroll bar isn't 964 // always visible, we can end up in a layout feedback loop. 965 ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiSetCond_FirstUseEver); 966 DisplayParams params = fWindow->getRequestedDisplayParams(); 967 bool paramsChanged = false; 968 if (ImGui::Begin("Tools", &fShowImGuiDebugWindow, 969 ImGuiWindowFlags_AlwaysVerticalScrollbar)) { 970 if (ImGui::CollapsingHeader("Backend")) { 971 int newBackend = static_cast<int>(fBackendType); 972 ImGui::RadioButton("Raster", &newBackend, sk_app::Window::kRaster_BackendType); 973 ImGui::SameLine(); 974 ImGui::RadioButton("OpenGL", &newBackend, sk_app::Window::kNativeGL_BackendType); 975 #if SK_ANGLE && defined(SK_BUILD_FOR_WIN) 976 ImGui::SameLine(); 977 ImGui::RadioButton("ANGLE", &newBackend, sk_app::Window::kANGLE_BackendType); 978 #endif 979 #if defined(SK_VULKAN) 980 ImGui::SameLine(); 981 ImGui::RadioButton("Vulkan", &newBackend, sk_app::Window::kVulkan_BackendType); 982 #endif 983 if (newBackend != fBackendType) { 984 fDeferredActions.push_back([=]() { 985 this->setBackend(static_cast<sk_app::Window::BackendType>(newBackend)); 986 }); 987 } 988 989 const GrContext* ctx = fWindow->getGrContext(); 990 bool* wire = ¶ms.fGrContextOptions.fWireframeMode; 991 if (ctx && ImGui::Checkbox("Wireframe Mode", wire)) { 992 paramsChanged = true; 993 } 994 995 if (ctx) { 996 int sampleCount = fWindow->sampleCount(); 997 ImGui::Text("MSAA: "); ImGui::SameLine(); 998 ImGui::RadioButton("1", &sampleCount, 1); ImGui::SameLine(); 999 ImGui::RadioButton("4", &sampleCount, 4); ImGui::SameLine(); 1000 ImGui::RadioButton("8", &sampleCount, 8); ImGui::SameLine(); 1001 ImGui::RadioButton("16", &sampleCount, 16); 1002 1003 if (sampleCount != params.fMSAASampleCount) { 1004 params.fMSAASampleCount = sampleCount; 1005 paramsChanged = true; 1006 } 1007 } 1008 1009 if (ImGui::TreeNode("Path Renderers")) { 1010 GpuPathRenderers prevPr = params.fGrContextOptions.fGpuPathRenderers; 1011 auto prButton = [&](GpuPathRenderers x) { 1012 if (ImGui::RadioButton(gPathRendererNames[x].c_str(), prevPr == x)) { 1013 if (x != params.fGrContextOptions.fGpuPathRenderers) { 1014 params.fGrContextOptions.fGpuPathRenderers = x; 1015 paramsChanged = true; 1016 } 1017 } 1018 }; 1019 1020 if (!ctx) { 1021 ImGui::RadioButton("Software", true); 1022 } else if (fWindow->sampleCount() > 1) { 1023 prButton(GpuPathRenderers::kDefault); 1024 prButton(GpuPathRenderers::kAll); 1025 if (ctx->caps()->shaderCaps()->pathRenderingSupport()) { 1026 prButton(GpuPathRenderers::kStencilAndCover); 1027 } 1028 if (ctx->caps()->sampleShadingSupport()) { 1029 prButton(GpuPathRenderers::kMSAA); 1030 } 1031 prButton(GpuPathRenderers::kTessellating); 1032 prButton(GpuPathRenderers::kNone); 1033 } else { 1034 prButton(GpuPathRenderers::kDefault); 1035 prButton(GpuPathRenderers::kAll); 1036 if (GrCoverageCountingPathRenderer::IsSupported(*ctx->caps())) { 1037 prButton(GpuPathRenderers::kCoverageCounting); 1038 } 1039 prButton(GpuPathRenderers::kSmall); 1040 prButton(GpuPathRenderers::kTessellating); 1041 prButton(GpuPathRenderers::kNone); 1042 } 1043 ImGui::TreePop(); 1044 } 1045 } 1046 1047 if (fShowSlidePicker) { 1048 ImGui::SetNextTreeNodeOpen(true); 1049 } 1050 1051 if (ImGui::CollapsingHeader("Slide")) { 1052 static ImGuiTextFilter filter; 1053 static ImVector<const char*> filteredSlideNames; 1054 static ImVector<int> filteredSlideIndices; 1055 1056 if (fShowSlidePicker) { 1057 ImGui::SetKeyboardFocusHere(); 1058 fShowSlidePicker = false; 1059 } 1060 1061 filter.Draw(); 1062 filteredSlideNames.clear(); 1063 filteredSlideIndices.clear(); 1064 int filteredIndex = 0; 1065 for (int i = 0; i < fSlides.count(); ++i) { 1066 const char* slideName = fSlides[i]->getName().c_str(); 1067 if (filter.PassFilter(slideName) || i == fCurrentSlide) { 1068 if (i == fCurrentSlide) { 1069 filteredIndex = filteredSlideIndices.size(); 1070 } 1071 filteredSlideNames.push_back(slideName); 1072 filteredSlideIndices.push_back(i); 1073 } 1074 } 1075 1076 if (ImGui::ListBox("", &filteredIndex, filteredSlideNames.begin(), 1077 filteredSlideNames.size(), 20)) { 1078 this->setCurrentSlide(filteredSlideIndices[filteredIndex]); 1079 } 1080 } 1081 1082 if (ImGui::CollapsingHeader("Color Mode")) { 1083 ColorMode newMode = fColorMode; 1084 auto cmButton = [&](ColorMode mode, const char* label) { 1085 if (ImGui::RadioButton(label, mode == fColorMode)) { 1086 newMode = mode; 1087 } 1088 }; 1089 1090 cmButton(ColorMode::kLegacy, "Legacy 8888"); 1091 cmButton(ColorMode::kColorManagedSRGB8888_NonLinearBlending, 1092 "Color Managed 8888 (Nonlinear blending)"); 1093 cmButton(ColorMode::kColorManagedSRGB8888, "Color Managed 8888"); 1094 cmButton(ColorMode::kColorManagedLinearF16, "Color Managed F16"); 1095 1096 if (newMode != fColorMode) { 1097 // It isn't safe to switch color mode now (in the middle of painting). We might 1098 // tear down the back-end, etc... Defer this change until the next onIdle. 1099 fDeferredActions.push_back([=]() { 1100 this->setColorMode(newMode); 1101 }); 1102 } 1103 1104 // Pick from common gamuts: 1105 int primariesIdx = 4; // Default: Custom 1106 for (size_t i = 0; i < SK_ARRAY_COUNT(gNamedPrimaries); ++i) { 1107 if (primaries_equal(*gNamedPrimaries[i].fPrimaries, fColorSpacePrimaries)) { 1108 primariesIdx = i; 1109 break; 1110 } 1111 } 1112 1113 // When we're in xform canvas mode, we can alter the transfer function, too 1114 if (ColorMode::kColorManagedSRGB8888_NonLinearBlending == fColorMode) { 1115 ImGui::SliderFloat("Gamma", &fColorSpaceTransferFn.fG, 0.5f, 3.5f); 1116 } 1117 1118 if (ImGui::Combo("Primaries", &primariesIdx, 1119 "sRGB\0AdobeRGB\0P3\0Rec. 2020\0Custom\0\0")) { 1120 if (primariesIdx >= 0 && primariesIdx <= 3) { 1121 fColorSpacePrimaries = *gNamedPrimaries[primariesIdx].fPrimaries; 1122 } 1123 } 1124 1125 // Allow direct editing of gamut 1126 ImGui_Primaries(&fColorSpacePrimaries, &fImGuiGamutPaint); 1127 } 1128 } 1129 if (paramsChanged) { 1130 fDeferredActions.push_back([=]() { 1131 fWindow->setRequestedDisplayParams(params); 1132 fWindow->inval(); 1133 this->updateTitle(); 1134 }); 1135 } 1136 ImGui::End(); 1137 } 1138 1139 if (fShowZoomWindow && fLastImage) { 1140 if (ImGui::Begin("Zoom", &fShowZoomWindow, ImVec2(200, 200))) { 1141 static int zoomFactor = 8; 1142 if (ImGui::Button("<<")) { 1143 zoomFactor = SkTMax(zoomFactor / 2, 4); 1144 } 1145 ImGui::SameLine(); ImGui::Text("%2d", zoomFactor); ImGui::SameLine(); 1146 if (ImGui::Button(">>")) { 1147 zoomFactor = SkTMin(zoomFactor * 2, 32); 1148 } 1149 1150 ImVec2 mousePos = ImGui::GetMousePos(); 1151 ImVec2 avail = ImGui::GetContentRegionAvail(); 1152 1153 uint32_t pixel = 0; 1154 SkImageInfo info = SkImageInfo::MakeN32Premul(1, 1); 1155 if (fLastImage->readPixels(info, &pixel, info.minRowBytes(), mousePos.x, mousePos.y)) { 1156 ImGui::SameLine(); 1157 ImGui::Text("(X, Y): %d, %d RGBA: %x %x %x %x", 1158 sk_float_round2int(mousePos.x), sk_float_round2int(mousePos.y), 1159 SkGetPackedR32(pixel), SkGetPackedG32(pixel), 1160 SkGetPackedB32(pixel), SkGetPackedA32(pixel)); 1161 } 1162 1163 fImGuiLayer.skiaWidget(avail, [=](SkCanvas* c) { 1164 // Translate so the region of the image that's under the mouse cursor is centered 1165 // in the zoom canvas: 1166 c->scale(zoomFactor, zoomFactor); 1167 c->translate(avail.x * 0.5f / zoomFactor - mousePos.x - 0.5f, 1168 avail.y * 0.5f / zoomFactor - mousePos.y - 0.5f); 1169 c->drawImage(this->fLastImage, 0, 0); 1170 1171 SkPaint outline; 1172 outline.setStyle(SkPaint::kStroke_Style); 1173 c->drawRect(SkRect::MakeXYWH(mousePos.x, mousePos.y, 1, 1), outline); 1174 }); 1175 } 1176 1177 ImGui::End(); 1178 } 1179 } 1180 1181 void Viewer::onIdle() { 1182 for (int i = 0; i < fDeferredActions.count(); ++i) { 1183 fDeferredActions[i](); 1184 } 1185 fDeferredActions.reset(); 1186 1187 fStatsLayer.beginTiming(fAnimateTimer); 1188 fAnimTimer.updateTime(); 1189 bool animateWantsInval = fSlides[fCurrentSlide]->animate(fAnimTimer); 1190 fStatsLayer.endTiming(fAnimateTimer); 1191 1192 ImGuiIO& io = ImGui::GetIO(); 1193 if (animateWantsInval || fStatsLayer.getActive() || fRefresh || io.MetricsActiveWindows) { 1194 fWindow->inval(); 1195 } 1196 } 1197 1198 void Viewer::updateUIState() { 1199 if (!fWindow) { 1200 return; 1201 } 1202 if (fWindow->sampleCount() < 1) { 1203 return; // Surface hasn't been created yet. 1204 } 1205 1206 // Slide state 1207 Json::Value slideState(Json::objectValue); 1208 slideState[kName] = kSlideStateName; 1209 slideState[kValue] = fSlides[fCurrentSlide]->getName().c_str(); 1210 if (fAllSlideNames.size() == 0) { 1211 for(auto slide : fSlides) { 1212 fAllSlideNames.append(Json::Value(slide->getName().c_str())); 1213 } 1214 } 1215 slideState[kOptions] = fAllSlideNames; 1216 1217 // Backend state 1218 Json::Value backendState(Json::objectValue); 1219 backendState[kName] = kBackendStateName; 1220 backendState[kValue] = kBackendTypeStrings[fBackendType]; 1221 backendState[kOptions] = Json::Value(Json::arrayValue); 1222 for (auto str : kBackendTypeStrings) { 1223 backendState[kOptions].append(Json::Value(str)); 1224 } 1225 1226 // MSAA state 1227 Json::Value msaaState(Json::objectValue); 1228 msaaState[kName] = kMSAAStateName; 1229 msaaState[kValue] = fWindow->sampleCount(); 1230 msaaState[kOptions] = Json::Value(Json::arrayValue); 1231 if (sk_app::Window::kRaster_BackendType == fBackendType) { 1232 msaaState[kOptions].append(Json::Value(0)); 1233 } else { 1234 for (int msaa : {0, 4, 8, 16}) { 1235 msaaState[kOptions].append(Json::Value(msaa)); 1236 } 1237 } 1238 1239 // Path renderer state 1240 GpuPathRenderers pr = fWindow->getRequestedDisplayParams().fGrContextOptions.fGpuPathRenderers; 1241 Json::Value prState(Json::objectValue); 1242 prState[kName] = kPathRendererStateName; 1243 prState[kValue] = gPathRendererNames[pr]; 1244 prState[kOptions] = Json::Value(Json::arrayValue); 1245 const GrContext* ctx = fWindow->getGrContext(); 1246 if (!ctx) { 1247 prState[kOptions].append("Software"); 1248 } else if (fWindow->sampleCount() > 1) { 1249 prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kDefault]); 1250 prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kAll]); 1251 if (ctx->caps()->shaderCaps()->pathRenderingSupport()) { 1252 prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kStencilAndCover]); 1253 } 1254 if (ctx->caps()->sampleShadingSupport()) { 1255 prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kMSAA]); 1256 } 1257 prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kTessellating]); 1258 prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kNone]); 1259 } else { 1260 prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kDefault]); 1261 prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kAll]); 1262 if (GrCoverageCountingPathRenderer::IsSupported(*ctx->caps())) { 1263 prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kCoverageCounting]); 1264 } 1265 prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kSmall]); 1266 prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kTessellating]); 1267 prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kNone]); 1268 } 1269 1270 // Softkey state 1271 Json::Value softkeyState(Json::objectValue); 1272 softkeyState[kName] = kSoftkeyStateName; 1273 softkeyState[kValue] = kSoftkeyHint; 1274 softkeyState[kOptions] = Json::Value(Json::arrayValue); 1275 softkeyState[kOptions].append(kSoftkeyHint); 1276 for (const auto& softkey : fCommands.getCommandsAsSoftkeys()) { 1277 softkeyState[kOptions].append(Json::Value(softkey.c_str())); 1278 } 1279 1280 // FPS state 1281 Json::Value fpsState(Json::objectValue); 1282 fpsState[kName] = kFpsStateName; 1283 double animTime = fStatsLayer.getLastTime(fAnimateTimer); 1284 double paintTime = fStatsLayer.getLastTime(fPaintTimer); 1285 double flushTime = fStatsLayer.getLastTime(fFlushTimer); 1286 fpsState[kValue] = SkStringPrintf("%8.3lf ms\n\nA %8.3lf\nP %8.3lf\nF%8.3lf", 1287 animTime + paintTime + flushTime, 1288 animTime, paintTime, flushTime).c_str(); 1289 fpsState[kOptions] = Json::Value(Json::arrayValue); 1290 1291 Json::Value state(Json::arrayValue); 1292 state.append(slideState); 1293 state.append(backendState); 1294 state.append(msaaState); 1295 state.append(prState); 1296 state.append(softkeyState); 1297 state.append(fpsState); 1298 1299 fWindow->setUIState(state.toStyledString().c_str()); 1300 } 1301 1302 void Viewer::onUIStateChanged(const SkString& stateName, const SkString& stateValue) { 1303 // For those who will add more features to handle the state change in this function: 1304 // After the change, please call updateUIState no notify the frontend (e.g., Android app). 1305 // For example, after slide change, updateUIState is called inside setupCurrentSlide; 1306 // after backend change, updateUIState is called in this function. 1307 if (stateName.equals(kSlideStateName)) { 1308 for (int i = 0; i < fSlides.count(); ++i) { 1309 if (fSlides[i]->getName().equals(stateValue)) { 1310 this->setCurrentSlide(i); 1311 return; 1312 } 1313 } 1314 1315 SkDebugf("Slide not found: %s", stateValue.c_str()); 1316 } else if (stateName.equals(kBackendStateName)) { 1317 for (int i = 0; i < sk_app::Window::kBackendTypeCount; i++) { 1318 if (stateValue.equals(kBackendTypeStrings[i])) { 1319 if (fBackendType != i) { 1320 fBackendType = (sk_app::Window::BackendType)i; 1321 fWindow->detach(); 1322 fWindow->attach(backend_type_for_window(fBackendType)); 1323 } 1324 break; 1325 } 1326 } 1327 } else if (stateName.equals(kMSAAStateName)) { 1328 DisplayParams params = fWindow->getRequestedDisplayParams(); 1329 int sampleCount = atoi(stateValue.c_str()); 1330 if (sampleCount != params.fMSAASampleCount) { 1331 params.fMSAASampleCount = sampleCount; 1332 fWindow->setRequestedDisplayParams(params); 1333 fWindow->inval(); 1334 this->updateTitle(); 1335 this->updateUIState(); 1336 } 1337 } else if (stateName.equals(kPathRendererStateName)) { 1338 DisplayParams params = fWindow->getRequestedDisplayParams(); 1339 for (const auto& pair : gPathRendererNames) { 1340 if (pair.second == stateValue.c_str()) { 1341 if (params.fGrContextOptions.fGpuPathRenderers != pair.first) { 1342 params.fGrContextOptions.fGpuPathRenderers = pair.first; 1343 fWindow->setRequestedDisplayParams(params); 1344 fWindow->inval(); 1345 this->updateTitle(); 1346 this->updateUIState(); 1347 } 1348 break; 1349 } 1350 } 1351 } else if (stateName.equals(kSoftkeyStateName)) { 1352 if (!stateValue.equals(kSoftkeyHint)) { 1353 fCommands.onSoftkey(stateValue); 1354 this->updateUIState(); // This is still needed to reset the value to kSoftkeyHint 1355 } 1356 } else if (stateName.equals(kRefreshStateName)) { 1357 // This state is actually NOT in the UI state. 1358 // We use this to allow Android to quickly set bool fRefresh. 1359 fRefresh = stateValue.equals(kON); 1360 } else { 1361 SkDebugf("Unknown stateName: %s", stateName.c_str()); 1362 } 1363 } 1364 1365 bool Viewer::onKey(sk_app::Window::Key key, sk_app::Window::InputState state, uint32_t modifiers) { 1366 return fCommands.onKey(key, state, modifiers); 1367 } 1368 1369 bool Viewer::onChar(SkUnichar c, uint32_t modifiers) { 1370 if (fSlides[fCurrentSlide]->onChar(c)) { 1371 fWindow->inval(); 1372 return true; 1373 } else { 1374 return fCommands.onChar(c, modifiers); 1375 } 1376 } 1377