Parse android layer annotations in debugger, play back layers

Bug: skia:9626
Change-Id: I3ae8fa83520690f9af534e9ab0b70834d7890fb0
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/256100
Commit-Queue: Nathaniel Nifong <nifong@google.com>
Reviewed-by: Kevin Lubick <kjlubick@google.com>
This commit is contained in:
Nathaniel Nifong 2019-12-12 11:05:10 -05:00 committed by Skia Commit-Bot
parent ed58654e39
commit 20b177a9bf
11 changed files with 620 additions and 50 deletions

View File

@ -1034,6 +1034,7 @@ if (target_cpu == "wasm") {
"tools/SkSharingProc.cpp",
"tools/UrlDataManager.cpp",
"tools/debugger/DebugCanvas.cpp",
"tools/debugger/DebugLayerManager.cpp",
"tools/debugger/DrawCommand.cpp",
"tools/debugger/JsonWriteBuffer.cpp",
]
@ -1455,6 +1456,7 @@ if (skia_enable_tools) {
"tools/ToolUtils.cpp",
"tools/UrlDataManager.cpp",
"tools/debugger/DebugCanvas.cpp",
"tools/debugger/DebugLayerManager.cpp",
"tools/debugger/DrawCommand.cpp",
"tools/debugger/JsonWriteBuffer.cpp",
"tools/fonts/RandomScalerContext.cpp",
@ -1998,6 +2000,7 @@ if (skia_enable_tools) {
"fuzz/oss_fuzz/FuzzTextBlobDeserialize.cpp",
"tools/UrlDataManager.cpp",
"tools/debugger/DebugCanvas.cpp",
"tools/debugger/DebugLayerManager.cpp",
"tools/debugger/DrawCommand.cpp",
"tools/debugger/JsonWriteBuffer.cpp",
]
@ -2416,6 +2419,7 @@ if (skia_enable_tools) {
sources = [
"tools/UrlDataManager.cpp",
"tools/debugger/DebugCanvas.cpp",
"tools/debugger/DebugLayerManager.cpp",
"tools/debugger/DrawCommand.cpp",
"tools/debugger/JsonWriteBuffer.cpp",
"tools/mdbviz/MainWindow.cpp",

View File

@ -13,7 +13,12 @@
#include "tools/SkSharingProc.h"
#include "tools/UrlDataManager.h"
#include "tools/debugger/DebugCanvas.h"
#include "tools/debugger/DebugLayerManager.h"
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#include <emscripten.h>
#include <emscripten/bind.h>
@ -98,10 +103,20 @@ class SkpDebugPlayer {
SkDebugf("Constrained command index (%d) within this frame's length (%d)\n", index, cmdlen);
index = cmdlen-1;
}
auto* canvas = surface->getCanvas();
canvas->clear(SK_ColorTRANSPARENT);
frames[fp]->drawTo(surface->getCanvas(), index);
surface->getCanvas()->flush();
}
// Draws to the end of the current frame.
void draw(SkSurface* surface) {
auto* canvas = surface->getCanvas();
canvas->clear(SK_ColorTRANSPARENT);
frames[fp]->draw(surface->getCanvas());
surface->getCanvas()->flush();
}
const SkIRect& getBounds() { return fBounds; }
// The following three operations apply to every frame because they are overdraw features.
@ -201,6 +216,11 @@ class SkpDebugPlayer {
return toSimpleImageInfo(fImages[index]->imageInfo(), fImages[index].get());
}
// return a list of layer draw events that happened at the beginning of this frame.
std::vector<DebugLayerManager::DrawEventSummary> getLayerDrawEvents() {
return fLayerManager->summarizeEvents(fp);
}
private:
// Loads a single frame (traditional) skp file from the provided data stream and returns
@ -215,54 +235,57 @@ class SkpDebugPlayer {
SkDebugf("Parsed SKP file.\n");
// Make debug canvas using bounds from SkPicture
fBounds = picture->cullRect().roundOut();
std::unique_ptr<DebugCanvas> debugDanvas = std::make_unique<DebugCanvas>(fBounds);
SkDebugf("DebugCanvas created.\n");
std::unique_ptr<DebugCanvas> debugCanvas = std::make_unique<DebugCanvas>(fBounds);
// Only draw picture to the debug canvas once.
debugDanvas->drawPicture(picture);
SkDebugf("Added picture with %d commands.\n", debugDanvas->getSize());
return debugDanvas;
debugCanvas->drawPicture(picture);
return debugCanvas;
}
void loadMultiFrame(SkMemoryStream* stream) {
// Attempt to deserialize with an image sharing serial proc.
auto deserialContext = std::make_unique<SkSharingDeserialContext>();
SkDeserialProcs procs;
procs.fImageProc = SkSharingDeserialContext::deserializeImage;
procs.fImageCtx = deserialContext.get();
// Attempt to deserialize with an image sharing serial proc.
auto deserialContext = std::make_unique<SkSharingDeserialContext>();
SkDeserialProcs procs;
procs.fImageProc = SkSharingDeserialContext::deserializeImage;
procs.fImageCtx = deserialContext.get();
int page_count = SkMultiPictureDocumentReadPageCount(stream);
if (!page_count) {
SkDebugf("Not a MultiPictureDocument");
return;
}
SkDebugf("Expecting %d frames\n", page_count);
int page_count = SkMultiPictureDocumentReadPageCount(stream);
if (!page_count) {
SkDebugf("Not a MultiPictureDocument");
return;
std::vector<SkDocumentPage> pages(page_count);
if (!SkMultiPictureDocumentRead(stream, pages.data(), page_count, &procs)) {
SkDebugf("Reading frames from MultiPictureDocument failed");
return;
}
fLayerManager = std::make_unique<DebugLayerManager>();
int i = 0;
for (const auto& page : pages) {
i++;
// Make debug canvas using bounds from SkPicture
fBounds = page.fPicture->cullRect().roundOut();
std::unique_ptr<DebugCanvas> debugCanvas = std::make_unique<DebugCanvas>(fBounds);
debugCanvas->setLayerManagerAndFrame(fLayerManager.get(), i);
// Only draw picture to the debug canvas once.
debugCanvas->drawPicture(page.fPicture);
if (debugCanvas->getSize() <=0 ){
SkDebugf("Skipped corrupted frame, had %d commands \n", debugCanvas->getSize());
continue;
}
SkDebugf("Expecting %d frames\n", page_count);
std::vector<SkDocumentPage> pages(page_count);
if (!SkMultiPictureDocumentRead(stream, pages.data(), page_count, &procs)) {
SkDebugf("Reading frames from MultiPictureDocument failed");
return;
}
for (const auto& page : pages) {
// Make debug canvas using bounds from SkPicture
fBounds = page.fPicture->cullRect().roundOut();
std::unique_ptr<DebugCanvas> debugDanvas = std::make_unique<DebugCanvas>(fBounds);
// Only draw picture to the debug canvas once.
debugDanvas->drawPicture(page.fPicture);
SkDebugf("Added picture with %d commands.\n", debugDanvas->getSize());
if (debugDanvas->getSize() <=0 ){
SkDebugf("Skipped corrupted frame, had %d commands \n", debugDanvas->getSize());
continue;
}
debugDanvas->setOverdrawViz(false);
debugDanvas->setDrawGpuOpBounds(false);
debugDanvas->setClipVizColor(SK_ColorTRANSPARENT);
frames.push_back(std::move(debugDanvas));
}
fImages = deserialContext->fImages;
// If you don't set these, they're undefined.
debugCanvas->setOverdrawViz(false);
debugCanvas->setDrawGpuOpBounds(false);
debugCanvas->setClipVizColor(SK_ColorTRANSPARENT);
frames.push_back(std::move(debugCanvas));
}
fImages = deserialContext->fImages;
}
// A vector of DebugCanvas, each one initialized to a frame of the animation.
@ -285,6 +308,10 @@ class SkpDebugPlayer {
// find anything. TODO(nifong): Unify these two numbering schemes in CollatingCanvas.
UrlDataManager udm;
// A structure holding the picture information needed to draw any layers used in an mskp file
// individual frames hold a pointer to it, store draw events, and request images from it.
// it is stateful and is set to the current frame at all times.
std::unique_ptr<DebugLayerManager> fLayerManager;
};
#if SK_SUPPORT_GPU
@ -356,6 +383,7 @@ EMSCRIPTEN_BINDINGS(my_module) {
.constructor<>()
.function("loadSkp", &SkpDebugPlayer::loadSkp, allow_raw_pointers())
.function("drawTo", &SkpDebugPlayer::drawTo, allow_raw_pointers())
.function("draw", &SkpDebugPlayer::draw, allow_raw_pointers())
.function("getBounds", &SkpDebugPlayer::getBounds)
.function("setOverdrawVis", &SkpDebugPlayer::setOverdrawVis)
.function("setClipVizColor", &SkpDebugPlayer::setClipVizColor)
@ -369,7 +397,8 @@ EMSCRIPTEN_BINDINGS(my_module) {
.function("getFrameCount", &SkpDebugPlayer::getFrameCount)
.function("getImageResource", &SkpDebugPlayer::getImageResource)
.function("getImageCount", &SkpDebugPlayer::getImageCount)
.function("getImageInfo", &SkpDebugPlayer::getImageInfo);
.function("getImageInfo", &SkpDebugPlayer::getImageInfo)
.function("getLayerDrawEvents", &SkpDebugPlayer::getLayerDrawEvents);
// Structs used as arguments or returns to the functions above
value_object<SkIRect>("SkIRect")
@ -377,6 +406,15 @@ EMSCRIPTEN_BINDINGS(my_module) {
.field("fTop", &SkIRect::fTop)
.field("fRight", &SkIRect::fRight)
.field("fBottom", &SkIRect::fBottom);
// emscripten provided the following convenience function for binding vector<T>
// https://emscripten.org/docs/api_reference/bind.h.html#_CPPv415register_vectorPKc
register_vector<DebugLayerManager::DrawEventSummary>("VectorDrawEventSummary");
value_object<DebugLayerManager::DrawEventSummary>("DebugLayerManager::DrawEventSummary")
.field("nodeId", &DebugLayerManager::DrawEventSummary::nodeId)
.field("fullRedraw", &DebugLayerManager::DrawEventSummary::fullRedraw)
.field("commandCount", &DebugLayerManager::DrawEventSummary::commandCount)
.field("layerWidth", &DebugLayerManager::DrawEventSummary::layerWidth)
.field("layerHeight", &DebugLayerManager::DrawEventSummary::layerHeight);
// Symbols needed by cpu.js to perform surface creation and flushing.
enum_<SkColorType>("ColorType")

View File

@ -50,6 +50,7 @@ tests_sources = [
"$_tests/ColorTest.cpp",
"$_tests/CopySurfaceTest.cpp",
"$_tests/CubicMapTest.cpp",
"$_tests/DebugLayerManagerTest.cpp",
"$_tests/DashPathEffectTest.cpp",
"$_tests/DataRefTest.cpp",
"$_tests/DefaultPathRendererTest.cpp",

View File

@ -0,0 +1,84 @@
/*
* Copyright 2019 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/core/SkPaint.h"
#include "include/core/SkPicture.h"
#include "include/core/SkPictureRecorder.h"
#include "include/core/SkRect.h"
#include "tests/Test.h"
#include "tools/debugger/DebugLayerManager.h"
// Adds one full update, one partial update, and requests one image a few frames later.
static void test_basic(skiatest::Reporter* reporter) {
// prepare supporting objects
int layerWidth = 100;
int layerHeight = 100;
// make a picture that fully updates the layer
SkPictureRecorder rec;
SkCanvas* canvas = rec.beginRecording(layerWidth, layerHeight);
canvas->clear(0x00000000);
SkPaint paint;
paint.setColor(SK_ColorBLUE);
canvas->drawOval(SkRect::MakeLTRB(1,1,99,99), paint);
canvas->flush();
auto picture1 = rec.finishRecordingAsPicture();
SkIRect dirtyRectFull = SkIRect::MakeLTRB(0, 0, layerWidth, layerHeight);
// make a second picture that acts as a partial update.
SkPictureRecorder rec2;
canvas = rec2.beginRecording(layerWidth, layerHeight);
paint.setColor(SK_ColorGREEN);
canvas->drawOval(SkRect::MakeLTRB(40,40,60,60), paint);
canvas->flush();
auto picture2 = rec2.finishRecordingAsPicture();
SkIRect dirtyRectPartial = SkIRect::MakeLTRB(40,40,60,60);
int node = 2;
// create and exercise DebugLayerManager
DebugLayerManager dlm;
dlm.storeSkPicture(node, 0, picture1, dirtyRectFull);
dlm.storeSkPicture(node, 10, picture2, dirtyRectPartial);
auto frames = dlm.listFramesForNode(node);
// Confirm the layer manager stored these at the right places.
REPORTER_ASSERT(reporter, frames.size() == 2);
REPORTER_ASSERT(reporter, frames[0] == 0);
REPORTER_ASSERT(reporter, frames[1] == 10);
SkPixmap pixmap;
// request an image of the layer between the two updates.
for (int i=0; i<10; i++) {
auto image = dlm.getLayerAsImage(node, i);
REPORTER_ASSERT(reporter, image->width() == layerWidth);
REPORTER_ASSERT(reporter, image->height() == layerHeight);
// confirm center is blue, proving only first pic was drawn.
image->peekPixels(&pixmap);
SkColor paintColor = pixmap.getColor(50, 50);
REPORTER_ASSERT(reporter, paintColor == SK_ColorBLUE);
}
// For any images after the second draw, confirm the center is green, but the area just outside
// that smaller circle is still blue, proving dlm drew both pictures.
for (int i=10; i<12; i++) {
auto image = dlm.getLayerAsImage(node, i);
REPORTER_ASSERT(reporter, image->width() == layerWidth);
REPORTER_ASSERT(reporter, image->height() == layerHeight);
image->peekPixels(&pixmap);
auto innerColor = pixmap.getColor(50, 50);
REPORTER_ASSERT(reporter, innerColor == SK_ColorGREEN);
auto outerColor = pixmap.getColor(10, 50);
REPORTER_ASSERT(reporter, outerColor == SK_ColorBLUE);
}
}
DEF_TEST(DebugLayerManagerTest, reporter) {
test_basic(reporter);
}

View File

@ -13,6 +13,7 @@
#include "src/core/SkRectPriv.h"
#include "src/utils/SkJSONWriter.h"
#include "tools/debugger/DebugCanvas.h"
#include "tools/debugger/DebugLayerManager.h"
#include "tools/debugger/DrawCommand.h"
#include "include/gpu/GrContext.h"
@ -20,11 +21,19 @@
#include "src/gpu/GrContextPriv.h"
#include "src/gpu/GrRenderTargetContext.h"
#include <string>
#define SKDEBUGCANVAS_VERSION 1
#define SKDEBUGCANVAS_ATTRIBUTE_VERSION "version"
#define SKDEBUGCANVAS_ATTRIBUTE_COMMANDS "commands"
#define SKDEBUGCANVAS_ATTRIBUTE_AUDITTRAIL "auditTrail"
namespace {
// Constants used in Annotations by Android for keeping track of layers
static constexpr char kOffscreenLayerDraw[] = "OffscreenLayerDraw";
static constexpr char kSurfaceID[] = "SurfaceID";
} // namespace
class DebugPaintFilterCanvas : public SkPaintFilterCanvas {
public:
DebugPaintFilterCanvas(SkCanvas* canvas) : INHERITED(canvas) {}
@ -53,7 +62,9 @@ DebugCanvas::DebugCanvas(int width, int height)
: INHERITED(width, height)
, fOverdrawViz(false)
, fClipVizColor(SK_ColorTRANSPARENT)
, fDrawGpuOpBounds(false) {
, fDrawGpuOpBounds(false)
, fnextDrawPictureLayerId(-1)
, fnextDrawImageRectLayerId(-1) {
// SkPicturePlayback uses the base-class' quickReject calls to cull clipped
// operations. This can lead to problems in the debugger which expects all
// the operations in the captured skp to appear in the debug canvas. To
@ -73,7 +84,9 @@ DebugCanvas::DebugCanvas(int width, int height)
this->INHERITED::onClipRect(large, kReplace_SkClipOp, kHard_ClipEdgeStyle);
}
DebugCanvas::DebugCanvas(SkIRect bounds) { DebugCanvas(bounds.width(), bounds.height()); }
DebugCanvas::DebugCanvas(SkIRect bounds) {
DebugCanvas(bounds.width(), bounds.height());
}
DebugCanvas::~DebugCanvas() { fCommandVector.deleteAll(); }
@ -94,7 +107,6 @@ void DebugCanvas::drawTo(SkCanvas* originalCanvas, int index, int m) {
SkRect windowRect = SkRect::MakeWH(SkIntToScalar(originalCanvas->getBaseLayerSize().width()),
SkIntToScalar(originalCanvas->getBaseLayerSize().height()));
originalCanvas->clear(SK_ColorTRANSPARENT);
originalCanvas->resetMatrix();
if (!windowRect.isEmpty()) {
originalCanvas->clipRect(windowRect, kReplace_SkClipOp);
@ -307,6 +319,23 @@ void DebugCanvas::didConcat(const SkMatrix& matrix) {
}
void DebugCanvas::onDrawAnnotation(const SkRect& rect, const char key[], SkData* value) {
// Parse layer-releated annotations added in SkiaPipeline.cpp and RenderNodeDrawable.cpp
// the format of the annotations is <Indicator|RenderNodeId>
SkTArray<SkString> tokens;
SkStrSplit(key, "|", kStrict_SkStrSplitMode, &tokens);
if (tokens.size() == 2) {
if (tokens[0].equals(kOffscreenLayerDraw)) {
// Indicates that the next drawPicture command contains the SkPicture to render the node
// at this id in an offscreen buffer.
fnextDrawPictureLayerId = std::stoi(tokens[1].c_str());
fnextDrawPictureDirtyRect = rect.roundOut();
return; // don't record it
} else if (tokens[0].equals(kSurfaceID)) {
// Indicates that the following drawImageRect should draw the offscreen buffer.
fnextDrawImageRectLayerId = std::stoi(tokens[1].c_str());
return; // don't record it
}
}
this->addDrawCommand(new DrawAnnotationCommand(rect, key, sk_ref_sp(value)));
}
@ -359,7 +388,22 @@ void DebugCanvas::onDrawImageRect(const SkImage* image,
const SkRect& dst,
const SkPaint* paint,
SrcRectConstraint constraint) {
this->addDrawCommand(new DrawImageRectCommand(image, src, dst, paint, constraint));
if (fnextDrawImageRectLayerId != -1 && fLayerManager) {
// This drawImageRect command would have drawn the offscreen buffer for a layer.
// On Android, we recorded an SkPicture of the commands that drew to the layer.
// To render the layer as it would have looked on the frame this DebugCanvas draws, we need
// to call fLayerManager->getLayerAsImage(id). This must be done just before
// drawTo(command), since it depends on the index into the layer's commands
// (managed by fLayerManager)
// Instead of adding a DrawImageRectCommand, we need a deferred command, that when
// executed, will call drawImageRect(fLayerManager->getLayerAsImage())
this->addDrawCommand(new DrawImageRectLayerCommand(
fLayerManager, fnextDrawImageRectLayerId, fFrame, src, dst, paint, constraint));
} else {
this->addDrawCommand(new DrawImageRectCommand(image, src, dst, paint, constraint));
}
// Reset expectation so next drawImageRect is not special.
fnextDrawImageRectLayerId = -1;
}
void DebugCanvas::onDrawImageNine(const SkImage* image,
@ -400,10 +444,16 @@ void DebugCanvas::onDrawRegion(const SkRegion& region, const SkPaint& paint) {
void DebugCanvas::onDrawPicture(const SkPicture* picture,
const SkMatrix* matrix,
const SkPaint* paint) {
this->addDrawCommand(new BeginDrawPictureCommand(picture, matrix, paint));
SkAutoCanvasMatrixPaint acmp(this, matrix, paint, picture->cullRect());
picture->playback(this);
this->addDrawCommand(new EndDrawPictureCommand(SkToBool(matrix) || SkToBool(paint)));
if (fnextDrawPictureLayerId != -1 && fLayerManager) {
fLayerManager->storeSkPicture(fnextDrawPictureLayerId, fFrame, sk_ref_sp(picture),
fnextDrawPictureDirtyRect);
} else {
this->addDrawCommand(new BeginDrawPictureCommand(picture, matrix, paint));
SkAutoCanvasMatrixPaint acmp(this, matrix, paint, picture->cullRect());
picture->playback(this);
this->addDrawCommand(new EndDrawPictureCommand(SkToBool(matrix) || SkToBool(paint)));
}
fnextDrawPictureLayerId = -1;
}
void DebugCanvas::onDrawPoints(PointMode mode,

View File

@ -16,11 +16,13 @@
#include "include/pathops/SkPathOps.h"
#include "include/private/SkTArray.h"
#include "tools/UrlDataManager.h"
#include "tools/debugger/DebugLayerManager.h"
#include "tools/debugger/DrawCommand.h"
class GrAuditTrail;
class SkNWayCanvas;
class SkPicture;
class DebugLayerManager;
class DebugCanvas : public SkCanvasVirtualEnforcer<SkCanvas> {
public:
@ -30,6 +32,21 @@ public:
~DebugCanvas() override;
/**
* Provide a DebugLayerManager for mskp files containing layer information
* when set this DebugCanvas will attempt to parse layer info from annotations.
* it will store layer pictures to the layer manager, and interpret some drawImageRects
* as layer draws, deferring to the layer manager for images.
* Provide a frame number that will be passed to all layer manager functions to identify this
* DebugCanvas.
*
* Used only in wasm debugger animations.
*/
void setLayerManagerAndFrame(DebugLayerManager* lm, int frame) {
fLayerManager = lm;
fFrame = frame;
}
/**
* Enable or disable overdraw visualization
*/
@ -54,6 +71,8 @@ public:
/**
Executes the draw calls up to the specified index.
Does not clear the canvas to transparent black first,
if needed, caller should do that first.
@param canvas The canvas being drawn to
@param index The index of the final command being executed
@param m an optional Mth gpu op to highlight, or -1
@ -207,6 +226,17 @@ private:
SkColor fClipVizColor;
bool fDrawGpuOpBounds;
// When not negative, indicates the render node id of the layer represented by the next
// drawPicture call.
int fnextDrawPictureLayerId = -1;
int fnextDrawImageRectLayerId = -1;
SkIRect fnextDrawPictureDirtyRect;
// may be null, in which case layer annotations are ignored.
DebugLayerManager* fLayerManager = nullptr;
// May be set when DebugCanvas is used in playing back an animation.
// Only used for passing to fLayerManager to identify itself.
int fFrame = -1;
/**
Adds the command to the class' vector of commands.
@param command The draw command for execution

View File

@ -0,0 +1,142 @@
/*
* Copyright 2019 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "tools/debugger/DebugLayerManager.h"
#include "include/core/SkImage.h"
#include "include/core/SkImageInfo.h"
#include "include/core/SkPicture.h"
#include "include/core/SkSurface.h"
#include "include/private/SkTHash.h"
#include "tools/debugger/DebugCanvas.h"
#include <memory>
#include <vector>
#include <tuple>
void DebugLayerManager::setCommand(int nodeId, int frame, int command) {
auto* drawEvent = fDraws.find({frame, nodeId});
if (!drawEvent) {
SkDebugf("Could not set command playhead for event {%d, %d}, it is not tracked by"
"DebugLayerManager.\n", frame, nodeId);
return;
}
const int count = drawEvent->debugCanvas->getSize();
drawEvent->command = command < count ? command : count - 1;
// Invalidate stored images that depended on this combination of node and frame.
// actually this does all of the events for this nodeId, but close enough.
auto relevantFrames = listFramesForNode(nodeId);
for (const auto& frame : relevantFrames) {
fDraws[{frame, nodeId}].image = nullptr;
}
}
void DebugLayerManager::storeSkPicture(int nodeId, int frame, sk_sp<SkPicture> picture,
SkIRect dirty) {
const LayerKey k = {frame, nodeId};
// Make debug canvas using bounds from SkPicture. This will be equal to whatever width and
// height were passed into SkPictureRecorder::beginRecording(w, h) which is the layer bounds.
const auto& layerBounds = picture->cullRect().roundOut();
auto debugCanvas = std::make_unique<DebugCanvas>(layerBounds);
// Must be set or they end up undefined due to cosmic rays, bad luck, etc.
debugCanvas->setOverdrawViz(false);
debugCanvas->setDrawGpuOpBounds(false);
debugCanvas->setClipVizColor(SK_ColorTRANSPARENT);
// Setting this allows a layer to contain another layer. TODO(nifong): write a test for this.
debugCanvas->setLayerManagerAndFrame(this, frame);
// Only draw picture to the debug canvas once.
debugCanvas->drawPicture(picture);
int numCommands = debugCanvas->getSize();
DrawEvent event = {
frame == 0 || dirty==layerBounds, // fullRedraw
nullptr, // image
std::move(debugCanvas), // debugCanvas
numCommands-1, // command
{layerBounds.height(), layerBounds.width()}, // layerBounds
};
fDraws.set(k, std::move(event));
keys.push_back(k);
}
sk_sp<SkImage> DebugLayerManager::getLayerAsImage(const int nodeId, const int frame) {
// What is the last frame having an SkPicture for this layer? call it frame N
// have cached image of it? if so, return it.
// if not, draw it at frame N by the following method:
// The picture at frame N could have been a full redraw, or it could have been clipped to a
// dirty region. In order to know what the layer looked like on this frame, we must draw every
// picture starting with the last full redraw, up to the last one before the current frame, since
// any of those previous draws could be showing through.
// list of frames this node was updated on.
auto relevantFrames = listFramesForNode(nodeId);
// find largest one not greater than `frame`.
uint32_t i = relevantFrames.size()-1;
while (relevantFrames[i] > frame) { i--; }
const int frameN = relevantFrames[i];
// Fetch the draw event
auto& drawEvent = fDraws[{frameN, nodeId}];
// if an image of this is cached, return it.
if (drawEvent.image) {
return drawEvent.image;
}
// when it's not cached, we'll have to render it in an offscreen surface.
// start at the last full redraw. (pick up counting backwards from above)
while (i>0 && !(fDraws[{relevantFrames[i], nodeId}].fullRedraw)) { i--; }
// The correct layer bounds can be obtained from any drawEvent on this layer.
// the color type and alpha type are chosen here to match wasm-skp-debugger/cpu.js which was
// chosen to match the capabilities of HTML canvas, which this ultimately has to be drawn into.
// TODO(nifong): introduce a method of letting the user choose the backend for this.
auto surface = SkSurface::MakeRaster(SkImageInfo::Make(drawEvent.layerBounds,
kRGBA_8888_SkColorType, kUnpremul_SkAlphaType, nullptr));
// draw everything from the last full redraw up to the current frame.
// other frames drawn are partial, meaning they were clipped to not completely cover the layer.
// count back up with i
auto* canvas = surface->getCanvas();
for (; i<relevantFrames.size() && relevantFrames[i]<=frameN; i++) {
auto& evt = fDraws[{relevantFrames[i], nodeId}];
evt.debugCanvas->drawTo(canvas, evt.command);
canvas->flush();
}
drawEvent.image = surface->makeImageSnapshot();
return drawEvent.image;
}
std::vector<DebugLayerManager::DrawEventSummary> DebugLayerManager::summarizeEvents(int frame) const {
std::vector<DrawEventSummary> result;
for (const auto& node : listNodesForFrame(frame)) {
auto* evt = fDraws.find({frame, node});
if (!evt) { continue; }
result.push_back({
node, evt->fullRedraw, evt->debugCanvas->getSize(),
evt->layerBounds.width(), evt->layerBounds.height()
});
}
return result;
}
std::vector<int> DebugLayerManager::listNodesForFrame(int frame) const {
std::vector<int> result;
for (const auto& key : keys) {
if (key.frame == frame) {
result.push_back(key.nodeId);
}
}
return result;
}
std::vector<int> DebugLayerManager::listFramesForNode(int nodeId) const {
std::vector<int> result;
for (const auto& key : keys) {
if (key.nodeId == nodeId) {
result.push_back(key.frame);
}
}
return result;
}

View File

@ -0,0 +1,126 @@
/*
* Copyright 2019 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef DEBUGLAYERMANAGER_H_
#define DEBUGLAYERMANAGER_H_
#include "include/core/SkImage.h"
#include "include/private/SkTHash.h"
#include "tools/debugger/DebugCanvas.h"
#include <vector>
// A class to assist in playing back and debugging an mskp file containing offscreen layer commands.
// Holds SkPictures necessary to draw layers in one or more DebugCanvases. During
// recording of the mskp file on Android, each layer had a RenderNode id, which is recorded with
// the layer's draw commands.
// Creates one surface (cpu only for now) for each layer, and renders
// pictures to it up to the requested command using a DebugCanvas.
// Animations are expected to, but may not always use a layer on more than frame.
// the layer may be drawn to more than once, and each different draw is saved for reconstructing the
// layer as it was on any frame. Draws may be partial, meaning their commands were clipped to not
// cover the entire layer.
// Clients may ask for a rendering of a given layer by it's RenderNode id and frame, and
// this class will return a rendering of how it looked on that frame.
// returning an SkImage snapshot of the internally managed surface.
class DebugCanvas;
class DebugLayerManager {
public:
DebugLayerManager() {}
// Store an SkPicture under a given nodeId (and under the currently set frame number)
// `dirty` is the recorded rect that was used to call androidFramework_setDeviceClipRestriction
// when the layer was drawn.
void storeSkPicture(int nodeId, int frame, sk_sp<SkPicture> picture, SkIRect dirty);
// Set's the command playback head for a given picture/draw event.
void setCommand(int nodeId, int frame, int command);
// getLayerAsImage draws the given layer as it would have looked on frame and returns an image.
// Though each picture can be played back in as many ways as there are commands, we will let
// that be determined by the user who sets an independent playhead for each draw event, tracked
// here, so it stays how they left it.
// For example: Say we are drawing a layer at frame 10.
// Frame 0: Layer was completely redrawn. By default we draw it to it's last command. We always
// save the result by (nodeId, frame)
// Frame 5: Layer was partially redrawn, and the user has inspected this draw event, leaving
// its command playhead at command 50/100. We have drew this at the time and save how
// the result looked (all of the commands at frame 0, then half of the commands in the
// partial draw at frame 5)
// Frame 10: Another partial redraw, un-altered, drawn on top of the result from frame 5. We
// return this as the image of how the layer should look on frame 10
// Frame 15: A full redraw
//
// If the user then comes along and moves the command playhead of the picture at frame 0,
// we invalidate the stored images for 0, 5, and 10, but we can leave 15 alone if we have it.
//
// Which leaves us with one less degree of freedom to think about when implementing this
// function: We can assume there is only one way to play back a given picture. :)
//
// The reason the public version of this function doesn't let you specify the frame, is that
// I expect DebugCanvas to call it, which doesn't know which frame it's rendering. The code in
// debugger_bindings.cpp does know, which it why I'm having it set the frame via setFrame(int)
sk_sp<SkImage> getLayerAsImage(const int nodeId, const int frame);
// Mean to be bindable by emscripted and returned to the javascript side
struct DrawEventSummary {
int nodeId;
bool fullRedraw;
int commandCount;
int layerWidth;
int layerHeight;
};
// Return a list summarizing the layer draw events on the current frame.
std::vector<DrawEventSummary> summarizeEvents(int frame) const;
// Return the list of node ids which have DrawEvents on the given frame
std::vector<int> listNodesForFrame(int frame) const;
// Return the list of frames on which the given node had DrawEvents.
std::vector<int> listFramesForNode(int nodeId) const;
private:
// This class is basically a map from (frame, node) to draw-event
// during recording, at the beginning of any frame, one or more layers could have been drawn on.
// every draw event was recorded, and when reading the mskp file they are stored and organized
// here.
struct LayerKey{
int frame; // frame of animation on which this event was recorded.
int nodeId; // the render node id of the layer which was drawn to.
bool operator==(const LayerKey& b) const {
return this->frame==b.frame && this->nodeId==b.nodeId;
}
};
struct DrawEvent {
// true the pic's clip equals the layer bounds.
bool fullRedraw;
// the saved result of how the layer looks on this frame.
// null if we don't have it.
sk_sp<SkImage> image;
// A debug canvas used for drawing this picture.
// the SkPicture itself isn't saved, since it's in the DebugCanvas.
std::unique_ptr<DebugCanvas> debugCanvas;
// the command index where the debugCanvas was left off.
int command;
// the size of the layer this drew into. redundant between multiple DrawEvents on the same
// layer but helpful.
SkISize layerBounds;
};
SkTHashMap<LayerKey, DrawEvent> fDraws;
// The list of all keys in the map above (it has no keys() method)
std::vector<LayerKey> keys;
};
#endif

View File

@ -28,6 +28,7 @@
#include "src/core/SkRectPriv.h"
#include "src/core/SkTextBlobPriv.h"
#include "src/core/SkWriteBuffer.h"
#include "tools/debugger/DebugLayerManager.h"
#include "tools/debugger/JsonWriteBuffer.h"
#define DEBUGCANVAS_ATTRIBUTE_COMMAND "command"
@ -125,6 +126,7 @@
#define DEBUGCANVAS_ATTRIBUTE_AMBIENTCOLOR "ambientColor"
#define DEBUGCANVAS_ATTRIBUTE_SPOTCOLOR "spotColor"
#define DEBUGCANVAS_ATTRIBUTE_LIGHTRADIUS "lightRadius"
#define DEBUGCANVAS_ATTRIBUTE_LAYERNODEID "layerNodeId"
#define DEBUGCANVAS_VERB_MOVE "move"
#define DEBUGCANVAS_VERB_LINE "line"
@ -225,6 +227,7 @@ const char* DrawCommand::GetCommandString(OpType type) {
case kDrawImageLattice_OpType: return "DrawImageLattice";
case kDrawImageNine_OpType: return "DrawImageNine";
case kDrawImageRect_OpType: return "DrawImageRect";
case kDrawImageRectLayer_OpType: return "DrawImageRectLayer";
case kDrawOval_OpType: return "DrawOval";
case kDrawPaint_OpType: return "DrawPaint";
case kDrawPatch_OpType: return "DrawPatch";
@ -1448,6 +1451,65 @@ void DrawImageRectCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataM
writer.appendString(DEBUGCANVAS_ATTRIBUTE_SHORTDESC, str_append(&desc, fDst)->c_str());
}
DrawImageRectLayerCommand::DrawImageRectLayerCommand(DebugLayerManager* layerManager,
const int nodeId,
const int frame,
const SkRect* src,
const SkRect& dst,
const SkPaint* paint,
SkCanvas::SrcRectConstraint constraint)
: INHERITED(kDrawImageRectLayer_OpType)
, fLayerManager(layerManager)
, fNodeId(nodeId)
, fFrame(frame)
, fSrc(src)
, fDst(dst)
, fPaint(paint)
, fConstraint(constraint) {}
void DrawImageRectLayerCommand::execute(SkCanvas* canvas) const {
sk_sp<SkImage> snapshot = fLayerManager->getLayerAsImage(fNodeId, fFrame);
canvas->legacy_drawImageRect(
snapshot.get(), fSrc.getMaybeNull(), fDst, fPaint.getMaybeNull(), fConstraint);
}
bool DrawImageRectLayerCommand::render(SkCanvas* canvas) const {
SkAutoCanvasRestore acr(canvas, true);
canvas->clear(0xFFFFFFFF);
xlate_and_scale_to_bounds(canvas, fDst);
this->execute(canvas);
return true;
}
void DrawImageRectLayerCommand::toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const {
INHERITED::toJSON(writer, urlDataManager);
// Don't append an image attribute here, the image can be rendered in as many different ways
// as there are commands in the layer, at least. the urlDataManager would save each one under
// a different URL.
// Append the node id, and the layer inspector of the debugger will know what to do with it.
writer.appendS64(DEBUGCANVAS_ATTRIBUTE_LAYERNODEID, fNodeId);
if (fSrc.isValid()) {
writer.appendName(DEBUGCANVAS_ATTRIBUTE_SRC);
MakeJsonRect(writer, *fSrc);
}
writer.appendName(DEBUGCANVAS_ATTRIBUTE_DST);
MakeJsonRect(writer, fDst);
if (fPaint.isValid()) {
writer.appendName(DEBUGCANVAS_ATTRIBUTE_PAINT);
MakeJsonPaint(writer, *fPaint, urlDataManager);
}
if (fConstraint == SkCanvas::kStrict_SrcRectConstraint) {
writer.appendBool(DEBUGCANVAS_ATTRIBUTE_STRICT, true);
}
SkString desc;
writer.appendString(DEBUGCANVAS_ATTRIBUTE_SHORTDESC, str_append(&desc, fDst)->c_str());
}
DrawImageNineCommand::DrawImageNineCommand(const SkImage* image,
const SkIRect& center,
const SkRect& dst,

View File

@ -23,6 +23,8 @@
#include "src/utils/SkJSONWriter.h"
#include "tools/UrlDataManager.h"
class DebugLayerManager;
class DrawCommand {
public:
enum OpType {
@ -43,6 +45,7 @@ public:
kDrawImageLattice_OpType,
kDrawImageNine_OpType,
kDrawImageRect_OpType,
kDrawImageRectLayer_OpType, // unique to DebugCanvas
kDrawOval_OpType,
kDrawArc_OpType,
kDrawPaint_OpType,
@ -369,6 +372,34 @@ private:
typedef DrawCommand INHERITED;
};
// Command for resolving the deferred SkImage representing an android layer
// Functions like DrawImageRect except it uses the saved UrlDataManager to resolve the image
// at the time execute() is called.
class DrawImageRectLayerCommand : public DrawCommand {
public:
DrawImageRectLayerCommand(DebugLayerManager* layerManager,
const int nodeId,
const int frame,
const SkRect* src,
const SkRect& dst,
const SkPaint* paint,
SkCanvas::SrcRectConstraint constraint);
void execute(SkCanvas* canvas) const override;
bool render(SkCanvas* canvas) const override;
void toJSON(SkJSONWriter& writer, UrlDataManager& urlDataManager) const override;
private:
DebugLayerManager* fLayerManager;
int fNodeId;
int fFrame;
SkTLazy<SkRect> fSrc;
SkRect fDst;
SkTLazy<SkPaint> fPaint;
SkCanvas::SrcRectConstraint fConstraint;
typedef DrawCommand INHERITED;
};
class DrawOvalCommand : public DrawCommand {
public:
DrawOvalCommand(const SkRect& oval, const SkPaint& paint);

View File

@ -71,7 +71,9 @@ SkCanvas* Request::getCanvas() {
sk_sp<SkData> Request::drawToPng(int n, int m) {
//fDebugCanvas->setOverdrawViz(true);
fDebugCanvas->drawTo(this->getCanvas(), n, m);
auto* canvas = this->getCanvas();
canvas->clear(SK_ColorTRANSPARENT);
fDebugCanvas->drawTo(canvas, n, m);
//fDebugCanvas->setOverdrawViz(false);
return writeCanvasToPng(this->getCanvas());
}