Begin atlasing
This CL makes it possible for pulled-forward-layers to be atlased. It currently has a couple glaring limitations (which is why it is disabled): 1) the atlased layers cannot be purged nor aged out 2) the texture backing the atlas is not pulled from (or returned to) the resource cache #1 is on hold until we have a recycling rectanizer A separate major limitation (the non-atlased layers aren't cached) is blocked until we can transmute entries in the resource cache from scratch to non-scratch while potentially preserving their contents. Committed: https://skia.googlesource.com/skia/+/55e61f0ef4e5c8c34ac107deaadc9b4ffef3111b R=bsalomon@google.com Author: robertphillips@google.com Review URL: https://codereview.chromium.org/354533004
This commit is contained in:
parent
a75b0fadbd
commit
952841bf41
@ -87,6 +87,7 @@
|
||||
'../tests/GifTest.cpp',
|
||||
'../tests/GpuColorFilterTest.cpp',
|
||||
'../tests/GpuDrawPathTest.cpp',
|
||||
'../tests/GpuLayerCacheTest.cpp',
|
||||
'../tests/GpuRectanizerTest.cpp',
|
||||
'../tests/GrBinHashKeyTest.cpp',
|
||||
'../tests/GrContextFactoryTest.cpp',
|
||||
|
@ -77,18 +77,29 @@ public:
|
||||
kDistanceField_TextRenderMode,
|
||||
};
|
||||
|
||||
enum RenderTargetFlags {
|
||||
kNone_RenderTargetFlag = 0x0,
|
||||
/*
|
||||
* By default a RenderTarget-based surface will be cleared on creation.
|
||||
* Pass in this flag to prevent the clear from happening.
|
||||
*/
|
||||
kDontClear_RenderTargetFlag = 0x01,
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a new surface using the specified render target.
|
||||
*/
|
||||
static SkSurface* NewRenderTargetDirect(GrRenderTarget*,
|
||||
TextRenderMode trm = kStandard_TextRenderMode);
|
||||
TextRenderMode trm = kStandard_TextRenderMode,
|
||||
RenderTargetFlags flags = kNone_RenderTargetFlag);
|
||||
|
||||
/**
|
||||
* Return a new surface whose contents will be drawn to an offscreen
|
||||
* render target, allocated by the surface.
|
||||
*/
|
||||
static SkSurface* NewRenderTarget(GrContext*, const SkImageInfo&, int sampleCount = 0,
|
||||
TextRenderMode trm = kStandard_TextRenderMode);
|
||||
TextRenderMode trm = kStandard_TextRenderMode,
|
||||
RenderTargetFlags flags = kNone_RenderTargetFlag);
|
||||
|
||||
/**
|
||||
* Return a new surface whose contents will be drawn to an offscreen
|
||||
@ -103,7 +114,8 @@ public:
|
||||
* budget.
|
||||
*/
|
||||
static SkSurface* NewScratchRenderTarget(GrContext*, const SkImageInfo&, int sampleCount = 0,
|
||||
TextRenderMode trm = kStandard_TextRenderMode);
|
||||
TextRenderMode trm = kStandard_TextRenderMode,
|
||||
RenderTargetFlags flags = kNone_RenderTargetFlag);
|
||||
|
||||
int width() const { return fWidth; }
|
||||
int height() const { return fHeight; }
|
||||
|
@ -20,6 +20,18 @@ struct GrIRect16 {
|
||||
return r;
|
||||
}
|
||||
|
||||
static GrIRect16 SK_WARN_UNUSED_RESULT MakeWH(int16_t w, int16_t h) {
|
||||
GrIRect16 r;
|
||||
r.set(0, 0, w, h);
|
||||
return r;
|
||||
}
|
||||
|
||||
static GrIRect16 SK_WARN_UNUSED_RESULT MakeXYWH(int16_t x, int16_t y, int16_t w, int16_t h) {
|
||||
GrIRect16 r;
|
||||
r.set(x, y, x + w, y + h);
|
||||
return r;
|
||||
}
|
||||
|
||||
int width() const { return fRight - fLeft; }
|
||||
int height() const { return fBottom - fTop; }
|
||||
int area() const { return this->width() * this->height(); }
|
||||
@ -27,6 +39,13 @@ struct GrIRect16 {
|
||||
|
||||
void setEmpty() { memset(this, 0, sizeof(*this)); }
|
||||
|
||||
void set(int16_t left, int16_t top, int16_t right, int16_t bottom) {
|
||||
fLeft = left;
|
||||
fTop = top;
|
||||
fRight = right;
|
||||
fBottom = bottom;
|
||||
}
|
||||
|
||||
void set(const SkIRect& r) {
|
||||
fLeft = SkToS16(r.fLeft);
|
||||
fTop = SkToS16(r.fTop);
|
||||
|
@ -929,7 +929,11 @@ void SkPicturePlayback::draw(SkCanvas& canvas, SkDrawPictureCallback* callback)
|
||||
SkASSERT(NULL != temp->fPaint);
|
||||
canvas.save();
|
||||
canvas.setMatrix(initialMatrix);
|
||||
canvas.drawBitmap(*temp->fBM, temp->fPos.fX, temp->fPos.fY, temp->fPaint);
|
||||
SkRect src = SkRect::Make(temp->fSrcRect);
|
||||
SkRect dst = SkRect::MakeXYWH(temp->fPos.fX, temp->fPos.fY,
|
||||
temp->fSrcRect.width(),
|
||||
temp->fSrcRect.height());
|
||||
canvas.drawBitmapRectToRect(*temp->fBM, &src, dst, temp->fPaint);
|
||||
canvas.restore();
|
||||
|
||||
if (it.isValid()) {
|
||||
|
@ -353,6 +353,8 @@ private:
|
||||
SkIPoint fPos;
|
||||
SkBitmap* fBM; // fBM is allocated so ReplacementInfo can remain POD
|
||||
const SkPaint* fPaint; // Note: this object doesn't own the paint
|
||||
|
||||
SkIRect fSrcRect;
|
||||
};
|
||||
|
||||
~PlaybackReplacements() { this->freeAll(); }
|
||||
|
@ -54,8 +54,7 @@ static inline void adjust_for_offset(SkIPoint16* loc, const SkIPoint16& offset)
|
||||
loc->fY += offset.fY;
|
||||
}
|
||||
|
||||
bool GrPlot::addSubImage(int width, int height, const void* image,
|
||||
SkIPoint16* loc) {
|
||||
bool GrPlot::addSubImage(int width, int height, const void* image, SkIPoint16* loc) {
|
||||
float percentFull = fRects->percentFull();
|
||||
if (!fRects->addRect(width, height, loc)) {
|
||||
return false;
|
||||
@ -88,7 +87,7 @@ bool GrPlot::addSubImage(int width, int height, const void* image,
|
||||
adjust_for_offset(loc, fOffset);
|
||||
fDirty = true;
|
||||
// otherwise, just upload the image directly
|
||||
} else {
|
||||
} else if (NULL != image) {
|
||||
adjust_for_offset(loc, fOffset);
|
||||
GrContext* context = fTexture->getContext();
|
||||
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("skia.gpu"), "GrPlot::uploadToTexture");
|
||||
@ -96,6 +95,8 @@ bool GrPlot::addSubImage(int width, int height, const void* image,
|
||||
loc->fX, loc->fY, width, height,
|
||||
fTexture->config(), image, 0,
|
||||
GrContext::kDontFlush_PixelOpsFlag);
|
||||
} else {
|
||||
adjust_for_offset(loc, fOffset);
|
||||
}
|
||||
|
||||
#if FONT_CACHE_STATS
|
||||
@ -146,11 +147,12 @@ void GrPlot::resetRects() {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
GrAtlas::GrAtlas(GrGpu* gpu, GrPixelConfig config,
|
||||
GrAtlas::GrAtlas(GrGpu* gpu, GrPixelConfig config, GrTextureFlags flags,
|
||||
const SkISize& backingTextureSize,
|
||||
int numPlotsX, int numPlotsY, bool batchUploads) {
|
||||
fGpu = SkRef(gpu);
|
||||
fPixelConfig = config;
|
||||
fFlags = flags;
|
||||
fBackingTextureSize = backingTextureSize;
|
||||
fNumPlotsX = numPlotsX;
|
||||
fNumPlotsY = numPlotsY;
|
||||
@ -221,7 +223,7 @@ GrPlot* GrAtlas::addToAtlas(ClientPlotUsage* usage,
|
||||
if (NULL == fTexture) {
|
||||
// TODO: Update this to use the cache rather than directly creating a texture.
|
||||
GrTextureDesc desc;
|
||||
desc.fFlags = kDynamicUpdate_GrTextureFlagBit;
|
||||
desc.fFlags = fFlags | kDynamicUpdate_GrTextureFlagBit;
|
||||
desc.fWidth = fBackingTextureSize.width();
|
||||
desc.fHeight = fBackingTextureSize.height();
|
||||
desc.fConfig = fPixelConfig;
|
||||
|
@ -81,13 +81,17 @@ public:
|
||||
friend class GrAtlas;
|
||||
};
|
||||
|
||||
GrAtlas(GrGpu*, GrPixelConfig, const SkISize& backingTextureSize,
|
||||
GrAtlas(GrGpu*, GrPixelConfig, GrTextureFlags flags,
|
||||
const SkISize& backingTextureSize,
|
||||
int numPlotsX, int numPlotsY, bool batchUploads);
|
||||
~GrAtlas();
|
||||
|
||||
// add subimage of width, height dimensions to atlas
|
||||
// returns the containing GrPlot and location relative to the backing texture
|
||||
GrPlot* addToAtlas(ClientPlotUsage*, int width, int height, const void*, SkIPoint16*);
|
||||
// Adds a width x height subimage to the atlas. Upon success it returns
|
||||
// the containing GrPlot and absolute location in the backing texture.
|
||||
// NULL is returned if the subimage cannot fit in the atlas.
|
||||
// If provided, the image data will either be immediately uploaded or
|
||||
// written to the CPU-side backing bitmap.
|
||||
GrPlot* addToAtlas(ClientPlotUsage*, int width, int height, const void* image, SkIPoint16* loc);
|
||||
|
||||
// remove reference to this plot
|
||||
void removePlot(ClientPlotUsage* usage, const GrPlot* plot);
|
||||
@ -105,13 +109,14 @@ public:
|
||||
private:
|
||||
void makeMRU(GrPlot* plot);
|
||||
|
||||
GrGpu* fGpu;
|
||||
GrPixelConfig fPixelConfig;
|
||||
GrTexture* fTexture;
|
||||
SkISize fBackingTextureSize;
|
||||
int fNumPlotsX;
|
||||
int fNumPlotsY;
|
||||
bool fBatchUploads;
|
||||
GrGpu* fGpu;
|
||||
GrPixelConfig fPixelConfig;
|
||||
GrTextureFlags fFlags;
|
||||
GrTexture* fTexture;
|
||||
SkISize fBackingTextureSize;
|
||||
int fNumPlotsX;
|
||||
int fNumPlotsY;
|
||||
bool fBatchUploads;
|
||||
|
||||
// allocated array of GrPlots
|
||||
GrPlot* fPlotArray;
|
||||
|
@ -42,14 +42,23 @@ private:
|
||||
};
|
||||
|
||||
GrLayerCache::GrLayerCache(GrContext* context)
|
||||
: fContext(context)
|
||||
, fLayerPool(16) { // TODO: may need to increase this later
|
||||
: fContext(context) {
|
||||
this->initAtlas();
|
||||
}
|
||||
|
||||
GrLayerCache::~GrLayerCache() {
|
||||
SkTDArray<GrCachedLayer*>& layerArray = fLayerHash.getArray();
|
||||
for (int i = 0; i < fLayerHash.count(); ++i) {
|
||||
this->unlock(layerArray[i]);
|
||||
}
|
||||
|
||||
fLayerHash.deleteAll();
|
||||
|
||||
// The atlas only lets go of its texture when the atlas is deleted.
|
||||
fAtlas.free();
|
||||
}
|
||||
|
||||
void GrLayerCache::init() {
|
||||
void GrLayerCache::initAtlas() {
|
||||
static const int kAtlasTextureWidth = 1024;
|
||||
static const int kAtlasTextureHeight = 1024;
|
||||
|
||||
@ -58,19 +67,31 @@ void GrLayerCache::init() {
|
||||
// The layer cache only gets 1 plot
|
||||
SkISize textureSize = SkISize::Make(kAtlasTextureWidth, kAtlasTextureHeight);
|
||||
fAtlas.reset(SkNEW_ARGS(GrAtlas, (fContext->getGpu(), kSkia8888_GrPixelConfig,
|
||||
kRenderTarget_GrTextureFlagBit,
|
||||
textureSize, 1, 1, false)));
|
||||
}
|
||||
|
||||
void GrLayerCache::freeAll() {
|
||||
SkTDArray<GrCachedLayer*>& layerArray = fLayerHash.getArray();
|
||||
for (int i = 0; i < fLayerHash.count(); ++i) {
|
||||
this->unlock(layerArray[i]);
|
||||
}
|
||||
|
||||
fLayerHash.deleteAll();
|
||||
|
||||
// The atlas only lets go of its texture when the atlas is deleted.
|
||||
fAtlas.free();
|
||||
// GrLayerCache always assumes an atlas exists so recreate it. The atlas
|
||||
// lazily allocates a replacement texture so reallocating a new
|
||||
// atlas here won't disrupt a GrContext::contextDestroyed or freeGpuResources.
|
||||
// TODO: Make GrLayerCache lazily allocate the atlas manager?
|
||||
this->initAtlas();
|
||||
}
|
||||
|
||||
GrCachedLayer* GrLayerCache::createLayer(const SkPicture* picture, int layerID) {
|
||||
GrCachedLayer* layer = fLayerPool.alloc();
|
||||
|
||||
SkASSERT(picture->uniqueID() != SK_InvalidGenID);
|
||||
layer->init(picture->uniqueID(), layerID);
|
||||
|
||||
GrCachedLayer* layer = SkNEW_ARGS(GrCachedLayer, (picture->uniqueID(), layerID));
|
||||
fLayerHash.insert(PictureLayerKey(picture->uniqueID(), layerID), layer);
|
||||
return layer;
|
||||
}
|
||||
@ -91,19 +112,71 @@ GrCachedLayer* GrLayerCache::findLayerOrCreate(const SkPicture* picture, int lay
|
||||
}
|
||||
|
||||
bool GrLayerCache::lock(GrCachedLayer* layer, const GrTextureDesc& desc) {
|
||||
SkASSERT(NULL == layer->getTexture());
|
||||
|
||||
// This just uses scratch textures and doesn't cache the texture.
|
||||
if (NULL != layer->texture()) {
|
||||
// This layer is already locked
|
||||
#ifdef SK_DEBUG
|
||||
if (!layer->rect().isEmpty()) {
|
||||
// It claims to be atlased
|
||||
SkASSERT(layer->rect().width() == desc.fWidth);
|
||||
SkASSERT(layer->rect().height() == desc.fHeight);
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
#if USE_ATLAS
|
||||
SkIPoint16 loc;
|
||||
GrPlot* plot = fAtlas->addToAtlas(&fPlotUsage, desc.fWidth, desc.fHeight, NULL, &loc);
|
||||
if (NULL != plot) {
|
||||
GrIRect16 bounds = GrIRect16::MakeXYWH(loc.fX, loc.fY,
|
||||
SkToS16(desc.fWidth), SkToS16(desc.fHeight));
|
||||
layer->setTexture(fAtlas->getTexture(), bounds);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// This path always uses a new scratch texture and (thus) doesn't cache anything.
|
||||
// This can yield a lot of re-rendering
|
||||
layer->setTexture(fContext->lockAndRefScratchTexture(desc, GrContext::kApprox_ScratchTexMatch));
|
||||
layer->setTexture(fContext->lockAndRefScratchTexture(desc, GrContext::kApprox_ScratchTexMatch),
|
||||
GrIRect16::MakeEmpty());
|
||||
return false;
|
||||
}
|
||||
|
||||
void GrLayerCache::unlock(GrCachedLayer* layer) {
|
||||
if (NULL == layer || NULL == layer->getTexture()) {
|
||||
if (NULL == layer || NULL == layer->texture()) {
|
||||
return;
|
||||
}
|
||||
|
||||
fContext->unlockScratchTexture(layer->getTexture());
|
||||
layer->setTexture(NULL);
|
||||
// The atlas doesn't currently use a scratch texture (and we would have
|
||||
// to free up space differently anyways)
|
||||
// TODO: unlock atlas space when a recycling rectanizer is available
|
||||
if (layer->texture() != fAtlas->getTexture()) {
|
||||
fContext->unlockScratchTexture(layer->texture());
|
||||
layer->setTexture(NULL, GrIRect16::MakeEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
void GrLayerCache::purge(const SkPicture* picture) {
|
||||
// This is somewhat of an abuse of GrTHashTable. We need to find all the
|
||||
// layers associated with 'picture' but the usual hash calls only look for
|
||||
// exact key matches. This code peeks into the hash table's innards to
|
||||
// find all the 'picture'-related layers.
|
||||
// TODO: use a different data structure for the layer hash?
|
||||
SkTDArray<GrCachedLayer*> toBeRemoved;
|
||||
|
||||
const SkTDArray<GrCachedLayer*>& layerArray = fLayerHash.getArray();
|
||||
for (int i = 0; i < fLayerHash.count(); ++i) {
|
||||
if (picture->uniqueID() == layerArray[i]->pictureID()) {
|
||||
*toBeRemoved.append() = layerArray[i];
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < toBeRemoved.count(); ++i) {
|
||||
this->unlock(toBeRemoved[i]);
|
||||
|
||||
PictureLayerKey key(picture->uniqueID(), toBeRemoved[i]->layerID());
|
||||
fLayerHash.remove(key, toBeRemoved[i]);
|
||||
SkDELETE(toBeRemoved[i]);
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,8 @@
|
||||
#ifndef GrLayerCache_DEFINED
|
||||
#define GrLayerCache_DEFINED
|
||||
|
||||
#define USE_ATLAS 0
|
||||
|
||||
#include "GrAllocPool.h"
|
||||
#include "GrAtlas.h"
|
||||
#include "GrTHashTable.h"
|
||||
@ -17,61 +19,38 @@
|
||||
class GrGpu;
|
||||
class SkPicture;
|
||||
|
||||
// GrAtlasLocation captures an atlased item's position in the atlas. This
|
||||
// means the plot in which it resides and its bounds inside the plot.
|
||||
// TODO: Make GrGlyph use one of these?
|
||||
class GrAtlasLocation {
|
||||
public:
|
||||
GrAtlasLocation() : fPlot(NULL) {}
|
||||
|
||||
void set(GrPlot* plot, const GrIRect16& bounds) {
|
||||
fPlot = plot;
|
||||
fBounds = bounds;
|
||||
}
|
||||
|
||||
const GrPlot* plot() const {
|
||||
return fPlot;
|
||||
}
|
||||
|
||||
const GrIRect16& bounds() const {
|
||||
return fBounds;
|
||||
}
|
||||
|
||||
private:
|
||||
GrPlot* fPlot;
|
||||
GrIRect16 fBounds; // only valid is fPlot != NULL
|
||||
};
|
||||
|
||||
// GrCachedLayer encapsulates the caching information for a single saveLayer.
|
||||
//
|
||||
// Atlased layers get a ref to their atlas GrTexture and their GrAtlasLocation
|
||||
// is filled in.
|
||||
// In this case GrCachedLayer is roughly equivalent to a GrGlyph in the font
|
||||
// caching system.
|
||||
// Atlased layers get a ref to their atlas GrTexture and 'fRect' contains
|
||||
// their absolute location in the backing texture.
|
||||
//
|
||||
// Non-atlased layers get a ref to the GrTexture in which they reside. Their
|
||||
// 'fRect' will be empty.
|
||||
//
|
||||
// Non-atlased layers get a ref to the GrTexture in which they reside.
|
||||
// TODO: can we easily reuse the empty space in the non-atlased GrTexture's?
|
||||
struct GrCachedLayer {
|
||||
public:
|
||||
GrCachedLayer(uint32_t pictureID, int layerID) {
|
||||
fPictureID = pictureID;
|
||||
fLayerID = layerID;
|
||||
fTexture = NULL;
|
||||
fRect = GrIRect16::MakeEmpty();
|
||||
}
|
||||
|
||||
uint32_t pictureID() const { return fPictureID; }
|
||||
int layerID() const { return fLayerID; }
|
||||
|
||||
void init(uint32_t pictureID, int layerID) {
|
||||
fPictureID = pictureID;
|
||||
fLayerID = layerID;
|
||||
fTexture = NULL;
|
||||
fLocation.set(NULL, GrIRect16::MakeEmpty());
|
||||
}
|
||||
|
||||
// This call takes over the caller's ref
|
||||
void setTexture(GrTexture* texture) {
|
||||
void setTexture(GrTexture* texture, const GrIRect16& rect) {
|
||||
if (NULL != fTexture) {
|
||||
fTexture->unref();
|
||||
}
|
||||
|
||||
fTexture = texture; // just take over caller's ref
|
||||
fRect = rect;
|
||||
}
|
||||
GrTexture* getTexture() { return fTexture; }
|
||||
GrTexture* texture() { return fTexture; }
|
||||
const GrIRect16& rect() const { return fRect; }
|
||||
|
||||
private:
|
||||
uint32_t fPictureID;
|
||||
@ -84,7 +63,9 @@ private:
|
||||
// non-NULL, that means that the texture is locked in the texture cache.
|
||||
GrTexture* fTexture;
|
||||
|
||||
GrAtlasLocation fLocation; // only valid if the layer is atlased
|
||||
// For non-atlased layers 'fRect' is empty otherwise it is the bound of
|
||||
// the layer in the atlas.
|
||||
GrIRect16 fRect;
|
||||
};
|
||||
|
||||
// The GrLayerCache caches pre-computed saveLayers for later rendering.
|
||||
@ -115,6 +96,9 @@ public:
|
||||
// Inform the cache that layer's cached image is not currently required
|
||||
void unlock(GrCachedLayer* layer);
|
||||
|
||||
// Remove all the layers (and unlock any resources) associated with 'picture'
|
||||
void purge(const SkPicture* picture);
|
||||
|
||||
private:
|
||||
GrContext* fContext; // pointer back to owning context
|
||||
SkAutoTDelete<GrAtlas> fAtlas; // TODO: could lazily allocate
|
||||
@ -122,10 +106,13 @@ private:
|
||||
|
||||
class PictureLayerKey;
|
||||
GrTHashTable<GrCachedLayer, PictureLayerKey, 7> fLayerHash;
|
||||
GrTAllocPool<GrCachedLayer> fLayerPool;
|
||||
|
||||
void init();
|
||||
void initAtlas();
|
||||
GrCachedLayer* createLayer(const SkPicture* picture, int layerID);
|
||||
|
||||
// for testing
|
||||
friend class GetNumLayers;
|
||||
int numLayers() const { return fLayerHash.count(); }
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -82,7 +82,7 @@ GrTextStrike* GrFontCache::generateStrike(GrFontScaler* scaler,
|
||||
if (NULL == fAtlases[atlasIndex]) {
|
||||
SkISize textureSize = SkISize::Make(GR_ATLAS_TEXTURE_WIDTH,
|
||||
GR_ATLAS_TEXTURE_HEIGHT);
|
||||
fAtlases[atlasIndex] = SkNEW_ARGS(GrAtlas, (fGpu, config,
|
||||
fAtlases[atlasIndex] = SkNEW_ARGS(GrAtlas, (fGpu, config, kNone_GrTextureFlags,
|
||||
textureSize,
|
||||
GR_NUM_PLOTS_X,
|
||||
GR_NUM_PLOTS_Y,
|
||||
|
@ -1825,7 +1825,7 @@ static void wrap_texture(GrTexture* texture, int width, int height, SkBitmap* re
|
||||
}
|
||||
|
||||
void SkGpuDevice::EXPERIMENTAL_purge(const SkPicture* picture) {
|
||||
|
||||
fContext->getLayerCache()->purge(picture);
|
||||
}
|
||||
|
||||
bool SkGpuDevice::EXPERIMENTAL_drawPicture(SkCanvas* canvas, const SkPicture* picture) {
|
||||
@ -1951,28 +1951,68 @@ bool SkGpuDevice::EXPERIMENTAL_drawPicture(SkCanvas* canvas, const SkPicture* pi
|
||||
// TODO: need to deal with sample count
|
||||
|
||||
bool needsRendering = !fContext->getLayerCache()->lock(layer, desc);
|
||||
if (NULL == layer->getTexture()) {
|
||||
if (NULL == layer->texture()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
layerInfo->fBM = SkNEW(SkBitmap); // fBM is allocated so ReplacementInfo can be POD
|
||||
wrap_texture(layer->getTexture(), desc.fWidth, desc.fHeight, layerInfo->fBM);
|
||||
wrap_texture(layer->texture(),
|
||||
layer->rect().isEmpty() ? desc.fWidth : layer->texture()->width(),
|
||||
layer->rect().isEmpty() ? desc.fHeight : layer->texture()->height(),
|
||||
layerInfo->fBM);
|
||||
|
||||
SkASSERT(info.fPaint);
|
||||
layerInfo->fPaint = info.fPaint;
|
||||
|
||||
if (layer->rect().isEmpty()) {
|
||||
layerInfo->fSrcRect = SkIRect::MakeWH(desc.fWidth, desc.fHeight);
|
||||
} else {
|
||||
layerInfo->fSrcRect = SkIRect::MakeXYWH(layer->rect().fLeft,
|
||||
layer->rect().fTop,
|
||||
layer->rect().width(),
|
||||
layer->rect().height());
|
||||
}
|
||||
|
||||
if (needsRendering) {
|
||||
SkAutoTUnref<SkSurface> surface(SkSurface::NewRenderTargetDirect(
|
||||
layer->getTexture()->asRenderTarget()));
|
||||
layer->texture()->asRenderTarget(),
|
||||
SkSurface::kStandard_TextRenderMode,
|
||||
SkSurface::kDontClear_RenderTargetFlag));
|
||||
|
||||
SkCanvas* canvas = surface->getCanvas();
|
||||
|
||||
if (!layer->rect().isEmpty()) {
|
||||
// Add a rect clip to make sure the rendering doesn't
|
||||
// extend beyond the boundaries of the atlased sub-rect
|
||||
SkRect bound = SkRect::MakeXYWH(SkIntToScalar(layer->rect().fLeft),
|
||||
SkIntToScalar(layer->rect().fTop),
|
||||
SkIntToScalar(layer->rect().width()),
|
||||
SkIntToScalar(layer->rect().height()));
|
||||
canvas->clipRect(bound);
|
||||
// Since 'clear' doesn't respect the clip we need to draw a rect
|
||||
// TODO: ensure none of the atlased layers contain a clear call!
|
||||
SkPaint paint;
|
||||
paint.setColor(SK_ColorTRANSPARENT);
|
||||
canvas->drawRect(bound, paint);
|
||||
} else {
|
||||
canvas->clear(SK_ColorTRANSPARENT);
|
||||
}
|
||||
|
||||
canvas->setMatrix(info.fCTM);
|
||||
canvas->clear(SK_ColorTRANSPARENT);
|
||||
|
||||
if (!layer->rect().isEmpty()) {
|
||||
// info.fCTM maps the layer's top/left to the origin.
|
||||
// Since this layer is atlased the top/left corner needs
|
||||
// to be offset to some arbitrary location in the backing
|
||||
// texture.
|
||||
canvas->translate(SkIntToScalar(layer->rect().fLeft),
|
||||
SkIntToScalar(layer->rect().fTop));
|
||||
}
|
||||
|
||||
picture->fPlayback->setDrawLimits(info.fSaveLayerOpID, info.fRestoreOpID);
|
||||
picture->fPlayback->draw(*canvas, NULL);
|
||||
picture->fPlayback->setDrawLimits(0, 0);
|
||||
|
||||
canvas->flush();
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,8 @@ class SkSurface_Gpu : public SkSurface_Base {
|
||||
public:
|
||||
SK_DECLARE_INST_COUNT(SkSurface_Gpu)
|
||||
|
||||
SkSurface_Gpu(GrRenderTarget*, bool cached, TextRenderMode trm);
|
||||
SkSurface_Gpu(GrRenderTarget*, bool cached, TextRenderMode trm,
|
||||
SkSurface::RenderTargetFlags flags);
|
||||
virtual ~SkSurface_Gpu();
|
||||
|
||||
virtual SkCanvas* onNewCanvas() SK_OVERRIDE;
|
||||
@ -33,14 +34,16 @@ private:
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
SkSurface_Gpu::SkSurface_Gpu(GrRenderTarget* renderTarget, bool cached, TextRenderMode trm)
|
||||
SkSurface_Gpu::SkSurface_Gpu(GrRenderTarget* renderTarget, bool cached, TextRenderMode trm,
|
||||
SkSurface::RenderTargetFlags flags)
|
||||
: INHERITED(renderTarget->width(), renderTarget->height()) {
|
||||
int flags = 0;
|
||||
flags |= cached ? SkGpuDevice::kCached_Flag : 0;
|
||||
flags |= (kDistanceField_TextRenderMode == trm) ? SkGpuDevice::kDFFonts_Flag : 0;
|
||||
fDevice = SkGpuDevice::Create(renderTarget, flags);
|
||||
int deviceFlags = 0;
|
||||
deviceFlags |= cached ? SkGpuDevice::kCached_Flag : 0;
|
||||
deviceFlags |= (kDistanceField_TextRenderMode == trm) ? SkGpuDevice::kDFFonts_Flag : 0;
|
||||
fDevice = SkGpuDevice::Create(renderTarget, deviceFlags);
|
||||
|
||||
if (kRGB_565_GrPixelConfig != renderTarget->config()) {
|
||||
if (kRGB_565_GrPixelConfig != renderTarget->config() &&
|
||||
!(flags & kDontClear_RenderTargetFlag)) {
|
||||
fDevice->clear(0x0);
|
||||
}
|
||||
}
|
||||
@ -101,15 +104,16 @@ void SkSurface_Gpu::onDiscard() {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
SkSurface* SkSurface::NewRenderTargetDirect(GrRenderTarget* target, TextRenderMode trm) {
|
||||
SkSurface* SkSurface::NewRenderTargetDirect(GrRenderTarget* target, TextRenderMode trm,
|
||||
RenderTargetFlags flags) {
|
||||
if (NULL == target) {
|
||||
return NULL;
|
||||
}
|
||||
return SkNEW_ARGS(SkSurface_Gpu, (target, false, trm));
|
||||
return SkNEW_ARGS(SkSurface_Gpu, (target, false, trm, flags));
|
||||
}
|
||||
|
||||
SkSurface* SkSurface::NewRenderTarget(GrContext* ctx, const SkImageInfo& info, int sampleCount,
|
||||
TextRenderMode trm) {
|
||||
TextRenderMode trm, RenderTargetFlags flags) {
|
||||
if (NULL == ctx) {
|
||||
return NULL;
|
||||
}
|
||||
@ -126,11 +130,12 @@ SkSurface* SkSurface::NewRenderTarget(GrContext* ctx, const SkImageInfo& info, i
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return SkNEW_ARGS(SkSurface_Gpu, (tex->asRenderTarget(), false, trm));
|
||||
return SkNEW_ARGS(SkSurface_Gpu, (tex->asRenderTarget(), false, trm, flags));
|
||||
}
|
||||
|
||||
SkSurface* SkSurface::NewScratchRenderTarget(GrContext* ctx, const SkImageInfo& info,
|
||||
int sampleCount, TextRenderMode trm) {
|
||||
int sampleCount, TextRenderMode trm,
|
||||
RenderTargetFlags flags) {
|
||||
if (NULL == ctx) {
|
||||
return NULL;
|
||||
}
|
||||
@ -148,5 +153,5 @@ SkSurface* SkSurface::NewScratchRenderTarget(GrContext* ctx, const SkImageInfo&
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return SkNEW_ARGS(SkSurface_Gpu, (tex->asRenderTarget(), true, trm));
|
||||
return SkNEW_ARGS(SkSurface_Gpu, (tex->asRenderTarget(), true, trm, flags));
|
||||
}
|
||||
|
137
tests/GpuLayerCacheTest.cpp
Normal file
137
tests/GpuLayerCacheTest.cpp
Normal file
@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright 2014 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#if SK_SUPPORT_GPU
|
||||
|
||||
#include "GrContext.h"
|
||||
#include "GrContextFactory.h"
|
||||
#include "GrLayerCache.h"
|
||||
#include "Test.h"
|
||||
|
||||
static const int kNumLayers = 5;
|
||||
|
||||
class GetNumLayers {
|
||||
public:
|
||||
static int NumLayers(GrLayerCache* cache) {
|
||||
return cache->numLayers();
|
||||
}
|
||||
};
|
||||
|
||||
// Add several layers to the cache
|
||||
static void create_layers(skiatest::Reporter* reporter,
|
||||
GrLayerCache* cache,
|
||||
const SkPicture& picture) {
|
||||
GrCachedLayer* layers[kNumLayers];
|
||||
|
||||
for (int i = 0; i < kNumLayers; ++i) {
|
||||
layers[i] = cache->findLayerOrCreate(&picture, i);
|
||||
REPORTER_ASSERT(reporter, NULL != layers[i]);
|
||||
GrCachedLayer* layer = cache->findLayer(&picture, i);
|
||||
REPORTER_ASSERT(reporter, layer == layers[i]);
|
||||
|
||||
REPORTER_ASSERT(reporter, GetNumLayers::NumLayers(cache) == i+1);
|
||||
|
||||
REPORTER_ASSERT(reporter, picture.uniqueID() == layers[i]->pictureID());
|
||||
REPORTER_ASSERT(reporter, layers[i]->layerID() == i);
|
||||
REPORTER_ASSERT(reporter, NULL == layers[i]->texture());
|
||||
REPORTER_ASSERT(reporter, layers[i]->rect().isEmpty());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// This test case exercises the public API of the GrLayerCache class.
|
||||
// In particular it checks its interaction with the resource cache (w.r.t.
|
||||
// locking & unlocking textures).
|
||||
// TODO: need to add checks on VRAM usage!
|
||||
DEF_GPUTEST(GpuLayerCache, reporter, factory) {
|
||||
|
||||
GrContext* context = factory->get(GrContextFactory::kNative_GLContextType);
|
||||
if (NULL == context) {
|
||||
return;
|
||||
}
|
||||
|
||||
SkPicture picture;
|
||||
|
||||
GrLayerCache cache(context);
|
||||
|
||||
create_layers(reporter, &cache, picture);
|
||||
|
||||
// Lock the layers making them all 512x512
|
||||
GrTextureDesc desc;
|
||||
desc.fWidth = 512;
|
||||
desc.fHeight = 512;
|
||||
desc.fConfig = kSkia8888_GrPixelConfig;
|
||||
|
||||
for (int i = 0; i < kNumLayers; ++i) {
|
||||
GrCachedLayer* layer = cache.findLayer(&picture, i);
|
||||
REPORTER_ASSERT(reporter, NULL != layer);
|
||||
|
||||
bool foundInCache = cache.lock(layer, desc);
|
||||
REPORTER_ASSERT(reporter, !foundInCache);
|
||||
foundInCache = cache.lock(layer, desc);
|
||||
REPORTER_ASSERT(reporter, foundInCache);
|
||||
|
||||
REPORTER_ASSERT(reporter, NULL != layer->texture());
|
||||
#if USE_ATLAS
|
||||
// The first 4 layers should be in the atlas (and thus have non-empty
|
||||
// rects)
|
||||
if (i < 4) {
|
||||
REPORTER_ASSERT(reporter, !layer->rect().isEmpty());
|
||||
} else {
|
||||
#endif
|
||||
REPORTER_ASSERT(reporter, layer->rect().isEmpty());
|
||||
#if USE_ATLAS
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Unlock the textures
|
||||
for (int i = 0; i < kNumLayers; ++i) {
|
||||
GrCachedLayer* layer = cache.findLayer(&picture, i);
|
||||
REPORTER_ASSERT(reporter, NULL != layer);
|
||||
|
||||
cache.unlock(layer);
|
||||
}
|
||||
|
||||
for (int i = 0; i < kNumLayers; ++i) {
|
||||
GrCachedLayer* layer = cache.findLayer(&picture, i);
|
||||
REPORTER_ASSERT(reporter, NULL != layer);
|
||||
|
||||
#if USE_ATLAS
|
||||
// The first 4 layers should be in the atlas (and thus do not
|
||||
// currently unlock). The final layer should be unlocked.
|
||||
if (i < 4) {
|
||||
REPORTER_ASSERT(reporter, NULL != layer->texture());
|
||||
REPORTER_ASSERT(reporter, !layer->rect().isEmpty());
|
||||
} else {
|
||||
#endif
|
||||
REPORTER_ASSERT(reporter, NULL == layer->texture());
|
||||
REPORTER_ASSERT(reporter, layer->rect().isEmpty());
|
||||
#if USE_ATLAS
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Free them all SkGpuDevice-style. This will not free up the
|
||||
// atlas' texture but will eliminate all the layers.
|
||||
cache.purge(&picture);
|
||||
|
||||
REPORTER_ASSERT(reporter, GetNumLayers::NumLayers(&cache) == 0);
|
||||
// TODO: add VRAM/resource cache check here
|
||||
#if 0
|
||||
// Re-create the layers
|
||||
create_layers(reporter, &cache, picture);
|
||||
|
||||
// Free them again GrContext-style. This should free up everything.
|
||||
cache.freeAll();
|
||||
|
||||
REPORTER_ASSERT(reporter, GetNumLayers::NumLayers(&cache) == 0);
|
||||
// TODO: add VRAM/resource cache check here
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
@ -205,6 +205,12 @@ static bool render_picture_internal(const SkString& inputPath, const SkString* w
|
||||
SkDebugf("Failed to render %s\n", inputFilename.c_str());
|
||||
}
|
||||
|
||||
if (FLAGS_preprocess) {
|
||||
if (NULL != renderer.getCanvas()) {
|
||||
renderer.getCanvas()->EXPERIMENTAL_purge(renderer.getPicture());
|
||||
}
|
||||
}
|
||||
|
||||
renderer.end();
|
||||
|
||||
SkDELETE(picture);
|
||||
|
Loading…
Reference in New Issue
Block a user