| /** |
| ** Copyright 2007, The Android Open Source Project |
| ** |
| ** Licensed under the Apache License, Version 2.0 (the "License"); |
| ** you may not use this file except in compliance with the License. |
| ** You may obtain a copy of the License at |
| ** |
| ** http://www.apache.org/licenses/LICENSE-2.0 |
| ** |
| ** Unless required by applicable law or agreed to in writing, software |
| ** distributed under the License is distributed on an "AS IS" BASIS, |
| ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| ** See the License for the specific language governing permissions and |
| ** limitations under the License. |
| */ |
| |
| #include "jni.h" |
| #include "JNIHelp.h" |
| |
| #include <math.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <assert.h> |
| #include <dlfcn.h> |
| |
| #include <GLES/gl.h> |
| #include <ETC1/etc1.h> |
| |
| #include <core/SkBitmap.h> |
| |
| #include "android_runtime/AndroidRuntime.h" |
| |
| #undef LOG_TAG |
| #define LOG_TAG "OpenGLUtil" |
| #include <utils/Log.h> |
| #include "utils/misc.h" |
| |
| #include "poly.h" |
| |
| namespace android { |
| |
| static inline |
| void mx4transform(float x, float y, float z, float w, const float* pM, float* pDest) { |
| pDest[0] = pM[0 + 4 * 0] * x + pM[0 + 4 * 1] * y + pM[0 + 4 * 2] * z + pM[0 + 4 * 3] * w; |
| pDest[1] = pM[1 + 4 * 0] * x + pM[1 + 4 * 1] * y + pM[1 + 4 * 2] * z + pM[1 + 4 * 3] * w; |
| pDest[2] = pM[2 + 4 * 0] * x + pM[2 + 4 * 1] * y + pM[2 + 4 * 2] * z + pM[2 + 4 * 3] * w; |
| pDest[3] = pM[3 + 4 * 0] * x + pM[3 + 4 * 1] * y + pM[3 + 4 * 2] * z + pM[3 + 4 * 3] * w; |
| } |
| |
| class MallocHelper { |
| public: |
| MallocHelper() { |
| mData = 0; |
| } |
| |
| ~MallocHelper() { |
| if (mData != 0) { |
| free(mData); |
| } |
| } |
| |
| void* alloc(size_t size) { |
| mData = malloc(size); |
| return mData; |
| } |
| |
| private: |
| void* mData; |
| }; |
| |
| #if 0 |
| static |
| void |
| print_poly(const char* label, Poly* pPoly) { |
| ALOGI("%s: %d verts", label, pPoly->n); |
| for(int i = 0; i < pPoly->n; i++) { |
| Poly_vert* pV = & pPoly->vert[i]; |
| ALOGI("[%d] %g, %g, %g %g", i, pV->sx, pV->sy, pV->sz, pV->sw); |
| } |
| } |
| #endif |
| |
| static |
| int visibilityTest(float* pWS, float* pPositions, int positionsLength, |
| unsigned short* pIndices, int indexCount) { |
| MallocHelper mallocHelper; |
| int result = POLY_CLIP_OUT; |
| float* pTransformed = 0; |
| int transformedIndexCount = 0; |
| |
| if ( indexCount < 3 ) { |
| return POLY_CLIP_OUT; |
| } |
| |
| // Find out how many vertices we need to transform |
| // We transform every vertex between the min and max indices, inclusive. |
| // This is OK for the data sets we expect to use with this function, but |
| // for other loads it might be better to use a more sophisticated vertex |
| // cache of some sort. |
| |
| int minIndex = 65536; |
| int maxIndex = -1; |
| for(int i = 0; i < indexCount; i++) { |
| int index = pIndices[i]; |
| if ( index < minIndex ) { |
| minIndex = index; |
| } |
| if ( index > maxIndex ) { |
| maxIndex = index; |
| } |
| } |
| |
| if ( maxIndex * 3 > positionsLength) { |
| return -1; |
| } |
| |
| transformedIndexCount = maxIndex - minIndex + 1; |
| pTransformed = (float*) mallocHelper.alloc(transformedIndexCount * 4 * sizeof(float)); |
| |
| if (pTransformed == 0 ) { |
| return -2; |
| } |
| |
| // Transform the vertices |
| { |
| const float* pSrc = pPositions + 3 * minIndex; |
| float* pDst = pTransformed; |
| for (int i = 0; i < transformedIndexCount; i++, pSrc += 3, pDst += 4) { |
| mx4transform(pSrc[0], pSrc[1], pSrc[2], 1.0f, pWS, pDst); |
| } |
| } |
| |
| // Clip the triangles |
| |
| Poly poly; |
| float* pDest = & poly.vert[0].sx; |
| for (int i = 0; i < indexCount; i += 3) { |
| poly.n = 3; |
| memcpy(pDest , pTransformed + 4 * (pIndices[i ] - minIndex), 4 * sizeof(float)); |
| memcpy(pDest + 4, pTransformed + 4 * (pIndices[i + 1] - minIndex), 4 * sizeof(float)); |
| memcpy(pDest + 8, pTransformed + 4 * (pIndices[i + 2] - minIndex), 4 * sizeof(float)); |
| result = poly_clip_to_frustum(&poly); |
| if ( result != POLY_CLIP_OUT) { |
| return result; |
| } |
| } |
| |
| return result; |
| } |
| |
| static void doThrowIAE(JNIEnv* env, const char* msg) { |
| jniThrowException(env, "java/lang/IllegalArgumentException", msg); |
| } |
| |
| template<class JArray, class T> |
| class ArrayHelper { |
| public: |
| ArrayHelper(JNIEnv* env, JArray ref, jint offset, jint minSize) { |
| mEnv = env; |
| mRef = ref; |
| mOffset = offset; |
| mMinSize = minSize; |
| mBase = 0; |
| mReleaseParam = JNI_ABORT; |
| } |
| |
| ~ArrayHelper() { |
| if (mBase) { |
| mEnv->ReleasePrimitiveArrayCritical(mRef, mBase, mReleaseParam); |
| } |
| } |
| |
| // We seperate the bounds check from the initialization because we want to |
| // be able to bounds-check multiple arrays, and we can't throw an exception |
| // after we've called GetPrimitiveArrayCritical. |
| |
| // Return true if the bounds check succeeded |
| // Else instruct the runtime to throw an exception |
| |
| bool check() { |
| if ( ! mRef) { |
| doThrowIAE(mEnv, "array == null"); |
| return false; |
| } |
| if ( mOffset < 0) { |
| doThrowIAE(mEnv, "offset < 0"); |
| return false; |
| } |
| mLength = mEnv->GetArrayLength(mRef) - mOffset; |
| if (mLength < mMinSize ) { |
| doThrowIAE(mEnv, "length - offset < n"); |
| return false; |
| } |
| return true; |
| } |
| |
| // Bind the array. |
| |
| void bind() { |
| mBase = (T*) mEnv->GetPrimitiveArrayCritical(mRef, (jboolean *) 0); |
| mData = mBase + mOffset; |
| } |
| |
| void commitChanges() { |
| mReleaseParam = 0; |
| } |
| |
| T* mData; |
| int mLength; |
| |
| private: |
| T* mBase; |
| JNIEnv* mEnv; |
| JArray mRef; |
| jint mOffset; |
| jint mMinSize; |
| int mReleaseParam; |
| }; |
| |
| typedef ArrayHelper<jfloatArray, float> FloatArrayHelper; |
| typedef ArrayHelper<jcharArray, unsigned short> UnsignedShortArrayHelper; |
| typedef ArrayHelper<jintArray, int> IntArrayHelper; |
| typedef ArrayHelper<jbyteArray, unsigned char> ByteArrayHelper; |
| |
| inline float distance2(float x, float y, float z) { |
| return x * x + y * y + z * z; |
| } |
| |
| inline float distance(float x, float y, float z) { |
| return sqrtf(distance2(x, y, z)); |
| } |
| |
| static |
| void util_computeBoundingSphere(JNIEnv *env, jclass clazz, |
| jfloatArray positions_ref, jint positionsOffset, jint positionsCount, |
| jfloatArray sphere_ref, jint sphereOffset) { |
| FloatArrayHelper positions(env, positions_ref, positionsOffset, 0); |
| FloatArrayHelper sphere(env, sphere_ref, sphereOffset, 4); |
| |
| bool checkOK = positions.check() && sphere.check(); |
| if (! checkOK) { |
| return; |
| } |
| |
| positions.bind(); |
| sphere.bind(); |
| |
| if ( positionsCount < 1 ) { |
| doThrowIAE(env, "positionsCount < 1"); |
| return; |
| } |
| |
| const float* pSrc = positions.mData; |
| |
| // find bounding box |
| float x0 = *pSrc++; |
| float x1 = x0; |
| float y0 = *pSrc++; |
| float y1 = y0; |
| float z0 = *pSrc++; |
| float z1 = z0; |
| |
| for(int i = 1; i < positionsCount; i++) { |
| { |
| float x = *pSrc++; |
| if (x < x0) { |
| x0 = x; |
| } |
| else if (x > x1) { |
| x1 = x; |
| } |
| } |
| { |
| float y = *pSrc++; |
| if (y < y0) { |
| y0 = y; |
| } |
| else if (y > y1) { |
| y1 = y; |
| } |
| } |
| { |
| float z = *pSrc++; |
| if (z < z0) { |
| z0 = z; |
| } |
| else if (z > z1) { |
| z1 = z; |
| } |
| } |
| } |
| |
| // Because we know our input meshes fit pretty well into bounding boxes, |
| // just take the diagonal of the box as defining our sphere. |
| float* pSphere = sphere.mData; |
| float dx = x1 - x0; |
| float dy = y1 - y0; |
| float dz = z1 - z0; |
| *pSphere++ = x0 + dx * 0.5f; |
| *pSphere++ = y0 + dy * 0.5f; |
| *pSphere++ = z0 + dz * 0.5f; |
| *pSphere++ = distance(dx, dy, dz) * 0.5f; |
| |
| sphere.commitChanges(); |
| } |
| |
| static void normalizePlane(float* p) { |
| float rdist = 1.0f / distance(p[0], p[1], p[2]); |
| for(int i = 0; i < 4; i++) { |
| p[i] *= rdist; |
| } |
| } |
| |
| static inline float dot3(float x0, float y0, float z0, float x1, float y1, float z1) { |
| return x0 * x1 + y0 * y1 + z0 * z1; |
| } |
| |
| static inline float signedDistance(const float* pPlane, float x, float y, float z) { |
| return dot3(pPlane[0], pPlane[1], pPlane[2], x, y, z) + pPlane[3]; |
| } |
| |
| // Return true if the sphere intersects or is inside the frustum |
| |
| static bool sphereHitsFrustum(const float* pFrustum, const float* pSphere) { |
| float x = pSphere[0]; |
| float y = pSphere[1]; |
| float z = pSphere[2]; |
| float negRadius = -pSphere[3]; |
| for (int i = 0; i < 6; i++, pFrustum += 4) { |
| if (signedDistance(pFrustum, x, y, z) <= negRadius) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| static void computeFrustum(const float* m, float* f) { |
| float m3 = m[3]; |
| float m7 = m[7]; |
| float m11 = m[11]; |
| float m15 = m[15]; |
| // right |
| f[0] = m3 - m[0]; |
| f[1] = m7 - m[4]; |
| f[2] = m11 - m[8]; |
| f[3] = m15 - m[12]; |
| normalizePlane(f); |
| f+= 4; |
| |
| // left |
| f[0] = m3 + m[0]; |
| f[1] = m7 + m[4]; |
| f[2] = m11 + m[8]; |
| f[3] = m15 + m[12]; |
| normalizePlane(f); |
| f+= 4; |
| |
| // top |
| f[0] = m3 - m[1]; |
| f[1] = m7 - m[5]; |
| f[2] = m11 - m[9]; |
| f[3] = m15 - m[13]; |
| normalizePlane(f); |
| f+= 4; |
| |
| // bottom |
| f[0] = m3 + m[1]; |
| f[1] = m7 + m[5]; |
| f[2] = m11 + m[9]; |
| f[3] = m15 + m[13]; |
| normalizePlane(f); |
| f+= 4; |
| |
| // far |
| f[0] = m3 - m[2]; |
| f[1] = m7 - m[6]; |
| f[2] = m11 - m[10]; |
| f[3] = m15 - m[14]; |
| normalizePlane(f); |
| f+= 4; |
| |
| // near |
| f[0] = m3 + m[2]; |
| f[1] = m7 + m[6]; |
| f[2] = m11 + m[10]; |
| f[3] = m15 + m[14]; |
| normalizePlane(f); |
| } |
| |
| static |
| int util_frustumCullSpheres(JNIEnv *env, jclass clazz, |
| jfloatArray mvp_ref, jint mvpOffset, |
| jfloatArray spheres_ref, jint spheresOffset, jint spheresCount, |
| jintArray results_ref, jint resultsOffset, jint resultsCapacity) { |
| float frustum[6*4]; |
| int outputCount; |
| int* pResults; |
| float* pSphere; |
| FloatArrayHelper mvp(env, mvp_ref, mvpOffset, 16); |
| FloatArrayHelper spheres(env, spheres_ref, spheresOffset, spheresCount * 4); |
| IntArrayHelper results(env, results_ref, resultsOffset, resultsCapacity); |
| |
| bool initializedOK = mvp.check() && spheres.check() && results.check(); |
| if (! initializedOK) { |
| return -1; |
| } |
| |
| mvp.bind(); |
| spheres.bind(); |
| results.bind(); |
| |
| computeFrustum(mvp.mData, frustum); |
| |
| // Cull the spheres |
| |
| pSphere = spheres.mData; |
| pResults = results.mData; |
| outputCount = 0; |
| for(int i = 0; i < spheresCount; i++, pSphere += 4) { |
| if (sphereHitsFrustum(frustum, pSphere)) { |
| if (outputCount < resultsCapacity) { |
| *pResults++ = i; |
| } |
| outputCount++; |
| } |
| } |
| results.commitChanges(); |
| return outputCount; |
| } |
| |
| /* |
| public native int visibilityTest(float[] ws, int wsOffset, |
| float[] positions, int positionsOffset, |
| char[] indices, int indicesOffset, int indexCount); |
| */ |
| |
| static |
| int util_visibilityTest(JNIEnv *env, jclass clazz, |
| jfloatArray ws_ref, jint wsOffset, |
| jfloatArray positions_ref, jint positionsOffset, |
| jcharArray indices_ref, jint indicesOffset, jint indexCount) { |
| |
| FloatArrayHelper ws(env, ws_ref, wsOffset, 16); |
| FloatArrayHelper positions(env, positions_ref, positionsOffset, 0); |
| UnsignedShortArrayHelper indices(env, indices_ref, indicesOffset, 0); |
| |
| bool checkOK = ws.check() && positions.check() && indices.check(); |
| if (! checkOK) { |
| // Return value will be ignored, because an exception has been thrown. |
| return -1; |
| } |
| |
| if (indices.mLength < indexCount) { |
| doThrowIAE(env, "length < offset + indexCount"); |
| return -1; |
| } |
| |
| ws.bind(); |
| positions.bind(); |
| indices.bind(); |
| |
| return visibilityTest(ws.mData, |
| positions.mData, positions.mLength, |
| indices.mData, indexCount); |
| } |
| |
| #define I(_i, _j) ((_j)+ 4*(_i)) |
| |
| static |
| void multiplyMM(float* r, const float* lhs, const float* rhs) |
| { |
| for (int i=0 ; i<4 ; i++) { |
| register const float rhs_i0 = rhs[ I(i,0) ]; |
| register float ri0 = lhs[ I(0,0) ] * rhs_i0; |
| register float ri1 = lhs[ I(0,1) ] * rhs_i0; |
| register float ri2 = lhs[ I(0,2) ] * rhs_i0; |
| register float ri3 = lhs[ I(0,3) ] * rhs_i0; |
| for (int j=1 ; j<4 ; j++) { |
| register const float rhs_ij = rhs[ I(i,j) ]; |
| ri0 += lhs[ I(j,0) ] * rhs_ij; |
| ri1 += lhs[ I(j,1) ] * rhs_ij; |
| ri2 += lhs[ I(j,2) ] * rhs_ij; |
| ri3 += lhs[ I(j,3) ] * rhs_ij; |
| } |
| r[ I(i,0) ] = ri0; |
| r[ I(i,1) ] = ri1; |
| r[ I(i,2) ] = ri2; |
| r[ I(i,3) ] = ri3; |
| } |
| } |
| |
| static |
| void util_multiplyMM(JNIEnv *env, jclass clazz, |
| jfloatArray result_ref, jint resultOffset, |
| jfloatArray lhs_ref, jint lhsOffset, |
| jfloatArray rhs_ref, jint rhsOffset) { |
| |
| FloatArrayHelper resultMat(env, result_ref, resultOffset, 16); |
| FloatArrayHelper lhs(env, lhs_ref, lhsOffset, 16); |
| FloatArrayHelper rhs(env, rhs_ref, rhsOffset, 16); |
| |
| bool checkOK = resultMat.check() && lhs.check() && rhs.check(); |
| |
| if ( !checkOK ) { |
| return; |
| } |
| |
| resultMat.bind(); |
| lhs.bind(); |
| rhs.bind(); |
| |
| multiplyMM(resultMat.mData, lhs.mData, rhs.mData); |
| |
| resultMat.commitChanges(); |
| } |
| |
| static |
| void multiplyMV(float* r, const float* lhs, const float* rhs) |
| { |
| mx4transform(rhs[0], rhs[1], rhs[2], rhs[3], lhs, r); |
| } |
| |
| static |
| void util_multiplyMV(JNIEnv *env, jclass clazz, |
| jfloatArray result_ref, jint resultOffset, |
| jfloatArray lhs_ref, jint lhsOffset, |
| jfloatArray rhs_ref, jint rhsOffset) { |
| |
| FloatArrayHelper resultV(env, result_ref, resultOffset, 4); |
| FloatArrayHelper lhs(env, lhs_ref, lhsOffset, 16); |
| FloatArrayHelper rhs(env, rhs_ref, rhsOffset, 4); |
| |
| bool checkOK = resultV.check() && lhs.check() && rhs.check(); |
| |
| if ( !checkOK ) { |
| return; |
| } |
| |
| resultV.bind(); |
| lhs.bind(); |
| rhs.bind(); |
| |
| multiplyMV(resultV.mData, lhs.mData, rhs.mData); |
| |
| resultV.commitChanges(); |
| } |
| |
| // --------------------------------------------------------------------------- |
| |
| static jfieldID nativeBitmapID = 0; |
| |
| void nativeUtilsClassInit(JNIEnv *env, jclass clazz) |
| { |
| jclass bitmapClass = env->FindClass("android/graphics/Bitmap"); |
| nativeBitmapID = env->GetFieldID(bitmapClass, "mNativeBitmap", "I"); |
| } |
| |
| static int checkFormat(SkBitmap::Config config, int format, int type) |
| { |
| switch(config) { |
| case SkBitmap::kIndex8_Config: |
| if (format == GL_PALETTE8_RGBA8_OES) |
| return 0; |
| case SkBitmap::kARGB_8888_Config: |
| case SkBitmap::kA8_Config: |
| if (type == GL_UNSIGNED_BYTE) |
| return 0; |
| case SkBitmap::kARGB_4444_Config: |
| case SkBitmap::kRGB_565_Config: |
| switch (type) { |
| case GL_UNSIGNED_SHORT_4_4_4_4: |
| case GL_UNSIGNED_SHORT_5_6_5: |
| case GL_UNSIGNED_SHORT_5_5_5_1: |
| return 0; |
| case GL_UNSIGNED_BYTE: |
| if (format == GL_LUMINANCE_ALPHA) |
| return 0; |
| } |
| break; |
| default: |
| break; |
| } |
| return -1; |
| } |
| |
| static int getInternalFormat(SkBitmap::Config config) |
| { |
| switch(config) { |
| case SkBitmap::kA8_Config: |
| return GL_ALPHA; |
| case SkBitmap::kARGB_4444_Config: |
| return GL_RGBA; |
| case SkBitmap::kARGB_8888_Config: |
| return GL_RGBA; |
| case SkBitmap::kIndex8_Config: |
| return GL_PALETTE8_RGBA8_OES; |
| case SkBitmap::kRGB_565_Config: |
| return GL_RGB; |
| default: |
| return -1; |
| } |
| } |
| |
| static int getType(SkBitmap::Config config) |
| { |
| switch(config) { |
| case SkBitmap::kA8_Config: |
| return GL_UNSIGNED_BYTE; |
| case SkBitmap::kARGB_4444_Config: |
| return GL_UNSIGNED_SHORT_4_4_4_4; |
| case SkBitmap::kARGB_8888_Config: |
| return GL_UNSIGNED_BYTE; |
| case SkBitmap::kIndex8_Config: |
| return -1; // No type for compressed data. |
| case SkBitmap::kRGB_565_Config: |
| return GL_UNSIGNED_SHORT_5_6_5; |
| default: |
| return -1; |
| } |
| } |
| |
| static jint util_getInternalFormat(JNIEnv *env, jclass clazz, |
| jobject jbitmap) |
| { |
| SkBitmap const * nativeBitmap = |
| (SkBitmap const *)env->GetIntField(jbitmap, nativeBitmapID); |
| const SkBitmap& bitmap(*nativeBitmap); |
| SkBitmap::Config config = bitmap.getConfig(); |
| return getInternalFormat(config); |
| } |
| |
| static jint util_getType(JNIEnv *env, jclass clazz, |
| jobject jbitmap) |
| { |
| SkBitmap const * nativeBitmap = |
| (SkBitmap const *)env->GetIntField(jbitmap, nativeBitmapID); |
| const SkBitmap& bitmap(*nativeBitmap); |
| SkBitmap::Config config = bitmap.getConfig(); |
| return getType(config); |
| } |
| |
| static jint util_texImage2D(JNIEnv *env, jclass clazz, |
| jint target, jint level, jint internalformat, |
| jobject jbitmap, jint type, jint border) |
| { |
| SkBitmap const * nativeBitmap = |
| (SkBitmap const *)env->GetIntField(jbitmap, nativeBitmapID); |
| const SkBitmap& bitmap(*nativeBitmap); |
| SkBitmap::Config config = bitmap.getConfig(); |
| if (internalformat < 0) { |
| internalformat = getInternalFormat(config); |
| } |
| if (type < 0) { |
| type = getType(config); |
| } |
| int err = checkFormat(config, internalformat, type); |
| if (err) |
| return err; |
| bitmap.lockPixels(); |
| const int w = bitmap.width(); |
| const int h = bitmap.height(); |
| const void* p = bitmap.getPixels(); |
| if (internalformat == GL_PALETTE8_RGBA8_OES) { |
| if (sizeof(SkPMColor) != sizeof(uint32_t)) { |
| err = -1; |
| goto error; |
| } |
| const size_t size = bitmap.getSize(); |
| const size_t palette_size = 256*sizeof(SkPMColor); |
| const size_t imageSize = size + palette_size; |
| void* const data = malloc(imageSize); |
| if (data) { |
| void* const pixels = (char*)data + palette_size; |
| SkColorTable* ctable = bitmap.getColorTable(); |
| memcpy(data, ctable->lockColors(), ctable->count() * sizeof(SkPMColor)); |
| memcpy(pixels, p, size); |
| ctable->unlockColors(false); |
| glCompressedTexImage2D(target, level, internalformat, w, h, border, imageSize, data); |
| free(data); |
| } else { |
| err = -1; |
| } |
| } else { |
| glTexImage2D(target, level, internalformat, w, h, border, internalformat, type, p); |
| } |
| error: |
| bitmap.unlockPixels(); |
| return err; |
| } |
| |
| static jint util_texSubImage2D(JNIEnv *env, jclass clazz, |
| jint target, jint level, jint xoffset, jint yoffset, |
| jobject jbitmap, jint format, jint type) |
| { |
| SkBitmap const * nativeBitmap = |
| (SkBitmap const *)env->GetIntField(jbitmap, nativeBitmapID); |
| const SkBitmap& bitmap(*nativeBitmap); |
| SkBitmap::Config config = bitmap.getConfig(); |
| if (format < 0) { |
| format = getInternalFormat(config); |
| if (format == GL_PALETTE8_RGBA8_OES) |
| return -1; // glCompressedTexSubImage2D() not supported |
| } |
| int err = checkFormat(config, format, type); |
| if (err) |
| return err; |
| bitmap.lockPixels(); |
| const int w = bitmap.width(); |
| const int h = bitmap.height(); |
| const void* p = bitmap.getPixels(); |
| glTexSubImage2D(target, level, xoffset, yoffset, w, h, format, type, p); |
| bitmap.unlockPixels(); |
| return 0; |
| } |
| |
| /* |
| * ETC1 methods. |
| */ |
| |
| static jclass nioAccessClass; |
| static jclass bufferClass; |
| static jmethodID getBasePointerID; |
| static jmethodID getBaseArrayID; |
| static jmethodID getBaseArrayOffsetID; |
| static jfieldID positionID; |
| static jfieldID limitID; |
| static jfieldID elementSizeShiftID; |
| |
| /* Cache method IDs each time the class is loaded. */ |
| |
| static void |
| nativeClassInitBuffer(JNIEnv *_env) |
| { |
| jclass nioAccessClassLocal = _env->FindClass("java/nio/NIOAccess"); |
| nioAccessClass = (jclass) _env->NewGlobalRef(nioAccessClassLocal); |
| |
| jclass bufferClassLocal = _env->FindClass("java/nio/Buffer"); |
| bufferClass = (jclass) _env->NewGlobalRef(bufferClassLocal); |
| |
| getBasePointerID = _env->GetStaticMethodID(nioAccessClass, |
| "getBasePointer", "(Ljava/nio/Buffer;)J"); |
| getBaseArrayID = _env->GetStaticMethodID(nioAccessClass, |
| "getBaseArray", "(Ljava/nio/Buffer;)Ljava/lang/Object;"); |
| getBaseArrayOffsetID = _env->GetStaticMethodID(nioAccessClass, |
| "getBaseArrayOffset", "(Ljava/nio/Buffer;)I"); |
| positionID = _env->GetFieldID(bufferClass, "position", "I"); |
| limitID = _env->GetFieldID(bufferClass, "limit", "I"); |
| elementSizeShiftID = |
| _env->GetFieldID(bufferClass, "_elementSizeShift", "I"); |
| } |
| |
| static void * |
| getPointer(JNIEnv *_env, jobject buffer, jint *remaining) |
| { |
| jint position; |
| jint limit; |
| jint elementSizeShift; |
| jlong pointer; |
| jint offset; |
| void *data; |
| |
| position = _env->GetIntField(buffer, positionID); |
| limit = _env->GetIntField(buffer, limitID); |
| elementSizeShift = _env->GetIntField(buffer, elementSizeShiftID); |
| *remaining = (limit - position) << elementSizeShift; |
| pointer = _env->CallStaticLongMethod(nioAccessClass, |
| getBasePointerID, buffer); |
| if (pointer != 0L) { |
| return (void *) (jint) pointer; |
| } |
| return NULL; |
| } |
| |
| class BufferHelper { |
| public: |
| BufferHelper(JNIEnv *env, jobject buffer) { |
| mEnv = env; |
| mBuffer = buffer; |
| mData = NULL; |
| mRemaining = 0; |
| } |
| |
| bool checkPointer(const char* errorMessage) { |
| if (mBuffer) { |
| mData = getPointer(mEnv, mBuffer, &mRemaining); |
| if (mData == NULL) { |
| doThrowIAE(mEnv, errorMessage); |
| } |
| return mData != NULL; |
| } else { |
| doThrowIAE(mEnv, errorMessage); |
| return false; |
| } |
| } |
| |
| inline void* getData() { |
| return mData; |
| } |
| |
| inline jint remaining() { |
| return mRemaining; |
| } |
| |
| private: |
| JNIEnv* mEnv; |
| jobject mBuffer; |
| void* mData; |
| jint mRemaining; |
| }; |
| |
| /** |
| * Encode a block of pixels. |
| * |
| * @param in a pointer to a ETC1_DECODED_BLOCK_SIZE array of bytes that represent a |
| * 4 x 4 square of 3-byte pixels in form R, G, B. Byte (3 * (x + 4 * y) is the R |
| * value of pixel (x, y). |
| * |
| * @param validPixelMask is a 16-bit mask where bit (1 << (x + y * 4)) indicates whether |
| * the corresponding (x,y) pixel is valid. Invalid pixel color values are ignored when compressing. |
| * |
| * @param out an ETC1 compressed version of the data. |
| * |
| */ |
| static void etc1_encodeBlock(JNIEnv *env, jclass clazz, |
| jobject in, jint validPixelMask, jobject out) { |
| if (validPixelMask < 0 || validPixelMask > 15) { |
| doThrowIAE(env, "validPixelMask"); |
| return; |
| } |
| BufferHelper inB(env, in); |
| BufferHelper outB(env, out); |
| if (inB.checkPointer("in") && outB.checkPointer("out")) { |
| if (inB.remaining() < ETC1_DECODED_BLOCK_SIZE) { |
| doThrowIAE(env, "in's remaining data < DECODED_BLOCK_SIZE"); |
| } else if (outB.remaining() < ETC1_ENCODED_BLOCK_SIZE) { |
| doThrowIAE(env, "out's remaining data < ENCODED_BLOCK_SIZE"); |
| } else { |
| etc1_encode_block((etc1_byte*) inB.getData(), validPixelMask, |
| (etc1_byte*) outB.getData()); |
| } |
| } |
| } |
| |
| /** |
| * Decode a block of pixels. |
| * |
| * @param in an ETC1 compressed version of the data. |
| * |
| * @param out a pointer to a ETC_DECODED_BLOCK_SIZE array of bytes that represent a |
| * 4 x 4 square of 3-byte pixels in form R, G, B. Byte (3 * (x + 4 * y) is the R |
| * value of pixel (x, y). |
| */ |
| static void etc1_decodeBlock(JNIEnv *env, jclass clazz, |
| jobject in, jobject out){ |
| BufferHelper inB(env, in); |
| BufferHelper outB(env, out); |
| if (inB.checkPointer("in") && outB.checkPointer("out")) { |
| if (inB.remaining() < ETC1_ENCODED_BLOCK_SIZE) { |
| doThrowIAE(env, "in's remaining data < ENCODED_BLOCK_SIZE"); |
| } else if (outB.remaining() < ETC1_DECODED_BLOCK_SIZE) { |
| doThrowIAE(env, "out's remaining data < DECODED_BLOCK_SIZE"); |
| } else { |
| etc1_decode_block((etc1_byte*) inB.getData(), |
| (etc1_byte*) outB.getData()); |
| } |
| } |
| } |
| |
| /** |
| * Return the size of the encoded image data (does not include size of PKM header). |
| */ |
| static jint etc1_getEncodedDataSize(JNIEnv *env, jclass clazz, |
| jint width, jint height) { |
| return etc1_get_encoded_data_size(width, height); |
| } |
| |
| /** |
| * Encode an entire image. |
| * @param in pointer to the image data. Formatted such that |
| * pixel (x,y) is at pIn + pixelSize * x + stride * y + redOffset; |
| * @param out pointer to encoded data. Must be large enough to store entire encoded image. |
| */ |
| static void etc1_encodeImage(JNIEnv *env, jclass clazz, |
| jobject in, jint width, jint height, |
| jint pixelSize, jint stride, jobject out) { |
| if (pixelSize < 2 || pixelSize > 3) { |
| doThrowIAE(env, "pixelSize must be 2 or 3"); |
| return; |
| } |
| BufferHelper inB(env, in); |
| BufferHelper outB(env, out); |
| if (inB.checkPointer("in") && outB.checkPointer("out")) { |
| jint imageSize = stride * height; |
| jint encodedImageSize = etc1_get_encoded_data_size(width, height); |
| if (inB.remaining() < imageSize) { |
| doThrowIAE(env, "in's remaining data < image size"); |
| } else if (outB.remaining() < encodedImageSize) { |
| doThrowIAE(env, "out's remaining data < encoded image size"); |
| } else { |
| int result = etc1_encode_image((etc1_byte*) inB.getData(), |
| width, height, pixelSize, |
| stride, |
| (etc1_byte*) outB.getData()); |
| } |
| } |
| } |
| |
| /** |
| * Decode an entire image. |
| * @param in the encoded data. |
| * @param out pointer to the image data. Will be written such that |
| * pixel (x,y) is at pIn + pixelSize * x + stride * y. Must be |
| * large enough to store entire image. |
| */ |
| static void etc1_decodeImage(JNIEnv *env, jclass clazz, |
| jobject in, jobject out, |
| jint width, jint height, |
| jint pixelSize, jint stride) { |
| if (pixelSize < 2 || pixelSize > 3) { |
| doThrowIAE(env, "pixelSize must be 2 or 3"); |
| return; |
| } |
| BufferHelper inB(env, in); |
| BufferHelper outB(env, out); |
| if (inB.checkPointer("in") && outB.checkPointer("out")) { |
| jint imageSize = stride * height; |
| jint encodedImageSize = etc1_get_encoded_data_size(width, height); |
| if (inB.remaining() < encodedImageSize) { |
| doThrowIAE(env, "in's remaining data < encoded image size"); |
| } else if (outB.remaining() < imageSize) { |
| doThrowIAE(env, "out's remaining data < image size"); |
| } else { |
| int result = etc1_decode_image((etc1_byte*) inB.getData(), |
| (etc1_byte*) outB.getData(), |
| width, height, pixelSize, |
| stride); |
| } |
| } |
| } |
| |
| /** |
| * Format a PKM header |
| */ |
| static void etc1_formatHeader(JNIEnv *env, jclass clazz, |
| jobject header, jint width, jint height) { |
| BufferHelper headerB(env, header); |
| if (headerB.checkPointer("header") ){ |
| if (headerB.remaining() < ETC_PKM_HEADER_SIZE) { |
| doThrowIAE(env, "header's remaining data < ETC_PKM_HEADER_SIZE"); |
| } else { |
| etc1_pkm_format_header((etc1_byte*) headerB.getData(), width, height); |
| } |
| } |
| } |
| |
| /** |
| * Check if a PKM header is correctly formatted. |
| */ |
| static jboolean etc1_isValid(JNIEnv *env, jclass clazz, |
| jobject header) { |
| jboolean result = false; |
| BufferHelper headerB(env, header); |
| if (headerB.checkPointer("header") ){ |
| if (headerB.remaining() < ETC_PKM_HEADER_SIZE) { |
| doThrowIAE(env, "header's remaining data < ETC_PKM_HEADER_SIZE"); |
| } else { |
| result = etc1_pkm_is_valid((etc1_byte*) headerB.getData()); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Read the image width from a PKM header |
| */ |
| static jint etc1_getWidth(JNIEnv *env, jclass clazz, |
| jobject header) { |
| jint result = 0; |
| BufferHelper headerB(env, header); |
| if (headerB.checkPointer("header") ){ |
| if (headerB.remaining() < ETC_PKM_HEADER_SIZE) { |
| doThrowIAE(env, "header's remaining data < ETC_PKM_HEADER_SIZE"); |
| } else { |
| result = etc1_pkm_get_width((etc1_byte*) headerB.getData()); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Read the image height from a PKM header |
| */ |
| static int etc1_getHeight(JNIEnv *env, jclass clazz, |
| jobject header) { |
| jint result = 0; |
| BufferHelper headerB(env, header); |
| if (headerB.checkPointer("header") ){ |
| if (headerB.remaining() < ETC_PKM_HEADER_SIZE) { |
| doThrowIAE(env, "header's remaining data < ETC_PKM_HEADER_SIZE"); |
| } else { |
| result = etc1_pkm_get_height((etc1_byte*) headerB.getData()); |
| } |
| } |
| return result; |
| } |
| |
| /* |
| * JNI registration |
| */ |
| |
| static JNINativeMethod gMatrixMethods[] = { |
| { "multiplyMM", "([FI[FI[FI)V", (void*)util_multiplyMM }, |
| { "multiplyMV", "([FI[FI[FI)V", (void*)util_multiplyMV }, |
| }; |
| |
| static JNINativeMethod gVisibilityMethods[] = { |
| { "computeBoundingSphere", "([FII[FI)V", (void*)util_computeBoundingSphere }, |
| { "frustumCullSpheres", "([FI[FII[III)I", (void*)util_frustumCullSpheres }, |
| { "visibilityTest", "([FI[FI[CII)I", (void*)util_visibilityTest }, |
| }; |
| |
| static JNINativeMethod gUtilsMethods[] = { |
| {"nativeClassInit", "()V", (void*)nativeUtilsClassInit }, |
| { "native_getInternalFormat", "(Landroid/graphics/Bitmap;)I", (void*) util_getInternalFormat }, |
| { "native_getType", "(Landroid/graphics/Bitmap;)I", (void*) util_getType }, |
| { "native_texImage2D", "(IIILandroid/graphics/Bitmap;II)I", (void*)util_texImage2D }, |
| { "native_texSubImage2D", "(IIIILandroid/graphics/Bitmap;II)I", (void*)util_texSubImage2D }, |
| }; |
| |
| static JNINativeMethod gEtc1Methods[] = { |
| { "encodeBlock", "(Ljava/nio/Buffer;ILjava/nio/Buffer;)V", (void*) etc1_encodeBlock }, |
| { "decodeBlock", "(Ljava/nio/Buffer;Ljava/nio/Buffer;)V", (void*) etc1_decodeBlock }, |
| { "getEncodedDataSize", "(II)I", (void*) etc1_getEncodedDataSize }, |
| { "encodeImage", "(Ljava/nio/Buffer;IIIILjava/nio/Buffer;)V", (void*) etc1_encodeImage }, |
| { "decodeImage", "(Ljava/nio/Buffer;Ljava/nio/Buffer;IIII)V", (void*) etc1_decodeImage }, |
| { "formatHeader", "(Ljava/nio/Buffer;II)V", (void*) etc1_formatHeader }, |
| { "isValid", "(Ljava/nio/Buffer;)Z", (void*) etc1_isValid }, |
| { "getWidth", "(Ljava/nio/Buffer;)I", (void*) etc1_getWidth }, |
| { "getHeight", "(Ljava/nio/Buffer;)I", (void*) etc1_getHeight }, |
| }; |
| |
| typedef struct _ClassRegistrationInfo { |
| const char* classPath; |
| JNINativeMethod* methods; |
| size_t methodCount; |
| } ClassRegistrationInfo; |
| |
| static ClassRegistrationInfo gClasses[] = { |
| {"android/opengl/Matrix", gMatrixMethods, NELEM(gMatrixMethods)}, |
| {"android/opengl/Visibility", gVisibilityMethods, NELEM(gVisibilityMethods)}, |
| {"android/opengl/GLUtils", gUtilsMethods, NELEM(gUtilsMethods)}, |
| {"android/opengl/ETC1", gEtc1Methods, NELEM(gEtc1Methods)}, |
| }; |
| |
| int register_android_opengl_classes(JNIEnv* env) |
| { |
| nativeClassInitBuffer(env); |
| int result = 0; |
| for (int i = 0; i < NELEM(gClasses); i++) { |
| ClassRegistrationInfo* cri = &gClasses[i]; |
| result = AndroidRuntime::registerNativeMethods(env, |
| cri->classPath, cri->methods, cri->methodCount); |
| if (result < 0) { |
| ALOGE("Failed to register %s: %d", cri->classPath, result); |
| break; |
| } |
| } |
| return result; |
| } |
| |
| } // namespace android |