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