Graphics.cpp revision 54b6cfa9a9e5b861a9930af873580d6dc20f773c
1#include "jni.h"
2#include "GraphicsJNI.h"
3#include "NIOBuffer.h"
4#include "SkPicture.h"
5#include "SkRegion.h"
6#include <android_runtime/AndroidRuntime.h>
7
8//#define TRACK_LOCK_COUNT
9
10void doThrow(JNIEnv* env, const char* exc, const char* msg) {
11    // don't throw a new exception if we already have one pending
12    if (env->ExceptionCheck() == JNI_FALSE) {
13        jclass npeClazz;
14
15        npeClazz = env->FindClass(exc);
16        LOG_FATAL_IF(npeClazz == NULL, "Unable to find class %s", exc);
17
18        env->ThrowNew(npeClazz, msg);
19    }
20}
21
22void doThrowNPE(JNIEnv* env) {
23    doThrow(env, "java/lang/NullPointerException");
24}
25
26void doThrowAIOOBE(JNIEnv* env) {
27    doThrow(env, "java/lang/ArrayIndexOutOfBoundsException");
28}
29
30void doThrowRE(JNIEnv* env, const char* msg) {
31    doThrow(env, "java/lang/RuntimeException", msg);
32}
33
34void doThrowIAE(JNIEnv* env, const char* msg) {
35    doThrow(env, "java/lang/IllegalArgumentException", msg);
36}
37
38void doThrowISE(JNIEnv* env, const char* msg) {
39    doThrow(env, "java/lang/IllegalStateException", msg);
40}
41
42void doThrowOOME(JNIEnv* env, const char* msg) {
43    doThrow(env, "java/lang/OutOfMemoryError", msg);
44}
45
46bool GraphicsJNI::hasException(JNIEnv *env) {
47    if (env->ExceptionCheck() != 0) {
48        LOGE("*** Uncaught exception returned from Java call!\n");
49        env->ExceptionDescribe();
50        return true;
51    }
52    return false;
53}
54
55///////////////////////////////////////////////////////////////////////////////
56
57AutoJavaFloatArray::AutoJavaFloatArray(JNIEnv* env, jfloatArray array,
58                                       int minLength)
59: fEnv(env), fArray(array), fPtr(NULL), fLen(0) {
60    SkASSERT(env);
61    if (array) {
62        fLen = env->GetArrayLength(array);
63        if (fLen < minLength) {
64            sk_throw();
65        }
66        fPtr = env->GetFloatArrayElements(array, NULL);
67    }
68}
69
70AutoJavaFloatArray::~AutoJavaFloatArray() {
71    if (fPtr) {
72        fEnv->ReleaseFloatArrayElements(fArray, fPtr, 0);
73    }
74}
75
76AutoJavaIntArray::AutoJavaIntArray(JNIEnv* env, jintArray array,
77                                       int minLength)
78: fEnv(env), fArray(array), fPtr(NULL), fLen(0) {
79    SkASSERT(env);
80    if (array) {
81        fLen = env->GetArrayLength(array);
82        if (fLen < minLength) {
83            sk_throw();
84        }
85        fPtr = env->GetIntArrayElements(array, NULL);
86    }
87}
88
89AutoJavaIntArray::~AutoJavaIntArray() {
90    if (fPtr) {
91        fEnv->ReleaseIntArrayElements(fArray, fPtr, 0);
92    }
93}
94
95AutoJavaShortArray::AutoJavaShortArray(JNIEnv* env, jshortArray array,
96                                       int minLength)
97: fEnv(env), fArray(array), fPtr(NULL), fLen(0) {
98    SkASSERT(env);
99    if (array) {
100        fLen = env->GetArrayLength(array);
101        if (fLen < minLength) {
102            sk_throw();
103        }
104        fPtr = env->GetShortArrayElements(array, NULL);
105    }
106}
107
108AutoJavaShortArray::~AutoJavaShortArray() {
109    if (fPtr) {
110        fEnv->ReleaseShortArrayElements(fArray, fPtr, 0);
111    }
112}
113
114AutoJavaByteArray::AutoJavaByteArray(JNIEnv* env, jbyteArray array,
115                                       int minLength)
116: fEnv(env), fArray(array), fPtr(NULL), fLen(0) {
117    SkASSERT(env);
118    if (array) {
119        fLen = env->GetArrayLength(array);
120        if (fLen < minLength) {
121            sk_throw();
122        }
123        fPtr = env->GetByteArrayElements(array, NULL);
124    }
125}
126
127AutoJavaByteArray::~AutoJavaByteArray() {
128    if (fPtr) {
129        fEnv->ReleaseByteArrayElements(fArray, fPtr, 0);
130    }
131}
132
133///////////////////////////////////////////////////////////////////////////////
134
135static jclass   gRect_class;
136static jfieldID gRect_leftFieldID;
137static jfieldID gRect_topFieldID;
138static jfieldID gRect_rightFieldID;
139static jfieldID gRect_bottomFieldID;
140
141static jclass   gRectF_class;
142static jfieldID gRectF_leftFieldID;
143static jfieldID gRectF_topFieldID;
144static jfieldID gRectF_rightFieldID;
145static jfieldID gRectF_bottomFieldID;
146
147static jclass   gPoint_class;
148static jfieldID gPoint_xFieldID;
149static jfieldID gPoint_yFieldID;
150
151static jclass   gPointF_class;
152static jfieldID gPointF_xFieldID;
153static jfieldID gPointF_yFieldID;
154
155static jclass   gBitmap_class;
156static jfieldID gBitmap_nativeInstanceID;
157static jmethodID gBitmap_constructorMethodID;
158static jmethodID gBitmap_allocBufferMethodID;
159
160static jclass   gBitmapConfig_class;
161static jfieldID gBitmapConfig_nativeInstanceID;
162
163static jclass   gCanvas_class;
164static jfieldID gCanvas_nativeInstanceID;
165
166static jclass   gPaint_class;
167static jfieldID gPaint_nativeInstanceID;
168
169static jclass   gPicture_class;
170static jfieldID gPicture_nativeInstanceID;
171
172static jclass   gRegion_class;
173static jfieldID gRegion_nativeInstanceID;
174static jmethodID gRegion_constructorMethodID;
175
176static jobject   gVMRuntime_singleton;
177static jmethodID gVMRuntime_trackExternalAllocationMethodID;
178static jmethodID gVMRuntime_trackExternalFreeMethodID;
179
180///////////////////////////////////////////////////////////////////////////////
181
182void GraphicsJNI::get_jrect(JNIEnv* env, jobject obj, int* L, int* T, int* R, int* B)
183{
184    SkASSERT(env->IsInstanceOf(obj, gRect_class));
185
186    *L = env->GetIntField(obj, gRect_leftFieldID);
187    *T = env->GetIntField(obj, gRect_topFieldID);
188    *R = env->GetIntField(obj, gRect_rightFieldID);
189    *B = env->GetIntField(obj, gRect_bottomFieldID);
190}
191
192void GraphicsJNI::set_jrect(JNIEnv* env, jobject obj, int L, int T, int R, int B)
193{
194    SkASSERT(env->IsInstanceOf(obj, gRect_class));
195
196    env->SetIntField(obj, gRect_leftFieldID, L);
197    env->SetIntField(obj, gRect_topFieldID, T);
198    env->SetIntField(obj, gRect_rightFieldID, R);
199    env->SetIntField(obj, gRect_bottomFieldID, B);
200}
201
202SkIRect* GraphicsJNI::jrect_to_irect(JNIEnv* env, jobject obj, SkIRect* ir)
203{
204    SkASSERT(env->IsInstanceOf(obj, gRect_class));
205
206    ir->set(env->GetIntField(obj, gRect_leftFieldID),
207            env->GetIntField(obj, gRect_topFieldID),
208            env->GetIntField(obj, gRect_rightFieldID),
209            env->GetIntField(obj, gRect_bottomFieldID));
210    return ir;
211}
212
213void GraphicsJNI::irect_to_jrect(const SkIRect& ir, JNIEnv* env, jobject obj)
214{
215    SkASSERT(env->IsInstanceOf(obj, gRect_class));
216
217    env->SetIntField(obj, gRect_leftFieldID, ir.fLeft);
218    env->SetIntField(obj, gRect_topFieldID, ir.fTop);
219    env->SetIntField(obj, gRect_rightFieldID, ir.fRight);
220    env->SetIntField(obj, gRect_bottomFieldID, ir.fBottom);
221}
222
223SkRect* GraphicsJNI::jrectf_to_rect(JNIEnv* env, jobject obj, SkRect* r)
224{
225    SkASSERT(env->IsInstanceOf(obj, gRectF_class));
226
227    r->set(SkFloatToScalar(env->GetFloatField(obj, gRectF_leftFieldID)),
228           SkFloatToScalar(env->GetFloatField(obj, gRectF_topFieldID)),
229           SkFloatToScalar(env->GetFloatField(obj, gRectF_rightFieldID)),
230           SkFloatToScalar(env->GetFloatField(obj, gRectF_bottomFieldID)));
231    return r;
232}
233
234SkRect* GraphicsJNI::jrect_to_rect(JNIEnv* env, jobject obj, SkRect* r)
235{
236    SkASSERT(env->IsInstanceOf(obj, gRect_class));
237
238    r->set(SkIntToScalar(env->GetIntField(obj, gRect_leftFieldID)),
239           SkIntToScalar(env->GetIntField(obj, gRect_topFieldID)),
240           SkIntToScalar(env->GetIntField(obj, gRect_rightFieldID)),
241           SkIntToScalar(env->GetIntField(obj, gRect_bottomFieldID)));
242    return r;
243}
244
245void GraphicsJNI::rect_to_jrectf(const SkRect& r, JNIEnv* env, jobject obj)
246{
247    SkASSERT(env->IsInstanceOf(obj, gRectF_class));
248
249    env->SetFloatField(obj, gRectF_leftFieldID, SkScalarToFloat(r.fLeft));
250    env->SetFloatField(obj, gRectF_topFieldID, SkScalarToFloat(r.fTop));
251    env->SetFloatField(obj, gRectF_rightFieldID, SkScalarToFloat(r.fRight));
252    env->SetFloatField(obj, gRectF_bottomFieldID, SkScalarToFloat(r.fBottom));
253}
254
255SkIPoint* GraphicsJNI::jpoint_to_ipoint(JNIEnv* env, jobject obj, SkIPoint* point)
256{
257    SkASSERT(env->IsInstanceOf(obj, gPoint_class));
258
259    point->set(env->GetIntField(obj, gPoint_xFieldID),
260               env->GetIntField(obj, gPoint_yFieldID));
261    return point;
262}
263
264void GraphicsJNI::ipoint_to_jpoint(const SkIPoint& ir, JNIEnv* env, jobject obj)
265{
266    SkASSERT(env->IsInstanceOf(obj, gPoint_class));
267
268    env->SetIntField(obj, gPointF_xFieldID, ir.fX);
269    env->SetIntField(obj, gPointF_yFieldID, ir.fY);
270}
271
272SkPoint* GraphicsJNI::jpointf_to_point(JNIEnv* env, jobject obj, SkPoint* point)
273{
274    SkASSERT(env->IsInstanceOf(obj, gPointF_class));
275
276    point->set(SkFloatToScalar(env->GetIntField(obj, gPointF_xFieldID)),
277               SkFloatToScalar(env->GetIntField(obj, gPointF_yFieldID)));
278    return point;
279}
280
281void GraphicsJNI::point_to_jpointf(const SkPoint& r, JNIEnv* env, jobject obj)
282{
283    SkASSERT(env->IsInstanceOf(obj, gPointF_class));
284
285    env->SetFloatField(obj, gPointF_xFieldID, SkScalarToFloat(r.fX));
286    env->SetFloatField(obj, gPointF_yFieldID, SkScalarToFloat(r.fY));
287}
288
289SkBitmap* GraphicsJNI::getNativeBitmap(JNIEnv* env, jobject bitmap) {
290    SkASSERT(env);
291    SkASSERT(bitmap);
292    SkASSERT(env->IsInstanceOf(bitmap, gBitmap_class));
293    SkBitmap* b = (SkBitmap*)env->GetIntField(bitmap, gBitmap_nativeInstanceID);
294    SkASSERT(b);
295    return b;
296}
297
298SkBitmap::Config GraphicsJNI::getNativeBitmapConfig(JNIEnv* env,
299                                                    jobject jconfig) {
300    SkASSERT(env);
301    if (NULL == jconfig) {
302        return SkBitmap::kNo_Config;
303    }
304    SkASSERT(env->IsInstanceOf(jconfig, gBitmapConfig_class));
305    int c = env->GetIntField(jconfig, gBitmapConfig_nativeInstanceID);
306    if (c < 0 || c >= SkBitmap::kConfigCount) {
307        c = SkBitmap::kNo_Config;
308    }
309    return static_cast<SkBitmap::Config>(c);
310}
311
312SkCanvas* GraphicsJNI::getNativeCanvas(JNIEnv* env, jobject canvas) {
313    SkASSERT(env);
314    SkASSERT(canvas);
315    SkASSERT(env->IsInstanceOf(canvas, gCanvas_class));
316    SkCanvas* c = (SkCanvas*)env->GetIntField(canvas, gCanvas_nativeInstanceID);
317    SkASSERT(c);
318    return c;
319}
320
321SkPaint* GraphicsJNI::getNativePaint(JNIEnv* env, jobject paint) {
322    SkASSERT(env);
323    SkASSERT(paint);
324    SkASSERT(env->IsInstanceOf(paint, gPaint_class));
325    SkPaint* p = (SkPaint*)env->GetIntField(paint, gPaint_nativeInstanceID);
326    SkASSERT(p);
327    return p;
328}
329
330SkPicture* GraphicsJNI::getNativePicture(JNIEnv* env, jobject picture)
331{
332    SkASSERT(env);
333    SkASSERT(picture);
334    SkASSERT(env->IsInstanceOf(picture, gPicture_class));
335    SkPicture* p = (SkPicture*)env->GetIntField(picture, gPicture_nativeInstanceID);
336    SkASSERT(p);
337    return p;
338}
339
340SkRegion* GraphicsJNI::getNativeRegion(JNIEnv* env, jobject region)
341{
342    SkASSERT(env);
343    SkASSERT(region);
344    SkASSERT(env->IsInstanceOf(region, gRegion_class));
345    SkRegion* r = (SkRegion*)env->GetIntField(region, gRegion_nativeInstanceID);
346    SkASSERT(r);
347    return r;
348}
349
350///////////////////////////////////////////////////////////////////////////////////////////
351
352jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, bool isMutable,
353                                  jbyteArray ninepatch)
354{
355    SkASSERT(bitmap != NULL);
356    SkASSERT(NULL != bitmap->pixelRef());
357
358    jobject obj = env->AllocObject(gBitmap_class);
359    if (obj) {
360        env->CallVoidMethod(obj, gBitmap_constructorMethodID,
361                            (jint)bitmap, isMutable, ninepatch);
362        if (hasException(env)) {
363            obj = NULL;
364        }
365    }
366    return obj;
367}
368
369jobject GraphicsJNI::createRegion(JNIEnv* env, SkRegion* region)
370{
371    SkASSERT(region != NULL);
372    jobject obj = env->AllocObject(gRegion_class);
373    if (obj) {
374        env->CallVoidMethod(obj, gRegion_constructorMethodID, (jint)region, 0);
375        if (hasException(env)) {
376            obj = NULL;
377        }
378    }
379    return obj;
380}
381
382#include "SkPixelRef.h"
383
384static JNIEnv* vm2env(JavaVM* vm)
385{
386    JNIEnv* env = NULL;
387    if (vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK || NULL == env)
388    {
389        SkDebugf("------- [%p] vm->GetEnv() failed\n", vm);
390        sk_throw();
391    }
392    return env;
393}
394
395#ifdef TRACK_LOCK_COUNT
396    static int gLockCount;
397#endif
398
399///////////////////////////////////////////////////////////////////////////////
400
401#include "SkMallocPixelRef.h"
402
403/*  Extend SkMallocPixelRef to inform the VM when we free up the storage
404*/
405class AndroidPixelRef : public SkMallocPixelRef {
406public:
407    /** Allocate the specified buffer for pixels. The memory is freed when the
408        last owner of this pixelref is gone. Our caller has already informed
409        the VM of our allocation.
410    */
411    AndroidPixelRef(JNIEnv* env, void* storage, size_t size,
412            SkColorTable* ctable) : SkMallocPixelRef(storage, size, ctable) {
413        SkASSERT(storage);
414        SkASSERT(env);
415
416        if (env->GetJavaVM(&fVM) != JNI_OK) {
417            SkDebugf("------ [%p] env->GetJavaVM failed\n", env);
418            sk_throw();
419        }
420    }
421
422    virtual ~AndroidPixelRef() {
423        JNIEnv* env = vm2env(fVM);
424//        SkDebugf("-------------- inform VM we're releasing %d bytes\n", this->getSize());
425        jlong jsize = this->getSize();  // the VM wants longs for the size
426        env->CallVoidMethod(gVMRuntime_singleton,
427                            gVMRuntime_trackExternalFreeMethodID,
428                            jsize);
429        if (GraphicsJNI::hasException(env)) {
430            env->ExceptionClear();
431        }
432    }
433
434private:
435    JavaVM* fVM;
436};
437
438bool GraphicsJNI::setJavaPixelRef(JNIEnv* env, SkBitmap* bitmap,
439                                  SkColorTable* ctable) {
440    Sk64 size64 = bitmap->getSize64();
441    if (size64.isNeg() || !size64.is32()) {
442        doThrow(env, "java/lang/IllegalArgumentException",
443                     "bitmap size exceeds 32bits");
444        return false;
445    }
446
447    size_t size = size64.get32();
448    //    SkDebugf("-------------- inform VM we've allocated %d bytes\n", size);
449    jlong jsize = size;  // the VM wants longs for the size
450    bool r = env->CallBooleanMethod(gVMRuntime_singleton,
451                                     gVMRuntime_trackExternalAllocationMethodID,
452                                     jsize);
453    if (GraphicsJNI::hasException(env)) {
454        return false;
455    }
456    if (!r) {
457        LOGE("VM won't let us allocate %zd bytes\n", size);
458        doThrowOOME(env, "bitmap size exceeds VM budget");
459        return false;
460    }
461
462    // call the version of malloc that returns null on failure
463    void* addr = sk_malloc_flags(size, 0);
464    if (NULL == addr) {
465        //        SkDebugf("-------------- inform VM we're releasing %d bytes which we couldn't allocate\n", size);
466        // we didn't actually allocate it, so inform the VM
467        env->CallVoidMethod(gVMRuntime_singleton,
468                             gVMRuntime_trackExternalFreeMethodID,
469                             jsize);
470        if (!GraphicsJNI::hasException(env)) {
471            doThrowOOME(env, "bitmap size too large for malloc");
472        }
473        return false;
474    }
475
476    bitmap->setPixelRef(new AndroidPixelRef(env, addr, size, ctable))->unref();
477    // since we're already allocated, we lockPixels right away
478    // HeapAllocator behaves this way too
479    bitmap->lockPixels();
480    return true;
481}
482
483///////////////////////////////////////////////////////////////////////////////
484
485JavaPixelAllocator::JavaPixelAllocator(JNIEnv* env) : fEnv(env)
486{
487}
488
489bool JavaPixelAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) {
490    return GraphicsJNI::setJavaPixelRef(fEnv, bitmap, ctable);
491}
492
493////////////////////////////////////////////////////////////////////////////////
494
495static jclass make_globalref(JNIEnv* env, const char classname[])
496{
497    jclass c = env->FindClass(classname);
498    SkASSERT(c);
499    return (jclass)env->NewGlobalRef(c);
500}
501
502static jfieldID getFieldIDCheck(JNIEnv* env, jclass clazz,
503                                const char fieldname[], const char type[])
504{
505    jfieldID id = env->GetFieldID(clazz, fieldname, type);
506    SkASSERT(id);
507    return id;
508}
509
510int register_android_graphics_Graphics(JNIEnv* env)
511{
512    jmethodID m;
513    jclass c;
514
515    gRect_class = make_globalref(env, "android/graphics/Rect");
516    gRect_leftFieldID = getFieldIDCheck(env, gRect_class, "left", "I");
517    gRect_topFieldID = getFieldIDCheck(env, gRect_class, "top", "I");
518    gRect_rightFieldID = getFieldIDCheck(env, gRect_class, "right", "I");
519    gRect_bottomFieldID = getFieldIDCheck(env, gRect_class, "bottom", "I");
520
521    gRectF_class = make_globalref(env, "android/graphics/RectF");
522    gRectF_leftFieldID = getFieldIDCheck(env, gRectF_class, "left", "F");
523    gRectF_topFieldID = getFieldIDCheck(env, gRectF_class, "top", "F");
524    gRectF_rightFieldID = getFieldIDCheck(env, gRectF_class, "right", "F");
525    gRectF_bottomFieldID = getFieldIDCheck(env, gRectF_class, "bottom", "F");
526
527    gPoint_class = make_globalref(env, "android/graphics/Point");
528    gPoint_xFieldID = getFieldIDCheck(env, gPoint_class, "x", "I");
529    gPoint_yFieldID = getFieldIDCheck(env, gPoint_class, "y", "I");
530
531    gPointF_class = make_globalref(env, "android/graphics/PointF");
532    gPointF_xFieldID = getFieldIDCheck(env, gPointF_class, "x", "F");
533    gPointF_yFieldID = getFieldIDCheck(env, gPointF_class, "y", "F");
534
535    gBitmap_class = make_globalref(env, "android/graphics/Bitmap");
536    gBitmap_nativeInstanceID = getFieldIDCheck(env, gBitmap_class, "mNativeBitmap", "I");
537    gBitmap_constructorMethodID = env->GetMethodID(gBitmap_class, "<init>",
538                                            "(IZ[B)V");
539
540    gBitmapConfig_class = make_globalref(env, "android/graphics/Bitmap$Config");
541    gBitmapConfig_nativeInstanceID = getFieldIDCheck(env, gBitmapConfig_class,
542                                                     "nativeInt", "I");
543
544    gCanvas_class = make_globalref(env, "android/graphics/Canvas");
545    gCanvas_nativeInstanceID = getFieldIDCheck(env, gCanvas_class, "mNativeCanvas", "I");
546
547    gPaint_class = make_globalref(env, "android/graphics/Paint");
548    gPaint_nativeInstanceID = getFieldIDCheck(env, gPaint_class, "mNativePaint", "I");
549
550    gPicture_class = make_globalref(env, "android/graphics/Picture");
551    gPicture_nativeInstanceID = getFieldIDCheck(env, gPicture_class, "mNativePicture", "I");
552
553    gRegion_class = make_globalref(env, "android/graphics/Region");
554    gRegion_nativeInstanceID = getFieldIDCheck(env, gRegion_class, "mNativeRegion", "I");
555    gRegion_constructorMethodID = env->GetMethodID(gRegion_class, "<init>",
556        "(II)V");
557
558    // Get the VMRuntime class.
559    c = env->FindClass("dalvik/system/VMRuntime");
560    SkASSERT(c);
561    // Look up VMRuntime.getRuntime().
562    m = env->GetStaticMethodID(c, "getRuntime", "()Ldalvik/system/VMRuntime;");
563    SkASSERT(m);
564    // Call VMRuntime.getRuntime() and hold onto its result.
565    gVMRuntime_singleton = env->CallStaticObjectMethod(c, m);
566    SkASSERT(gVMRuntime_singleton);
567    gVMRuntime_singleton = (jobject)env->NewGlobalRef(gVMRuntime_singleton);
568    // Look up the VMRuntime methods we'll be using.
569    gVMRuntime_trackExternalAllocationMethodID =
570                        env->GetMethodID(c, "trackExternalAllocation", "(J)Z");
571    gVMRuntime_trackExternalFreeMethodID =
572                            env->GetMethodID(c, "trackExternalFree", "(J)V");
573
574    NIOBuffer::RegisterJNI(env);
575
576    return 0;
577}
578
579