Skip to content

Commit 748f407

Browse files
authored
Merge pull request godotengine#96439 from darksylinc/matias-TheForge-pr03-rebased
Add Swappy & Pre-Transformed Swapchain
2 parents 08f9cba + aaa0e2f commit 748f407

23 files changed

+1064
-14
lines changed

.github/workflows/android_builds.yml

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,19 @@ jobs:
2424
cache-name: android-editor
2525
target: editor
2626
tests: false
27-
sconsflags: arch=arm64 production=yes
27+
sconsflags: arch=arm64 production=yes swappy=yes
2828

2929
- name: Template arm32 (target=template_release, arch=arm32)
3030
cache-name: android-template-arm32
3131
target: template_release
3232
tests: false
33-
sconsflags: arch=arm32
33+
sconsflags: arch=arm32 swappy=yes
3434

3535
- name: Template arm64 (target=template_release, arch=arm64)
3636
cache-name: android-template-arm64
3737
target: template_release
3838
tests: false
39-
sconsflags: arch=arm64
39+
sconsflags: arch=arm64 swappy=yes
4040

4141
steps:
4242
- name: Checkout
@@ -59,6 +59,17 @@ jobs:
5959
- name: Setup Python and SCons
6060
uses: ./.github/actions/godot-deps
6161

62+
- name: Download pre-built Android Swappy Frame Pacing Library
63+
uses: dsaltares/[email protected]
64+
with:
65+
repo: darksylinc/godot-swappy
66+
version: tags/v2023.3.0.0
67+
file: godot-swappy.7z
68+
target: swappy/godot-swappy.7z
69+
70+
- name: Extract pre-built Android Swappy Frame Pacing Library
71+
run: 7za x -y swappy/godot-swappy.7z -o${{github.workspace}}/thirdparty/swappy-frame-pacing
72+
6273
- name: Compilation
6374
uses: ./.github/actions/godot-build
6475
with:

SConstruct

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ opts.Add(BoolVariable("use_volk", "Use the volk library to load the Vulkan loade
229229
opts.Add(BoolVariable("disable_exceptions", "Force disabling exception handling code", True))
230230
opts.Add("custom_modules", "A list of comma-separated directory paths containing custom modules to build.", "")
231231
opts.Add(BoolVariable("custom_modules_recursive", "Detect custom modules recursively for each specified path.", True))
232+
opts.Add(BoolVariable("swappy", "Use Swappy Frame Pacing Library in Android builds.", False))
232233

233234
# Advanced options
234235
opts.Add(
@@ -611,6 +612,8 @@ if env["dev_mode"]:
611612
if env["production"]:
612613
env["use_static_cpp"] = methods.get_cmdline_bool("use_static_cpp", True)
613614
env["debug_symbols"] = methods.get_cmdline_bool("debug_symbols", False)
615+
if platform_arg == "android":
616+
env["swappy"] = methods.get_cmdline_bool("swappy", True)
614617
# LTO "auto" means we handle the preferred option in each platform detect.py.
615618
env["lto"] = ARGUMENTS.get("lto", "auto")
616619

core/config/engine.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include "core/license.gen.h"
3737
#include "core/variant/typed_array.h"
3838
#include "core/version.h"
39+
#include "servers/rendering/rendering_device.h"
3940

4041
void Engine::set_physics_ticks_per_second(int p_ips) {
4142
ERR_FAIL_COND_MSG(p_ips <= 0, "Engine iterations per second must be greater than 0.");
@@ -68,6 +69,11 @@ double Engine::get_physics_jitter_fix() const {
6869

6970
void Engine::set_max_fps(int p_fps) {
7071
_max_fps = p_fps > 0 ? p_fps : 0;
72+
73+
RenderingDevice *rd = RenderingDevice::get_singleton();
74+
if (rd) {
75+
rd->_set_max_fps(_max_fps);
76+
}
7177
}
7278

7379
int Engine::get_max_fps() const {

core/config/project_settings.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1503,6 +1503,10 @@ ProjectSettings::ProjectSettings() {
15031503
GLOBAL_DEF("display/window/subwindows/embed_subwindows", true);
15041504
// Keep the enum values in sync with the `DisplayServer::VSyncMode` enum.
15051505
custom_prop_info["display/window/vsync/vsync_mode"] = PropertyInfo(Variant::INT, "display/window/vsync/vsync_mode", PROPERTY_HINT_ENUM, "Disabled,Enabled,Adaptive,Mailbox");
1506+
1507+
GLOBAL_DEF("display/window/frame_pacing/android/enable_frame_pacing", true);
1508+
GLOBAL_DEF(PropertyInfo(Variant::INT, "display/window/frame_pacing/android/swappy_mode", PROPERTY_HINT_ENUM, "pipeline_forced_on,auto_fps_pipeline_forced_on,auto_fps_auto_pipeline"), 2);
1509+
15061510
custom_prop_info["rendering/driver/threads/thread_model"] = PropertyInfo(Variant::INT, "rendering/driver/threads/thread_model", PROPERTY_HINT_ENUM, "Single-Unsafe,Single-Safe,Multi-Threaded");
15071511
GLOBAL_DEF("physics/2d/run_on_separate_thread", false);
15081512
GLOBAL_DEF("physics/3d/run_on_separate_thread", false);

doc/classes/ProjectSettings.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,17 @@
818818
<member name="display/window/energy_saving/keep_screen_on" type="bool" setter="" getter="" default="true">
819819
If [code]true[/code], keeps the screen on (even in case of inactivity), so the screensaver does not take over. Works on desktop and mobile platforms.
820820
</member>
821+
<member name="display/window/frame_pacing/android/enable_frame_pacing" type="bool" setter="" getter="" default="true">
822+
Enable Swappy for stable frame pacing on Android. Highly recommended.
823+
[b]Note:[/b] This option will be forced off when using OpenXR.
824+
</member>
825+
<member name="display/window/frame_pacing/android/swappy_mode" type="int" setter="" getter="" default="2">
826+
Swappy mode to use. The options are:
827+
- pipeline_forced_on: Try to honor [member Engine.max_fps]. Pipelining is always on. This is the same behavior as Desktop PC.
828+
- auto_fps_pipeline_forced_on: Autocalculate max fps. Actual max_fps will be between 0 and [member Engine.max_fps]. While this sounds convenient, beware that Swappy will often downgrade max fps until it finds something that can be met and sustained. That means if your game runs between 40fps and 60fps on a 60hz screen, after some time Swappy will downgrade max fps so that the game renders at perfect 30fps.
829+
- auto_fps_auto_pipeline: Same as auto_fps_pipeline_forced_on, but if Swappy detects that rendering is very fast (e.g. it takes &lt; 8ms to render on a 60hz screen) Swappy will disable pipelining to minimize input latency. This is the default.
830+
[b]Note:[/b] If [member Engine.max_fps] is 0, actual max_fps will considered as to be the screen's refresh rate (often 60hz, 90hz or 120hz depending on device model and OS settings).
831+
</member>
821832
<member name="display/window/handheld/orientation" type="int" setter="" getter="" default="0">
822833
The default screen orientation to use on mobile devices. See [enum DisplayServer.ScreenOrientation] for possible values.
823834
[b]Note:[/b] When set to a portrait orientation, this project setting does not flip the project resolution's width and height automatically. Instead, you have to set [member display/window/size/viewport_width] and [member display/window/size/viewport_height] accordingly.

drivers/vulkan/rendering_device_driver_vulkan.cpp

Lines changed: 143 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,16 @@
3535
#include "thirdparty/misc/smolv.h"
3636
#include "vulkan_hooks.h"
3737

38+
#if defined(ANDROID_ENABLED)
39+
#include "platform/android/java_godot_wrapper.h"
40+
#include "platform/android/os_android.h"
41+
#include "platform/android/thread_jandroid.h"
42+
#endif
43+
44+
#if defined(SWAPPY_FRAME_PACING_ENABLED)
45+
#include "thirdparty/swappy-frame-pacing/swappyVk.h"
46+
#endif
47+
3848
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
3949

4050
#define PRINT_NATIVE_COMMANDS 0
@@ -538,6 +548,37 @@ Error RenderingDeviceDriverVulkan::_initialize_device_extensions() {
538548
err = vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &device_extension_count, device_extensions.ptr());
539549
ERR_FAIL_COND_V(err != VK_SUCCESS, ERR_CANT_CREATE);
540550

551+
#if defined(SWAPPY_FRAME_PACING_ENABLED)
552+
if (swappy_frame_pacer_enable) {
553+
char **swappy_required_extensions;
554+
uint32_t swappy_required_extensions_count = 0;
555+
// Determine number of extensions required by Swappy frame pacer.
556+
SwappyVk_determineDeviceExtensions(physical_device, device_extension_count, device_extensions.ptr(), &swappy_required_extensions_count, nullptr);
557+
558+
if (swappy_required_extensions_count < device_extension_count) {
559+
// Determine the actual extensions.
560+
swappy_required_extensions = (char **)malloc(swappy_required_extensions_count * sizeof(char *));
561+
char *pRequiredExtensionsData = (char *)malloc(swappy_required_extensions_count * (VK_MAX_EXTENSION_NAME_SIZE + 1));
562+
for (uint32_t i = 0; i < swappy_required_extensions_count; i++) {
563+
swappy_required_extensions[i] = &pRequiredExtensionsData[i * (VK_MAX_EXTENSION_NAME_SIZE + 1)];
564+
}
565+
SwappyVk_determineDeviceExtensions(physical_device, device_extension_count,
566+
device_extensions.ptr(), &swappy_required_extensions_count, swappy_required_extensions);
567+
568+
// Enable extensions requested by Swappy.
569+
for (uint32_t i = 0; i < swappy_required_extensions_count; i++) {
570+
CharString extension_name(swappy_required_extensions[i]);
571+
if (requested_device_extensions.has(extension_name)) {
572+
enabled_device_extension_names.insert(extension_name);
573+
}
574+
}
575+
576+
free(pRequiredExtensionsData);
577+
free(swappy_required_extensions);
578+
}
579+
}
580+
#endif
581+
541582
#ifdef DEV_ENABLED
542583
for (uint32_t i = 0; i < device_extension_count; i++) {
543584
print_verbose(String("VULKAN: Found device extension ") + String::utf8(device_extensions[i].extensionName));
@@ -1379,6 +1420,18 @@ Error RenderingDeviceDriverVulkan::initialize(uint32_t p_device_index, uint32_t
13791420
breadcrumb_buffer = buffer_create(2u * sizeof(uint32_t) * BREADCRUMB_BUFFER_ENTRIES, BufferUsageBits::BUFFER_USAGE_TRANSFER_TO_BIT, MemoryAllocationType::MEMORY_ALLOCATION_TYPE_CPU);
13801421
#endif
13811422

1423+
#if defined(SWAPPY_FRAME_PACING_ENABLED)
1424+
swappy_frame_pacer_enable = GLOBAL_GET("display/window/frame_pacing/android/enable_frame_pacing");
1425+
swappy_mode = GLOBAL_GET("display/window/frame_pacing/android/swappy_mode");
1426+
1427+
if (VulkanHooks::get_singleton() != nullptr) {
1428+
// Hooks control device creation & possibly presentation
1429+
// (e.g. OpenXR) thus it's too risky to use Swappy.
1430+
swappy_frame_pacer_enable = false;
1431+
OS::get_singleton()->print("VulkanHooks detected (e.g. OpenXR): Force-disabling Swappy Frame Pacing.\n");
1432+
}
1433+
#endif
1434+
13821435
return OK;
13831436
}
13841437

@@ -2364,6 +2417,14 @@ RDD::CommandQueueID RenderingDeviceDriverVulkan::command_queue_create(CommandQue
23642417

23652418
ERR_FAIL_COND_V_MSG(picked_queue_index >= queue_family.size(), CommandQueueID(), "A queue in the picked family could not be found.");
23662419

2420+
#if defined(SWAPPY_FRAME_PACING_ENABLED)
2421+
if (swappy_frame_pacer_enable) {
2422+
VkQueue selected_queue;
2423+
vkGetDeviceQueue(vk_device, family_index, picked_queue_index, &selected_queue);
2424+
SwappyVk_setQueueFamilyIndex(vk_device, selected_queue, family_index);
2425+
}
2426+
#endif
2427+
23672428
// Create the virtual queue.
23682429
CommandQueue *command_queue = memnew(CommandQueue);
23692430
command_queue->queue_family = family_index;
@@ -2509,7 +2570,16 @@ Error RenderingDeviceDriverVulkan::command_queue_execute_and_present(CommandQueu
25092570
present_info.pResults = results.ptr();
25102571

25112572
device_queue.submit_mutex.lock();
2573+
#if defined(SWAPPY_FRAME_PACING_ENABLED)
2574+
if (swappy_frame_pacer_enable) {
2575+
err = SwappyVk_queuePresent(device_queue.queue, &present_info);
2576+
} else {
2577+
err = device_functions.QueuePresentKHR(device_queue.queue, &present_info);
2578+
}
2579+
#else
25122580
err = device_functions.QueuePresentKHR(device_queue.queue, &present_info);
2581+
#endif
2582+
25132583
device_queue.submit_mutex.unlock();
25142584

25152585
// Set the index to an invalid value. If any of the swap chains returned out of date, indicate it should be resized the next time it's acquired.
@@ -2691,6 +2761,14 @@ void RenderingDeviceDriverVulkan::_swap_chain_release(SwapChain *swap_chain) {
26912761
swap_chain->framebuffers.clear();
26922762

26932763
if (swap_chain->vk_swapchain != VK_NULL_HANDLE) {
2764+
#if defined(SWAPPY_FRAME_PACING_ENABLED)
2765+
if (swappy_frame_pacer_enable) {
2766+
// Swappy has a bug where the ANativeWindow will be leaked if we call
2767+
// SwappyVk_destroySwapchain, so we must release it by hand.
2768+
SwappyVk_setWindow(vk_device, swap_chain->vk_swapchain, nullptr);
2769+
SwappyVk_destroySwapchain(vk_device, swap_chain->vk_swapchain);
2770+
}
2771+
#endif
26942772
device_functions.DestroySwapchainKHR(vk_device, swap_chain->vk_swapchain, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_SWAPCHAIN_KHR));
26952773
swap_chain->vk_swapchain = VK_NULL_HANDLE;
26962774
}
@@ -2807,6 +2885,20 @@ Error RenderingDeviceDriverVulkan::swap_chain_resize(CommandQueueID p_cmd_queue,
28072885
VkResult err = functions.GetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, surface->vk_surface, &surface_capabilities);
28082886
ERR_FAIL_COND_V(err != VK_SUCCESS, ERR_CANT_CREATE);
28092887

2888+
// No swapchain yet, this is the first time we're creating it.
2889+
if (!swap_chain->vk_swapchain) {
2890+
uint32_t width = surface_capabilities.currentExtent.width;
2891+
uint32_t height = surface_capabilities.currentExtent.height;
2892+
if (surface_capabilities.currentTransform & VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR ||
2893+
surface_capabilities.currentTransform & VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR) {
2894+
// Swap to get identity width and height.
2895+
surface_capabilities.currentExtent.height = width;
2896+
surface_capabilities.currentExtent.width = height;
2897+
}
2898+
2899+
native_display_size = surface_capabilities.currentExtent;
2900+
}
2901+
28102902
VkExtent2D extent;
28112903
if (surface_capabilities.currentExtent.width == 0xFFFFFFFF) {
28122904
// The current extent is currently undefined, so the current surface width and height will be clamped to the surface's capabilities.
@@ -2871,15 +2963,8 @@ Error RenderingDeviceDriverVulkan::swap_chain_resize(CommandQueueID p_cmd_queue,
28712963
desired_swapchain_images = MIN(desired_swapchain_images, surface_capabilities.maxImageCount);
28722964
}
28732965

2874-
// Prefer identity transform if it's supported, use the current transform otherwise.
2875-
// This behavior is intended as Godot does not supported native rotation in platforms that use these bits.
28762966
// Refer to the comment in command_queue_present() for more details.
2877-
VkSurfaceTransformFlagBitsKHR surface_transform_bits;
2878-
if (surface_capabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) {
2879-
surface_transform_bits = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
2880-
} else {
2881-
surface_transform_bits = surface_capabilities.currentTransform;
2882-
}
2967+
VkSurfaceTransformFlagBitsKHR surface_transform_bits = surface_capabilities.currentTransform;
28832968

28842969
VkCompositeAlphaFlagBitsKHR composite_alpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
28852970
if (OS::get_singleton()->is_layered_allowed() || !(surface_capabilities.supportedCompositeAlpha & composite_alpha)) {
@@ -2906,7 +2991,7 @@ Error RenderingDeviceDriverVulkan::swap_chain_resize(CommandQueueID p_cmd_queue,
29062991
swap_create_info.minImageCount = desired_swapchain_images;
29072992
swap_create_info.imageFormat = swap_chain->format;
29082993
swap_create_info.imageColorSpace = swap_chain->color_space;
2909-
swap_create_info.imageExtent = extent;
2994+
swap_create_info.imageExtent = native_display_size;
29102995
swap_create_info.imageArrayLayers = 1;
29112996
swap_create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
29122997
swap_create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
@@ -2917,6 +3002,39 @@ Error RenderingDeviceDriverVulkan::swap_chain_resize(CommandQueueID p_cmd_queue,
29173002
err = device_functions.CreateSwapchainKHR(vk_device, &swap_create_info, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_SWAPCHAIN_KHR), &swap_chain->vk_swapchain);
29183003
ERR_FAIL_COND_V(err != VK_SUCCESS, ERR_CANT_CREATE);
29193004

3005+
#if defined(SWAPPY_FRAME_PACING_ENABLED)
3006+
if (swappy_frame_pacer_enable) {
3007+
const double max_fps = Engine::get_singleton()->get_max_fps();
3008+
const uint64_t max_time = max_fps > 0 ? uint64_t((1000.0 * 1000.0 * 1000.0) / max_fps) : 0;
3009+
3010+
SwappyVk_initAndGetRefreshCycleDuration(get_jni_env(), static_cast<OS_Android *>(OS::get_singleton())->get_godot_java()->get_activity(), physical_device,
3011+
vk_device, swap_chain->vk_swapchain, &swap_chain->refresh_duration);
3012+
SwappyVk_setWindow(vk_device, swap_chain->vk_swapchain, static_cast<OS_Android *>(OS::get_singleton())->get_native_window());
3013+
SwappyVk_setSwapIntervalNS(vk_device, swap_chain->vk_swapchain, MAX(swap_chain->refresh_duration, max_time));
3014+
3015+
enum SwappyModes {
3016+
PIPELINE_FORCED_ON,
3017+
AUTO_FPS_PIPELINE_FORCED_ON,
3018+
AUTO_FPS_AUTO_PIPELINE,
3019+
};
3020+
3021+
switch (swappy_mode) {
3022+
case PIPELINE_FORCED_ON:
3023+
SwappyVk_setAutoSwapInterval(true);
3024+
SwappyVk_setAutoPipelineMode(true);
3025+
break;
3026+
case AUTO_FPS_PIPELINE_FORCED_ON:
3027+
SwappyVk_setAutoSwapInterval(true);
3028+
SwappyVk_setAutoPipelineMode(false);
3029+
break;
3030+
case AUTO_FPS_AUTO_PIPELINE:
3031+
SwappyVk_setAutoSwapInterval(false);
3032+
SwappyVk_setAutoPipelineMode(false);
3033+
break;
3034+
}
3035+
}
3036+
#endif
3037+
29203038
uint32_t image_count = 0;
29213039
err = device_functions.GetSwapchainImagesKHR(vk_device, swap_chain->vk_swapchain, &image_count, nullptr);
29223040
ERR_FAIL_COND_V(err != VK_SUCCESS, ERR_CANT_CREATE);
@@ -3064,6 +3182,22 @@ RDD::DataFormat RenderingDeviceDriverVulkan::swap_chain_get_format(SwapChainID p
30643182
}
30653183
}
30663184

3185+
void RenderingDeviceDriverVulkan::swap_chain_set_max_fps(SwapChainID p_swap_chain, int p_max_fps) {
3186+
DEV_ASSERT(p_swap_chain.id != 0);
3187+
3188+
#ifdef SWAPPY_FRAME_PACING_ENABLED
3189+
if (!swappy_frame_pacer_enable) {
3190+
return;
3191+
}
3192+
3193+
SwapChain *swap_chain = (SwapChain *)(p_swap_chain.id);
3194+
if (swap_chain->vk_swapchain != VK_NULL_HANDLE) {
3195+
const uint64_t max_time = p_max_fps > 0 ? uint64_t((1000.0 * 1000.0 * 1000.0) / p_max_fps) : 0;
3196+
SwappyVk_setSwapIntervalNS(vk_device, swap_chain->vk_swapchain, MAX(swap_chain->refresh_duration, max_time));
3197+
}
3198+
#endif
3199+
}
3200+
30673201
void RenderingDeviceDriverVulkan::swap_chain_free(SwapChainID p_swap_chain) {
30683202
DEV_ASSERT(p_swap_chain.id != 0);
30693203

drivers/vulkan/rendering_device_driver_vulkan.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,11 @@ class RenderingDeviceDriverVulkan : public RenderingDeviceDriver {
142142
bool device_fault_support = false;
143143
#if defined(VK_TRACK_DEVICE_MEMORY)
144144
bool device_memory_report_support = false;
145+
#endif
146+
#if defined(SWAPPY_FRAME_PACING_ENABLED)
147+
// Swappy frame pacer for Android.
148+
bool swappy_frame_pacer_enable = false;
149+
uint8_t swappy_mode = 2; // See default value for display/window/frame_pacing/android/swappy_mode.
145150
#endif
146151
DeviceFunctions device_functions;
147152

@@ -355,16 +360,21 @@ class RenderingDeviceDriverVulkan : public RenderingDeviceDriver {
355360
LocalVector<uint32_t> command_queues_acquired_semaphores;
356361
RenderPassID render_pass;
357362
uint32_t image_index = 0;
363+
#ifdef ANDROID_ENABLED
364+
uint64_t refresh_duration = 0;
365+
#endif
358366
};
359367

360368
void _swap_chain_release(SwapChain *p_swap_chain);
369+
VkExtent2D native_display_size;
361370

362371
public:
363372
virtual SwapChainID swap_chain_create(RenderingContextDriver::SurfaceID p_surface) override final;
364373
virtual Error swap_chain_resize(CommandQueueID p_cmd_queue, SwapChainID p_swap_chain, uint32_t p_desired_framebuffer_count) override final;
365374
virtual FramebufferID swap_chain_acquire_framebuffer(CommandQueueID p_cmd_queue, SwapChainID p_swap_chain, bool &r_resize_required) override final;
366375
virtual RenderPassID swap_chain_get_render_pass(SwapChainID p_swap_chain) override final;
367376
virtual DataFormat swap_chain_get_format(SwapChainID p_swap_chain) override final;
377+
virtual void swap_chain_set_max_fps(SwapChainID p_swap_chain, int p_max_fps) override final;
368378
virtual void swap_chain_free(SwapChainID p_swap_chain) override final;
369379

370380
/*********************/

0 commit comments

Comments
 (0)