From 50a3043194cf278a74ff51c33c6cdb52cbe1f8f9 Mon Sep 17 00:00:00 2001 From: "commit-bot@chromium.org" Date: Thu, 24 Oct 2013 17:44:27 +0000 Subject: [PATCH] We want to give SkPixelRef a way to signal over to GrResourceCache that it's become pointless to keep around textures based on that SkPixelRef when its pixels change, so that it can be a good citizen and free those textures. This adds an invalidation listener mechanism to SkPixelRef to let it send this message while still staying ignorant of who's listening. These messages are tricky to deliver. The SkPixelRefs they originates from and the GrResourceCaches they ultimately end up at may be on different threads; neither class is threadsafe; their object lifetimes are totally independent; it's a many-senders-to-many-receivers relation; and neither codebase should really know about the other. So I've added a per-message-type global message bus to broadcast messages to threadsafe inboxes. Anyone can post() a message, which will show up in all the inboxes of that type, read whenever the inbox's owner calls poll(). The implementation is _dumb_; it can be improved in several dimensions (inbox size limits, lock-free message delivery) if we find the need. I took some care to make sure not to send the invalidation message for any SkPixelRef that's sharing a generation ID with another SkPixelRef. BUG= R=bsalomon@google.com, scroggo@google.com, reed@google.com Author: mtklein@google.com Review URL: https://codereview.chromium.org/26734003 git-svn-id: http://skia.googlecode.com/svn/trunk@11949 2bbb7eff-a529-9590-31e7-b0007b416f81 --- gyp/core.gypi | 1 + gyp/tests.gyp | 2 + include/core/SkPixelRef.h | 32 ++++++++-- include/gpu/GrContext.h | 5 +- src/core/SkBitmap.cpp | 11 ++-- src/core/SkMessageBus.h | 115 ++++++++++++++++++++++++++++++++++++ src/core/SkPixelRef.cpp | 47 +++++++++++++-- src/gpu/GrContext.cpp | 7 ++- src/gpu/GrResourceCache.cpp | 21 +++++++ src/gpu/GrResourceCache.h | 10 ++++ src/gpu/SkGr.cpp | 41 ++++++++++++- tests/MessageBusTest.cpp | 57 ++++++++++++++++++ tests/PixelRefTest.cpp | 49 +++++++++++++++ 13 files changed, 381 insertions(+), 17 deletions(-) create mode 100644 src/core/SkMessageBus.h create mode 100644 tests/MessageBusTest.cpp create mode 100644 tests/PixelRefTest.cpp diff --git a/gyp/core.gypi b/gyp/core.gypi index 5be1eadb85..b96a2ec7ee 100644 --- a/gyp/core.gypi +++ b/gyp/core.gypi @@ -114,6 +114,7 @@ '<(skia_src_path)/core/SkMaskGamma.h', '<(skia_src_path)/core/SkMath.cpp', '<(skia_src_path)/core/SkMatrix.cpp', + '<(skia_src_path)/core/SkMessageBus.h', '<(skia_src_path)/core/SkMetaData.cpp', '<(skia_src_path)/core/SkMipMap.cpp', '<(skia_src_path)/core/SkOnce.h', diff --git a/gyp/tests.gyp b/gyp/tests.gyp index 5a3ae4da80..2e04de1b0e 100644 --- a/gyp/tests.gyp +++ b/gyp/tests.gyp @@ -89,6 +89,7 @@ '../tests/Matrix44Test.cpp', '../tests/MemoryTest.cpp', '../tests/MemsetTest.cpp', + '../tests/MessageBusTest.cpp', '../tests/MetaDataTest.cpp', '../tests/MipMapTest.cpp', '../tests/OnceTest.cpp', @@ -104,6 +105,7 @@ '../tests/PictureTest.cpp', '../tests/PictureUtilsTest.cpp', '../tests/PipeTest.cpp', + '../tests/PixelRefTest.cpp', '../tests/PointTest.cpp', '../tests/PremulAlphaRoundTripTest.cpp', '../tests/QuickRejectTest.cpp', diff --git a/include/core/SkPixelRef.h b/include/core/SkPixelRef.h index 958e82fee5..d90e58719b 100644 --- a/include/core/SkPixelRef.h +++ b/include/core/SkPixelRef.h @@ -14,6 +14,7 @@ #include "SkRefCnt.h" #include "SkString.h" #include "SkFlattenable.h" +#include "SkTDArray.h" #ifdef SK_DEBUG /** @@ -49,6 +50,7 @@ public: SK_DECLARE_INST_COUNT(SkPixelRef) explicit SkPixelRef(SkBaseMutex* mutex = NULL); + virtual ~SkPixelRef(); /** Return the pixel memory returned from lockPixels, or null if the lockCount is 0. @@ -209,6 +211,22 @@ public: SK_DEFINE_FLATTENABLE_TYPE(SkPixelRef) + // Register a listener that may be called the next time our generation ID changes. + // + // We'll only call the listener if we're confident that we are the only SkPixelRef with this + // generation ID. If our generation ID changes and we decide not to call the listener, we'll + // never call it: you must add a new listener for each generation ID change. We also won't call + // the listener when we're certain no one knows what our generation ID is. + // + // This can be used to invalidate caches keyed by SkPixelRef generation ID. + struct GenIDChangeListener { + virtual ~GenIDChangeListener() {} + virtual void onChange() = 0; + }; + + // Takes ownership of listener. + void addGenIDChangeListener(GenIDChangeListener* listener); + protected: /** Called when the lockCount goes from 0 to 1. The caller will have already acquire a mutex for thread safety, so this method need not do that. @@ -254,17 +272,15 @@ protected: void setPreLocked(void* pixels, SkColorTable* ctable); private: - SkBaseMutex* fMutex; // must remain in scope for the life of this object void* fPixels; SkColorTable* fColorTable; // we do not track ownership, subclass does int fLockCount; mutable uint32_t fGenerationID; + mutable bool fUniqueGenerationID; - // SkBitmap is only a friend so that when copying, it can modify the new SkPixelRef to have the - // same fGenerationID as the original. - friend class SkBitmap; + SkTDArray fGenIDChangeListeners; // pointers are owned SkString fURI; @@ -273,8 +289,16 @@ private: // only ever set in constructor, const after that bool fPreLocked; + void needsNewGenID(); + void callGenIDChangeListeners(); + void setMutex(SkBaseMutex* mutex); + // When copying a bitmap to another with the same shape and config, we can safely + // clone the pixelref generation ID too, which makes them equivalent under caching. + friend class SkBitmap; // only for cloneGenID + void cloneGenID(const SkPixelRef&); + typedef SkFlattenable INHERITED; }; diff --git a/include/gpu/GrContext.h b/include/gpu/GrContext.h index 7716f9ae7c..2b306c0158 100644 --- a/include/gpu/GrContext.h +++ b/include/gpu/GrContext.h @@ -131,11 +131,14 @@ public: * @param srcData Pointer to the pixel values. * @param rowBytes The number of bytes between rows of the texture. Zero * implies tightly packed rows. + * @param cacheKey (optional) If non-NULL, we'll write the cache key we used to cacheKey. */ GrTexture* createTexture(const GrTextureParams* params, const GrTextureDesc& desc, const GrCacheID& cacheID, - void* srcData, size_t rowBytes); + void* srcData, + size_t rowBytes, + GrResourceKey* cacheKey = NULL); /** * Search for an entry based on key and dimensions. If found, ref it and return it. The return diff --git a/src/core/SkBitmap.cpp b/src/core/SkBitmap.cpp index 046e20ab62..2f1c9a49ad 100644 --- a/src/core/SkBitmap.cpp +++ b/src/core/SkBitmap.cpp @@ -1057,7 +1057,8 @@ bool SkBitmap::copyTo(SkBitmap* dst, Config dstConfig, Allocator* alloc) const { if (tmpSrc.config() == dstConfig && NULL == alloc) { dst->swap(tmpSrc); if (dst->pixelRef() && this->config() == dstConfig) { - dst->pixelRef()->fGenerationID = fPixelRef->getGenerationID(); + // TODO(scroggo): fix issue 1742 + dst->pixelRef()->cloneGenID(*fPixelRef); } return true; } @@ -1097,8 +1098,9 @@ bool SkBitmap::copyTo(SkBitmap* dst, Config dstConfig, Allocator* alloc) const { if (tmpDst.getSize() == src->getSize()) { memcpy(tmpDst.getPixels(), src->getPixels(), src->getSafeSize()); SkPixelRef* pixelRef = tmpDst.pixelRef(); - if (pixelRef != NULL) { - pixelRef->fGenerationID = this->getGenerationID(); + if (NULL != pixelRef && NULL != fPixelRef) { + // TODO(scroggo): fix issue 1742 + pixelRef->cloneGenID(*fPixelRef); } } else { const char* srcP = reinterpret_cast(src->getPixels()); @@ -1152,7 +1154,8 @@ bool SkBitmap::deepCopyTo(SkBitmap* dst, Config dstConfig) const { if (pixelRef) { uint32_t rowBytes; if (dstConfig == fConfig) { - pixelRef->fGenerationID = fPixelRef->getGenerationID(); + // TODO(scroggo): fix issue 1742 + pixelRef->cloneGenID(*fPixelRef); // Use the same rowBytes as the original. rowBytes = fRowBytes; } else { diff --git a/src/core/SkMessageBus.h b/src/core/SkMessageBus.h new file mode 100644 index 0000000000..0a408319c1 --- /dev/null +++ b/src/core/SkMessageBus.h @@ -0,0 +1,115 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkMessageBus_DEFINED +#define SkMessageBus_DEFINED + +#include "SkOnce.h" +#include "SkTDArray.h" +#include "SkThread.h" +#include "SkTypes.h" + +template +class SkMessageBus : SkNoncopyable { +public: + // Post a message to be received by all Inboxes for this Message type. Threadsafe. + static void Post(const Message& m); + + class Inbox { + public: + Inbox(); + ~Inbox(); + + // Overwrite out with all the messages we've received since the last call. Threadsafe. + void poll(SkTDArray* out); + + private: + SkTDArray fMessages; + SkMutex fMessagesMutex; + + friend class SkMessageBus; + void receive(const Message& m); // SkMessageBus is a friend only to call this. + }; + +private: + SkMessageBus(); + static SkMessageBus* Get(); + static void New(SkMessageBus**); + + SkTDArray fInboxes; + SkMutex fInboxesMutex; +}; + +// ----------------------- Implementation of SkMessageBus::Inbox ----------------------- + +template +SkMessageBus::Inbox::Inbox() { + // Register ourselves with the corresponding message bus. + SkMessageBus* bus = SkMessageBus::Get(); + SkAutoMutexAcquire lock(bus->fInboxesMutex); + bus->fInboxes.push(this); +} + +template +SkMessageBus::Inbox::~Inbox() { + // Remove ourselves from the corresponding message bus. + SkMessageBus* bus = SkMessageBus::Get(); + SkAutoMutexAcquire lock(bus->fInboxesMutex); + // This is a cheaper fInboxes.remove(fInboxes.find(this)) when order doesn't matter. + for (int i = 0; i < bus->fInboxes.count(); i++) { + if (this == bus->fInboxes[i]) { + bus->fInboxes.removeShuffle(i); + break; + } + } +} + +template +void SkMessageBus::Inbox::receive(const Message& m) { + SkAutoMutexAcquire lock(fMessagesMutex); + fMessages.push(m); +} + +template +void SkMessageBus::Inbox::poll(SkTDArray* messages) { + SkASSERT(NULL != messages); + messages->reset(); + SkAutoMutexAcquire lock(fMessagesMutex); + messages->swap(fMessages); +} + +// ----------------------- Implementation of SkMessageBus ----------------------- + +template +SkMessageBus::SkMessageBus() {} + +template +/*static*/ void SkMessageBus::New(SkMessageBus** bus) { + *bus = new SkMessageBus(); +} + +template +/*static*/ SkMessageBus* SkMessageBus::Get() { + // The first time this method is called, create the singleton bus for this message type. + static SkMessageBus* bus = NULL; + SK_DECLARE_STATIC_ONCE(once); + SkOnce(&once, &New, &bus); + + SkASSERT(bus != NULL); + return bus; +} + +template +/*static*/ void SkMessageBus::Post(const Message& m) { + SkMessageBus* bus = SkMessageBus::Get(); + SkAutoMutexAcquire lock(bus->fInboxesMutex); + for (int i = 0; i < bus->fInboxes.count(); i++) { + bus->fInboxes[i]->receive(m); + } +} + +#endif // SkMessageBus_DEFINED diff --git a/src/core/SkPixelRef.cpp b/src/core/SkPixelRef.cpp index 08775f24de..972474ccc3 100644 --- a/src/core/SkPixelRef.cpp +++ b/src/core/SkPixelRef.cpp @@ -85,12 +85,12 @@ void SkPixelRef::setMutex(SkBaseMutex* mutex) { // just need a > 0 value, so pick a funny one to aid in debugging #define SKPIXELREF_PRELOCKED_LOCKCOUNT 123456789 -SkPixelRef::SkPixelRef(SkBaseMutex* mutex) : fPreLocked(false) { +SkPixelRef::SkPixelRef(SkBaseMutex* mutex) { this->setMutex(mutex); fPixels = NULL; fColorTable = NULL; // we do not track ownership of this fLockCount = 0; - fGenerationID = 0; // signal to rebuild + this->needsNewGenID(); fIsImmutable = false; fPreLocked = false; } @@ -103,9 +103,26 @@ SkPixelRef::SkPixelRef(SkFlattenableReadBuffer& buffer, SkBaseMutex* mutex) fLockCount = 0; fIsImmutable = buffer.readBool(); fGenerationID = buffer.readUInt(); + fUniqueGenerationID = false; // Conservatively assuming the original still exists. fPreLocked = false; } +SkPixelRef::~SkPixelRef() { + this->callGenIDChangeListeners(); +} + +void SkPixelRef::needsNewGenID() { + fGenerationID = 0; + fUniqueGenerationID = false; +} + +void SkPixelRef::cloneGenID(const SkPixelRef& that) { + // This is subtle. We must call that.getGenerationID() to make sure its genID isn't 0. + this->fGenerationID = that.getGenerationID(); + this->fUniqueGenerationID = false; + that.fUniqueGenerationID = false; +} + void SkPixelRef::setPreLocked(void* pixels, SkColorTable* ctable) { #ifndef SK_IGNORE_PIXELREF_SETPRELOCKED // only call me in your constructor, otherwise fLockCount tracking can get @@ -129,6 +146,7 @@ void SkPixelRef::flatten(SkFlattenableWriteBuffer& buffer) const { buffer.writeUInt(0); } else { buffer.writeUInt(fGenerationID); + fUniqueGenerationID = false; // Conservative, a copy is probably about to exist. } } @@ -178,18 +196,39 @@ bool SkPixelRef::onDecodeInto(int pow2, SkBitmap* bitmap) { uint32_t SkPixelRef::getGenerationID() const { if (0 == fGenerationID) { fGenerationID = SkNextPixelRefGenerationID(); + fUniqueGenerationID = true; // The only time we can be sure of this! } return fGenerationID; } +void SkPixelRef::addGenIDChangeListener(GenIDChangeListener* listener) { + if (NULL == listener || !fUniqueGenerationID) { + // No point in tracking this if we're not going to call it. + SkDELETE(listener); + return; + } + *fGenIDChangeListeners.append() = listener; +} + +void SkPixelRef::callGenIDChangeListeners() { + // We don't invalidate ourselves if we think another SkPixelRef is sharing our genID. + if (fUniqueGenerationID) { + for (int i = 0; i < fGenIDChangeListeners.count(); i++) { + fGenIDChangeListeners[i]->onChange(); + } + } + // Listeners get at most one shot, so whether these triggered or not, blow them away. + fGenIDChangeListeners.deleteAll(); +} + void SkPixelRef::notifyPixelsChanged() { #ifdef SK_DEBUG if (fIsImmutable) { SkDebugf("========== notifyPixelsChanged called on immutable pixelref"); } #endif - // this signals us to recompute this next time around - fGenerationID = 0; + this->callGenIDChangeListeners(); + this->needsNewGenID(); } void SkPixelRef::setImmutable() { diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp index c5933ee47e..3311d255b1 100644 --- a/src/gpu/GrContext.cpp +++ b/src/gpu/GrContext.cpp @@ -393,7 +393,8 @@ GrTexture* GrContext::createTexture(const GrTextureParams* params, const GrTextureDesc& desc, const GrCacheID& cacheID, void* srcData, - size_t rowBytes) { + size_t rowBytes, + GrResourceKey* cacheKey) { SK_TRACE_EVENT0("GrContext::createTexture"); GrResourceKey resourceKey = GrTexture::ComputeKey(fGpu, params, desc, cacheID); @@ -412,6 +413,10 @@ GrTexture* GrContext::createTexture(const GrTextureParams* params, // necessary space before adding it. fTextureCache->purgeAsNeeded(1, texture->sizeInBytes()); fTextureCache->addResource(resourceKey, texture); + + if (NULL != cacheKey) { + *cacheKey = resourceKey; + } } return texture; diff --git a/src/gpu/GrResourceCache.cpp b/src/gpu/GrResourceCache.cpp index 1d0f3845c5..5cf3f82c7d 100644 --- a/src/gpu/GrResourceCache.cpp +++ b/src/gpu/GrResourceCache.cpp @@ -284,6 +284,8 @@ void GrResourceCache::purgeAsNeeded(int extraCount, size_t extraBytes) { fPurging = true; + this->purgeInvalidated(); + this->internalPurge(extraCount, extraBytes); if (((fEntryCount+extraCount) > fMaxCount || (fEntryBytes+extraBytes) > fMaxBytes) && @@ -298,6 +300,25 @@ void GrResourceCache::purgeAsNeeded(int extraCount, size_t extraBytes) { fPurging = false; } +void GrResourceCache::purgeInvalidated() { + SkTDArray invalidated; + fInvalidationInbox.poll(&invalidated); + + for (int i = 0; i < invalidated.count(); i++) { + // We're somewhat missing an opportunity here. We could use the + // default find functor that gives us back resources whether we own + // them exclusively or not, and when they're not exclusively owned mark + // them for purging later when they do become exclusively owned. + // + // This is complicated and confusing. May try this in the future. For + // now, these resources are just LRU'd as if we never got the message. + GrResourceEntry* entry = fCache.find(invalidated[i].key, GrTFindUnreffedFunctor()); + if (entry) { + this->deleteResource(entry); + } + } +} + void GrResourceCache::deleteResource(GrResourceEntry* entry) { SkASSERT(1 == entry->fResource->getRefCnt()); diff --git a/src/gpu/GrResourceCache.h b/src/gpu/GrResourceCache.h index a9adf91b6f..38378ac771 100644 --- a/src/gpu/GrResourceCache.h +++ b/src/gpu/GrResourceCache.h @@ -15,6 +15,7 @@ #include "GrTypes.h" #include "GrTHashTable.h" #include "GrBinHashKey.h" +#include "SkMessageBus.h" #include "SkTInternalLList.h" class GrResource; @@ -141,6 +142,11 @@ private: Key fKey; }; +// The cache listens for these messages to purge junk resources proactively. +struct GrResourceInvalidatedMessage { + GrResourceKey key; +}; + /////////////////////////////////////////////////////////////////////////////// class GrResourceEntry { @@ -395,6 +401,10 @@ private: void internalPurge(int extraCount, size_t extraBytes); + // Listen for messages that a resource has been invalidated and purge cached junk proactively. + SkMessageBus::Inbox fInvalidationInbox; + void purgeInvalidated(); + #ifdef SK_DEBUG static size_t countBytes(const SkTInternalLList& list); #endif diff --git a/src/gpu/SkGr.cpp b/src/gpu/SkGr.cpp index 9cafbf69c0..18c54ef3c2 100644 --- a/src/gpu/SkGr.cpp +++ b/src/gpu/SkGr.cpp @@ -7,6 +7,9 @@ #include "SkGr.h" #include "SkConfig8888.h" +#include "SkMessageBus.h" +#include "SkPixelRef.h" +#include "GrResourceCache.h" /* Fill out buffer with the compressed format Ganesh expects from a colortable based bitmap. [palette (colortable) + indices]. @@ -86,6 +89,28 @@ static void generate_bitmap_texture_desc(const SkBitmap& bitmap, GrTextureDesc* desc->fSampleCnt = 0; } +namespace { + +// When the SkPixelRef genID changes, invalidate a corresponding GrResource described by key. +class GrResourceInvalidator : public SkPixelRef::GenIDChangeListener { +public: + explicit GrResourceInvalidator(GrResourceKey key) : fKey(key) {} +private: + GrResourceKey fKey; + + virtual void onChange() SK_OVERRIDE { + const GrResourceInvalidatedMessage message = { fKey }; + SkMessageBus::Post(message); + } +}; + +} // namespace + +static void add_genID_listener(GrResourceKey key, SkPixelRef* pixelRef) { + SkASSERT(NULL != pixelRef); + pixelRef->addGenIDChangeListener(SkNEW_ARGS(GrResourceInvalidator, (key))); +} + static GrTexture* sk_gr_create_bitmap_texture(GrContext* ctx, bool cache, const GrTextureParams* params, @@ -112,7 +137,12 @@ static GrTexture* sk_gr_create_bitmap_texture(GrContext* ctx, if (cache) { GrCacheID cacheID; generate_bitmap_cache_id(origBitmap, &cacheID); - return ctx->createTexture(params, desc, cacheID, storage.get(), bitmap->width()); + + GrResourceKey key; + GrTexture* result = ctx->createTexture(params, desc, cacheID, + storage.get(), bitmap->width(), &key); + add_genID_listener(key, origBitmap.pixelRef()); + return result; } else { GrTexture* result = ctx->lockAndRefScratchTexture(desc, GrContext::kExact_ScratchTexMatch); @@ -137,8 +167,13 @@ static GrTexture* sk_gr_create_bitmap_texture(GrContext* ctx, // This texture is likely to be used again so leave it in the cache GrCacheID cacheID; generate_bitmap_cache_id(origBitmap, &cacheID); - return ctx->createTexture(params, desc, cacheID, bitmap->getPixels(), bitmap->rowBytes()); - } else { + + GrResourceKey key; + GrTexture* result = ctx->createTexture(params, desc, cacheID, + bitmap->getPixels(), bitmap->rowBytes(), &key); + add_genID_listener(key, origBitmap.pixelRef()); + return result; + } else { // This texture is unlikely to be used again (in its present form) so // just use a scratch texture. This will remove the texture from the // cache so no one else can find it. Additionally, once unlocked, the diff --git a/tests/MessageBusTest.cpp b/tests/MessageBusTest.cpp new file mode 100644 index 0000000000..0e718cbebd --- /dev/null +++ b/tests/MessageBusTest.cpp @@ -0,0 +1,57 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkMessageBus.h" +#include "Test.h" +#include "TestClassDef.h" + +namespace { + +struct TestMessage { + int x; + float y; +}; + +} // namespace + +DEF_TEST(MessageBus, r) { + // Register two inboxes to receive all TestMessages. + SkMessageBus::Inbox inbox1, inbox2; + + // Send two messages. + const TestMessage m1 = { 5, 4.2f }; + const TestMessage m2 = { 6, 4.3f }; + SkMessageBus::Post(m1); + SkMessageBus::Post(m2); + + // Make sure we got two. + SkTDArray messages; + inbox1.poll(&messages); + REPORTER_ASSERT(r, 2 == messages.count()); + REPORTER_ASSERT(r, 5 == messages[0].x); + REPORTER_ASSERT(r, 6 == messages[1].x); + + // Send another; check we get just that one. + const TestMessage m3 = { 1, 0.3f }; + SkMessageBus::Post(m3); + inbox1.poll(&messages); + REPORTER_ASSERT(r, 1 == messages.count()); + REPORTER_ASSERT(r, 1 == messages[0].x); + + // Nothing was sent since the last read. + inbox1.poll(&messages); + REPORTER_ASSERT(r, 0 == messages.count()); + + // Over all this time, inbox2 should have piled up 3 messages. + inbox2.poll(&messages); + REPORTER_ASSERT(r, 3 == messages.count()); + REPORTER_ASSERT(r, 5 == messages[0].x); + REPORTER_ASSERT(r, 6 == messages[1].x); + REPORTER_ASSERT(r, 1 == messages[2].x); +} + +// Multithreaded tests tbd. diff --git a/tests/PixelRefTest.cpp b/tests/PixelRefTest.cpp new file mode 100644 index 0000000000..ce2575e830 --- /dev/null +++ b/tests/PixelRefTest.cpp @@ -0,0 +1,49 @@ +#include "Test.h" +#include "TestClassDef.h" + +#include "SkPixelRef.h" +#include "SkMallocPixelRef.h" + +namespace { + +class TestListener : public SkPixelRef::GenIDChangeListener { +public: + explicit TestListener(int* ptr) : fPtr(ptr) {} + void onChange() SK_OVERRIDE { (*fPtr)++; } +private: + int* fPtr; +}; + +} // namespace + +DEF_TEST(PixelRef_GenIDChange, r) { + SkMallocPixelRef pixelRef(NULL, 0, NULL); // We don't really care about the pixels here. + + // Register a listener. + int count = 0; + pixelRef.addGenIDChangeListener(SkNEW_ARGS(TestListener, (&count))); + REPORTER_ASSERT(r, 0 == count); + + // No one has looked at our pixelRef's generation ID, so invalidating it doesn't make sense. + // (An SkPixelRef tree falls in the forest but there's nobody around to hear it. Do we care?) + pixelRef.notifyPixelsChanged(); + REPORTER_ASSERT(r, 0 == count); + + // Force the generation ID to be calculated. + REPORTER_ASSERT(r, 0 != pixelRef.getGenerationID()); + + // Our listener was dropped in the first call to notifyPixelsChanged(). This is a no-op. + pixelRef.notifyPixelsChanged(); + REPORTER_ASSERT(r, 0 == count); + + // Force the generation ID to be recalculated, then add a listener. + REPORTER_ASSERT(r, 0 != pixelRef.getGenerationID()); + pixelRef.addGenIDChangeListener(SkNEW_ARGS(TestListener, (&count))); + pixelRef.notifyPixelsChanged(); + REPORTER_ASSERT(r, 1 == count); + + // Quick check that NULL is safe. + REPORTER_ASSERT(r, 0 != pixelRef.getGenerationID()); + pixelRef.addGenIDChangeListener(NULL); + pixelRef.notifyPixelsChanged(); +}