KDE/app/src/main/cpp/NativeEngine.cpp
2022-12-08 12:59:54 +01:00

501 lines
17 KiB
C++

//
// Created by kevin on 02.10.2022.
//
#include "NativeEngine.h"
#include "Log.h"
#include "AndroidAssetManager.h"
#include "Renderer.h"
#include "TouchInput.h"
/*#include "Game.h"
#include "base.h"*/
#include <assert.h>
#include <stdlib.h>
#include <swappy/swappyGL.h>
#include <GLES3/gl3.h>
static GameState g_app_state = {false};
static void _handle_cmd_proxy(struct android_app* app, int32_t cmd)
{
auto* engine = reinterpret_cast<NativeEngine*>(app->userData);
engine->handleAppCmd(cmd);
}
NativeEngine::NativeEngine(struct android_app* app)
: m_app(app),
m_egl_display(EGL_NO_DISPLAY),
m_egl_surface(EGL_NO_SURFACE),
m_egl_context(EGL_NO_CONTEXT),
m_jni_env(nullptr),
m_has_focus(false),
m_has_window(false),
m_is_visible(false),
m_in_motion(false)
{
SwappyGL_init(getJniEnv(), m_app->activity->javaGameActivity);
SwappyGL_setSwapIntervalNS(SWAPPY_SWAP_20FPS);
android_app_set_key_event_filter(m_app, NULL);
android_app_set_motion_event_filter(m_app, NULL);
AndroidAssetManager::create(m_app->activity->assetManager);
// INTEGRATION
// Hier kannst du Initialisierungscode aufrufen.
// Beachte das Renderer noch _nicht_ gültig ist.
// Der Renderer wird erst während des ersten Frames erzeugt.
//
// Du kannst schon Assets laden, da oben der AssetManager erzeugt wurde.
// Ende INTEGRATION
// BEISPIELCODE
m_smiley = StringRepository::global->internString("smiley_PNG42.png");
// m_smiley_pos = { 100, 100 };
// ENDE VOM BEISPIELCODE
}
NativeEngine::~NativeEngine()
{
ALOGI("NativeEngine: destructor");
// INTEGRATION
// Hier könntest du eine "onExit" funktion aufrufen
//
SwappyGL_destroy();
killContext();
if (m_jni_env) {
m_app->activity->vm->DetachCurrentThread();
}
}
void NativeEngine::gameLoop()
{
m_app->userData = this;
m_app->onAppCmd = _handle_cmd_proxy;
m_app->textInputState = 0;
while (true) {
int events;
struct android_poll_source* source;
// Poll commands
while ((ALooper_pollAll(isAnimating() ? 0 : -1, nullptr, &events, reinterpret_cast<void**>(&source))) >= 0) {
if (source)
source->process(source->app, source);
if (m_app->destroyRequested)
return;
}
constexpr int MAX_INPUT_EVENTS = 16;
TouchInputEvent input_events[MAX_INPUT_EVENTS];
int input_event_count = 0;
// Handle input
android_input_buffer* input_buffer = android_app_swap_input_buffers(m_app);
if (input_buffer) {
if (input_buffer->keyEventsCount > 0) {
android_app_clear_key_events(input_buffer);
}
if (input_buffer->motionEventsCount > 0) {
float rel_size = m_display_height;
if (m_display_height > m_display_width) {
rel_size = m_display_width;
}
rel_size *= 0.04f;
// float rel_size = MIN(m_display_height, m_display_width) * 0.04f;
for (unsigned int i = 0; i < input_buffer->motionEventsCount; ++i) {
GameActivityMotionEvent motion_event = input_buffer->motionEvents[i];
if (motion_event.action == AMOTION_EVENT_ACTION_DOWN) {
if (motion_event.pointerCount > 0) {
GameActivityPointerAxes pointer = motion_event.pointers[0];
float x = GameActivityPointerAxes_getX(&pointer);
float y = GameActivityPointerAxes_getY(&pointer);
// Record this motion
m_in_motion = true;
m_last_down.x = x;
m_last_down.y = y;
}
}
else if (motion_event.action == AMOTION_EVENT_ACTION_MOVE) {
if (motion_event.pointerCount > 0) {
GameActivityPointerAxes pointer = motion_event.pointers[0];
float x = GameActivityPointerAxes_getX(&pointer);
float y = GameActivityPointerAxes_getY(&pointer);
if (!m_in_motion) {
// Record this motion
m_in_motion = true;
m_last_down.x = x;
m_last_down.y = y;
}
float dist = sqrtf((x - m_last_down.x) * (x - m_last_down.x) +
(y - m_last_down.y) * (y - m_last_down.y));
if (dist >= rel_size) {
input_events[input_event_count].kind = TouchInputEventKind::InSwipe;
input_events[input_event_count].start = {m_last_down.x, m_last_down.y};
input_events[input_event_count].end = {x, y};
++input_event_count;
}
}
}
else if (motion_event.action == AMOTION_EVENT_ACTION_UP) {
if (!m_in_motion) {
ALOGW("Got an UP motion without a corresponding DOWN motion");
continue;
}
if (motion_event.pointerCount > 0) {
GameActivityPointerAxes pointer = motion_event.pointers[0];
float x = GameActivityPointerAxes_getX(&pointer);
float y = GameActivityPointerAxes_getY(&pointer);
float dist = sqrtf((x - m_last_down.x) * (x - m_last_down.x) +
(y - m_last_down.y) * (y - m_last_down.y));
if (dist < rel_size) {
// TAP
ALOGI("TAP at %f %f", x, y);
input_events[input_event_count].kind = TouchInputEventKind::Tap;
input_events[input_event_count].start = {x, y};
input_events[input_event_count].end = {x, y};
++input_event_count;
}
else {
// Swipe
ALOGI("Swipe from %f %f to %f %f", m_last_down.x, m_last_down.y, x, y);
input_events[input_event_count].kind = TouchInputEventKind::EndSwipe;
input_events[input_event_count].start = {m_last_down.x, m_last_down.y};
input_events[input_event_count].end = {x, y};
++input_event_count;
}
m_in_motion = false;
if (input_event_count == MAX_INPUT_EVENTS)
break;
}
}
else if (motion_event.action == AMOTION_EVENT_ACTION_CANCEL) {
m_in_motion = false;
}
}
android_app_clear_motion_events(input_buffer);
}
}
else {
m_in_motion = false;
}
if (isAnimating() && Renderer::ptr) {
// INTEGRATION Rufe hier deine "gameloop"/"update" funktion auf, die als Parameter
// den GameState, input_events und input_event_count und die Displaygröße bekommen sollte:
//
// kUpdate(&m_state, input_events, input_event_count,
// 0.0,
// nullptr, 0,
// nullptr, 0,
// m_display_width, m_display_height)
//
// (Für Kompatibilität zu Windows sollte die funktion auch Tastatur und Scroll input akzeptieren,
// den es unter Android aber momentan nicht gibt.)
//
// Die Funktion könnte folgende Definition haben:
// void kUpdate(GameState* state,
// const TouchInputEvent* touch_events,
// unsigned int touch_event_count,
// double scroll_offset,
// const unsigned int* pressed_codepoints,
// unsigned int codepoint_count,
// const int* pressed_keys,
// unsigned int pressed_key_count,
// float display_width,
// float display_height)
// ENDE INTEGRATION
// BEISPIELCODE
static float x = 1.f;
static float d = -0.01f;
x += d;
if (x <= 0.f)
d *= -1.f;
else if (x >= 1.f)
d *= -1.f;
if (input_event_count > 0) {
m_smiley_pos = input_events[input_event_count - 1].end;
m_smiley_pos.x -= 250;
m_smiley_pos.y -= 250;
}
Renderer::ptr->addRect(100, 100, 500, 500, 0.3f, 0.3f, 0.3f, 1.f);
Renderer::ptr->addRect(m_smiley_pos.x, m_smiley_pos.y, 500, 500, 0.f, x * x, 1.f - x * x, 1.f, m_smiley);
// ENDE VOM BEISPIELCODE
}
if (isAnimating()) {
renderFrame();
}
}
}
bool NativeEngine::isAnimating() const
{
return m_has_window && m_has_focus && m_is_visible;
}
void NativeEngine::handleAppCmd(int32_t cmd)
{
ALOGV("NativeEngine: Handling command %d.", cmd);
switch (cmd) {
case APP_CMD_SAVE_STATE:
ALOGV("NativeEngine:: APP_CMD_SAVE_STATE");
m_state.has_focus = m_has_focus;
m_app->savedState = malloc(sizeof(m_state));
*reinterpret_cast<GameState*>(m_app->savedState) = m_state;
m_app->savedStateSize = sizeof(m_state);
break;
case APP_CMD_INIT_WINDOW:
ALOGV("NativeEngine:: APP_CMD_INIT_WINDOW");
if (m_app->window) {
m_has_window = true;
SwappyGL_setWindow(m_app->window);
if (m_app->savedStateSize == sizeof(m_state) && m_app->savedState) {
m_state = *reinterpret_cast<GameState*>(m_app->savedState);
m_has_focus = m_state.has_focus;
}
else {
// Workaround APP_CMD_GAINED_FOCUS issue where the focus state is not passed
// down from GameActivity when restarting Activity
m_has_focus = g_app_state.has_focus;
}
}
ALOGV("HandleCommand(%d): hasWindow = %d, hasFocus = %d", cmd, m_has_window ? 1 : 0, m_has_focus ? 1 : 0);
break;
case APP_CMD_TERM_WINDOW:
ALOGV("NativeEngine: APP_CMD_TERM_WINDOW");
killSurface();
m_has_window = false;
break;
case APP_CMD_GAINED_FOCUS:
ALOGV("NativeEngine: APP_CMD_GAINED_FOCUS");
m_has_focus = true;
m_state.has_focus = g_app_state.has_focus = m_has_focus;
break;
case APP_CMD_LOST_FOCUS:
ALOGV("NativeEngine: APP_CMD_LOST_FOCUS");
m_has_focus = false;
m_state.has_focus = g_app_state.has_focus = m_has_focus;
break;
case APP_CMD_START:
ALOGV("NativeEngine: APP_CMD_START");
m_is_visible = true;
break;
case APP_CMD_STOP:
ALOGV("NativeEngine: APP_CMD_STOP");
m_is_visible = false;
break;
case APP_CMD_RESUME:
ALOGV("NativeEngine: APP_CMD_RESUME");
break;
default:
ALOGW("Unhandled command.");
break;
}
ALOGV("NativeEngine STATUS: F%d, V%d, W%d, EGL: D %p, S %p, CTX %p, CFG %p",
m_has_focus,
m_is_visible,
m_has_window,
m_egl_display,
m_egl_surface,
m_egl_context,
m_egl_config);
}
JNIEnv* NativeEngine::getJniEnv()
{
if (!m_jni_env) {
if (m_app->activity->vm->AttachCurrentThread(&m_jni_env, nullptr) != 0) {
ALOGE("*** FATAL ERROR: failed to attach thread to JNI.");
exit(1);
}
ALOGI("Attached current thread to JNI, %p", m_jni_env);
}
return m_jni_env;
}
bool NativeEngine::prepareToRender()
{
if (m_egl_display == EGL_NO_DISPLAY || m_egl_surface == EGL_NO_SURFACE || m_egl_context == EGL_NO_CONTEXT) {
if (!initDisplay()) {
ALOGE("NativeEngine: failed to create display");
return false;
}
if (!initSurface()) {
ALOGE("NativeEngine: failed to create surface.");
return false;
}
if (!initContext()) {
ALOGE("NativeEngine: failed to create context.");
return false;
}
ALOGI("NativeEngine: binding surface and context (display %p, surface %p, context %p)",
m_egl_display,
m_egl_surface,
m_egl_context);
if (eglMakeCurrent(m_egl_display, m_egl_surface, m_egl_surface, m_egl_context) == EGL_FALSE) {
ALOGE("NativeEngine: eglMakeCurrent failed, EGL error: %d", eglGetError());
return false;
}
initGLObjects();
}
return true;
}
bool NativeEngine::initDisplay()
{
if (m_egl_display != EGL_NO_DISPLAY) {
return true;
}
ALOGI("NativeEngine: Initializing display");
m_egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (eglInitialize(m_egl_display, 0, 0) == EGL_FALSE) {
ALOGE("NativeEngine: Failed to init display, error %d", eglGetError());
return false;
}
return true;
}
void NativeEngine::killDisplay()
{
ALOGI("NativeEngine: Killing display");
killContext();
killSurface();
if (m_egl_display != EGL_NO_DISPLAY) {
eglTerminate(m_egl_display);
m_egl_display = EGL_NO_DISPLAY;
}
}
bool NativeEngine::initSurface()
{
assert(m_egl_display != EGL_NO_DISPLAY);
if (m_egl_surface != EGL_NO_SURFACE) {
return true;
}
ALOGI("NativeEngine: initializing surface");
EGLint num_configs;
const EGLint attribs[] = {
EGL_RENDERABLE_TYPE,
EGL_OPENGL_ES3_BIT,
EGL_SURFACE_TYPE,
EGL_WINDOW_BIT,
EGL_BLUE_SIZE,
8,
EGL_GREEN_SIZE,
8,
EGL_RED_SIZE,
8,
EGL_DEPTH_SIZE,
16,
EGL_NONE,
};
eglChooseConfig(m_egl_display, attribs, &m_egl_config, 1, &num_configs);
m_egl_surface = eglCreateWindowSurface(m_egl_display, m_egl_config, m_app->window, nullptr);
if (m_egl_surface == EGL_NO_SURFACE) {
ALOGE("Failed to create EGL surface. EGL error: %d", eglGetError());
return false;
}
return true;
}
void NativeEngine::killSurface()
{
ALOGI("NativeEngine: Killing surface.");
eglMakeCurrent(m_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (m_egl_surface != EGL_NO_SURFACE) {
eglDestroySurface(m_egl_display, m_egl_surface);
m_egl_surface = EGL_NO_SURFACE;
}
}
bool NativeEngine::initContext()
{
ALOGI("NativeEngine: initializing context");
assert(m_egl_display != EGL_NO_DISPLAY);
EGLint attrib_list[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE};
if (m_egl_context != EGL_NO_CONTEXT)
return true;
m_egl_context = eglCreateContext(m_egl_display, m_egl_config, nullptr, attrib_list);
if (m_egl_context == EGL_NO_CONTEXT) {
ALOGE("Failed to create EGL context, egl error: %d", eglGetError());
return false;
}
return true;
}
void NativeEngine::killContext()
{
ALOGI("NativeEngine: Killing context");
killGLObjects();
eglMakeCurrent(m_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (m_egl_context != EGL_NO_CONTEXT) {
eglDestroyContext(m_egl_display, m_egl_context);
m_egl_context = EGL_NO_CONTEXT;
}
}
bool NativeEngine::initGLObjects()
{
if (Renderer::ptr) {
return true;
}
Renderer::create();
return true;
}
void NativeEngine::killGLObjects()
{
if (Renderer::ptr) {
Renderer::destroy();
}
}
void NativeEngine::renderFrame()
{
if (!prepareToRender()) {
ALOGE("NativeEngine: preparation to render failed");
return;
}
int width, height;
eglQuerySurface(m_egl_display, m_egl_surface, EGL_WIDTH, &width);
eglQuerySurface(m_egl_display, m_egl_surface, EGL_HEIGHT, &height);
m_display_width = static_cast<float>(width);
m_display_height = static_cast<float>(height);
Renderer::ptr->renderFrame(m_display_width, m_display_height);
if (!SwappyGL_swap(m_egl_display, m_egl_surface)) {
ALOGW("NativeEngine: SwappyGL_swap failed, EGL error %d", eglGetError());
}
}