diff options
author | Romain Guy <romainguy@google.com> | 2013-06-26 15:45:41 -0700 |
---|---|---|
committer | Romain Guy <romainguy@google.com> | 2013-06-26 17:15:08 -0700 |
commit | e3b0a0117a2ab4118f868a731b238fe8f2430276 (patch) | |
tree | a4a6ac3783ace541cd35a0f9d3868af6d2bf97b7 /libs/hwui | |
parent | 89dc02a9bed818cc6f5296c97eb504ccb010db42 (diff) | |
download | frameworks_base-e3b0a0117a2ab4118f868a731b238fe8f2430276.zip frameworks_base-e3b0a0117a2ab4118f868a731b238fe8f2430276.tar.gz frameworks_base-e3b0a0117a2ab4118f868a731b238fe8f2430276.tar.bz2 |
Refcount 9-patches and properly handle GC events
This change adds refcounting of Res_png_9patch instances, the native
data structure used to represent 9-patches. The Dalvik NinePatch class
now holds a native pointer instead of a Dalvik byte[]. This pointer
is used whenever we need to draw the 9-patch (software or hardware.)
Since we are now tracking garbage collection of NinePatch objects
libhwui's PatchCache must keep a list of free blocks in the VBO
used to store the meshes.
This change also removes unnecessary instances tracking from
GLES20DisplayList. Bitmaps and 9-patches are refcounted at the
native level and do not need to be tracked by the Dalvik layer.
Change-Id: Ib8682d573a538aaf1945f8ec5a9bd5da5d16f74b
Diffstat (limited to 'libs/hwui')
-rw-r--r-- | libs/hwui/Caches.cpp | 1 | ||||
-rw-r--r-- | libs/hwui/DisplayList.cpp | 12 | ||||
-rw-r--r-- | libs/hwui/DisplayList.h | 4 | ||||
-rw-r--r-- | libs/hwui/DisplayListRenderer.cpp | 6 | ||||
-rw-r--r-- | libs/hwui/DisplayListRenderer.h | 11 | ||||
-rw-r--r-- | libs/hwui/PatchCache.cpp | 152 | ||||
-rw-r--r-- | libs/hwui/PatchCache.h | 55 | ||||
-rw-r--r-- | libs/hwui/PathCache.cpp | 25 | ||||
-rw-r--r-- | libs/hwui/PathCache.h | 2 | ||||
-rw-r--r-- | libs/hwui/ResourceCache.cpp | 52 | ||||
-rw-r--r-- | libs/hwui/ResourceCache.h | 11 |
11 files changed, 299 insertions, 32 deletions
diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp index 1089b7c..6de8c8c 100644 --- a/libs/hwui/Caches.cpp +++ b/libs/hwui/Caches.cpp @@ -286,6 +286,7 @@ void Caches::dumpMemoryUsage(String8 &log) { void Caches::clearGarbage() { textureCache.clearGarbage(); pathCache.clearGarbage(); + patchCache.clearGarbage(); Vector<DisplayList*> displayLists; Vector<Layer*> layers; diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp index 0d4dd1a..cebfd26 100644 --- a/libs/hwui/DisplayList.cpp +++ b/libs/hwui/DisplayList.cpp @@ -100,6 +100,10 @@ void DisplayList::clearResources() { caches.resourceCache.decrementRefcountLocked(mFilterResources.itemAt(i)); } + for (size_t i = 0; i < mPatchResources.size(); i++) { + caches.resourceCache.decrementRefcountLocked(mPatchResources.itemAt(i)); + } + for (size_t i = 0; i < mShaders.size(); i++) { caches.resourceCache.decrementRefcountLocked(mShaders.itemAt(i)); caches.resourceCache.destructorLocked(mShaders.itemAt(i)); @@ -134,6 +138,7 @@ void DisplayList::clearResources() { mBitmapResources.clear(); mOwnedBitmapResources.clear(); mFilterResources.clear(); + mPatchResources.clear(); mShaders.clear(); mSourcePaths.clear(); mPaints.clear(); @@ -201,6 +206,13 @@ void DisplayList::initFromDisplayListRenderer(const DisplayListRenderer& recorde caches.resourceCache.incrementRefcountLocked(resource); } + const Vector<Res_png_9patch*>& patchResources = recorder.getPatchResources(); + for (size_t i = 0; i < patchResources.size(); i++) { + Res_png_9patch* resource = patchResources.itemAt(i); + mPatchResources.add(resource); + caches.resourceCache.incrementRefcountLocked(resource); + } + const Vector<SkiaShader*>& shaders = recorder.getShaders(); for (size_t i = 0; i < shaders.size(); i++) { SkiaShader* resource = shaders.itemAt(i); diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h index 1417df7..194be9e 100644 --- a/libs/hwui/DisplayList.h +++ b/libs/hwui/DisplayList.h @@ -30,8 +30,11 @@ #include <utils/SortedVector.h> #include <utils/String8.h> #include <utils/Vector.h> + #include <cutils/compiler.h> +#include <androidfw/ResourceTypes.h> + #include "utils/LinearAllocator.h" #include "Debug.h" @@ -484,6 +487,7 @@ private: Vector<SkBitmap*> mBitmapResources; Vector<SkBitmap*> mOwnedBitmapResources; Vector<SkiaColorFilter*> mFilterResources; + Vector<Res_png_9patch*> mPatchResources; Vector<SkPaint*> mPaints; Vector<SkPath*> mPaths; diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp index 5d23e1d..9113092 100644 --- a/libs/hwui/DisplayListRenderer.cpp +++ b/libs/hwui/DisplayListRenderer.cpp @@ -57,6 +57,10 @@ void DisplayListRenderer::reset() { mCaches.resourceCache.decrementRefcountLocked(mFilterResources.itemAt(i)); } + for (size_t i = 0; i < mPatchResources.size(); i++) { + mCaches.resourceCache.decrementRefcountLocked(mPatchResources.itemAt(i)); + } + for (size_t i = 0; i < mShaders.size(); i++) { mCaches.resourceCache.decrementRefcountLocked(mShaders.itemAt(i)); } @@ -74,6 +78,7 @@ void DisplayListRenderer::reset() { mBitmapResources.clear(); mOwnedBitmapResources.clear(); mFilterResources.clear(); + mPatchResources.clear(); mSourcePaths.clear(); mShaders.clear(); @@ -318,6 +323,7 @@ status_t DisplayListRenderer::drawBitmapMesh(SkBitmap* bitmap, int meshWidth, in status_t DisplayListRenderer::drawPatch(SkBitmap* bitmap, Res_png_9patch* patch, float left, float top, float right, float bottom, SkPaint* paint) { bitmap = refBitmap(bitmap); + patch = refPatch(patch); paint = refPaint(paint); addDrawOp(new (alloc()) DrawPatchOp(bitmap, patch, left, top, right, bottom, paint)); diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h index 85d6107..03f50c8 100644 --- a/libs/hwui/DisplayListRenderer.h +++ b/libs/hwui/DisplayListRenderer.h @@ -156,6 +156,10 @@ public: return mFilterResources; } + const Vector<Res_png_9patch*>& getPatchResources() const { + return mPatchResources; + } + const Vector<SkiaShader*>& getShaders() const { return mShaders; } @@ -315,9 +319,16 @@ private: return colorFilter; } + inline Res_png_9patch* refPatch(Res_png_9patch* patch) { + mPatchResources.add(patch); + mCaches.resourceCache.incrementRefcount(patch); + return patch; + } + Vector<SkBitmap*> mBitmapResources; Vector<SkBitmap*> mOwnedBitmapResources; Vector<SkiaColorFilter*> mFilterResources; + Vector<Res_png_9patch*> mPatchResources; Vector<SkPaint*> mPaints; DefaultKeyedVector<SkPaint*, SkPaint*> mPaintMap; diff --git a/libs/hwui/PatchCache.cpp b/libs/hwui/PatchCache.cpp index dc69d7f..dc0d98c 100644 --- a/libs/hwui/PatchCache.cpp +++ b/libs/hwui/PatchCache.cpp @@ -32,7 +32,7 @@ namespace uirenderer { PatchCache::PatchCache(): mSize(0), mCache(LruCache<PatchDescription, Patch*>::kUnlimitedCapacity), - mMeshBuffer(0), mGenerationId(0) { + mMeshBuffer(0), mFreeBlocks(NULL), mGenerationId(0) { char property[PROPERTY_VALUE_MAX]; if (property_get(PROPERTY_PATCH_CACHE_SIZE, property, NULL) > 0) { INIT_LOGD(" Setting patch cache size to %skB", property); @@ -97,14 +97,130 @@ void PatchCache::clearCache() { delete i.value(); } mCache.clear(); + + BufferBlock* block = mFreeBlocks; + while (block) { + BufferBlock* next = block->next; + delete block; + block = next; + } + mFreeBlocks = NULL; +} + +void PatchCache::remove(Vector<patch_pair_t>& patchesToRemove, Res_png_9patch* patch) { + LruCache<PatchDescription, Patch*>::Iterator i(mCache); + while (i.next()) { + const PatchDescription& key = i.key(); + if (key.getPatch() == patch) { + patchesToRemove.push(patch_pair_t(&key, i.value())); + } + } +} + +void PatchCache::removeDeferred(Res_png_9patch* patch) { + Mutex::Autolock _l(mLock); + mGarbage.push(patch); +} + +void PatchCache::clearGarbage() { + Vector<patch_pair_t> patchesToRemove; + + { // scope for the mutex + Mutex::Autolock _l(mLock); + size_t count = mGarbage.size(); + for (size_t i = 0; i < count; i++) { + remove(patchesToRemove, mGarbage[i]); + } + mGarbage.clear(); + } + + // TODO: We could sort patchesToRemove by offset to merge + // adjacent free blocks + for (size_t i = 0; i < patchesToRemove.size(); i++) { + const patch_pair_t& pair = patchesToRemove[i]; + + // Add a new free block to the list + const Patch* patch = pair.getSecond(); + BufferBlock* block = new BufferBlock(patch->offset, patch->getSize()); + block->next = mFreeBlocks; + mFreeBlocks = block; + + mSize -= patch->getSize(); + + mCache.remove(*pair.getFirst()); + } + +#if DEBUG_PATCHES + if (patchesToRemove.size() > 0) { + dumpFreeBlocks("Removed garbage"); + } +#endif } void PatchCache::createVertexBuffer() { glBufferData(GL_ARRAY_BUFFER, mMaxSize, NULL, GL_DYNAMIC_DRAW); mSize = 0; + mFreeBlocks = new BufferBlock(0, mMaxSize); mGenerationId++; } +/** + * Sets the mesh's offsets and copies its associated vertices into + * the mesh buffer (VBO). + */ +void PatchCache::setupMesh(Patch* newMesh, TextureVertex* vertices) { + // This call ensures the VBO exists and that it is bound + init(Caches::getInstance()); + + // If we're running out of space, let's clear the entire cache + uint32_t size = newMesh->getSize(); + if (mSize + size > mMaxSize) { + clearCache(); + createVertexBuffer(); + } + + // Find a block where we can fit the mesh + BufferBlock* previous = NULL; + BufferBlock* block = mFreeBlocks; + while (block) { + // The mesh fits + if (block->size >= size) { + break; + } + previous = block; + block = block->next; + } + + // We have enough space left in the buffer, but it's + // too fragmented, let's clear the cache + if (!block) { + clearCache(); + createVertexBuffer(); + previous = NULL; + block = mFreeBlocks; + } + + // Copy the 9patch mesh in the VBO + newMesh->offset = (GLintptr) (block->offset); + newMesh->textureOffset = newMesh->offset + gMeshTextureOffset; + glBufferSubData(GL_ARRAY_BUFFER, newMesh->offset, size, vertices); + + // Remove the block since we've used it entirely + if (block->size == size) { + if (previous) { + previous->next = block->next; + } else { + mFreeBlocks = block->next; + } + } else { + // Resize the block now that it's occupied + block->offset += size; + block->size -= size; + } + + mSize += size; +} + const Patch* PatchCache::get(const AssetAtlas::Entry* entry, const uint32_t bitmapWidth, const uint32_t bitmapHeight, const float pixelWidth, const float pixelHeight, const Res_png_9patch* patch) { @@ -117,6 +233,7 @@ const Patch* PatchCache::get(const AssetAtlas::Entry* entry, TextureVertex* vertices; if (entry) { + // An atlas entry has a UV mapper vertices = newMesh->createMesh(bitmapWidth, bitmapHeight, pixelWidth, pixelHeight, entry->uvMapper, patch); } else { @@ -125,24 +242,13 @@ const Patch* PatchCache::get(const AssetAtlas::Entry* entry, } if (vertices) { - // This call ensures the VBO exists and that it is bound - init(Caches::getInstance()); - - // TODO: Simply remove the oldest items until we have enough room - // This will require to keep a list of free blocks in the VBO - uint32_t size = newMesh->getSize(); - if (mSize + size > mMaxSize) { - clearCache(); - createVertexBuffer(); - } - - newMesh->offset = (GLintptr) mSize; - newMesh->textureOffset = newMesh->offset + gMeshTextureOffset; - mSize += size; - - glBufferSubData(GL_ARRAY_BUFFER, newMesh->offset, size, vertices); + setupMesh(newMesh, vertices); } +#if DEBUG_PATCHES + dumpFreeBlocks("Adding patch"); +#endif + mCache.put(description, newMesh); return newMesh; } @@ -150,5 +256,17 @@ const Patch* PatchCache::get(const AssetAtlas::Entry* entry, return mesh; } +#if DEBUG_PATCHES +void PatchCache::dumpFreeBlocks(const char* prefix) { + String8 dump; + BufferBlock* block = mFreeBlocks; + while (block) { + dump.appendFormat("->(%d, %d)", block->offset, block->size); + block = block->next; + } + ALOGD("%s: Free blocks%s", prefix, dump.string()); +} +#endif + }; // namespace uirenderer }; // namespace android diff --git a/libs/hwui/PatchCache.h b/libs/hwui/PatchCache.h index 1829b89..9f2c9a5 100644 --- a/libs/hwui/PatchCache.h +++ b/libs/hwui/PatchCache.h @@ -26,6 +26,7 @@ #include "AssetAtlas.h" #include "Debug.h" #include "Patch.h" +#include "utils/Pair.h" namespace android { namespace uirenderer { @@ -74,10 +75,20 @@ public: return mGenerationId; } -private: - void clearCache(); - void createVertexBuffer(); + /** + * Removes the entries associated with the specified 9-patch. This is meant + * to be called from threads that are not the EGL context thread (GC thread + * on the VM side for instance.) + */ + void removeDeferred(Res_png_9patch* patch); + /** + * Process deferred removals. + */ + void clearGarbage(); + + +private: struct PatchDescription { PatchDescription(): mPatch(NULL), mBitmapWidth(0), mBitmapHeight(0), mPixelWidth(0), mPixelHeight(0) { @@ -91,6 +102,8 @@ private: hash_t hash() const; + const Res_png_9patch* getPatch() const { return mPatch; } + static int compare(const PatchDescription& lhs, const PatchDescription& rhs); bool operator==(const PatchDescription& other) const { @@ -124,14 +137,50 @@ private: }; // struct PatchDescription + /** + * A buffer block represents an empty range in the mesh buffer + * that can be used to store vertices. + * + * The patch cache maintains a linked-list of buffer blocks + * to track available regions of memory in the VBO. + */ + struct BufferBlock { + BufferBlock(uint32_t offset, uint32_t size): offset(offset), size(size), next(NULL) { + } + + uint32_t offset; + uint32_t size; + + BufferBlock* next; + }; // struct BufferBlock + + typedef Pair<const PatchDescription*, Patch*> patch_pair_t; + + void clearCache(); + void createVertexBuffer(); + + void setupMesh(Patch* newMesh, TextureVertex* vertices); + + void remove(Vector<patch_pair_t>& patchesToRemove, Res_png_9patch* patch); + +#if DEBUG_PATCHES + void dumpFreeBlocks(const char* prefix); +#endif + uint32_t mMaxSize; uint32_t mSize; LruCache<PatchDescription, Patch*> mCache; GLuint mMeshBuffer; + // First available free block inside the mesh buffer + BufferBlock* mFreeBlocks; uint32_t mGenerationId; + + // Garbage tracking, required to handle GC events on the VM side + Vector<Res_png_9patch*> mGarbage; + mutable Mutex mLock; }; // class PatchCache }; // namespace uirenderer diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp index 3ab40da..70ab6e7 100644 --- a/libs/hwui/PathCache.cpp +++ b/libs/hwui/PathCache.cpp @@ -350,8 +350,7 @@ void PathCache::PathProcessor::onProcess(const sp<Task<SkBitmap*> >& task) { // Paths /////////////////////////////////////////////////////////////////////////////// -void PathCache::remove(const path_pair_t& pair) { - Vector<PathDescription> pathsToRemove; +void PathCache::remove(Vector<PathDescription>& pathsToRemove, const path_pair_t& pair) { LruCache<PathDescription, PathTexture*>::Iterator i(mCache); while (i.next()) { @@ -362,10 +361,6 @@ void PathCache::remove(const path_pair_t& pair) { pathsToRemove.push(key); } } - - for (size_t i = 0; i < pathsToRemove.size(); i++) { - mCache.remove(pathsToRemove.itemAt(i)); - } } void PathCache::removeDeferred(SkPath* path) { @@ -374,12 +369,20 @@ void PathCache::removeDeferred(SkPath* path) { } void PathCache::clearGarbage() { - Mutex::Autolock l(mLock); - size_t count = mGarbage.size(); - for (size_t i = 0; i < count; i++) { - remove(mGarbage.itemAt(i)); + Vector<PathDescription> pathsToRemove; + + { // scope for the mutex + Mutex::Autolock l(mLock); + size_t count = mGarbage.size(); + for (size_t i = 0; i < count; i++) { + remove(pathsToRemove, mGarbage.itemAt(i)); + } + mGarbage.clear(); + } + + for (size_t i = 0; i < pathsToRemove.size(); i++) { + mCache.remove(pathsToRemove.itemAt(i)); } - mGarbage.clear(); } /** diff --git a/libs/hwui/PathCache.h b/libs/hwui/PathCache.h index a191f0e..16d20a8 100644 --- a/libs/hwui/PathCache.h +++ b/libs/hwui/PathCache.h @@ -269,7 +269,7 @@ private: * Removes an entry. * The pair must define first=path, second=sourcePath */ - void remove(const path_pair_t& pair); + void remove(Vector<PathDescription>& pathsToRemove, const path_pair_t& pair); /** * Ensures there is enough space in the cache for a texture of the specified diff --git a/libs/hwui/ResourceCache.cpp b/libs/hwui/ResourceCache.cpp index 347bd78..58fa21c 100644 --- a/libs/hwui/ResourceCache.cpp +++ b/libs/hwui/ResourceCache.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#define LOG_TAG "OpenGLRenderer" + #include <SkPixelRef.h> #include "ResourceCache.h" #include "Caches.h" @@ -79,6 +81,10 @@ void ResourceCache::incrementRefcount(SkiaColorFilter* filterResource) { incrementRefcount((void*) filterResource, kColorFilter); } +void ResourceCache::incrementRefcount(Res_png_9patch* patchResource) { + incrementRefcount((void*) patchResource, kNinePatch); +} + void ResourceCache::incrementRefcount(Layer* layerResource) { incrementRefcount((void*) layerResource, kLayer); } @@ -113,6 +119,10 @@ void ResourceCache::incrementRefcountLocked(SkiaColorFilter* filterResource) { incrementRefcountLocked((void*) filterResource, kColorFilter); } +void ResourceCache::incrementRefcountLocked(Res_png_9patch* patchResource) { + incrementRefcountLocked((void*) patchResource, kNinePatch); +} + void ResourceCache::incrementRefcountLocked(Layer* layerResource) { incrementRefcountLocked((void*) layerResource, kLayer); } @@ -142,6 +152,10 @@ void ResourceCache::decrementRefcount(SkiaColorFilter* filterResource) { decrementRefcount((void*) filterResource); } +void ResourceCache::decrementRefcount(Res_png_9patch* patchResource) { + decrementRefcount((void*) patchResource); +} + void ResourceCache::decrementRefcount(Layer* layerResource) { decrementRefcount((void*) layerResource); } @@ -179,6 +193,10 @@ void ResourceCache::decrementRefcountLocked(SkiaColorFilter* filterResource) { decrementRefcountLocked((void*) filterResource); } +void ResourceCache::decrementRefcountLocked(Res_png_9patch* patchResource) { + decrementRefcountLocked((void*) patchResource); +} + void ResourceCache::decrementRefcountLocked(Layer* layerResource) { decrementRefcountLocked((void*) layerResource); } @@ -265,6 +283,30 @@ void ResourceCache::destructorLocked(SkiaColorFilter* resource) { } } +void ResourceCache::destructor(Res_png_9patch* resource) { + Mutex::Autolock _l(mLock); + destructorLocked(resource); +} + +void ResourceCache::destructorLocked(Res_png_9patch* resource) { + ssize_t index = mCache->indexOfKey(resource); + ResourceReference* ref = index >= 0 ? mCache->valueAt(index) : NULL; + if (ref == NULL) { + if (Caches::hasInstance()) { + Caches::getInstance().patchCache.removeDeferred(resource); + } + // If we're not tracking this resource, just delete it + // A Res_png_9patch is actually an array of byte that's larger + // than sizeof(Res_png_9patch). It must be freed as an array. + delete[] (int8_t*) resource; + return; + } + ref->destroyed = true; + if (ref->refCount == 0) { + deleteResourceReferenceLocked(resource, ref); + } +} + /** * Return value indicates whether resource was actually recycled, which happens when RefCnt * reaches 0. @@ -335,6 +377,16 @@ void ResourceCache::deleteResourceReferenceLocked(void* resource, ResourceRefere delete filter; } break; + case kNinePatch: { + if (Caches::hasInstance()) { + Caches::getInstance().patchCache.removeDeferred((Res_png_9patch*) resource); + } + // A Res_png_9patch is actually an array of byte that's larger + // than sizeof(Res_png_9patch). It must be freed as an array. + int8_t* patch = (int8_t*) resource; + delete[] patch; + } + break; case kLayer: { Layer* layer = (Layer*) resource; Caches::getInstance().deleteLayerDeferred(layer); diff --git a/libs/hwui/ResourceCache.h b/libs/hwui/ResourceCache.h index ab493e5..ea0c1b5 100644 --- a/libs/hwui/ResourceCache.h +++ b/libs/hwui/ResourceCache.h @@ -22,7 +22,11 @@ #include <SkBitmap.h> #include <SkiaColorFilter.h> #include <SkiaShader.h> + #include <utils/KeyedVector.h> + +#include <androidfw/ResourceTypes.h> + #include "Layer.h" namespace android { @@ -35,6 +39,7 @@ enum ResourceType { kBitmap, kShader, kColorFilter, + kNinePatch, kPath, kLayer }; @@ -69,35 +74,41 @@ public: void incrementRefcount(SkBitmap* resource); void incrementRefcount(SkiaShader* resource); void incrementRefcount(SkiaColorFilter* resource); + void incrementRefcount(Res_png_9patch* resource); void incrementRefcount(Layer* resource); void incrementRefcountLocked(SkPath* resource); void incrementRefcountLocked(SkBitmap* resource); void incrementRefcountLocked(SkiaShader* resource); void incrementRefcountLocked(SkiaColorFilter* resource); + void incrementRefcountLocked(Res_png_9patch* resource); void incrementRefcountLocked(Layer* resource); void decrementRefcount(SkBitmap* resource); void decrementRefcount(SkPath* resource); void decrementRefcount(SkiaShader* resource); void decrementRefcount(SkiaColorFilter* resource); + void decrementRefcount(Res_png_9patch* resource); void decrementRefcount(Layer* resource); void decrementRefcountLocked(SkBitmap* resource); void decrementRefcountLocked(SkPath* resource); void decrementRefcountLocked(SkiaShader* resource); void decrementRefcountLocked(SkiaColorFilter* resource); + void decrementRefcountLocked(Res_png_9patch* resource); void decrementRefcountLocked(Layer* resource); void destructor(SkPath* resource); void destructor(SkBitmap* resource); void destructor(SkiaShader* resource); void destructor(SkiaColorFilter* resource); + void destructor(Res_png_9patch* resource); void destructorLocked(SkPath* resource); void destructorLocked(SkBitmap* resource); void destructorLocked(SkiaShader* resource); void destructorLocked(SkiaColorFilter* resource); + void destructorLocked(Res_png_9patch* resource); bool recycle(SkBitmap* resource); bool recycleLocked(SkBitmap* resource); |