add pojav native code
This commit is contained in:
parent
611ed7ded3
commit
9ef4652e52
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
|
@ -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 */
|
||||||
|
|
|
@ -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
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -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
|
|
@ -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");
|
||||||
|
}
|
|
@ -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
|
|
@ -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);
|
||||||
|
}
|
|
@ -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
|
|
@ -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 = ¤tBundle->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, ¤tBundle->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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -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");
|
||||||
|
}
|
|
@ -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
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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
|
|
@ -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]));
|
||||||
|
}
|
|
@ -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"
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue