aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar robertphillips <robertphillips@google.com>2014-06-30 08:26:50 -0700
committerGravatar Commit bot <commit-bot@chromium.org>2014-06-30 08:26:50 -0700
commit952841bf41a81228c23d16c7204b458abe0d7136 (patch)
tree31a4bff5594b2ea0e582c27b927a8bc9a14f0dc3
parenta75b0fadbdec4214afec6dd727fd224d34ed164f (diff)
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
-rw-r--r--gyp/tests.gypi1
-rw-r--r--include/core/SkSurface.h18
-rw-r--r--include/gpu/GrRect.h19
-rw-r--r--src/core/SkPicturePlayback.cpp6
-rw-r--r--src/core/SkPicturePlayback.h2
-rw-r--r--src/gpu/GrAtlas.cpp12
-rw-r--r--src/gpu/GrAtlas.h27
-rw-r--r--src/gpu/GrLayerCache.cpp97
-rw-r--r--src/gpu/GrLayerCache.h71
-rw-r--r--src/gpu/GrTextStrike.cpp2
-rw-r--r--src/gpu/SkGpuDevice.cpp50
-rw-r--r--src/image/SkSurface_Gpu.cpp31
-rw-r--r--tests/GpuLayerCacheTest.cpp137
-rw-r--r--tools/render_pictures_main.cpp6
14 files changed, 386 insertions, 93 deletions
diff --git a/gyp/tests.gypi b/gyp/tests.gypi
index a94ad985b4..3b67ebd1e7 100644
--- a/gyp/tests.gypi
+++ b/gyp/tests.gypi
@@ -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',
diff --git a/include/core/SkSurface.h b/include/core/SkSurface.h
index 68d4702cb5..cb5133ae00 100644
--- a/include/core/SkSurface.h
+++ b/include/core/SkSurface.h
@@ -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; }
diff --git a/include/gpu/GrRect.h b/include/gpu/GrRect.h
index ddb23b5a19..14130f831c 100644
--- a/include/gpu/GrRect.h
+++ b/include/gpu/GrRect.h
@@ -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);
diff --git a/src/core/SkPicturePlayback.cpp b/src/core/SkPicturePlayback.cpp
index 35e66bb7eb..92b1b01e02 100644
--- a/src/core/SkPicturePlayback.cpp
+++ b/src/core/SkPicturePlayback.cpp
@@ -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()) {
diff --git a/src/core/SkPicturePlayback.h b/src/core/SkPicturePlayback.h
index b929a735b4..f5260f8d0f 100644
--- a/src/core/SkPicturePlayback.h
+++ b/src/core/SkPicturePlayback.h
@@ -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(); }
diff --git a/src/gpu/GrAtlas.cpp b/src/gpu/GrAtlas.cpp
index 30b4bac139..1bff42a82f 100644
--- a/src/gpu/GrAtlas.cpp
+++ b/src/gpu/GrAtlas.cpp
@@ -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;
diff --git a/src/gpu/GrAtlas.h b/src/gpu/GrAtlas.h
index 49a7bacf27..d63c8b920c 100644
--- a/src/gpu/GrAtlas.h
+++ b/src/gpu/GrAtlas.h
@@ -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;
diff --git a/src/gpu/GrLayerCache.cpp b/src/gpu/GrLayerCache.cpp
index c20d809062..62c97208cc 100644
--- a/src/gpu/GrLayerCache.cpp
+++ b/src/gpu/GrLayerCache.cpp
@@ -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]);
+ }
}
diff --git a/src/gpu/GrLayerCache.h b/src/gpu/GrLayerCache.h
index a7ba2afb30..27479193aa 100644
--- a/src/gpu/GrLayerCache.h
+++ b/src/gpu/GrLayerCache.h
@@ -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:
- uint32_t pictureID() const { return fPictureID; }
- int layerID() const { return fLayerID; }
-
- void init(uint32_t pictureID, int layerID) {
+ GrCachedLayer(uint32_t pictureID, int layerID) {
fPictureID = pictureID;
- fLayerID = layerID;
- fTexture = NULL;
- fLocation.set(NULL, GrIRect16::MakeEmpty());
+ fLayerID = layerID;
+ fTexture = NULL;
+ fRect = GrIRect16::MakeEmpty();
}
+ uint32_t pictureID() const { return fPictureID; }
+ int layerID() const { return fLayerID; }
+
// 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
diff --git a/src/gpu/GrTextStrike.cpp b/src/gpu/GrTextStrike.cpp
index fb9d631b60..a9405ca852 100644
--- a/src/gpu/GrTextStrike.cpp
+++ b/src/gpu/GrTextStrike.cpp
@@ -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,
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index 3b82a33c9c..318de8db3d 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -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();
}
}
diff --git a/src/image/SkSurface_Gpu.cpp b/src/image/SkSurface_Gpu.cpp
index a34b774397..fab130cdde 100644
--- a/src/image/SkSurface_Gpu.cpp
+++ b/src/image/SkSurface_Gpu.cpp
@@ -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));
}
diff --git a/tests/GpuLayerCacheTest.cpp b/tests/GpuLayerCacheTest.cpp
new file mode 100644
index 0000000000..d6371e1657
--- /dev/null
+++ b/tests/GpuLayerCacheTest.cpp
@@ -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
diff --git a/tools/render_pictures_main.cpp b/tools/render_pictures_main.cpp
index c2c7875b28..13ae6ead3f 100644
--- a/tools/render_pictures_main.cpp
+++ b/tools/render_pictures_main.cpp
@@ -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);