| /* | 
 |  * Copyright 2021 Google LLC | 
 |  * | 
 |  * Use of this source code is governed by a BSD-style license that can be | 
 |  * found in the LICENSE file. | 
 |  */ | 
 |  | 
 | #include "tools/viewer/MSKPSlide.h" | 
 |  | 
 | #include "include/core/SkCanvas.h" | 
 | #include "include/core/SkColor.h" | 
 | #include "include/core/SkImage.h" | 
 | #include "include/core/SkPaint.h" | 
 | #include "include/core/SkRect.h" | 
 | #include "include/core/SkRefCnt.h" | 
 | #include "include/core/SkStream.h" | 
 | #include "include/private/base/SkTPin.h" | 
 |  | 
 | #include <cstddef> | 
 | #include <utility> | 
 |  | 
 | #include "imgui.h" | 
 |  | 
 | MSKPSlide::MSKPSlide(const SkString& name, const SkString& path) | 
 |         : MSKPSlide(name, SkStream::MakeFromFile(path.c_str())) {} | 
 |  | 
 | MSKPSlide::MSKPSlide(const SkString& name, std::unique_ptr<SkStreamSeekable> stream) | 
 |         : fStream(std::move(stream)) { | 
 |     fName = name; | 
 | } | 
 |  | 
 | SkISize MSKPSlide::getDimensions() const { | 
 |     return fPlayer ? fPlayer->maxDimensions() : SkISize{0, 0}; | 
 | } | 
 |  | 
 | void MSKPSlide::draw(SkCanvas* canvas) { | 
 |     if (!fPlayer) { | 
 |         ImGui::Text("Could not read mskp file %s.\n", fName.c_str()); | 
 |         return; | 
 |     } | 
 |     ImGui::Begin("MSKP"); | 
 |  | 
 |     ImGui::BeginGroup(); | 
 |     // Play/Pause button | 
 |     if (ImGui::Button(fPaused ? "Play " : "Pause")) { | 
 |         fPaused = !fPaused; | 
 |         if (fPaused) { | 
 |             // This will ensure that when playback is unpaused we start on the current frame. | 
 |             fLastFrameTime = -1; | 
 |         } | 
 |     } | 
 |     // Control the frame rate of MSKP playback | 
 |     ImGui::Text("FPS: ");                   ImGui::SameLine(); | 
 |     ImGui::RadioButton(  "1", &fFPS,    1); ImGui::SameLine(); | 
 |     ImGui::RadioButton( "15", &fFPS,   15); ImGui::SameLine(); | 
 |     ImGui::RadioButton( "30", &fFPS,   30); ImGui::SameLine(); | 
 |     ImGui::RadioButton( "60", &fFPS,   60); ImGui::SameLine(); | 
 |     ImGui::RadioButton("120", &fFPS,  120); ImGui::SameLine(); | 
 |     ImGui::RadioButton("1:1", &fFPS,   -1); // Draw one MSKP frame for each real viewer frame. | 
 |     if (fFPS < 0) { | 
 |         // Like above, will cause onAnimate() to resume at current frame when FPS is changed | 
 |         // back to another frame rate. | 
 |         fLastFrameTime = -1; | 
 |     } | 
 |     // Frame control. Slider and +/- buttons. Ctrl-Click slider to type frame number. | 
 |     ImGui::Text("Frame:"); | 
 |     ImGui::SameLine(); | 
 |     ImGui::PushButtonRepeat(true);  // Enable click-and-hold for frame arrows. | 
 |     int oldFrame = fFrame; | 
 |     if (ImGui::ArrowButton("-mksp_frame", ImGuiDir_Left)) { | 
 |         fFrame = (fFrame + fPlayer->numFrames() - 1)%fPlayer->numFrames(); | 
 |     } | 
 |     ImGui::SameLine(); | 
 |     if (ImGui::SliderInt("##msk_frameslider", &fFrame, 0, fPlayer->numFrames()-1, "% 3d")) { | 
 |         fFrame = SkTPin(fFrame, 0, fPlayer->numFrames() - 1); | 
 |     } | 
 |     ImGui::SameLine(); | 
 |     if (ImGui::ArrowButton("+mskp_frame", ImGuiDir_Right)) { | 
 |         fFrame = (fFrame + 1)%fPlayer->numFrames(); | 
 |     } | 
 |     if (fFrame != oldFrame) { | 
 |         // When manually adjusting frames force layers to redraw. | 
 |         this->redrawLayers(); | 
 |     } | 
 |  | 
 |     ImGui::PopButtonRepeat(); | 
 |     ImGui::EndGroup(); | 
 |  | 
 |     ImGui::BeginGroup(); | 
 |     ImGui::Checkbox("Show Frame Bounds", &fShowFrameBounds); | 
 |     ImGui::SetNextItemWidth(200); | 
 |     ImGui::ColorPicker4("background", fBackgroundColor, ImGuiColorEditFlags_AlphaBar); | 
 |     // ImGui lets user enter out of range values by typing. | 
 |     for (float& component : fBackgroundColor) { | 
 |         component = SkTPin(component, 0.f, 1.f); | 
 |     } | 
 |     ImGui::EndGroup(); | 
 |  | 
 |     // UI for visualizing contents of offscreen layers. | 
 |     ImGui::Text("Offscreen Layers "); ImGui::SameLine(); | 
 |     ImGui::Checkbox("List All Layers", &fListAllLayers); | 
 |     ImGui::RadioButton("root", &fDrawLayerID, -1); | 
 |     const std::vector<int>& layerIDs = fListAllLayers ? fAllLayerIDs : fFrameLayerIDs[fFrame]; | 
 |     fLayerIDStrings.resize(layerIDs.size()); | 
 |     for (size_t i = 0; i < layerIDs.size(); ++i) { | 
 |         fLayerIDStrings[i] = SkStringPrintf("%d", layerIDs[i]); | 
 |         ImGui::RadioButton(fLayerIDStrings[i].c_str(), &fDrawLayerID, layerIDs[i]); | 
 |     } | 
 |     ImGui::End(); | 
 |  | 
 |     auto bounds = SkIRect::MakeSize(fPlayer->frameDimensions(fFrame)); | 
 |  | 
 |     if (fShowFrameBounds) { | 
 |         SkPaint boundsPaint; | 
 |         boundsPaint.setStyle(SkPaint::kStroke_Style); | 
 |         boundsPaint.setColor(SK_ColorRED); | 
 |         boundsPaint.setStrokeWidth(0.f); | 
 |         boundsPaint.setAntiAlias(true); | 
 |         // Outset so that at default scale we draw at pixel centers of the rows/cols surrounding the | 
 |         // bounds. | 
 |         canvas->drawRect(SkRect::Make(bounds).makeOutset(0.5f, 0.5f), boundsPaint); | 
 |     } | 
 |  | 
 |     canvas->save(); | 
 |     if (fDrawLayerID >= 0) { | 
 |         // clip out the root layer content, but still call playFrame so layer contents are updated | 
 |         // to fFrame. | 
 |         bounds = SkIRect::MakeEmpty(); | 
 |     } | 
 |     canvas->clipIRect(bounds); | 
 |     canvas->clear(SkColor4f{fBackgroundColor[0], | 
 |                             fBackgroundColor[1], | 
 |                             fBackgroundColor[2], | 
 |                             fBackgroundColor[3]}); | 
 |     fPlayer->playFrame(canvas, fFrame); | 
 |     canvas->restore(); | 
 |  | 
 |     if (fDrawLayerID >= 0) { | 
 |         if (sk_sp<SkImage> layerImage = fPlayer->layerSnapshot(fDrawLayerID)) { | 
 |             canvas->save(); | 
 |             canvas->clipIRect(SkIRect::MakeSize(layerImage->dimensions())); | 
 |             canvas->clear(SkColor4f{fBackgroundColor[0], | 
 |                                     fBackgroundColor[1], | 
 |                                     fBackgroundColor[2], | 
 |                                     fBackgroundColor[3]}); | 
 |             canvas->drawImage(std::move(layerImage), 0, 0); | 
 |             canvas->restore(); | 
 |         } | 
 |         return; | 
 |     } | 
 | } | 
 |  | 
 | bool MSKPSlide::animate(double nanos) { | 
 |     if (!fPlayer || fPaused) { | 
 |         return false; | 
 |     } | 
 |     if (fLastFrameTime < 0) { | 
 |         // We're coming off being paused or switching from 1:1 mode to steady FPS. Advance 1 frame | 
 |         // and reset the frame time to start accumulating time from now. | 
 |         fFrame = (fFrame + 1)%fPlayer->numFrames(); | 
 |         fLastFrameTime = nanos; | 
 |         return this->fPlayer->numFrames() > 1; | 
 |     } | 
 |     if (fFPS < 0) { | 
 |         // 1:1 mode. Always draw the next frame on each animation cycle. | 
 |         fFrame = (fFrame + 1)%fPlayer->numFrames(); | 
 |         return this->fPlayer->numFrames() > 1; | 
 |     } | 
 |     double elapsed = nanos - fLastFrameTime; | 
 |     double frameTime = 1E9/fFPS; | 
 |     int framesToAdvance = elapsed/frameTime; | 
 |     fFrame = fFrame + framesToAdvance; | 
 |     if (fFrame >= fPlayer->numFrames()) { | 
 |         this->redrawLayers(); | 
 |     } | 
 |     fFrame %= fPlayer->numFrames(); | 
 |     // Instead of just adding elapsed, note the time when this frame should have begun. | 
 |     fLastFrameTime += framesToAdvance*frameTime; | 
 |     return framesToAdvance > 0; | 
 | } | 
 |  | 
 | void MSKPSlide::load(SkScalar, SkScalar) { | 
 |     if (!fStream) { | 
 |         return; | 
 |     } | 
 |     fStream->rewind(); | 
 |     fPlayer = MSKPPlayer::Make(fStream.get()); | 
 |     if (!fPlayer) { | 
 |         return; | 
 |     } | 
 |     fAllLayerIDs = fPlayer->layerIDs(); | 
 |     fFrameLayerIDs.clear(); | 
 |     fFrameLayerIDs.resize(fPlayer->numFrames()); | 
 |     for (int i = 0; i < fPlayer->numFrames(); ++i) { | 
 |         fFrameLayerIDs[i] = fPlayer->layerIDs(i); | 
 |     } | 
 | } | 
 |  | 
 | void MSKPSlide::unload() { fPlayer.reset(); } | 
 |  | 
 | void MSKPSlide::gpuTeardown() { | 
 |     if (fPlayer) { | 
 |         fPlayer->resetLayers(); | 
 |     } | 
 | } | 
 |  | 
 | void MSKPSlide::redrawLayers() { | 
 |     if (fDrawLayerID >= 0) { | 
 |         // Completely reset the layers so that we won't see content from later frames on layers | 
 |         // that haven't been visited from frames 0..fFrames. | 
 |         fPlayer->resetLayers(); | 
 |     } else { | 
 |         // Just rewind layers so that we redraw any layer from scratch on the next frame that | 
 |         // updates it. Important for benchmarking/profiling as otherwise if a layer is only | 
 |         // drawn once in the frame sequence then it will never be updated after the first play | 
 |         // through. This doesn't reallocate the layer backing stores. | 
 |         fPlayer->rewindLayers(); | 
 |     } | 
 | } |