add pojav native code

This commit is contained in:
ShirosakiMio 2024-08-19 17:17:57 +08:00
parent 611ed7ded3
commit 9ef4652e52
22 changed files with 2259 additions and 2 deletions

View File

@ -0,0 +1,11 @@
package com.tungsten.fclauncher;
import dalvik.annotation.optimization.CriticalNative;
public class CriticalNativeTest {
@CriticalNative
public static native void testCriticalNative(int arg0, int arg1);
public static void invokeTest() {
testCriticalNative(0, 0);
}
}

View File

@ -48,4 +48,30 @@ LOCAL_SHARED_LIBRARIES := fcl
LOCAL_SRC_FILES := awt/awt_bridge.c LOCAL_SRC_FILES := awt/awt_bridge.c
include $(BUILD_SHARED_LIBRARY) include $(BUILD_SHARED_LIBRARY)
include $(CLEAR_VARS)
# Link GLESv2 for test
LOCAL_LDLIBS := -ldl -llog -landroid
# -lGLESv2
LOCAL_MODULE := pojavexec
LOCAL_SHARED_LIBRARIES := fcl
# LOCAL_CFLAGS += -DDEBUG
# -DGLES_TEST
LOCAL_SRC_FILES := \
pojav/bigcoreaffinity.c \
pojav/egl_bridge.c \
pojav/ctxbridges/gl_bridge.c \
pojav/ctxbridges/osm_bridge.c \
pojav/ctxbridges/egl_loader.c \
pojav/ctxbridges/osmesa_loader.c \
pojav/ctxbridges/swap_interval_no_egl.c \
pojav/environ/environ.c \
pojav/input_bridge_v3.c \
driver_helper/nsbypass.c
ifeq ($(TARGET_ARCH_ABI),arm64-v8a)
LOCAL_CFLAGS += -DADRENO_POSSIBLE
LOCAL_LDLIBS += -lEGL -lGLESv2
endif
include $(BUILD_SHARED_LIBRARY)
$(call import-module,prefab/bytehook) $(call import-module,prefab/bytehook)

View File

@ -2046,7 +2046,7 @@ typedef void (APIENTRYP PFNGLMULTITEXCOORD4SVARBPROC) (GLenum target, const GLsh
#else /* GL_GLEXT_LEGACY */ #else /* GL_GLEXT_LEGACY */
#include "glext.h" #include "pojav/GL/glext.h"
#endif /* GL_GLEXT_LEGACY */ #endif /* GL_GLEXT_LEGACY */

View File

@ -0,0 +1,332 @@
/*
* Mesa 3-D graphics library
*
* Copyright (C) 1999-2005 Brian Paul All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* Mesa Off-Screen rendering interface.
*
* This is an operating system and window system independent interface to
* Mesa which allows one to render images into a client-supplied buffer in
* main memory. Such images may manipulated or saved in whatever way the
* client wants.
*
* These are the API functions:
* OSMesaCreateContext - create a new Off-Screen Mesa rendering context
* OSMesaMakeCurrent - bind an OSMesaContext to a client's image buffer
* and make the specified context the current one.
* OSMesaDestroyContext - destroy an OSMesaContext
* OSMesaGetCurrentContext - return thread's current context ID
* OSMesaPixelStore - controls how pixels are stored in image buffer
* OSMesaGetIntegerv - return OSMesa state parameters
*
*
* The limits on the width and height of an image buffer can be retrieved
* via OSMesaGetIntegerv(OSMESA_MAX_WIDTH/OSMESA_MAX_HEIGHT).
*/
#ifndef OSMESA_H
#define OSMESA_H
#ifdef __cplusplus
extern "C" {
#endif
#include "pojav/GL/gl.h"
#define OSMESA_MAJOR_VERSION 11
#define OSMESA_MINOR_VERSION 2
#define OSMESA_PATCH_VERSION 0
/*
* Values for the format parameter of OSMesaCreateContext()
* New in version 2.0.
*/
#define OSMESA_COLOR_INDEX GL_COLOR_INDEX
#define OSMESA_RGBA GL_RGBA
#define OSMESA_BGRA 0x1
#define OSMESA_ARGB 0x2
#define OSMESA_RGB GL_RGB
#define OSMESA_BGR 0x4
#define OSMESA_RGB_565 0x5
/*
* OSMesaPixelStore() parameters:
* New in version 2.0.
*/
#define OSMESA_ROW_LENGTH 0x10
#define OSMESA_Y_UP 0x11
/*
* Accepted by OSMesaGetIntegerv:
*/
#define OSMESA_WIDTH 0x20
#define OSMESA_HEIGHT 0x21
#define OSMESA_FORMAT 0x22
#define OSMESA_TYPE 0x23
#define OSMESA_MAX_WIDTH 0x24 /* new in 4.0 */
#define OSMESA_MAX_HEIGHT 0x25 /* new in 4.0 */
/*
* Accepted in OSMesaCreateContextAttrib's attribute list.
*/
#define OSMESA_DEPTH_BITS 0x30
#define OSMESA_STENCIL_BITS 0x31
#define OSMESA_ACCUM_BITS 0x32
#define OSMESA_PROFILE 0x33
#define OSMESA_CORE_PROFILE 0x34
#define OSMESA_COMPAT_PROFILE 0x35
#define OSMESA_CONTEXT_MAJOR_VERSION 0x36
#define OSMESA_CONTEXT_MINOR_VERSION 0x37
typedef struct osmesa_context *OSMesaContext;
/*
* Create an Off-Screen Mesa rendering context. The only attribute needed is
* an RGBA vs Color-Index mode flag.
*
* Input: format - one of OSMESA_COLOR_INDEX, OSMESA_RGBA, OSMESA_BGRA,
* OSMESA_ARGB, OSMESA_RGB, or OSMESA_BGR.
* sharelist - specifies another OSMesaContext with which to share
* display lists. NULL indicates no sharing.
* Return: an OSMesaContext or 0 if error
*/
GLAPI OSMesaContext GLAPIENTRY
OSMesaCreateContext( GLenum format, OSMesaContext sharelist );
/*
* Create an Off-Screen Mesa rendering context and specify desired
* size of depth buffer, stencil buffer and accumulation buffer.
* If you specify zero for depthBits, stencilBits, accumBits you
* can save some memory.
*
* New in Mesa 3.5
*/
GLAPI OSMesaContext GLAPIENTRY
OSMesaCreateContextExt( GLenum format, GLint depthBits, GLint stencilBits,
GLint accumBits, OSMesaContext sharelist);
/*
* Create an Off-Screen Mesa rendering context with attribute list.
* The list is composed of (attribute, value) pairs and terminated with
* attribute==0. Supported Attributes:
*
* Attributes Values
* --------------------------------------------------------------------------
* OSMESA_FORMAT OSMESA_RGBA*, OSMESA_BGRA, OSMESA_ARGB, etc.
* OSMESA_DEPTH_BITS 0*, 16, 24, 32
* OSMESA_STENCIL_BITS 0*, 8
* OSMESA_ACCUM_BITS 0*, 16
* OSMESA_PROFILE OSMESA_COMPAT_PROFILE*, OSMESA_CORE_PROFILE
* OSMESA_CONTEXT_MAJOR_VERSION 1*, 2, 3
* OSMESA_CONTEXT_MINOR_VERSION 0+
*
* Note: * = default value
*
* We return a context version >= what's specified by OSMESA_CONTEXT_MAJOR/
* MINOR_VERSION for the given profile. For example, if you request a GL 1.4
* compat profile, you might get a GL 3.0 compat profile.
* Otherwise, null is returned if the version/profile is not supported.
*
* New in Mesa 11.2
*/
GLAPI OSMesaContext GLAPIENTRY
OSMesaCreateContextAttribs( const int *attribList, OSMesaContext sharelist );
/*
* Destroy an Off-Screen Mesa rendering context.
*
* Input: ctx - the context to destroy
*/
GLAPI void GLAPIENTRY
OSMesaDestroyContext( OSMesaContext ctx );
/*
* Bind an OSMesaContext to an image buffer. The image buffer is just a
* block of memory which the client provides. Its size must be at least
* as large as width*height*sizeof(type). Its address should be a multiple
* of 4 if using RGBA mode.
*
* Image data is stored in the order of glDrawPixels: row-major order
* with the lower-left image pixel stored in the first array position
* (ie. bottom-to-top).
*
* Since the only type initially supported is GL_UNSIGNED_BYTE, if the
* context is in RGBA mode, each pixel will be stored as a 4-byte RGBA
* value. If the context is in color indexed mode, each pixel will be
* stored as a 1-byte value.
*
* If the context's viewport hasn't been initialized yet, it will now be
* initialized to (0,0,width,height).
*
* Input: ctx - the rendering context
* buffer - the image buffer memory
* type - data type for pixel components, only GL_UNSIGNED_BYTE
* supported now
* width, height - size of image buffer in pixels, at least 1
* Return: GL_TRUE if success, GL_FALSE if error because of invalid ctx,
* invalid buffer address, type!=GL_UNSIGNED_BYTE, width<1, height<1,
* width>internal limit or height>internal limit.
*/
GLAPI GLboolean GLAPIENTRY
OSMesaMakeCurrent( OSMesaContext ctx, void *buffer, GLenum type,
GLsizei width, GLsizei height );
/*
* Return the current Off-Screen Mesa rendering context handle.
*/
GLAPI OSMesaContext GLAPIENTRY
OSMesaGetCurrentContext( void );
/*
* Set pixel store/packing parameters for the current context.
* This is similar to glPixelStore.
* Input: pname - OSMESA_ROW_LENGTH
* specify actual pixels per row in image buffer
* 0 = same as image width (default)
* OSMESA_Y_UP
* zero = Y coordinates increase downward
* non-zero = Y coordinates increase upward (default)
* value - the value for the parameter pname
*
* New in version 2.0.
*/
GLAPI void GLAPIENTRY
OSMesaPixelStore( GLint pname, GLint value );
/*
* Return an integer value like glGetIntegerv.
* Input: pname -
* OSMESA_WIDTH return current image width
* OSMESA_HEIGHT return current image height
* OSMESA_FORMAT return image format
* OSMESA_TYPE return color component data type
* OSMESA_ROW_LENGTH return row length in pixels
* OSMESA_Y_UP returns 1 or 0 to indicate Y axis direction
* value - pointer to integer in which to return result.
*/
GLAPI void GLAPIENTRY
OSMesaGetIntegerv( GLint pname, GLint *value );
/*
* Return the depth buffer associated with an OSMesa context.
* Input: c - the OSMesa context
* Output: width, height - size of buffer in pixels
* bytesPerValue - bytes per depth value (2 or 4)
* buffer - pointer to depth buffer values
* Return: GL_TRUE or GL_FALSE to indicate success or failure.
*
* New in Mesa 2.4.
*/
GLAPI GLboolean GLAPIENTRY
OSMesaGetDepthBuffer( OSMesaContext c, GLint *width, GLint *height,
GLint *bytesPerValue, void **buffer );
/*
* Return the color buffer associated with an OSMesa context.
* Input: c - the OSMesa context
* Output: width, height - size of buffer in pixels
* format - buffer format (OSMESA_FORMAT)
* buffer - pointer to depth buffer values
* Return: GL_TRUE or GL_FALSE to indicate success or failure.
*
* New in Mesa 3.3.
*/
GLAPI GLboolean GLAPIENTRY
OSMesaGetColorBuffer( OSMesaContext c, GLint *width, GLint *height,
GLint *format, void **buffer );
/**
* This typedef is new in Mesa 6.3.
*/
typedef void (*OSMESAproc)();
/*
* Return pointer to the named function.
* New in Mesa 4.1
* Return OSMESAproc in 6.3.
*/
GLAPI OSMESAproc GLAPIENTRY
OSMesaGetProcAddress( const char *funcName );
/**
* Enable/disable color clamping, off by default.
* New in Mesa 6.4.2
*/
GLAPI void GLAPIENTRY
OSMesaColorClamp(GLboolean enable);
/**
* Enable/disable Gallium post-process filters.
* This should be called after a context is created, but before it is
* made current for the first time. After a context has been made
* current, this function has no effect.
* If the enable_value param is zero, the filter is disabled. Otherwise
* the filter is enabled, and the value may control the filter's quality.
* New in Mesa 10.0
*/
GLAPI void GLAPIENTRY
OSMesaPostprocess(OSMesaContext osmesa, const char *filter,
unsigned enable_value);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,55 @@
//
// Created by maks on 19.06.2023.
//
#define _GNU_SOURCE // we are GNU GPLv3
#include <linux/limits.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sched.h>
#include <string.h>
#define FREQ_MAX 256
void bigcore_format_cpu_path(char* buffer, unsigned int cpu_core) {
snprintf(buffer, PATH_MAX, "/sys/devices/system/cpu/cpu%i/cpufreq/cpuinfo_max_freq", cpu_core);
}
void bigcore_set_affinity() {
char path_buffer[PATH_MAX];
char freq_buffer[FREQ_MAX];
char* discard;
unsigned long core_freq;
unsigned long max_freq = 0;
unsigned int corecnt = 0;
unsigned int big_core_id = 0;
while(1) {
bigcore_format_cpu_path(path_buffer, corecnt);
int corefreqfd = open(path_buffer, O_RDONLY);
if(corefreqfd != -1) {
ssize_t read_count = read(corefreqfd, freq_buffer, FREQ_MAX);
close(corefreqfd);
freq_buffer[read_count] = 0;
core_freq = strtoul(freq_buffer, &discard, 10);
if(core_freq >= max_freq) {
max_freq = core_freq;
big_core_id = corecnt;
}
}else{
break;
}
corecnt++;
}
printf("bigcore: big CPU number is %u, frequency %lu Hz\n", big_core_id, max_freq);
cpu_set_t bigcore_affinity_set;
CPU_ZERO(&bigcore_affinity_set);
CPU_SET_S(big_core_id, CPU_SETSIZE, &bigcore_affinity_set);
int result = sched_setaffinity(0, CPU_SETSIZE, &bigcore_affinity_set);
if(result != 0) {
printf("bigcore: setting affinity failed: %s\n", strerror(result));
}else{
printf("bigcore: forced current thread onto big core\n");
}
}

View File

@ -0,0 +1,45 @@
//
// Created by maks on 18.10.2023.
//
#ifndef POJAVLAUNCHER_BRIDGE_TBL_H
#define POJAVLAUNCHER_BRIDGE_TBL_H
#include "pojav/ctxbridges/common.h"
#include "pojav/ctxbridges/gl_bridge.h"
#include "pojav/ctxbridges/osm_bridge.h"
typedef basic_render_window_t* (*br_init_context_t)(basic_render_window_t* share);
typedef void (*br_make_current_t)(basic_render_window_t* bundle);
typedef basic_render_window_t* (*br_get_current_t)();
bool (*br_init)() = NULL;
br_init_context_t br_init_context = NULL;
br_make_current_t br_make_current = NULL;
br_get_current_t br_get_current = NULL;
void (*br_swap_buffers)() = NULL;
void (*br_setup_window)() = NULL;
void (*br_swap_interval)(int swapInterval) = NULL;
void set_osm_bridge_tbl() {
br_init = osm_init;
br_init_context = (br_init_context_t) osm_init_context;
br_make_current = (br_make_current_t) osm_make_current;
br_get_current = (br_get_current_t) osm_get_current;
br_swap_buffers = osm_swap_buffers;
br_setup_window = osm_setup_window;
br_swap_interval = osm_swap_interval;
}
void set_gl_bridge_tbl() {
br_init = gl_init;
br_init_context = (br_init_context_t) gl_init_context;
br_make_current = (br_make_current_t) gl_make_current;
br_get_current = (br_get_current_t) gl_get_current;
br_swap_buffers = gl_swap_buffers;
br_setup_window = gl_setup_window;
br_swap_interval = gl_swap_interval;
}
#endif //POJAVLAUNCHER_BRIDGE_TBL_H

View File

@ -0,0 +1,17 @@
//
// Created by maks on 18.10.2023.
//
#ifndef POJAVLAUNCHER_COMMON_H
#define POJAVLAUNCHER_COMMON_H
#define STATE_RENDERER_ALIVE 0
#define STATE_RENDERER_NEW_WINDOW 1
typedef struct {
char state;
struct ANativeWindow *nativeSurface;
struct ANativeWindow *newNativeSurface;
} basic_render_window_t;
#endif //POJAVLAUNCHER_COMMON_H

View File

@ -0,0 +1,51 @@
//
// Created by maks on 21.09.2022.
//
#include <stddef.h>
#include <stdlib.h>
#include <dlfcn.h>
#include "egl_loader.h"
EGLBoolean (*eglMakeCurrent_p) (EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx);
EGLBoolean (*eglDestroyContext_p) (EGLDisplay dpy, EGLContext ctx);
EGLBoolean (*eglDestroySurface_p) (EGLDisplay dpy, EGLSurface surface);
EGLBoolean (*eglTerminate_p) (EGLDisplay dpy);
EGLBoolean (*eglReleaseThread_p) (void);
EGLContext (*eglGetCurrentContext_p) (void);
EGLDisplay (*eglGetDisplay_p) (NativeDisplayType display);
EGLBoolean (*eglInitialize_p) (EGLDisplay dpy, EGLint *major, EGLint *minor);
EGLBoolean (*eglChooseConfig_p) (EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config);
EGLBoolean (*eglGetConfigAttrib_p) (EGLDisplay dpy, EGLConfig config, EGLint attribute, EGLint *value);
EGLBoolean (*eglBindAPI_p) (EGLenum api);
EGLSurface (*eglCreatePbufferSurface_p) (EGLDisplay dpy, EGLConfig config, const EGLint *attrib_list);
EGLSurface (*eglCreateWindowSurface_p) (EGLDisplay dpy, EGLConfig config, NativeWindowType window, const EGLint *attrib_list);
EGLBoolean (*eglSwapBuffers_p) (EGLDisplay dpy, EGLSurface draw);
EGLint (*eglGetError_p) (void);
EGLContext (*eglCreateContext_p) (EGLDisplay dpy, EGLConfig config, EGLContext share_list, const EGLint *attrib_list);
EGLBoolean (*eglSwapInterval_p) (EGLDisplay dpy, EGLint interval);
EGLSurface (*eglGetCurrentSurface_p) (EGLint readdraw);
void dlsym_EGL() {
void* dl_handle = NULL;
if(getenv("POJAVEXEC_EGL")) dl_handle = dlopen(getenv("POJAVEXEC_EGL"), RTLD_LAZY);
if(dl_handle == NULL) dl_handle = dlopen("libEGL.so", RTLD_LAZY);
if(dl_handle == NULL) abort();
eglBindAPI_p = dlsym(dl_handle,"eglBindAPI");
eglChooseConfig_p = dlsym(dl_handle, "eglChooseConfig");
eglCreateContext_p = dlsym(dl_handle, "eglCreateContext");
eglCreatePbufferSurface_p = dlsym(dl_handle, "eglCreatePbufferSurface");
eglCreateWindowSurface_p = dlsym(dl_handle, "eglCreateWindowSurface");
eglDestroyContext_p = dlsym(dl_handle, "eglDestroyContext");
eglDestroySurface_p = dlsym(dl_handle, "eglDestroySurface");
eglGetConfigAttrib_p = dlsym(dl_handle, "eglGetConfigAttrib");
eglGetCurrentContext_p = dlsym(dl_handle, "eglGetCurrentContext");
eglGetDisplay_p = dlsym(dl_handle, "eglGetDisplay");
eglGetError_p = dlsym(dl_handle, "eglGetError");
eglInitialize_p = dlsym(dl_handle, "eglInitialize");
eglMakeCurrent_p = dlsym(dl_handle, "eglMakeCurrent");
eglSwapBuffers_p = dlsym(dl_handle, "eglSwapBuffers");
eglReleaseThread_p = dlsym(dl_handle, "eglReleaseThread");
eglSwapInterval_p = dlsym(dl_handle, "eglSwapInterval");
eglTerminate_p = dlsym(dl_handle, "eglTerminate");
eglGetCurrentSurface_p = dlsym(dl_handle,"eglGetCurrentSurface");
}

View File

@ -0,0 +1,29 @@
//
// Created by maks on 21.09.2022.
//
#include <EGL/egl.h>
#ifndef POJAVLAUNCHER_EGL_LOADER_H
#define POJAVLAUNCHER_EGL_LOADER_H
extern EGLBoolean (*eglMakeCurrent_p) (EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx);
extern EGLBoolean (*eglDestroyContext_p) (EGLDisplay dpy, EGLContext ctx);
extern EGLBoolean (*eglDestroySurface_p) (EGLDisplay dpy, EGLSurface surface);
extern EGLBoolean (*eglTerminate_p) (EGLDisplay dpy);
extern EGLBoolean (*eglReleaseThread_p) (void);
extern EGLContext (*eglGetCurrentContext_p) (void);
extern EGLDisplay (*eglGetDisplay_p) (NativeDisplayType display);
extern EGLBoolean (*eglInitialize_p) (EGLDisplay dpy, EGLint *major, EGLint *minor);
extern EGLBoolean (*eglChooseConfig_p) (EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config);
extern EGLBoolean (*eglGetConfigAttrib_p) (EGLDisplay dpy, EGLConfig config, EGLint attribute, EGLint *value);
extern EGLBoolean (*eglBindAPI_p) (EGLenum api);
extern EGLSurface (*eglCreatePbufferSurface_p) (EGLDisplay dpy, EGLConfig config, const EGLint *attrib_list);
extern EGLSurface (*eglCreateWindowSurface_p) (EGLDisplay dpy, EGLConfig config, NativeWindowType window, const EGLint *attrib_list);
extern EGLBoolean (*eglSwapBuffers_p) (EGLDisplay dpy, EGLSurface draw);
extern EGLint (*eglGetError_p) (void);
extern EGLContext (*eglCreateContext_p) (EGLDisplay dpy, EGLConfig config, EGLContext share_list, const EGLint *attrib_list);
extern EGLBoolean (*eglSwapInterval_p) (EGLDisplay dpy, EGLint interval);
extern EGLSurface (*eglGetCurrentSurface_p) (EGLint readdraw);
void dlsym_EGL();
#endif //POJAVLAUNCHER_EGL_LOADER_H

View File

@ -0,0 +1,173 @@
#include <EGL/egl.h>
#include <android/log.h>
#include <android/native_window.h>
#include <android/native_window_jni.h>
#include <string.h>
#include <malloc.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <stdbool.h>
#include "pojav/environ/environ.h"
#include "gl_bridge.h"
#include "egl_loader.h"
//
// Created by maks on 17.09.2022.
//
static const char* g_LogTag = "GLBridge";
static __thread gl_render_window_t* currentBundle;
static EGLDisplay g_EglDisplay;
bool gl_init() {
dlsym_EGL();
g_EglDisplay = eglGetDisplay_p(EGL_DEFAULT_DISPLAY);
if (g_EglDisplay == EGL_NO_DISPLAY) {
__android_log_print(ANDROID_LOG_ERROR, g_LogTag, "%s",
"eglGetDisplay_p(EGL_DEFAULT_DISPLAY) returned EGL_NO_DISPLAY");
return false;
}
if (eglInitialize_p(g_EglDisplay, 0, 0) != EGL_TRUE) {
__android_log_print(ANDROID_LOG_ERROR, g_LogTag, "eglInitialize_p() failed: %04x",
eglGetError_p());
return false;
}
return true;
}
gl_render_window_t* gl_get_current() {
return currentBundle;
}
gl_render_window_t* gl_init_context(gl_render_window_t *share) {
gl_render_window_t* bundle = malloc(sizeof(gl_render_window_t));
memset(bundle, 0, sizeof(gl_render_window_t));
EGLint egl_attributes[] = { EGL_BLUE_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_RED_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_DEPTH_SIZE, 24, EGL_SURFACE_TYPE, EGL_WINDOW_BIT|EGL_PBUFFER_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_NONE };
EGLint num_configs = 0;
if (eglChooseConfig_p(g_EglDisplay, egl_attributes, NULL, 0, &num_configs) != EGL_TRUE) {
__android_log_print(ANDROID_LOG_ERROR, g_LogTag, "eglChooseConfig_p() failed: %04x",
eglGetError_p());
free(bundle);
return NULL;
}
if (num_configs == 0) {
__android_log_print(ANDROID_LOG_ERROR, g_LogTag, "%s",
"eglChooseConfig_p() found no matching config");
free(bundle);
return NULL;
}
// Get the first matching config
eglChooseConfig_p(g_EglDisplay, egl_attributes, &bundle->config, 1, &num_configs);
eglGetConfigAttrib_p(g_EglDisplay, bundle->config, EGL_NATIVE_VISUAL_ID, &bundle->format);
{
EGLBoolean bindResult;
if (strncmp(getenv("POJAV_RENDERER"), "opengles3_desktopgl", 19) == 0) {
printf("EGLBridge: Binding to desktop OpenGL\n");
bindResult = eglBindAPI_p(EGL_OPENGL_API);
} else {
printf("EGLBridge: Binding to OpenGL ES\n");
bindResult = eglBindAPI_p(EGL_OPENGL_ES_API);
}
if (!bindResult) printf("EGLBridge: bind failed: %p\n", eglGetError_p());
}
int libgl_es = strtol(getenv("LIBGL_ES"), NULL, 0);
if(libgl_es < 0 || libgl_es > INT16_MAX) libgl_es = 2;
const EGLint egl_context_attributes[] = { EGL_CONTEXT_CLIENT_VERSION, libgl_es, EGL_NONE };
bundle->context = eglCreateContext_p(g_EglDisplay, bundle->config, share == NULL ? EGL_NO_CONTEXT : share->context, egl_context_attributes);
if (bundle->context == EGL_NO_CONTEXT) {
__android_log_print(ANDROID_LOG_ERROR, g_LogTag, "eglCreateContext_p() finished with error: %04x",
eglGetError_p());
free(bundle);
return NULL;
}
return bundle;
}
void gl_swap_surface(gl_render_window_t* bundle) {
if(bundle->nativeSurface != NULL) {
ANativeWindow_release(bundle->nativeSurface);
}
if(bundle->surface != NULL) eglDestroySurface_p(g_EglDisplay, bundle->surface);
if(bundle->newNativeSurface != NULL) {
__android_log_print(ANDROID_LOG_ERROR, g_LogTag, "Switching to new native surface");
bundle->nativeSurface = bundle->newNativeSurface;
bundle->newNativeSurface = NULL;
ANativeWindow_acquire(bundle->nativeSurface);
ANativeWindow_setBuffersGeometry(bundle->nativeSurface, 0, 0, bundle->format);
bundle->surface = eglCreateWindowSurface_p(g_EglDisplay, bundle->config, bundle->nativeSurface, NULL);
}else{
__android_log_print(ANDROID_LOG_ERROR, g_LogTag, "No new native surface, switching to 1x1 pbuffer");
bundle->nativeSurface = NULL;
const EGLint pbuffer_attrs[] = {EGL_WIDTH, 1 , EGL_HEIGHT, 1, EGL_NONE};
bundle->surface = eglCreatePbufferSurface_p(g_EglDisplay, bundle->config, pbuffer_attrs);
}
//eglMakeCurrent_p(g_EglDisplay, bundle->surface, bundle->surface, bundle->context);
}
void gl_make_current(gl_render_window_t* bundle) {
if(bundle == NULL) {
if(eglMakeCurrent_p(g_EglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) {
currentBundle = NULL;
}
return;
}
bool hasSetMainWindow = false;
if(pojav_environ->mainWindowBundle == NULL) {
pojav_environ->mainWindowBundle = (basic_render_window_t*)bundle;
__android_log_print(ANDROID_LOG_INFO, g_LogTag, "Main window bundle is now %p", pojav_environ->mainWindowBundle);
pojav_environ->mainWindowBundle->newNativeSurface = pojav_environ->pojavWindow;
hasSetMainWindow = true;
}
__android_log_print(ANDROID_LOG_INFO, g_LogTag, "Making current, surface=%p, nativeSurface=%p, newNativeSurface=%p", bundle->surface, bundle->nativeSurface, bundle->newNativeSurface);
if(bundle->surface == NULL) { //it likely will be on the first run
gl_swap_surface(bundle);
}
if(eglMakeCurrent_p(g_EglDisplay, bundle->surface, bundle->surface, bundle->context)) {
currentBundle = bundle;
}else {
if(hasSetMainWindow) {
pojav_environ->mainWindowBundle->newNativeSurface = NULL;
gl_swap_surface((gl_render_window_t*)pojav_environ->mainWindowBundle);
pojav_environ->mainWindowBundle = NULL;
}
__android_log_print(ANDROID_LOG_ERROR, g_LogTag, "eglMakeCurrent returned with error: %04x", eglGetError_p());
}
}
void gl_swap_buffers() {
if(currentBundle->state == STATE_RENDERER_NEW_WINDOW) {
eglMakeCurrent_p(g_EglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); //detach everything to destroy the old EGLSurface
gl_swap_surface(currentBundle);
eglMakeCurrent_p(g_EglDisplay, currentBundle->surface, currentBundle->surface, currentBundle->context);
currentBundle->state = STATE_RENDERER_ALIVE;
}
if(currentBundle->surface != NULL)
if(!eglSwapBuffers_p(g_EglDisplay, currentBundle->surface) && eglGetError_p() == EGL_BAD_SURFACE) {
eglMakeCurrent_p(g_EglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
currentBundle->newNativeSurface = NULL;
gl_swap_surface(currentBundle);
eglMakeCurrent_p(g_EglDisplay, currentBundle->surface, currentBundle->surface, currentBundle->context);
__android_log_print(ANDROID_LOG_INFO, g_LogTag, "The window has died, awaiting window change");
}
}
void gl_setup_window() {
if(pojav_environ->mainWindowBundle != NULL) {
__android_log_print(ANDROID_LOG_INFO, g_LogTag, "Main window bundle is not NULL, changing state");
pojav_environ->mainWindowBundle->state = STATE_RENDERER_NEW_WINDOW;
pojav_environ->mainWindowBundle->newNativeSurface = pojav_environ->pojavWindow;
}
}
void gl_swap_interval(int swapInterval) {
if(pojav_environ->force_vsync) swapInterval = 1;
eglSwapInterval_p(g_EglDisplay, swapInterval);
}

View File

@ -0,0 +1,28 @@
//
// Created by maks on 17.09.2022.
//
#include <EGL//egl.h>
#include <stdbool.h>
#ifndef POJAVLAUNCHER_GL_BRIDGE_H
#define POJAVLAUNCHER_GL_BRIDGE_H
typedef struct {
char state;
struct ANativeWindow *nativeSurface;
struct ANativeWindow *newNativeSurface;
EGLConfig config;
EGLint format;
EGLContext context;
EGLSurface surface;
} gl_render_window_t;
bool gl_init();
gl_render_window_t* gl_get_current();
gl_render_window_t* gl_init_context(gl_render_window_t* share);
void gl_make_current(gl_render_window_t* bundle);
void gl_swap_buffers();
void gl_setup_window();
void gl_swap_interval(int swapInterval);
#endif //POJAVLAUNCHER_GL_BRIDGE_H

View File

@ -0,0 +1,143 @@
//
// Created by maks on 18.10.2023.
//
#include <malloc.h>
#include <string.h>
#include "pojav/environ/environ.h"
#include <android/log.h>
#include "osm_bridge.h"
static const char* g_LogTag = "GLBridge";
static __thread osm_render_window_t* currentBundle;
// a tiny buffer for rendering when there's nowhere t render
static char no_render_buffer[4];
// Its not in a .h file because it is not supposed to be used outsife of this file.
void setNativeWindowSwapInterval(struct ANativeWindow* nativeWindow, int swapInterval);
bool osm_init() {
dlsym_OSMesa();
return true; // no more specific initialization required
}
osm_render_window_t* osm_get_current() {
return currentBundle;
}
osm_render_window_t* osm_init_context(osm_render_window_t* share) {
osm_render_window_t* render_window = malloc(sizeof(osm_render_window_t));
if(render_window == NULL) return NULL;
memset(render_window, 0, sizeof(osm_render_window_t));
OSMesaContext osmesa_share = NULL;
if(share != NULL) osmesa_share = share->context;
OSMesaContext context = OSMesaCreateContext_p(GL_RGBA, osmesa_share);
if(context == NULL) {
free(render_window);
return NULL;
}
render_window->context = context;
return render_window;
}
void osm_set_no_render_buffer(ANativeWindow_Buffer* buffer) {
buffer->bits = &no_render_buffer;
buffer->width = 1;
buffer->height = 1;
buffer->stride = 0;
}
void osm_swap_surfaces(osm_render_window_t* bundle) {
if(bundle->nativeSurface != NULL && bundle->newNativeSurface != bundle->nativeSurface) {
if(!bundle->disable_rendering) {
__android_log_print(ANDROID_LOG_INFO, g_LogTag, "Unlocking for cleanup...");
ANativeWindow_unlockAndPost(bundle->nativeSurface);
}
ANativeWindow_release(bundle->nativeSurface);
}
if(bundle->newNativeSurface != NULL) {
__android_log_print(ANDROID_LOG_ERROR, g_LogTag, "Switching to new native surface");
bundle->nativeSurface = bundle->newNativeSurface;
bundle->newNativeSurface = NULL;
ANativeWindow_acquire(bundle->nativeSurface);
ANativeWindow_setBuffersGeometry(bundle->nativeSurface, 0, 0, WINDOW_FORMAT_RGBX_8888);
bundle->disable_rendering = false;
return;
}else {
__android_log_print(ANDROID_LOG_ERROR, g_LogTag,
"No new native surface, switching to dummy framebuffer");
bundle->nativeSurface = NULL;
osm_set_no_render_buffer(&bundle->buffer);
bundle->disable_rendering = true;
}
}
void osm_release_window() {
currentBundle->newNativeSurface = NULL;
osm_swap_surfaces(currentBundle);
}
void osm_apply_current_ll() {
ANativeWindow_Buffer* buffer = &currentBundle->buffer;
OSMesaMakeCurrent_p(currentBundle->context, buffer->bits, GL_UNSIGNED_BYTE, buffer->width, buffer->height);
if(buffer->stride != currentBundle->last_stride)
OSMesaPixelStore_p(OSMESA_ROW_LENGTH, buffer->stride);
currentBundle->last_stride = buffer->stride;
}
void osm_make_current(osm_render_window_t* bundle) {
if(bundle == NULL) {
//technically this does nothing as its not possible to unbind a context in OSMesa
OSMesaMakeCurrent_p(NULL, NULL, 0, 0, 0);
currentBundle = NULL;
return;
}
bool hasSetMainWindow = false;
currentBundle = bundle;
if(pojav_environ->mainWindowBundle == NULL) {
pojav_environ->mainWindowBundle = (basic_render_window_t*) bundle;
__android_log_print(ANDROID_LOG_INFO, g_LogTag, "Main window bundle is now %p", pojav_environ->mainWindowBundle);
pojav_environ->mainWindowBundle->newNativeSurface = pojav_environ->pojavWindow;
hasSetMainWindow = true;
}
if(bundle->nativeSurface == NULL) {
//prepare the buffer for our first render!
osm_swap_surfaces(bundle);
if(hasSetMainWindow) pojav_environ->mainWindowBundle->state = STATE_RENDERER_ALIVE;
}
osm_set_no_render_buffer(&bundle->buffer);
osm_apply_current_ll();
OSMesaPixelStore_p(OSMESA_Y_UP,0);
}
void osm_swap_buffers() {
if(currentBundle->state == STATE_RENDERER_NEW_WINDOW) {
osm_swap_surfaces(currentBundle);
currentBundle->state = STATE_RENDERER_ALIVE;
}
if(currentBundle->nativeSurface != NULL && !currentBundle->disable_rendering)
if(ANativeWindow_lock(currentBundle->nativeSurface, &currentBundle->buffer, NULL) != 0)
osm_release_window();
osm_apply_current_ll();
glFinish_p(); // this will force osmesa to write the last rendered image into the buffer
if(currentBundle->nativeSurface != NULL && !currentBundle->disable_rendering)
if(ANativeWindow_unlockAndPost(currentBundle->nativeSurface) != 0)
osm_release_window();
}
void osm_setup_window() {
if(pojav_environ->mainWindowBundle != NULL) {
__android_log_print(ANDROID_LOG_INFO, g_LogTag, "Main window bundle is not NULL, changing state");
pojav_environ->mainWindowBundle->state = STATE_RENDERER_NEW_WINDOW;
pojav_environ->mainWindowBundle->newNativeSurface = pojav_environ->pojavWindow;
}
}
void osm_swap_interval(int swapInterval) {
if(pojav_environ->mainWindowBundle != NULL && pojav_environ->mainWindowBundle->nativeSurface != NULL) {
setNativeWindowSwapInterval(pojav_environ->mainWindowBundle->nativeSurface, swapInterval);
}
}

View File

@ -0,0 +1,29 @@
//
// Created by maks on 18.10.2023.
//
#include <android/native_window.h>
#include <stdbool.h>
#ifndef POJAVLAUNCHER_OSM_BRIDGE_H
#define POJAVLAUNCHER_OSM_BRIDGE_H
#include "osmesa_loader.h"
typedef struct {
char state;
struct ANativeWindow *nativeSurface;
struct ANativeWindow *newNativeSurface;
ANativeWindow_Buffer buffer;
int32_t last_stride;
bool disable_rendering;
OSMesaContext context;
} osm_render_window_t;
bool osm_init();
osm_render_window_t* osm_get_current();
osm_render_window_t* osm_init_context(osm_render_window_t* share);
void osm_make_current(osm_render_window_t* bundle);
void osm_swap_buffers();
void osm_setup_window();
void osm_swap_interval(int swapInterval);
#endif //POJAVLAUNCHER_OSM_BRIDGE_H

View File

@ -0,0 +1,42 @@
//
// Created by maks on 21.09.2022.
//
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include "osmesa_loader.h"
GLboolean (*OSMesaMakeCurrent_p) (OSMesaContext ctx, void *buffer, GLenum type,
GLsizei width, GLsizei height);
OSMesaContext (*OSMesaGetCurrentContext_p) (void);
OSMesaContext (*OSMesaCreateContext_p) (GLenum format, OSMesaContext sharelist);
void (*OSMesaDestroyContext_p) (OSMesaContext ctx);
void (*OSMesaPixelStore_p) ( GLint pname, GLint value );
GLubyte* (*glGetString_p) (GLenum name);
void (*glFinish_p) (void);
void (*glClearColor_p) (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha);
void (*glClear_p) (GLbitfield mask);
void (*glReadPixels_p) (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void * data);
void dlsym_OSMesa() {
char* main_path = NULL;
char* alt_path = NULL;
if(asprintf(&main_path, "%s/libOSMesa_8.so", getenv("POJAV_NATIVEDIR")) == -1 ||
asprintf(&alt_path, "%s/libOSMesa.so.8", getenv("POJAV_NATIVEDIR")) == -1) {
abort();
}
void* dl_handle = NULL;
dl_handle = dlopen(alt_path, RTLD_GLOBAL);
if(dl_handle == NULL) dl_handle = dlopen(main_path, RTLD_GLOBAL);
if(dl_handle == NULL) abort();
OSMesaMakeCurrent_p = dlsym(dl_handle, "OSMesaMakeCurrent");
OSMesaGetCurrentContext_p = dlsym(dl_handle,"OSMesaGetCurrentContext");
OSMesaCreateContext_p = dlsym(dl_handle, "OSMesaCreateContext");
OSMesaDestroyContext_p = dlsym(dl_handle, "OSMesaDestroyContext");
OSMesaPixelStore_p = dlsym(dl_handle,"OSMesaPixelStore");
glGetString_p = dlsym(dl_handle,"glGetString");
glClearColor_p = dlsym(dl_handle, "glClearColor");
glClear_p = dlsym(dl_handle,"glClear");
glFinish_p = dlsym(dl_handle,"glFinish");
glReadPixels_p = dlsym(dl_handle,"glReadPixels");
}

View File

@ -0,0 +1,22 @@
//
// Created by maks on 21.09.2022.
//
#ifndef POJAVLAUNCHER_OSMESA_LOADER_H
#define POJAVLAUNCHER_OSMESA_LOADER_H
#include "pojav/GL/osmesa.h"
extern GLboolean (*OSMesaMakeCurrent_p) (OSMesaContext ctx, void *buffer, GLenum type,
GLsizei width, GLsizei height);
extern OSMesaContext (*OSMesaGetCurrentContext_p) (void);
extern OSMesaContext (*OSMesaCreateContext_p) (GLenum format, OSMesaContext sharelist);
extern void (*OSMesaDestroyContext_p) (OSMesaContext ctx);
extern void (*OSMesaPixelStore_p) ( GLint pname, GLint value );
extern GLubyte* (*glGetString_p) (GLenum name);
extern void (*glFinish_p) (void);
extern void (*glClearColor_p) (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha);
extern void (*glClear_p) (GLbitfield mask);
extern void (*glReadPixels_p) (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void * data);
void dlsym_OSMesa();
#endif //POJAVLAUNCHER_OSMESA_LOADER_H

View File

@ -0,0 +1,243 @@
//
// Created by maks on 08.11.2023.
//
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <android/log.h>
#include <android/native_window.h>
// Taken from https://android.googlesource.com/platform/frameworks/native/+/41abd67/include/ui/egl/android_natives.h
// Might be outdated, if you can find a more recent version please add it there
// region android_native_base_t definition
typedef struct android_native_base_t
{
/* a magic value defined by the actual EGL native type */
int magic;
/* the sizeof() of the actual EGL native type */
int version;
void* reserved[4];
/* reference-counting interface */
void (*incRef)(struct android_native_base_t* base);
void (*decRef)(struct android_native_base_t* base);
} android_native_base_t;
// endregion
// region ANativeWindow magic definition
#define ANDROID_NATIVE_MAKE_CONSTANT(a,b,c,d) \
(((unsigned)(a)<<24)|((unsigned)(b)<<16)|((unsigned)(c)<<8)|(unsigned)(d))
#define ANDROID_NATIVE_WINDOW_MAGIC \
ANDROID_NATIVE_MAKE_CONSTANT('_','w','n','d')
// endregion
struct ANativeWindowBuffer; // opaque, actually an internal system struct but we don't use it
// Taken from https://android.googlesource.com/platform/frameworks/native/+/refs/heads/main/libs/nativewindow/include/system/window.h
// region ANativeWindow struct definition
struct ANativeWindow_real
{
struct android_native_base_t common;
/* flags describing some attributes of this surface or its updater */
const uint32_t flags;
/* min swap interval supported by this updated */
const int minSwapInterval;
/* max swap interval supported by this updated */
const int maxSwapInterval;
/* horizontal and vertical resolution in DPI */
const float xdpi;
const float ydpi;
/* Some storage reserved for the OEM's driver. */
intptr_t oem[4];
/*
* Set the swap interval for this surface.
*
* Returns 0 on success or -errno on error.
*/
int (*setSwapInterval)(struct ANativeWindow* window,
int interval);
/*
* Hook called by EGL to acquire a buffer. After this call, the buffer
* is not locked, so its content cannot be modified. This call may block if
* no buffers are available.
*
* The window holds a reference to the buffer between dequeueBuffer and
* either queueBuffer or cancelBuffer, so clients only need their own
* reference if they might use the buffer after queueing or canceling it.
* Holding a reference to a buffer after queueing or canceling it is only
* allowed if a specific buffer count has been set.
*
* Returns 0 on success or -errno on error.
*
* XXX: This function is deprecated. It will continue to work for some
* time for binary compatibility, but the new dequeueBuffer function that
* outputs a fence file descriptor should be used in its place.
*/
int (*dequeueBuffer_DEPRECATED)(struct ANativeWindow* window,
struct ANativeWindowBuffer** buffer);
/*
* hook called by EGL to lock a buffer. This MUST be called before modifying
* the content of a buffer. The buffer must have been acquired with
* dequeueBuffer first.
*
* Returns 0 on success or -errno on error.
*
* XXX: This function is deprecated. It will continue to work for some
* time for binary compatibility, but it is essentially a no-op, and calls
* to it should be removed.
*/
int (*lockBuffer_DEPRECATED)(struct ANativeWindow* window,
struct ANativeWindowBuffer* buffer);
/*
* Hook called by EGL when modifications to the render buffer are done.
* This unlocks and post the buffer.
*
* The window holds a reference to the buffer between dequeueBuffer and
* either queueBuffer or cancelBuffer, so clients only need their own
* reference if they might use the buffer after queueing or canceling it.
* Holding a reference to a buffer after queueing or canceling it is only
* allowed if a specific buffer count has been set.
*
* Buffers MUST be queued in the same order than they were dequeued.
*
* Returns 0 on success or -errno on error.
*
* XXX: This function is deprecated. It will continue to work for some
* time for binary compatibility, but the new queueBuffer function that
* takes a fence file descriptor should be used in its place (pass a value
* of -1 for the fence file descriptor if there is no valid one to pass).
*/
int (*queueBuffer_DEPRECATED)(struct ANativeWindow* window,
struct ANativeWindowBuffer* buffer);
/*
* hook used to retrieve information about the native window.
*
* Returns 0 on success or -errno on error.
*/
int (*query)(const struct ANativeWindow* window,
int what, int* value);
/*
* hook used to perform various operations on the surface.
* (*perform)() is a generic mechanism to add functionality to
* ANativeWindow while keeping backward binary compatibility.
*
* DO NOT CALL THIS HOOK DIRECTLY. Instead, use the helper functions
* defined below.
*
* (*perform)() returns -ENOENT if the 'what' parameter is not supported
* by the surface's implementation.
*
* See above for a list of valid operations, such as
* NATIVE_WINDOW_SET_USAGE or NATIVE_WINDOW_CONNECT
*/
int (*perform)(struct ANativeWindow* window,
int operation, ... );
/*
* Hook used to cancel a buffer that has been dequeued.
* No synchronization is performed between dequeue() and cancel(), so
* either external synchronization is needed, or these functions must be
* called from the same thread.
*
* The window holds a reference to the buffer between dequeueBuffer and
* either queueBuffer or cancelBuffer, so clients only need their own
* reference if they might use the buffer after queueing or canceling it.
* Holding a reference to a buffer after queueing or canceling it is only
* allowed if a specific buffer count has been set.
*
* XXX: This function is deprecated. It will continue to work for some
* time for binary compatibility, but the new cancelBuffer function that
* takes a fence file descriptor should be used in its place (pass a value
* of -1 for the fence file descriptor if there is no valid one to pass).
*/
int (*cancelBuffer_DEPRECATED)(struct ANativeWindow* window,
struct ANativeWindowBuffer* buffer);
/*
* Hook called by EGL to acquire a buffer. This call may block if no
* buffers are available.
*
* The window holds a reference to the buffer between dequeueBuffer and
* either queueBuffer or cancelBuffer, so clients only need their own
* reference if they might use the buffer after queueing or canceling it.
* Holding a reference to a buffer after queueing or canceling it is only
* allowed if a specific buffer count has been set.
*
* The libsync fence file descriptor returned in the int pointed to by the
* fenceFd argument will refer to the fence that must signal before the
* dequeued buffer may be written to. A value of -1 indicates that the
* caller may access the buffer immediately without waiting on a fence. If
* a valid file descriptor is returned (i.e. any value except -1) then the
* caller is responsible for closing the file descriptor.
*
* Returns 0 on success or -errno on error.
*/
int (*dequeueBuffer)(struct ANativeWindow* window,
struct ANativeWindowBuffer** buffer, int* fenceFd);
/*
* Hook called by EGL when modifications to the render buffer are done.
* This unlocks and post the buffer.
*
* The window holds a reference to the buffer between dequeueBuffer and
* either queueBuffer or cancelBuffer, so clients only need their own
* reference if they might use the buffer after queueing or canceling it.
* Holding a reference to a buffer after queueing or canceling it is only
* allowed if a specific buffer count has been set.
*
* The fenceFd argument specifies a libsync fence file descriptor for a
* fence that must signal before the buffer can be accessed. If the buffer
* can be accessed immediately then a value of -1 should be used. The
* caller must not use the file descriptor after it is passed to
* queueBuffer, and the ANativeWindow implementation is responsible for
* closing it.
*
* Returns 0 on success or -errno on error.
*/
int (*queueBuffer)(struct ANativeWindow* window,
struct ANativeWindowBuffer* buffer, int fenceFd);
/*
* Hook used to cancel a buffer that has been dequeued.
* No synchronization is performed between dequeue() and cancel(), so
* either external synchronization is needed, or these functions must be
* called from the same thread.
*
* The window holds a reference to the buffer between dequeueBuffer and
* either queueBuffer or cancelBuffer, so clients only need their own
* reference if they might use the buffer after queueing or canceling it.
* Holding a reference to a buffer after queueing or canceling it is only
* allowed if a specific buffer count has been set.
*
* The fenceFd argument specifies a libsync fence file decsriptor for a
* fence that must signal before the buffer can be accessed. If the buffer
* can be accessed immediately then a value of -1 should be used.
*
* Note that if the client has not waited on the fence that was returned
* from dequeueBuffer, that same fence should be passed to cancelBuffer to
* ensure that future uses of the buffer are preceded by a wait on that
* fence. The caller must not use the file descriptor after it is passed
* to cancelBuffer, and the ANativeWindow implementation is responsible for
* closing it.
*
* Returns 0 on success or -errno on error.
*/
int (*cancelBuffer)(struct ANativeWindow* window,
struct ANativeWindowBuffer* buffer, int fenceFd);
};
// endregion
void setNativeWindowSwapInterval(struct ANativeWindow* nativeWindow, int swapInterval) {
if(!getenv("POJAV_VSYNC_IN_ZINK")) {
return;
}
struct ANativeWindow_real* nativeWindowReal = (struct ANativeWindow_real*) nativeWindow;
if(nativeWindowReal->common.magic != ANDROID_NATIVE_WINDOW_MAGIC) {
__android_log_print(ANDROID_LOG_WARN, "SwapIntervalNoEGL", "ANativeWindow magic does not match. Expected %i, got %i",
ANDROID_NATIVE_WINDOW_MAGIC, nativeWindowReal->common.magic);
return;
}
if(nativeWindowReal->common.version != sizeof(struct ANativeWindow_real)) {
__android_log_print(ANDROID_LOG_WARN, "SwapIntervalNoEGL", "ANativeWindow version does not match. Expected %i, got %i",
sizeof(struct ANativeWindow_real), nativeWindowReal->common.version);
return;
}
int error;
if((error = nativeWindowReal->setSwapInterval(nativeWindow, swapInterval)) != 0) {
__android_log_print(ANDROID_LOG_WARN, "SwapIntervalNoEGL", "Failed to set swap interval: %s", strerror(-error));
}
}

View File

@ -0,0 +1,270 @@
#include <jni.h>
#include <assert.h>
#include <dlfcn.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <EGL/egl.h>
#include "pojav/GL/osmesa.h"
#include "ctxbridges/osmesa_loader.h"
#include "driver_helper/nsbypass.h"
#ifdef GLES_TEST
#include <GLES2/gl2.h>
#endif
#include <android/native_window.h>
#include <android/native_window_jni.h>
#include <android/rect.h>
#include <string.h>
#include "pojav/environ/environ.h"
#include <android/dlext.h>
#include "ctxbridges/bridge_tbl.h"
#include "ctxbridges/osm_bridge.h"
#define GLFW_CLIENT_API 0x22001
/* Consider GLFW_NO_API as Vulkan API */
#define GLFW_NO_API 0
#define GLFW_OPENGL_API 0x30001
// This means that the function is an external API and that it will be used
#define EXTERNAL_API __attribute__((used))
// This means that you are forced to have this function/variable for ABI compatibility
#define ABI_COMPAT __attribute__((unused))
struct PotatoBridge {
/* EGLContext */ void* eglContext;
/* EGLDisplay */ void* eglDisplay;
/* EGLSurface */ void* eglSurface;
/*
void* eglSurfaceRead;
void* eglSurfaceDraw;
*/
};
EGLConfig config;
struct PotatoBridge potatoBridge;
#include "ctxbridges/egl_loader.h"
#include "ctxbridges/osmesa_loader.h"
#define RENDERER_GL4ES 1
#define RENDERER_VK_ZINK 2
#define RENDERER_VULKAN 4
EXTERNAL_API void pojavTerminate() {
printf("EGLBridge: Terminating\n");
switch (pojav_environ->config_renderer) {
case RENDERER_GL4ES: {
eglMakeCurrent_p(potatoBridge.eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglDestroySurface_p(potatoBridge.eglDisplay, potatoBridge.eglSurface);
eglDestroyContext_p(potatoBridge.eglDisplay, potatoBridge.eglContext);
eglTerminate_p(potatoBridge.eglDisplay);
eglReleaseThread_p();
potatoBridge.eglContext = EGL_NO_CONTEXT;
potatoBridge.eglDisplay = EGL_NO_DISPLAY;
potatoBridge.eglSurface = EGL_NO_SURFACE;
} break;
//case RENDERER_VIRGL:
case RENDERER_VK_ZINK: {
// Nothing to do here
} break;
}
}
JNIEXPORT void JNICALL Java_net_kdt_pojavlaunch_utils_JREUtils_setupBridgeWindow(JNIEnv* env, ABI_COMPAT jclass clazz, jobject surface) {
pojav_environ->pojavWindow = ANativeWindow_fromSurface(env, surface);
if(br_setup_window != NULL) br_setup_window();
}
JNIEXPORT void JNICALL
Java_net_kdt_pojavlaunch_utils_JREUtils_releaseBridgeWindow(ABI_COMPAT JNIEnv *env, ABI_COMPAT jclass clazz) {
ANativeWindow_release(pojav_environ->pojavWindow);
}
EXTERNAL_API void* pojavGetCurrentContext() {
return br_get_current();
}
//#define ADRENO_POSSIBLE
#ifdef ADRENO_POSSIBLE
//Checks if your graphics are Adreno. Returns true if your graphics are Adreno, false otherwise or if there was an error
bool checkAdrenoGraphics() {
EGLDisplay eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if(eglDisplay == EGL_NO_DISPLAY || eglInitialize(eglDisplay, NULL, NULL) != EGL_TRUE) return false;
EGLint egl_attributes[] = { EGL_BLUE_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_RED_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_DEPTH_SIZE, 24, EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_NONE };
EGLint num_configs = 0;
if(eglChooseConfig(eglDisplay, egl_attributes, NULL, 0, &num_configs) != EGL_TRUE || num_configs == 0) {
eglTerminate(eglDisplay);
return false;
}
EGLConfig eglConfig;
eglChooseConfig(eglDisplay, egl_attributes, &eglConfig, 1, &num_configs);
const EGLint egl_context_attributes[] = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE };
EGLContext context = eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, egl_context_attributes);
if(context == EGL_NO_CONTEXT) {
eglTerminate(eglDisplay);
return false;
}
if(eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, context) != EGL_TRUE) {
eglDestroyContext(eglDisplay, context);
eglTerminate(eglDisplay);
}
const char* vendor = glGetString(GL_VENDOR);
const char* renderer = glGetString(GL_RENDERER);
bool is_adreno = false;
if(strcmp(vendor, "Qualcomm") == 0 && strstr(renderer, "Adreno") != NULL) {
is_adreno = true; // TODO: check for Turnip support
}
eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglDestroyContext(eglDisplay, context);
eglTerminate(eglDisplay);
return is_adreno;
}
void* load_turnip_vulkan() {
if(!checkAdrenoGraphics()) return NULL;
const char* native_dir = getenv("POJAV_NATIVEDIR");
const char* cache_dir = getenv("TMPDIR");
if(!linker_ns_load(native_dir)) return NULL;
void* linkerhook = linker_ns_dlopen("liblinkerhook.so", RTLD_LOCAL | RTLD_NOW);
if(linkerhook == NULL) return NULL;
void* turnip_driver_handle = linker_ns_dlopen("libvulkan_freedreno.so", RTLD_LOCAL | RTLD_NOW);
if(turnip_driver_handle == NULL) {
printf("AdrenoSupp: Failed to load Turnip!\n%s\n", dlerror());
dlclose(linkerhook);
return NULL;
}
void* dl_android = linker_ns_dlopen("libdl_android.so", RTLD_LOCAL | RTLD_LAZY);
if(dl_android == NULL) {
dlclose(linkerhook);
dlclose(turnip_driver_handle);
return NULL;
}
void* android_get_exported_namespace = dlsym(dl_android, "android_get_exported_namespace");
void (*linkerhook_pass_handles)(void*, void*, void*) = dlsym(linkerhook, "app__pojav_linkerhook_pass_handles");
if(linkerhook_pass_handles == NULL || android_get_exported_namespace == NULL) {
dlclose(dl_android);
dlclose(linkerhook);
dlclose(turnip_driver_handle);
return NULL;
}
linkerhook_pass_handles(turnip_driver_handle, android_dlopen_ext, android_get_exported_namespace);
void* libvulkan = linker_ns_dlopen_unique(cache_dir, "libvulkan.so", RTLD_LOCAL | RTLD_NOW);
return libvulkan;
}
#endif
static void set_vulkan_ptr(void* ptr) {
char envval[64];
sprintf(envval, "%"PRIxPTR, (uintptr_t)ptr);
setenv("VULKAN_PTR", envval, 1);
}
void load_vulkan() {
if(getenv("POJAV_ZINK_PREFER_SYSTEM_DRIVER") == NULL &&
android_get_device_api_level() >= 28) { // the loader does not support below that
#ifdef ADRENO_POSSIBLE
void* result = load_turnip_vulkan();
if(result != NULL) {
printf("AdrenoSupp: Loaded Turnip, loader address: %p\n", result);
set_vulkan_ptr(result);
return;
}
#endif
}
printf("OSMDroid: loading vulkan regularly...\n");
void* vulkan_ptr = dlopen("libvulkan.so", RTLD_LAZY | RTLD_LOCAL);
printf("OSMDroid: loaded vulkan, ptr=%p\n", vulkan_ptr);
set_vulkan_ptr(vulkan_ptr);
}
int pojavInitOpenGL() {
// Only affects GL4ES as of now
const char *forceVsync = getenv("FORCE_VSYNC");
if (strcmp(forceVsync, "true") == 0)
pojav_environ->force_vsync = true;
// NOTE: Override for now.
const char *renderer = getenv("POJAV_RENDERER");
if (strncmp("opengles", renderer, 8) == 0) {
pojav_environ->config_renderer = RENDERER_GL4ES;
set_gl_bridge_tbl();
} else if (strcmp(renderer, "vulkan_zink") == 0) {
pojav_environ->config_renderer = RENDERER_VK_ZINK;
load_vulkan();
setenv("GALLIUM_DRIVER","zink",1);
set_osm_bridge_tbl();
}
if(br_init()) {
br_setup_window();
}
return 0;
}
EXTERNAL_API int pojavInit() {
ANativeWindow_acquire(pojav_environ->pojavWindow);
pojav_environ->savedWidth = ANativeWindow_getWidth(pojav_environ->pojavWindow);
pojav_environ->savedHeight = ANativeWindow_getHeight(pojav_environ->pojavWindow);
ANativeWindow_setBuffersGeometry(pojav_environ->pojavWindow,pojav_environ->savedWidth,pojav_environ->savedHeight,AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM);
pojavInitOpenGL();
return 1;
}
EXTERNAL_API void pojavSetWindowHint(int hint, int value) {
if (hint != GLFW_CLIENT_API) return;
switch (value) {
case GLFW_NO_API:
pojav_environ->config_renderer = RENDERER_VULKAN;
/* Nothing to do: initialization is handled in Java-side */
// pojavInitVulkan();
break;
case GLFW_OPENGL_API:
/* Nothing to do: initialization is called in pojavCreateContext */
// pojavInitOpenGL();
break;
default:
printf("GLFW: Unimplemented API 0x%x\n", value);
abort();
}
}
EXTERNAL_API void pojavSwapBuffers() {
br_swap_buffers();
}
EXTERNAL_API void pojavMakeCurrent(void* window) {
br_make_current((basic_render_window_t*)window);
}
EXTERNAL_API void* pojavCreateContext(void* contextSrc) {
if (pojav_environ->config_renderer == RENDERER_VULKAN) {
return (void *) pojav_environ->pojavWindow;
}
return br_init_context((basic_render_window_t*)contextSrc);
}
EXTERNAL_API JNIEXPORT jlong JNICALL
Java_org_lwjgl_vulkan_VK_getVulkanDriverHandle(ABI_COMPAT JNIEnv *env, ABI_COMPAT jclass thiz) {
printf("EGLBridge: LWJGL-side Vulkan loader requested the Vulkan handle\n");
// The code below still uses the env var because
// 1. it's easier to do that
// 2. it won't break if something will try to load vulkan and osmesa simultaneously
if(getenv("VULKAN_PTR") == NULL) load_vulkan();
return strtoul(getenv("VULKAN_PTR"), NULL, 0x10);
}
EXTERNAL_API void pojavSwapInterval(int interval) {
br_swap_interval(interval);
}

View File

@ -0,0 +1,26 @@
//
// Created by maks on 24.09.2022.
//
#include <stdlib.h>
#include <android/log.h>
#include <assert.h>
#include <string.h>
#include "environ.h"
struct pojav_environ_s *pojav_environ;
__attribute__((constructor)) void env_init() {
char* strptr_env = getenv("POJAV_ENVIRON");
if(strptr_env == NULL) {
__android_log_print(ANDROID_LOG_INFO, "Environ", "No environ found, creating...");
pojav_environ = malloc(sizeof(struct pojav_environ_s));
assert(pojav_environ);
memset(pojav_environ, 0 , sizeof(struct pojav_environ_s));
if(asprintf(&strptr_env, "%p", pojav_environ) == -1) abort();
setenv("POJAV_ENVIRON", strptr_env, 1);
free(strptr_env);
}else{
__android_log_print(ANDROID_LOG_INFO, "Environ", "Found existing environ: %s", strptr_env);
pojav_environ = (void*) strtoul(strptr_env, NULL, 0x10);
}
__android_log_print(ANDROID_LOG_INFO, "Environ", "%p", pojav_environ);
}

View File

@ -0,0 +1,77 @@
//
// Created by maks on 24.09.2022.
//
#ifndef POJAVLAUNCHER_ENVIRON_H
#define POJAVLAUNCHER_ENVIRON_H
#include "pojav/ctxbridges/common.h"
#include <stdatomic.h>
#include <jni.h>
/* How many events can be handled at the same time */
#define EVENT_WINDOW_SIZE 8000
typedef struct {
int type;
int i1;
int i2;
int i3;
int i4;
} GLFWInputEvent;
typedef void GLFW_invoke_Char_func(void* window, unsigned int codepoint);
typedef void GLFW_invoke_CharMods_func(void* window, unsigned int codepoint, int mods);
typedef void GLFW_invoke_CursorEnter_func(void* window, int entered);
typedef void GLFW_invoke_CursorPos_func(void* window, double xpos, double ypos);
typedef void GLFW_invoke_FramebufferSize_func(void* window, int width, int height);
typedef void GLFW_invoke_Key_func(void* window, int key, int scancode, int action, int mods);
typedef void GLFW_invoke_MouseButton_func(void* window, int button, int action, int mods);
typedef void GLFW_invoke_Scroll_func(void* window, double xoffset, double yoffset);
typedef void GLFW_invoke_WindowSize_func(void* window, int width, int height);
struct pojav_environ_s {
struct ANativeWindow* pojavWindow;
basic_render_window_t* mainWindowBundle;
int config_renderer;
bool force_vsync;
atomic_size_t eventCounter; // Count the number of events to be pumped out
GLFWInputEvent events[EVENT_WINDOW_SIZE];
size_t outEventIndex; // Point to the current event that has yet to be pumped out to MC
size_t outTargetIndex; // Point to the newt index to stop by
size_t inEventIndex; // Point to the next event that has to be filled
size_t inEventCount; // Count registered right before pumping OUT events. Used as a cache.
double cursorX, cursorY, cLastX, cLastY;
jmethodID method_accessAndroidClipboard;
jmethodID method_onGrabStateChanged;
jmethodID method_glftSetWindowAttrib;
jmethodID method_internalWindowSizeChanged;
jclass bridgeClazz;
jclass vmGlfwClass;
jboolean isGrabbing;
jbyte* keyDownBuffer;
jbyte* mouseDownBuffer;
JavaVM* runtimeJavaVMPtr;
JNIEnv* runtimeJNIEnvPtr_JRE;
JavaVM* dalvikJavaVMPtr;
JNIEnv* dalvikJNIEnvPtr_ANDROID;
long showingWindow;
bool isInputReady, isCursorEntered, isUseStackQueueCall, isPumpingEvents;
int savedWidth, savedHeight;
#define ADD_CALLBACK_WWIN(NAME) \
GLFW_invoke_##NAME##_func* GLFW_invoke_##NAME;
ADD_CALLBACK_WWIN(Char);
ADD_CALLBACK_WWIN(CharMods);
ADD_CALLBACK_WWIN(CursorEnter);
ADD_CALLBACK_WWIN(CursorPos);
ADD_CALLBACK_WWIN(FramebufferSize);
ADD_CALLBACK_WWIN(Key);
ADD_CALLBACK_WWIN(MouseButton);
ADD_CALLBACK_WWIN(Scroll);
ADD_CALLBACK_WWIN(WindowSize);
#undef ADD_CALLBACK_WWIN
};
extern struct pojav_environ_s *pojav_environ;
#endif //POJAVLAUNCHER_ENVIRON_H

View File

@ -0,0 +1,638 @@
/*
* V3 input bridge implementation.
*
* Status:
* - Active development
* - Works with some bugs:
* + Modded versions gives broken stuff..
*
*
* - Implements glfwSetCursorPos() to handle grab camera pos correctly.
*/
#include <assert.h>
#include <dlfcn.h>
#include <jni.h>
#include <libgen.h>
#include <stdlib.h>
#include <string.h>
#include <stdatomic.h>
#include <math.h>
#include <android/log.h>
#include "environ/environ.h"
#include "fcl/include/fcl_internal.h"
#define EVENT_TYPE_CHAR 1000
#define EVENT_TYPE_CHAR_MODS 1001
#define EVENT_TYPE_CURSOR_ENTER 1002
#define EVENT_TYPE_FRAMEBUFFER_SIZE 1004
#define EVENT_TYPE_KEY 1005
#define EVENT_TYPE_MOUSE_BUTTON 1006
#define EVENT_TYPE_SCROLL 1007
#define EVENT_TYPE_WINDOW_SIZE 1008
jint (*orig_ProcessImpl_forkAndExec)(JNIEnv *env, jobject process, jint mode, jbyteArray helperpath, jbyteArray prog, jbyteArray argBlock, jint argc, jbyteArray envBlock, jint envc, jbyteArray dir, jintArray std_fds, jboolean redirectErrorStream);
static void registerFunctions(JNIEnv *env);
void hookExec();
void installLinkerBugMitigation();
void installEMUIIteratorMititgation();
JNIEXPORT jstring JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeClipboard(JNIEnv* env, jclass clazz, jint action, jbyteArray copySrc);
jint JNI_OnLoad(JavaVM* vm, __attribute__((unused)) void* reserved) {
if (pojav_environ->dalvikJavaVMPtr == NULL) {
__android_log_print(ANDROID_LOG_INFO, "Native", "Saving DVM environ...");
//Save dalvik global JavaVM pointer
pojav_environ->dalvikJavaVMPtr = vm;
(*vm)->GetEnv(vm, (void**) &pojav_environ->dalvikJNIEnvPtr_ANDROID, JNI_VERSION_1_4);
pojav_environ->bridgeClazz = (*pojav_environ->dalvikJNIEnvPtr_ANDROID)->NewGlobalRef(pojav_environ->dalvikJNIEnvPtr_ANDROID,(*pojav_environ->dalvikJNIEnvPtr_ANDROID) ->FindClass(pojav_environ->dalvikJNIEnvPtr_ANDROID,"org/lwjgl/glfw/CallbackBridge"));
pojav_environ->method_accessAndroidClipboard = (*pojav_environ->dalvikJNIEnvPtr_ANDROID)->GetStaticMethodID(pojav_environ->dalvikJNIEnvPtr_ANDROID, pojav_environ->bridgeClazz, "accessAndroidClipboard", "(ILjava/lang/String;)Ljava/lang/String;");
pojav_environ->method_onGrabStateChanged = (*pojav_environ->dalvikJNIEnvPtr_ANDROID)->GetStaticMethodID(pojav_environ->dalvikJNIEnvPtr_ANDROID, pojav_environ->bridgeClazz, "onGrabStateChanged", "(Z)V");
pojav_environ->isUseStackQueueCall = JNI_FALSE;
} else if (pojav_environ->dalvikJavaVMPtr != vm) {
__android_log_print(ANDROID_LOG_INFO, "Native", "Saving JVM environ...");
pojav_environ->runtimeJavaVMPtr = vm;
(*vm)->GetEnv(vm, (void**) &pojav_environ->runtimeJNIEnvPtr_JRE, JNI_VERSION_1_4);
pojav_environ->vmGlfwClass = (*pojav_environ->runtimeJNIEnvPtr_JRE)->NewGlobalRef(pojav_environ->runtimeJNIEnvPtr_JRE, (*pojav_environ->runtimeJNIEnvPtr_JRE)->FindClass(pojav_environ->runtimeJNIEnvPtr_JRE, "org/lwjgl/glfw/GLFW"));
pojav_environ->method_glftSetWindowAttrib = (*pojav_environ->runtimeJNIEnvPtr_JRE)->GetStaticMethodID(pojav_environ->runtimeJNIEnvPtr_JRE, pojav_environ->vmGlfwClass, "glfwSetWindowAttrib", "(JII)V");
pojav_environ->method_internalWindowSizeChanged = (*pojav_environ->runtimeJNIEnvPtr_JRE)->GetStaticMethodID(pojav_environ->runtimeJNIEnvPtr_JRE, pojav_environ->vmGlfwClass, "internalWindowSizeChanged", "(JII)V");
jfieldID field_keyDownBuffer = (*pojav_environ->runtimeJNIEnvPtr_JRE)->GetStaticFieldID(pojav_environ->runtimeJNIEnvPtr_JRE, pojav_environ->vmGlfwClass, "keyDownBuffer", "Ljava/nio/ByteBuffer;");
jobject keyDownBufferJ = (*pojav_environ->runtimeJNIEnvPtr_JRE)->GetStaticObjectField(pojav_environ->runtimeJNIEnvPtr_JRE, pojav_environ->vmGlfwClass, field_keyDownBuffer);
pojav_environ->keyDownBuffer = (*pojav_environ->runtimeJNIEnvPtr_JRE)->GetDirectBufferAddress(pojav_environ->runtimeJNIEnvPtr_JRE, keyDownBufferJ);
jfieldID field_mouseDownBuffer = (*pojav_environ->runtimeJNIEnvPtr_JRE)->GetStaticFieldID(pojav_environ->runtimeJNIEnvPtr_JRE, pojav_environ->vmGlfwClass, "mouseDownBuffer", "Ljava/nio/ByteBuffer;");
jobject mouseDownBufferJ = (*pojav_environ->runtimeJNIEnvPtr_JRE)->GetStaticObjectField(pojav_environ->runtimeJNIEnvPtr_JRE, pojav_environ->vmGlfwClass, field_mouseDownBuffer);
pojav_environ->mouseDownBuffer = (*pojav_environ->runtimeJNIEnvPtr_JRE)->GetDirectBufferAddress(pojav_environ->runtimeJNIEnvPtr_JRE, mouseDownBufferJ);
hookExec();
installLinkerBugMitigation();
installEMUIIteratorMititgation();
}
if(pojav_environ->dalvikJavaVMPtr == vm) {
//perform in all DVM instances, not only during first ever set up
JNIEnv *env;
(*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4);
registerFunctions(env);
}
pojav_environ->isGrabbing = JNI_FALSE;
return JNI_VERSION_1_4;
}
#define ADD_CALLBACK_WWIN(NAME) \
JNIEXPORT jlong JNICALL Java_org_lwjgl_glfw_GLFW_nglfwSet##NAME##Callback(JNIEnv * env, jclass cls, jlong window, jlong callbackptr) { \
void** oldCallback = (void**) &pojav_environ->GLFW_invoke_##NAME; \
pojav_environ->GLFW_invoke_##NAME = (GLFW_invoke_##NAME##_func*) (uintptr_t) callbackptr; \
return (jlong) (uintptr_t) *oldCallback; \
}
ADD_CALLBACK_WWIN(Char)
ADD_CALLBACK_WWIN(CharMods)
ADD_CALLBACK_WWIN(CursorEnter)
ADD_CALLBACK_WWIN(CursorPos)
ADD_CALLBACK_WWIN(FramebufferSize)
ADD_CALLBACK_WWIN(Key)
ADD_CALLBACK_WWIN(MouseButton)
ADD_CALLBACK_WWIN(Scroll)
ADD_CALLBACK_WWIN(WindowSize)
#undef ADD_CALLBACK_WWIN
void handleFramebufferSizeJava(long window, int w, int h) {
(*pojav_environ->runtimeJNIEnvPtr_JRE)->CallStaticVoidMethod(pojav_environ->runtimeJNIEnvPtr_JRE, pojav_environ->vmGlfwClass, pojav_environ->method_internalWindowSizeChanged, (long)window, w, h);
}
void pojavPumpEvents(void* window) {
if(pojav_environ->isPumpingEvents) return;
// prevent further calls until we exit the loop
// by spec, they will be called on the same thread so no synchronization here
pojav_environ->isPumpingEvents = true;
if((pojav_environ->cLastX != pojav_environ->cursorX || pojav_environ->cLastY != pojav_environ->cursorY) && pojav_environ->GLFW_invoke_CursorPos) {
pojav_environ->cLastX = pojav_environ->cursorX;
pojav_environ->cLastY = pojav_environ->cursorY;
pojav_environ->GLFW_invoke_CursorPos(window, floor(pojav_environ->cursorX),
floor(pojav_environ->cursorY));
}
size_t index = pojav_environ->outEventIndex;
size_t targetIndex = pojav_environ->outTargetIndex;
while (targetIndex != index) {
GLFWInputEvent event = pojav_environ->events[index];
switch (event.type) {
case EVENT_TYPE_CHAR:
if(pojav_environ->GLFW_invoke_Char) pojav_environ->GLFW_invoke_Char(window, event.i1);
break;
case EVENT_TYPE_CHAR_MODS:
if(pojav_environ->GLFW_invoke_CharMods) pojav_environ->GLFW_invoke_CharMods(window, event.i1, event.i2);
break;
case EVENT_TYPE_KEY:
if(pojav_environ->GLFW_invoke_Key) pojav_environ->GLFW_invoke_Key(window, event.i1, event.i2, event.i3, event.i4);
break;
case EVENT_TYPE_MOUSE_BUTTON:
if(pojav_environ->GLFW_invoke_MouseButton) pojav_environ->GLFW_invoke_MouseButton(window, event.i1, event.i2, event.i3);
break;
case EVENT_TYPE_SCROLL:
if(pojav_environ->GLFW_invoke_Scroll) pojav_environ->GLFW_invoke_Scroll(window, event.i1, event.i2);
break;
case EVENT_TYPE_FRAMEBUFFER_SIZE:
handleFramebufferSizeJava(pojav_environ->showingWindow, event.i1, event.i2);
if(pojav_environ->GLFW_invoke_FramebufferSize) pojav_environ->GLFW_invoke_FramebufferSize(window, event.i1, event.i2);
break;
case EVENT_TYPE_WINDOW_SIZE:
handleFramebufferSizeJava(pojav_environ->showingWindow, event.i1, event.i2);
if(pojav_environ->GLFW_invoke_WindowSize) pojav_environ->GLFW_invoke_WindowSize(window, event.i1, event.i2);
break;
}
index++;
if (index >= EVENT_WINDOW_SIZE)
index -= EVENT_WINDOW_SIZE;
}
// The out target index is updated by the rewinder
pojav_environ->isPumpingEvents = false;
}
/** Setup the amount of event that will get pumped into each window */
void pojavComputeEventTarget() {
size_t counter = atomic_load_explicit(&pojav_environ->eventCounter, memory_order_acquire);
size_t index = pojav_environ->outEventIndex;
unsigned targetIndex = index + counter;
if (targetIndex >= EVENT_WINDOW_SIZE)
targetIndex -= EVENT_WINDOW_SIZE;
// Only accessed by one unique thread, no need for atomic store
pojav_environ->inEventCount = counter;
pojav_environ->outTargetIndex = targetIndex;
}
/** Apply index offsets after events have been pumped */
void pojavRewindEvents() {
pojav_environ->outEventIndex = pojav_environ->outTargetIndex;
// New events may have arrived while pumping, so remove only the difference before the start and end of execution
atomic_fetch_sub_explicit(&pojav_environ->eventCounter, pojav_environ->inEventCount, memory_order_acquire);
}
JNIEXPORT void JNICALL
Java_org_lwjgl_glfw_GLFW_nglfwGetCursorPos(JNIEnv *env, __attribute__((unused)) jclass clazz, __attribute__((unused)) jlong window, jobject xpos,
jobject ypos) {
*(double*)(*env)->GetDirectBufferAddress(env, xpos) = pojav_environ->cursorX;
*(double*)(*env)->GetDirectBufferAddress(env, ypos) = pojav_environ->cursorY;
}
JNIEXPORT void JNICALL JavaCritical_org_lwjgl_glfw_GLFW_nglfwGetCursorPosA(__attribute__((unused)) jlong window, jint lengthx, jdouble* xpos, jint lengthy, jdouble* ypos) {
*xpos = pojav_environ->cursorX;
*ypos = pojav_environ->cursorY;
}
JNIEXPORT void JNICALL
Java_org_lwjgl_glfw_GLFW_nglfwGetCursorPosA(JNIEnv *env, __attribute__((unused)) jclass clazz, __attribute__((unused)) jlong window,
jdoubleArray xpos, jdoubleArray ypos) {
(*env)->SetDoubleArrayRegion(env, xpos, 0,1, &pojav_environ->cursorX);
(*env)->SetDoubleArrayRegion(env, ypos, 0,1, &pojav_environ->cursorY);
}
JNIEXPORT void JNICALL JavaCritical_org_lwjgl_glfw_GLFW_glfwSetCursorPos(__attribute__((unused)) jlong window, jdouble xpos,
jdouble ypos) {
pojav_environ->cLastX = pojav_environ->cursorX = xpos;
pojav_environ->cLastY = pojav_environ->cursorY = ypos;
}
JNIEXPORT void JNICALL
Java_org_lwjgl_glfw_GLFW_glfwSetCursorPos(__attribute__((unused)) JNIEnv *env, __attribute__((unused)) jclass clazz, __attribute__((unused)) jlong window, jdouble xpos,
jdouble ypos) {
JavaCritical_org_lwjgl_glfw_GLFW_glfwSetCursorPos(window, xpos, ypos);
}
void sendData(int type, int i1, int i2, int i3, int i4) {
GLFWInputEvent *event = &pojav_environ->events[pojav_environ->inEventIndex];
event->type = type;
event->i1 = i1;
event->i2 = i2;
event->i3 = i3;
event->i4 = i4;
if (++pojav_environ->inEventIndex >= EVENT_WINDOW_SIZE)
pojav_environ->inEventIndex -= EVENT_WINDOW_SIZE;
atomic_fetch_add_explicit(&pojav_environ->eventCounter, 1, memory_order_acquire);
}
/**
* Hooked version of java.lang.UNIXProcess.forkAndExec()
* which is used to handle the "open" command.
*/
jint
hooked_ProcessImpl_forkAndExec(JNIEnv *env, jobject process, jint mode, jbyteArray helperpath, jbyteArray prog, jbyteArray argBlock, jint argc, jbyteArray envBlock, jint envc, jbyteArray dir, jintArray std_fds, jboolean redirectErrorStream) {
char *pProg = (char *)((*env)->GetByteArrayElements(env, prog, NULL));
// Here we only handle the "xdg-open" command
if (strcmp(basename(pProg), "xdg-open") != 0) {
(*env)->ReleaseByteArrayElements(env, prog, (jbyte *)pProg, 0);
return orig_ProcessImpl_forkAndExec(env, process, mode, helperpath, prog, argBlock, argc, envBlock, envc, dir, std_fds, redirectErrorStream);
}
(*env)->ReleaseByteArrayElements(env, prog, (jbyte *)pProg, 0);
Java_org_lwjgl_glfw_CallbackBridge_nativeClipboard(env, NULL, /* CLIPBOARD_OPEN */ 2002, argBlock);
return 0;
}
void hookExec() {
jclass cls;
orig_ProcessImpl_forkAndExec = dlsym(RTLD_DEFAULT, "Java_java_lang_UNIXProcess_forkAndExec");
if (!orig_ProcessImpl_forkAndExec) {
orig_ProcessImpl_forkAndExec = dlsym(RTLD_DEFAULT, "Java_java_lang_ProcessImpl_forkAndExec");
cls = (*pojav_environ->runtimeJNIEnvPtr_JRE)->FindClass(pojav_environ->runtimeJNIEnvPtr_JRE, "java/lang/ProcessImpl");
} else {
cls = (*pojav_environ->runtimeJNIEnvPtr_JRE)->FindClass(pojav_environ->runtimeJNIEnvPtr_JRE, "java/lang/UNIXProcess");
}
JNINativeMethod methods[] = {
{"forkAndExec", "(I[B[B[BI[BI[B[IZ)I", (void *)&hooked_ProcessImpl_forkAndExec}
};
(*pojav_environ->runtimeJNIEnvPtr_JRE)->RegisterNatives(pojav_environ->runtimeJNIEnvPtr_JRE, cls, methods, 1);
printf("Registered forkAndExec\n");
}
/**
* Basically a verbatim implementation of ndlopen(), found at
* https://github.com/PojavLauncherTeam/lwjgl3/blob/3.3.1/modules/lwjgl/core/src/generated/c/linux/org_lwjgl_system_linux_DynamicLinkLoader.c#L11
* The idea is that since, on Android 10 and earlier, the linker doesn't really do namespace nesting.
* It is not a problem as most of the libraries are in the launcher path, but when you try to run
* VulkanMod which loads shaderc outside of the default jni libs directory through this method,
* it can't load it because the path is not in the allowed paths for the anonymous namesapce.
* This method fixes the issue by being in libpojavexec, and thus being in the classloader namespace
*/
jlong ndlopen_bugfix(__attribute__((unused)) JNIEnv *env,
__attribute__((unused)) jclass class,
jlong filename_ptr,
jint jmode) {
const char* filename = (const char*) filename_ptr;
int mode = (int)jmode;
return (jlong) dlopen(filename, mode);
}
/**
* Install the linker bug mitigation for Android 10 and lower. Fixes VulkanMod crashing on these
* Android versions due to missing namespace nesting.
*/
void installLinkerBugMitigation() {
if(android_get_device_api_level() >= 30) return;
__android_log_print(ANDROID_LOG_INFO, "Api29LinkerFix", "API < 30 detected, installing linker bug mitigation");
JNIEnv* env = pojav_environ->runtimeJNIEnvPtr_JRE;
jclass dynamicLinkLoader = (*env)->FindClass(env, "org/lwjgl/system/linux/DynamicLinkLoader");
if(dynamicLinkLoader == NULL) {
__android_log_print(ANDROID_LOG_ERROR, "Api29LinkerFix", "Failed to find the target class");
(*env)->ExceptionClear(env);
return;
}
JNINativeMethod ndlopenMethod[] = {
{"ndlopen", "(JI)J", &ndlopen_bugfix}
};
if((*env)->RegisterNatives(env, dynamicLinkLoader, ndlopenMethod, 1) != 0) {
__android_log_print(ANDROID_LOG_ERROR, "Api29LinkerFix", "Failed to register the bugfix method");
(*env)->ExceptionClear(env);
}
}
/**
* This function is meant as a substitute for SharedLibraryUtil.getLibraryPath() that just returns 0
* (thus making the parent Java function return null). This is done to avoid using the LWJGL's default function,
* which will hang the crappy EMUI linker by dlopen()ing inside of dl_iterate_phdr().
* @return 0, to make the parent Java function return null immediately.
* For reference: https://github.com/PojavLauncherTeam/lwjgl3/blob/fix_huawei_hang/modules/lwjgl/core/src/main/java/org/lwjgl/system/SharedLibraryUtil.java
*/
jint getLibraryPath_fix(__attribute__((unused)) JNIEnv *env,
__attribute__((unused)) jclass class,
__attribute__((unused)) jlong pLibAddress,
__attribute__((unused)) jlong sOutAddress,
__attribute__((unused)) jint bufSize){
return 0;
}
/**
* Install the linker hang mitigation that is meant to prevent linker hangs on old EMUI firmware.
*/
void installEMUIIteratorMititgation() {
if(getenv("POJAV_EMUI_ITERATOR_MITIGATE") == NULL) return;
__android_log_print(ANDROID_LOG_INFO, "EMUIIteratorFix", "Installing...");
JNIEnv* env = pojav_environ->runtimeJNIEnvPtr_JRE;
jclass sharedLibraryUtil = (*env)->FindClass(env, "org/lwjgl/system/SharedLibraryUtil");
if(sharedLibraryUtil == NULL) {
__android_log_print(ANDROID_LOG_ERROR, "EMUIIteratorFix", "Failed to find the target class");
(*env)->ExceptionClear(env);
return;
}
JNINativeMethod getLibraryPathMethod[] = {
{"getLibraryPath", "(JJI)I", &getLibraryPath_fix}
};
if((*env)->RegisterNatives(env, sharedLibraryUtil, getLibraryPathMethod, 1) != 0) {
__android_log_print(ANDROID_LOG_ERROR, "EMUIIteratorFix", "Failed to register the mitigation method");
(*env)->ExceptionClear(env);
}
}
void critical_set_stackqueue(jboolean use_input_stack_queue) {
pojav_environ->isUseStackQueueCall = (int) use_input_stack_queue;
}
void noncritical_set_stackqueue(__attribute__((unused)) JNIEnv *env, __attribute__((unused)) jclass clazz, jboolean use_input_stack_queue) {
critical_set_stackqueue(use_input_stack_queue);
}
jstring convertStringJVM(JNIEnv* srcEnv, JNIEnv* dstEnv, jstring srcStr) {
if (srcStr == NULL) {
return NULL;
}
const char* srcStrC = (*srcEnv)->GetStringUTFChars(srcEnv, srcStr, 0);
jstring dstStr = (*dstEnv)->NewStringUTF(dstEnv, srcStrC);
(*srcEnv)->ReleaseStringUTFChars(srcEnv, srcStr, srcStrC);
return dstStr;
}
JNIEXPORT jstring JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeClipboard(JNIEnv* env, __attribute__((unused)) jclass clazz, jint action, jbyteArray copySrc) {
#ifdef DEBUG
FCL_INTERNAL_LOG("Debug: Clipboard access is going on\n", pojav_environ->isUseStackQueueCall);
#endif
JNIEnv *dalvikEnv;
(*pojav_environ->dalvikJavaVMPtr)->AttachCurrentThread(pojav_environ->dalvikJavaVMPtr, &dalvikEnv, NULL);
assert(dalvikEnv != NULL);
assert(pojav_environ->bridgeClazz != NULL);
FCL_INTERNAL_LOG("Clipboard: Converting string\n");
char *copySrcC;
jstring copyDst = NULL;
if (copySrc) {
copySrcC = (char *)((*env)->GetByteArrayElements(env, copySrc, NULL));
copyDst = (*dalvikEnv)->NewStringUTF(dalvikEnv, copySrcC);
}
FCL_INTERNAL_LOG("Clipboard: Calling 2nd\n");
jstring pasteDst = convertStringJVM(dalvikEnv, env, (jstring) (*dalvikEnv)->CallStaticObjectMethod(dalvikEnv, pojav_environ->bridgeClazz, pojav_environ->method_accessAndroidClipboard, action, copyDst));
if (copySrc) {
(*dalvikEnv)->DeleteLocalRef(dalvikEnv, copyDst);
(*env)->ReleaseByteArrayElements(env, copySrc, (jbyte *)copySrcC, 0);
}
(*pojav_environ->dalvikJavaVMPtr)->DetachCurrentThread(pojav_environ->dalvikJavaVMPtr);
return pasteDst;
}
JNIEXPORT jboolean JNICALL JavaCritical_org_lwjgl_glfw_CallbackBridge_nativeSetInputReady(jboolean inputReady) {
#ifdef DEBUG
FCL_INTERNAL_LOG("Debug: Changing input state, isReady=%d, pojav_environ->isUseStackQueueCall=%d\n", inputReady, pojav_environ->isUseStackQueueCall);
#endif
__android_log_print(ANDROID_LOG_INFO, "NativeInput", "Input ready: %i", inputReady);
pojav_environ->isInputReady = inputReady;
return pojav_environ->isUseStackQueueCall;
}
JNIEXPORT jboolean JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeSetInputReady(__attribute__((unused)) JNIEnv* env, __attribute__((unused)) jclass clazz, jboolean inputReady) {
return JavaCritical_org_lwjgl_glfw_CallbackBridge_nativeSetInputReady(inputReady);
}
JNIEXPORT void JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeSetGrabbing(__attribute__((unused)) JNIEnv* env, __attribute__((unused)) jclass clazz, jboolean grabbing) {
JNIEnv *dalvikEnv;
(*pojav_environ->dalvikJavaVMPtr)->AttachCurrentThread(pojav_environ->dalvikJavaVMPtr, &dalvikEnv, NULL);
(*dalvikEnv)->CallStaticVoidMethod(dalvikEnv, pojav_environ->bridgeClazz, pojav_environ->method_onGrabStateChanged, grabbing);
(*pojav_environ->dalvikJavaVMPtr)->DetachCurrentThread(pojav_environ->dalvikJavaVMPtr);
pojav_environ->isGrabbing = grabbing;
}
jboolean critical_send_char(jchar codepoint) {
if (pojav_environ->GLFW_invoke_Char && pojav_environ->isInputReady) {
if (pojav_environ->isUseStackQueueCall) {
sendData(EVENT_TYPE_CHAR, codepoint, 0, 0, 0);
} else {
pojav_environ->GLFW_invoke_Char((void*) pojav_environ->showingWindow, (unsigned int) codepoint);
}
return JNI_TRUE;
}
return JNI_FALSE;
}
jboolean noncritical_send_char(__attribute__((unused)) JNIEnv* env, __attribute__((unused)) jclass clazz, jchar codepoint) {
return critical_send_char(codepoint);
}
jboolean critical_send_char_mods(jchar codepoint, jint mods) {
if (pojav_environ->GLFW_invoke_CharMods && pojav_environ->isInputReady) {
if (pojav_environ->isUseStackQueueCall) {
sendData(EVENT_TYPE_CHAR_MODS, (int) codepoint, mods, 0, 0);
} else {
pojav_environ->GLFW_invoke_CharMods((void*) pojav_environ->showingWindow, codepoint, mods);
}
return JNI_TRUE;
}
return JNI_FALSE;
}
jboolean noncritical_send_char_mods(__attribute__((unused)) JNIEnv* env, __attribute__((unused)) jclass clazz, jchar codepoint, jint mods) {
return critical_send_char_mods(codepoint, mods);
}
/*
JNIEXPORT void JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeSendCursorEnter(JNIEnv* env, jclass clazz, jint entered) {
if (pojav_environ->GLFW_invoke_CursorEnter && pojav_environ->isInputReady) {
pojav_environ->GLFW_invoke_CursorEnter(pojav_environ->showingWindow, entered);
}
}
*/
void critical_send_cursor_pos(jfloat x, jfloat y) {
#ifdef DEBUG
FCL_INTERNAL_LOG("Sending cursor position \n");
#endif
if (pojav_environ->GLFW_invoke_CursorPos && pojav_environ->isInputReady) {
#ifdef DEBUG
FCL_INTERNAL_LOG("pojav_environ->GLFW_invoke_CursorPos && pojav_environ->isInputReady \n");
#endif
if (!pojav_environ->isCursorEntered) {
if (pojav_environ->GLFW_invoke_CursorEnter) {
pojav_environ->isCursorEntered = true;
if (pojav_environ->isUseStackQueueCall) {
sendData(EVENT_TYPE_CURSOR_ENTER, 1, 0, 0, 0);
} else {
pojav_environ->GLFW_invoke_CursorEnter((void*) pojav_environ->showingWindow, 1);
}
} else if (pojav_environ->isGrabbing) {
// Some Minecraft versions does not use GLFWCursorEnterCallback
// This is a smart check, as Minecraft will not in grab mode if already not.
pojav_environ->isCursorEntered = true;
}
}
if (!pojav_environ->isUseStackQueueCall) {
pojav_environ->GLFW_invoke_CursorPos((void*) pojav_environ->showingWindow, (double) (x), (double) (y));
} else {
pojav_environ->cursorX = x;
pojav_environ->cursorY = y;
}
}
}
void noncritical_send_cursor_pos(__attribute__((unused)) JNIEnv* env, __attribute__((unused)) jclass clazz, jfloat x, jfloat y) {
critical_send_cursor_pos(x, y);
}
#define max(a,b) \
({ __typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a > _b ? _a : _b; })
void critical_send_key(jint key, jint scancode, jint action, jint mods) {
if (pojav_environ->GLFW_invoke_Key && pojav_environ->isInputReady) {
pojav_environ->keyDownBuffer[max(0, key-31)] = (jbyte) action;
if (pojav_environ->isUseStackQueueCall) {
sendData(EVENT_TYPE_KEY, key, scancode, action, mods);
} else {
pojav_environ->GLFW_invoke_Key((void*) pojav_environ->showingWindow, key, scancode, action, mods);
}
}
}
void noncritical_send_key(__attribute__((unused)) JNIEnv* env, __attribute__((unused)) jclass clazz, jint key, jint scancode, jint action, jint mods) {
critical_send_key(key, scancode, action, mods);
}
void critical_send_mouse_button(jint button, jint action, jint mods) {
if (pojav_environ->GLFW_invoke_MouseButton && pojav_environ->isInputReady) {
pojav_environ->mouseDownBuffer[max(0, button)] = (jbyte) action;
if (pojav_environ->isUseStackQueueCall) {
sendData(EVENT_TYPE_MOUSE_BUTTON, button, action, mods, 0);
} else {
pojav_environ->GLFW_invoke_MouseButton((void*) pojav_environ->showingWindow, button, action, mods);
}
}
}
void noncritical_send_mouse_button(__attribute__((unused)) JNIEnv* env, __attribute__((unused)) jclass clazz, jint button, jint action, jint mods) {
critical_send_mouse_button(button, action, mods);
}
void critical_send_screen_size(jint width, jint height) {
pojav_environ->savedWidth = width;
pojav_environ->savedHeight = height;
if (pojav_environ->isInputReady) {
if (pojav_environ->GLFW_invoke_FramebufferSize) {
if (pojav_environ->isUseStackQueueCall) {
sendData(EVENT_TYPE_FRAMEBUFFER_SIZE, width, height, 0, 0);
} else {
pojav_environ->GLFW_invoke_FramebufferSize((void*) pojav_environ->showingWindow, width, height);
}
}
if (pojav_environ->GLFW_invoke_WindowSize) {
if (pojav_environ->isUseStackQueueCall) {
sendData(EVENT_TYPE_WINDOW_SIZE, width, height, 0, 0);
} else {
pojav_environ->GLFW_invoke_WindowSize((void*) pojav_environ->showingWindow, width, height);
}
}
}
}
void noncritical_send_screen_size(__attribute__((unused)) JNIEnv* env, __attribute__((unused)) jclass clazz, jint width, jint height) {
critical_send_screen_size(width, height);
}
void critical_send_scroll(jdouble xoffset, jdouble yoffset) {
if (pojav_environ->GLFW_invoke_Scroll && pojav_environ->isInputReady) {
if (pojav_environ->isUseStackQueueCall) {
sendData(EVENT_TYPE_SCROLL, (int)xoffset, (int)yoffset, 0, 0);
} else {
pojav_environ->GLFW_invoke_Scroll((void*) pojav_environ->showingWindow, (double) xoffset, (double) yoffset);
}
}
}
void noncritical_send_scroll(__attribute__((unused)) JNIEnv* env, __attribute__((unused)) jclass clazz, jdouble xoffset, jdouble yoffset) {
critical_send_scroll(xoffset, yoffset);
}
JNIEXPORT void JNICALL Java_org_lwjgl_glfw_GLFW_nglfwSetShowingWindow(__attribute__((unused)) JNIEnv* env, __attribute__((unused)) jclass clazz, jlong window) {
pojav_environ->showingWindow = (long) window;
}
JNIEXPORT void JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeSetWindowAttrib(__attribute__((unused)) JNIEnv* env, __attribute__((unused)) jclass clazz, jint attrib, jint value) {
if (!pojav_environ->showingWindow || !pojav_environ->isUseStackQueueCall) {
// If the window is not shown, there is nothing to do yet.
// For Minecraft < 1.13, calling to JNI functions here crashes the JVM for some reason, therefore it is skipped for now.
return;
}
(*pojav_environ->runtimeJNIEnvPtr_JRE)->CallStaticVoidMethod(
pojav_environ->runtimeJNIEnvPtr_JRE,
pojav_environ->vmGlfwClass, pojav_environ->method_glftSetWindowAttrib,
(jlong) pojav_environ->showingWindow, attrib, value
);
}
const static JNINativeMethod critical_fcns[] = {
{"nativeSetUseInputStackQueue", "(Z)V", critical_set_stackqueue},
{"nativeSendChar", "(C)Z", critical_send_char},
{"nativeSendCharMods", "(CI)Z", critical_send_char_mods},
{"nativeSendKey", "(IIII)V", critical_send_key},
{"nativeSendCursorPos", "(FF)V", critical_send_cursor_pos},
{"nativeSendMouseButton", "(III)V", critical_send_mouse_button},
{"nativeSendScroll", "(DD)V", critical_send_scroll},
{"nativeSendScreenSize", "(II)V", critical_send_screen_size}
};
const static JNINativeMethod noncritical_fcns[] = {
{"nativeSetUseInputStackQueue", "(Z)V", noncritical_set_stackqueue},
{"nativeSendChar", "(C)Z", noncritical_send_char},
{"nativeSendCharMods", "(CI)Z", noncritical_send_char_mods},
{"nativeSendKey", "(IIII)V", noncritical_send_key},
{"nativeSendCursorPos", "(FF)V", noncritical_send_cursor_pos},
{"nativeSendMouseButton", "(III)V", noncritical_send_mouse_button},
{"nativeSendScroll", "(DD)V", noncritical_send_scroll},
{"nativeSendScreenSize", "(II)V", noncritical_send_screen_size}
};
static bool criticalNativeAvailable;
void dvm_testCriticalNative(void* arg0, void* arg1, void* arg2, void* arg3) {
if(arg0 != 0 && arg2 == 0 && arg3 == 0) {
criticalNativeAvailable = false;
}else if (arg0 == 0 && arg1 == 0){
criticalNativeAvailable = true;
}else {
criticalNativeAvailable = false; // just to be safe
}
}
static bool tryCriticalNative(JNIEnv *env) {
static const JNINativeMethod testJNIMethod[] = {
{ "testCriticalNative", "(II)V", dvm_testCriticalNative}
};
jclass criticalNativeTest = (*env)->FindClass(env, "com/tungsten/fclauncher/CriticalNativeTest");
if(criticalNativeTest == NULL) {
(*env)->ExceptionClear(env);
return false;
}
jmethodID criticalNativeTestMethod = (*env)->GetStaticMethodID(env, criticalNativeTest, "invokeTest", "()V");
(*env)->RegisterNatives(env, criticalNativeTest, testJNIMethod, 1);
(*env)->CallStaticVoidMethod(env, criticalNativeTest, criticalNativeTestMethod);
(*env)->UnregisterNatives(env, criticalNativeTest);
return criticalNativeAvailable;
}
static void registerFunctions(JNIEnv *env) {
bool use_critical_cc = tryCriticalNative(env);
jclass bridge_class = (*env)->FindClass(env, "org/lwjgl/glfw/CallbackBridge");
if(use_critical_cc) {
__android_log_print(ANDROID_LOG_INFO, "pojavexec", "CriticalNative is available. Enjoy the 4.6x times faster input!");
}else{
__android_log_print(ANDROID_LOG_INFO, "pojavexec", "CriticalNative is not available. Upgrade, maybe?");
}
(*env)->RegisterNatives(env,
bridge_class,
use_critical_cc ? critical_fcns : noncritical_fcns,
sizeof(critical_fcns)/sizeof(critical_fcns[0]));
}

View File

@ -4,7 +4,7 @@
#include <malloc.h> #include <malloc.h>
#include <string.h> #include <string.h>
#include "gl/gl.h" #include "pojav/GL/gl.h"
#include "GLES3/gl32.h" #include "GLES3/gl32.h"
#include "include/string_utils.h" #include "include/string_utils.h"