/*
* Copyright 2013 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 <stdlib.h>
#include <time.h>
#include "gles3jni.h"
const Vertex QUAD[4] = {
// Square with diagonal < 2 so that it fits in a [-1 .. 1]^2 square
// regardless of rotation.
{{-0.7f, -0.7f}, {0x00, 0xFF, 0x00}},
{{ 0.7f, -0.7f}, {0x00, 0x00, 0xFF}},
{{-0.7f, 0.7f}, {0xFF, 0x00, 0x00}},
{{ 0.7f, 0.7f}, {0xFF, 0xFF, 0xFF}},
};
bool checkGlError(const char* funcName) {
GLint err = glGetError();
if (err != GL_NO_ERROR) {
ALOGE("GL error after %s(): 0x%08x\n", funcName, err);
return true;
}
return false;
}
GLuint createShader(GLenum shaderType, const char* src) {
GLuint shader = glCreateShader(shaderType);
if (!shader) {
checkGlError("glCreateShader");
return 0;
}
glShaderSource(shader, 1, &src, NULL);
GLint compiled = GL_FALSE;
glCompileShader(shader);
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
if (!compiled) {
GLint infoLogLen = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLen);
if (infoLogLen > 0) {
GLchar* infoLog = (GLchar*)malloc(infoLogLen);
if (infoLog) {
glGetShaderInfoLog(shader, infoLogLen, NULL, infoLog);
ALOGE("Could not compile %s shader:\n%s\n",
shaderType == GL_VERTEX_SHADER ? "vertex" : "fragment",
infoLog);
free(infoLog);
}
}
glDeleteShader(shader);
return 0;
}
return shader;
}
GLuint createProgram(const char* vtxSrc, const char* fragSrc) {
GLuint vtxShader = 0;
GLuint fragShader = 0;
GLuint program = 0;
GLint linked = GL_FALSE;
vtxShader = createShader(GL_VERTEX_SHADER, vtxSrc);
if (!vtxShader)
goto exit;
fragShader = createShader(GL_FRAGMENT_SHADER, fragSrc);
if (!fragShader)
goto exit;
program = glCreateProgram();
if (!program) {
checkGlError("glCreateProgram");
goto exit;
}
glAttachShader(program, vtxShader);
glAttachShader(program, fragShader);
glLinkProgram(program);
glGetProgramiv(program, GL_LINK_STATUS, &linked);
if (!linked) {
ALOGE("Could not link program");
GLint infoLogLen = 0;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLen);
if (infoLogLen) {
GLchar* infoLog = (GLchar*)malloc(infoLogLen);
if (infoLog) {
glGetProgramInfoLog(program, infoLogLen, NULL, infoLog);
ALOGE("Could not link program:\n%s\n", infoLog);
free(infoLog);
}
}
glDeleteProgram(program);
program = 0;
}
exit:
glDeleteShader(vtxShader);
glDeleteShader(fragShader);
return program;
}
static void printGlString(const char* name, GLenum s) {
const char* v = (const char*)glGetString(s);
ALOGV("GL %s: %s\n", name, v);
}
// ----------------------------------------------------------------------------
Renderer::Renderer()
: mNumInstances(0),
mLastFrameNs(0)
{
memset(mScale, 0, sizeof(mScale));
memset(mAngularVelocity, 0, sizeof(mAngularVelocity));
memset(mAngles, 0, sizeof(mAngles));
}
Renderer::~Renderer() {
}
void Renderer::resize(int w, int h) {
float* offsets = mapOffsetBuf();
calcSceneParams(w, h, offsets);
unmapOffsetBuf();
for (unsigned int i = 0; i < mNumInstances; i++) {
mAngles[i] = drand48() * TWO_PI;
mAngularVelocity[i] = MAX_ROT_SPEED * (2.0*drand48() - 1.0);
}
mLastFrameNs = 0;
glViewport(0, 0, w, h);
}
void Renderer::calcSceneParams(unsigned int w, unsigned int h,
float* offsets) {
// number of cells along the larger screen dimension
const float NCELLS_MAJOR = MAX_INSTANCES_PER_SIDE;
// cell size in scene space
const float CELL_SIZE = 2.0f / NCELLS_MAJOR;
// Calculations are done in "landscape", i.e. assuming dim[0] >= dim[1].
// Only at the end are values put in the opposite order if h > w.
const float dim[2] = {fmaxf(w,h), fminf(w,h)};
const float aspect[2] = {dim[0] / dim[1], dim[1] / dim[0]};
const float scene2clip[2] = {1.0f, aspect[0]};
const int ncells[2] = {
NCELLS_MAJOR,
(int)floorf(NCELLS_MAJOR * aspect[1])
};
float centers[2][MAX_INSTANCES_PER_SIDE];
for (int d = 0; d < 2; d++) {
float offset = -ncells[d] / NCELLS_MAJOR; // -1.0 for d=0
for (int i = 0; i < ncells[d]; i++) {
centers[d][i] = scene2clip[d] * (CELL_SIZE*(i + 0.5f) + offset);
}
}
int major = w >= h ? 0 : 1;
int minor = w >= h ? 1 : 0;
// outer product of centers[0] and centers[1]
for (int i = 0; i < ncells[0]; i++) {
for (int j = 0; j < ncells[1]; j++) {
int idx = i*ncells[1] + j;
offsets[2*idx + major] = centers[0][i];
offsets[2*idx + minor] = centers[1][j];
}
}
mNumInstances = ncells[0] * ncells[1];
mScale[major] = 0.5f * CELL_SIZE * scene2clip[0];
mScale[minor] = 0.5f * CELL_SIZE * scene2clip[1];
}
void Renderer::step() {
timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
uint64_t nowNs = now.tv_sec*1000000000ull + now.tv_nsec;
if (mLastFrameNs > 0) {
float dt = float(nowNs - mLastFrameNs) * 0.000000001f;
for (unsigned int i = 0; i < mNumInstances; i++) {
mAngles[i] += mAngularVelocity[i] * dt;
if (mAngles[i] >= TWO_PI) {
mAngles[i] -= TWO_PI;
} else if (mAngles[i] <= -TWO_PI) {
mAngles[i] += TWO_PI;
}
}
float* transforms = mapTransformBuf();
for (unsigned int i = 0; i < mNumInstances; i++) {
float s = sinf(mAngles[i]);
float c = cosf(mAngles[i]);
transforms[4*i + 0] = c * mScale[0];
transforms[4*i + 1] = s * mScale[1];
transforms[4*i + 2] = -s * mScale[0];
transforms[4*i + 3] = c * mScale[1];
}
unmapTransformBuf();
}
mLastFrameNs = nowNs;
}
void Renderer::render() {
step();
glClearColor(0.2f, 0.2f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
draw(mNumInstances);
checkGlError("Renderer::render");
}
// ----------------------------------------------------------------------------
static Renderer* g_renderer = NULL;
extern "C" {
JNIEXPORT void JNICALL Java_com_android_gles3jni_GLES3JNILib_init(JNIEnv* env, jobject obj);
JNIEXPORT void JNICALL Java_com_android_gles3jni_GLES3JNILib_resize(JNIEnv* env, jobject obj, jint width, jint height);
JNIEXPORT void JNICALL Java_com_android_gles3jni_GLES3JNILib_step(JNIEnv* env, jobject obj);
};
#if !defined(DYNAMIC_ES3)
static GLboolean gl3stubInit() {
return GL_TRUE;
}
#endif
JNIEXPORT void JNICALL
Java_com_android_gles3jni_GLES3JNILib_init(JNIEnv* env, jobject obj) {
if (g_renderer) {
delete g_renderer;
g_renderer = NULL;
}
printGlString("Version", GL_VERSION);
printGlString("Vendor", GL_VENDOR);
printGlString("Renderer", GL_RENDERER);
printGlString("Extensions", GL_EXTENSIONS);
const char* versionStr = (const char*)glGetString(GL_VERSION);
if (strstr(versionStr, "OpenGL ES 3.") && gl3stubInit()) {
g_renderer = createES3Renderer();
} else if (strstr(versionStr, "OpenGL ES 2.")) {
g_renderer = createES2Renderer();
} else {
ALOGE("Unsupported OpenGL ES version");
}
}
JNIEXPORT void JNICALL
Java_com_android_gles3jni_GLES3JNILib_resize(JNIEnv* env, jobject obj, jint width, jint height) {
if (g_renderer) {
g_renderer->resize(width, height);
}
}
JNIEXPORT void JNICALL
Java_com_android_gles3jni_GLES3JNILib_step(JNIEnv* env, jobject obj) {
if (g_renderer) {
g_renderer->render();
}
}