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