From c773b925270e02893a3daa8cac895d1e234bd1e1 Mon Sep 17 00:00:00 2001 From: Maksim Belov <45949002+artdeell@users.noreply.github.com> Date: Fri, 10 Nov 2023 22:24:50 +0300 Subject: [PATCH] Feat[osmesa]: Implement V-Sync (#4827) --- .../prefs/LauncherPreferences.java | 3 +- .../net/kdt/pojavlaunch/utils/JREUtils.java | 3 + app_pojavlauncher/src/main/jni/Android.mk | 1 + .../src/main/jni/ctxbridges/osm_bridge.c | 7 +- .../jni/ctxbridges/swap_interval_no_egl.c | 243 ++++++++++++++++++ .../src/main/res/values/strings.xml | 2 + .../src/main/res/xml/pref_video.xml | 7 +- 7 files changed, 263 insertions(+), 3 deletions(-) create mode 100644 app_pojavlauncher/src/main/jni/ctxbridges/swap_interval_no_egl.c diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferences.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferences.java index fdabdd8fe..03acb7b9d 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferences.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferences.java @@ -67,7 +67,7 @@ public class LauncherPreferences { public static boolean PREF_VERIFY_MANIFEST = true; public static String PREF_DOWNLOAD_SOURCE = "default"; public static boolean PREF_SKIP_NOTIFICATION_PERMISSION_CHECK = false; - + public static boolean PREF_VSYNC_IN_ZINK = true; public static void loadPreferences(Context ctx) { @@ -113,6 +113,7 @@ public class LauncherPreferences { PREF_DOWNLOAD_SOURCE = DEFAULT_PREF.getString("downloadSource", "default"); PREF_VERIFY_MANIFEST = DEFAULT_PREF.getBoolean("verifyManifest", true); PREF_SKIP_NOTIFICATION_PERMISSION_CHECK = DEFAULT_PREF.getBoolean(PREF_KEY_SKIP_NOTIFICATION_CHECK, false); + PREF_VSYNC_IN_ZINK = DEFAULT_PREF.getBoolean("vsync_in_zink", true); String argLwjglLibname = "-Dorg.lwjgl.opengl.libname="; for (String arg : JREUtils.parseJavaArguments(PREF_CUSTOM_JAVA_ARGS)) { diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java index 795f010e6..0094e8d9c 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java @@ -7,6 +7,7 @@ import static net.kdt.pojavlaunch.Tools.NATIVE_LIB_DIR; import static net.kdt.pojavlaunch.Tools.currentDisplayMetrics; import static net.kdt.pojavlaunch.Tools.shareLog; import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_DUMP_SHADERS; +import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_VSYNC_IN_ZINK; import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_ZINK_PREFER_SYSTEM_DRIVER; import android.app.*; @@ -193,6 +194,8 @@ public class JREUtils { envMap.put("LIBGL_VGPU_DUMP", "1"); if(PREF_ZINK_PREFER_SYSTEM_DRIVER) envMap.put("POJAV_ZINK_PREFER_SYSTEM_DRIVER", "1"); + if(PREF_VSYNC_IN_ZINK) + envMap.put("POJAV_VSYNC_IN_ZINK", "1"); // The OPEN GL version is changed according diff --git a/app_pojavlauncher/src/main/jni/Android.mk b/app_pojavlauncher/src/main/jni/Android.mk index 49bbc3bcc..3569b498f 100644 --- a/app_pojavlauncher/src/main/jni/Android.mk +++ b/app_pojavlauncher/src/main/jni/Android.mk @@ -49,6 +49,7 @@ LOCAL_SRC_FILES := \ ctxbridges/osm_bridge.c \ ctxbridges/egl_loader.c \ ctxbridges/osmesa_loader.c \ + ctxbridges/swap_interval_no_egl.c \ environ/environ.c \ input_bridge_v3.c \ jre_launcher.c \ diff --git a/app_pojavlauncher/src/main/jni/ctxbridges/osm_bridge.c b/app_pojavlauncher/src/main/jni/ctxbridges/osm_bridge.c index c637d6504..9f6e5c613 100644 --- a/app_pojavlauncher/src/main/jni/ctxbridges/osm_bridge.c +++ b/app_pojavlauncher/src/main/jni/ctxbridges/osm_bridge.c @@ -12,6 +12,9 @@ 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 @@ -134,5 +137,7 @@ void osm_setup_window() { } void osm_swap_interval(int swapInterval) { - // nothing, as you can only set the swap interval with internal system APIs + if(pojav_environ->mainWindowBundle != NULL && pojav_environ->mainWindowBundle->nativeSurface != NULL) { + setNativeWindowSwapInterval(pojav_environ->mainWindowBundle->nativeSurface, swapInterval); + } } \ No newline at end of file diff --git a/app_pojavlauncher/src/main/jni/ctxbridges/swap_interval_no_egl.c b/app_pojavlauncher/src/main/jni/ctxbridges/swap_interval_no_egl.c new file mode 100644 index 000000000..580aa9e3e --- /dev/null +++ b/app_pojavlauncher/src/main/jni/ctxbridges/swap_interval_no_egl.c @@ -0,0 +1,243 @@ +// +// Created by maks on 08.11.2023. +// + +#include +#include +#include +#include +#include + +// 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)); + } +} \ No newline at end of file diff --git a/app_pojavlauncher/src/main/res/values/strings.xml b/app_pojavlauncher/src/main/res/values/strings.xml index cef687cfb..df01b3109 100644 --- a/app_pojavlauncher/src/main/res/values/strings.xml +++ b/app_pojavlauncher/src/main/res/values/strings.xml @@ -364,5 +364,7 @@ You can always change your mind later by going into Settings Allow notifications Click to re-request the notification permission, required for game/modpack background downloads to work properly. + Allow V-Sync with Zink + Allows the launcher to use internal system APIs to enable V-Sync for Zink. Turn this off if your launcher suddenly crashes with Zink after a system update. Failed to install JRE 17 diff --git a/app_pojavlauncher/src/main/res/xml/pref_video.xml b/app_pojavlauncher/src/main/res/xml/pref_video.xml index 2e3a02086..3748f24e7 100644 --- a/app_pojavlauncher/src/main/res/xml/pref_video.xml +++ b/app_pojavlauncher/src/main/res/xml/pref_video.xml @@ -43,12 +43,17 @@ android:summary="@string/mcl_setting_subtitle_use_surface_view" android:title="@string/mcl_setting_title_use_surface_view" /> - + \ No newline at end of file