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 "SKPSlide.h" 15 16 #include "GrContext.h" 17 #include "SkATrace.h" 18 #include "SkCanvas.h" 19 #include "SkColorSpace_Base.h" 20 #include "SkColorSpaceXformCanvas.h" 21 #include "SkCommandLineFlags.h" 22 #include "SkCommonFlagsPathRenderer.h" 23 #include "SkDashPathEffect.h" 24 #include "SkGraphics.h" 25 #include "SkImagePriv.h" 26 #include "SkMetaData.h" 27 #include "SkOnce.h" 28 #include "SkOSFile.h" 29 #include "SkOSPath.h" 30 #include "SkRandom.h" 31 #include "SkStream.h" 32 #include "SkSurface.h" 33 #include "SkSwizzle.h" 34 #include "SkTaskGroup.h" 35 #include "SkTime.h" 36 37 #include "imgui.h" 38 39 #include <stdlib.h> 40 #include <map> 41 42 using namespace sk_app; 43 44 using GpuPathRenderers = GrContextOptions::GpuPathRenderers; 45 static std::map<GpuPathRenderers, std::string> gPathRendererNames; 46 47 Application* Application::Create(int argc, char** argv, void* platformData) { 48 return new Viewer(argc, argv, platformData); 49 } 50 51 static void on_backend_created_func(void* userData) { 52 Viewer* vv = reinterpret_cast<Viewer*>(userData); 53 54 return vv->onBackendCreated(); 55 } 56 57 static void on_paint_handler(SkCanvas* canvas, void* userData) { 58 Viewer* vv = reinterpret_cast<Viewer*>(userData); 59 60 return vv->onPaint(canvas); 61 } 62 63 static bool on_touch_handler(intptr_t owner, Window::InputState state, float x, float y, void* userData) 64 { 65 Viewer* viewer = reinterpret_cast<Viewer*>(userData); 66 67 return viewer->onTouch(owner, state, x, y); 68 } 69 70 static void on_ui_state_changed_handler(const SkString& stateName, const SkString& stateValue, void* userData) { 71 Viewer* viewer = reinterpret_cast<Viewer*>(userData); 72 73 return viewer->onUIStateChanged(stateName, stateValue); 74 } 75 76 static bool on_mouse_handler(int x, int y, Window::InputState state, uint32_t modifiers, 77 void* userData) { 78 ImGuiIO& io = ImGui::GetIO(); 79 io.MousePos.x = static_cast<float>(x); 80 io.MousePos.y = static_cast<float>(y); 81 if (Window::kDown_InputState == state) { 82 io.MouseDown[0] = true; 83 } else if (Window::kUp_InputState == state) { 84 io.MouseDown[0] = false; 85 } 86 return true; 87 } 88 89 static bool on_mouse_wheel_handler(float delta, uint32_t modifiers, void* userData) { 90 ImGuiIO& io = ImGui::GetIO(); 91 io.MouseWheel += delta; 92 return true; 93 } 94 95 static bool on_key_handler(Window::Key key, Window::InputState state, uint32_t modifiers, 96 void* userData) { 97 ImGuiIO& io = ImGui::GetIO(); 98 io.KeysDown[static_cast<int>(key)] = (Window::kDown_InputState == state); 99 100 if (io.WantCaptureKeyboard) { 101 return true; 102 } else { 103 Viewer* viewer = reinterpret_cast<Viewer*>(userData); 104 return viewer->onKey(key, state, modifiers); 105 } 106 } 107 108 static bool on_char_handler(SkUnichar c, uint32_t modifiers, void* userData) { 109 ImGuiIO& io = ImGui::GetIO(); 110 if (io.WantTextInput) { 111 if (c > 0 && c < 0x10000) { 112 io.AddInputCharacter(c); 113 } 114 return true; 115 } else { 116 Viewer* viewer = reinterpret_cast<Viewer*>(userData); 117 return viewer->onChar(c, modifiers); 118 } 119 } 120 121 static DEFINE_bool2(fullscreen, f, true, "Run fullscreen."); 122 123 static DEFINE_string2(match, m, nullptr, 124 "[~][^]substring[$] [...] of bench name to run.\n" 125 "Multiple matches may be separated by spaces.\n" 126 "~ causes a matching bench to always be skipped\n" 127 "^ requires the start of the bench to match\n" 128 "$ requires the end of the bench to match\n" 129 "^ and $ requires an exact match\n" 130 "If a bench does not match any list entry,\n" 131 "it is skipped unless some list entry starts with ~"); 132 133 DEFINE_string(slide, "", "Start on this sample."); 134 DEFINE_bool(list, false, "List samples?"); 135 136 #ifdef SK_VULKAN 137 # define BACKENDS_STR "\"sw\", \"gl\", and \"vk\"" 138 #else 139 # define BACKENDS_STR "\"sw\" and \"gl\"" 140 #endif 141 142 #ifdef SK_BUILD_FOR_ANDROID 143 static DEFINE_string(skps, "/data/local/tmp/skia", "Directory to read skps from."); 144 static DEFINE_string(jpgs, "/data/local/tmp/skia", "Directory to read jpgs from."); 145 #else 146 static DEFINE_string(skps, "skps", "Directory to read skps from."); 147 static DEFINE_string(jpgs, "jpgs", "Directory to read jpgs from."); 148 #endif 149 150 static DEFINE_string2(backend, b, "sw", "Backend to use. Allowed values are " BACKENDS_STR "."); 151 152 static DEFINE_bool(atrace, false, "Enable support for using ATrace. ATrace is only supported on Android."); 153 154 DEFINE_int32(msaa, 0, "Number of subpixel samples. 0 for no HW antialiasing."); 155 DEFINE_pathrenderer_flag; 156 157 DEFINE_bool(instancedRendering, false, "Enable instanced rendering on GPU backends."); 158 159 const char *kBackendTypeStrings[sk_app::Window::kBackendTypeCount] = { 160 "OpenGL", 161 #ifdef SK_VULKAN 162 "Vulkan", 163 #endif 164 "Raster" 165 }; 166 167 static sk_app::Window::BackendType get_backend_type(const char* str) { 168 #ifdef SK_VULKAN 169 if (0 == strcmp(str, "vk")) { 170 return sk_app::Window::kVulkan_BackendType; 171 } else 172 #endif 173 if (0 == strcmp(str, "gl")) { 174 return sk_app::Window::kNativeGL_BackendType; 175 } else if (0 == strcmp(str, "sw")) { 176 return sk_app::Window::kRaster_BackendType; 177 } else { 178 SkDebugf("Unknown backend type, %s, defaulting to sw.", str); 179 return sk_app::Window::kRaster_BackendType; 180 } 181 } 182 183 static SkColorSpacePrimaries gSrgbPrimaries = { 184 0.64f, 0.33f, 185 0.30f, 0.60f, 186 0.15f, 0.06f, 187 0.3127f, 0.3290f }; 188 189 static SkColorSpacePrimaries gAdobePrimaries = { 190 0.64f, 0.33f, 191 0.21f, 0.71f, 192 0.15f, 0.06f, 193 0.3127f, 0.3290f }; 194 195 static SkColorSpacePrimaries gP3Primaries = { 196 0.680f, 0.320f, 197 0.265f, 0.690f, 198 0.150f, 0.060f, 199 0.3127f, 0.3290f }; 200 201 static SkColorSpacePrimaries gRec2020Primaries = { 202 0.708f, 0.292f, 203 0.170f, 0.797f, 204 0.131f, 0.046f, 205 0.3127f, 0.3290f }; 206 207 struct NamedPrimaries { 208 const char* fName; 209 SkColorSpacePrimaries* fPrimaries; 210 } gNamedPrimaries[] = { 211 { "sRGB", &gSrgbPrimaries }, 212 { "AdobeRGB", &gAdobePrimaries }, 213 { "P3", &gP3Primaries }, 214 { "Rec. 2020", &gRec2020Primaries }, 215 }; 216 217 static bool primaries_equal(const SkColorSpacePrimaries& a, const SkColorSpacePrimaries& b) { 218 return memcmp(&a, &b, sizeof(SkColorSpacePrimaries)) == 0; 219 } 220 221 const char* kName = "name"; 222 const char* kValue = "value"; 223 const char* kOptions = "options"; 224 const char* kSlideStateName = "Slide"; 225 const char* kBackendStateName = "Backend"; 226 const char* kMSAAStateName = "MSAA"; 227 const char* kPathRendererStateName = "Path renderer"; 228 const char* kInstancedRenderingStateName = "Instanced rendering"; 229 const char* kSoftkeyStateName = "Softkey"; 230 const char* kSoftkeyHint = "Please select a softkey"; 231 const char* kFpsStateName = "FPS"; 232 const char* kON = "ON"; 233 const char* kOFF = "OFF"; 234 const char* kRefreshStateName = "Refresh"; 235 236 Viewer::Viewer(int argc, char** argv, void* platformData) 237 : fCurrentMeasurement(0) 238 , fDisplayStats(false) 239 , fRefresh(false) 240 , fShowImGuiDebugWindow(false) 241 , fShowImGuiTestWindow(false) 242 , fShowZoomWindow(false) 243 , fLastImage(nullptr) 244 , fBackendType(sk_app::Window::kNativeGL_BackendType) 245 , fColorMode(ColorMode::kLegacy) 246 , fColorSpacePrimaries(gSrgbPrimaries) 247 , fZoomCenterX(0.0f) 248 , fZoomCenterY(0.0f) 249 , fZoomLevel(0.0f) 250 , fZoomScale(SK_Scalar1) 251 { 252 static SkTaskGroup::Enabler kTaskGroupEnabler; 253 SkGraphics::Init(); 254 255 static SkOnce initPathRendererNames; 256 initPathRendererNames([]() { 257 gPathRendererNames[GpuPathRenderers::kAll] = "Default Ganesh Behavior (best path renderer)"; 258 gPathRendererNames[GpuPathRenderers::kStencilAndCover] = "NV_path_rendering"; 259 gPathRendererNames[GpuPathRenderers::kMSAA] = "Sample shading"; 260 gPathRendererNames[GpuPathRenderers::kSmall] = "Small paths (cached sdf or alpha masks)"; 261 gPathRendererNames[GpuPathRenderers::kTessellating] = "Tessellating"; 262 gPathRendererNames[GpuPathRenderers::kDefault] = "Original Ganesh path renderer"; 263 gPathRendererNames[GpuPathRenderers::kNone] = "Software masks"; 264 }); 265 266 memset(fPaintTimes, 0, sizeof(fPaintTimes)); 267 memset(fFlushTimes, 0, sizeof(fFlushTimes)); 268 memset(fAnimateTimes, 0, sizeof(fAnimateTimes)); 269 270 SkDebugf("Command line arguments: "); 271 for (int i = 1; i < argc; ++i) { 272 SkDebugf("%s ", argv[i]); 273 } 274 SkDebugf("\n"); 275 276 SkCommandLineFlags::Parse(argc, argv); 277 #ifdef SK_BUILD_FOR_ANDROID 278 SetResourcePath("/data/local/tmp/skia"); 279 #endif 280 281 if (FLAGS_atrace) { 282 SkEventTracer::SetInstance(new SkATrace()); 283 } 284 285 fBackendType = get_backend_type(FLAGS_backend[0]); 286 fWindow = Window::CreateNativeWindow(platformData); 287 288 DisplayParams displayParams; 289 displayParams.fMSAASampleCount = FLAGS_msaa; 290 displayParams.fGrContextOptions.fEnableInstancedRendering = FLAGS_instancedRendering; 291 displayParams.fGrContextOptions.fGpuPathRenderers = CollectGpuPathRenderersFromFlags(); 292 fWindow->setRequestedDisplayParams(displayParams); 293 294 // register callbacks 295 fCommands.attach(fWindow); 296 fWindow->registerBackendCreatedFunc(on_backend_created_func, this); 297 fWindow->registerPaintFunc(on_paint_handler, this); 298 fWindow->registerTouchFunc(on_touch_handler, this); 299 fWindow->registerUIStateChangedFunc(on_ui_state_changed_handler, this); 300 fWindow->registerMouseFunc(on_mouse_handler, this); 301 fWindow->registerMouseWheelFunc(on_mouse_wheel_handler, this); 302 fWindow->registerKeyFunc(on_key_handler, this); 303 fWindow->registerCharFunc(on_char_handler, this); 304 305 // add key-bindings 306 fCommands.addCommand(' ', "GUI", "Toggle Debug GUI", [this]() { 307 this->fShowImGuiDebugWindow = !this->fShowImGuiDebugWindow; 308 fWindow->inval(); 309 }); 310 fCommands.addCommand('g', "GUI", "Toggle GUI Demo", [this]() { 311 this->fShowImGuiTestWindow = !this->fShowImGuiTestWindow; 312 fWindow->inval(); 313 }); 314 fCommands.addCommand('z', "GUI", "Toggle zoom window", [this]() { 315 this->fShowZoomWindow = !this->fShowZoomWindow; 316 fWindow->inval(); 317 }); 318 fCommands.addCommand('s', "Overlays", "Toggle stats display", [this]() { 319 this->fDisplayStats = !this->fDisplayStats; 320 fWindow->inval(); 321 }); 322 fCommands.addCommand('c', "Modes", "Cycle color mode", [this]() { 323 switch (fColorMode) { 324 case ColorMode::kLegacy: 325 this->setColorMode(ColorMode::kColorManagedSRGB8888_NonLinearBlending); 326 break; 327 case ColorMode::kColorManagedSRGB8888_NonLinearBlending: 328 this->setColorMode(ColorMode::kColorManagedSRGB8888); 329 break; 330 case ColorMode::kColorManagedSRGB8888: 331 this->setColorMode(ColorMode::kColorManagedLinearF16); 332 break; 333 case ColorMode::kColorManagedLinearF16: 334 this->setColorMode(ColorMode::kLegacy); 335 break; 336 } 337 }); 338 fCommands.addCommand(Window::Key::kRight, "Right", "Navigation", "Next slide", [this]() { 339 int previousSlide = fCurrentSlide; 340 fCurrentSlide++; 341 if (fCurrentSlide >= fSlides.count()) { 342 fCurrentSlide = 0; 343 } 344 this->setupCurrentSlide(previousSlide); 345 }); 346 fCommands.addCommand(Window::Key::kLeft, "Left", "Navigation", "Previous slide", [this]() { 347 int previousSlide = fCurrentSlide; 348 fCurrentSlide--; 349 if (fCurrentSlide < 0) { 350 fCurrentSlide = fSlides.count() - 1; 351 } 352 this->setupCurrentSlide(previousSlide); 353 }); 354 fCommands.addCommand(Window::Key::kUp, "Up", "Transform", "Zoom in", [this]() { 355 this->changeZoomLevel(1.f / 32.f); 356 fWindow->inval(); 357 }); 358 fCommands.addCommand(Window::Key::kDown, "Down", "Transform", "Zoom out", [this]() { 359 this->changeZoomLevel(-1.f / 32.f); 360 fWindow->inval(); 361 }); 362 fCommands.addCommand('d', "Modes", "Change rendering backend", [this]() { 363 sk_app::Window::BackendType newBackend = fBackendType; 364 #if defined(SK_BUILD_FOR_WIN) || defined(SK_BUILD_FOR_MAC) 365 if (sk_app::Window::kRaster_BackendType == fBackendType) { 366 newBackend = sk_app::Window::kNativeGL_BackendType; 367 #ifdef SK_VULKAN 368 } else if (sk_app::Window::kNativeGL_BackendType == fBackendType) { 369 newBackend = sk_app::Window::kVulkan_BackendType; 370 #endif 371 } else { 372 newBackend = sk_app::Window::kRaster_BackendType; 373 } 374 #elif defined(SK_BUILD_FOR_UNIX) 375 // Switching to and from Vulkan is problematic on Linux so disabled for now 376 if (sk_app::Window::kRaster_BackendType == fBackendType) { 377 newBackend = sk_app::Window::kNativeGL_BackendType; 378 } else if (sk_app::Window::kNativeGL_BackendType == fBackendType) { 379 newBackend = sk_app::Window::kRaster_BackendType; 380 } 381 #endif 382 383 this->setBackend(newBackend); 384 }); 385 386 // set up slides 387 this->initSlides(); 388 this->setStartupSlide(); 389 if (FLAGS_list) { 390 this->listNames(); 391 } 392 393 fAnimTimer.run(); 394 395 // ImGui initialization: 396 ImGuiIO& io = ImGui::GetIO(); 397 io.DisplaySize.x = static_cast<float>(fWindow->width()); 398 io.DisplaySize.y = static_cast<float>(fWindow->height()); 399 400 // Keymap... 401 io.KeyMap[ImGuiKey_Tab] = (int)Window::Key::kTab; 402 io.KeyMap[ImGuiKey_LeftArrow] = (int)Window::Key::kLeft; 403 io.KeyMap[ImGuiKey_RightArrow] = (int)Window::Key::kRight; 404 io.KeyMap[ImGuiKey_UpArrow] = (int)Window::Key::kUp; 405 io.KeyMap[ImGuiKey_DownArrow] = (int)Window::Key::kDown; 406 io.KeyMap[ImGuiKey_PageUp] = (int)Window::Key::kPageUp; 407 io.KeyMap[ImGuiKey_PageDown] = (int)Window::Key::kPageDown; 408 io.KeyMap[ImGuiKey_Home] = (int)Window::Key::kHome; 409 io.KeyMap[ImGuiKey_End] = (int)Window::Key::kEnd; 410 io.KeyMap[ImGuiKey_Delete] = (int)Window::Key::kDelete; 411 io.KeyMap[ImGuiKey_Backspace] = (int)Window::Key::kBack; 412 io.KeyMap[ImGuiKey_Enter] = (int)Window::Key::kOK; 413 io.KeyMap[ImGuiKey_Escape] = (int)Window::Key::kEscape; 414 io.KeyMap[ImGuiKey_A] = (int)Window::Key::kA; 415 io.KeyMap[ImGuiKey_C] = (int)Window::Key::kC; 416 io.KeyMap[ImGuiKey_V] = (int)Window::Key::kV; 417 io.KeyMap[ImGuiKey_X] = (int)Window::Key::kX; 418 io.KeyMap[ImGuiKey_Y] = (int)Window::Key::kY; 419 io.KeyMap[ImGuiKey_Z] = (int)Window::Key::kZ; 420 421 int w, h; 422 unsigned char* pixels; 423 io.Fonts->GetTexDataAsAlpha8(&pixels, &w, &h); 424 SkImageInfo info = SkImageInfo::MakeA8(w, h); 425 SkPixmap pmap(info, pixels, info.minRowBytes()); 426 SkMatrix localMatrix = SkMatrix::MakeScale(1.0f / w, 1.0f / h); 427 auto fontImage = SkImage::MakeFromRaster(pmap, nullptr, nullptr); 428 auto fontShader = fontImage->makeShader(SkShader::kClamp_TileMode, SkShader::kClamp_TileMode, 429 &localMatrix); 430 fImGuiFontPaint.setShader(fontShader); 431 fImGuiFontPaint.setColor(SK_ColorWHITE); 432 fImGuiFontPaint.setFilterQuality(kLow_SkFilterQuality); 433 io.Fonts->TexID = &fImGuiFontPaint; 434 435 auto gamutImage = GetResourceAsImage("gamut.png"); 436 if (gamutImage) { 437 auto gamutShader = gamutImage->makeShader(SkShader::kClamp_TileMode, 438 SkShader::kClamp_TileMode); 439 fImGuiGamutPaint.setShader(gamutShader); 440 } 441 fImGuiGamutPaint.setColor(SK_ColorWHITE); 442 fImGuiGamutPaint.setFilterQuality(kLow_SkFilterQuality); 443 444 fWindow->attach(fBackendType); 445 } 446 447 void Viewer::initSlides() { 448 fAllSlideNames = Json::Value(Json::arrayValue); 449 450 const skiagm::GMRegistry* gms(skiagm::GMRegistry::Head()); 451 while (gms) { 452 std::unique_ptr<skiagm::GM> gm(gms->factory()(nullptr)); 453 454 if (!SkCommandLineFlags::ShouldSkip(FLAGS_match, gm->getName())) { 455 sk_sp<Slide> slide(new GMSlide(gm.release())); 456 fSlides.push_back(slide); 457 } 458 459 gms = gms->next(); 460 } 461 462 // reverse array 463 for (int i = 0; i < fSlides.count()/2; ++i) { 464 sk_sp<Slide> temp = fSlides[i]; 465 fSlides[i] = fSlides[fSlides.count() - i - 1]; 466 fSlides[fSlides.count() - i - 1] = temp; 467 } 468 469 // samples 470 const SkViewRegister* reg = SkViewRegister::Head(); 471 while (reg) { 472 sk_sp<Slide> slide(new SampleSlide(reg->factory())); 473 if (!SkCommandLineFlags::ShouldSkip(FLAGS_match, slide->getName().c_str())) { 474 fSlides.push_back(slide); 475 } 476 reg = reg->next(); 477 } 478 479 // SKPs 480 for (int i = 0; i < FLAGS_skps.count(); i++) { 481 if (SkStrEndsWith(FLAGS_skps[i], ".skp")) { 482 if (SkCommandLineFlags::ShouldSkip(FLAGS_match, FLAGS_skps[i])) { 483 continue; 484 } 485 486 SkString path(FLAGS_skps[i]); 487 sk_sp<SKPSlide> slide(new SKPSlide(SkOSPath::Basename(path.c_str()), path)); 488 if (slide) { 489 fSlides.push_back(slide); 490 } 491 } else { 492 SkOSFile::Iter it(FLAGS_skps[i], ".skp"); 493 SkString skpName; 494 while (it.next(&skpName)) { 495 if (SkCommandLineFlags::ShouldSkip(FLAGS_match, skpName.c_str())) { 496 continue; 497 } 498 499 SkString path = SkOSPath::Join(FLAGS_skps[i], skpName.c_str()); 500 sk_sp<SKPSlide> slide(new SKPSlide(skpName, path)); 501 if (slide) { 502 fSlides.push_back(slide); 503 } 504 } 505 } 506 } 507 508 // JPGs 509 for (int i = 0; i < FLAGS_jpgs.count(); i++) { 510 SkOSFile::Iter it(FLAGS_jpgs[i], ".jpg"); 511 SkString jpgName; 512 while (it.next(&jpgName)) { 513 if (SkCommandLineFlags::ShouldSkip(FLAGS_match, jpgName.c_str())) { 514 continue; 515 } 516 517 SkString path = SkOSPath::Join(FLAGS_jpgs[i], jpgName.c_str()); 518 sk_sp<ImageSlide> slide(new ImageSlide(jpgName, path)); 519 if (slide) { 520 fSlides.push_back(slide); 521 } 522 } 523 } 524 } 525 526 527 Viewer::~Viewer() { 528 fWindow->detach(); 529 delete fWindow; 530 } 531 532 void Viewer::updateTitle() { 533 if (!fWindow) { 534 return; 535 } 536 if (fWindow->sampleCount() < 0) { 537 return; // Surface hasn't been created yet. 538 } 539 540 SkString title("Viewer: "); 541 title.append(fSlides[fCurrentSlide]->getName()); 542 543 switch (fColorMode) { 544 case ColorMode::kLegacy: 545 title.append(" Legacy 8888"); 546 break; 547 case ColorMode::kColorManagedSRGB8888_NonLinearBlending: 548 title.append(" ColorManaged 8888 (Nonlinear blending)"); 549 break; 550 case ColorMode::kColorManagedSRGB8888: 551 title.append(" ColorManaged 8888"); 552 break; 553 case ColorMode::kColorManagedLinearF16: 554 title.append(" ColorManaged F16"); 555 break; 556 } 557 558 if (ColorMode::kLegacy != fColorMode) { 559 int curPrimaries = -1; 560 for (size_t i = 0; i < SK_ARRAY_COUNT(gNamedPrimaries); ++i) { 561 if (primaries_equal(*gNamedPrimaries[i].fPrimaries, fColorSpacePrimaries)) { 562 curPrimaries = i; 563 break; 564 } 565 } 566 title.appendf(" %s", curPrimaries >= 0 ? gNamedPrimaries[curPrimaries].fName : "Custom"); 567 } 568 569 title.append(" ["); 570 title.append(kBackendTypeStrings[fBackendType]); 571 if (int msaa = fWindow->sampleCount()) { 572 title.appendf(" MSAA: %i", msaa); 573 } 574 title.append("]"); 575 576 GpuPathRenderers pr = fWindow->getRequestedDisplayParams().fGrContextOptions.fGpuPathRenderers; 577 if (GpuPathRenderers::kAll != pr) { 578 title.appendf(" [Path renderer: %s]", gPathRendererNames[pr].c_str()); 579 } 580 581 fWindow->setTitle(title.c_str()); 582 } 583 584 void Viewer::setStartupSlide() { 585 586 if (!FLAGS_slide.isEmpty()) { 587 int count = fSlides.count(); 588 for (int i = 0; i < count; i++) { 589 if (fSlides[i]->getName().equals(FLAGS_slide[0])) { 590 fCurrentSlide = i; 591 return; 592 } 593 } 594 595 fprintf(stderr, "Unknown slide \"%s\"\n", FLAGS_slide[0]); 596 this->listNames(); 597 } 598 599 fCurrentSlide = 0; 600 } 601 602 void Viewer::listNames() { 603 int count = fSlides.count(); 604 SkDebugf("All Slides:\n"); 605 for (int i = 0; i < count; i++) { 606 SkDebugf(" %s\n", fSlides[i]->getName().c_str()); 607 } 608 } 609 610 void Viewer::setupCurrentSlide(int previousSlide) { 611 if (fCurrentSlide == previousSlide) { 612 return; // no change; do nothing 613 } 614 // prepare dimensions for image slides 615 fSlides[fCurrentSlide]->load(SkIntToScalar(fWindow->width()), SkIntToScalar(fWindow->height())); 616 617 fGesture.reset(); 618 fDefaultMatrix.reset(); 619 fDefaultMatrixInv.reset(); 620 621 if (fWindow->supportsContentRect() && fWindow->scaleContentToFit()) { 622 const SkRect contentRect = fWindow->getContentRect(); 623 const SkISize slideSize = fSlides[fCurrentSlide]->getDimensions(); 624 const SkRect slideBounds = SkRect::MakeIWH(slideSize.width(), slideSize.height()); 625 if (contentRect.width() > 0 && contentRect.height() > 0) { 626 fDefaultMatrix.setRectToRect(slideBounds, contentRect, SkMatrix::kStart_ScaleToFit); 627 SkAssertResult(fDefaultMatrix.invert(&fDefaultMatrixInv)); 628 } 629 } 630 631 if (fWindow->supportsContentRect()) { 632 const SkISize slideSize = fSlides[fCurrentSlide]->getDimensions(); 633 SkRect windowRect = fWindow->getContentRect(); 634 fDefaultMatrixInv.mapRect(&windowRect); 635 fGesture.setTransLimit(SkRect::MakeWH(SkIntToScalar(slideSize.width()), 636 SkIntToScalar(slideSize.height())), 637 windowRect); 638 } 639 640 this->updateTitle(); 641 this->updateUIState(); 642 if (previousSlide >= 0) { 643 fSlides[previousSlide]->unload(); 644 } 645 fWindow->inval(); 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 if (fZoomLevel > 0) { 654 fZoomLevel = SkMinScalar(fZoomLevel, MAX_ZOOM_LEVEL); 655 fZoomScale = fZoomLevel + SK_Scalar1; 656 } else if (fZoomLevel < 0) { 657 fZoomLevel = SkMaxScalar(fZoomLevel, MIN_ZOOM_LEVEL); 658 fZoomScale = SK_Scalar1 / (SK_Scalar1 - fZoomLevel); 659 } else { 660 fZoomScale = SK_Scalar1; 661 } 662 } 663 664 SkMatrix Viewer::computeMatrix() { 665 SkMatrix m; 666 m.reset(); 667 668 if (fZoomLevel) { 669 SkPoint center; 670 //m = this->getLocalMatrix();//.invert(&m); 671 m.mapXY(fZoomCenterX, fZoomCenterY, ¢er); 672 SkScalar cx = center.fX; 673 SkScalar cy = center.fY; 674 675 m.setTranslate(-cx, -cy); 676 m.postScale(fZoomScale, fZoomScale); 677 m.postTranslate(cx, cy); 678 } 679 680 m.preConcat(fGesture.localM()); 681 m.preConcat(fGesture.globalM()); 682 683 return m; 684 } 685 686 void Viewer::setBackend(sk_app::Window::BackendType backendType) { 687 fBackendType = backendType; 688 689 fWindow->detach(); 690 691 #if defined(SK_BUILD_FOR_WIN) && defined(SK_VULKAN) 692 // Switching from OpenGL to Vulkan in the same window is problematic at this point on 693 // Windows, so we just delete the window and recreate it. 694 if (sk_app::Window::kVulkan_BackendType == fBackendType) { 695 delete fWindow; 696 fWindow = Window::CreateNativeWindow(nullptr); 697 698 // re-register callbacks 699 fCommands.attach(fWindow); 700 fWindow->registerBackendCreatedFunc(on_backend_created_func, this); 701 fWindow->registerPaintFunc(on_paint_handler, this); 702 fWindow->registerTouchFunc(on_touch_handler, this); 703 fWindow->registerUIStateChangedFunc(on_ui_state_changed_handler, this); 704 fWindow->registerMouseFunc(on_mouse_handler, this); 705 fWindow->registerMouseWheelFunc(on_mouse_wheel_handler, this); 706 fWindow->registerKeyFunc(on_key_handler, this); 707 fWindow->registerCharFunc(on_char_handler, this); 708 } 709 #endif 710 711 fWindow->attach(fBackendType); 712 } 713 714 void Viewer::setColorMode(ColorMode colorMode) { 715 fColorMode = colorMode; 716 717 // When we're in color managed mode, we tag our window surface as sRGB. If we've switched into 718 // or out of legacy/nonlinear mode, we need to update our window configuration. 719 DisplayParams params = fWindow->getRequestedDisplayParams(); 720 bool wasInLegacy = !SkToBool(params.fColorSpace); 721 bool wantLegacy = (ColorMode::kLegacy == fColorMode) || 722 (ColorMode::kColorManagedSRGB8888_NonLinearBlending == fColorMode); 723 if (wasInLegacy != wantLegacy) { 724 params.fColorSpace = wantLegacy ? nullptr : SkColorSpace::MakeSRGB(); 725 fWindow->setRequestedDisplayParams(params); 726 } 727 728 this->updateTitle(); 729 fWindow->inval(); 730 } 731 732 void Viewer::drawSlide(SkCanvas* canvas) { 733 SkAutoCanvasRestore autorestore(canvas, false); 734 735 if (fWindow->supportsContentRect()) { 736 SkRect contentRect = fWindow->getContentRect(); 737 canvas->clipRect(contentRect); 738 canvas->translate(contentRect.fLeft, contentRect.fTop); 739 } 740 741 // By default, we render directly into the window's surface/canvas 742 SkCanvas* slideCanvas = canvas; 743 fLastImage.reset(); 744 745 // If we're in any of the color managed modes, construct the color space we're going to use 746 sk_sp<SkColorSpace> cs = nullptr; 747 if (ColorMode::kLegacy != fColorMode) { 748 auto transferFn = (ColorMode::kColorManagedLinearF16 == fColorMode) 749 ? SkColorSpace::kLinear_RenderTargetGamma : SkColorSpace::kSRGB_RenderTargetGamma; 750 SkMatrix44 toXYZ; 751 SkAssertResult(fColorSpacePrimaries.toXYZD50(&toXYZ)); 752 cs = SkColorSpace::MakeRGB(transferFn, toXYZ); 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 757 sk_sp<SkSurface> offscreenSurface = nullptr; 758 if (ColorMode::kColorManagedLinearF16 == fColorMode || 759 fShowZoomWindow || 760 (ColorMode::kColorManagedSRGB8888 == fColorMode && 761 !primaries_equal(fColorSpacePrimaries, gSrgbPrimaries))) { 762 763 SkColorType colorType = (ColorMode::kColorManagedLinearF16 == fColorMode) 764 ? kRGBA_F16_SkColorType : kN32_SkColorType; 765 // In nonlinear blending mode, we actually use a legacy off-screen canvas, and wrap it 766 // with a special canvas (below) that has the color space attached 767 sk_sp<SkColorSpace> offscreenColorSpace = 768 (ColorMode::kColorManagedSRGB8888_NonLinearBlending == fColorMode) ? nullptr : cs; 769 SkImageInfo info = SkImageInfo::Make(fWindow->width(), fWindow->height(), colorType, 770 kPremul_SkAlphaType, std::move(offscreenColorSpace)); 771 offscreenSurface = canvas->makeSurface(info); 772 slideCanvas = offscreenSurface->getCanvas(); 773 } 774 775 std::unique_ptr<SkCanvas> xformCanvas = nullptr; 776 if (ColorMode::kColorManagedSRGB8888_NonLinearBlending == fColorMode) { 777 xformCanvas = SkCreateColorSpaceXformCanvas(slideCanvas, cs); 778 slideCanvas = xformCanvas.get(); 779 } 780 781 int count = slideCanvas->save(); 782 slideCanvas->clear(SK_ColorWHITE); 783 slideCanvas->concat(fDefaultMatrix); 784 slideCanvas->concat(computeMatrix()); 785 // Time the painting logic of the slide 786 double startTime = SkTime::GetMSecs(); 787 fSlides[fCurrentSlide]->draw(slideCanvas); 788 fPaintTimes[fCurrentMeasurement] = SkTime::GetMSecs() - startTime; 789 slideCanvas->restoreToCount(count); 790 791 // Force a flush so we can time that, too 792 startTime = SkTime::GetMSecs(); 793 slideCanvas->flush(); 794 fFlushTimes[fCurrentMeasurement] = SkTime::GetMSecs() - startTime; 795 796 // If we rendered offscreen, snap an image and push the results to the window's canvas 797 if (offscreenSurface) { 798 fLastImage = offscreenSurface->makeImageSnapshot(); 799 800 // Tag the image with the sRGB gamut, so no further color space conversion happens 801 sk_sp<SkColorSpace> srgb = (ColorMode::kColorManagedLinearF16 == fColorMode) 802 ? SkColorSpace::MakeSRGBLinear() : SkColorSpace::MakeSRGB(); 803 auto retaggedImage = SkImageMakeRasterCopyAndAssignColorSpace(fLastImage.get(), srgb.get()); 804 SkPaint paint; 805 paint.setBlendMode(SkBlendMode::kSrc); 806 canvas->drawImage(retaggedImage, 0, 0, &paint); 807 } 808 } 809 810 void Viewer::onBackendCreated() { 811 this->updateTitle(); 812 this->updateUIState(); 813 this->setupCurrentSlide(-1); 814 fWindow->show(); 815 fWindow->inval(); 816 } 817 818 void Viewer::onPaint(SkCanvas* canvas) { 819 // Update ImGui input 820 ImGuiIO& io = ImGui::GetIO(); 821 io.DeltaTime = 1.0f / 60.0f; 822 io.DisplaySize.x = static_cast<float>(fWindow->width()); 823 io.DisplaySize.y = static_cast<float>(fWindow->height()); 824 825 io.KeyAlt = io.KeysDown[static_cast<int>(Window::Key::kOption)]; 826 io.KeyCtrl = io.KeysDown[static_cast<int>(Window::Key::kCtrl)]; 827 io.KeyShift = io.KeysDown[static_cast<int>(Window::Key::kShift)]; 828 829 ImGui::NewFrame(); 830 831 drawSlide(canvas); 832 833 // Advance our timing bookkeeping 834 fCurrentMeasurement = (fCurrentMeasurement + 1) & (kMeasurementCount - 1); 835 SkASSERT(fCurrentMeasurement < kMeasurementCount); 836 837 // Draw any overlays or UI that we don't want timed 838 if (fDisplayStats) { 839 drawStats(canvas); 840 } 841 fCommands.drawHelp(canvas); 842 843 drawImGui(canvas); 844 845 // Update the FPS 846 updateUIState(); 847 } 848 849 bool Viewer::onTouch(intptr_t owner, Window::InputState state, float x, float y) { 850 void* castedOwner = reinterpret_cast<void*>(owner); 851 SkPoint touchPoint = fDefaultMatrixInv.mapXY(x, y); 852 switch (state) { 853 case Window::kUp_InputState: { 854 fGesture.touchEnd(castedOwner); 855 break; 856 } 857 case Window::kDown_InputState: { 858 fGesture.touchBegin(castedOwner, touchPoint.fX, touchPoint.fY); 859 break; 860 } 861 case Window::kMove_InputState: { 862 fGesture.touchMoved(castedOwner, touchPoint.fX, touchPoint.fY); 863 break; 864 } 865 } 866 fWindow->inval(); 867 return true; 868 } 869 870 void Viewer::drawStats(SkCanvas* canvas) { 871 static const float kPixelPerMS = 2.0f; 872 static const int kDisplayWidth = 130; 873 static const int kDisplayHeight = 100; 874 static const int kDisplayPadding = 10; 875 static const int kGraphPadding = 3; 876 static const SkScalar kBaseMS = 1000.f / 60.f; // ms/frame to hit 60 fps 877 878 SkISize canvasSize = canvas->getBaseLayerSize(); 879 SkRect rect = SkRect::MakeXYWH(SkIntToScalar(canvasSize.fWidth-kDisplayWidth-kDisplayPadding), 880 SkIntToScalar(kDisplayPadding), 881 SkIntToScalar(kDisplayWidth), SkIntToScalar(kDisplayHeight)); 882 SkPaint paint; 883 canvas->save(); 884 885 if (fWindow->supportsContentRect()) { 886 SkRect contentRect = fWindow->getContentRect(); 887 canvas->clipRect(contentRect); 888 canvas->translate(contentRect.fLeft, contentRect.fTop); 889 } 890 891 canvas->clipRect(rect); 892 paint.setColor(SK_ColorBLACK); 893 canvas->drawRect(rect, paint); 894 // draw the 16ms line 895 paint.setColor(SK_ColorLTGRAY); 896 canvas->drawLine(rect.fLeft, rect.fBottom - kBaseMS*kPixelPerMS, 897 rect.fRight, rect.fBottom - kBaseMS*kPixelPerMS, paint); 898 paint.setColor(SK_ColorRED); 899 paint.setStyle(SkPaint::kStroke_Style); 900 canvas->drawRect(rect, paint); 901 902 int x = SkScalarTruncToInt(rect.fLeft) + kGraphPadding; 903 const int xStep = 2; 904 int i = fCurrentMeasurement; 905 do { 906 // Round to nearest values 907 int animateHeight = (int)(fAnimateTimes[i] * kPixelPerMS + 0.5); 908 int paintHeight = (int)(fPaintTimes[i] * kPixelPerMS + 0.5); 909 int flushHeight = (int)(fFlushTimes[i] * kPixelPerMS + 0.5); 910 int startY = SkScalarTruncToInt(rect.fBottom); 911 int endY = startY - flushHeight; 912 paint.setColor(SK_ColorRED); 913 canvas->drawLine(SkIntToScalar(x), SkIntToScalar(startY), 914 SkIntToScalar(x), SkIntToScalar(endY), paint); 915 startY = endY; 916 endY = startY - paintHeight; 917 paint.setColor(SK_ColorGREEN); 918 canvas->drawLine(SkIntToScalar(x), SkIntToScalar(startY), 919 SkIntToScalar(x), SkIntToScalar(endY), paint); 920 startY = endY; 921 endY = startY - animateHeight; 922 paint.setColor(SK_ColorMAGENTA); 923 canvas->drawLine(SkIntToScalar(x), SkIntToScalar(startY), 924 SkIntToScalar(x), SkIntToScalar(endY), paint); 925 i++; 926 i &= (kMeasurementCount - 1); // fast mod 927 x += xStep; 928 } while (i != fCurrentMeasurement); 929 930 canvas->restore(); 931 } 932 933 static ImVec2 ImGui_DragPrimary(const char* label, float* x, float* y, 934 const ImVec2& pos, const ImVec2& size) { 935 // Transform primaries ([0, 0] - [0.8, 0.9]) to screen coords (including Y-flip) 936 ImVec2 center(pos.x + (*x / 0.8f) * size.x, pos.y + (1.0f - (*y / 0.9f)) * size.y); 937 938 // Invisible 10x10 button 939 ImGui::SetCursorScreenPos(ImVec2(center.x - 5, center.y - 5)); 940 ImGui::InvisibleButton(label, ImVec2(10, 10)); 941 942 if (ImGui::IsItemActive() && ImGui::IsMouseDragging()) { 943 ImGuiIO& io = ImGui::GetIO(); 944 // Normalized mouse position, relative to our gamut box 945 ImVec2 mousePosXY((io.MousePos.x - pos.x) / size.x, (io.MousePos.y - pos.y) / size.y); 946 // Clamp to edge of box, convert back to primary scale 947 *x = SkTPin(mousePosXY.x, 0.0f, 1.0f) * 0.8f; 948 *y = SkTPin(1 - mousePosXY.y, 0.0f, 1.0f) * 0.9f; 949 } 950 951 if (ImGui::IsItemHovered()) { 952 ImGui::SetTooltip("x: %.3f\ny: %.3f", *x, *y); 953 } 954 955 // Return screen coordinates for the caller. We could just return center here, but we'd have 956 // one frame of lag during drag. 957 return ImVec2(pos.x + (*x / 0.8f) * size.x, pos.y + (1.0f - (*y / 0.9f)) * size.y); 958 } 959 960 static void ImGui_Primaries(SkColorSpacePrimaries* primaries, SkPaint* gamutPaint) { 961 ImDrawList* drawList = ImGui::GetWindowDrawList(); 962 963 // The gamut image covers a (0.8 x 0.9) shaped region, so fit our image/canvas to the available 964 // width, and scale the height to maintain aspect ratio. 965 float canvasWidth = SkTMax(ImGui::GetContentRegionAvailWidth(), 50.0f); 966 ImVec2 size = ImVec2(canvasWidth, canvasWidth * (0.9f / 0.8f)); 967 ImVec2 pos = ImGui::GetCursorScreenPos(); 968 969 // Background image. Only draw a subset of the image, to avoid the regions less than zero. 970 // Simplifes re-mapping math, clipping behavior, and increases resolution in the useful area. 971 // Magic numbers are pixel locations of the origin and upper-right corner. 972 drawList->AddImage(gamutPaint, pos, ImVec2(pos.x + size.x, pos.y + size.y), 973 ImVec2(242, 61), ImVec2(1897, 1922)); 974 ImVec2 endPos = ImGui::GetCursorPos(); 975 976 // Primary markers 977 ImVec2 r = ImGui_DragPrimary("R", &primaries->fRX, &primaries->fRY, pos, size); 978 ImVec2 g = ImGui_DragPrimary("G", &primaries->fGX, &primaries->fGY, pos, size); 979 ImVec2 b = ImGui_DragPrimary("B", &primaries->fBX, &primaries->fBY, pos, size); 980 ImVec2 w = ImGui_DragPrimary("W", &primaries->fWX, &primaries->fWY, pos, size); 981 982 // Gamut triangle 983 drawList->AddCircle(r, 5.0f, 0xFF000040); 984 drawList->AddCircle(g, 5.0f, 0xFF004000); 985 drawList->AddCircle(b, 5.0f, 0xFF400000); 986 drawList->AddCircle(w, 5.0f, 0xFFFFFFFF); 987 drawList->AddTriangle(r, g, b, 0xFFFFFFFF); 988 989 // Re-position cursor immediate after the diagram for subsequent controls 990 ImGui::SetCursorPos(endPos); 991 } 992 993 void Viewer::drawImGui(SkCanvas* canvas) { 994 // Support drawing the ImGui demo window. Superfluous, but gives a good idea of what's possible 995 if (fShowImGuiTestWindow) { 996 ImGui::ShowTestWindow(&fShowImGuiTestWindow); 997 } 998 999 if (fShowImGuiDebugWindow) { 1000 // We have some dynamic content that sizes to fill available size. If the scroll bar isn't 1001 // always visible, we can end up in a layout feedback loop. 1002 ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiSetCond_FirstUseEver); 1003 DisplayParams params = fWindow->getRequestedDisplayParams(); 1004 bool paramsChanged = false; 1005 if (ImGui::Begin("Tools", &fShowImGuiDebugWindow, 1006 ImGuiWindowFlags_AlwaysVerticalScrollbar)) { 1007 if (ImGui::CollapsingHeader("Backend")) { 1008 int newBackend = static_cast<int>(fBackendType); 1009 ImGui::RadioButton("Raster", &newBackend, sk_app::Window::kRaster_BackendType); 1010 ImGui::SameLine(); 1011 ImGui::RadioButton("OpenGL", &newBackend, sk_app::Window::kNativeGL_BackendType); 1012 #if defined(SK_VULKAN) 1013 ImGui::SameLine(); 1014 ImGui::RadioButton("Vulkan", &newBackend, sk_app::Window::kVulkan_BackendType); 1015 #endif 1016 if (newBackend != fBackendType) { 1017 fDeferredActions.push_back([=]() { 1018 this->setBackend(static_cast<sk_app::Window::BackendType>(newBackend)); 1019 }); 1020 } 1021 1022 const GrContext* ctx = fWindow->getGrContext(); 1023 bool* inst = ¶ms.fGrContextOptions.fEnableInstancedRendering; 1024 if (ctx && ImGui::Checkbox("Instanced Rendering", inst)) { 1025 paramsChanged = true; 1026 } 1027 1028 if (ctx) { 1029 int sampleCount = fWindow->sampleCount(); 1030 ImGui::Text("MSAA: "); ImGui::SameLine(); 1031 ImGui::RadioButton("0", &sampleCount, 0); ImGui::SameLine(); 1032 ImGui::RadioButton("4", &sampleCount, 4); ImGui::SameLine(); 1033 ImGui::RadioButton("8", &sampleCount, 8); ImGui::SameLine(); 1034 ImGui::RadioButton("16", &sampleCount, 16); 1035 1036 if (sampleCount != params.fMSAASampleCount) { 1037 params.fMSAASampleCount = sampleCount; 1038 paramsChanged = true; 1039 } 1040 } 1041 1042 if (ImGui::TreeNode("Path Renderers")) { 1043 GpuPathRenderers prevPr = params.fGrContextOptions.fGpuPathRenderers; 1044 auto prButton = [&](GpuPathRenderers x) { 1045 if (ImGui::RadioButton(gPathRendererNames[x].c_str(), prevPr == x)) { 1046 if (x != params.fGrContextOptions.fGpuPathRenderers) { 1047 params.fGrContextOptions.fGpuPathRenderers = x; 1048 paramsChanged = true; 1049 } 1050 } 1051 }; 1052 1053 if (!ctx) { 1054 ImGui::RadioButton("Software", true); 1055 } else if (fWindow->sampleCount()) { 1056 prButton(GpuPathRenderers::kAll); 1057 if (ctx->caps()->shaderCaps()->pathRenderingSupport()) { 1058 prButton(GpuPathRenderers::kStencilAndCover); 1059 } 1060 if (ctx->caps()->sampleShadingSupport()) { 1061 prButton(GpuPathRenderers::kMSAA); 1062 } 1063 prButton(GpuPathRenderers::kTessellating); 1064 prButton(GpuPathRenderers::kDefault); 1065 prButton(GpuPathRenderers::kNone); 1066 } else { 1067 prButton(GpuPathRenderers::kAll); 1068 prButton(GpuPathRenderers::kSmall); 1069 prButton(GpuPathRenderers::kTessellating); 1070 prButton(GpuPathRenderers::kNone); 1071 } 1072 ImGui::TreePop(); 1073 } 1074 } 1075 1076 if (ImGui::CollapsingHeader("Slide")) { 1077 static ImGuiTextFilter filter; 1078 filter.Draw(); 1079 int previousSlide = fCurrentSlide; 1080 fCurrentSlide = 0; 1081 for (auto slide : fSlides) { 1082 if (filter.PassFilter(slide->getName().c_str())) { 1083 ImGui::BulletText("%s", slide->getName().c_str()); 1084 if (ImGui::IsItemClicked()) { 1085 setupCurrentSlide(previousSlide); 1086 break; 1087 } 1088 } 1089 ++fCurrentSlide; 1090 } 1091 if (fCurrentSlide >= fSlides.count()) { 1092 fCurrentSlide = previousSlide; 1093 } 1094 } 1095 1096 if (ImGui::CollapsingHeader("Color Mode")) { 1097 ColorMode newMode = fColorMode; 1098 auto cmButton = [&](ColorMode mode, const char* label) { 1099 if (ImGui::RadioButton(label, mode == fColorMode)) { 1100 newMode = mode; 1101 } 1102 }; 1103 1104 cmButton(ColorMode::kLegacy, "Legacy 8888"); 1105 cmButton(ColorMode::kColorManagedSRGB8888_NonLinearBlending, 1106 "Color Managed 8888 (Nonlinear blending)"); 1107 cmButton(ColorMode::kColorManagedSRGB8888, "Color Managed 8888"); 1108 cmButton(ColorMode::kColorManagedLinearF16, "Color Managed F16"); 1109 1110 if (newMode != fColorMode) { 1111 // It isn't safe to switch color mode now (in the middle of painting). We might 1112 // tear down the back-end, etc... Defer this change until the next onIdle. 1113 fDeferredActions.push_back([=]() { 1114 this->setColorMode(newMode); 1115 }); 1116 } 1117 1118 // Pick from common gamuts: 1119 int primariesIdx = 4; // Default: Custom 1120 for (size_t i = 0; i < SK_ARRAY_COUNT(gNamedPrimaries); ++i) { 1121 if (primaries_equal(*gNamedPrimaries[i].fPrimaries, fColorSpacePrimaries)) { 1122 primariesIdx = i; 1123 break; 1124 } 1125 } 1126 1127 if (ImGui::Combo("Primaries", &primariesIdx, 1128 "sRGB\0AdobeRGB\0P3\0Rec. 2020\0Custom\0\0")) { 1129 if (primariesIdx >= 0 && primariesIdx <= 3) { 1130 fColorSpacePrimaries = *gNamedPrimaries[primariesIdx].fPrimaries; 1131 } 1132 } 1133 1134 // Allow direct editing of gamut 1135 ImGui_Primaries(&fColorSpacePrimaries, &fImGuiGamutPaint); 1136 } 1137 } 1138 if (paramsChanged) { 1139 fDeferredActions.push_back([=]() { 1140 fWindow->setRequestedDisplayParams(params); 1141 fWindow->inval(); 1142 this->updateTitle(); 1143 }); 1144 } 1145 ImGui::End(); 1146 } 1147 1148 SkPaint zoomImagePaint; 1149 if (fShowZoomWindow && fLastImage) { 1150 if (ImGui::Begin("Zoom", &fShowZoomWindow, ImVec2(200, 200))) { 1151 static int zoomFactor = 4; 1152 ImGui::SliderInt("Scale", &zoomFactor, 1, 16); 1153 1154 zoomImagePaint.setShader(fLastImage->makeShader(SkShader::kClamp_TileMode, 1155 SkShader::kClamp_TileMode)); 1156 zoomImagePaint.setColor(SK_ColorWHITE); 1157 1158 // Zoom by shrinking the corner UVs towards the mouse cursor 1159 ImVec2 mousePos = ImGui::GetMousePos(); 1160 ImVec2 avail = ImGui::GetContentRegionAvail(); 1161 1162 ImVec2 zoomHalfExtents = ImVec2((avail.x * 0.5f) / zoomFactor, 1163 (avail.y * 0.5f) / zoomFactor); 1164 ImGui::Image(&zoomImagePaint, avail, 1165 ImVec2(mousePos.x - zoomHalfExtents.x, mousePos.y - zoomHalfExtents.y), 1166 ImVec2(mousePos.x + zoomHalfExtents.x, mousePos.y + zoomHalfExtents.y)); 1167 } 1168 1169 ImGui::End(); 1170 } 1171 1172 // This causes ImGui to rebuild vertex/index data based on all immediate-mode commands 1173 // (widgets, etc...) that have been issued 1174 ImGui::Render(); 1175 1176 // Then we fetch the most recent data, and convert it so we can render with Skia 1177 const ImDrawData* drawData = ImGui::GetDrawData(); 1178 SkTDArray<SkPoint> pos; 1179 SkTDArray<SkPoint> uv; 1180 SkTDArray<SkColor> color; 1181 1182 for (int i = 0; i < drawData->CmdListsCount; ++i) { 1183 const ImDrawList* drawList = drawData->CmdLists[i]; 1184 1185 // De-interleave all vertex data (sigh), convert to Skia types 1186 pos.rewind(); uv.rewind(); color.rewind(); 1187 for (int i = 0; i < drawList->VtxBuffer.size(); ++i) { 1188 const ImDrawVert& vert = drawList->VtxBuffer[i]; 1189 pos.push(SkPoint::Make(vert.pos.x, vert.pos.y)); 1190 uv.push(SkPoint::Make(vert.uv.x, vert.uv.y)); 1191 color.push(vert.col); 1192 } 1193 // ImGui colors are RGBA 1194 SkSwapRB(color.begin(), color.begin(), color.count()); 1195 1196 int indexOffset = 0; 1197 1198 // Draw everything with canvas.drawVertices... 1199 for (int j = 0; j < drawList->CmdBuffer.size(); ++j) { 1200 const ImDrawCmd* drawCmd = &drawList->CmdBuffer[j]; 1201 1202 // TODO: Find min/max index for each draw, so we know how many vertices (sigh) 1203 if (drawCmd->UserCallback) { 1204 drawCmd->UserCallback(drawList, drawCmd); 1205 } else { 1206 SkPaint* paint = static_cast<SkPaint*>(drawCmd->TextureId); 1207 SkASSERT(paint); 1208 1209 canvas->save(); 1210 canvas->clipRect(SkRect::MakeLTRB(drawCmd->ClipRect.x, drawCmd->ClipRect.y, 1211 drawCmd->ClipRect.z, drawCmd->ClipRect.w)); 1212 canvas->drawVertices(SkCanvas::kTriangles_VertexMode, drawList->VtxBuffer.size(), 1213 pos.begin(), uv.begin(), color.begin(), 1214 drawList->IdxBuffer.begin() + indexOffset, drawCmd->ElemCount, 1215 *paint); 1216 indexOffset += drawCmd->ElemCount; 1217 canvas->restore(); 1218 } 1219 } 1220 } 1221 } 1222 1223 void Viewer::onIdle() { 1224 for (int i = 0; i < fDeferredActions.count(); ++i) { 1225 fDeferredActions[i](); 1226 } 1227 fDeferredActions.reset(); 1228 1229 double startTime = SkTime::GetMSecs(); 1230 fAnimTimer.updateTime(); 1231 bool animateWantsInval = fSlides[fCurrentSlide]->animate(fAnimTimer); 1232 fAnimateTimes[fCurrentMeasurement] = SkTime::GetMSecs() - startTime; 1233 1234 ImGuiIO& io = ImGui::GetIO(); 1235 if (animateWantsInval || fDisplayStats || fRefresh || io.MetricsActiveWindows) { 1236 fWindow->inval(); 1237 } 1238 } 1239 1240 void Viewer::updateUIState() { 1241 if (!fWindow) { 1242 return; 1243 } 1244 if (fWindow->sampleCount() < 0) { 1245 return; // Surface hasn't been created yet. 1246 } 1247 1248 // Slide state 1249 Json::Value slideState(Json::objectValue); 1250 slideState[kName] = kSlideStateName; 1251 slideState[kValue] = fSlides[fCurrentSlide]->getName().c_str(); 1252 if (fAllSlideNames.size() == 0) { 1253 for(auto slide : fSlides) { 1254 fAllSlideNames.append(Json::Value(slide->getName().c_str())); 1255 } 1256 } 1257 slideState[kOptions] = fAllSlideNames; 1258 1259 // Backend state 1260 Json::Value backendState(Json::objectValue); 1261 backendState[kName] = kBackendStateName; 1262 backendState[kValue] = kBackendTypeStrings[fBackendType]; 1263 backendState[kOptions] = Json::Value(Json::arrayValue); 1264 for (auto str : kBackendTypeStrings) { 1265 backendState[kOptions].append(Json::Value(str)); 1266 } 1267 1268 // MSAA state 1269 Json::Value msaaState(Json::objectValue); 1270 msaaState[kName] = kMSAAStateName; 1271 msaaState[kValue] = fWindow->sampleCount(); 1272 msaaState[kOptions] = Json::Value(Json::arrayValue); 1273 if (sk_app::Window::kRaster_BackendType == fBackendType) { 1274 msaaState[kOptions].append(Json::Value(0)); 1275 } else { 1276 for (int msaa : {0, 4, 8, 16}) { 1277 msaaState[kOptions].append(Json::Value(msaa)); 1278 } 1279 } 1280 1281 // Path renderer state 1282 GpuPathRenderers pr = fWindow->getRequestedDisplayParams().fGrContextOptions.fGpuPathRenderers; 1283 Json::Value prState(Json::objectValue); 1284 prState[kName] = kPathRendererStateName; 1285 prState[kValue] = gPathRendererNames[pr]; 1286 prState[kOptions] = Json::Value(Json::arrayValue); 1287 const GrContext* ctx = fWindow->getGrContext(); 1288 if (!ctx) { 1289 prState[kOptions].append("Software"); 1290 } else if (fWindow->sampleCount()) { 1291 prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kAll]); 1292 if (ctx->caps()->shaderCaps()->pathRenderingSupport()) { 1293 prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kStencilAndCover]); 1294 } 1295 if (ctx->caps()->sampleShadingSupport()) { 1296 prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kMSAA]); 1297 } 1298 prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kTessellating]); 1299 prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kDefault]); 1300 prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kNone]); 1301 } else { 1302 prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kAll]); 1303 prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kSmall]); 1304 prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kTessellating]); 1305 prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kNone]); 1306 } 1307 1308 // Instanced rendering state 1309 Json::Value instState(Json::objectValue); 1310 instState[kName] = kInstancedRenderingStateName; 1311 if (ctx) { 1312 if (fWindow->getRequestedDisplayParams().fGrContextOptions.fEnableInstancedRendering) { 1313 instState[kValue] = kON; 1314 } else { 1315 instState[kValue] = kOFF; 1316 } 1317 instState[kOptions] = Json::Value(Json::arrayValue); 1318 instState[kOptions].append(kOFF); 1319 instState[kOptions].append(kON); 1320 } 1321 1322 // Softkey state 1323 Json::Value softkeyState(Json::objectValue); 1324 softkeyState[kName] = kSoftkeyStateName; 1325 softkeyState[kValue] = kSoftkeyHint; 1326 softkeyState[kOptions] = Json::Value(Json::arrayValue); 1327 softkeyState[kOptions].append(kSoftkeyHint); 1328 for (const auto& softkey : fCommands.getCommandsAsSoftkeys()) { 1329 softkeyState[kOptions].append(Json::Value(softkey.c_str())); 1330 } 1331 1332 // FPS state 1333 Json::Value fpsState(Json::objectValue); 1334 fpsState[kName] = kFpsStateName; 1335 int idx = (fCurrentMeasurement + (kMeasurementCount - 1)) & (kMeasurementCount - 1); 1336 fpsState[kValue] = SkStringPrintf("%8.3lf ms\n\nA %8.3lf\nP %8.3lf\nF%8.3lf", 1337 fAnimateTimes[idx] + fPaintTimes[idx] + fFlushTimes[idx], 1338 fAnimateTimes[idx], 1339 fPaintTimes[idx], 1340 fFlushTimes[idx]).c_str(); 1341 fpsState[kOptions] = Json::Value(Json::arrayValue); 1342 1343 Json::Value state(Json::arrayValue); 1344 state.append(slideState); 1345 state.append(backendState); 1346 state.append(msaaState); 1347 state.append(prState); 1348 state.append(instState); 1349 state.append(softkeyState); 1350 state.append(fpsState); 1351 1352 fWindow->setUIState(state); 1353 } 1354 1355 void Viewer::onUIStateChanged(const SkString& stateName, const SkString& stateValue) { 1356 // For those who will add more features to handle the state change in this function: 1357 // After the change, please call updateUIState no notify the frontend (e.g., Android app). 1358 // For example, after slide change, updateUIState is called inside setupCurrentSlide; 1359 // after backend change, updateUIState is called in this function. 1360 if (stateName.equals(kSlideStateName)) { 1361 int previousSlide = fCurrentSlide; 1362 fCurrentSlide = 0; 1363 for(auto slide : fSlides) { 1364 if (slide->getName().equals(stateValue)) { 1365 this->setupCurrentSlide(previousSlide); 1366 break; 1367 } 1368 fCurrentSlide++; 1369 } 1370 if (fCurrentSlide >= fSlides.count()) { 1371 fCurrentSlide = previousSlide; 1372 SkDebugf("Slide not found: %s", stateValue.c_str()); 1373 } 1374 } else if (stateName.equals(kBackendStateName)) { 1375 for (int i = 0; i < sk_app::Window::kBackendTypeCount; i++) { 1376 if (stateValue.equals(kBackendTypeStrings[i])) { 1377 if (fBackendType != i) { 1378 fBackendType = (sk_app::Window::BackendType)i; 1379 fWindow->detach(); 1380 fWindow->attach(fBackendType); 1381 } 1382 break; 1383 } 1384 } 1385 } else if (stateName.equals(kMSAAStateName)) { 1386 DisplayParams params = fWindow->getRequestedDisplayParams(); 1387 int sampleCount = atoi(stateValue.c_str()); 1388 if (sampleCount != params.fMSAASampleCount) { 1389 params.fMSAASampleCount = sampleCount; 1390 fWindow->setRequestedDisplayParams(params); 1391 fWindow->inval(); 1392 this->updateTitle(); 1393 this->updateUIState(); 1394 } 1395 } else if (stateName.equals(kPathRendererStateName)) { 1396 DisplayParams params = fWindow->getRequestedDisplayParams(); 1397 for (const auto& pair : gPathRendererNames) { 1398 if (pair.second == stateValue.c_str()) { 1399 if (params.fGrContextOptions.fGpuPathRenderers != pair.first) { 1400 params.fGrContextOptions.fGpuPathRenderers = pair.first; 1401 fWindow->setRequestedDisplayParams(params); 1402 fWindow->inval(); 1403 this->updateTitle(); 1404 this->updateUIState(); 1405 } 1406 break; 1407 } 1408 } 1409 } else if (stateName.equals(kInstancedRenderingStateName)) { 1410 DisplayParams params = fWindow->getRequestedDisplayParams(); 1411 bool value = !strcmp(stateValue.c_str(), kON); 1412 if (params.fGrContextOptions.fEnableInstancedRendering != value) { 1413 params.fGrContextOptions.fEnableInstancedRendering = value; 1414 fWindow->setRequestedDisplayParams(params); 1415 fWindow->inval(); 1416 this->updateTitle(); 1417 this->updateUIState(); 1418 } 1419 } else if (stateName.equals(kSoftkeyStateName)) { 1420 if (!stateValue.equals(kSoftkeyHint)) { 1421 fCommands.onSoftkey(stateValue); 1422 this->updateUIState(); // This is still needed to reset the value to kSoftkeyHint 1423 } 1424 } else if (stateName.equals(kRefreshStateName)) { 1425 // This state is actually NOT in the UI state. 1426 // We use this to allow Android to quickly set bool fRefresh. 1427 fRefresh = stateValue.equals(kON); 1428 } else { 1429 SkDebugf("Unknown stateName: %s", stateName.c_str()); 1430 } 1431 } 1432 1433 bool Viewer::onKey(sk_app::Window::Key key, sk_app::Window::InputState state, uint32_t modifiers) { 1434 return fCommands.onKey(key, state, modifiers); 1435 } 1436 1437 bool Viewer::onChar(SkUnichar c, uint32_t modifiers) { 1438 if (fSlides[fCurrentSlide]->onChar(c)) { 1439 fWindow->inval(); 1440 return true; 1441 } 1442 1443 return fCommands.onChar(c, modifiers); 1444 } 1445