501 lines
17 KiB
C++
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());
|
|
}
|
|
} |