viewer can show offscreen layers in MSKPSlide
Also three fixes for drawing to offscreen layers in MSKPPlayer:
*Only play from last full redraw to next cmd
*Clear before full redraw
*Actually track current cmd in layer state.
Bug: skia:11900
Change-Id: I988afb61f96c8acb7e7554d65bfa6cd6020196c7
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/407460
Reviewed-by: Nathaniel Nifong <nifong@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
diff --git a/tools/MSKPPlayer.cpp b/tools/MSKPPlayer.cpp
index 758c398..3ba6ab3 100644
--- a/tools/MSKPPlayer.cpp
+++ b/tools/MSKPPlayer.cpp
@@ -25,15 +25,33 @@
// Base Cmd struct.
struct MSKPPlayer::Cmd {
virtual ~Cmd() = default;
+ virtual bool isFullRedraw(SkCanvas*) const = 0;
virtual void draw(SkCanvas* canvas, const LayerMap&, LayerStateMap*) const = 0;
+ // If this command draws another layer, return it's ID. Otherwise, -1.
+ virtual int layerID() const { return -1; }
};
// Draws a SkPicture.
struct MSKPPlayer::PicCmd : Cmd {
sk_sp<SkPicture> fContent;
+ SkIRect fClipRect = SkIRect::MakeEmpty(); // clip for picture (no clip if empty).
+
+ bool isFullRedraw(SkCanvas* canvas) const override {
+ if (fClipRect.isEmpty()) {
+ return false;
+ }
+ return fClipRect.contains(SkIRect::MakeSize(canvas->getBaseLayerSize()));
+ }
void draw(SkCanvas* canvas, const LayerMap&, LayerStateMap*) const override {
+ if (!fClipRect.isEmpty()) {
+ canvas->save();
+ canvas->clipIRect(fClipRect);
+ }
canvas->drawPicture(fContent.get());
+ if (!fClipRect.isEmpty()) {
+ canvas->restore();
+ }
}
};
@@ -49,7 +67,9 @@
SkCanvas::SrcRectConstraint fConstraint;
SkTLazy<SkPaint> fPaint;
+ bool isFullRedraw(SkCanvas* canvas) const override { return false; }
void draw(SkCanvas* canvas, const LayerMap&, LayerStateMap*) const override;
+ int layerID() const override { return fLayerId; }
};
void MSKPPlayer::DrawLayerCmd::draw(SkCanvas* canvas,
@@ -71,9 +91,21 @@
cmd = 0;
}
SkCanvas* layerCanvas = layerState->fSurface->getCanvas();
+ // Check if there is a full redraw between cmd and fLayerCmdCnt and if so jump to it and ensure
+ // we clear the canvas if starting from a full redraw.
+ for (size_t checkCmd = fLayerCmdCnt - 1; checkCmd > cmd; --checkCmd) {
+ if (layer.fCmds[checkCmd]->isFullRedraw(layerCanvas)) {
+ cmd = checkCmd;
+ break;
+ }
+ }
for (; cmd < fLayerCmdCnt; ++cmd) {
+ if (cmd == 0 || layer.fCmds[cmd]->isFullRedraw(layerCanvas)) {
+ layerState->fSurface->getCanvas()->clear(SK_ColorTRANSPARENT);
+ }
layer.fCmds[cmd]->draw(layerCanvas, layerMap, layerStateMap);
}
+ layerState->fCurrCmd = fLayerCmdCnt;
const SkPaint* paint = fPaint.isValid() ? fPaint.get() : nullptr;
canvas->drawImageRect(layerState->fSurface->makeImageSnapshot(),
fSrcRect,
@@ -87,8 +119,11 @@
class MSKPPlayer::CmdRecordCanvas : public SkCanvasVirtualEnforcer<SkCanvas> {
public:
- CmdRecordCanvas(Layer* dst, LayerMap* offscreenLayers)
+ CmdRecordCanvas(Layer* dst, LayerMap* offscreenLayers, const SkIRect* clipRect = nullptr)
: fDst(dst), fOffscreenLayers(offscreenLayers) {
+ if (clipRect) {
+ fClipRect = *clipRect;
+ }
fRecorder.beginRecording(SkRect::Make(dst->fDimensions));
}
~CmdRecordCanvas() override { this->recordPicCmd(); }
@@ -263,15 +298,16 @@
// Indicates that the next drawPicture command contains the SkPicture to render
// to the layer identified by the ID. 'rect' indicates the dirty area to update
// (and indicates the layer size if this command is introducing a new layer id).
- fNextDrawToLayerID = std::stoi(tokens[1].c_str());
- if (fOffscreenLayers->find(fNextDrawToLayerID) == fOffscreenLayers->end()) {
- SkASSERT(rect.left() == 0 && rect.top() == 0);
- SkISize size = {SkScalarCeilToInt(rect.right()),
- SkScalarCeilToInt(rect.bottom())};
- (*fOffscreenLayers)[fNextDrawToLayerID].fDimensions = size;
+ fNextDrawPictureToLayerID = std::stoi(tokens[1].c_str());
+ fNextDrawPictureToLayerClipRect = rect.roundOut();
+ if (fOffscreenLayers->find(fNextDrawPictureToLayerID) == fOffscreenLayers->end()) {
+ SkASSERT(fNextDrawPictureToLayerClipRect.left() == 0 &&
+ fNextDrawPictureToLayerClipRect.top() == 0);
+ (*fOffscreenLayers)[fNextDrawPictureToLayerID].fDimensions =
+ fNextDrawPictureToLayerClipRect.size();
}
- // The next draw picture will notice that fNextDrawLayerID is set and redirect
- // the picture to the offscreen layer.
+ // The next draw picture will notice that fNextDrawPictureToLayerID is set and
+ // redirect the picture to the offscreen layer.
return;
} else if (tokens[0].equals(kSurfaceID)) {
// Indicates that the following drawImageRect should draw an offscreen layer
@@ -293,12 +329,14 @@
void onDrawPicture(const SkPicture* picture,
const SkMatrix* matrix,
const SkPaint* paint) override {
- if (fNextDrawToLayerID != -1) {
+ if (fNextDrawPictureToLayerID != -1) {
SkASSERT(!matrix);
SkASSERT(!paint);
- CmdRecordCanvas sc(&fOffscreenLayers->at(fNextDrawToLayerID), fOffscreenLayers);
+ Layer* layer = &fOffscreenLayers->at(fNextDrawPictureToLayerID);
+ CmdRecordCanvas sc(layer, fOffscreenLayers, &fNextDrawPictureToLayerClipRect);
picture->playback(&sc);
- fNextDrawToLayerID = -1;
+ fNextDrawPictureToLayerID = -1;
+ fNextDrawPictureToLayerClipRect = SkIRect::MakeEmpty();
return;
}
if (paint) {
@@ -324,6 +362,7 @@
void recordPicCmd() {
auto cmd = std::make_unique<PicCmd>();
cmd->fContent = fRecorder.finishRecordingAsPicture();
+ cmd->fClipRect = fClipRect;
if (cmd->fContent) {
fDst->fCmds.push_back(std::move(cmd));
}
@@ -332,10 +371,12 @@
}
SkPictureRecorder fRecorder; // accumulates draws until we draw an offscreen into this layer.
- Layer* fDst = nullptr;
- int fNextDrawToLayerID = -1;
- int fNextDrawImageFromLayerID = -1;
- LayerMap* fOffscreenLayers = nullptr;
+ Layer* fDst = nullptr;
+ SkIRect fClipRect = SkIRect::MakeEmpty();
+ int fNextDrawPictureToLayerID = -1;
+ SkIRect fNextDrawPictureToLayerClipRect = SkIRect::MakeEmpty();
+ int fNextDrawImageFromLayerID = -1;
+ LayerMap* fOffscreenLayers = nullptr;
};
///////////////////////////////////////////////////////////////////////////////
@@ -432,3 +473,40 @@
}
}
}
+
+std::vector<int> MSKPPlayer::layerIDs(int frame) const {
+ std::vector<int> result;
+ if (frame < 0) {
+ result.reserve(fOffscreenLayers.size());
+ for (auto& [id, _] : fOffscreenLayers) {
+ result.push_back(id);
+ }
+ return result;
+ }
+ if (frame < static_cast<int>(fRootLayers.size())) {
+ this->collectReferencedLayers(fRootLayers[frame], &result);
+ }
+ return result;
+}
+
+sk_sp<SkImage> MSKPPlayer::layerSnapshot(int layerID) const {
+ auto iter = fOffscreenLayerStates.find(layerID);
+ if (iter == fOffscreenLayerStates.end() || !iter->second.fSurface) {
+ return nullptr;
+ }
+ return iter->second.fSurface->makeImageSnapshot();
+}
+
+void MSKPPlayer::collectReferencedLayers(const Layer& layer, std::vector<int>* out) const {
+ for (const auto& cmd : layer.fCmds) {
+ if (int id = cmd->layerID(); id >= 0) {
+ // Linear, but we'd need to have a lot of layers to actually care.
+ if (std::find(out->begin(), out->end(), id) == out->end()) {
+ out->push_back(id);
+ auto iter = fOffscreenLayers.find(id);
+ SkASSERT(iter != fOffscreenLayers.end());
+ this->collectReferencedLayers(iter->second, out);
+ }
+ }
+ }
+}
diff --git a/tools/MSKPPlayer.h b/tools/MSKPPlayer.h
index 5359f19..1b66852 100644
--- a/tools/MSKPPlayer.h
+++ b/tools/MSKPPlayer.h
@@ -15,6 +15,7 @@
#include <vector>
class SkCanvas;
+class SkImage;
class SkStreamSeekable;
class SkSurface;
@@ -60,6 +61,22 @@
*/
void allocateLayers(SkCanvas*);
+ /**
+ * A set of IDs of offscreen layers in no particular order. If frame value >= 0 is specified
+ * then the layer set is filtered to layers used by that frame (or empty if >= numFrames). If
+ * < 0 then gathers all the layers across all frames.
+ */
+ std::vector<int> layerIDs(int frame = -1) const;
+
+ /**
+ * Gets the contents of an offscreen layer. It's contents will depend on current playback state
+ * (playFrame(), updateFrameLayers(), resetLayers()). If the layer currently has no backing
+ * store because it hasn't been drawn or resetLayers() was called then this will return nullptr.
+ * Layer contents are not affected by rewindLayers() as that simply lazily redraws the frame
+ * contents the next time it is required by playFrame*() or updateFrameLayers().
+ */
+ sk_sp<SkImage> layerSnapshot(int layerID) const;
+
private:
MSKPPlayer() = default;
// noncopyable, nonmoveable.
@@ -93,6 +110,8 @@
static sk_sp<SkSurface> MakeSurfaceForLayer(const Layer&, SkCanvas* rootCanvas);
+ void collectReferencedLayers(const Layer& layer, std::vector<int>*) const;
+
// MSKP layer ID -> Layer
using LayerMap = std::unordered_map<int, Layer>;
// MSKP layer ID -> LayerState
diff --git a/tools/viewer/MSKPSlide.cpp b/tools/viewer/MSKPSlide.cpp
index abb34f7..1bc5f3a 100644
--- a/tools/viewer/MSKPSlide.cpp
+++ b/tools/viewer/MSKPSlide.cpp
@@ -58,6 +58,7 @@
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();
}
@@ -69,6 +70,11 @@
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();
@@ -82,6 +88,16 @@
}
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));
@@ -98,6 +114,11 @@
}
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],
@@ -105,6 +126,20 @@
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) {
@@ -126,10 +161,14 @@
double elapsed = nanos - fLastFrameTime;
double frameTime = 1E9/fFPS;
int framesToAdvance = elapsed/frameTime;
- fFrame = (fFrame + framesToAdvance)%fPlayer->numFrames();
+ 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%fPlayer->numFrames() != 0;
+ return framesToAdvance > 0;
}
void MSKPSlide::load(SkScalar, SkScalar) {
@@ -138,9 +177,31 @@
}
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() { 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();
+ }
+}
diff --git a/tools/viewer/MSKPSlide.h b/tools/viewer/MSKPSlide.h
index a03fcd9..1e47f94 100644
--- a/tools/viewer/MSKPSlide.h
+++ b/tools/viewer/MSKPSlide.h
@@ -27,6 +27,9 @@
void gpuTeardown() override;
private:
+ // Call if layers need to be redrawn because we've looped playback or UI interaction.
+ void redrawLayers();
+
std::unique_ptr<SkStreamSeekable> fStream;
std::unique_ptr<MSKPPlayer> fPlayer;
@@ -40,6 +43,12 @@
// Default to transparent black, which is correct for Android MSKPS.
float fBackgroundColor[4] = {0, 0, 0, 0};
+ std::vector<int> fAllLayerIDs;
+ std::vector<std::vector<int>> fFrameLayerIDs;
+ std::vector<SkString> fLayerIDStrings;
+ int fDrawLayerID = -1; // -1 means just draw the root layer
+ bool fListAllLayers = true;
+
using INHERITED = Slide;
};