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