| /* |
| * 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/SkImage.h" |
| #include "include/core/SkStream.h" |
| #include "include/private/base/SkTPin.h" |
| #include "src/core/SkOSFile.h" |
| #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(); |
| } |
| } |