Compare commits

...
7 Commits
Author SHA1 Message Date
Chloe df513983d1 cover the license 2026-09-07 17:45:15 -07:00
Chloe a71071e3ac Merge branch 'main' into latency-test 2026-09-07 17:22:30 -07:00
Chloe 8578200db1 Merge branch 'main' into latency-test 2026-09-07 00:52:32 -07:00
Chloe adb9dcc1e9 add toggle for frame pacing 2026-09-07 00:41:18 -07:00
Chloe a32589b06c disable auto swap interval 2026-09-07 00:41:18 -07:00
Chloe 1773e54c84 fix surface update being called outside gl thread 2026-09-07 00:41:18 -07:00
Chloe 5718b73470 my evil test 2026-09-07 00:41:17 -07:00
11 changed files with 1763 additions and 43 deletions

No files matched your search

+2
View File
@@ -318,6 +318,7 @@ android {
buildFeatures {
compose = true
buildConfig = true
prefab = true
}
packaging {
resources {
@@ -359,6 +360,7 @@ dependencies {
implementation ("androidx.activity:activity-ktx:1.13.0")
implementation ("androidx.appcompat:appcompat:1.8.0")
implementation ("androidx.documentfile:documentfile:1.1.0")
implementation ("androidx.games:games-frame-pacing:2.1.3")
implementation ("com.squareup.okio:okio:3.18.2")
implementation ("com.squareup.okhttp3:okhttp:5.5.0")
+5 -2
View File
@@ -2,7 +2,9 @@ cmake_minimum_required(VERSION 3.21)
project(launcherfix)
add_library(launcherfix SHARED launcher-fix.cpp patcher.cpp)
find_package(games-frame-pacing REQUIRED CONFIG)
add_library(launcherfix SHARED launcher-fix.cpp patcher.cpp pacing.cpp)
target_compile_features(launcherfix PUBLIC cxx_std_20)
set_target_properties(launcherfix PROPERTIES CXX_EXTENSIONS OFF)
@@ -14,4 +16,5 @@ set_target_properties(launcherfix PROPERTIES
CXX_VISIBILITY_PRESET hidden
)
target_link_libraries(launcherfix PRIVATE log)
target_link_libraries(launcherfix PRIVATE log android)
target_link_libraries(launcherfix PRIVATE games-frame-pacing::swappy_static)
+65
View File
@@ -0,0 +1,65 @@
#include <swappy/swappyGL.h>
#include <swappy/swappyGL_extra.h>
#include <android/native_window_jni.h>
#include <memory>
extern "C"
JNIEXPORT void JNICALL Java_com_geode_launcher_LauncherFix_initFramePacing(
JNIEnv *env,
jobject,
jobject activity
) {
SwappyGL_init(env, activity);
}
struct NativeWindowDeleter {
void operator()(ANativeWindow* p) const {
if (p != nullptr) ANativeWindow_release(p);
}
};
std::unique_ptr<ANativeWindow, NativeWindowDeleter> native_window = nullptr;
extern "C"
JNIEXPORT void JNICALL Java_com_geode_launcher_LauncherFix_destroyFramePacing(JNIEnv*, jobject) {
SwappyGL_destroy();
}
extern "C"
JNIEXPORT jboolean JNICALL Java_com_geode_launcher_LauncherFix_swapFrame(
JNIEnv*,
jobject,
jlong display,
jlong surface
) {
return SwappyGL_swap(
reinterpret_cast<EGLDisplay>(display),
reinterpret_cast<EGLSurface>(surface)
);
}
extern "C"
JNIEXPORT void JNICALL Java_com_geode_launcher_LauncherFix_setSurface(
JNIEnv* env,
jobject,
jobject surface
) {
if (surface == nullptr) {
native_window.reset();
SwappyGL_setWindow(nullptr);
return;
}
native_window.reset(ANativeWindow_fromSurface(env, surface));
SwappyGL_setWindow(native_window.get());
}
extern "C"
JNIEXPORT void JNICALL Java_com_geode_launcher_LauncherFix_setSwapInterval(
JNIEnv* env,
jobject,
jlong interval_ns
) {
SwappyGL_setAutoSwapInterval(false);
SwappyGL_setSwapIntervalNS(interval_ns);
}
File diff suppressed because it is too large. Load diff
@@ -5,7 +5,9 @@ import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.opengl.GLSurfaceView
import android.opengl.EGL14
import android.opengl.EGLConfig
import android.opengl.EGLDisplay
import android.os.Build
import android.os.Bundle
import android.os.Environment
@@ -43,9 +45,6 @@ import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
import javax.microedition.khronos.egl.EGL10
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.egl.EGLDisplay
enum class DisplayMode {
DEFAULT, LIMITED, FULLSCREEN;
@@ -78,6 +77,7 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
private var mAspectRatio = 0.0f
private var mScreenZoom = 1.0f
private var mScreenZoomFit = false
private var mNativeFramePacing = false
override fun onCreate(savedInstanceState: Bundle?) {
setupUIState()
@@ -131,6 +131,10 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
}
})
mGLSurfaceView?.manualBackEvents = true
if (mNativeFramePacing) {
LauncherFix.initFramePacing(this)
}
}
private fun createVersionFile() {
@@ -491,9 +495,11 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
val frameRate = mLimitedRefreshRate
if (frameRate != null) {
renderer.limitFrameRate(frameRate)
glSurfaceView.setMaxFrameRate(frameRate)
}
glSurfaceView.nativeFramePacing = mNativeFramePacing
editText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
glSurfaceView.cocos2dxEditText = editText
@@ -529,6 +535,8 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
}
}
mNativeFramePacing = preferenceUtils.getBoolean(PreferenceUtils.Key.NATIVE_FRAME_PACING)
if (displayMode == DisplayMode.FULLSCREEN && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
GeodeUtils.handleSafeArea = true
@@ -562,6 +570,10 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
override fun onDestroy() {
super.onDestroy()
FMOD.close()
if (mNativeFramePacing) {
LauncherFix.destroyFramePacing()
}
}
private fun resumeGame() {
@@ -733,11 +745,7 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
}
}
class EGLConfigChooser : GLSurfaceView.EGLConfigChooser {
// this comes from EGL14, but is unavailable on EGL10
// also EGL14 is incompatible with EGL10. so whatever
private var EGL_OPENGL_ES2_BIT: Int = 0x04
class EGLConfigChooser : ExplicitGLSurfaceView.EGLConfigChooser {
private data class ConfigValues(
val redSize: Int,
val greenSize: Int,
@@ -747,29 +755,29 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
val stencilSize: Int,
)
private fun getAttribValue(egl: EGL10, display: EGLDisplay, config: EGLConfig, attrib: Int): Int {
private fun getAttribValue(display: EGLDisplay, config: EGLConfig, attrib: Int): Int {
val value = IntArray(1)
egl.eglGetConfigAttrib(display, config, attrib, value)
EGL14.eglGetConfigAttrib(display, config, attrib, value, 0)
return value[0]
}
private fun testConfig(egl: EGL10, display: EGLDisplay, attrib: ConfigValues): EGLConfig? {
private fun testConfig(display: EGLDisplay, attrib: ConfigValues): EGLConfig? {
val attribList = intArrayOf(
EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL10.EGL_RED_SIZE, attrib.redSize,
EGL10.EGL_GREEN_SIZE, attrib.greenSize,
EGL10.EGL_BLUE_SIZE, attrib.blueSize,
EGL10.EGL_ALPHA_SIZE, attrib.alphaSize,
EGL10.EGL_DEPTH_SIZE, attrib.depthSize,
EGL10.EGL_STENCIL_SIZE, attrib.stencilSize,
EGL10.EGL_NONE
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
EGL14.EGL_RED_SIZE, attrib.redSize,
EGL14.EGL_GREEN_SIZE, attrib.greenSize,
EGL14.EGL_BLUE_SIZE, attrib.blueSize,
EGL14.EGL_ALPHA_SIZE, attrib.alphaSize,
EGL14.EGL_DEPTH_SIZE, attrib.depthSize,
EGL14.EGL_STENCIL_SIZE, attrib.stencilSize,
EGL14.EGL_NONE
)
val configs = arrayOfNulls<EGLConfig>(1)
val numConfigs = IntArray(1)
val res = egl.eglChooseConfig(display, attribList, configs, configs.size, numConfigs)
val res = EGL14.eglChooseConfig(display, attribList, 0, configs, 0, configs.size, numConfigs, 0)
if (res && numConfigs[0] > 0 && configs[0] != null) {
return configs[0]
}
@@ -777,18 +785,18 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
return null
}
private fun printConfig(egl: EGL10, display: EGLDisplay, config: EGLConfig) {
val id = getAttribValue(egl, display, config, EGL10.EGL_CONFIG_ID)
val red = getAttribValue(egl, display, config, EGL10.EGL_RED_SIZE)
val green = getAttribValue(egl, display, config, EGL10.EGL_GREEN_SIZE)
val blue = getAttribValue(egl, display, config, EGL10.EGL_BLUE_SIZE)
val alpha = getAttribValue(egl, display, config, EGL10.EGL_ALPHA_SIZE)
val depth = getAttribValue(egl, display, config, EGL10.EGL_DEPTH_SIZE)
val stencil = getAttribValue(egl, display, config, EGL10.EGL_STENCIL_SIZE)
private fun printConfig(display: EGLDisplay, config: EGLConfig) {
val id = getAttribValue(display, config, EGL14.EGL_CONFIG_ID)
val red = getAttribValue(display, config, EGL14.EGL_RED_SIZE)
val green = getAttribValue(display, config, EGL14.EGL_GREEN_SIZE)
val blue = getAttribValue(display, config, EGL14.EGL_BLUE_SIZE)
val alpha = getAttribValue(display, config, EGL14.EGL_ALPHA_SIZE)
val depth = getAttribValue(display, config, EGL14.EGL_DEPTH_SIZE)
val stencil = getAttribValue(display, config, EGL14.EGL_STENCIL_SIZE)
println("EGLConfig $id: (red = $red, green = $green, blue = $blue, alpha = $alpha, depth = $depth, stencil = $stencil)")
}
override fun chooseConfig(egl: EGL10, display: EGLDisplay): EGLConfig {
override fun chooseConfig(display: EGLDisplay): EGLConfig {
val configs = listOf(
ConfigValues(8, 8, 8, 8, 16, 8),
ConfigValues(8, 8, 8, 0, 16, 8),
@@ -797,9 +805,9 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
)
return configs.firstNotNullOf {
val config = testConfig(egl, display, it)
val config = testConfig(display, it)
if (config != null) {
printConfig(egl, display, config)
printConfig(display, config)
}
config
@@ -1,5 +1,7 @@
package com.geode.launcher
import android.app.Activity
import android.view.Surface
import androidx.annotation.Keep
import com.geode.launcher.utils.Constants
@@ -16,4 +18,10 @@ object LauncherFix {
external fun enableExceptionsRenaming()
external fun performPatches()
external fun initFramePacing(activity: Activity)
external fun destroyFramePacing()
external fun swapFrame(display: Long, surface: Long): Boolean
external fun setSurface(surface: Surface?)
external fun setSwapInterval(intervalNs: Long)
}
@@ -169,6 +169,12 @@ fun DeveloperSettingsScreen(onBackPressedDispatcher: OnBackPressedDispatcher?) {
}
)
SettingsCard(
title = stringResource(R.string.preference_native_pacing),
description = stringResource(R.string.preference_native_pacing_description),
preferenceKey = PreferenceUtils.Key.NATIVE_FRAME_PACING,
)
/*
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
SettingsCard(
@@ -150,6 +150,7 @@ class PreferenceUtils(private val sharedPreferences: SharedPreferences) {
LAST_LAUNCHER_UPDATE,
DISABLE_UPDATE_CACHE,
PREBUNDLED_GEODE_MIGRATION_PERFORMED,
NATIVE_FRAME_PACING,
}
private fun defaultValueForBooleanKey(key: Key): Boolean {
@@ -201,6 +202,7 @@ class PreferenceUtils(private val sharedPreferences: SharedPreferences) {
Key.LAST_LAUNCHER_UPDATE -> "PreferenceLastLauncherUpdate"
Key.DISABLE_UPDATE_CACHE -> "PreferenceDisableUpdateCache"
Key.PREBUNDLED_GEODE_MIGRATION_PERFORMED -> "PreferencePrebundledGeodeMigrationPerformed"
Key.NATIVE_FRAME_PACING -> "PreferenceNativeFramePacing"
// Key.RELEASE_CHANNEL -> "PreferenceReleaseChannel"
// Key.LAST_DISMISSED_UPDATE -> "PreferenceLastDismissedUpdate"
// Key.FORCE_HRR -> "PreferenceForceHighRefreshRate"
@@ -17,15 +17,18 @@ import android.view.inputmethod.InputMethodManager
import androidx.annotation.RequiresApi
import androidx.appcompat.widget.AppCompatEditText
import com.customRobTop.BaseRobTopActivity
import com.geode.launcher.ExplicitGLSurfaceView
import com.geode.launcher.LauncherFix
import com.geode.launcher.utils.GeodeUtils
import kotlin.math.abs
import kotlin.math.roundToLong
private const val HANDLER_OPEN_IME_KEYBOARD = 2
private const val HANDLER_CLOSE_IME_KEYBOARD = 3
private const val MS_TO_NS = 1_000_000
private const val SEC_TO_NS = 1_000_000_000.0
class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
class Cocos2dxGLSurfaceView(context: Context) : ExplicitGLSurfaceView(context) {
companion object {
lateinit var cocos2dxGLSurfaceView: Cocos2dxGLSurfaceView
private lateinit var handler: Handler
@@ -64,6 +67,7 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
var controllerEventsEnabled = false
private set
var supportsResizeEvents = false
var saveNativeFramePacing = false
var cocos2dxEditText: AppCompatEditText? = null
set(value) {
@@ -139,6 +143,10 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
println("updateRefreshRate: selecting refresh rate of ${chosenDisplay.refreshRate} (display ${chosenDisplay.modeId})")
if (saveNativeFramePacing) {
LauncherFix.setSwapInterval((1_000_000_000L / chosenRefreshRate).roundToLong())
}
holder.surface.setFrameRate(chosenRefreshRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT)
if (isAttachedToWindow) {
(context as Activity).window.attributes.preferredRefreshRate = chosenRefreshRate
@@ -153,6 +161,7 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
override fun onResume() {
super.onResume()
renderMode = RENDERMODE_CONTINUOUSLY
queueEvent { cocos2dxRenderer.handleOnResume() }
}
@@ -533,6 +542,11 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
setRenderer(this.cocos2dxRenderer)
}
override fun setNativeFramePacing(nativeFramePacing: Boolean) {
super.setNativeFramePacing(nativeFramePacing)
saveNativeFramePacing = nativeFramePacing
}
private fun getContentText(): String {
return cocos2dxRenderer.getContentText()
}
@@ -567,6 +581,10 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
inputManager.unregisterInputDeviceListener(InputListener)
}
fun setMaxFrameRate(rate: Int) {
cocos2dxRenderer.limitFrameRate(rate)
}
object InputListener : InputManager.InputDeviceListener {
override fun onInputDeviceAdded(deviceId: Int) {
GeodeUtils.inputDeviceAdded(deviceId)
@@ -1,15 +1,14 @@
package org.cocos2dx.lib
import android.opengl.GLSurfaceView
import android.opengl.EGLConfig
import android.os.Build
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.opengles.GL10
import com.geode.launcher.ExplicitGLSurfaceView
private const val NANOSECONDS_PER_SECOND = 1000000000L
private const val NANOSECONDS_PER_MICROSECOND = 1000000L
@Suppress("unused", "KotlinJniMissingFunction")
class Cocos2dxRenderer(private var handler: Cocos2dxGLSurfaceView) : GLSurfaceView.Renderer {
class Cocos2dxRenderer(private var handler: Cocos2dxGLSurfaceView) : ExplicitGLSurfaceView.Renderer {
companion object {
@JvmStatic
fun setAnimationInterval(@Suppress("UNUSED_PARAMETER") animationInterval: Double) {
@@ -80,12 +79,12 @@ class Cocos2dxRenderer(private var handler: Cocos2dxGLSurfaceView) : GLSurfaceVi
screenHeight = surfaceHeight
}
override fun onSurfaceCreated(gl10: GL10?, eglConfig: EGLConfig?) {
override fun onSurfaceCreated(eglConfig: EGLConfig?) {
nativeInit(screenWidth, screenHeight)
lastTickInNanoSeconds = System.nanoTime()
}
override fun onSurfaceChanged(gl10: GL10?, width: Int, height: Int) {
override fun onSurfaceChanged(width: Int, height: Int) {
if (setFrameRate && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
handler.updateRefreshRate()
}
@@ -93,7 +92,7 @@ class Cocos2dxRenderer(private var handler: Cocos2dxGLSurfaceView) : GLSurfaceVi
println("renderer surfaceChanged: ${width}x${height}@${handler.display.refreshRate}fps")
}
override fun onDrawFrame(gl: GL10?) {
override fun onDrawFrame() {
val animationInterval = mAnimationInterval
if (animationInterval == null) {
nativeRender()
+3
View File
@@ -298,6 +298,9 @@
<string name="preference_profiles_delete_alt">Delete</string>
<string name="preference_profiles_delete_undo_alt">Undo</string>
<string name="preference_native_pacing">Native frame pacing</string>
<string name="preference_native_pacing_description">Reduces latency, but may worsen frame drops.</string>
<!-- ApplicationLogsActivity strings -->
<string name="title_activity_application_logs">Application logs</string>
<string name="application_logs_no_logs">No logs available.</string>