1084 lines
32 KiB
C++
1084 lines
32 KiB
C++
|
/**
|
||
|
** 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 <nativehelper/jni.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 jclass gIAEClass;
|
||
|
static jclass gUOEClass;
|
||
|
static jclass gAIOOBEClass;
|
||
|
|
||
|
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) {
|
||
|
LOGI("%s: %d verts", label, pPoly->n);
|
||
|
for(int i = 0; i < pPoly->n; i++) {
|
||
|
Poly_vert* pV = & pPoly->vert[i];
|
||
|
LOGI("[%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;
|
||
|
}
|
||
|
|
||
|
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) {
|
||
|
mEnv->ThrowNew(gIAEClass, "array == null");
|
||
|
return false;
|
||
|
}
|
||
|
if ( mOffset < 0) {
|
||
|
mEnv->ThrowNew(gIAEClass, "offset < 0");
|
||
|
return false;
|
||
|
}
|
||
|
mLength = mEnv->GetArrayLength(mRef) - mOffset;
|
||
|
if (mLength < mMinSize ) {
|
||
|
mEnv->ThrowNew(gIAEClass, "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 ) {
|
||
|
env->ThrowNew(gIAEClass, "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) {
|
||
|
env->ThrowNew(gIAEClass, "length < offset + indexCount");
|
||
|
// Return value will be ignored, because an exception has been thrown.
|
||
|
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) {
|
||
|
mEnv->ThrowNew(gIAEClass, errorMessage);
|
||
|
}
|
||
|
return mData != NULL;
|
||
|
} else {
|
||
|
mEnv->ThrowNew(gIAEClass, 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) {
|
||
|
env->ThrowNew(gIAEClass, "validPixelMask");
|
||
|
return;
|
||
|
}
|
||
|
BufferHelper inB(env, in);
|
||
|
BufferHelper outB(env, out);
|
||
|
if (inB.checkPointer("in") && outB.checkPointer("out")) {
|
||
|
if (inB.remaining() < ETC1_DECODED_BLOCK_SIZE) {
|
||
|
env->ThrowNew(gIAEClass, "in's remaining data < DECODED_BLOCK_SIZE");
|
||
|
} else if (outB.remaining() < ETC1_ENCODED_BLOCK_SIZE) {
|
||
|
env->ThrowNew(gIAEClass, "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) {
|
||
|
env->ThrowNew(gIAEClass, "in's remaining data < ENCODED_BLOCK_SIZE");
|
||
|
} else if (outB.remaining() < ETC1_DECODED_BLOCK_SIZE) {
|
||
|
env->ThrowNew(gIAEClass, "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) {
|
||
|
env->ThrowNew(gIAEClass, "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) {
|
||
|
env->ThrowNew(gIAEClass, "in's remaining data < image size");
|
||
|
} else if (outB.remaining() < encodedImageSize) {
|
||
|
env->ThrowNew(gIAEClass, "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) {
|
||
|
env->ThrowNew(gIAEClass, "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) {
|
||
|
env->ThrowNew(gIAEClass, "in's remaining data < encoded image size");
|
||
|
} else if (outB.remaining() < imageSize) {
|
||
|
env->ThrowNew(gIAEClass, "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) {
|
||
|
env->ThrowNew(gIAEClass, "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) {
|
||
|
env->ThrowNew(gIAEClass, "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) {
|
||
|
env->ThrowNew(gIAEClass, "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) {
|
||
|
env->ThrowNew(gIAEClass, "header's remaining data < ETC_PKM_HEADER_SIZE");
|
||
|
} else {
|
||
|
result = etc1_pkm_get_height((etc1_byte*) headerB.getData());
|
||
|
}
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* JNI registration
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
lookupClasses(JNIEnv* env) {
|
||
|
gIAEClass = (jclass) env->NewGlobalRef(
|
||
|
env->FindClass("java/lang/IllegalArgumentException"));
|
||
|
gUOEClass = (jclass) env->NewGlobalRef(
|
||
|
env->FindClass("java/lang/UnsupportedOperationException"));
|
||
|
gAIOOBEClass = (jclass) env->NewGlobalRef(
|
||
|
env->FindClass("java/lang/ArrayIndexOutOfBoundsException"));
|
||
|
}
|
||
|
|
||
|
static JNINativeMethod gMatrixMethods[] = {
|
||
|
{ "multiplyMM", "([FI[FI[FI)V", (void*)util_multiplyMM },
|
||
|
{ "multiplyMV", "([FI[FI[FI)V", (void*)util_multiplyMV },
|
||
|
};
|
||
|
|
||
|
static JNINativeMethod gVisiblityMethods[] = {
|
||
|
{ "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", gVisiblityMethods, NELEM(gVisiblityMethods)},
|
||
|
{"android/opengl/GLUtils", gUtilsMethods, NELEM(gUtilsMethods)},
|
||
|
{"android/opengl/ETC1", gEtc1Methods, NELEM(gEtc1Methods)},
|
||
|
};
|
||
|
|
||
|
int register_android_opengl_classes(JNIEnv* env)
|
||
|
{
|
||
|
lookupClasses(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) {
|
||
|
LOGE("Failed to register %s: %d", cri->classPath, result);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
} // namespace android
|
||
|
|