util.cpp revision b2915245b74b3b5541b123e38403f8e26426b4b7
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 const * nativeBitmap = GraphicsJNI::getSkBitmap(env, jbitmap);
622    return getInternalFormat(nativeBitmap->colorType());
623}
624
625static jint util_getType(JNIEnv *env, jclass clazz,
626        jobject jbitmap)
627{
628    SkBitmap const * nativeBitmap = GraphicsJNI::getSkBitmap(env, jbitmap);
629    return getType(nativeBitmap->colorType());
630}
631
632static jint util_texImage2D(JNIEnv *env, jclass clazz,
633        jint target, jint level, jint internalformat,
634        jobject jbitmap, jint type, jint border)
635{
636    SkBitmap const * nativeBitmap = GraphicsJNI::getSkBitmap(env, jbitmap);
637    const SkBitmap& bitmap(*nativeBitmap);
638    SkColorType colorType = bitmap.colorType();
639    if (internalformat < 0) {
640        internalformat = getInternalFormat(colorType);
641    }
642    if (type < 0) {
643        type = getType(colorType);
644    }
645    int err = checkFormat(colorType, internalformat, type);
646    if (err)
647        return err;
648    bitmap.lockPixels();
649    const int w = bitmap.width();
650    const int h = bitmap.height();
651    const void* p = bitmap.getPixels();
652    if (internalformat == GL_PALETTE8_RGBA8_OES) {
653        if (sizeof(SkPMColor) != sizeof(uint32_t)) {
654            err = -1;
655            goto error;
656        }
657        const size_t size = bitmap.getSize();
658        const size_t palette_size = 256*sizeof(SkPMColor);
659        const size_t imageSize = size + palette_size;
660        void* const data = malloc(imageSize);
661        if (data) {
662            void* const pixels = (char*)data + palette_size;
663            SkColorTable* ctable = bitmap.getColorTable();
664            memcpy(data, ctable->readColors(), ctable->count() * sizeof(SkPMColor));
665            memcpy(pixels, p, size);
666            glCompressedTexImage2D(target, level, internalformat, w, h, border, imageSize, data);
667            free(data);
668        } else {
669            err = -1;
670        }
671    } else {
672        glTexImage2D(target, level, internalformat, w, h, border, internalformat, type, p);
673    }
674error:
675    bitmap.unlockPixels();
676    return err;
677}
678
679static jint util_texSubImage2D(JNIEnv *env, jclass clazz,
680        jint target, jint level, jint xoffset, jint yoffset,
681        jobject jbitmap, jint format, jint type)
682{
683    SkBitmap const * nativeBitmap = GraphicsJNI::getSkBitmap(env, jbitmap);
684    const SkBitmap& bitmap(*nativeBitmap);
685    SkColorType colorType = bitmap.colorType();
686    if (format < 0) {
687        format = getInternalFormat(colorType);
688        if (format == GL_PALETTE8_RGBA8_OES)
689            return -1; // glCompressedTexSubImage2D() not supported
690    }
691    int err = checkFormat(colorType, format, type);
692    if (err)
693        return err;
694    bitmap.lockPixels();
695    const int w = bitmap.width();
696    const int h = bitmap.height();
697    const void* p = bitmap.getPixels();
698    glTexSubImage2D(target, level, xoffset, yoffset, w, h, format, type, p);
699    bitmap.unlockPixels();
700    return 0;
701}
702
703/*
704 * ETC1 methods.
705 */
706
707static jclass nioAccessClass;
708static jclass bufferClass;
709static jmethodID getBasePointerID;
710static jmethodID getBaseArrayID;
711static jmethodID getBaseArrayOffsetID;
712static jfieldID positionID;
713static jfieldID limitID;
714static jfieldID elementSizeShiftID;
715
716/* Cache method IDs each time the class is loaded. */
717
718static void
719nativeClassInitBuffer(JNIEnv *env)
720{
721    jclass nioAccessClassLocal = FindClassOrDie(env, "java/nio/NIOAccess");
722    nioAccessClass = MakeGlobalRefOrDie(env, nioAccessClassLocal);
723    getBasePointerID = GetStaticMethodIDOrDie(env, nioAccessClass,
724            "getBasePointer", "(Ljava/nio/Buffer;)J");
725    getBaseArrayID = GetStaticMethodIDOrDie(env, nioAccessClass,
726            "getBaseArray", "(Ljava/nio/Buffer;)Ljava/lang/Object;");
727    getBaseArrayOffsetID = GetStaticMethodIDOrDie(env, nioAccessClass,
728            "getBaseArrayOffset", "(Ljava/nio/Buffer;)I");
729
730    jclass bufferClassLocal = FindClassOrDie(env, "java/nio/Buffer");
731    bufferClass = MakeGlobalRefOrDie(env, bufferClassLocal);
732    positionID = GetFieldIDOrDie(env, bufferClass, "position", "I");
733    limitID = GetFieldIDOrDie(env, bufferClass, "limit", "I");
734    elementSizeShiftID = GetFieldIDOrDie(env, bufferClass, "_elementSizeShift", "I");
735}
736
737static void *
738getPointer(JNIEnv *_env, jobject buffer, jint *remaining)
739{
740    jint position;
741    jint limit;
742    jint elementSizeShift;
743    jlong pointer;
744
745    position = _env->GetIntField(buffer, positionID);
746    limit = _env->GetIntField(buffer, limitID);
747    elementSizeShift = _env->GetIntField(buffer, elementSizeShiftID);
748    *remaining = (limit - position) << elementSizeShift;
749    pointer = _env->CallStaticLongMethod(nioAccessClass,
750            getBasePointerID, buffer);
751    if (pointer != 0L) {
752        return reinterpret_cast<void *>(pointer);
753    }
754    return NULL;
755}
756
757class BufferHelper {
758public:
759    BufferHelper(JNIEnv *env, jobject buffer) {
760        mEnv = env;
761        mBuffer = buffer;
762        mData = NULL;
763        mRemaining = 0;
764    }
765
766    bool checkPointer(const char* errorMessage) {
767        if (mBuffer) {
768            mData = getPointer(mEnv, mBuffer, &mRemaining);
769            if (mData == NULL) {
770                doThrowIAE(mEnv, errorMessage);
771            }
772            return mData != NULL;
773        } else {
774            doThrowIAE(mEnv, errorMessage);
775            return false;
776        }
777    }
778
779    inline void* getData() {
780        return mData;
781    }
782
783    inline jint remaining() {
784        return mRemaining;
785    }
786
787private:
788    JNIEnv* mEnv;
789    jobject mBuffer;
790    void* mData;
791    jint mRemaining;
792};
793
794/**
795 * Encode a block of pixels.
796 *
797 * @param in a pointer to a ETC1_DECODED_BLOCK_SIZE array of bytes that represent a
798 * 4 x 4 square of 3-byte pixels in form R, G, B. Byte (3 * (x + 4 * y) is the R
799 * value of pixel (x, y).
800 *
801 * @param validPixelMask is a 16-bit mask where bit (1 << (x + y * 4)) indicates whether
802 * the corresponding (x,y) pixel is valid. Invalid pixel color values are ignored when compressing.
803 *
804 * @param out an ETC1 compressed version of the data.
805 *
806 */
807static void etc1_encodeBlock(JNIEnv *env, jclass clazz,
808        jobject in, jint validPixelMask, jobject out) {
809    if (validPixelMask < 0 || validPixelMask > 15) {
810        doThrowIAE(env, "validPixelMask");
811        return;
812    }
813    BufferHelper inB(env, in);
814    BufferHelper outB(env, out);
815    if (inB.checkPointer("in") && outB.checkPointer("out")) {
816        if (inB.remaining() < ETC1_DECODED_BLOCK_SIZE) {
817            doThrowIAE(env, "in's remaining data < DECODED_BLOCK_SIZE");
818        } else if (outB.remaining() < ETC1_ENCODED_BLOCK_SIZE) {
819            doThrowIAE(env, "out's remaining data < ENCODED_BLOCK_SIZE");
820        } else {
821            etc1_encode_block((etc1_byte*) inB.getData(), validPixelMask,
822                    (etc1_byte*) outB.getData());
823        }
824    }
825}
826
827/**
828 * Decode a block of pixels.
829 *
830 * @param in an ETC1 compressed version of the data.
831 *
832 * @param out a pointer to a ETC_DECODED_BLOCK_SIZE array of bytes that represent a
833 * 4 x 4 square of 3-byte pixels in form R, G, B. Byte (3 * (x + 4 * y) is the R
834 * value of pixel (x, y).
835 */
836static void etc1_decodeBlock(JNIEnv *env, jclass clazz,
837        jobject in, jobject out){
838    BufferHelper inB(env, in);
839    BufferHelper outB(env, out);
840    if (inB.checkPointer("in") && outB.checkPointer("out")) {
841        if (inB.remaining() < ETC1_ENCODED_BLOCK_SIZE) {
842            doThrowIAE(env, "in's remaining data < ENCODED_BLOCK_SIZE");
843        } else if (outB.remaining() < ETC1_DECODED_BLOCK_SIZE) {
844            doThrowIAE(env, "out's remaining data < DECODED_BLOCK_SIZE");
845        } else {
846            etc1_decode_block((etc1_byte*) inB.getData(),
847                    (etc1_byte*) outB.getData());
848        }
849    }
850}
851
852/**
853 * Return the size of the encoded image data (does not include size of PKM header).
854 */
855static jint etc1_getEncodedDataSize(JNIEnv *env, jclass clazz,
856        jint width, jint height) {
857    return etc1_get_encoded_data_size(width, height);
858}
859
860/**
861 * Encode an entire image.
862 * @param in pointer to the image data. Formatted such that
863 *           pixel (x,y) is at pIn + pixelSize * x + stride * y + redOffset;
864 * @param out pointer to encoded data. Must be large enough to store entire encoded image.
865 */
866static void etc1_encodeImage(JNIEnv *env, jclass clazz,
867        jobject in, jint width, jint height,
868        jint pixelSize, jint stride, jobject out) {
869    if (pixelSize < 2 || pixelSize > 3) {
870        doThrowIAE(env, "pixelSize must be 2 or 3");
871        return;
872    }
873    BufferHelper inB(env, in);
874    BufferHelper outB(env, out);
875    if (inB.checkPointer("in") && outB.checkPointer("out")) {
876        jint imageSize = stride * height;
877        jint encodedImageSize = etc1_get_encoded_data_size(width, height);
878        if (inB.remaining() < imageSize) {
879            doThrowIAE(env, "in's remaining data < image size");
880        } else if (outB.remaining() < encodedImageSize) {
881            doThrowIAE(env, "out's remaining data < encoded image size");
882        } else {
883            etc1_encode_image((etc1_byte*) inB.getData(), width, height, pixelSize, stride,
884                              (etc1_byte*) outB.getData());
885        }
886    }
887}
888
889/**
890 * Decode an entire image.
891 * @param in the encoded data.
892 * @param out pointer to the image data. Will be written such that
893 *            pixel (x,y) is at pIn + pixelSize * x + stride * y. Must be
894 *            large enough to store entire image.
895 */
896static void etc1_decodeImage(JNIEnv *env, jclass clazz,
897        jobject  in, jobject out,
898        jint width, jint height,
899        jint pixelSize, jint stride) {
900    if (pixelSize < 2 || pixelSize > 3) {
901        doThrowIAE(env, "pixelSize must be 2 or 3");
902        return;
903    }
904    BufferHelper inB(env, in);
905    BufferHelper outB(env, out);
906    if (inB.checkPointer("in") && outB.checkPointer("out")) {
907        jint imageSize = stride * height;
908        jint encodedImageSize = etc1_get_encoded_data_size(width, height);
909        if (inB.remaining() < encodedImageSize) {
910            doThrowIAE(env, "in's remaining data < encoded image size");
911        } else if (outB.remaining() < imageSize) {
912            doThrowIAE(env, "out's remaining data < image size");
913        } else {
914            etc1_decode_image((etc1_byte*) inB.getData(), (etc1_byte*) outB.getData(),
915                              width, height, pixelSize, stride);
916        }
917    }
918}
919
920/**
921 * Format a PKM header
922 */
923static void etc1_formatHeader(JNIEnv *env, jclass clazz,
924        jobject header, jint width, jint height) {
925    BufferHelper headerB(env, header);
926    if (headerB.checkPointer("header") ){
927        if (headerB.remaining() < ETC_PKM_HEADER_SIZE) {
928            doThrowIAE(env, "header's remaining data < ETC_PKM_HEADER_SIZE");
929        } else {
930            etc1_pkm_format_header((etc1_byte*) headerB.getData(), width, height);
931        }
932    }
933}
934
935/**
936 * Check if a PKM header is correctly formatted.
937 */
938static jboolean etc1_isValid(JNIEnv *env, jclass clazz,
939        jobject header) {
940    jboolean result = false;
941    BufferHelper headerB(env, header);
942    if (headerB.checkPointer("header") ){
943        if (headerB.remaining() < ETC_PKM_HEADER_SIZE) {
944            doThrowIAE(env, "header's remaining data < ETC_PKM_HEADER_SIZE");
945        } else {
946            result = etc1_pkm_is_valid((etc1_byte*) headerB.getData());
947        }
948    }
949    return result ? JNI_TRUE : JNI_FALSE;
950}
951
952/**
953 * Read the image width from a PKM header
954 */
955static jint etc1_getWidth(JNIEnv *env, jclass clazz,
956        jobject header) {
957    jint result = 0;
958    BufferHelper headerB(env, header);
959    if (headerB.checkPointer("header") ){
960        if (headerB.remaining() < ETC_PKM_HEADER_SIZE) {
961            doThrowIAE(env, "header's remaining data < ETC_PKM_HEADER_SIZE");
962        } else {
963            result = etc1_pkm_get_width((etc1_byte*) headerB.getData());
964        }
965    }
966    return result;
967}
968
969/**
970 * Read the image height from a PKM header
971 */
972static jint etc1_getHeight(JNIEnv *env, jclass clazz,
973        jobject header) {
974    jint result = 0;
975    BufferHelper headerB(env, header);
976    if (headerB.checkPointer("header") ){
977        if (headerB.remaining() < ETC_PKM_HEADER_SIZE) {
978            doThrowIAE(env, "header's remaining data < ETC_PKM_HEADER_SIZE");
979        } else {
980            result = etc1_pkm_get_height((etc1_byte*) headerB.getData());
981        }
982    }
983    return result;
984}
985
986/*
987 * JNI registration
988 */
989
990static JNINativeMethod gMatrixMethods[] = {
991    { "multiplyMM", "([FI[FI[FI)V", (void*)util_multiplyMM },
992    { "multiplyMV", "([FI[FI[FI)V", (void*)util_multiplyMV },
993};
994
995static JNINativeMethod gVisibilityMethods[] = {
996    { "computeBoundingSphere", "([FII[FI)V", (void*)util_computeBoundingSphere },
997    { "frustumCullSpheres", "([FI[FII[III)I", (void*)util_frustumCullSpheres },
998    { "visibilityTest", "([FI[FI[CII)I", (void*)util_visibilityTest },
999};
1000
1001static JNINativeMethod gUtilsMethods[] = {
1002    { "native_getInternalFormat", "(Landroid/graphics/Bitmap;)I", (void*) util_getInternalFormat },
1003    { "native_getType", "(Landroid/graphics/Bitmap;)I", (void*) util_getType },
1004    { "native_texImage2D", "(IIILandroid/graphics/Bitmap;II)I", (void*)util_texImage2D },
1005    { "native_texSubImage2D", "(IIIILandroid/graphics/Bitmap;II)I", (void*)util_texSubImage2D },
1006    { "setTracingLevel", "(I)V",                        (void*)setTracingLevel },
1007};
1008
1009static JNINativeMethod gEtc1Methods[] = {
1010    { "encodeBlock", "(Ljava/nio/Buffer;ILjava/nio/Buffer;)V", (void*) etc1_encodeBlock },
1011    { "decodeBlock", "(Ljava/nio/Buffer;Ljava/nio/Buffer;)V", (void*) etc1_decodeBlock },
1012    { "getEncodedDataSize", "(II)I", (void*) etc1_getEncodedDataSize },
1013    { "encodeImage", "(Ljava/nio/Buffer;IIIILjava/nio/Buffer;)V", (void*) etc1_encodeImage },
1014    { "decodeImage", "(Ljava/nio/Buffer;Ljava/nio/Buffer;IIII)V", (void*) etc1_decodeImage },
1015    { "formatHeader", "(Ljava/nio/Buffer;II)V", (void*) etc1_formatHeader },
1016    { "isValid", "(Ljava/nio/Buffer;)Z", (void*) etc1_isValid },
1017    { "getWidth", "(Ljava/nio/Buffer;)I", (void*) etc1_getWidth },
1018    { "getHeight", "(Ljava/nio/Buffer;)I", (void*) etc1_getHeight },
1019};
1020
1021typedef struct _ClassRegistrationInfo {
1022    const char* classPath;
1023    JNINativeMethod* methods;
1024    size_t methodCount;
1025} ClassRegistrationInfo;
1026
1027static ClassRegistrationInfo gClasses[] = {
1028    {"android/opengl/Matrix", gMatrixMethods, NELEM(gMatrixMethods)},
1029    {"android/opengl/Visibility", gVisibilityMethods, NELEM(gVisibilityMethods)},
1030    {"android/opengl/GLUtils", gUtilsMethods, NELEM(gUtilsMethods)},
1031    {"android/opengl/ETC1", gEtc1Methods, NELEM(gEtc1Methods)},
1032};
1033
1034int register_android_opengl_classes(JNIEnv* env)
1035{
1036    nativeClassInitBuffer(env);
1037    int result = 0;
1038    for (int i = 0; i < NELEM(gClasses); i++) {
1039        ClassRegistrationInfo* cri = &gClasses[i];
1040        result = RegisterMethodsOrDie(env, cri->classPath, cri->methods, cri->methodCount);
1041    }
1042    return result;
1043}
1044
1045} // namespace android
1046