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

View File

@ -13,7 +13,12 @@
#include "tools/SkSharingProc.h" #include "tools/SkSharingProc.h"
#include "tools/UrlDataManager.h" #include "tools/UrlDataManager.h"
#include "tools/debugger/DebugCanvas.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.h>
#include <emscripten/bind.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); SkDebugf("Constrained command index (%d) within this frame's length (%d)\n", index, cmdlen);
index = cmdlen-1; index = cmdlen-1;
} }
auto* canvas = surface->getCanvas();
canvas->clear(SK_ColorTRANSPARENT);
frames[fp]->drawTo(surface->getCanvas(), index); frames[fp]->drawTo(surface->getCanvas(), index);
surface->getCanvas()->flush(); 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; } const SkIRect& getBounds() { return fBounds; }
// The following three operations apply to every frame because they are overdraw features. // 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 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: private:
// Loads a single frame (traditional) skp file from the provided data stream and returns // 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"); SkDebugf("Parsed SKP file.\n");
// Make debug canvas using bounds from SkPicture // Make debug canvas using bounds from SkPicture
fBounds = picture->cullRect().roundOut(); fBounds = picture->cullRect().roundOut();
std::unique_ptr<DebugCanvas> debugDanvas = std::make_unique<DebugCanvas>(fBounds); std::unique_ptr<DebugCanvas> debugCanvas = std::make_unique<DebugCanvas>(fBounds);
SkDebugf("DebugCanvas created.\n");
// Only draw picture to the debug canvas once. // Only draw picture to the debug canvas once.
debugDanvas->drawPicture(picture); debugCanvas->drawPicture(picture);
SkDebugf("Added picture with %d commands.\n", debugDanvas->getSize()); return debugCanvas;
return debugDanvas;
} }
void loadMultiFrame(SkMemoryStream* stream) { 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. int page_count = SkMultiPictureDocumentReadPageCount(stream);
auto deserialContext = std::make_unique<SkSharingDeserialContext>(); if (!page_count) {
SkDeserialProcs procs; SkDebugf("Not a MultiPictureDocument");
procs.fImageProc = SkSharingDeserialContext::deserializeImage; return;
procs.fImageCtx = deserialContext.get(); }
SkDebugf("Expecting %d frames\n", page_count);
int page_count = SkMultiPictureDocumentReadPageCount(stream); std::vector<SkDocumentPage> pages(page_count);
if (!page_count) { if (!SkMultiPictureDocumentRead(stream, pages.data(), page_count, &procs)) {
SkDebugf("Not a MultiPictureDocument"); SkDebugf("Reading frames from MultiPictureDocument failed");
return; 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); // If you don't set these, they're undefined.
debugCanvas->setOverdrawViz(false);
std::vector<SkDocumentPage> pages(page_count); debugCanvas->setDrawGpuOpBounds(false);
if (!SkMultiPictureDocumentRead(stream, pages.data(), page_count, &procs)) { debugCanvas->setClipVizColor(SK_ColorTRANSPARENT);
SkDebugf("Reading frames from MultiPictureDocument failed"); frames.push_back(std::move(debugCanvas));
return; }
} fImages = deserialContext->fImages;
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;
} }
// A vector of DebugCanvas, each one initialized to a frame of the animation. // 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. // find anything. TODO(nifong): Unify these two numbering schemes in CollatingCanvas.
UrlDataManager udm; 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 #if SK_SUPPORT_GPU
@ -356,6 +383,7 @@ EMSCRIPTEN_BINDINGS(my_module) {
.constructor<>() .constructor<>()
.function("loadSkp", &SkpDebugPlayer::loadSkp, allow_raw_pointers()) .function("loadSkp", &SkpDebugPlayer::loadSkp, allow_raw_pointers())
.function("drawTo", &SkpDebugPlayer::drawTo, allow_raw_pointers()) .function("drawTo", &SkpDebugPlayer::drawTo, allow_raw_pointers())
.function("draw", &SkpDebugPlayer::draw, allow_raw_pointers())
.function("getBounds", &SkpDebugPlayer::getBounds) .function("getBounds", &SkpDebugPlayer::getBounds)
.function("setOverdrawVis", &SkpDebugPlayer::setOverdrawVis) .function("setOverdrawVis", &SkpDebugPlayer::setOverdrawVis)
.function("setClipVizColor", &SkpDebugPlayer::setClipVizColor) .function("setClipVizColor", &SkpDebugPlayer::setClipVizColor)
@ -369,7 +397,8 @@ EMSCRIPTEN_BINDINGS(my_module) {
.function("getFrameCount", &SkpDebugPlayer::getFrameCount) .function("getFrameCount", &SkpDebugPlayer::getFrameCount)
.function("getImageResource", &SkpDebugPlayer::getImageResource) .function("getImageResource", &SkpDebugPlayer::getImageResource)
.function("getImageCount", &SkpDebugPlayer::getImageCount) .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 // Structs used as arguments or returns to the functions above
value_object<SkIRect>("SkIRect") value_object<SkIRect>("SkIRect")
@ -377,6 +406,15 @@ EMSCRIPTEN_BINDINGS(my_module) {
.field("fTop", &SkIRect::fTop) .field("fTop", &SkIRect::fTop)
.field("fRight", &SkIRect::fRight) .field("fRight", &SkIRect::fRight)
.field("fBottom", &SkIRect::fBottom); .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. // Symbols needed by cpu.js to perform surface creation and flushing.
enum_<SkColorType>("ColorType") enum_<SkColorType>("ColorType")

View File

@ -50,6 +50,7 @@ tests_sources = [
"$_tests/ColorTest.cpp", "$_tests/ColorTest.cpp",
"$_tests/CopySurfaceTest.cpp", "$_tests/CopySurfaceTest.cpp",
"$_tests/CubicMapTest.cpp", "$_tests/CubicMapTest.cpp",
"$_tests/DebugLayerManagerTest.cpp",
"$_tests/DashPathEffectTest.cpp", "$_tests/DashPathEffectTest.cpp",
"$_tests/DataRefTest.cpp", "$_tests/DataRefTest.cpp",
"$_tests/DefaultPathRendererTest.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/core/SkRectPriv.h"
#include "src/utils/SkJSONWriter.h" #include "src/utils/SkJSONWriter.h"
#include "tools/debugger/DebugCanvas.h" #include "tools/debugger/DebugCanvas.h"
#include "tools/debugger/DebugLayerManager.h"
#include "tools/debugger/DrawCommand.h" #include "tools/debugger/DrawCommand.h"
#include "include/gpu/GrContext.h" #include "include/gpu/GrContext.h"
@ -20,11 +21,19 @@
#include "src/gpu/GrContextPriv.h" #include "src/gpu/GrContextPriv.h"
#include "src/gpu/GrRenderTargetContext.h" #include "src/gpu/GrRenderTargetContext.h"
#include <string>
#define SKDEBUGCANVAS_VERSION 1 #define SKDEBUGCANVAS_VERSION 1
#define SKDEBUGCANVAS_ATTRIBUTE_VERSION "version" #define SKDEBUGCANVAS_ATTRIBUTE_VERSION "version"
#define SKDEBUGCANVAS_ATTRIBUTE_COMMANDS "commands" #define SKDEBUGCANVAS_ATTRIBUTE_COMMANDS "commands"
#define SKDEBUGCANVAS_ATTRIBUTE_AUDITTRAIL "auditTrail" #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 { class DebugPaintFilterCanvas : public SkPaintFilterCanvas {
public: public:
DebugPaintFilterCanvas(SkCanvas* canvas) : INHERITED(canvas) {} DebugPaintFilterCanvas(SkCanvas* canvas) : INHERITED(canvas) {}
@ -53,7 +62,9 @@ DebugCanvas::DebugCanvas(int width, int height)
: INHERITED(width, height) : INHERITED(width, height)
, fOverdrawViz(false) , fOverdrawViz(false)
, fClipVizColor(SK_ColorTRANSPARENT) , fClipVizColor(SK_ColorTRANSPARENT)
, fDrawGpuOpBounds(false) { , fDrawGpuOpBounds(false)
, fnextDrawPictureLayerId(-1)
, fnextDrawImageRectLayerId(-1) {
// SkPicturePlayback uses the base-class' quickReject calls to cull clipped // SkPicturePlayback uses the base-class' quickReject calls to cull clipped
// operations. This can lead to problems in the debugger which expects all // 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 // 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); 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(); } 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()), SkRect windowRect = SkRect::MakeWH(SkIntToScalar(originalCanvas->getBaseLayerSize().width()),
SkIntToScalar(originalCanvas->getBaseLayerSize().height())); SkIntToScalar(originalCanvas->getBaseLayerSize().height()));
originalCanvas->clear(SK_ColorTRANSPARENT);
originalCanvas->resetMatrix(); originalCanvas->resetMatrix();
if (!windowRect.isEmpty()) { if (!windowRect.isEmpty()) {
originalCanvas->clipRect(windowRect, kReplace_SkClipOp); 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) { 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))); this->addDrawCommand(new DrawAnnotationCommand(rect, key, sk_ref_sp(value)));
} }
@ -359,7 +388,22 @@ void DebugCanvas::onDrawImageRect(const SkImage* image,
const SkRect& dst, const SkRect& dst,
const SkPaint* paint, const SkPaint* paint,
SrcRectConstraint constraint) { 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, 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, void DebugCanvas::onDrawPicture(const SkPicture* picture,
const SkMatrix* matrix, const SkMatrix* matrix,
const SkPaint* paint) { const SkPaint* paint) {
this->addDrawCommand(new BeginDrawPictureCommand(picture, matrix, paint)); if (fnextDrawPictureLayerId != -1 && fLayerManager) {
SkAutoCanvasMatrixPaint acmp(this, matrix, paint, picture->cullRect()); fLayerManager->storeSkPicture(fnextDrawPictureLayerId, fFrame, sk_ref_sp(picture),
picture->playback(this); fnextDrawPictureDirtyRect);
this->addDrawCommand(new EndDrawPictureCommand(SkToBool(matrix) || SkToBool(paint))); } 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, void DebugCanvas::onDrawPoints(PointMode mode,

View File

@ -16,11 +16,13 @@
#include "include/pathops/SkPathOps.h" #include "include/pathops/SkPathOps.h"
#include "include/private/SkTArray.h" #include "include/private/SkTArray.h"
#include "tools/UrlDataManager.h" #include "tools/UrlDataManager.h"
#include "tools/debugger/DebugLayerManager.h"
#include "tools/debugger/DrawCommand.h" #include "tools/debugger/DrawCommand.h"
class GrAuditTrail; class GrAuditTrail;
class SkNWayCanvas; class SkNWayCanvas;
class SkPicture; class SkPicture;
class DebugLayerManager;
class DebugCanvas : public SkCanvasVirtualEnforcer<SkCanvas> { class DebugCanvas : public SkCanvasVirtualEnforcer<SkCanvas> {
public: public:
@ -30,6 +32,21 @@ public:
~DebugCanvas() override; ~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 * Enable or disable overdraw visualization
*/ */
@ -54,6 +71,8 @@ public:
/** /**
Executes the draw calls up to the specified index. 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 canvas The canvas being drawn to
@param index The index of the final command being executed @param index The index of the final command being executed
@param m an optional Mth gpu op to highlight, or -1 @param m an optional Mth gpu op to highlight, or -1
@ -207,6 +226,17 @@ private:
SkColor fClipVizColor; SkColor fClipVizColor;
bool fDrawGpuOpBounds; 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. Adds the command to the class' vector of commands.
@param command The draw command for execution @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/SkRectPriv.h"
#include "src/core/SkTextBlobPriv.h" #include "src/core/SkTextBlobPriv.h"
#include "src/core/SkWriteBuffer.h" #include "src/core/SkWriteBuffer.h"
#include "tools/debugger/DebugLayerManager.h"
#include "tools/debugger/JsonWriteBuffer.h" #include "tools/debugger/JsonWriteBuffer.h"
#define DEBUGCANVAS_ATTRIBUTE_COMMAND "command" #define DEBUGCANVAS_ATTRIBUTE_COMMAND "command"
@ -125,6 +126,7 @@
#define DEBUGCANVAS_ATTRIBUTE_AMBIENTCOLOR "ambientColor" #define DEBUGCANVAS_ATTRIBUTE_AMBIENTCOLOR "ambientColor"
#define DEBUGCANVAS_ATTRIBUTE_SPOTCOLOR "spotColor" #define DEBUGCANVAS_ATTRIBUTE_SPOTCOLOR "spotColor"
#define DEBUGCANVAS_ATTRIBUTE_LIGHTRADIUS "lightRadius" #define DEBUGCANVAS_ATTRIBUTE_LIGHTRADIUS "lightRadius"
#define DEBUGCANVAS_ATTRIBUTE_LAYERNODEID "layerNodeId"
#define DEBUGCANVAS_VERB_MOVE "move" #define DEBUGCANVAS_VERB_MOVE "move"
#define DEBUGCANVAS_VERB_LINE "line" #define DEBUGCANVAS_VERB_LINE "line"
@ -225,6 +227,7 @@ const char* DrawCommand::GetCommandString(OpType type) {
case kDrawImageLattice_OpType: return "DrawImageLattice"; case kDrawImageLattice_OpType: return "DrawImageLattice";
case kDrawImageNine_OpType: return "DrawImageNine"; case kDrawImageNine_OpType: return "DrawImageNine";
case kDrawImageRect_OpType: return "DrawImageRect"; case kDrawImageRect_OpType: return "DrawImageRect";
case kDrawImageRectLayer_OpType: return "DrawImageRectLayer";
case kDrawOval_OpType: return "DrawOval"; case kDrawOval_OpType: return "DrawOval";
case kDrawPaint_OpType: return "DrawPaint"; case kDrawPaint_OpType: return "DrawPaint";
case kDrawPatch_OpType: return "DrawPatch"; 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()); 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, DrawImageNineCommand::DrawImageNineCommand(const SkImage* image,
const SkIRect& center, const SkIRect& center,
const SkRect& dst, const SkRect& dst,

View File

@ -23,6 +23,8 @@
#include "src/utils/SkJSONWriter.h" #include "src/utils/SkJSONWriter.h"
#include "tools/UrlDataManager.h" #include "tools/UrlDataManager.h"
class DebugLayerManager;
class DrawCommand { class DrawCommand {
public: public:
enum OpType { enum OpType {
@ -43,6 +45,7 @@ public:
kDrawImageLattice_OpType, kDrawImageLattice_OpType,
kDrawImageNine_OpType, kDrawImageNine_OpType,
kDrawImageRect_OpType, kDrawImageRect_OpType,
kDrawImageRectLayer_OpType, // unique to DebugCanvas
kDrawOval_OpType, kDrawOval_OpType,
kDrawArc_OpType, kDrawArc_OpType,
kDrawPaint_OpType, kDrawPaint_OpType,
@ -369,6 +372,34 @@ private:
typedef DrawCommand INHERITED; 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 { class DrawOvalCommand : public DrawCommand {
public: public:
DrawOvalCommand(const SkRect& oval, const SkPaint& paint); 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) { sk_sp<SkData> Request::drawToPng(int n, int m) {
//fDebugCanvas->setOverdrawViz(true); //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); //fDebugCanvas->setOverdrawViz(false);
return writeCanvasToPng(this->getCanvas()); return writeCanvasToPng(this->getCanvas());
} }