Add Swappy & Pre-Transformed Swapchain

- Adds Swappy for Android for stable frame pacing
- Implements pre-transformed Swapchain so that Godot's compositor is in
charge of rotating the screen instead of Android's compositor
(performance optimization for phones that don't have HW rotator)

============================

The work was performed by collaboration of TheForge and Google. I am
merely splitting it up into smaller PRs and cleaning it up.

Changes from original PR:

- Removed "display/window/frame_pacing/android/target_frame_rate" option
to use Engine::get_max_fps instead.
- Target framerate can be changed at runtime using Engine::set_max_fps.
- Swappy is enabled by default.
- Added documentation.
- enable_auto_swap setting is replaced with swappy_mode.
This commit is contained in:
Matias N. Goldberg
2024-05-05 19:15:56 -03:00
parent 92e51fca72
commit aaa0e2fddf
23 changed files with 1064 additions and 14 deletions
+35
View File
@@ -95,6 +95,15 @@ def install_ndk_if_needed(env: "SConsEnvironment"):
env["ANDROID_NDK_ROOT"] = get_android_ndk_root(env)
def detect_swappy():
archs = ["arm64-v8a", "armeabi-v7a", "x86", "x86_64"]
has_swappy = True
for arch in archs:
if not os.path.isfile("thirdparty/swappy-frame-pacing/" + arch + "/libswappy_static.a"):
has_swappy = False
return has_swappy
def configure(env: "SConsEnvironment"):
# Validate arch.
supported_arches = ["x86_32", "x86_64", "arm32", "arm64"]
@@ -176,19 +185,42 @@ def configure(env: "SConsEnvironment"):
)
)
has_swappy = detect_swappy()
if not has_swappy:
print_warning(
"Swappy Frame Pacing not detected! It is strongly recommended you download it from https://github.com/darksylinc/godot-swappy/releases and extract it so that the following files can be found:\n"
+ " thirdparty/swappy-frame-pacing/arm64-v8a/libswappy_static.a\n"
+ " thirdparty/swappy-frame-pacing/armeabi-v7a/libswappy_static.a\n"
+ " thirdparty/swappy-frame-pacing/x86/libswappy_static.a\n"
+ " thirdparty/swappy-frame-pacing/x86_64/libswappy_static.a\n"
+ "Without Swappy, Godot apps on Android will inevitable suffer stutter and struggle to keep consistent 30/60/90/120 fps. Though Swappy cannot guarantee your app will be stutter-free, not having Swappy will guarantee there will be stutter even on the best phones and the most simple of scenes."
)
if env["swappy"]:
print_error("Use build option `swappy=no` to ignore missing Swappy dependency and build without it.")
sys.exit(255)
if get_min_sdk_version(env["ndk_platform"]) >= 24:
env.Append(CPPDEFINES=[("_FILE_OFFSET_BITS", 64)])
if env["arch"] == "x86_32":
# The NDK adds this if targeting API < 24, so we can drop it when Godot targets it at least
env.Append(CCFLAGS=["-mstackrealign"])
if has_swappy:
env.Append(LIBPATH=["../../thirdparty/swappy-frame-pacing/x86"])
elif env["arch"] == "x86_64":
if has_swappy:
env.Append(LIBPATH=["../../thirdparty/swappy-frame-pacing/x86_64"])
elif env["arch"] == "arm32":
env.Append(CCFLAGS="-march=armv7-a -mfloat-abi=softfp".split())
env.Append(CPPDEFINES=["__ARM_ARCH_7__", "__ARM_ARCH_7A__"])
env.Append(CPPDEFINES=["__ARM_NEON__"])
if has_swappy:
env.Append(LIBPATH=["../../thirdparty/swappy-frame-pacing/armeabi-v7a"])
elif env["arch"] == "arm64":
env.Append(CCFLAGS=["-mfix-cortex-a53-835769"])
env.Append(CPPDEFINES=["__ARM_ARCH_8A__"])
if has_swappy:
env.Append(LIBPATH=["../../thirdparty/swappy-frame-pacing/arm64-v8a"])
env.Append(CCFLAGS=["-ffp-contract=off"])
@@ -203,6 +235,9 @@ def configure(env: "SConsEnvironment"):
if env["vulkan"]:
env.Append(CPPDEFINES=["VULKAN_ENABLED", "RD_ENABLED"])
if has_swappy:
env.Append(CPPDEFINES=["SWAPPY_FRAME_PACING_ENABLED"])
env.Append(LIBS=["swappy_static"])
if not env["use_volk"]:
env.Append(LIBS=["vulkan"])
@@ -216,6 +216,14 @@ DisplayServer::ScreenOrientation DisplayServerAndroid::screen_get_orientation(in
return (ScreenOrientation)orientation;
}
int DisplayServerAndroid::screen_get_internal_current_rotation(int p_screen) const {
GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java();
ERR_FAIL_NULL_V(godot_io_java, 0);
const int rotation = godot_io_java->get_internal_current_screen_rotation();
return rotation;
}
int DisplayServerAndroid::get_screen_count() const {
return 1;
}
@@ -124,6 +124,7 @@ public:
virtual void screen_set_orientation(ScreenOrientation p_orientation, int p_screen = SCREEN_OF_MAIN_WINDOW) override;
virtual ScreenOrientation screen_get_orientation(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual int screen_get_internal_current_rotation(int p_screen) const override;
virtual int get_screen_count() const override;
virtual int get_primary_screen() const override;
@@ -47,6 +47,7 @@ import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.DisplayCutout;
import android.view.Surface;
import android.view.WindowInsets;
import androidx.core.content.FileProvider;
@@ -295,6 +296,28 @@ public class GodotIO {
}
}
/**
This function is used by DisplayServer::screen_get_internal_current_rotation (C++)
and is used to implement a performance optimization in devices that do not offer
a HW rotator.
@return
Rotation in degrees, in multiples of 90°
*/
public int getInternalCurrentScreenRotation() {
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
switch (rotation) {
case Surface.ROTATION_90:
return 90;
case Surface.ROTATION_180:
return 180;
case Surface.ROTATION_270:
return 270;
default:
return 0;
}
}
public void setEdit(GodotEditText _edit) {
edit = _edit;
}
@@ -66,6 +66,7 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc
_has_hardware_keyboard = p_env->GetMethodID(cls, "hasHardwareKeyboard", "()Z");
_set_screen_orientation = p_env->GetMethodID(cls, "setScreenOrientation", "(I)V");
_get_screen_orientation = p_env->GetMethodID(cls, "getScreenOrientation", "()I");
_get_internal_current_screen_rotation = p_env->GetMethodID(cls, "getInternalCurrentScreenRotation", "()I");
_get_system_dir = p_env->GetMethodID(cls, "getSystemDir", "(IZ)Ljava/lang/String;");
}
}
@@ -267,6 +268,16 @@ int GodotIOJavaWrapper::get_screen_orientation() {
}
}
int GodotIOJavaWrapper::get_internal_current_screen_rotation() {
if (_get_internal_current_screen_rotation) {
JNIEnv *env = get_jni_env();
ERR_FAIL_NULL_V(env, 0);
return env->CallIntMethod(godot_io_instance, _get_internal_current_screen_rotation);
} else {
return 0;
}
}
String GodotIOJavaWrapper::get_system_dir(int p_dir, bool p_shared_storage) {
if (_get_system_dir) {
JNIEnv *env = get_jni_env();
+2
View File
@@ -61,6 +61,7 @@ private:
jmethodID _has_hardware_keyboard = 0;
jmethodID _set_screen_orientation = 0;
jmethodID _get_screen_orientation = 0;
jmethodID _get_internal_current_screen_rotation = 0;
jmethodID _get_system_dir = 0;
public:
@@ -88,6 +89,7 @@ public:
void set_vk_height(int p_height);
void set_screen_orientation(int p_orient);
int get_screen_orientation();
int get_internal_current_screen_rotation();
String get_system_dir(int p_dir, bool p_shared_storage);
};