Allocate bitmap backing buffers in the Java heap.
Change-Id: I60f6ccff13357c1c518e9d56b02fe0171637edd1
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index 9467be8..fc358a1 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -2,6 +2,9 @@
#include "jni.h"
#include "GraphicsJNI.h"
+
+#include "SkCanvas.h"
+#include "SkDevice.h"
#include "SkPicture.h"
#include "SkRegion.h"
#include <android_runtime/AndroidRuntime.h>
@@ -163,7 +166,6 @@
static jclass gBitmap_class;
static jfieldID gBitmap_nativeInstanceID;
static jmethodID gBitmap_constructorMethodID;
-static jmethodID gBitmap_allocBufferMethodID;
static jclass gBitmapConfig_class;
static jfieldID gBitmapConfig_nativeInstanceID;
@@ -360,16 +362,16 @@
///////////////////////////////////////////////////////////////////////////////////////////
-jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, bool isMutable,
- jbyteArray ninepatch, int density)
+jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, jbyteArray buffer,
+ bool isMutable, jbyteArray ninepatch, int density)
{
- SkASSERT(bitmap != NULL);
- SkASSERT(NULL != bitmap->pixelRef());
+ SkASSERT(bitmap);
+ SkASSERT(bitmap->pixelRef());
jobject obj = env->AllocObject(gBitmap_class);
if (obj) {
env->CallVoidMethod(obj, gBitmap_constructorMethodID,
- (jint)bitmap, isMutable, ninepatch, density);
+ (jint)bitmap, buffer, isMutable, ninepatch, density);
if (hasException(env)) {
obj = NULL;
}
@@ -377,6 +379,13 @@
return obj;
}
+jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, bool isMutable,
+ jbyteArray ninepatch, int density)
+{
+ return createBitmap(env, bitmap, NULL, isMutable, ninepatch, density);
+}
+
+
jobject GraphicsJNI::createBitmapRegionDecoder(JNIEnv* env, SkBitmapRegionDecoder* bitmap)
{
SkASSERT(bitmap != NULL);
@@ -408,8 +417,6 @@
return obj;
}
-#include "SkPixelRef.h"
-
static JNIEnv* vm2env(JavaVM* vm)
{
JNIEnv* env = NULL;
@@ -427,87 +434,125 @@
///////////////////////////////////////////////////////////////////////////////
-#include "SkMallocPixelRef.h"
+AndroidPixelRef::AndroidPixelRef(JNIEnv* env, void* storage, size_t size, jbyteArray storageObj,
+ SkColorTable* ctable) : SkMallocPixelRef(storage, size, ctable) {
+ SkASSERT(storage);
+ SkASSERT(env);
-/* Extend SkMallocPixelRef to inform the VM when we free up the storage
-*/
-class AndroidPixelRef : public SkMallocPixelRef {
-public:
- /** Allocate the specified buffer for pixels. The memory is freed when the
- last owner of this pixelref is gone. Our caller has already informed
- the VM of our allocation.
- */
- AndroidPixelRef(JNIEnv* env, void* storage, size_t size,
- SkColorTable* ctable) : SkMallocPixelRef(storage, size, ctable) {
- SkASSERT(storage);
- SkASSERT(env);
-
- if (env->GetJavaVM(&fVM) != JNI_OK) {
- SkDebugf("------ [%p] env->GetJavaVM failed\n", env);
- sk_throw();
- }
+ if (env->GetJavaVM(&fVM) != JNI_OK) {
+ SkDebugf("------ [%p] env->GetJavaVM failed\n", env);
+ sk_throw();
}
+ fStorageObj = storageObj;
+ fHasGlobalRef = false;
+ fGlobalRefCnt = 0;
- virtual ~AndroidPixelRef() {
+ // If storageObj is NULL, the memory was NOT allocated on the Java heap
+ fOnJavaHeap = (storageObj != NULL);
+
+}
+
+AndroidPixelRef::~AndroidPixelRef() {
+ if (fOnJavaHeap) {
JNIEnv* env = vm2env(fVM);
-// SkDebugf("-------------- inform VM we're releasing %d bytes\n", this->getSize());
- jlong jsize = this->getSize(); // the VM wants longs for the size
- env->CallVoidMethod(gVMRuntime_singleton,
- gVMRuntime_trackExternalFreeMethodID,
- jsize);
- if (GraphicsJNI::hasException(env)) {
- env->ExceptionClear();
+
+ if (fStorageObj && fHasGlobalRef) {
+ env->DeleteGlobalRef(fStorageObj);
+ }
+ fStorageObj = NULL;
+
+ // Set this to NULL to prevent the SkMallocPixelRef destructor
+ // from freeing the memory.
+ fStorage = NULL;
+ }
+}
+
+void AndroidPixelRef::setLocalJNIRef(jbyteArray arr) {
+ if (!fHasGlobalRef) {
+ fStorageObj = arr;
+ }
+}
+
+void AndroidPixelRef::globalRef() {
+ if (fOnJavaHeap && sk_atomic_inc(&fGlobalRefCnt) == 0) {
+ JNIEnv *env = vm2env(fVM);
+ if (fStorageObj == NULL) {
+ SkDebugf("Cannot create a global ref, fStorage obj is NULL");
+ sk_throw();
+ }
+ if (fHasGlobalRef) {
+ // This should never happen
+ SkDebugf("Already holding a global ref");
+ sk_throw();
+ }
+
+ fStorageObj = (jbyteArray) env->NewGlobalRef(fStorageObj);
+ // TODO: Check for failure here
+ fHasGlobalRef = true;
+ }
+ ref();
+}
+
+void AndroidPixelRef::globalUnref() {
+ if (fOnJavaHeap && sk_atomic_dec(&fGlobalRefCnt) == 1) {
+ JNIEnv *env = vm2env(fVM);
+ if (!fHasGlobalRef) {
+ SkDebugf("We don't have a global ref!");
+ sk_throw();
+ }
+ env->DeleteGlobalRef(fStorageObj);
+ fStorageObj = NULL;
+ fHasGlobalRef = false;
+ }
+ unref();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+jbyteArray GraphicsJNI::allocateJavaPixelRef(JNIEnv* env, SkBitmap* bitmap,
+ SkColorTable* ctable) {
+ Sk64 size64 = bitmap->getSize64();
+ if (size64.isNeg() || !size64.is32()) {
+ doThrow(env, "java/lang/IllegalArgumentException",
+ "bitmap size exceeds 32bits");
+ return NULL;
+ }
+
+ size_t size = size64.get32();
+ jbyteArray arrayObj = env->NewByteArray(size);
+ if (arrayObj) {
+ jbyte *addr = env->GetByteArrayElements(arrayObj, NULL);
+ env->ReleaseByteArrayElements(arrayObj, addr, 0);
+ if (addr) {
+ SkPixelRef* pr = new AndroidPixelRef(env, (void*) addr, size, arrayObj, ctable);
+ bitmap->setPixelRef(pr)->unref();
+ // since we're already allocated, we lockPixels right away
+ // HeapAllocator behaves this way too
+ bitmap->lockPixels();
}
}
-private:
- JavaVM* fVM;
-};
+ return arrayObj;
+}
-bool GraphicsJNI::setJavaPixelRef(JNIEnv* env, SkBitmap* bitmap,
- SkColorTable* ctable, bool reportSizeToVM) {
+bool GraphicsJNI::mallocPixelRef(JNIEnv* env, SkBitmap* bitmap, SkColorTable* ctable) {
Sk64 size64 = bitmap->getSize64();
if (size64.isNeg() || !size64.is32()) {
doThrow(env, "java/lang/IllegalArgumentException",
"bitmap size exceeds 32bits");
return false;
}
-
+
size_t size = size64.get32();
- jlong jsize = size; // the VM wants longs for the size
- if (reportSizeToVM) {
- // SkDebugf("-------------- inform VM we've allocated %d bytes\n", size);
- bool r = env->CallBooleanMethod(gVMRuntime_singleton,
- gVMRuntime_trackExternalAllocationMethodID,
- jsize);
- if (GraphicsJNI::hasException(env)) {
- return false;
- }
- if (!r) {
- LOGE("VM won't let us allocate %zd bytes\n", size);
- doThrowOOME(env, "bitmap size exceeds VM budget");
- return false;
- }
- }
+
// call the version of malloc that returns null on failure
void* addr = sk_malloc_flags(size, 0);
+
if (NULL == addr) {
- if (reportSizeToVM) {
- // SkDebugf("-------------- inform VM we're releasing %d bytes which we couldn't allocate\n", size);
- // we didn't actually allocate it, so inform the VM
- env->CallVoidMethod(gVMRuntime_singleton,
- gVMRuntime_trackExternalFreeMethodID,
- jsize);
- if (!GraphicsJNI::hasException(env)) {
- doThrowOOME(env, "bitmap size too large for malloc");
- }
- }
return false;
}
-
- SkPixelRef* pr = reportSizeToVM ?
- new AndroidPixelRef(env, addr, size, ctable) :
- new SkMallocPixelRef(addr, size, ctable);
+
+ SkPixelRef* pr = new AndroidPixelRef(env, addr, size, NULL, ctable);
bitmap->setPixelRef(pr)->unref();
// since we're already allocated, we lockPixels right away
// HeapAllocator behaves this way too
@@ -517,8 +562,9 @@
///////////////////////////////////////////////////////////////////////////////
-JavaPixelAllocator::JavaPixelAllocator(JNIEnv* env, bool reportSizeToVM)
- : fReportSizeToVM(reportSizeToVM) {
+JavaPixelAllocator::JavaPixelAllocator(JNIEnv* env, bool allocateInJavaHeap)
+ : fAllocateInJavaHeap(allocateInJavaHeap),
+ fStorageObj(NULL) {
if (env->GetJavaVM(&fVM) != JNI_OK) {
SkDebugf("------ [%p] env->GetJavaVM failed\n", env);
sk_throw();
@@ -527,7 +573,19 @@
bool JavaPixelAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) {
JNIEnv* env = vm2env(fVM);
- return GraphicsJNI::setJavaPixelRef(env, bitmap, ctable, fReportSizeToVM);
+
+ // If allocating in the Java heap, only allow a single object to be
+ // allocated for the lifetime of this object.
+ if (fStorageObj != NULL) {
+ SkDebugf("ERROR: One-shot allocator has already allocated\n");
+ sk_throw();
+ }
+
+ if (fAllocateInJavaHeap) {
+ fStorageObj = GraphicsJNI::allocateJavaPixelRef(env, bitmap, ctable);
+ return fStorageObj != NULL;
+ }
+ return GraphicsJNI::mallocPixelRef(env, bitmap, ctable);
}
////////////////////////////////////////////////////////////////////////////////
@@ -568,6 +626,25 @@
////////////////////////////////////////////////////////////////////////////////
+JavaHeapBitmapRef::JavaHeapBitmapRef(JNIEnv* env, SkBitmap* nativeBitmap, jbyteArray buffer) {
+ fEnv = env;
+ fNativeBitmap = nativeBitmap;
+ fBuffer = buffer;
+
+ // If the buffer is NULL, the backing memory wasn't allocated on the Java heap
+ if (fBuffer) {
+ ((AndroidPixelRef*) fNativeBitmap->pixelRef())->setLocalJNIRef(fBuffer);
+ }
+}
+
+JavaHeapBitmapRef::~JavaHeapBitmapRef() {
+ if (fBuffer) {
+ ((AndroidPixelRef*) fNativeBitmap->pixelRef())->setLocalJNIRef(NULL);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
static jclass make_globalref(JNIEnv* env, const char classname[])
{
jclass c = env->FindClass(classname);
@@ -609,10 +686,9 @@
gPointF_yFieldID = getFieldIDCheck(env, gPointF_class, "y", "F");
gBitmap_class = make_globalref(env, "android/graphics/Bitmap");
- gBitmap_nativeInstanceID = getFieldIDCheck(env, gBitmap_class, "mNativeBitmap", "I");
+ gBitmap_nativeInstanceID = getFieldIDCheck(env, gBitmap_class, "mNativeBitmap", "I");
gBitmap_constructorMethodID = env->GetMethodID(gBitmap_class, "<init>",
- "(IZ[BI)V");
-
+ "(I[BZ[BI)V");
gBitmapRegionDecoder_class = make_globalref(env, "android/graphics/BitmapRegionDecoder");
gBitmapRegionDecoder_constructorMethodID = env->GetMethodID(gBitmapRegionDecoder_class, "<init>", "(I)V");