// // 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 #include #include #include static GameState g_app_state = {false}; static void _handle_cmd_proxy(struct android_app* app, int32_t cmd) { auto* engine = reinterpret_cast(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(&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(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(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(width); m_display_height = static_cast(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()); } }