util.cpp revision a6276fdd4253c3a7150ab675678c750473ab6c45
1/**
2 ** Copyright 2007, The Android Open Source Project
3 **
4 ** Licensed under the Apache License, Version 2.0 (the "License");
5 ** you may not use this file except in compliance with the License.
6 ** You may obtain a copy of the License at
7 **
8 **     http://www.apache.org/licenses/LICENSE-2.0
9 **
10 ** Unless required by applicable law or agreed to in writing, software
11 ** distributed under the License is distributed on an "AS IS" BASIS,
12 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 ** See the License for the specific language governing permissions and
14 ** limitations under the License.
15 */
16
17#include <nativehelper/jni.h>
18#include <math.h>
19#include <stdio.h>
20#include <stdlib.h>
21#include <string.h>
22#include <assert.h>
23#include <dlfcn.h>
24
25#include <GLES/gl.h>
26#include <ETC1/etc1.h>
27
28#include <core/SkBitmap.h>
29
30#include "android_runtime/AndroidRuntime.h"
31
32#undef LOG_TAG
33#define LOG_TAG "OpenGLUtil"
34#include <utils/Log.h>
35#include "utils/misc.h"
36
37#include "poly.h"
38
39namespace android {
40
41static jclass gIAEClass;
42static jclass gUOEClass;
43static jclass gAIOOBEClass;
44
45static inline
46void mx4transform(float x, float y, float z, float w, const float* pM, float* pDest) {
47    pDest[0] = pM[0 + 4 * 0] * x + pM[0 + 4 * 1] * y + pM[0 + 4 * 2] * z + pM[0 + 4 * 3] * w;
48    pDest[1] = pM[1 + 4 * 0] * x + pM[1 + 4 * 1] * y + pM[1 + 4 * 2] * z + pM[1 + 4 * 3] * w;
49    pDest[2] = pM[2 + 4 * 0] * x + pM[2 + 4 * 1] * y + pM[2 + 4 * 2] * z + pM[2 + 4 * 3] * w;
50    pDest[3] = pM[3 + 4 * 0] * x + pM[3 + 4 * 1] * y + pM[3 + 4 * 2] * z + pM[3 + 4 * 3] * w;
51}
52
53class MallocHelper {
54public:
55    MallocHelper() {
56        mData = 0;
57    }
58
59    ~MallocHelper() {
60        if (mData != 0) {
61            free(mData);
62        }
63    }
64
65    void* alloc(size_t size) {
66        mData = malloc(size);
67        return mData;
68    }
69
70private:
71    void* mData;
72};
73
74#if 0
75static
76void
77print_poly(const char* label, Poly* pPoly) {
78    LOGI("%s: %d verts", label, pPoly->n);
79    for(int i = 0; i < pPoly->n; i++) {
80        Poly_vert* pV = & pPoly->vert[i];
81        LOGI("[%d] %g, %g, %g %g", i, pV->sx, pV->sy, pV->sz, pV->sw);
82    }
83}
84#endif
85
86static
87int visibilityTest(float* pWS, float* pPositions, int positionsLength,
88        unsigned short* pIndices, int indexCount) {
89    MallocHelper mallocHelper;
90    int result = POLY_CLIP_OUT;
91    float* pTransformed = 0;
92    int transformedIndexCount = 0;
93
94    if ( indexCount < 3 ) {
95        return POLY_CLIP_OUT;
96    }
97
98    // Find out how many vertices we need to transform
99    // We transform every vertex between the min and max indices, inclusive.
100    // This is OK for the data sets we expect to use with this function, but
101    // for other loads it might be better to use a more sophisticated vertex
102    // cache of some sort.
103
104    int minIndex = 65536;
105    int maxIndex = -1;
106    for(int i = 0; i < indexCount; i++) {
107        int index = pIndices[i];
108        if ( index < minIndex ) {
109            minIndex = index;
110        }
111        if ( index > maxIndex ) {
112            maxIndex = index;
113        }
114    }
115
116    if ( maxIndex * 3 > positionsLength) {
117        return -1;
118    }
119
120    transformedIndexCount = maxIndex - minIndex + 1;
121    pTransformed = (float*) mallocHelper.alloc(transformedIndexCount * 4 * sizeof(float));
122
123    if (pTransformed == 0 ) {
124        return -2;
125    }
126
127    // Transform the vertices
128    {
129        const float* pSrc = pPositions + 3 * minIndex;
130        float* pDst = pTransformed;
131        for (int i = 0; i < transformedIndexCount; i++, pSrc += 3, pDst += 4) {
132            mx4transform(pSrc[0], pSrc[1], pSrc[2], 1.0f, pWS,  pDst);
133        }
134    }
135
136    // Clip the triangles
137
138    Poly poly;
139    float* pDest = & poly.vert[0].sx;
140    for (int i = 0; i < indexCount; i += 3) {
141        poly.n = 3;
142        memcpy(pDest    , pTransformed + 4 * (pIndices[i    ] - minIndex), 4 * sizeof(float));
143        memcpy(pDest + 4, pTransformed + 4 * (pIndices[i + 1] - minIndex), 4 * sizeof(float));
144        memcpy(pDest + 8, pTransformed + 4 * (pIndices[i + 2] - minIndex), 4 * sizeof(float));
145        result = poly_clip_to_frustum(&poly);
146        if ( result != POLY_CLIP_OUT) {
147            return result;
148        }
149    }
150
151    return result;
152}
153
154template<class JArray, class T>
155class ArrayHelper {
156public:
157    ArrayHelper(JNIEnv* env, JArray ref, jint offset, jint minSize) {
158        mEnv = env;
159        mRef = ref;
160        mOffset = offset;
161        mMinSize = minSize;
162        mBase = 0;
163        mReleaseParam = JNI_ABORT;
164    }
165
166    ~ArrayHelper() {
167        if (mBase) {
168            mEnv->ReleasePrimitiveArrayCritical(mRef, mBase, mReleaseParam);
169        }
170    }
171
172    // We seperate the bounds check from the initialization because we want to
173    // be able to bounds-check multiple arrays, and we can't throw an exception
174    // after we've called GetPrimitiveArrayCritical.
175
176    // Return true if the bounds check succeeded
177    // Else instruct the runtime to throw an exception
178
179    bool check() {
180        if ( ! mRef) {
181            mEnv->ThrowNew(gIAEClass, "array == null");
182            return false;
183        }
184        if ( mOffset < 0) {
185            mEnv->ThrowNew(gIAEClass, "offset < 0");
186            return false;
187        }
188        mLength = mEnv->GetArrayLength(mRef) - mOffset;
189        if (mLength < mMinSize ) {
190            mEnv->ThrowNew(gIAEClass, "length - offset < n");
191            return false;
192        }
193        return true;
194    }
195
196    // Bind the array.
197
198    void bind() {
199        mBase = (T*) mEnv->GetPrimitiveArrayCritical(mRef, (jboolean *) 0);
200        mData = mBase + mOffset;
201    }
202
203    void commitChanges() {
204        mReleaseParam = 0;
205    }
206
207    T* mData;
208    int mLength;
209
210private:
211    T* mBase;
212    JNIEnv* mEnv;
213    JArray mRef;
214    jint mOffset;
215    jint mMinSize;
216    int mReleaseParam;
217};
218
219typedef ArrayHelper<jfloatArray, float> FloatArrayHelper;
220typedef ArrayHelper<jcharArray, unsigned short> UnsignedShortArrayHelper;
221typedef ArrayHelper<jintArray, int> IntArrayHelper;
222typedef ArrayHelper<jbyteArray, unsigned char> ByteArrayHelper;
223
224inline float distance2(float x, float y, float z) {
225    return x * x + y * y + z * z;
226}
227
228inline float distance(float x, float y, float z) {
229    return sqrtf(distance2(x, y, z));
230}
231
232static
233void util_computeBoundingSphere(JNIEnv *env, jclass clazz,
234        jfloatArray positions_ref, jint positionsOffset, jint positionsCount,
235        jfloatArray sphere_ref, jint sphereOffset) {
236    FloatArrayHelper positions(env, positions_ref, positionsOffset, 0);
237    FloatArrayHelper sphere(env, sphere_ref, sphereOffset, 4);
238
239    bool checkOK = positions.check() && sphere.check();
240        if (! checkOK) {
241        return;
242    }
243
244    positions.bind();
245    sphere.bind();
246
247    if ( positionsCount < 1 ) {
248        env->ThrowNew(gIAEClass, "positionsCount < 1");
249        return;
250    }
251
252    const float* pSrc = positions.mData;
253
254    // find bounding box
255    float x0 = *pSrc++;
256    float x1 = x0;
257    float y0 = *pSrc++;
258    float y1 = y0;
259    float z0 = *pSrc++;
260    float z1 = z0;
261
262    for(int i = 1; i < positionsCount; i++) {
263        {
264            float x = *pSrc++;
265            if (x < x0) {
266                x0 = x;
267            }
268            else if (x > x1) {
269                x1 = x;
270            }
271        }
272        {
273            float y = *pSrc++;
274            if (y < y0) {
275                y0 = y;
276            }
277            else if (y > y1) {
278                y1 = y;
279            }
280        }
281        {
282            float z = *pSrc++;
283            if (z < z0) {
284                z0 = z;
285            }
286            else if (z > z1) {
287                z1 = z;
288            }
289        }
290    }
291
292    // Because we know our input meshes fit pretty well into bounding boxes,
293    // just take the diagonal of the box as defining our sphere.
294    float* pSphere = sphere.mData;
295    float dx = x1 - x0;
296    float dy = y1 - y0;
297    float dz = z1 - z0;
298    *pSphere++ = x0 + dx * 0.5f;
299    *pSphere++ = y0 + dy * 0.5f;
300    *pSphere++ = z0 + dz * 0.5f;
301    *pSphere++ = distance(dx, dy, dz) * 0.5f;
302
303    sphere.commitChanges();
304}
305
306static void normalizePlane(float* p) {
307    float rdist = 1.0f / distance(p[0], p[1], p[2]);
308    for(int i = 0; i < 4; i++) {
309        p[i] *= rdist;
310    }
311}
312
313static inline float dot3(float x0, float y0, float z0, float x1, float y1, float z1) {
314    return x0 * x1 + y0 * y1 + z0 * z1;
315}
316
317static inline float signedDistance(const float* pPlane, float x, float y, float z) {
318    return dot3(pPlane[0], pPlane[1], pPlane[2], x, y, z) + pPlane[3];
319}
320
321// Return true if the sphere intersects or is inside the frustum
322
323static bool sphereHitsFrustum(const float* pFrustum, const float* pSphere) {
324    float x = pSphere[0];
325    float y = pSphere[1];
326    float z = pSphere[2];
327    float negRadius = -pSphere[3];
328    for (int i = 0; i < 6; i++, pFrustum += 4) {
329        if (signedDistance(pFrustum, x, y, z) <= negRadius) {
330            return false;
331        }
332    }
333    return true;
334}
335
336static void computeFrustum(const float* m, float* f) {
337    float m3 = m[3];
338    float m7 = m[7];
339    float m11 = m[11];
340    float m15 = m[15];
341    // right
342    f[0] = m3  - m[0];
343    f[1] = m7  - m[4];
344    f[2] = m11 - m[8];
345    f[3] = m15 - m[12];
346    normalizePlane(f);
347    f+= 4;
348
349    // left
350    f[0] = m3  + m[0];
351    f[1] = m7  + m[4];
352    f[2] = m11 + m[8];
353    f[3] = m15 + m[12];
354    normalizePlane(f);
355    f+= 4;
356
357    // top
358    f[0] = m3  - m[1];
359    f[1] = m7  - m[5];
360    f[2] = m11 - m[9];
361    f[3] = m15 - m[13];
362    normalizePlane(f);
363    f+= 4;
364
365    // bottom
366    f[0] = m3  + m[1];
367    f[1] = m7  + m[5];
368    f[2] = m11 + m[9];
369    f[3] = m15 + m[13];
370    normalizePlane(f);
371    f+= 4;
372
373    // far
374    f[0] = m3  - m[2];
375    f[1] = m7  - m[6];
376    f[2] = m11 - m[10];
377    f[3] = m15 - m[14];
378    normalizePlane(f);
379    f+= 4;
380
381    // near
382    f[0] = m3  + m[2];
383    f[1] = m7  + m[6];
384    f[2] = m11 + m[10];
385    f[3] = m15 + m[14];
386    normalizePlane(f);
387}
388
389static
390int util_frustumCullSpheres(JNIEnv *env, jclass clazz,
391        jfloatArray mvp_ref, jint mvpOffset,
392        jfloatArray spheres_ref, jint spheresOffset, jint spheresCount,
393        jintArray results_ref, jint resultsOffset, jint resultsCapacity) {
394    float frustum[6*4];
395    int outputCount;
396    int* pResults;
397    float* pSphere;
398    FloatArrayHelper mvp(env, mvp_ref, mvpOffset, 16);
399    FloatArrayHelper spheres(env, spheres_ref, spheresOffset, spheresCount * 4);
400    IntArrayHelper results(env, results_ref, resultsOffset, resultsCapacity);
401
402    bool initializedOK = mvp.check() && spheres.check() && results.check();
403        if (! initializedOK) {
404        return -1;
405    }
406
407    mvp.bind();
408    spheres.bind();
409    results.bind();
410
411    computeFrustum(mvp.mData, frustum);
412
413    // Cull the spheres
414
415    pSphere = spheres.mData;
416    pResults = results.mData;
417    outputCount = 0;
418    for(int i = 0; i < spheresCount; i++, pSphere += 4) {
419        if (sphereHitsFrustum(frustum, pSphere)) {
420            if (outputCount < resultsCapacity) {
421                *pResults++ = i;
422            }
423            outputCount++;
424        }
425    }
426    results.commitChanges();
427    return outputCount;
428}
429
430/*
431 public native int visibilityTest(float[] ws, int wsOffset,
432 float[] positions, int positionsOffset,
433 char[] indices, int indicesOffset, int indexCount);
434 */
435
436static
437int util_visibilityTest(JNIEnv *env, jclass clazz,
438        jfloatArray ws_ref, jint wsOffset,
439        jfloatArray positions_ref, jint positionsOffset,
440        jcharArray indices_ref, jint indicesOffset, jint indexCount) {
441
442    FloatArrayHelper ws(env, ws_ref, wsOffset, 16);
443    FloatArrayHelper positions(env, positions_ref, positionsOffset, 0);
444    UnsignedShortArrayHelper indices(env, indices_ref, indicesOffset, 0);
445
446    bool checkOK = ws.check() && positions.check() && indices.check();
447    if (! checkOK) {
448        // Return value will be ignored, because an exception has been thrown.
449        return -1;
450    }
451
452    if (indices.mLength < indexCount) {
453        env->ThrowNew(gIAEClass, "length < offset + indexCount");
454        // Return value will be ignored, because an exception has been thrown.
455        return -1;
456    }
457
458    ws.bind();
459    positions.bind();
460    indices.bind();
461
462    return visibilityTest(ws.mData,
463            positions.mData, positions.mLength,
464            indices.mData, indexCount);
465}
466
467#define I(_i, _j) ((_j)+ 4*(_i))
468
469static
470void multiplyMM(float* r, const float* lhs, const float* rhs)
471{
472    for (int i=0 ; i<4 ; i++) {
473        register const float rhs_i0 = rhs[ I(i,0) ];
474        register float ri0 = lhs[ I(0,0) ] * rhs_i0;
475        register float ri1 = lhs[ I(0,1) ] * rhs_i0;
476        register float ri2 = lhs[ I(0,2) ] * rhs_i0;
477        register float ri3 = lhs[ I(0,3) ] * rhs_i0;
478        for (int j=1 ; j<4 ; j++) {
479            register const float rhs_ij = rhs[ I(i,j) ];
480            ri0 += lhs[ I(j,0) ] * rhs_ij;
481            ri1 += lhs[ I(j,1) ] * rhs_ij;
482            ri2 += lhs[ I(j,2) ] * rhs_ij;
483            ri3 += lhs[ I(j,3) ] * rhs_ij;
484        }
485        r[ I(i,0) ] = ri0;
486        r[ I(i,1) ] = ri1;
487        r[ I(i,2) ] = ri2;
488        r[ I(i,3) ] = ri3;
489    }
490}
491
492static
493void util_multiplyMM(JNIEnv *env, jclass clazz,
494    jfloatArray result_ref, jint resultOffset,
495    jfloatArray lhs_ref, jint lhsOffset,
496    jfloatArray rhs_ref, jint rhsOffset) {
497
498    FloatArrayHelper resultMat(env, result_ref, resultOffset, 16);
499    FloatArrayHelper lhs(env, lhs_ref, lhsOffset, 16);
500    FloatArrayHelper rhs(env, rhs_ref, rhsOffset, 16);
501
502    bool checkOK = resultMat.check() && lhs.check() && rhs.check();
503
504    if ( !checkOK ) {
505        return;
506    }
507
508    resultMat.bind();
509    lhs.bind();
510    rhs.bind();
511
512    multiplyMM(resultMat.mData, lhs.mData, rhs.mData);
513
514    resultMat.commitChanges();
515}
516
517static
518void multiplyMV(float* r, const float* lhs, const float* rhs)
519{
520    mx4transform(rhs[0], rhs[1], rhs[2], rhs[3], lhs, r);
521}
522
523static
524void util_multiplyMV(JNIEnv *env, jclass clazz,
525    jfloatArray result_ref, jint resultOffset,
526    jfloatArray lhs_ref, jint lhsOffset,
527    jfloatArray rhs_ref, jint rhsOffset) {
528
529    FloatArrayHelper resultV(env, result_ref, resultOffset, 4);
530    FloatArrayHelper lhs(env, lhs_ref, lhsOffset, 16);
531    FloatArrayHelper rhs(env, rhs_ref, rhsOffset, 4);
532
533    bool checkOK = resultV.check() && lhs.check() && rhs.check();
534
535    if ( !checkOK ) {
536        return;
537    }
538
539    resultV.bind();
540    lhs.bind();
541    rhs.bind();
542
543    multiplyMV(resultV.mData, lhs.mData, rhs.mData);
544
545    resultV.commitChanges();
546}
547
548// ---------------------------------------------------------------------------
549
550static jfieldID nativeBitmapID = 0;
551
552void nativeUtilsClassInit(JNIEnv *env, jclass clazz)
553{
554    jclass bitmapClass = env->FindClass("android/graphics/Bitmap");
555    nativeBitmapID = env->GetFieldID(bitmapClass, "mNativeBitmap", "I");
556}
557
558static int checkFormat(SkBitmap::Config config, int format, int type)
559{
560    switch(config) {
561        case SkBitmap::kIndex8_Config:
562            if (format == GL_PALETTE8_RGBA8_OES)
563                return 0;
564        case SkBitmap::kARGB_8888_Config:
565        case SkBitmap::kA8_Config:
566            if (type == GL_UNSIGNED_BYTE)
567                return 0;
568        case SkBitmap::kARGB_4444_Config:
569        case SkBitmap::kRGB_565_Config:
570            switch (type) {
571                case GL_UNSIGNED_SHORT_4_4_4_4:
572                case GL_UNSIGNED_SHORT_5_6_5:
573                case GL_UNSIGNED_SHORT_5_5_5_1:
574                    return 0;
575                case GL_UNSIGNED_BYTE:
576                    if (format == GL_LUMINANCE_ALPHA)
577                        return 0;
578            }
579            break;
580        default:
581            break;
582    }
583    return -1;
584}
585
586static int getInternalFormat(SkBitmap::Config config)
587{
588    switch(config) {
589        case SkBitmap::kA8_Config:
590            return GL_ALPHA;
591        case SkBitmap::kARGB_4444_Config:
592            return GL_RGBA;
593        case SkBitmap::kARGB_8888_Config:
594            return GL_RGBA;
595        case SkBitmap::kIndex8_Config:
596            return GL_PALETTE8_RGBA8_OES;
597        case SkBitmap::kRGB_565_Config:
598            return GL_RGB;
599        default:
600            return -1;
601    }
602}
603
604static int getType(SkBitmap::Config config)
605{
606    switch(config) {
607        case SkBitmap::kA8_Config:
608            return GL_UNSIGNED_BYTE;
609        case SkBitmap::kARGB_4444_Config:
610            return GL_UNSIGNED_SHORT_4_4_4_4;
611        case SkBitmap::kARGB_8888_Config:
612            return GL_UNSIGNED_BYTE;
613        case SkBitmap::kIndex8_Config:
614            return -1; // No type for compressed data.
615        case SkBitmap::kRGB_565_Config:
616            return GL_UNSIGNED_SHORT_5_6_5;
617        default:
618            return -1;
619    }
620}
621
622static jint util_getInternalFormat(JNIEnv *env, jclass clazz,
623        jobject jbitmap)
624{
625    SkBitmap const * nativeBitmap =
626            (SkBitmap const *)env->GetIntField(jbitmap, nativeBitmapID);
627    const SkBitmap& bitmap(*nativeBitmap);
628    SkBitmap::Config config = bitmap.getConfig();
629    return getInternalFormat(config);
630}
631
632static jint util_getType(JNIEnv *env, jclass clazz,
633        jobject jbitmap)
634{
635    SkBitmap const * nativeBitmap =
636            (SkBitmap const *)env->GetIntField(jbitmap, nativeBitmapID);
637    const SkBitmap& bitmap(*nativeBitmap);
638    SkBitmap::Config config = bitmap.getConfig();
639    return getType(config);
640}
641
642static jint util_texImage2D(JNIEnv *env, jclass clazz,
643        jint target, jint level, jint internalformat,
644        jobject jbitmap, jint type, jint border)
645{
646    SkBitmap const * nativeBitmap =
647            (SkBitmap const *)env->GetIntField(jbitmap, nativeBitmapID);
648    const SkBitmap& bitmap(*nativeBitmap);
649    SkBitmap::Config config = bitmap.getConfig();
650    if (internalformat < 0) {
651        internalformat = getInternalFormat(config);
652    }
653    if (type < 0) {
654        type = getType(config);
655    }
656    int err = checkFormat(config, internalformat, type);
657    if (err)
658        return err;
659    bitmap.lockPixels();
660    const int w = bitmap.width();
661    const int h = bitmap.height();
662    const void* p = bitmap.getPixels();
663    if (internalformat == GL_PALETTE8_RGBA8_OES) {
664        if (sizeof(SkPMColor) != sizeof(uint32_t)) {
665            err = -1;
666            goto error;
667        }
668        const size_t size = bitmap.getSize();
669        const size_t palette_size = 256*sizeof(SkPMColor);
670        const size_t imageSize = size + palette_size;
671        void* const data = malloc(imageSize);
672        if (data) {
673            void* const pixels = (char*)data + palette_size;
674            SkColorTable* ctable = bitmap.getColorTable();
675            memcpy(data, ctable->lockColors(), ctable->count() * sizeof(SkPMColor));
676            memcpy(pixels, p, size);
677            ctable->unlockColors(false);
678            glCompressedTexImage2D(target, level, internalformat, w, h, border, imageSize, data);
679            free(data);
680        } else {
681            err = -1;
682        }
683    } else {
684        glTexImage2D(target, level, internalformat, w, h, border, internalformat, type, p);
685    }
686error:
687    bitmap.unlockPixels();
688    return err;
689}
690
691static jint util_texSubImage2D(JNIEnv *env, jclass clazz,
692        jint target, jint level, jint xoffset, jint yoffset,
693        jobject jbitmap, jint format, jint type)
694{
695    SkBitmap const * nativeBitmap =
696            (SkBitmap const *)env->GetIntField(jbitmap, nativeBitmapID);
697    const SkBitmap& bitmap(*nativeBitmap);
698    SkBitmap::Config config = bitmap.getConfig();
699    if (format < 0) {
700        format = getInternalFormat(config);
701        if (format == GL_PALETTE8_RGBA8_OES)
702            return -1; // glCompressedTexSubImage2D() not supported
703    }
704    int err = checkFormat(config, format, type);
705    if (err)
706        return err;
707    bitmap.lockPixels();
708    const int w = bitmap.width();
709    const int h = bitmap.height();
710    const void* p = bitmap.getPixels();
711    glTexSubImage2D(target, level, xoffset, yoffset, w, h, format, type, p);
712    bitmap.unlockPixels();
713    return 0;
714}
715
716/*
717 * ETC1 methods.
718 */
719
720static jclass nioAccessClass;
721static jclass bufferClass;
722static jmethodID getBasePointerID;
723static jmethodID getBaseArrayID;
724static jmethodID getBaseArrayOffsetID;
725static jfieldID positionID;
726static jfieldID limitID;
727static jfieldID elementSizeShiftID;
728
729/* Cache method IDs each time the class is loaded. */
730
731static void
732nativeClassInitBuffer(JNIEnv *_env)
733{
734    jclass nioAccessClassLocal = _env->FindClass("java/nio/NIOAccess");
735    nioAccessClass = (jclass) _env->NewGlobalRef(nioAccessClassLocal);
736
737    jclass bufferClassLocal = _env->FindClass("java/nio/Buffer");
738    bufferClass = (jclass) _env->NewGlobalRef(bufferClassLocal);
739
740    getBasePointerID = _env->GetStaticMethodID(nioAccessClass,
741            "getBasePointer", "(Ljava/nio/Buffer;)J");
742    getBaseArrayID = _env->GetStaticMethodID(nioAccessClass,
743            "getBaseArray", "(Ljava/nio/Buffer;)Ljava/lang/Object;");
744    getBaseArrayOffsetID = _env->GetStaticMethodID(nioAccessClass,
745            "getBaseArrayOffset", "(Ljava/nio/Buffer;)I");
746    positionID = _env->GetFieldID(bufferClass, "position", "I");
747    limitID = _env->GetFieldID(bufferClass, "limit", "I");
748    elementSizeShiftID =
749        _env->GetFieldID(bufferClass, "_elementSizeShift", "I");
750}
751
752static void *
753getPointer(JNIEnv *_env, jobject buffer, jint *remaining)
754{
755    jint position;
756    jint limit;
757    jint elementSizeShift;
758    jlong pointer;
759    jint offset;
760    void *data;
761
762    position = _env->GetIntField(buffer, positionID);
763    limit = _env->GetIntField(buffer, limitID);
764    elementSizeShift = _env->GetIntField(buffer, elementSizeShiftID);
765    *remaining = (limit - position) << elementSizeShift;
766    pointer = _env->CallStaticLongMethod(nioAccessClass,
767            getBasePointerID, buffer);
768    if (pointer != 0L) {
769        return (void *) (jint) pointer;
770    }
771    return NULL;
772}
773
774class BufferHelper {
775public:
776    BufferHelper(JNIEnv *env, jobject buffer) {
777        mEnv = env;
778        mBuffer = buffer;
779        mData = NULL;
780        mRemaining = 0;
781    }
782
783    bool checkPointer(const char* errorMessage) {
784        if (mBuffer) {
785            mData = getPointer(mEnv, mBuffer, &mRemaining);
786            if (mData == NULL) {
787                mEnv->ThrowNew(gIAEClass, errorMessage);
788            }
789            return mData != NULL;
790        } else {
791            mEnv->ThrowNew(gIAEClass, errorMessage);
792            return false;
793        }
794    }
795
796    inline void* getData() {
797        return mData;
798    }
799
800    inline jint remaining() {
801        return mRemaining;
802    }
803
804private:
805    JNIEnv* mEnv;
806    jobject mBuffer;
807    void* mData;
808    jint mRemaining;
809};
810
811/**
812 * Encode a block of pixels.
813 *
814 * @param in a pointer to a ETC1_DECODED_BLOCK_SIZE array of bytes that represent a
815 * 4 x 4 square of 3-byte pixels in form R, G, B. Byte (3 * (x + 4 * y) is the R
816 * value of pixel (x, y).
817 *
818 * @param validPixelMask is a 16-bit mask where bit (1 << (x + y * 4)) indicates whether
819 * the corresponding (x,y) pixel is valid. Invalid pixel color values are ignored when compressing.
820 *
821 * @param out an ETC1 compressed version of the data.
822 *
823 */
824static void etc1_encodeBlock(JNIEnv *env, jclass clazz,
825        jobject in, jint validPixelMask, jobject out) {
826    if (validPixelMask < 0 || validPixelMask > 15) {
827        env->ThrowNew(gIAEClass, "validPixelMask");
828        return;
829    }
830    BufferHelper inB(env, in);
831    BufferHelper outB(env, out);
832    if (inB.checkPointer("in") && outB.checkPointer("out")) {
833        if (inB.remaining() < ETC1_DECODED_BLOCK_SIZE) {
834            env->ThrowNew(gIAEClass, "in's remaining data < DECODED_BLOCK_SIZE");
835        } else if (outB.remaining() < ETC1_ENCODED_BLOCK_SIZE) {
836            env->ThrowNew(gIAEClass, "out's remaining data < ENCODED_BLOCK_SIZE");
837        } else {
838            etc1_encode_block((etc1_byte*) inB.getData(), validPixelMask,
839                    (etc1_byte*) outB.getData());
840        }
841    }
842}
843
844/**
845 * Decode a block of pixels.
846 *
847 * @param in an ETC1 compressed version of the data.
848 *
849 * @param out a pointer to a ETC_DECODED_BLOCK_SIZE array of bytes that represent a
850 * 4 x 4 square of 3-byte pixels in form R, G, B. Byte (3 * (x + 4 * y) is the R
851 * value of pixel (x, y).
852 */
853static void etc1_decodeBlock(JNIEnv *env, jclass clazz,
854        jobject in, jobject out){
855    BufferHelper inB(env, in);
856    BufferHelper outB(env, out);
857    if (inB.checkPointer("in") && outB.checkPointer("out")) {
858        if (inB.remaining() < ETC1_ENCODED_BLOCK_SIZE) {
859            env->ThrowNew(gIAEClass, "in's remaining data < ENCODED_BLOCK_SIZE");
860        } else if (outB.remaining() < ETC1_DECODED_BLOCK_SIZE) {
861            env->ThrowNew(gIAEClass, "out's remaining data < DECODED_BLOCK_SIZE");
862        } else {
863            etc1_decode_block((etc1_byte*) inB.getData(),
864                    (etc1_byte*) outB.getData());
865        }
866    }
867}
868
869/**
870 * Return the size of the encoded image data (does not include size of PKM header).
871 */
872static jint etc1_getEncodedDataSize(JNIEnv *env, jclass clazz,
873        jint width, jint height) {
874    return etc1_get_encoded_data_size(width, height);
875}
876
877/**
878 * Encode an entire image.
879 * @param in pointer to the image data. Formatted such that
880 *           pixel (x,y) is at pIn + pixelSize * x + stride * y + redOffset;
881 * @param out pointer to encoded data. Must be large enough to store entire encoded image.
882 */
883static void etc1_encodeImage(JNIEnv *env, jclass clazz,
884        jobject in, jint width, jint height,
885        jint pixelSize, jint stride, jobject out) {
886    if (pixelSize < 2 || pixelSize > 3) {
887        env->ThrowNew(gIAEClass, "pixelSize must be 2 or 3");
888        return;
889    }
890    BufferHelper inB(env, in);
891    BufferHelper outB(env, out);
892    if (inB.checkPointer("in") && outB.checkPointer("out")) {
893        jint imageSize = stride * height;
894        jint encodedImageSize = etc1_get_encoded_data_size(width, height);
895        if (inB.remaining() < imageSize) {
896            env->ThrowNew(gIAEClass, "in's remaining data < image size");
897        } else if (outB.remaining() < encodedImageSize) {
898            env->ThrowNew(gIAEClass, "out's remaining data < encoded image size");
899        } else {
900            int result = etc1_encode_image((etc1_byte*) inB.getData(),
901                    width, height, pixelSize,
902                    stride,
903                    (etc1_byte*) outB.getData());
904        }
905    }
906}
907
908/**
909 * Decode an entire image.
910 * @param in the encoded data.
911 * @param out pointer to the image data. Will be written such that
912 *            pixel (x,y) is at pIn + pixelSize * x + stride * y. Must be
913 *            large enough to store entire image.
914 */
915static void etc1_decodeImage(JNIEnv *env, jclass clazz,
916        jobject  in, jobject out,
917        jint width, jint height,
918        jint pixelSize, jint stride) {
919    if (pixelSize < 2 || pixelSize > 3) {
920        env->ThrowNew(gIAEClass, "pixelSize must be 2 or 3");
921        return;
922    }
923    BufferHelper inB(env, in);
924    BufferHelper outB(env, out);
925    if (inB.checkPointer("in") && outB.checkPointer("out")) {
926        jint imageSize = stride * height;
927        jint encodedImageSize = etc1_get_encoded_data_size(width, height);
928        if (inB.remaining() < encodedImageSize) {
929            env->ThrowNew(gIAEClass, "in's remaining data < encoded image size");
930        } else if (outB.remaining() < imageSize) {
931            env->ThrowNew(gIAEClass, "out's remaining data < image size");
932        } else {
933            int result = etc1_decode_image((etc1_byte*) inB.getData(),
934                    (etc1_byte*) outB.getData(),
935                    width, height, pixelSize,
936                    stride);
937        }
938    }
939}
940
941/**
942 * Format a PKM header
943 */
944static void etc1_formatHeader(JNIEnv *env, jclass clazz,
945        jobject header, jint width, jint height) {
946    BufferHelper headerB(env, header);
947    if (headerB.checkPointer("header") ){
948        if (headerB.remaining() < ETC_PKM_HEADER_SIZE) {
949            env->ThrowNew(gIAEClass, "header's remaining data < ETC_PKM_HEADER_SIZE");
950        } else {
951            etc1_pkm_format_header((etc1_byte*) headerB.getData(), width, height);
952        }
953    }
954}
955
956/**
957 * Check if a PKM header is correctly formatted.
958 */
959static jboolean etc1_isValid(JNIEnv *env, jclass clazz,
960        jobject header) {
961    jboolean result = false;
962    BufferHelper headerB(env, header);
963    if (headerB.checkPointer("header") ){
964        if (headerB.remaining() < ETC_PKM_HEADER_SIZE) {
965            env->ThrowNew(gIAEClass, "header's remaining data < ETC_PKM_HEADER_SIZE");
966        } else {
967            result = etc1_pkm_is_valid((etc1_byte*) headerB.getData());
968        }
969    }
970    return result;
971}
972
973/**
974 * Read the image width from a PKM header
975 */
976static jint etc1_getWidth(JNIEnv *env, jclass clazz,
977        jobject header) {
978    jint result = 0;
979    BufferHelper headerB(env, header);
980    if (headerB.checkPointer("header") ){
981        if (headerB.remaining() < ETC_PKM_HEADER_SIZE) {
982            env->ThrowNew(gIAEClass, "header's remaining data < ETC_PKM_HEADER_SIZE");
983        } else {
984            result = etc1_pkm_get_width((etc1_byte*) headerB.getData());
985        }
986    }
987    return result;
988}
989
990/**
991 * Read the image height from a PKM header
992 */
993static int etc1_getHeight(JNIEnv *env, jclass clazz,
994        jobject header) {
995    jint result = 0;
996    BufferHelper headerB(env, header);
997    if (headerB.checkPointer("header") ){
998        if (headerB.remaining() < ETC_PKM_HEADER_SIZE) {
999            env->ThrowNew(gIAEClass, "header's remaining data < ETC_PKM_HEADER_SIZE");
1000        } else {
1001            result = etc1_pkm_get_height((etc1_byte*) headerB.getData());
1002        }
1003    }
1004    return result;
1005}
1006
1007/*
1008 * JNI registration
1009 */
1010
1011static void
1012lookupClasses(JNIEnv* env) {
1013    gIAEClass = (jclass) env->NewGlobalRef(
1014            env->FindClass("java/lang/IllegalArgumentException"));
1015    gUOEClass = (jclass) env->NewGlobalRef(
1016            env->FindClass("java/lang/UnsupportedOperationException"));
1017    gAIOOBEClass = (jclass) env->NewGlobalRef(
1018            env->FindClass("java/lang/ArrayIndexOutOfBoundsException"));
1019}
1020
1021static JNINativeMethod gMatrixMethods[] = {
1022    { "multiplyMM", "([FI[FI[FI)V", (void*)util_multiplyMM },
1023    { "multiplyMV", "([FI[FI[FI)V", (void*)util_multiplyMV },
1024};
1025
1026static JNINativeMethod gVisiblityMethods[] = {
1027    { "computeBoundingSphere", "([FII[FI)V", (void*)util_computeBoundingSphere },
1028    { "frustumCullSpheres", "([FI[FII[III)I", (void*)util_frustumCullSpheres },
1029    { "visibilityTest", "([FI[FI[CII)I", (void*)util_visibilityTest },
1030};
1031
1032static JNINativeMethod gUtilsMethods[] = {
1033    {"nativeClassInit", "()V",                          (void*)nativeUtilsClassInit },
1034    { "native_getInternalFormat", "(Landroid/graphics/Bitmap;)I", (void*) util_getInternalFormat },
1035    { "native_getType", "(Landroid/graphics/Bitmap;)I", (void*) util_getType },
1036    { "native_texImage2D", "(IIILandroid/graphics/Bitmap;II)I", (void*)util_texImage2D },
1037    { "native_texSubImage2D", "(IIIILandroid/graphics/Bitmap;II)I", (void*)util_texSubImage2D },
1038};
1039
1040static JNINativeMethod gEtc1Methods[] = {
1041    { "encodeBlock", "(Ljava/nio/Buffer;ILjava/nio/Buffer;)V", (void*) etc1_encodeBlock },
1042    { "decodeBlock", "(Ljava/nio/Buffer;Ljava/nio/Buffer;)V", (void*) etc1_decodeBlock },
1043    { "getEncodedDataSize", "(II)I", (void*) etc1_getEncodedDataSize },
1044    { "encodeImage", "(Ljava/nio/Buffer;IIIILjava/nio/Buffer;)V", (void*) etc1_encodeImage },
1045    { "decodeImage", "(Ljava/nio/Buffer;Ljava/nio/Buffer;IIII)V", (void*) etc1_decodeImage },
1046    { "formatHeader", "(Ljava/nio/Buffer;II)V", (void*) etc1_formatHeader },
1047    { "isValid", "(Ljava/nio/Buffer;)Z", (void*) etc1_isValid },
1048    { "getWidth", "(Ljava/nio/Buffer;)I", (void*) etc1_getWidth },
1049    { "getHeight", "(Ljava/nio/Buffer;)I", (void*) etc1_getHeight },
1050};
1051
1052typedef struct _ClassRegistrationInfo {
1053    const char* classPath;
1054    JNINativeMethod* methods;
1055    size_t methodCount;
1056} ClassRegistrationInfo;
1057
1058static ClassRegistrationInfo gClasses[] = {
1059        {"android/opengl/Matrix", gMatrixMethods, NELEM(gMatrixMethods)},
1060        {"android/opengl/Visibility", gVisiblityMethods, NELEM(gVisiblityMethods)},
1061        {"android/opengl/GLUtils", gUtilsMethods, NELEM(gUtilsMethods)},
1062        {"android/opengl/ETC1", gEtc1Methods, NELEM(gEtc1Methods)},
1063};
1064
1065int register_android_opengl_classes(JNIEnv* env)
1066{
1067    lookupClasses(env);
1068    nativeClassInitBuffer(env);
1069    int result = 0;
1070    for (int i = 0; i < NELEM(gClasses); i++) {
1071        ClassRegistrationInfo* cri = &gClasses[i];
1072        result = AndroidRuntime::registerNativeMethods(env,
1073                cri->classPath, cri->methods, cri->methodCount);
1074        if (result < 0) {
1075            LOGE("Failed to register %s: %d", cri->classPath, result);
1076            break;
1077        }
1078    }
1079    return result;
1080}
1081
1082} // namespace android
1083
1084