First pass at pre-rendering saveLayers for GPU

https://codereview.chromium.org/261663003/



git-svn-id: http://skia.googlecode.com/svn/trunk@14632 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
robertphillips@google.com 2014-05-07 21:31:09 +00:00
Родитель c71da1f6ed
Коммит beb1af2f34
8 изменённых файлов: 413 добавлений и 46 удалений

Просмотреть файл

@ -477,6 +477,7 @@ private:
friend class SkPicturePlayback;
friend class SkPictureRecorder;
friend class SkGpuDevice;
friend class GrGatherCanvas;
friend class GrGatherDevice;
friend class SkDebugCanvas;

Просмотреть файл

@ -23,6 +23,33 @@ template <typename T> int SafeCount(const T* obj) {
*/
#define SPEW_CLIP_SKIPPINGx
SkPicturePlayback::PlaybackReplacements::ReplacementInfo*
SkPicturePlayback::PlaybackReplacements::push() {
SkDEBUGCODE(this->validate());
return fReplacements.push();
}
void SkPicturePlayback::PlaybackReplacements::freeAll() {
for (int i = 0; i < fReplacements.count(); ++i) {
SkDELETE(fReplacements[i].fBM);
}
fReplacements.reset();
}
#ifdef SK_DEBUG
void SkPicturePlayback::PlaybackReplacements::validate() const {
// Check that the ranges are monotonically increasing and non-overlapping
if (fReplacements.count() > 0) {
SkASSERT(fReplacements[0].fStart < fReplacements[0].fStop);
for (int i = 1; i < fReplacements.count(); ++i) {
SkASSERT(fReplacements[i].fStart < fReplacements[i].fStop);
SkASSERT(fReplacements[i-1].fStop < fReplacements[i].fStart);
}
}
}
#endif
SkPicturePlayback::SkPicturePlayback(const SkPicture* picture, const SkPictInfo& info)
: fPicture(picture)
, fInfo(info) {
@ -205,6 +232,10 @@ void SkPicturePlayback::init() {
fStateTree = NULL;
fCachedActiveOps = NULL;
fCurOffset = 0;
fUseBBH = true;
fStart = 0;
fStop = 0;
fReplacements = NULL;
}
SkPicturePlayback::~SkPicturePlayback() {
@ -744,6 +775,21 @@ private:
SkPicturePlayback* fPlayback;
};
// TODO: Replace with hash or pass in "lastLookedUp" hint
SkPicturePlayback::PlaybackReplacements::ReplacementInfo*
SkPicturePlayback::PlaybackReplacements::lookupByStart(size_t start) {
SkDEBUGCODE(this->validate());
for (int i = 0; i < fReplacements.count(); ++i) {
if (start == fReplacements[i].fStart) {
return &fReplacements[i];
} else if (start < fReplacements[i].fStart) {
return NULL; // the ranges are monotonically increasing and non-overlapping
}
}
return NULL;
}
void SkPicturePlayback::draw(SkCanvas& canvas, SkDrawPictureCallback* callback) {
SkAutoResetOpID aroi(this);
SkASSERT(0 == fCurOffset);
@ -769,7 +815,10 @@ void SkPicturePlayback::draw(SkCanvas& canvas, SkDrawPictureCallback* callback)
TextContainer text;
const SkTDArray<void*>* activeOps = NULL;
if (NULL != fStateTree && NULL != fBoundingHierarchy) {
// When draw limits are enabled (i.e., 0 != fStart || 0 != fStop) the state
// tree isn't used to pick and choose the draw operations
if (0 == fStart && 0 == fStop) {
if (fUseBBH && NULL != fStateTree && NULL != fBoundingHierarchy) {
SkRect clipBounds;
if (canvas.getClipBounds(&clipBounds)) {
SkIRect query;
@ -786,11 +835,20 @@ void SkPicturePlayback::draw(SkCanvas& canvas, SkDrawPictureCallback* callback)
}
}
}
}
SkPictureStateTree::Iterator it = (NULL == activeOps) ?
SkPictureStateTree::Iterator() :
fStateTree->getIterator(*activeOps, &canvas);
if (0 != fStart || 0 != fStop) {
reader.setOffset(fStart);
uint32_t size;
SkDEBUGCODE(DrawType op =) read_op_and_size(&reader, &size);
SkASSERT(SAVE_LAYER == op);
reader.setOffset(fStart+size);
}
if (it.isValid()) {
uint32_t skipTo = it.nextDraw();
if (kDrawComplete == skipTo) {
@ -821,6 +879,60 @@ void SkPicturePlayback::draw(SkCanvas& canvas, SkDrawPictureCallback* callback)
return;
}
#endif
if (0 != fStart || 0 != fStop) {
size_t offset = reader.offset() ;
if (offset >= fStop) {
uint32_t size;
SkDEBUGCODE(DrawType op =) read_op_and_size(&reader, &size);
SkASSERT(RESTORE == op);
return;
}
}
if (NULL != fReplacements) {
// Potentially replace a block of operations with a single drawBitmap call
SkPicturePlayback::PlaybackReplacements::ReplacementInfo* temp =
fReplacements->lookupByStart(reader.offset());
if (NULL != temp) {
SkASSERT(NULL != temp->fBM);
SkASSERT(NULL != temp->fPaint);
canvas.drawBitmap(*temp->fBM, temp->fPos.fX, temp->fPos.fY, temp->fPaint);
if (it.isValid()) {
// This save is needed since the BBH will automatically issue
// a restore to balanced the saveLayer we're skipping
canvas.save();
// Note: This skipping only works if the client only issues
// well behaved saveLayer calls (i.e., doesn't use
// kMatrix_SaveFlag or kClip_SaveFlag in isolation)
// At this point we know that the PictureStateTree was aiming
// for some draw op within temp's saveLayer (although potentially
// in a separate saveLayer nested inside it).
// We need to skip all the operations inside temp's range
// along with all the associated state changes but update
// the state tree to the first operation outside temp's range.
SkASSERT(it.peekDraw() >= temp->fStart && it.peekDraw() <= temp->fStop);
while (kDrawComplete != it.peekDraw() && it.peekDraw() <= temp->fStop) {
it.skipDraw();
}
if (kDrawComplete == it.peekDraw()) {
break;
}
uint32_t skipTo = it.nextDraw();
reader.setOffset(skipTo);
} else {
reader.setOffset(temp->fStop);
uint32_t size;
SkDEBUGCODE(DrawType op =) read_op_and_size(&reader, &size);
SkASSERT(RESTORE == op);
}
continue;
}
}
#ifdef SPEW_CLIP_SKIPPING
opCount++;
@ -915,8 +1027,7 @@ void SkPicturePlayback::draw(SkCanvas& canvas, SkDrawPictureCallback* callback)
SkRegion::Op regionOp = ClipParams_unpackRegionOp(packed);
bool doAA = ClipParams_unpackDoAA(packed);
size_t offsetToRestore = reader.readInt();
SkASSERT(!offsetToRestore || \
offsetToRestore >= reader.offset());
SkASSERT(!offsetToRestore || offsetToRestore >= reader.offset());
canvas.clipRRect(rrect, regionOp, doAA);
if (canvas.isClipEmpty() && offsetToRestore) {
#ifdef SPEW_CLIP_SKIPPING

Просмотреть файл

@ -5,6 +5,7 @@
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkPicturePlayback_DEFINED
#define SkPicturePlayback_DEFINED
@ -91,6 +92,8 @@ public:
const SkPicture::OperationList& getActiveOps(const SkIRect& queryRect);
void setUseBBH(bool useBBH) { fUseBBH = useBBH; }
void draw(SkCanvas& canvas, SkDrawPictureCallback*);
void serialize(SkWStream*, SkPicture::EncodeBitmap) const;
@ -227,6 +230,7 @@ private: // these help us with reading/writing
private:
friend class SkPicture;
friend class SkGpuDevice; // for access to setDrawLimits & setReplacements
// The picture that owns this SkPicturePlayback object
const SkPicture* fPicture;
@ -248,6 +252,68 @@ private:
SkBBoxHierarchy* fBoundingHierarchy;
SkPictureStateTree* fStateTree;
// Limit the opcode playback to be between the offsets 'start' and 'stop'.
// The opcode at 'start' should be a saveLayer while the opcode at
// 'stop' should be a restore. Neither of those commands will be issued.
// Set both start & stop to 0 to disable draw limiting
// Draw limiting cannot be enabled at the same time as draw replacing
void setDrawLimits(size_t start, size_t stop) {
SkASSERT(NULL == fReplacements);
fStart = start;
fStop = stop;
}
// PlaybackReplacements collects op ranges that can be replaced with
// a single drawBitmap call (using a precomputed bitmap).
class PlaybackReplacements {
public:
// All the operations between fStart and fStop (inclusive) will be replaced with
// a single drawBitmap call using fPos, fBM and fPaint.
// fPaint will be NULL if the picture's paint wasn't copyable
struct ReplacementInfo {
size_t fStart;
size_t fStop;
SkPoint fPos;
SkBitmap* fBM;
const SkPaint* fPaint; // Note: this object doesn't own the paint
};
~PlaybackReplacements() { this->freeAll(); }
// Add a new replacement range. The replacement ranges should be
// sorted in increasing order and non-overlapping (esp. no nested
// saveLayers).
ReplacementInfo* push();
private:
friend class SkPicturePlayback; // for access to lookupByStart
// look up a replacement range by its start offset
ReplacementInfo* lookupByStart(size_t start);
void freeAll();
#ifdef SK_DEBUG
void validate() const;
#endif
SkTDArray<ReplacementInfo> fReplacements;
};
// Replace all the draw ops in the replacement ranges in 'replacements' with
// the associated drawBitmap call
// Draw replacing cannot be enabled at the same time as draw limiting
void setReplacements(PlaybackReplacements* replacements) {
SkASSERT(fStart == 0 && fStop == 0);
fReplacements = replacements;
}
bool fUseBBH;
size_t fStart;
size_t fStop;
PlaybackReplacements* fReplacements;
class CachedOperationList : public SkPicture::OperationList {
public:
CachedOperationList() {

Просмотреть файл

@ -114,9 +114,36 @@ void SkPictureStateTree::Iterator::setCurrentMatrix(const SkMatrix* matrix) {
fCurrentMatrix = matrix;
}
uint32_t SkPictureStateTree::Iterator::nextDraw() {
uint32_t SkPictureStateTree::Iterator::peekDraw() {
SkASSERT(this->isValid());
if (fPlaybackIndex >= fDraws->count()) {
return kDrawComplete;
}
Draw* draw = static_cast<Draw*>((*fDraws)[fPlaybackIndex]);
return draw->fOffset;
}
uint32_t SkPictureStateTree::Iterator::skipDraw() {
SkASSERT(this->isValid());
if (fPlaybackIndex >= fDraws->count()) {
return this->finish();
}
Draw* draw = static_cast<Draw*>((*fDraws)[fPlaybackIndex]);
if (fSave) {
fCanvas->save();
fSave = false;
}
fNodes.rewind();
++fPlaybackIndex;
return draw->fOffset;
}
uint32_t SkPictureStateTree::Iterator::finish() {
if (fCurrentNode->fFlags & Node::kSaveLayer_Flag) {
fCanvas->restore();
}
@ -135,6 +162,12 @@ uint32_t SkPictureStateTree::Iterator::nextDraw() {
fCanvas->setMatrix(fPlaybackMatrix);
fCurrentMatrix = NULL;
return kDrawComplete;
}
uint32_t SkPictureStateTree::Iterator::nextDraw() {
SkASSERT(this->isValid());
if (fPlaybackIndex >= fDraws->count()) {
return this->finish();
}
Draw* draw = static_cast<Draw*>((*fDraws)[fPlaybackIndex]);

Просмотреть файл

@ -74,8 +74,29 @@ public:
*/
class Iterator {
public:
/** Returns the next offset into the picture stream, or kDrawComplete if complete. */
/** Returns the next op offset needed to create the drawing state
required by the queued up draw operation or the offset of the queued
up draw operation itself. In the latter case, the next draw operation
will move into the queued up slot.
It retuns kDrawComplete when done.
TODO: this might be better named nextOp
*/
uint32_t nextDraw();
/** Peek at the currently queued up draw op's offset. Note that this can
be different then what 'nextDraw' would return b.c. it is
the offset of the next _draw_ op while 'nextDraw' can return
the offsets to saveLayer and clip ops while it is creating the proper
drawing context for the queued up draw op.
*/
uint32_t peekDraw();
/** Stop trying to create the drawing context for the currently queued
up _draw_ operation and queue up the next one. This call returns
the offset of the skipped _draw_ operation. Obviously (since the
correct drawing context has not been established), the skipped
_draw_ operation should not be issued. Returns kDrawComplete if
the end of the draw operations is reached.
*/
uint32_t skipDraw();
static const uint32_t kDrawComplete = SK_MaxU32;
Iterator() : fPlaybackMatrix(), fValid(false) { }
bool isValid() const { return fValid; }
@ -111,6 +132,8 @@ public:
// Whether or not this is a valid iterator (the default public constructor sets this false)
bool fValid;
uint32_t finish();
friend class SkPictureStateTree;
};

Просмотреть файл

@ -9,6 +9,7 @@
#include "SkDevice.h"
#include "SkDraw.h"
#include "SkPaintPriv.h"
#include "SkPicturePlayback.h"
SkPicture::AccelData::Key GPUAccelData::ComputeAccelDataKey() {
static const SkPicture::AccelData::Key gGPUID = SkPicture::AccelData::GenerateDomain();
@ -249,7 +250,15 @@ public:
}
virtual void drawPicture(SkPicture& picture) SK_OVERRIDE {
// BBH-based rendering doesn't re-issue many of the operations the gather
// process cares about (e.g., saves and restores) so it must be disabled.
if (NULL != picture.fPlayback) {
picture.fPlayback->setUseBBH(false);
}
picture.draw(this);
if (NULL != picture.fPlayback) {
picture.fPlayback->setUseBBH(true);
}
}
protected:
// disable aa for speed

Просмотреть файл

@ -68,10 +68,9 @@ public:
// incorporate the clip and matrix state into the key
static SkPicture::AccelData::Key ComputeAccelDataKey();
protected:
private:
SkTDArray<SaveLayerInfo> fSaveLayerInfo;
private:
typedef SkPicture::AccelData INHERITED;
};

Просмотреть файл

@ -28,6 +28,7 @@
#include "SkMaskFilter.h"
#include "SkPathEffect.h"
#include "SkPicture.h"
#include "SkPicturePlayback.h"
#include "SkRRect.h"
#include "SkStroke.h"
#include "SkSurface.h"
@ -1920,6 +1921,12 @@ void SkGpuDevice::EXPERIMENTAL_optimize(SkPicture* picture) {
GatherGPUInfo(picture, data);
}
static void wrap_texture(GrTexture* texture, int width, int height, SkBitmap* result) {
SkImageInfo info = SkImageInfo::MakeN32Premul(width, height);
result->setConfig(info);
result->setPixelRef(SkNEW_ARGS(SkGrPixelRef, (info, texture)))->unref();
}
void SkGpuDevice::EXPERIMENTAL_purge(SkPicture* picture) {
}
@ -1940,30 +1947,148 @@ bool SkGpuDevice::EXPERIMENTAL_drawPicture(SkCanvas* canvas, SkPicture* picture)
pullForward[i] = false;
}
SkIRect clip;
fClipData.getConservativeBounds(this->width(), this->height(), &clip, NULL);
SkMatrix inv;
if (!fContext->getMatrix().invert(&inv)) {
return false;
SkRect clipBounds;
if (!canvas->getClipBounds(&clipBounds)) {
return true;
}
SkIRect query;
clipBounds.roundOut(&query);
SkRect r = SkRect::Make(clip);
inv.mapRect(&r);
r.roundOut(&clip);
const SkPicture::OperationList& ops = picture->EXPERIMENTAL_getActiveOps(query);
const SkPicture::OperationList& ops = picture->EXPERIMENTAL_getActiveOps(clip);
// This code pre-renders the entire layer since it will be cached and potentially
// reused with different clips (e.g., in different tiles). Because of this the
// clip will not be limiting the size of the pre-rendered layer. kSaveLayerMaxSize
// is used to limit which clips are pre-rendered.
static const int kSaveLayerMaxSize = 256;
if (ops.valid()) {
// In this case the picture has been generated with a BBH so we use
// the BBH to limit the pre-rendering to just the layers needed to cover
// the region being drawn
for (int i = 0; i < ops.numOps(); ++i) {
for (int j = 0; j < gpuData->numSaveLayers(); ++j) {
uint32_t offset = ops.offset(i);
// For now we're saving all the layers in the GPUAccelData so they
// can be nested. Additionally, the nested layers appear before
// their parent in the list.
for (int j = 0 ; j < gpuData->numSaveLayers(); ++j) {
const GPUAccelData::SaveLayerInfo& info = gpuData->saveLayerInfo(j);
if (ops.offset(i) > info.fSaveLayerOpID && ops.offset(i) < info.fRestoreOpID) {
if (pullForward[j]) {
continue; // already pulling forward
}
if (offset < info.fSaveLayerOpID || offset > info.fRestoreOpID) {
continue; // the op isn't in this range
}
// TODO: once this code is more stable unsuitable layers can
// just be omitted during the optimization stage
if (!info.fValid ||
kSaveLayerMaxSize < info.fSize.fWidth ||
kSaveLayerMaxSize < info.fSize.fHeight ||
info.fIsNested) {
continue; // this layer is unsuitable
}
pullForward[j] = true;
}
}
} else {
// In this case there is no BBH associated with the picture. Pre-render
// all the layers
// TODO: intersect the bounds of each layer with the clip region to
// reduce the number of pre-rendered layers
for (int j = 0; j < gpuData->numSaveLayers(); ++j) {
const GPUAccelData::SaveLayerInfo& info = gpuData->saveLayerInfo(j);
// TODO: once this code is more stable unsuitable layers can
// just be omitted during the optimization stage
if (!info.fValid ||
kSaveLayerMaxSize < info.fSize.fWidth ||
kSaveLayerMaxSize < info.fSize.fHeight ||
info.fIsNested) {
continue;
}
return false;
pullForward[j] = true;
}
}
SkPicturePlayback::PlaybackReplacements replacements;
for (int i = 0; i < gpuData->numSaveLayers(); ++i) {
if (pullForward[i]) {
GrCachedLayer* layer = fContext->getLayerCache()->findLayerOrCreate(picture, i);
const GPUAccelData::SaveLayerInfo& info = gpuData->saveLayerInfo(i);
if (NULL != picture->fPlayback) {
SkPicturePlayback::PlaybackReplacements::ReplacementInfo* layerInfo =
replacements.push();
layerInfo->fStart = info.fSaveLayerOpID;
layerInfo->fStop = info.fRestoreOpID;
layerInfo->fPos = info.fOffset;
GrTextureDesc desc;
desc.fFlags = kRenderTarget_GrTextureFlagBit;
desc.fWidth = info.fSize.fWidth;
desc.fHeight = info.fSize.fHeight;
desc.fConfig = kSkia8888_GrPixelConfig;
// TODO: need to deal with sample count
bool bNeedsRendering = true;
// This just uses scratch textures and doesn't cache the texture.
// This can yield a lot of re-rendering
if (NULL == layer->getTexture()) {
layer->setTexture(fContext->lockAndRefScratchTexture(desc,
GrContext::kApprox_ScratchTexMatch));
if (NULL == layer->getTexture()) {
continue;
}
} else {
bNeedsRendering = false;
}
layerInfo->fBM = SkNEW(SkBitmap);
wrap_texture(layer->getTexture(), desc.fWidth, desc.fHeight, layerInfo->fBM);
SkASSERT(info.fPaint);
layerInfo->fPaint = info.fPaint;
if (bNeedsRendering) {
SkAutoTUnref<SkSurface> surface(SkSurface::NewRenderTargetDirect(
layer->getTexture()->asRenderTarget()));
SkCanvas* canvas = surface->getCanvas();
canvas->setMatrix(info.fCTM);
canvas->clear(SK_ColorTRANSPARENT);
picture->fPlayback->setDrawLimits(info.fSaveLayerOpID, info.fRestoreOpID);
picture->fPlayback->draw(*canvas, NULL);
picture->fPlayback->setDrawLimits(0, 0);
canvas->flush();
}
}
}
}
// Playback using new layers
picture->fPlayback->setReplacements(&replacements);
picture->fPlayback->draw(*canvas, NULL);
picture->fPlayback->setReplacements(NULL);
for (int i = 0; i < gpuData->numSaveLayers(); ++i) {
GrCachedLayer* layer = fContext->getLayerCache()->findLayerOrCreate(picture, i);
if (NULL != layer->getTexture()) {
fContext->unlockScratchTexture(layer->getTexture());
layer->setTexture(NULL);
}
}
return true;
}