skia2/tools/viewer/MSKPSlide.cpp
Brian Osman b5282fea23 Fix viewer crash with corrupted MSKPs
If an MSKP fails to load, fPlayer will be null, and viewer will crash
when switching backends.

Change-Id: I68b63da21c8f7c5726b3c5bbbdcb6c17018a63ac
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/510209
Auto-Submit: Brian Osman <brianosman@google.com>
Reviewed-by: Jim Van Verth <jvanverth@google.com>
Commit-Queue: Jim Van Verth <jvanverth@google.com>
2022-02-17 22:19:18 +00:00

212 lines
7.5 KiB
C++

/*
* 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"
#include "include/private/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();
}
}