1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker * Copyright 2021 Google LLC
3*c8dee2aaSAndroid Build Coastguard Worker *
4*c8dee2aaSAndroid Build Coastguard Worker * Use of this source code is governed by a BSD-style license that can be
5*c8dee2aaSAndroid Build Coastguard Worker * found in the LICENSE file.
6*c8dee2aaSAndroid Build Coastguard Worker */
7*c8dee2aaSAndroid Build Coastguard Worker
8*c8dee2aaSAndroid Build Coastguard Worker #include "tools/viewer/MSKPSlide.h"
9*c8dee2aaSAndroid Build Coastguard Worker
10*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkCanvas.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkColor.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkImage.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkPaint.h"
14*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkRect.h"
15*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkRefCnt.h"
16*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkStream.h"
17*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkTPin.h"
18*c8dee2aaSAndroid Build Coastguard Worker
19*c8dee2aaSAndroid Build Coastguard Worker #include <cstddef>
20*c8dee2aaSAndroid Build Coastguard Worker #include <utility>
21*c8dee2aaSAndroid Build Coastguard Worker
22*c8dee2aaSAndroid Build Coastguard Worker #include "imgui.h"
23*c8dee2aaSAndroid Build Coastguard Worker
MSKPSlide(const SkString & name,const SkString & path)24*c8dee2aaSAndroid Build Coastguard Worker MSKPSlide::MSKPSlide(const SkString& name, const SkString& path)
25*c8dee2aaSAndroid Build Coastguard Worker : MSKPSlide(name, SkStream::MakeFromFile(path.c_str())) {}
26*c8dee2aaSAndroid Build Coastguard Worker
MSKPSlide(const SkString & name,std::unique_ptr<SkStreamSeekable> stream)27*c8dee2aaSAndroid Build Coastguard Worker MSKPSlide::MSKPSlide(const SkString& name, std::unique_ptr<SkStreamSeekable> stream)
28*c8dee2aaSAndroid Build Coastguard Worker : fStream(std::move(stream)) {
29*c8dee2aaSAndroid Build Coastguard Worker fName = name;
30*c8dee2aaSAndroid Build Coastguard Worker }
31*c8dee2aaSAndroid Build Coastguard Worker
getDimensions() const32*c8dee2aaSAndroid Build Coastguard Worker SkISize MSKPSlide::getDimensions() const {
33*c8dee2aaSAndroid Build Coastguard Worker return fPlayer ? fPlayer->maxDimensions() : SkISize{0, 0};
34*c8dee2aaSAndroid Build Coastguard Worker }
35*c8dee2aaSAndroid Build Coastguard Worker
draw(SkCanvas * canvas)36*c8dee2aaSAndroid Build Coastguard Worker void MSKPSlide::draw(SkCanvas* canvas) {
37*c8dee2aaSAndroid Build Coastguard Worker if (!fPlayer) {
38*c8dee2aaSAndroid Build Coastguard Worker ImGui::Text("Could not read mskp file %s.\n", fName.c_str());
39*c8dee2aaSAndroid Build Coastguard Worker return;
40*c8dee2aaSAndroid Build Coastguard Worker }
41*c8dee2aaSAndroid Build Coastguard Worker ImGui::Begin("MSKP");
42*c8dee2aaSAndroid Build Coastguard Worker
43*c8dee2aaSAndroid Build Coastguard Worker ImGui::BeginGroup();
44*c8dee2aaSAndroid Build Coastguard Worker // Play/Pause button
45*c8dee2aaSAndroid Build Coastguard Worker if (ImGui::Button(fPaused ? "Play " : "Pause")) {
46*c8dee2aaSAndroid Build Coastguard Worker fPaused = !fPaused;
47*c8dee2aaSAndroid Build Coastguard Worker if (fPaused) {
48*c8dee2aaSAndroid Build Coastguard Worker // This will ensure that when playback is unpaused we start on the current frame.
49*c8dee2aaSAndroid Build Coastguard Worker fLastFrameTime = -1;
50*c8dee2aaSAndroid Build Coastguard Worker }
51*c8dee2aaSAndroid Build Coastguard Worker }
52*c8dee2aaSAndroid Build Coastguard Worker // Control the frame rate of MSKP playback
53*c8dee2aaSAndroid Build Coastguard Worker ImGui::Text("FPS: "); ImGui::SameLine();
54*c8dee2aaSAndroid Build Coastguard Worker ImGui::RadioButton( "1", &fFPS, 1); ImGui::SameLine();
55*c8dee2aaSAndroid Build Coastguard Worker ImGui::RadioButton( "15", &fFPS, 15); ImGui::SameLine();
56*c8dee2aaSAndroid Build Coastguard Worker ImGui::RadioButton( "30", &fFPS, 30); ImGui::SameLine();
57*c8dee2aaSAndroid Build Coastguard Worker ImGui::RadioButton( "60", &fFPS, 60); ImGui::SameLine();
58*c8dee2aaSAndroid Build Coastguard Worker ImGui::RadioButton("120", &fFPS, 120); ImGui::SameLine();
59*c8dee2aaSAndroid Build Coastguard Worker ImGui::RadioButton("1:1", &fFPS, -1); // Draw one MSKP frame for each real viewer frame.
60*c8dee2aaSAndroid Build Coastguard Worker if (fFPS < 0) {
61*c8dee2aaSAndroid Build Coastguard Worker // Like above, will cause onAnimate() to resume at current frame when FPS is changed
62*c8dee2aaSAndroid Build Coastguard Worker // back to another frame rate.
63*c8dee2aaSAndroid Build Coastguard Worker fLastFrameTime = -1;
64*c8dee2aaSAndroid Build Coastguard Worker }
65*c8dee2aaSAndroid Build Coastguard Worker // Frame control. Slider and +/- buttons. Ctrl-Click slider to type frame number.
66*c8dee2aaSAndroid Build Coastguard Worker ImGui::Text("Frame:");
67*c8dee2aaSAndroid Build Coastguard Worker ImGui::SameLine();
68*c8dee2aaSAndroid Build Coastguard Worker ImGui::PushButtonRepeat(true); // Enable click-and-hold for frame arrows.
69*c8dee2aaSAndroid Build Coastguard Worker int oldFrame = fFrame;
70*c8dee2aaSAndroid Build Coastguard Worker if (ImGui::ArrowButton("-mksp_frame", ImGuiDir_Left)) {
71*c8dee2aaSAndroid Build Coastguard Worker fFrame = (fFrame + fPlayer->numFrames() - 1)%fPlayer->numFrames();
72*c8dee2aaSAndroid Build Coastguard Worker }
73*c8dee2aaSAndroid Build Coastguard Worker ImGui::SameLine();
74*c8dee2aaSAndroid Build Coastguard Worker if (ImGui::SliderInt("##msk_frameslider", &fFrame, 0, fPlayer->numFrames()-1, "% 3d")) {
75*c8dee2aaSAndroid Build Coastguard Worker fFrame = SkTPin(fFrame, 0, fPlayer->numFrames() - 1);
76*c8dee2aaSAndroid Build Coastguard Worker }
77*c8dee2aaSAndroid Build Coastguard Worker ImGui::SameLine();
78*c8dee2aaSAndroid Build Coastguard Worker if (ImGui::ArrowButton("+mskp_frame", ImGuiDir_Right)) {
79*c8dee2aaSAndroid Build Coastguard Worker fFrame = (fFrame + 1)%fPlayer->numFrames();
80*c8dee2aaSAndroid Build Coastguard Worker }
81*c8dee2aaSAndroid Build Coastguard Worker if (fFrame != oldFrame) {
82*c8dee2aaSAndroid Build Coastguard Worker // When manually adjusting frames force layers to redraw.
83*c8dee2aaSAndroid Build Coastguard Worker this->redrawLayers();
84*c8dee2aaSAndroid Build Coastguard Worker }
85*c8dee2aaSAndroid Build Coastguard Worker
86*c8dee2aaSAndroid Build Coastguard Worker ImGui::PopButtonRepeat();
87*c8dee2aaSAndroid Build Coastguard Worker ImGui::EndGroup();
88*c8dee2aaSAndroid Build Coastguard Worker
89*c8dee2aaSAndroid Build Coastguard Worker ImGui::BeginGroup();
90*c8dee2aaSAndroid Build Coastguard Worker ImGui::Checkbox("Show Frame Bounds", &fShowFrameBounds);
91*c8dee2aaSAndroid Build Coastguard Worker ImGui::SetNextItemWidth(200);
92*c8dee2aaSAndroid Build Coastguard Worker ImGui::ColorPicker4("background", fBackgroundColor, ImGuiColorEditFlags_AlphaBar);
93*c8dee2aaSAndroid Build Coastguard Worker // ImGui lets user enter out of range values by typing.
94*c8dee2aaSAndroid Build Coastguard Worker for (float& component : fBackgroundColor) {
95*c8dee2aaSAndroid Build Coastguard Worker component = SkTPin(component, 0.f, 1.f);
96*c8dee2aaSAndroid Build Coastguard Worker }
97*c8dee2aaSAndroid Build Coastguard Worker ImGui::EndGroup();
98*c8dee2aaSAndroid Build Coastguard Worker
99*c8dee2aaSAndroid Build Coastguard Worker // UI for visualizing contents of offscreen layers.
100*c8dee2aaSAndroid Build Coastguard Worker ImGui::Text("Offscreen Layers "); ImGui::SameLine();
101*c8dee2aaSAndroid Build Coastguard Worker ImGui::Checkbox("List All Layers", &fListAllLayers);
102*c8dee2aaSAndroid Build Coastguard Worker ImGui::RadioButton("root", &fDrawLayerID, -1);
103*c8dee2aaSAndroid Build Coastguard Worker const std::vector<int>& layerIDs = fListAllLayers ? fAllLayerIDs : fFrameLayerIDs[fFrame];
104*c8dee2aaSAndroid Build Coastguard Worker fLayerIDStrings.resize(layerIDs.size());
105*c8dee2aaSAndroid Build Coastguard Worker for (size_t i = 0; i < layerIDs.size(); ++i) {
106*c8dee2aaSAndroid Build Coastguard Worker fLayerIDStrings[i] = SkStringPrintf("%d", layerIDs[i]);
107*c8dee2aaSAndroid Build Coastguard Worker ImGui::RadioButton(fLayerIDStrings[i].c_str(), &fDrawLayerID, layerIDs[i]);
108*c8dee2aaSAndroid Build Coastguard Worker }
109*c8dee2aaSAndroid Build Coastguard Worker ImGui::End();
110*c8dee2aaSAndroid Build Coastguard Worker
111*c8dee2aaSAndroid Build Coastguard Worker auto bounds = SkIRect::MakeSize(fPlayer->frameDimensions(fFrame));
112*c8dee2aaSAndroid Build Coastguard Worker
113*c8dee2aaSAndroid Build Coastguard Worker if (fShowFrameBounds) {
114*c8dee2aaSAndroid Build Coastguard Worker SkPaint boundsPaint;
115*c8dee2aaSAndroid Build Coastguard Worker boundsPaint.setStyle(SkPaint::kStroke_Style);
116*c8dee2aaSAndroid Build Coastguard Worker boundsPaint.setColor(SK_ColorRED);
117*c8dee2aaSAndroid Build Coastguard Worker boundsPaint.setStrokeWidth(0.f);
118*c8dee2aaSAndroid Build Coastguard Worker boundsPaint.setAntiAlias(true);
119*c8dee2aaSAndroid Build Coastguard Worker // Outset so that at default scale we draw at pixel centers of the rows/cols surrounding the
120*c8dee2aaSAndroid Build Coastguard Worker // bounds.
121*c8dee2aaSAndroid Build Coastguard Worker canvas->drawRect(SkRect::Make(bounds).makeOutset(0.5f, 0.5f), boundsPaint);
122*c8dee2aaSAndroid Build Coastguard Worker }
123*c8dee2aaSAndroid Build Coastguard Worker
124*c8dee2aaSAndroid Build Coastguard Worker canvas->save();
125*c8dee2aaSAndroid Build Coastguard Worker if (fDrawLayerID >= 0) {
126*c8dee2aaSAndroid Build Coastguard Worker // clip out the root layer content, but still call playFrame so layer contents are updated
127*c8dee2aaSAndroid Build Coastguard Worker // to fFrame.
128*c8dee2aaSAndroid Build Coastguard Worker bounds = SkIRect::MakeEmpty();
129*c8dee2aaSAndroid Build Coastguard Worker }
130*c8dee2aaSAndroid Build Coastguard Worker canvas->clipIRect(bounds);
131*c8dee2aaSAndroid Build Coastguard Worker canvas->clear(SkColor4f{fBackgroundColor[0],
132*c8dee2aaSAndroid Build Coastguard Worker fBackgroundColor[1],
133*c8dee2aaSAndroid Build Coastguard Worker fBackgroundColor[2],
134*c8dee2aaSAndroid Build Coastguard Worker fBackgroundColor[3]});
135*c8dee2aaSAndroid Build Coastguard Worker fPlayer->playFrame(canvas, fFrame);
136*c8dee2aaSAndroid Build Coastguard Worker canvas->restore();
137*c8dee2aaSAndroid Build Coastguard Worker
138*c8dee2aaSAndroid Build Coastguard Worker if (fDrawLayerID >= 0) {
139*c8dee2aaSAndroid Build Coastguard Worker if (sk_sp<SkImage> layerImage = fPlayer->layerSnapshot(fDrawLayerID)) {
140*c8dee2aaSAndroid Build Coastguard Worker canvas->save();
141*c8dee2aaSAndroid Build Coastguard Worker canvas->clipIRect(SkIRect::MakeSize(layerImage->dimensions()));
142*c8dee2aaSAndroid Build Coastguard Worker canvas->clear(SkColor4f{fBackgroundColor[0],
143*c8dee2aaSAndroid Build Coastguard Worker fBackgroundColor[1],
144*c8dee2aaSAndroid Build Coastguard Worker fBackgroundColor[2],
145*c8dee2aaSAndroid Build Coastguard Worker fBackgroundColor[3]});
146*c8dee2aaSAndroid Build Coastguard Worker canvas->drawImage(std::move(layerImage), 0, 0);
147*c8dee2aaSAndroid Build Coastguard Worker canvas->restore();
148*c8dee2aaSAndroid Build Coastguard Worker }
149*c8dee2aaSAndroid Build Coastguard Worker return;
150*c8dee2aaSAndroid Build Coastguard Worker }
151*c8dee2aaSAndroid Build Coastguard Worker }
152*c8dee2aaSAndroid Build Coastguard Worker
animate(double nanos)153*c8dee2aaSAndroid Build Coastguard Worker bool MSKPSlide::animate(double nanos) {
154*c8dee2aaSAndroid Build Coastguard Worker if (!fPlayer || fPaused) {
155*c8dee2aaSAndroid Build Coastguard Worker return false;
156*c8dee2aaSAndroid Build Coastguard Worker }
157*c8dee2aaSAndroid Build Coastguard Worker if (fLastFrameTime < 0) {
158*c8dee2aaSAndroid Build Coastguard Worker // We're coming off being paused or switching from 1:1 mode to steady FPS. Advance 1 frame
159*c8dee2aaSAndroid Build Coastguard Worker // and reset the frame time to start accumulating time from now.
160*c8dee2aaSAndroid Build Coastguard Worker fFrame = (fFrame + 1)%fPlayer->numFrames();
161*c8dee2aaSAndroid Build Coastguard Worker fLastFrameTime = nanos;
162*c8dee2aaSAndroid Build Coastguard Worker return this->fPlayer->numFrames() > 1;
163*c8dee2aaSAndroid Build Coastguard Worker }
164*c8dee2aaSAndroid Build Coastguard Worker if (fFPS < 0) {
165*c8dee2aaSAndroid Build Coastguard Worker // 1:1 mode. Always draw the next frame on each animation cycle.
166*c8dee2aaSAndroid Build Coastguard Worker fFrame = (fFrame + 1)%fPlayer->numFrames();
167*c8dee2aaSAndroid Build Coastguard Worker return this->fPlayer->numFrames() > 1;
168*c8dee2aaSAndroid Build Coastguard Worker }
169*c8dee2aaSAndroid Build Coastguard Worker double elapsed = nanos - fLastFrameTime;
170*c8dee2aaSAndroid Build Coastguard Worker double frameTime = 1E9/fFPS;
171*c8dee2aaSAndroid Build Coastguard Worker int framesToAdvance = elapsed/frameTime;
172*c8dee2aaSAndroid Build Coastguard Worker fFrame = fFrame + framesToAdvance;
173*c8dee2aaSAndroid Build Coastguard Worker if (fFrame >= fPlayer->numFrames()) {
174*c8dee2aaSAndroid Build Coastguard Worker this->redrawLayers();
175*c8dee2aaSAndroid Build Coastguard Worker }
176*c8dee2aaSAndroid Build Coastguard Worker fFrame %= fPlayer->numFrames();
177*c8dee2aaSAndroid Build Coastguard Worker // Instead of just adding elapsed, note the time when this frame should have begun.
178*c8dee2aaSAndroid Build Coastguard Worker fLastFrameTime += framesToAdvance*frameTime;
179*c8dee2aaSAndroid Build Coastguard Worker return framesToAdvance > 0;
180*c8dee2aaSAndroid Build Coastguard Worker }
181*c8dee2aaSAndroid Build Coastguard Worker
load(SkScalar,SkScalar)182*c8dee2aaSAndroid Build Coastguard Worker void MSKPSlide::load(SkScalar, SkScalar) {
183*c8dee2aaSAndroid Build Coastguard Worker if (!fStream) {
184*c8dee2aaSAndroid Build Coastguard Worker return;
185*c8dee2aaSAndroid Build Coastguard Worker }
186*c8dee2aaSAndroid Build Coastguard Worker fStream->rewind();
187*c8dee2aaSAndroid Build Coastguard Worker fPlayer = MSKPPlayer::Make(fStream.get());
188*c8dee2aaSAndroid Build Coastguard Worker if (!fPlayer) {
189*c8dee2aaSAndroid Build Coastguard Worker return;
190*c8dee2aaSAndroid Build Coastguard Worker }
191*c8dee2aaSAndroid Build Coastguard Worker fAllLayerIDs = fPlayer->layerIDs();
192*c8dee2aaSAndroid Build Coastguard Worker fFrameLayerIDs.clear();
193*c8dee2aaSAndroid Build Coastguard Worker fFrameLayerIDs.resize(fPlayer->numFrames());
194*c8dee2aaSAndroid Build Coastguard Worker for (int i = 0; i < fPlayer->numFrames(); ++i) {
195*c8dee2aaSAndroid Build Coastguard Worker fFrameLayerIDs[i] = fPlayer->layerIDs(i);
196*c8dee2aaSAndroid Build Coastguard Worker }
197*c8dee2aaSAndroid Build Coastguard Worker }
198*c8dee2aaSAndroid Build Coastguard Worker
unload()199*c8dee2aaSAndroid Build Coastguard Worker void MSKPSlide::unload() { fPlayer.reset(); }
200*c8dee2aaSAndroid Build Coastguard Worker
gpuTeardown()201*c8dee2aaSAndroid Build Coastguard Worker void MSKPSlide::gpuTeardown() {
202*c8dee2aaSAndroid Build Coastguard Worker if (fPlayer) {
203*c8dee2aaSAndroid Build Coastguard Worker fPlayer->resetLayers();
204*c8dee2aaSAndroid Build Coastguard Worker }
205*c8dee2aaSAndroid Build Coastguard Worker }
206*c8dee2aaSAndroid Build Coastguard Worker
redrawLayers()207*c8dee2aaSAndroid Build Coastguard Worker void MSKPSlide::redrawLayers() {
208*c8dee2aaSAndroid Build Coastguard Worker if (fDrawLayerID >= 0) {
209*c8dee2aaSAndroid Build Coastguard Worker // Completely reset the layers so that we won't see content from later frames on layers
210*c8dee2aaSAndroid Build Coastguard Worker // that haven't been visited from frames 0..fFrames.
211*c8dee2aaSAndroid Build Coastguard Worker fPlayer->resetLayers();
212*c8dee2aaSAndroid Build Coastguard Worker } else {
213*c8dee2aaSAndroid Build Coastguard Worker // Just rewind layers so that we redraw any layer from scratch on the next frame that
214*c8dee2aaSAndroid Build Coastguard Worker // updates it. Important for benchmarking/profiling as otherwise if a layer is only
215*c8dee2aaSAndroid Build Coastguard Worker // drawn once in the frame sequence then it will never be updated after the first play
216*c8dee2aaSAndroid Build Coastguard Worker // through. This doesn't reallocate the layer backing stores.
217*c8dee2aaSAndroid Build Coastguard Worker fPlayer->rewindLayers();
218*c8dee2aaSAndroid Build Coastguard Worker }
219*c8dee2aaSAndroid Build Coastguard Worker }
220