Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 982b71bf authored by Patrick Dubroy's avatar Patrick Dubroy Committed by Android (Google) Code Review
Browse files

Merge "Allocate bitmap backing buffers in the Java heap."

parents 52e08834 e4ac2d6b
Loading
Loading
Loading
Loading
+15 −13
Original line number Diff line number Diff line
@@ -459,35 +459,37 @@ class GLES20Canvas extends HardwareCanvas {
        // Shaders are ignored when drawing patches
        boolean hasColorFilter = paint != null && setupColorFilter(paint);
        final int nativePaint = paint == null ? 0 : paint.mNativePaint;
        nDrawPatch(mRenderer, bitmap.mNativeBitmap, chunks, dst.left, dst.top,
                dst.right, dst.bottom, nativePaint);
        nDrawPatch(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, chunks,
                dst.left, dst.top, dst.right, dst.bottom, nativePaint);
        if (hasColorFilter) nResetModifiers(mRenderer);
    }

    private native void nDrawPatch(int renderer, int bitmap, byte[] chunks, float left, float top,
            float right, float bottom, int paint);
    private native void nDrawPatch(int renderer, int bitmap, byte[] buffer, byte[] chunks,
            float left, float top, float right, float bottom, int paint);

    @Override
    public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) {
        // Shaders are ignored when drawing bitmaps
        boolean hasColorFilter = paint != null && setupColorFilter(paint);
        final int nativePaint = paint == null ? 0 : paint.mNativePaint;
        nDrawBitmap(mRenderer, bitmap.mNativeBitmap, left, top, nativePaint);
        nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, nativePaint);
        if (hasColorFilter) nResetModifiers(mRenderer);
    }

    private native void nDrawBitmap(int renderer, int bitmap, float left, float top, int paint);
    private native void nDrawBitmap(
            int renderer, int bitmap, byte[] buffer, float left, float top, int paint);

    @Override
    public void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) {
        // Shaders are ignored when drawing bitmaps
        boolean hasColorFilter = paint != null && setupColorFilter(paint);
        final int nativePaint = paint == null ? 0 : paint.mNativePaint;
        nDrawBitmap(mRenderer, bitmap.mNativeBitmap, matrix.native_instance, nativePaint);
        nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer,
                matrix.native_instance, nativePaint);
        if (hasColorFilter) nResetModifiers(mRenderer);
    }

    private native void nDrawBitmap(int renderer, int bitmap, int matrix, int paint);
    private native void nDrawBitmap(int renderer, int bitmap, byte[] buff, int matrix, int paint);

    @Override
    public void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) {
@@ -507,7 +509,7 @@ class GLES20Canvas extends HardwareCanvas {
            bottom = src.bottom;
        }

        nDrawBitmap(mRenderer, bitmap.mNativeBitmap, left, top, right, bottom,
        nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, right, bottom,
                dst.left, dst.top, dst.right, dst.bottom, nativePaint);
        if (hasColorFilter) nResetModifiers(mRenderer);
    }
@@ -517,12 +519,12 @@ class GLES20Canvas extends HardwareCanvas {
        // Shaders are ignored when drawing bitmaps
        boolean hasColorFilter = paint != null && setupColorFilter(paint);
        final int nativePaint = paint == null ? 0 : paint.mNativePaint;
        nDrawBitmap(mRenderer, bitmap.mNativeBitmap, src.left, src.top, src.right, src.bottom,
                dst.left, dst.top, dst.right, dst.bottom, nativePaint);
        nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, src.left, src.top, src.right,
                src.bottom, dst.left, dst.top, dst.right, dst.bottom, nativePaint);
        if (hasColorFilter) nResetModifiers(mRenderer);
    }

    private native void nDrawBitmap(int renderer, int bitmap,
    private native void nDrawBitmap(int renderer, int bitmap, byte[] buffer,
            float srcLeft, float srcTop, float srcRight, float srcBottom,
            float left, float top, float right, float bottom, int paint);

@@ -534,7 +536,7 @@ class GLES20Canvas extends HardwareCanvas {
        final Bitmap.Config config = hasAlpha ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
        final Bitmap b = Bitmap.createBitmap(colors, offset, stride, width, height, config);
        final int nativePaint = paint == null ? 0 : paint.mNativePaint;
        nDrawBitmap(mRenderer, b.mNativeBitmap, x, y, nativePaint);
        nDrawBitmap(mRenderer, b.mNativeBitmap, b.mBuffer, x, y, nativePaint);
        b.recycle();
        if (hasColorFilter) nResetModifiers(mRenderer);
    }
+13 −15
Original line number Diff line number Diff line
#include "SkBitmap.h"
#include "SkPixelRef.h"
#include "SkImageEncoder.h"
#include "SkColorPriv.h"
#include "GraphicsJNI.h"
@@ -210,11 +211,6 @@ static ToColorProc ChooseToColorProc(const SkBitmap& src) {
static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors,
                              int offset, int stride, int width, int height,
                              SkBitmap::Config config, jboolean isMutable) {
    if (width <= 0 || height <= 0) {
        doThrowIAE(env, "width and height must be > 0");
        return NULL;
    }

    if (NULL != jColors) {
        size_t n = env->GetArrayLength(jColors);
        if (n < SkAbs32(stride) * (size_t)height) {
@@ -226,7 +222,9 @@ static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors,
    SkBitmap bitmap;

    bitmap.setConfig(config, width, height);
    if (!GraphicsJNI::setJavaPixelRef(env, &bitmap, NULL, true)) {

    jbyteArray buff = GraphicsJNI::allocateJavaPixelRef(env, &bitmap, NULL);
    if (NULL == buff) {
        return NULL;
    }

@@ -235,21 +233,19 @@ static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors,
                               0, 0, width, height, bitmap);
    }

    return GraphicsJNI::createBitmap(env, new SkBitmap(bitmap), isMutable,
                                     NULL);
    return GraphicsJNI::createBitmap(env, new SkBitmap(bitmap), buff, isMutable, NULL);
}

static jobject Bitmap_copy(JNIEnv* env, jobject, const SkBitmap* src,
                           SkBitmap::Config dstConfig, jboolean isMutable) {
    SkBitmap            result;
    JavaPixelAllocator  allocator(env, true);
    JavaPixelAllocator  allocator(env);

    if (!src->copyTo(&result, dstConfig, &allocator)) {
        return NULL;
    }

    return GraphicsJNI::createBitmap(env, new SkBitmap(result), isMutable,
                                     NULL);
    return GraphicsJNI::createBitmap(env, new SkBitmap(result), allocator.getStorageObj(), isMutable, NULL);
}

static void Bitmap_destructor(JNIEnv* env, jobject, SkBitmap* bitmap) {
@@ -380,7 +376,8 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) {
        }
    }

    if (!GraphicsJNI::setJavaPixelRef(env, bitmap, ctable, true)) {
    jbyteArray buffer = GraphicsJNI::allocateJavaPixelRef(env, bitmap, ctable);
    if (NULL == buffer) {
        ctable->safeUnref();
        delete bitmap;
        return NULL;
@@ -393,7 +390,7 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) {
    memcpy(bitmap->getPixels(), p->readInplace(size), size);
    bitmap->unlockPixels();

    return GraphicsJNI::createBitmap(env, bitmap, isMutable, NULL, density);
    return GraphicsJNI::createBitmap(env, bitmap, buffer, isMutable, NULL, density);
}

static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject,
@@ -447,8 +444,9 @@ static jobject Bitmap_extractAlpha(JNIEnv* env, jobject clazz,
                                   jintArray offsetXY) {
    SkIPoint  offset;
    SkBitmap* dst = new SkBitmap;
    JavaPixelAllocator allocator(env);

    src->extractAlpha(dst, paint, &offset);
    src->extractAlpha(dst, paint, &allocator, &offset);
    if (offsetXY != 0 && env->GetArrayLength(offsetXY) >= 2) {
        int* array = env->GetIntArrayElements(offsetXY, NULL);
        array[0] = offset.fX;
@@ -456,7 +454,7 @@ static jobject Bitmap_extractAlpha(JNIEnv* env, jobject clazz,
        env->ReleaseIntArrayElements(offsetXY, array, 0);
    }

    return GraphicsJNI::createBitmap(env, dst, true, NULL);
    return GraphicsJNI::createBitmap(env, dst, allocator.getStorageObj(), true, NULL);
}

///////////////////////////////////////////////////////////////////////////////
+2 −2
Original line number Diff line number Diff line
@@ -291,7 +291,7 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,
        env->ReleasePrimitiveArrayCritical(ninePatchChunk, array, 0);
    }

    // detach bitmap from its autotdeleter, since we want to own it now
    // detach bitmap from its autodeleter, since we want to own it now
    adb.detach();

    if (padding) {
@@ -322,7 +322,7 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,
        return javaBitmap;
    }
    // now create the java bitmap
    return GraphicsJNI::createBitmap(env, bitmap, false, ninePatchChunk);
    return GraphicsJNI::createBitmap(env, bitmap, javaAllocator.getStorageObj(), false, ninePatchChunk);
}

static jobject nativeDecodeStream(JNIEnv* env, jobject clazz,
+6 −5
Original line number Diff line number Diff line
@@ -79,7 +79,7 @@ static jobject doBuildTileIndex(JNIEnv* env, SkStream* stream) {
        return nullObjectReturn("SkImageDecoder::Factory returned null");
    }

    JavaPixelAllocator *javaAllocator = new JavaPixelAllocator(env, true);
    JavaPixelAllocator *javaAllocator = new JavaPixelAllocator(env);
    decoder->setAllocator(javaAllocator);
    JavaMemoryUsageReporter *javaMemoryReporter = new JavaMemoryUsageReporter(env);
    decoder->setReporter(javaMemoryReporter);
@@ -241,15 +241,16 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, SkBitmapRegionDecoder *b
                            getMimeTypeString(env, decoder->getFormat()));
    }

    // detach bitmap from its autotdeleter, since we want to own it now
    // detach bitmap from its autodeleter, since we want to own it now
    adb.detach();

    SkPixelRef* pr;
    pr = bitmap->pixelRef();
    SkPixelRef* pr = bitmap->pixelRef();
    // promise we will never change our pixels (great for sharing and pictures)
    pr->setImmutable();

    // now create the java bitmap
    return GraphicsJNI::createBitmap(env, bitmap, false, NULL);
    jbyteArray buff = ((AndroidPixelRef*) pr)->getStorageObj();
    return GraphicsJNI::createBitmap(env, bitmap, buff, false, NULL, -1);
}

static int nativeGetHeight(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd) {
+150 −74
Original line number Diff line number Diff line
@@ -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 jfieldID gPointF_yFieldID;
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 @@ SkRegion* GraphicsJNI::getNativeRegion(JNIEnv* env, jobject region)

///////////////////////////////////////////////////////////////////////////////////////////

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 @@ jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, bool isMutable,
    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 @@ jobject GraphicsJNI::createRegion(JNIEnv* env, SkRegion* region)
    return obj;
}

#include "SkPixelRef.h"

static JNIEnv* vm2env(JavaVM* vm)
{
    JNIEnv* env = NULL;
@@ -427,17 +434,7 @@ static JNIEnv* vm2env(JavaVM* vm)

///////////////////////////////////////////////////////////////////////////////

#include "SkMallocPixelRef.h"

/*  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,
AndroidPixelRef::AndroidPixelRef(JNIEnv* env, void* storage, size_t size, jbyteArray storageObj,
        SkColorTable* ctable) : SkMallocPixelRef(storage, size, ctable) {
    SkASSERT(storage);
    SkASSERT(env);
@@ -446,68 +443,116 @@ public:
        SkDebugf("------ [%p] env->GetJavaVM failed\n", env);
        sk_throw();
    }
    fStorageObj = storageObj;
    fHasGlobalRef = false;
    fGlobalRefCnt = 0;

    // If storageObj is NULL, the memory was NOT allocated on the Java heap
    fOnJavaHeap = (storageObj != NULL);
    
}

    virtual ~AndroidPixelRef() {
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;
    }
}

private:
    JavaVM* fVM;
};
void AndroidPixelRef::setLocalJNIRef(jbyteArray arr) {
    if (!fHasGlobalRef) {
        fStorageObj = arr;
    }
}

bool GraphicsJNI::setJavaPixelRef(JNIEnv* env, SkBitmap* bitmap,
                                  SkColorTable* ctable, bool reportSizeToVM) {
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 false;
        return NULL;
    }
    
    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;
    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();
        }
        if (!r) {
            LOGE("VM won't let us allocate %zd bytes\n", size);
            doThrowOOME(env, "bitmap size exceeds VM budget");
            return false;
    }

    return arrayObj;
}

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();

    // 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 @@ bool GraphicsJNI::setJavaPixelRef(JNIEnv* env, SkBitmap* bitmap,

///////////////////////////////////////////////////////////////////////////////

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 @@ JavaPixelAllocator::JavaPixelAllocator(JNIEnv* env, bool reportSizeToVM)
    
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 @@ bool JavaMemoryUsageReporter::reportMemory(size_t memorySize) {

////////////////////////////////////////////////////////////////////////////////

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);
@@ -611,8 +688,7 @@ int register_android_graphics_Graphics(JNIEnv* env)
    gBitmap_class = make_globalref(env, "android/graphics/Bitmap");
    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");

Loading