Files
panopainter/android/src/main/cpp/main.cpp

653 lines
22 KiB
C++
Executable File

/*
* Copyright (C) 2010 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.
*
*/
//BEGIN_INCLUDE(all)
#include <initializer_list>
#include <memory>
#include <jni.h>
#include <errno.h>
#include <cassert>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include "pch.h"
#include "app.h"
#include "asset.h"
#include "keymap.h"
typedef void (*GLDEBUGPROC)(GLenum source,
GLenum type,
GLuint id,
GLenum severity,
GLsizei length,
const GLchar* message,
const void* userParam);
typedef void (*fnDebugMessageCallback)(GLDEBUGPROC callback, const void* userParam);
#define GL_DEBUG_SEVERITY_HIGH 0x9146
#define GL_DEBUG_SEVERITY_MEDIUM 0x9147
#define GL_DEBUG_SEVERITY_LOW 0x9148
#define GL_DEBUG_SEVERITY_NOTIFICATION 0x826B
#define GL_DEBUG_OUTPUT 0x92E0
#define GL_DEBUG_OUTPUT_SYNCHRONOUS 0x8242
/**
* Our saved state data.
*/
struct saved_state {
float angle;
int32_t x;
int32_t y;
};
/**
* Shared state for our app.
*/
struct engine {
struct android_app* app;
ASensorManager* sensorManager;
const ASensor* accelerometerSensor;
ASensorEventQueue* sensorEventQueue;
int animating;
EGLDisplay display;
EGLSurface surface;
EGLContext context;
int32_t width;
int32_t height;
struct saved_state state;
};
/**
* Initialize an EGL context for the current display.
*/
static int engine_init_display(struct engine* engine) {
// initialize OpenGL ES and EGL
App::I.initLog();
/*
* Here specify the attributes of the desired configuration.
* Below, we select an EGLConfig with at least 8 bits per color
* component compatible with on-screen windows
*/
const EGLint attribs[] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_BLUE_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_RED_SIZE, 8,
EGL_NONE
};
EGLint w, h, dummy, format;
EGLint numConfigs;
EGLConfig config;
EGLSurface surface;
EGLContext context;
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(display, 0, 0);
/* Here, the application chooses the configuration it desires.
* find the best match if possible, otherwise use the very first one
*/
eglChooseConfig(display, attribs, nullptr,0, &numConfigs);
std::unique_ptr<EGLConfig[]> supportedConfigs(new EGLConfig[numConfigs]);
assert(supportedConfigs);
eglChooseConfig(display, attribs, supportedConfigs.get(), numConfigs, &numConfigs);
assert(numConfigs);
auto i = 0;
for (; i < numConfigs; i++) {
auto& cfg = supportedConfigs[i];
EGLint r, g, b, d;
if (eglGetConfigAttrib(display, cfg, EGL_RED_SIZE, &r) &&
eglGetConfigAttrib(display, cfg, EGL_GREEN_SIZE, &g) &&
eglGetConfigAttrib(display, cfg, EGL_BLUE_SIZE, &b) &&
eglGetConfigAttrib(display, cfg, EGL_DEPTH_SIZE, &d) &&
r == 8 && g == 8 && b == 8 && d == 0 ) {
config = supportedConfigs[i];
break;
}
}
if (i == numConfigs) {
config = supportedConfigs[0];
}
/* EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is
* guaranteed to be accepted by ANativeWindow_setBuffersGeometry().
* As soon as we picked a EGLConfig, we can safely reconfigure the
* ANativeWindow buffers to match, using EGL_NATIVE_VISUAL_ID. */
eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format);
surface = eglCreateWindowSurface(display, config, engine->app->window, NULL);
const EGLint attribs_test[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR | EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR,
//EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR,
EGL_NONE
};
context = eglCreateContext(display, config, EGL_NO_CONTEXT, attribs_test);
if (context == EGL_NO_CONTEXT)
{
LOG("EGL: debug and forward context failed");
const EGLint attribs_test[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR,
EGL_NONE
};
context = eglCreateContext(display, config, EGL_NO_CONTEXT, attribs_test);
if (context == EGL_NO_CONTEXT)
{
LOG("EGL: only forward context failed");
const EGLint attribs_test[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR,
//EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR,
EGL_NONE
};
context = eglCreateContext(display, config, EGL_NO_CONTEXT, attribs_test);
if (context == EGL_NO_CONTEXT)
{
LOG("EGL: only debug context failed");
const EGLint attribs_test[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
context = eglCreateContext(display, config, EGL_NO_CONTEXT, attribs_test);
if (context == EGL_NO_CONTEXT)
{
LOG("EGL: all the context creation failed");
}
else
{
LOG("EGL: created simple context");
}
}
else
{
LOG("EGL: created only debug context");
}
}
else
{
LOG("EGL: created only forward context");
}
}
else
{
LOG("EGL: created debug and forward context");
}
if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) {
LOG("Unable to eglMakeCurrent");
return -1;
}
eglQuerySurface(display, surface, EGL_WIDTH, &w);
eglQuerySurface(display, surface, EGL_HEIGHT, &h);
engine->display = display;
engine->context = context;
engine->surface = surface;
engine->width = w;
engine->height = h;
engine->state.angle = 0;
// Check openGL on the system
auto opengl_info = { GL_VENDOR, GL_RENDERER, GL_VERSION/*, GL_EXTENSIONS*/ };
for (auto name : opengl_info) {
auto info = glGetString(name);
LOG("OpenGL Info: %s", info);
}
GLint n_exts;
std::map<std::string, bool> ext_map;
glGetIntegerv(GL_NUM_EXTENSIONS, &n_exts);
for (int i = 0; i < n_exts; i++)
{
ext_map.emplace((char*)glGetStringi(GL_EXTENSIONS, i), true);
}
//const char* ext = (const char*) glGetString(GL_EXTENSIONS);
//int ext_len = strlen(ext);
//static char ext_name[256];
//int ext_name_i = 0;
//for (int i = 0; i < ext_len; i++)
//{
// char c = ext[i];
// if (c == ' ')
// {
// ext_map.emplace(std::string(ext_name, ext_name_i), true);
// ext_name_i = 0;
// }
// else
// {
// ext_name[ext_name_i++] = c;
// }
//}
if (ext_map.count("GL_KHR_debug"))
{
LOG("GL_KHR_debug supported");
auto glDebugMessageCallback = (fnDebugMessageCallback)eglGetProcAddress("glDebugMessageCallbackKHR");
if (glDebugMessageCallback)
{
LOG("glDebugMessageCallback proc found at %p", glDebugMessageCallback);
glDebugMessageCallback([](GLenum source, GLenum type, GLuint id,
GLenum severity, GLsizei length, const GLchar* message, const void* userParam)
{
//if (severity == GL_DEBUG_SEVERITY_MEDIUM || severity == GL_DEBUG_SEVERITY_HIGH)
{
LOG("OPENGL: %.*s", length, message);
}
}, nullptr);
glEnable(GL_DEBUG_OUTPUT);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
}
else
{
LOG("glDebugMessageCallback proc NOT FOUND");
}
}
int ret = -1;
FILE* file = popen("getprop", "r");
std::map<std::string, std::string> os_props;
if (file)
{
char output[100];
while (fgets(output, sizeof(output), file) != nullptr)
{
int i = 0;
int l = strlen(output);
char buf[64];
int j = 0;
while (i < l && output[i] != '[') i++;
i++;
while (i < l && output[i] != ']') { buf[j++] = output[i]; i++; }
std::string key(buf, j);
j = 0;
while (i < l && output[i] != '[') i++;
i++;
while (i < l && output[i] != ']') { buf[j++] = output[i]; i++; }
os_props[key] = std::string(buf, j);
//LOG("PROP: %s -> %s", key.c_str(), os_props[key].c_str());
}
pclose(file);
}
LOG("PROP Android Version: %s", os_props["ro.build.version.release"].c_str());
LOG("PROP Android SDK: %s", os_props["ro.build.version.sdk"].c_str());
LOG("PROP Country Code: %s", os_props["ro.csc.country_code"].c_str());
LOG("PROP ABI: %s", os_props["ro.product.cpu.abilist"].c_str());
LOG("PROP Brand: %s", os_props["ro.product.brand"].c_str());
LOG("PROP Maker: %s", os_props["ro.product.manufacturer"].c_str());
LOG("PROP Mode: %s", os_props["ro.product.model"].c_str());
//LOG("PROP: %s", os_props[""].c_str());
//LOG("PROP: %s", os_props[""].c_str());
//LOG("PROP: %s", os_props[""].c_str());
// Initialize GL state.
//glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
//glEnable(GL_CULL_FACE);
//glShadeModel(GL_SMOOTH);
glDisable(GL_DEPTH_TEST);
//glEnableClientState(GL_VERTEX_ARRAY);
Asset::m_am = engine->app->activity->assetManager;
App::I.data_path = "/sdcard/PanoPainter";// engine->app->activity->externalDataPath;
App::I.width = w;
App::I.height = h;
App::I.init();
LOG("All ready");
engine->animating = 1;
ANativeActivity_showSoftInput(engine->app->activity, ANATIVEACTIVITY_SHOW_SOFT_INPUT_FORCED);
return 0;
}
/**
* Just the current frame in the display.
*/
static void engine_draw_frame(struct engine* engine) {
if (engine->display == NULL)
return;
glClearColor(.1f, .1f, .1f, 1.f);
glViewport(0, 0, (GLsizei)engine->width, (GLsizei)engine->height);
glClear(GL_COLOR_BUFFER_BIT);
App::I.update(0);
eglSwapBuffers(engine->display, engine->surface);
}
/**
* Tear down the EGL context currently associated with the display.
*/
static void engine_term_display(struct engine* engine) {
if (engine->display != EGL_NO_DISPLAY) {
eglMakeCurrent(engine->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (engine->context != EGL_NO_CONTEXT) {
eglDestroyContext(engine->display, engine->context);
}
if (engine->surface != EGL_NO_SURFACE) {
eglDestroySurface(engine->display, engine->surface);
}
eglTerminate(engine->display);
}
engine->animating = 0;
engine->display = EGL_NO_DISPLAY;
engine->context = EGL_NO_CONTEXT;
engine->surface = EGL_NO_SURFACE;
}
/**
* Process the next input event.
*/
static int32_t engine_handle_input(struct android_app* app, AInputEvent* event) {
struct engine* engine = (struct engine*)app->userData;
int32_t eventType = AInputEvent_getType(event);
//LOG("event type: %d", eventType);
switch (eventType) {
case AINPUT_EVENT_TYPE_MOTION:
// switch (AInputEvent_getSource(event)) {
// case AINPUT_SOURCE_STYLUS:
// case AINPUT_SOURCE_TOUCHSCREEN:
{
int action = AKeyEvent_getAction(event) & AMOTION_EVENT_ACTION_MASK;
int32_t index = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
>> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
int pointer_id = AMotionEvent_getPointerId(event, index);
int32_t count = AMotionEvent_getPointerCount(event);
auto findPointer = [](int id, AInputEvent* event)
{
int32_t count = AMotionEvent_getPointerCount(event);
int ret = -1;
for (int i = 0; i < count; i++)
{
//LOG("pointer %d id %d == %d", i, id, AMotionEvent_getPointerId(event, i));
if (AMotionEvent_getPointerId(event, i) == id)
ret = i;
}
return ret;
return -1;
};
struct Pointer
{
int id = -1;
int idx;
glm::vec2 pos;
};
static Pointer p0, p1;
static int tracked = 0;
//LOG("event source: %d", AInputEvent_getSource(event));
//LOG("pointer id %d count %d", pointer_id, count);
MouseEvent e;
switch (action) {
case AMOTION_EVENT_ACTION_DOWN:
{
float y = AMotionEvent_getY(event, 0);
float x = AMotionEvent_getX(event, 0);
p0.id = AMotionEvent_getPointerId(event, 0);
p0.pos = {x, y};
p0.idx = index;
App::I.mouse_down(0, x, y);
tracked = 1;
//LOG("first down");
return 1;
}
case AMOTION_EVENT_ACTION_POINTER_DOWN:
{
//LOG("pointer down index %d", index);
if (count == 2)
{
float y = AMotionEvent_getY(event, 1);
float x = AMotionEvent_getX(event, 1);
p1.id = AMotionEvent_getPointerId(event, 1);
p1.idx = index;
p1.pos = {x, y};
tracked = 2;
//LOG("second down");
App::I.mouse_cancel(0);
App::I.gesture_start(p0.pos, p1.pos);
}
return 1;
}
case AMOTION_EVENT_ACTION_UP:
{
float y = AMotionEvent_getY(event, 0);
float x = AMotionEvent_getX(event, 0);
p0.id = -1;
p1.id = -1;
if (tracked == 1)
App::I.mouse_up(0, x, y);
tracked = 0;
//LOG("first up");
return 1;
}
case AMOTION_EVENT_ACTION_POINTER_UP:
if (p1.id == AMotionEvent_getPointerId(event, 1))
{
p1.id = -1;
//LOG("second up");
App::I.gesture_end();
}
return 1;
case AMOTION_EVENT_ACTION_HOVER_MOVE: // pen move before touching
{
float y = AMotionEvent_getY(event, 0);
float x = AMotionEvent_getX(event, 0);
App::I.mouse_move(x, y);
//LOG("single move");
return 1;
}
case AMOTION_EVENT_ACTION_MOVE:
if (count == 1 && tracked == 1)
{
float y = AMotionEvent_getY(event, 0);
float x = AMotionEvent_getX(event, 0);
App::I.mouse_move(x, y);
//LOG("single move");
}
else if (count == 2)
{
int idx = findPointer(pointer_id, event);
//LOG("pointer move index %d", idx);
if (p0.idx == idx)
{
//LOG("first move");
float y = AMotionEvent_getY(event, 0);
float x = AMotionEvent_getX(event, 0);
p0.pos = {x, y};
}
if (p1.idx == idx)
{
//LOG("second move");
float x = AMotionEvent_getX(event, 1);
float y = AMotionEvent_getY(event, 1);
p1.pos = {x, y};
}
App::I.gesture_move(p0.pos, p1.pos);
}
return 1;
default:
//LOG("motion action: %d", action);
break;
}
}
// break;
// } // end switch
break;
case AINPUT_EVENT_TYPE_KEY:
{
int32_t key_val = AKeyEvent_getKeyCode(event);
LOG("Received key event: %d\n", key_val);
App::I.key_down(convert_key(key_val));
return 1;
}
} // end switch
return 0;
}
/**
* Process the next main command.
*/
static void engine_handle_cmd(struct android_app* app, int32_t cmd) {
struct engine* engine = (struct engine*)app->userData;
switch (cmd) {
case APP_CMD_SAVE_STATE:
// The system has asked us to save our current state. Do so.
engine->app->savedState = malloc(sizeof(struct saved_state));
*((struct saved_state*)engine->app->savedState) = engine->state;
engine->app->savedStateSize = sizeof(struct saved_state);
break;
case APP_CMD_INIT_WINDOW:
// The window is being shown, get it ready.
if (engine->app->window != NULL) {
engine_init_display(engine);
engine_draw_frame(engine);
}
break;
case APP_CMD_TERM_WINDOW:
// The window is being hidden or closed, clean it up.
App::I.terminate();
engine_term_display(engine);
//exit(0);
break;
case APP_CMD_GAINED_FOCUS:
// When our app gains focus, we start monitoring the accelerometer.
if (engine->accelerometerSensor != NULL) {
ASensorEventQueue_enableSensor(engine->sensorEventQueue,
engine->accelerometerSensor);
// We'd like to get 60 events per second (in us).
ASensorEventQueue_setEventRate(engine->sensorEventQueue,
engine->accelerometerSensor,
(1000L/60)*1000);
}
engine->animating = 1;
break;
case APP_CMD_LOST_FOCUS:
// When our app loses focus, we stop monitoring the accelerometer.
// This is to avoid consuming battery while not being used.
if (engine->accelerometerSensor != NULL) {
ASensorEventQueue_disableSensor(engine->sensorEventQueue,
engine->accelerometerSensor);
}
// Also stop animating.
engine->animating = 0;
engine_draw_frame(engine);
break;
}
}
/**
* This is the main entry point of a native application that is using
* android_native_app_glue. It runs in its own thread, with its own
* event loop for receiving input events and doing other things.
*/
void android_main(struct android_app* state) {
struct engine engine;
// Make sure glue isn't stripped.
app_dummy();
memset(&engine, 0, sizeof(engine));
state->userData = &engine;
state->onAppCmd = engine_handle_cmd;
state->onInputEvent = engine_handle_input;
engine.app = state;
// Prepare to monitor accelerometer
engine.sensorManager = ASensorManager_getInstance();
engine.accelerometerSensor = ASensorManager_getDefaultSensor(
engine.sensorManager,
ASENSOR_TYPE_ACCELEROMETER);
engine.sensorEventQueue = ASensorManager_createEventQueue(
engine.sensorManager,
state->looper, LOOPER_ID_USER,
NULL, NULL);
if (state->savedState != NULL) {
// We are starting with a previous saved state; restore from it.
engine.state = *(struct saved_state*)state->savedState;
}
//App::I.create();
// loop waiting for stuff to do.
while (1) {
// Read all pending events.
int ident;
int events;
struct android_poll_source* source;
// If not animating, we will block forever waiting for events.
// If animating, we loop until all events are read, then continue
// to draw the next frame of animation.
bool used = false;
while (!used && (ident=ALooper_pollAll(-1, NULL, &events,
(void**)&source)) >= 0) {
// Process this event.
if (source != NULL) {
source->process(state, source);
used = true;
}
// If a sensor has data, process it now.
if (ident == LOOPER_ID_USER) {
if (engine.accelerometerSensor != NULL) {
ASensorEvent event;
while (ASensorEventQueue_getEvents(engine.sensorEventQueue,
&event, 1) > 0) {
// LOGI("accelerometer: x=%f y=%f z=%f",
// event.acceleration.x, event.acceleration.y,
// event.acceleration.z);
}
}
}
// Check if we are exiting.
if (state->destroyRequested != 0) {
engine_term_display(&engine);
return;
}
}
//if (engine.animating)
{
// Done with events; draw next animation frame.
engine.state.angle += .01f;
if (engine.state.angle > 1) {
engine.state.angle = 0;
}
// Drawing is throttled to the screen update rate, so there
// is no need to do timing here.
engine_draw_frame(&engine);
}
}
}
//END_INCLUDE(all)