diff --git a/FCLauncher/src/main/jni/Android.mk b/FCLauncher/src/main/jni/Android.mk index db87958e..31119426 100644 --- a/FCLauncher/src/main/jni/Android.mk +++ b/FCLauncher/src/main/jni/Android.mk @@ -66,6 +66,7 @@ LOCAL_SRC_FILES := \ pojav/ctxbridges/swap_interval_no_egl.c \ pojav/environ/environ.c \ pojav/input_bridge_v3.c \ + pojav/virgl/virgl.c \ driver_helper/nsbypass.c ifeq ($(TARGET_ARCH_ABI),arm64-v8a) diff --git a/FCLauncher/src/main/jni/pojav/ctxbridges/osmesa_loader.c b/FCLauncher/src/main/jni/pojav/ctxbridges/osmesa_loader.c index c81c1977..32efcc6f 100644 --- a/FCLauncher/src/main/jni/pojav/ctxbridges/osmesa_loader.c +++ b/FCLauncher/src/main/jni/pojav/ctxbridges/osmesa_loader.c @@ -5,6 +5,8 @@ #include #include #include "osmesa_loader.h" +#include "pojav/environ/environ.h" +#include "pojav/virgl/virgl.h" GLboolean (*OSMesaMakeCurrent_p) (OSMesaContext ctx, void *buffer, GLenum type, GLsizei width, GLsizei height); @@ -20,14 +22,15 @@ void (*glReadPixels_p) (GLint x, GLint y, GLsizei width, GLsizei height, GLenum 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) { + char *fmt = "%s/libOSMesa_8.so"; + if (pojav_environ->config_renderer == RENDERER_VIRGL) { + fmt = "%s/libOSMesa_81.so"; + } + if (asprintf(&main_path, fmt, 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); + dl_handle = dlopen(main_path, RTLD_GLOBAL); if(dl_handle == NULL) abort(); OSMesaMakeCurrent_p = dlsym(dl_handle, "OSMesaMakeCurrent"); OSMesaGetCurrentContext_p = dlsym(dl_handle,"OSMesaGetCurrentContext"); diff --git a/FCLauncher/src/main/jni/pojav/egl_bridge.c b/FCLauncher/src/main/jni/pojav/egl_bridge.c index 8ec545bb..8e344272 100644 --- a/FCLauncher/src/main/jni/pojav/egl_bridge.c +++ b/FCLauncher/src/main/jni/pojav/egl_bridge.c @@ -1,3 +1,4 @@ +#include "pojav/egl_bridge.h" #include #include #include @@ -37,22 +38,12 @@ // 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" +#include "pojav/virgl/virgl.h" #define RENDERER_GL4ES 1 #define RENDERER_VK_ZINK 2 @@ -94,6 +85,9 @@ Java_net_kdt_pojavlaunch_utils_JREUtils_releaseBridgeWindow(ABI_COMPAT JNIEnv *e } EXTERNAL_API void* pojavGetCurrentContext() { + if (pojav_environ->config_renderer == RENDERER_VIRGL) { + return virglGetCurrentContext(); + } return br_get_current(); } @@ -197,7 +191,12 @@ int pojavInitOpenGL() { // NOTE: Override for now. const char *renderer = getenv("POJAV_RENDERER"); - if (strncmp("opengles", renderer, 8) == 0) { + if (strcmp(renderer, "opengles3_virgl") == 0) { + pojav_environ->config_renderer = RENDERER_VIRGL; + loadSymbolsVirGL(); + virglInit(); + return 0; + } else if (strncmp("opengles", renderer, 8) == 0) { pojav_environ->config_renderer = RENDERER_GL4ES; set_gl_bridge_tbl(); } else if (strcmp(renderer, "vulkan_zink") == 0) { @@ -240,17 +239,28 @@ EXTERNAL_API void pojavSetWindowHint(int hint, int value) { } EXTERNAL_API void pojavSwapBuffers() { - br_swap_buffers(); + if (pojav_environ->config_renderer == RENDERER_VIRGL) { + virglSwapBuffers(); + } else { + br_swap_buffers(); + } } EXTERNAL_API void pojavMakeCurrent(void* window) { - br_make_current((basic_render_window_t*)window); + if (pojav_environ->config_renderer == RENDERER_VIRGL) + { + virglMakeCurrent(window); + } else { + 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; + } else if (pojav_environ->config_renderer == RENDERER_VIRGL) { + return virglCreateContext(contextSrc); } return br_init_context((basic_render_window_t*)contextSrc); } @@ -266,6 +276,10 @@ Java_org_lwjgl_vulkan_VK_getVulkanDriverHandle(ABI_COMPAT JNIEnv *env, ABI_COMPA } EXTERNAL_API void pojavSwapInterval(int interval) { - br_swap_interval(interval); + if (pojav_environ->config_renderer == RENDERER_VIRGL) { + virglSwapInterval(interval); + } else { + br_swap_interval(interval); + } } diff --git a/FCLauncher/src/main/jni/pojav/egl_bridge.h b/FCLauncher/src/main/jni/pojav/egl_bridge.h new file mode 100644 index 00000000..7d239f27 --- /dev/null +++ b/FCLauncher/src/main/jni/pojav/egl_bridge.h @@ -0,0 +1,23 @@ +// +// Created by mio on 2024/8/20. +// + +#ifndef FOLD_CRAFT_LAUNCHER_EGL_BRIDGE_H +#define FOLD_CRAFT_LAUNCHER_EGL_BRIDGE_H + +#include + +struct PotatoBridge { + + /* EGLContext */ void* eglContext; + /* EGLDisplay */ void* eglDisplay; + /* EGLSurface */ void* eglSurface; +/* + void* eglSurfaceRead; + void* eglSurfaceDraw; +*/ +}; +extern EGLConfig config; +extern struct PotatoBridge potatoBridge; + +#endif //FOLD_CRAFT_LAUNCHER_EGL_BRIDGE_H diff --git a/FCLauncher/src/main/jni/pojav/virgl/virgl.c b/FCLauncher/src/main/jni/pojav/virgl/virgl.c new file mode 100644 index 00000000..b62720a9 --- /dev/null +++ b/FCLauncher/src/main/jni/pojav/virgl/virgl.c @@ -0,0 +1,192 @@ +// +// Created by mio on 2024/8/20. +// + +#include +#include +#include +#include +#include +#include +#include +#include "virgl.h" +#include "pojav/environ/environ.h" +#include "pojav/ctxbridges/osm_bridge.h" +#include "pojav/egl_bridge.h" +#include "pojav/ctxbridges/egl_loader.h" + +#define RENDERER_VIRGL 3 + +int (*vtest_main_p)(int argc, char **argv); + +void (*vtest_swap_buffers_p)(void); + +void *virglGetCurrentContext() { + return (void *) OSMesaGetCurrentContext_p(); +} + +void virglSwapBuffers() { + glFinish_p(); + vtest_swap_buffers_p(); +} + +void virglMakeCurrent(void *window) { + printf("OSMDroid: making current\n"); + OSMesaMakeCurrent_p((OSMesaContext) window, + setbuffer, + GL_UNSIGNED_BYTE, + pojav_environ->savedWidth, + pojav_environ->savedHeight); + + printf("OSMDroid: vendor: %s\n", glGetString_p(GL_VENDOR)); + printf("OSMDroid: renderer: %s\n", glGetString_p(GL_RENDERER)); + glClear_p(GL_COLOR_BUFFER_BIT); + glClearColor_p(0.4f, 0.4f, 0.4f, 1.0f); + + // Trigger a texture creation, which then set VIRGL_TEXTURE_ID + int pixelsArr[4]; + glReadPixels_p(0, 0, 1, 1, GL_RGB, GL_INT, &pixelsArr); + + virglSwapBuffers(); +} + +void *virglCreateContext(void *contextSrc) { + printf("OSMDroid: generating context\n"); + void *ctx = OSMesaCreateContext_p(OSMESA_RGBA, contextSrc); + printf("OSMDroid: context=%p\n", ctx); + return ctx; +} + +void loadSymbolsVirGL() { + dlsym_OSMesa(); + dlsym_EGL(); + + char *fileName = calloc(1, 1024); + + sprintf(fileName, "%s/libvirgl_test_server.so", getenv("POJAV_NATIVEDIR")); + void *handle = dlopen(fileName, RTLD_LAZY); + printf("VirGL: libvirgl_test_server = %p\n", handle); + if (!handle) { + printf("VirGL: %s\n", dlerror()); + } + vtest_main_p = dlsym(handle, "vtest_main"); + vtest_swap_buffers_p = dlsym(handle, "vtest_swap_buffers"); + + free(fileName); +} + +void *egl_make_current(void *window) { + EGLBoolean success = eglMakeCurrent_p( + potatoBridge.eglDisplay, + window == 0 ? (EGLSurface *) 0 : potatoBridge.eglSurface, + window == 0 ? (EGLSurface *) 0 : potatoBridge.eglSurface, + /* window==0 ? EGL_NO_CONTEXT : */ (EGLContext *) window + ); + + if (success == EGL_FALSE) { + printf("EGLBridge: Error: eglMakeCurrent() failed: %p\n", eglGetError_p()); + } else { + printf("EGLBridge: eglMakeCurrent() succeed!\n"); + } + + if (pojav_environ->config_renderer == RENDERER_VIRGL) { + printf("VirGL: vtest_main = %p\n", vtest_main_p); + printf("VirGL: Calling VTest server's main function\n"); + vtest_main_p(3, (const char *[]) {"vtest", "--no-loop-or-fork", "--use-gles", NULL, NULL}); + } +} + +void virglSwapInterval(int interval) { + eglSwapInterval_p(potatoBridge.eglDisplay, interval); +} + +int virglInit() { + if (potatoBridge.eglDisplay == NULL || potatoBridge.eglDisplay == EGL_NO_DISPLAY) { + potatoBridge.eglDisplay = eglGetDisplay_p(EGL_DEFAULT_DISPLAY); + if (potatoBridge.eglDisplay == EGL_NO_DISPLAY) { + printf("EGLBridge: Error eglGetDefaultDisplay() failed: %p\n", eglGetError_p()); + return 0; + } + } + + printf("EGLBridge: Initializing\n"); + // printf("EGLBridge: ANativeWindow pointer = %p\n", pojav_environ->pojavWindow); + //(*env)->ThrowNew(env,(*env)->FindClass(env,"java/lang/Exception"),"Trace exception"); + if (!eglInitialize_p(potatoBridge.eglDisplay, NULL, NULL)) { + printf("EGLBridge: Error eglInitialize() failed: %s\n", eglGetError_p()); + return 0; + } + + static const EGLint attribs[] = { + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + // Minecraft required on initial 24 + EGL_DEPTH_SIZE, 24, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_NONE + }; + + EGLint num_configs; + EGLint vid; + + if (!eglChooseConfig_p(potatoBridge.eglDisplay, attribs, &config, 1, &num_configs)) { + printf("EGLBridge: Error couldn't get an EGL visual config: %s\n", eglGetError_p()); + return 0; + } + + assert(config); + assert(num_configs > 0); + + if (!eglGetConfigAttrib_p(potatoBridge.eglDisplay, config, EGL_NATIVE_VISUAL_ID, &vid)) { + printf("EGLBridge: Error eglGetConfigAttrib() failed: %s\n", eglGetError_p()); + return 0; + } + + ANativeWindow_setBuffersGeometry(pojav_environ->pojavWindow, 0, 0, vid); + + eglBindAPI_p(EGL_OPENGL_ES_API); + + potatoBridge.eglSurface = eglCreateWindowSurface_p(potatoBridge.eglDisplay, config, + pojav_environ->pojavWindow, NULL); + + if (!potatoBridge.eglSurface) { + printf("EGLBridge: Error eglCreateWindowSurface failed: %p\n", eglGetError_p()); + //(*env)->ThrowNew(env,(*env)->FindClass(env,"java/lang/Exception"),"Trace exception"); + return 0; + } + + // sanity checks + { + EGLint val; + assert(eglGetConfigAttrib_p(potatoBridge.eglDisplay, config, EGL_SURFACE_TYPE, &val)); + assert(val & EGL_WINDOW_BIT); + } + + printf("EGLBridge: Initialized!\n"); + printf("EGLBridge: ThreadID=%d\n", gettid()); + printf("EGLBridge: EGLDisplay=%p, EGLSurface=%p\n", +/* window==0 ? EGL_NO_CONTEXT : */ + potatoBridge.eglDisplay, + potatoBridge.eglSurface + ); + + // Init EGL context and vtest server + const EGLint ctx_attribs[] = { + EGL_CONTEXT_CLIENT_VERSION, 3, + EGL_NONE + }; + EGLContext *ctx = eglCreateContext_p(potatoBridge.eglDisplay, config, NULL, ctx_attribs); + printf("VirGL: created EGL context %p\n", ctx); + + pthread_t t; + pthread_create(&t, NULL, egl_make_current, (void *) ctx); + usleep(100 * 1000); // need enough time for the server to init + + if (OSMesaCreateContext_p == NULL) { + __android_log_print(ANDROID_LOG_ERROR, "测试", "null"); + printf("OSMDroid: %s\n", dlerror()); + } + return 0; +} \ No newline at end of file diff --git a/FCLauncher/src/main/jni/pojav/virgl/virgl.h b/FCLauncher/src/main/jni/pojav/virgl/virgl.h new file mode 100644 index 00000000..17d476d4 --- /dev/null +++ b/FCLauncher/src/main/jni/pojav/virgl/virgl.h @@ -0,0 +1,18 @@ +// +// Created by mio on 2024/8/20. +// + +#ifndef FOLD_CRAFT_LAUNCHER_VIRGL_H +#define FOLD_CRAFT_LAUNCHER_VIRGL_H + +#define RENDERER_VIRGL 3 + +void* virglGetCurrentContext(); +void loadSymbolsVirGL(); +int virglInit(); +void virglSwapBuffers(); +void virglMakeCurrent(void* window); +void* virglCreateContext(void* contextSrc); +void virglSwapInterval(int interval); + +#endif //FOLD_CRAFT_LAUNCHER_VIRGL_H