/** ** 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 #include "GraphicsJNI.h" #include #include #include #include #include #include #include #include #include #include #include #include "core_jni_helpers.h" #undef LOG_TAG #define LOG_TAG "OpenGLUtil" #include #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; } #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) { int result = POLY_CLIP_OUT; 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; } int transformedIndexCount = maxIndex - minIndex + 1; std::unique_ptr holder{new float[transformedIndexCount * 4]}; float* pTransformed = holder.get(); 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; } class ByteArrayGetter { public: static void* Get(JNIEnv* _env, jbyteArray array, jboolean* is_copy) { return _env->GetByteArrayElements(array, is_copy); } }; class BooleanArrayGetter { public: static void* Get(JNIEnv* _env, jbooleanArray array, jboolean* is_copy) { return _env->GetBooleanArrayElements(array, is_copy); } }; class CharArrayGetter { public: static void* Get(JNIEnv* _env, jcharArray array, jboolean* is_copy) { return _env->GetCharArrayElements(array, is_copy); } }; class ShortArrayGetter { public: static void* Get(JNIEnv* _env, jshortArray array, jboolean* is_copy) { return _env->GetShortArrayElements(array, is_copy); } }; class IntArrayGetter { public: static void* Get(JNIEnv* _env, jintArray array, jboolean* is_copy) { return _env->GetIntArrayElements(array, is_copy); } }; class LongArrayGetter { public: static void* Get(JNIEnv* _env, jlongArray array, jboolean* is_copy) { return _env->GetLongArrayElements(array, is_copy); } }; class FloatArrayGetter { public: static void* Get(JNIEnv* _env, jfloatArray array, jboolean* is_copy) { return _env->GetFloatArrayElements(array, is_copy); } }; class DoubleArrayGetter { public: static void* Get(JNIEnv* _env, jdoubleArray array, jboolean* is_copy) { return _env->GetDoubleArrayElements(array, is_copy); } }; class ByteArrayReleaser { public: static void Release(JNIEnv* _env, jbyteArray array, jbyte* data, jint mode) { _env->ReleaseByteArrayElements(array, data, mode); } }; class BooleanArrayReleaser { public: static void Release(JNIEnv* _env, jbooleanArray array, jboolean* data, jint mode) { _env->ReleaseBooleanArrayElements(array, data, mode); } }; class CharArrayReleaser { public: static void Release(JNIEnv* _env, jcharArray array, jchar* data, jint mode) { _env->ReleaseCharArrayElements(array, data, mode); } }; class ShortArrayReleaser { public: static void Release(JNIEnv* _env, jshortArray array, jshort* data, jint mode) { _env->ReleaseShortArrayElements(array, data, mode); } }; class IntArrayReleaser { public: static void Release(JNIEnv* _env, jintArray array, jint* data, jint mode) { _env->ReleaseIntArrayElements(array, data, mode); } }; class LongArrayReleaser { public: static void Release(JNIEnv* _env, jlongArray array, jlong* data, jint mode) { _env->ReleaseLongArrayElements(array, data, mode); } }; class FloatArrayReleaser { public: static void Release(JNIEnv* _env, jfloatArray array, jfloat* data, jint mode) { _env->ReleaseFloatArrayElements(array, data, mode); } }; class DoubleArrayReleaser { public: static void Release(JNIEnv* _env, jdoubleArray array, jdouble* data, jint mode) { _env->ReleaseDoubleArrayElements(array, data, mode); } }; template 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) { ArrayReleaser::Release(mEnv, 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*) ArrayGetter::Get(mEnv, 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 FloatArrayHelper; typedef ArrayHelper UnsignedShortArrayHelper; typedef ArrayHelper IntArrayHelper; typedef ArrayHelper 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 jint 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 jint 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++) { const float rhs_i0 = rhs[ I(i,0) ]; float ri0 = lhs[ I(0,0) ] * rhs_i0; float ri1 = lhs[ I(0,1) ] * rhs_i0; float ri2 = lhs[ I(0,2) ] * rhs_i0; float ri3 = lhs[ I(0,3) ] * rhs_i0; for (int j=1 ; j<4 ; j++) { 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(); } // --------------------------------------------------------------------------- // The internal format is no longer the same as pixel format, per Table 2 in // https://www.khronos.org/registry/OpenGL-Refpages/es3.1/html/glTexImage2D.xhtml static int checkInternalFormat(SkColorType colorType, int internalformat, int type) { switch(colorType) { case kN32_SkColorType: return (type == GL_UNSIGNED_BYTE && internalformat == GL_RGBA) || (type == GL_UNSIGNED_BYTE && internalformat == GL_SRGB8_ALPHA8) ? 0 : -1; case kAlpha_8_SkColorType: return (type == GL_UNSIGNED_BYTE && internalformat == GL_ALPHA) ? 0 : -1; case kARGB_4444_SkColorType: return (type == GL_UNSIGNED_SHORT_4_4_4_4 && internalformat == GL_RGBA) ? 0 : -1; case kRGB_565_SkColorType: return (type == GL_UNSIGNED_SHORT_5_6_5 && internalformat == GL_RGB) ? 0 : -1; case kRGBA_F16_SkColorType: return (type == GL_HALF_FLOAT && internalformat == GL_RGBA16F) ? 0 : -1; default: break; } return -1; } // The internal format is no longer the same as pixel format, per Table 2 in // https://www.khronos.org/registry/OpenGL-Refpages/es3.1/html/glTexImage2D.xhtml static int getPixelFormatFromInternalFormat(uint32_t internalFormat) { switch (internalFormat) { // For sized internal format. case GL_RGBA16F: case GL_SRGB8_ALPHA8: return GL_RGBA; // Base internal formats and pixel formats are still the same, see Table 1 in // https://www.khronos.org/registry/OpenGL-Refpages/es3.1/html/glTexImage2D.xhtml default: return internalFormat; } } static int getInternalFormat(SkColorType colorType) { switch(colorType) { case kAlpha_8_SkColorType: return GL_ALPHA; case kARGB_4444_SkColorType: return GL_RGBA; case kN32_SkColorType: return GL_RGBA; case kRGB_565_SkColorType: return GL_RGB; case kRGBA_F16_SkColorType: return GL_RGBA16F; default: return -1; } } static int getType(SkColorType colorType) { switch(colorType) { case kAlpha_8_SkColorType: return GL_UNSIGNED_BYTE; case kARGB_4444_SkColorType: return GL_UNSIGNED_SHORT_4_4_4_4; case kN32_SkColorType: return GL_UNSIGNED_BYTE; case kRGB_565_SkColorType: return GL_UNSIGNED_SHORT_5_6_5; case kRGBA_F16_SkColorType: return GL_HALF_FLOAT; default: return -1; } } static jint util_getInternalFormat(JNIEnv *env, jclass clazz, jlong bitmapPtr) { SkBitmap nativeBitmap; bitmap::toBitmap(bitmapPtr).getSkBitmap(&nativeBitmap); return getInternalFormat(nativeBitmap.colorType()); } static jint util_getType(JNIEnv *env, jclass clazz, jlong bitmapPtr) { SkBitmap nativeBitmap; bitmap::toBitmap(bitmapPtr).getSkBitmap(&nativeBitmap); return getType(nativeBitmap.colorType()); } static jint util_texImage2D(JNIEnv *env, jclass clazz, jint target, jint level, jint internalformat, jlong bitmapPtr, jint type, jint border) { SkBitmap bitmap; bitmap::toBitmap(bitmapPtr).getSkBitmap(&bitmap); SkColorType colorType = bitmap.colorType(); if (internalformat < 0) { internalformat = getInternalFormat(colorType); } if (type < 0) { type = getType(colorType); } int err = checkInternalFormat(colorType, internalformat, type); if (err) return err; const int w = bitmap.width(); const int h = bitmap.height(); const void* p = bitmap.getPixels(); if (internalformat == GL_PALETTE8_RGBA8_OES) { err = -1; } else { glTexImage2D(target, level, internalformat, w, h, border, getPixelFormatFromInternalFormat(internalformat), type, p); } return err; } static jint util_texSubImage2D(JNIEnv *env, jclass clazz, jint target, jint level, jint xoffset, jint yoffset, jlong bitmapPtr, jint format, jint type) { SkBitmap bitmap; bitmap::toBitmap(bitmapPtr).getSkBitmap(&bitmap); SkColorType colorType = bitmap.colorType(); int internalFormat = getInternalFormat(colorType); if (format < 0) { format = getPixelFormatFromInternalFormat(internalFormat); if (format == GL_PALETTE8_RGBA8_OES) return -1; // glCompressedTexSubImage2D() not supported } int err = checkInternalFormat(colorType, internalFormat, type); if (err) return err; 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); return 0; } /* * ETC1 methods. */ static void * getPointer(JNIEnv *_env, jobject buffer, jint *remaining) { jint position; jint limit; jint elementSizeShift; jlong pointer = jniGetNioBufferFields(_env, buffer, &position, &limit, &elementSizeShift); if (pointer != 0L) { pointer += position << elementSizeShift; } *remaining = (limit - position) << elementSizeShift; return reinterpret_cast(pointer); } 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 { 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 { 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 ? JNI_TRUE : JNI_FALSE; } /** * 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 jint 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 const JNINativeMethod gMatrixMethods[] = { { "multiplyMM", "([FI[FI[FI)V", (void*)util_multiplyMM }, { "multiplyMV", "([FI[FI[FI)V", (void*)util_multiplyMV }, }; static const 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 const JNINativeMethod gUtilsMethods[] = { { "native_getInternalFormat", "(J)I", (void*) util_getInternalFormat }, { "native_getType", "(J)I", (void*) util_getType }, { "native_texImage2D", "(IIIJII)I", (void*)util_texImage2D }, { "native_texSubImage2D", "(IIIIJII)I", (void*)util_texSubImage2D }, }; static const 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; const JNINativeMethod* methods; size_t methodCount; } ClassRegistrationInfo; static const 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) { int result = 0; for (int i = 0; i < NELEM(gClasses); i++) { const ClassRegistrationInfo* cri = &gClasses[i]; result = RegisterMethodsOrDie(env, cri->classPath, cri->methods, cri->methodCount); } return result; } } // namespace android