diff options
author | robertphillips <robertphillips@google.com> | 2014-06-30 08:26:50 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-06-30 08:26:50 -0700 |
commit | 952841bf41a81228c23d16c7204b458abe0d7136 (patch) | |
tree | 31a4bff5594b2ea0e582c27b927a8bc9a14f0dc3 | |
parent | a75b0fadbdec4214afec6dd727fd224d34ed164f (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.gypi | 1 | ||||
-rw-r--r-- | include/core/SkSurface.h | 18 | ||||
-rw-r--r-- | include/gpu/GrRect.h | 19 | ||||
-rw-r--r-- | src/core/SkPicturePlayback.cpp | 6 | ||||
-rw-r--r-- | src/core/SkPicturePlayback.h | 2 | ||||
-rw-r--r-- | src/gpu/GrAtlas.cpp | 12 | ||||
-rw-r--r-- | src/gpu/GrAtlas.h | 27 | ||||
-rw-r--r-- | src/gpu/GrLayerCache.cpp | 97 | ||||
-rw-r--r-- | src/gpu/GrLayerCache.h | 71 | ||||
-rw-r--r-- | src/gpu/GrTextStrike.cpp | 2 | ||||
-rw-r--r-- | src/gpu/SkGpuDevice.cpp | 50 | ||||
-rw-r--r-- | src/image/SkSurface_Gpu.cpp | 31 | ||||
-rw-r--r-- | tests/GpuLayerCacheTest.cpp | 137 | ||||
-rw-r--r-- | tools/render_pictures_main.cpp | 6 |
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); |