2021-04-29 18:10:23 +00:00
|
|
|
/*
|
|
|
|
* 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/SkStream.h"
|
2021-04-23 23:14:02 +00:00
|
|
|
#include "include/private/SkTPin.h"
|
2021-04-29 18:10:23 +00:00
|
|
|
#include "src/core/SkOSFile.h"
|
2021-04-23 23:14:02 +00:00
|
|
|
#include "imgui.h"
|
2021-04-29 18:10:23 +00:00
|
|
|
|
|
|
|
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) {
|
2021-04-23 23:14:02 +00:00
|
|
|
if (!fPlayer) {
|
|
|
|
ImGui::Text("Could not read mskp file %s.\n", fName.c_str());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
ImGui::Begin("MSKP");
|
2021-04-30 17:50:05 +00:00
|
|
|
|
2021-04-23 23:14:02 +00:00
|
|
|
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.
|
2021-05-12 16:03:02 +00:00
|
|
|
int oldFrame = fFrame;
|
2021-04-23 23:14:02 +00:00
|
|
|
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();
|
2021-04-29 18:10:23 +00:00
|
|
|
}
|
2021-05-12 16:03:02 +00:00
|
|
|
if (fFrame != oldFrame) {
|
|
|
|
// When manually adjusting frames force layers to redraw.
|
|
|
|
this->redrawLayers();
|
|
|
|
}
|
|
|
|
|
2021-04-23 23:14:02 +00:00
|
|
|
ImGui::PopButtonRepeat();
|
2021-04-30 17:50:05 +00:00
|
|
|
ImGui::EndGroup();
|
2021-04-23 23:14:02 +00:00
|
|
|
|
2021-04-30 17:50:05 +00:00
|
|
|
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);
|
|
|
|
}
|
2021-04-23 23:14:02 +00:00
|
|
|
ImGui::EndGroup();
|
2021-04-30 17:50:05 +00:00
|
|
|
|
2021-05-12 16:03:02 +00:00
|
|
|
// 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]);
|
|
|
|
}
|
2021-04-23 23:14:02 +00:00
|
|
|
ImGui::End();
|
2021-04-30 17:50:05 +00:00
|
|
|
|
|
|
|
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();
|
2021-05-12 16:03:02 +00:00
|
|
|
if (fDrawLayerID >= 0) {
|
|
|
|
// clip out the root layer content, but still call playFrame so layer contents are updated
|
|
|
|
// to fFrame.
|
|
|
|
bounds = SkIRect::MakeEmpty();
|
|
|
|
}
|
2021-04-30 17:50:05 +00:00
|
|
|
canvas->clipIRect(bounds);
|
|
|
|
canvas->clear(SkColor4f{fBackgroundColor[0],
|
|
|
|
fBackgroundColor[1],
|
|
|
|
fBackgroundColor[2],
|
|
|
|
fBackgroundColor[3]});
|
|
|
|
fPlayer->playFrame(canvas, fFrame);
|
|
|
|
canvas->restore();
|
2021-05-12 16:03:02 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2021-04-29 18:10:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool MSKPSlide::animate(double nanos) {
|
2021-04-23 23:14:02 +00:00
|
|
|
if (!fPlayer || fPaused) {
|
2021-04-29 18:10:23 +00:00
|
|
|
return false;
|
|
|
|
}
|
2021-04-23 23:14:02 +00:00
|
|
|
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;
|
|
|
|
}
|
2021-04-29 18:10:23 +00:00
|
|
|
double elapsed = nanos - fLastFrameTime;
|
|
|
|
double frameTime = 1E9/fFPS;
|
|
|
|
int framesToAdvance = elapsed/frameTime;
|
2021-05-12 16:03:02 +00:00
|
|
|
fFrame = fFrame + framesToAdvance;
|
|
|
|
if (fFrame >= fPlayer->numFrames()) {
|
|
|
|
this->redrawLayers();
|
|
|
|
}
|
|
|
|
fFrame %= fPlayer->numFrames();
|
2021-04-29 18:10:23 +00:00
|
|
|
// Instead of just adding elapsed, note the time when this frame should have begun.
|
|
|
|
fLastFrameTime += framesToAdvance*frameTime;
|
2021-05-12 16:03:02 +00:00
|
|
|
return framesToAdvance > 0;
|
2021-04-29 18:10:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void MSKPSlide::load(SkScalar, SkScalar) {
|
|
|
|
if (!fStream) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
fStream->rewind();
|
|
|
|
fPlayer = MSKPPlayer::Make(fStream.get());
|
2021-05-12 16:03:02 +00:00
|
|
|
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);
|
|
|
|
}
|
2021-04-29 18:10:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void MSKPSlide::unload() { fPlayer.reset(); }
|
|
|
|
|
|
|
|
void MSKPSlide::gpuTeardown() { fPlayer->resetLayers(); }
|
|
|
|
|
2021-05-12 16:03:02 +00:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|