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