/*
100% native, glueless Android app.
Compile with -std=c11 -lm -landroid -lEGL -lGLESv3
AndroidManifest.xml:
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define APP_CMD_INPUT_QUEUE_DESTROYED 1
#define APP_CMD_WINDOW_DESTROYED 2
#define APP_CMD_WINDOW_CREATED 3
#define APP_CMD_CONFIG_CHANGED 4
#define APP_CMD_LOW_MEMORY 5
#define APP_CMD_WINDOW_REDRAW_NEEDED 6
#define APP_CMD_WINDOW_RESIZED 7
#define APP_CMD_PAUSE 8
#define APP_CMD_RESUME 9
#define APP_CMD_START 10
#define APP_CMD_STOP 11
#define APP_CMD_DESTROY 12
#define APP_CMD_GAINED_FOCUS 13
#define APP_CMD_LOST_FOCUS 14
#define APP_CMD_CONTENT_RECT_CHANGED 15
struct {
int cmd_queue[0x200];
int cmd_read_travel;
int cmd_write_travel;
ANativeActivity *activity;
ANativeWindow *window;
AInputQueue *input_queue;
pthread_t thread;
ARect pending_rect;
bool running;
struct {
EGLDisplay display;
EGLContext context;
EGLSurface surface;
int width;
int height;
} egl;
struct {
float x;
float y;
} input;
struct {
float back_color[3];
} state;
} static app_global = {0};
int init_egl()
{
const EGLint attribs[] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_BLUE_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_RED_SIZE, 8,
EGL_NONE
};
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(display, 0, 0);
EGLint n_configs = 0;
eglChooseConfig(display, attribs, NULL, 0, &n_configs);
EGLConfig cfg = NULL;
EGLConfig *configs = (EGLConfig*)alloca(n_configs * sizeof(EGLConfig));
eglChooseConfig(display, attribs, configs, n_configs, &n_configs);
for (int i = 0; i < n_configs; i++) {
EGLint r, g, b, d;
if (eglGetConfigAttrib(display, configs[i], EGL_RED_SIZE, &r) &&
eglGetConfigAttrib(display, configs[i], EGL_GREEN_SIZE, &g) &&
eglGetConfigAttrib(display, configs[i], EGL_BLUE_SIZE, &b) &&
eglGetConfigAttrib(display, configs[i], EGL_DEPTH_SIZE, &d) &&
r == 8 && g == 8 && b == 8 && d == 0
) {
cfg = configs[i];
break;
}
}
if (!cfg)
cfg = configs[0];
if (!cfg)
return -1;
EGLint format;
eglGetConfigAttrib(display, cfg, EGL_NATIVE_VISUAL_ID, &format);
EGLSurface surface = eglCreateWindowSurface(display, cfg, app_global.window, NULL);
EGLContext context = eglCreateContext(display, cfg, NULL, NULL);
if (!eglMakeCurrent(display, surface, surface, context))
return -2;
EGLint w, h;
eglQuerySurface(display, surface, EGL_WIDTH, &w);
eglQuerySurface(display, surface, EGL_HEIGHT, &h);
app_global.egl.display = display;
app_global.egl.context = context;
app_global.egl.surface = surface;
app_global.egl.width = w;
app_global.egl.height = h;
//glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
glEnable(GL_CULL_FACE);
//glShadeModel(GL_SMOOTH);
glDisable(GL_DEPTH_TEST);
return 0;
}
void quit_egl()
{
if (app_global.egl.display) {
eglMakeCurrent(app_global.egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (app_global.egl.context)
eglDestroyContext(app_global.egl.display, app_global.egl.context);
if (app_global.egl.surface)
eglDestroySurface(app_global.egl.display, app_global.egl.surface);
eglTerminate(app_global.egl.display);
}
app_global.egl.display = NULL;
app_global.egl.context = NULL;
app_global.egl.surface = NULL;
}
void handle_app_command(int cmd)
{
switch (cmd) {
case APP_CMD_INPUT_QUEUE_DESTROYED:
{
app_global.input_queue = NULL;
break;
}
case APP_CMD_WINDOW_DESTROYED:
{
app_global.window = NULL;
quit_egl();
break;
}
case APP_CMD_WINDOW_CREATED:
{
init_egl();
break;
}
case APP_CMD_WINDOW_REDRAW_NEEDED:
{
break;
}
case APP_CMD_WINDOW_RESIZED:
{
break;
}
case APP_CMD_PAUSE:
{
break;
}
case APP_CMD_RESUME:
{
break;
}
case APP_CMD_START:
{
break;
}
case APP_CMD_STOP:
{
break;
}
case APP_CMD_DESTROY:
{
quit_egl();
app_global.running = false;
break;
}
case APP_CMD_GAINED_FOCUS:
{
break;
}
case APP_CMD_LOST_FOCUS:
{
break;
}
case APP_CMD_CONTENT_RECT_CHANGED:
{
ARect rect = app_global.pending_rect;
break;
}
}
}
bool handle_motion_input(AInputEvent *event)
{
app_global.input.x = AMotionEvent_getX(event, 0);
app_global.input.y = AMotionEvent_getY(event, 0);
return true;
}
bool handle_key_input(AInputEvent *event)
{
return false;
}
void app_tick()
{
float half_w = (float)(app_global.egl.width / 2);
float half_h = (float)(app_global.egl.height / 2);
float x = app_global.input.x - half_w;
float y = app_global.input.y - half_h;
float angle = 0;
if ((y <= -0.001 || y > 0.001) && (x <= -0.001 || x > 0.001))
angle = atan2(y, x);
float radius = sqrt(x*x + y*y) / 256.0;
float *rgb = app_global.state.back_color;
for (int i = 0; i < 3; i++) {
rgb[i] = cos(angle + ((float)i * M_PI * 2.0/3.0)) + 0.5;
if (rgb[i] >= 1.0) rgb[i] = 1.0;
if (rgb[i] < 0) rgb[i] = 0;
if (radius < 1.0) rgb[i] *= radius;
}
}
void *app_main(void *data)
{
app_global.running = true;
while (true) {
int cmd = app_global.cmd_queue[app_global.cmd_read_travel & 0x1ff];
if (cmd) {
app_global.cmd_queue[app_global.cmd_read_travel & 0x1ff] = 0;
app_global.cmd_read_travel++;
handle_app_command(cmd);
}
AInputEvent *event = NULL;
while (app_global.input_queue && AInputQueue_getEvent(app_global.input_queue, &event) >= 0) {
if (AInputQueue_preDispatchEvent(app_global.input_queue, event))
continue;
bool handled = false;
int type = AInputEvent_getType(event);
if (type == AINPUT_EVENT_TYPE_MOTION)
handled = handle_motion_input(event);
else if (type == AINPUT_EVENT_TYPE_KEY)
handled = handle_key_input(event);
AInputQueue_finishEvent(app_global.input_queue, event, handled);
}
if (!app_global.running)
break;
app_tick();
float *rgb = app_global.state.back_color;
glClearColor(rgb[0], rgb[1], rgb[2], 1);
glClear(GL_COLOR_BUFFER_BIT);
eglSwapBuffers(app_global.egl.display, app_global.egl.surface);
}
return NULL;
}
void push_app_command(int cmd)
{
app_global.cmd_queue[app_global.cmd_write_travel++ & 0x1ff] = cmd;
}
static void onInputQueueCreated(ANativeActivity *activity, AInputQueue *queue) {
app_global.input_queue = queue;
}
static void onInputQueueDestroyed(ANativeActivity *activity, AInputQueue *queue) {
//app_global.input_queue = NULL;
push_app_command(APP_CMD_INPUT_QUEUE_DESTROYED);
}
static void onNativeWindowCreated(ANativeActivity *activity, ANativeWindow *window) {
app_global.window = window;
push_app_command(APP_CMD_WINDOW_CREATED);
}
static void onNativeWindowDestroyed(ANativeActivity *activity, ANativeWindow *window) {
//app_global.window = NULL;
push_app_command(APP_CMD_WINDOW_DESTROYED);
}
static void onConfigurationChanged(ANativeActivity *activity) {
push_app_command(APP_CMD_CONFIG_CHANGED);
}
static void onLowMemory(ANativeActivity *activity) {
push_app_command(APP_CMD_LOW_MEMORY);
}
static void onNativeWindowRedrawNeeded(ANativeActivity *activity, ANativeWindow *window) {
push_app_command(APP_CMD_WINDOW_REDRAW_NEEDED);
}
static void onNativeWindowResized(ANativeActivity *activity, ANativeWindow *window) {
push_app_command(APP_CMD_WINDOW_RESIZED);
}
static void onPause(ANativeActivity *activity) {
push_app_command(APP_CMD_PAUSE);
}
static void onResume(ANativeActivity *activity) {
push_app_command(APP_CMD_RESUME);
}
static void onStart(ANativeActivity *activity) {
push_app_command(APP_CMD_START);
}
static void onStop(ANativeActivity *activity) {
push_app_command(APP_CMD_STOP);
}
static void onDestroy(ANativeActivity *activity) {
push_app_command(APP_CMD_DESTROY);
}
static void onWindowFocusChanged(ANativeActivity *activity, int hasFocus) {
push_app_command(hasFocus ? APP_CMD_GAINED_FOCUS : APP_CMD_LOST_FOCUS);
}
static void onContentRectChanged(ANativeActivity *activity, const ARect *rect) {
app_global.pending_rect = *rect;
push_app_command(APP_CMD_CONTENT_RECT_CHANGED);
}
static void *onSaveInstanceState(ANativeActivity *activity, size_t *outSize) {
*outSize = sizeof(app_global.state);
return &app_global.state;
}
JNIEXPORT void ANativeActivity_onCreate(ANativeActivity *activity, void *savedState, size_t savedStateSize)
{
struct ANativeActivityCallbacks *cb = activity->callbacks;
cb->onConfigurationChanged = onConfigurationChanged;
cb->onContentRectChanged = onContentRectChanged;
cb->onDestroy = onDestroy;
cb->onInputQueueCreated = onInputQueueCreated;
cb->onInputQueueDestroyed = onInputQueueDestroyed;
cb->onLowMemory = onLowMemory;
cb->onNativeWindowCreated = onNativeWindowCreated;
cb->onNativeWindowDestroyed = onNativeWindowDestroyed;
cb->onNativeWindowRedrawNeeded = onNativeWindowRedrawNeeded;
cb->onNativeWindowResized = onNativeWindowResized;
cb->onPause = onPause;
cb->onResume = onResume;
cb->onSaveInstanceState = onSaveInstanceState;
cb->onStart = onStart;
cb->onStop = onStop;
cb->onWindowFocusChanged = onWindowFocusChanged;
app_global.activity = activity;
activity->instance = &app_global;
if (savedState && savedStateSize == sizeof(app_global.state))
memcpy(&app_global.state, savedState, savedStateSize);
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&app_global.thread, &attr, app_main, NULL);
while (!app_global.running);
}