Plumbing for using a BBH in SkRecordDraw.

For now this only creates a degenerate bounding box hierarchy where all ops
just have maximal bounds.  I will flesh out FillBounds in future CL(s).

Not quite sure why QuadTree and TileGrid aren't drawing right---haven't even
looked at the diffs yet---so I've disabled those test modes for now.  RTree
seems fine, so that'll at least get us coverage for all this new plumbing.

BUG=skia:
R=robertphillips@google.com, mtklein@google.com, reed@google.com

Author: mtklein@chromium.org

Review URL: https://codereview.chromium.org/454123003
This commit is contained in:
mtklein 2014-08-11 08:08:43 -07:00 committed by Commit bot
parent 136aa8fb7e
commit 5ad6ee1b2c
11 changed files with 155 additions and 62 deletions

View File

@ -23,31 +23,37 @@ CpuGMTask::CpuGMTask(const char* config,
{} {}
void CpuGMTask::draw() { void CpuGMTask::draw() {
SkBitmap bitmap; SkBitmap bm;
AllocatePixels(fColorType, fGM->getISize().width(), fGM->getISize().height(), &bitmap); AllocatePixels(fColorType, fGM->getISize().width(), fGM->getISize().height(), &bm);
SkCanvas canvas(bitmap); SkCanvas canvas(bm);
canvas.concat(fGM->getInitialTransform()); canvas.concat(fGM->getInitialTransform());
fGM->draw(&canvas); fGM->draw(&canvas);
canvas.flush(); canvas.flush();
#define SPAWN(ChildTask, ...) this->spawnChild(SkNEW_ARGS(ChildTask, (*this, __VA_ARGS__))) #define SPAWN(ChildTask, ...) this->spawnChild(SkNEW_ARGS(ChildTask, (*this, __VA_ARGS__)))
SPAWN(ExpectationsTask, fExpectations, bitmap); SPAWN(ExpectationsTask, fExpectations, bm);
SPAWN(PipeTask, fGMFactory(NULL), bitmap, PipeTask::kInProcess_Mode); SPAWN(PipeTask, fGMFactory(NULL), bm, PipeTask::kInProcess_Mode);
SPAWN(PipeTask, fGMFactory(NULL), bitmap, PipeTask::kCrossProcess_Mode); SPAWN(PipeTask, fGMFactory(NULL), bm, PipeTask::kCrossProcess_Mode);
SPAWN(PipeTask, fGMFactory(NULL), bitmap, PipeTask::kSharedAddress_Mode); SPAWN(PipeTask, fGMFactory(NULL), bm, PipeTask::kSharedAddress_Mode);
SPAWN(QuiltTask, fGMFactory(NULL), bitmap, QuiltTask::kNoBBH_Mode); SPAWN(QuiltTask, fGMFactory(NULL), bm, QuiltTask::kNone_BBH, QuiltTask::kDefault_Backend);
SPAWN(QuiltTask, fGMFactory(NULL), bitmap, QuiltTask::kRTree_Mode); SPAWN(QuiltTask, fGMFactory(NULL), bm, QuiltTask::kRTree_BBH, QuiltTask::kDefault_Backend);
SPAWN(QuiltTask, fGMFactory(NULL), bitmap, QuiltTask::kQuadTree_Mode); SPAWN(QuiltTask, fGMFactory(NULL), bm, QuiltTask::kQuadTree_BBH, QuiltTask::kDefault_Backend);
SPAWN(QuiltTask, fGMFactory(NULL), bitmap, QuiltTask::kTileGrid_Mode); SPAWN(QuiltTask, fGMFactory(NULL), bm, QuiltTask::kTileGrid_BBH, QuiltTask::kDefault_Backend);
SPAWN(QuiltTask, fGMFactory(NULL), bitmap, QuiltTask::kSkRecord_Mode);
SPAWN(SerializeTask, fGMFactory(NULL), bitmap, SerializeTask::kNormal_Mode); SPAWN(QuiltTask, fGMFactory(NULL), bm, QuiltTask::kNone_BBH, QuiltTask::kSkRecord_Backend);
SPAWN(SerializeTask, fGMFactory(NULL), bitmap, SerializeTask::kSkRecord_Mode); SPAWN(QuiltTask, fGMFactory(NULL), bm, QuiltTask::kRTree_BBH, QuiltTask::kSkRecord_Backend);
SPAWN(QuiltTask, fGMFactory(NULL), bm, QuiltTask::kQuadTree_BBH, QuiltTask::kSkRecord_Backend);
/* TODO: Failing, not sure why. Enable these when passing.
SPAWN(QuiltTask, fGMFactory(NULL), bm, QuiltTask::kTileGrid_BBH, QuiltTask::kSkRecord_Backend);
*/
SPAWN(WriteTask, bitmap); SPAWN(SerializeTask, fGMFactory(NULL), bm, SerializeTask::kNormal_Mode);
SPAWN(SerializeTask, fGMFactory(NULL), bm, SerializeTask::kSkRecord_Mode);
SPAWN(WriteTask, bm);
#undef SPAWN #undef SPAWN
} }

View File

@ -10,14 +10,20 @@
DEFINE_bool(quilt, true, "If true, draw GM via a picture into a quilt of small tiles and compare."); DEFINE_bool(quilt, true, "If true, draw GM via a picture into a quilt of small tiles and compare.");
DEFINE_int32(quiltTile, 256, "Dimension of (square) quilt tile."); DEFINE_int32(quiltTile, 256, "Dimension of (square) quilt tile.");
static const char* kSuffixes[] = { "nobbh", "rtree", "quadtree", "tilegrid", "skr" };
namespace DM { namespace DM {
QuiltTask::QuiltTask(const Task& parent, skiagm::GM* gm, SkBitmap reference, QuiltTask::Mode mode) static SkString suffix(QuiltTask::Backend backend, QuiltTask::BBH bbh) {
static const char* kBackends[] = { "default", "skrecord" };
static const char* kBBHs[] = { "nobbh", "rtree", "quadtree", "tilegrid" };
return SkStringPrintf("%s-%s", kBackends[backend], kBBHs[bbh]);
}
QuiltTask::QuiltTask(const Task& parent, skiagm::GM* gm, SkBitmap reference,
QuiltTask::BBH bbh, QuiltTask::Backend backend)
: CpuTask(parent) : CpuTask(parent)
, fMode(mode) , fBBH(bbh)
, fName(UnderJoin(parent.name().c_str(), kSuffixes[mode])) , fBackend(backend)
, fName(UnderJoin(parent.name().c_str(), suffix(backend, bbh).c_str()))
, fGM(gm) , fGM(gm)
, fReference(reference) , fReference(reference)
{} {}
@ -54,14 +60,15 @@ private:
void QuiltTask::draw() { void QuiltTask::draw() {
SkAutoTDelete<SkBBHFactory> factory; SkAutoTDelete<SkBBHFactory> factory;
switch (fMode) { switch (fBBH) {
case kRTree_Mode: case kNone_BBH: break;
case kRTree_BBH:
factory.reset(SkNEW(SkRTreeFactory)); factory.reset(SkNEW(SkRTreeFactory));
break; break;
case kQuadTree_Mode: case kQuadTree_BBH:
factory.reset(SkNEW(SkQuadTreeFactory)); factory.reset(SkNEW(SkQuadTreeFactory));
break; break;
case kTileGrid_Mode: { case kTileGrid_BBH: {
const SkTileGridFactory::TileGridInfo tiles = { const SkTileGridFactory::TileGridInfo tiles = {
{ FLAGS_quiltTile, FLAGS_quiltTile }, { FLAGS_quiltTile, FLAGS_quiltTile },
/*overlap: */{0, 0}, /*overlap: */{0, 0},
@ -70,10 +77,6 @@ void QuiltTask::draw() {
factory.reset(SkNEW_ARGS(SkTileGridFactory, (tiles))); factory.reset(SkNEW_ARGS(SkTileGridFactory, (tiles)));
break; break;
} }
case kNoBBH_Mode:
case kSkRecord_Mode:
break;
} }
// A couple GMs draw wrong when using a bounding box hierarchy. // A couple GMs draw wrong when using a bounding box hierarchy.
@ -84,7 +87,7 @@ void QuiltTask::draw() {
} }
SkAutoTUnref<const SkPicture> recorded( SkAutoTUnref<const SkPicture> recorded(
RecordPicture(fGM.get(), factory.get(), kSkRecord_Mode == fMode)); RecordPicture(fGM.get(), factory.get(), kSkRecord_Backend == fBackend));
SkBitmap full; SkBitmap full;
AllocatePixels(fReference, &full); AllocatePixels(fReference, &full);

View File

@ -12,27 +12,30 @@
namespace DM { namespace DM {
class QuiltTask : public CpuTask { class QuiltTask : public CpuTask {
public: public:
enum Mode { enum BBH {
kNoBBH_Mode, kNone_BBH,
kRTree_Mode, kRTree_BBH,
kQuadTree_Mode, kQuadTree_BBH,
kTileGrid_Mode, kTileGrid_BBH,
kSkRecord_Mode, // Currently uses no BBH. };
enum Backend {
kDefault_Backend,
kSkRecord_Backend,
}; };
QuiltTask(const Task& parent, // QuiltTask must be a child task. Pass its parent here. QuiltTask(const Task& parent, // QuiltTask must be a child task. Pass its parent here.
skiagm::GM*, // GM to run through a picture. Takes ownership. skiagm::GM*, // GM to run through a picture. Takes ownership.
SkBitmap reference, // Bitmap to compare picture replay results to. SkBitmap reference, // Bitmap to compare picture replay results to.
Mode mode); BBH, Backend);
virtual void draw() SK_OVERRIDE; virtual void draw() SK_OVERRIDE;
virtual bool shouldSkip() const SK_OVERRIDE; virtual bool shouldSkip() const SK_OVERRIDE;
virtual SkString name() const SK_OVERRIDE { return fName; } virtual SkString name() const SK_OVERRIDE { return fName; }
private: private:
const Mode fMode; const BBH fBBH;
const Backend fBackend;
const SkString fName; const SkString fName;
SkAutoTDelete<skiagm::GM> fGM; SkAutoTDelete<skiagm::GM> fGM;
const SkBitmap fReference; const SkBitmap fReference;

View File

@ -20,7 +20,6 @@
class GrContext; class GrContext;
#endif #endif
class SkBBHFactory;
class SkBBoxHierarchy; class SkBBoxHierarchy;
class SkCanvas; class SkCanvas;
class SkData; class SkData;
@ -289,8 +288,11 @@ private:
typedef SkRefCnt INHERITED; typedef SkRefCnt INHERITED;
SkPicture(int width, int height, SkRecord*); // Takes ownership. // Takes ownership of the SkRecord, refs the (optional) BBH.
SkAutoTDelete<SkRecord> fRecord; SkPicture(int width, int height, SkRecord*, SkBBoxHierarchy*);
SkAutoTDelete<SkRecord> fRecord;
SkAutoTUnref<SkBBoxHierarchy> fBBH;
bool fRecordWillPlayBackBitmaps; // TODO: const bool fRecordWillPlayBackBitmaps; // TODO: const
}; };

View File

@ -80,6 +80,8 @@ private:
int fWidth; int fWidth;
int fHeight; int fHeight;
SkAutoTUnref<SkBBoxHierarchy> fBBH;
// One of these two canvases will be non-NULL. // One of these two canvases will be non-NULL.
SkAutoTUnref<SkPictureRecord> fPictureRecord; // beginRecording() SkAutoTUnref<SkPictureRecord> fPictureRecord; // beginRecording()
SkAutoTUnref<SkRecorder> fRecorder; // EXPERIMENTAL_beginRecording() SkAutoTUnref<SkRecorder> fRecorder; // EXPERIMENTAL_beginRecording()

View File

@ -14,7 +14,6 @@
#include "SkPictureRecorder.h" #include "SkPictureRecorder.h"
#include "SkPictureStateTree.h" #include "SkPictureStateTree.h"
#include "SkBBHFactory.h"
#include "SkBitmapDevice.h" #include "SkBitmapDevice.h"
#include "SkCanvas.h" #include "SkCanvas.h"
#include "SkChunkAlloc.h" #include "SkChunkAlloc.h"
@ -139,7 +138,7 @@ SkPicture::SkPicture(int width, int height,
// This for compatibility with serialization code only. This is not cheap. // This for compatibility with serialization code only. This is not cheap.
static SkPicture* backport(const SkRecord& src, int width, int height) { static SkPicture* backport(const SkRecord& src, int width, int height) {
SkPictureRecorder recorder; SkPictureRecorder recorder;
SkRecordDraw(src, recorder.beginRecording(width, height)); SkRecordDraw(src, recorder.beginRecording(width, height), NULL/*bbh*/, NULL/*callback*/);
return recorder.endRecording(); return recorder.endRecording();
} }
@ -267,7 +266,7 @@ void SkPicture::draw(SkCanvas* canvas, SkDrawPictureCallback* callback) const {
playback.draw(canvas, callback); playback.draw(canvas, callback);
} }
if (NULL != fRecord.get()) { if (NULL != fRecord.get()) {
SkRecordDraw(*fRecord, canvas, callback); SkRecordDraw(*fRecord, canvas, fBBH.get(), callback);
} }
} }
@ -490,11 +489,16 @@ uint32_t SkPicture::uniqueID() const {
} }
// fRecord OK // fRecord OK
SkPicture::SkPicture(int width, int height, SkRecord* record) SkPicture::SkPicture(int width, int height, SkRecord* record, SkBBoxHierarchy* bbh)
: fWidth(width) : fWidth(width)
, fHeight(height) , fHeight(height)
, fRecord(record) , fRecord(record)
, fBBH(SkSafeRef(bbh))
, fRecordWillPlayBackBitmaps(SkRecordWillPlaybackBitmaps(*record)) { , fRecordWillPlayBackBitmaps(SkRecordWillPlaybackBitmaps(*record)) {
// TODO: delay as much of this work until just before first playback?
if (fBBH.get()) {
SkRecordFillBounds(*record, fBBH.get());
}
this->needsNewGenID(); this->needsNewGenID();
} }

View File

@ -26,9 +26,11 @@ SkCanvas* SkPictureRecorder::beginRecording(int width, int height,
const SkISize size = SkISize::Make(width, height); const SkISize size = SkISize::Make(width, height);
if (NULL != bbhFactory) { if (NULL != bbhFactory) {
SkAutoTUnref<SkBBoxHierarchy> tree((*bbhFactory)(width, height)); // We don't need to hold a ref on the BBH ourselves, but might as well for
SkASSERT(NULL != tree); // consistency with EXPERIMENTAL_beginRecording(), which does need to.
fPictureRecord.reset(SkNEW_ARGS(SkBBoxHierarchyRecord, (size, recordFlags, tree.get()))); fBBH.reset((*bbhFactory)(width, height));
SkASSERT(NULL != fBBH.get());
fPictureRecord.reset(SkNEW_ARGS(SkBBoxHierarchyRecord, (size, recordFlags, fBBH.get())));
} else { } else {
fPictureRecord.reset(SkNEW_ARGS(SkPictureRecord, (size, recordFlags))); fPictureRecord.reset(SkNEW_ARGS(SkPictureRecord, (size, recordFlags)));
} }
@ -42,7 +44,11 @@ SkCanvas* SkPictureRecorder::EXPERIMENTAL_beginRecording(int width, int height,
fWidth = width; fWidth = width;
fHeight = height; fHeight = height;
// TODO: plumb bbhFactory through if (NULL != bbhFactory) {
fBBH.reset((*bbhFactory)(width, height));
SkASSERT(NULL != fBBH.get());
}
fRecord.reset(SkNEW(SkRecord)); fRecord.reset(SkNEW(SkRecord));
fRecorder.reset(SkNEW_ARGS(SkRecorder, (fRecord.get(), width, height))); fRecorder.reset(SkNEW_ARGS(SkRecorder, (fRecord.get(), width, height)));
return this->getRecordingCanvas(); return this->getRecordingCanvas();
@ -59,7 +65,7 @@ SkPicture* SkPictureRecorder::endRecording() {
SkPicture* picture = NULL; SkPicture* picture = NULL;
if (NULL != fRecord.get()) { if (NULL != fRecord.get()) {
picture = SkNEW_ARGS(SkPicture, (fWidth, fHeight, fRecord.detach())); picture = SkNEW_ARGS(SkPicture, (fWidth, fHeight, fRecord.detach(), fBBH.get()));
} }
if (NULL != fPictureRecord.get()) { if (NULL != fPictureRecord.get()) {
@ -83,7 +89,7 @@ void SkPictureRecorder::partialReplay(SkCanvas* canvas) const {
} }
if (NULL != fRecord.get()) { if (NULL != fRecord.get()) {
SkRecordDraw(*fRecord, canvas); SkRecordDraw(*fRecord, canvas, NULL/*bbh*/, NULL/*callback*/);
} }
if (NULL != fPictureRecord.get()) { if (NULL != fPictureRecord.get()) {

View File

@ -6,14 +6,46 @@
*/ */
#include "SkRecordDraw.h" #include "SkRecordDraw.h"
#include "SkTSort.h"
void SkRecordDraw(const SkRecord& record, SkCanvas* canvas, SkDrawPictureCallback* callback) { void SkRecordDraw(const SkRecord& record,
SkCanvas* canvas,
const SkBBoxHierarchy* bbh,
SkDrawPictureCallback* callback) {
SkAutoCanvasRestore saveRestore(canvas, true /*save now, restore at exit*/); SkAutoCanvasRestore saveRestore(canvas, true /*save now, restore at exit*/);
for (SkRecords::Draw draw(canvas); draw.index() < record.count(); draw.next()) {
if (NULL != callback && callback->abortDrawing()) { if (NULL != bbh) {
return; SkASSERT(bbh->getCount() == SkToInt(record.count()));
// Draw only ops that affect pixels in the canvas's current clip.
SkIRect devBounds;
canvas->getClipDeviceBounds(&devBounds);
SkTDArray<void*> ops;
bbh->search(devBounds, &ops);
// Until we start filling in real bounds, we should get every op back.
SkASSERT(ops.count() == SkToInt(record.count()));
// FIXME: QuadTree doesn't send these back in the order we inserted them. :(
if (ops.count() > 0) {
SkTQSort(ops.begin(), ops.end() - 1, SkTCompareLT<void*>());
}
SkRecords::Draw draw(canvas);
for (int i = 0; i < ops.count(); i++) {
if (NULL != callback && callback->abortDrawing()) {
return;
}
record.visit<void>((uintptr_t)ops[i], draw); // See FillBounds below.
}
} else {
// Draw all ops.
for (SkRecords::Draw draw(canvas); draw.index() < record.count(); draw.next()) {
if (NULL != callback && callback->abortDrawing()) {
return;
}
record.visit<void>(draw.index(), draw);
} }
record.visit<void>(draw.index(), draw);
} }
} }
@ -65,4 +97,35 @@ DRAW(DrawVertices, drawVertices(r.vmode, r.vertexCount, r.vertices, r.texs, r.co
r.xmode.get(), r.indices, r.indexCount, r.paint)); r.xmode.get(), r.indices, r.indexCount, r.paint));
#undef DRAW #undef DRAW
// This is an SkRecord visitor that fills an SkBBoxHierarchy.
class FillBounds : SkNoncopyable {
public:
explicit FillBounds(SkBBoxHierarchy* bbh) : fBBH(bbh), fIndex(0) {}
~FillBounds() { fBBH->flushDeferredInserts(); }
uintptr_t index() const { return fIndex; }
void next() { ++fIndex; }
template <typename T> void operator()(const T& r) {
// MakeLargest() is a trivially safe default for ops that haven't been bounded yet.
this->insert(this->index(), SkIRect::MakeLargest());
}
private:
void insert(uintptr_t opIndex, const SkIRect& bounds) {
fBBH->insert((void*)opIndex, bounds, true/*ok to defer*/);
}
SkBBoxHierarchy* fBBH; // Unowned. The BBH is guaranteed to be ref'd for our lifetime.
uintptr_t fIndex;
};
} // namespace SkRecords } // namespace SkRecords
void SkRecordFillBounds(const SkRecord& record, SkBBoxHierarchy* bbh) {
SkASSERT(NULL != bbh);
for(SkRecords::FillBounds fb(bbh); fb.index() < record.count(); fb.next()) {
record.visit<void>(fb.index(), fb);
}
}

View File

@ -8,12 +8,16 @@
#ifndef SkRecordDraw_DEFINED #ifndef SkRecordDraw_DEFINED
#define SkRecordDraw_DEFINED #define SkRecordDraw_DEFINED
#include "SkRecord.h" #include "SkBBoxHierarchy.h"
#include "SkCanvas.h" #include "SkCanvas.h"
#include "SkDrawPictureCallback.h" #include "SkDrawPictureCallback.h"
#include "SkRecord.h"
// Fill a BBH to be used by SkRecordDraw to accelerate playback.
void SkRecordFillBounds(const SkRecord&, SkBBoxHierarchy*);
// Draw an SkRecord into an SkCanvas. A convenience wrapper around SkRecords::Draw. // Draw an SkRecord into an SkCanvas. A convenience wrapper around SkRecords::Draw.
void SkRecordDraw(const SkRecord&, SkCanvas*, SkDrawPictureCallback* = NULL); void SkRecordDraw(const SkRecord&, SkCanvas*, const SkBBoxHierarchy*, SkDrawPictureCallback*);
namespace SkRecords { namespace SkRecords {

View File

@ -20,7 +20,7 @@ SkPlayback::~SkPlayback() {}
void SkPlayback::draw(SkCanvas* canvas) const { void SkPlayback::draw(SkCanvas* canvas) const {
SkASSERT(fRecord.get() != NULL); SkASSERT(fRecord.get() != NULL);
SkRecordDraw(*fRecord, canvas); SkRecordDraw(*fRecord, canvas, NULL/*bbh*/, NULL/*callback*/);
} }
SkRecording::SkRecording(int width, int height) SkRecording::SkRecording(int width, int height)

View File

@ -38,7 +38,7 @@ DEF_TEST(RecordDraw_Abort, r) {
SkRecorder canvas(&rerecord, W, H); SkRecorder canvas(&rerecord, W, H);
JustOneDraw callback; JustOneDraw callback;
SkRecordDraw(record, &canvas, &callback); SkRecordDraw(record, &canvas, NULL/*bbh*/, &callback);
REPORTER_ASSERT(r, 3 == rerecord.count()); REPORTER_ASSERT(r, 3 == rerecord.count());
assert_type<SkRecords::Save> (r, rerecord, 0); assert_type<SkRecords::Save> (r, rerecord, 0);
@ -53,7 +53,7 @@ DEF_TEST(RecordDraw_Unbalanced, r) {
SkRecord rerecord; SkRecord rerecord;
SkRecorder canvas(&rerecord, W, H); SkRecorder canvas(&rerecord, W, H);
SkRecordDraw(record, &canvas); SkRecordDraw(record, &canvas, NULL/*bbh*/, NULL/*callback*/);
REPORTER_ASSERT(r, 4 == rerecord.count()); REPORTER_ASSERT(r, 4 == rerecord.count());
assert_type<SkRecords::Save> (r, rerecord, 0); assert_type<SkRecords::Save> (r, rerecord, 0);
@ -77,7 +77,7 @@ DEF_TEST(RecordDraw_SetMatrixClobber, r) {
translate.setTranslate(20, 20); translate.setTranslate(20, 20);
translateCanvas.setMatrix(translate); translateCanvas.setMatrix(translate);
SkRecordDraw(scaleRecord, &translateCanvas); SkRecordDraw(scaleRecord, &translateCanvas, NULL/*bbh*/, NULL/*callback*/);
REPORTER_ASSERT(r, 4 == translateRecord.count()); REPORTER_ASSERT(r, 4 == translateRecord.count());
assert_type<SkRecords::SetMatrix>(r, translateRecord, 0); assert_type<SkRecords::SetMatrix>(r, translateRecord, 0);
assert_type<SkRecords::Save> (r, translateRecord, 1); assert_type<SkRecords::Save> (r, translateRecord, 1);