mirror of
https://github.com/godotengine/godot.git
synced 2024-11-21 11:32:13 +00:00
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:
parent
92e51fca72
commit
aaa0e2fddf
17
.github/workflows/android_builds.yml
vendored
17
.github/workflows/android_builds.yml
vendored
@ -24,19 +24,19 @@ jobs:
|
||||
cache-name: android-editor
|
||||
target: editor
|
||||
tests: false
|
||||
sconsflags: arch=arm64 production=yes
|
||||
sconsflags: arch=arm64 production=yes swappy=yes
|
||||
|
||||
- name: Template arm32 (target=template_release, arch=arm32)
|
||||
cache-name: android-template-arm32
|
||||
target: template_release
|
||||
tests: false
|
||||
sconsflags: arch=arm32
|
||||
sconsflags: arch=arm32 swappy=yes
|
||||
|
||||
- name: Template arm64 (target=template_release, arch=arm64)
|
||||
cache-name: android-template-arm64
|
||||
target: template_release
|
||||
tests: false
|
||||
sconsflags: arch=arm64
|
||||
sconsflags: arch=arm64 swappy=yes
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
@ -59,6 +59,17 @@ jobs:
|
||||
- name: Setup Python and SCons
|
||||
uses: ./.github/actions/godot-deps
|
||||
|
||||
- name: Download pre-built Android Swappy Frame Pacing Library
|
||||
uses: dsaltares/fetch-gh-release-asset@1.1.2
|
||||
with:
|
||||
repo: darksylinc/godot-swappy
|
||||
version: tags/v2023.3.0.0
|
||||
file: godot-swappy.7z
|
||||
target: swappy/godot-swappy.7z
|
||||
|
||||
- name: Extract pre-built Android Swappy Frame Pacing Library
|
||||
run: 7za x -y swappy/godot-swappy.7z -o${{github.workspace}}/thirdparty/swappy-frame-pacing
|
||||
|
||||
- name: Compilation
|
||||
uses: ./.github/actions/godot-build
|
||||
with:
|
||||
|
@ -229,6 +229,7 @@ opts.Add(BoolVariable("use_volk", "Use the volk library to load the Vulkan loade
|
||||
opts.Add(BoolVariable("disable_exceptions", "Force disabling exception handling code", True))
|
||||
opts.Add("custom_modules", "A list of comma-separated directory paths containing custom modules to build.", "")
|
||||
opts.Add(BoolVariable("custom_modules_recursive", "Detect custom modules recursively for each specified path.", True))
|
||||
opts.Add(BoolVariable("swappy", "Use Swappy Frame Pacing Library in Android builds.", False))
|
||||
|
||||
# Advanced options
|
||||
opts.Add(
|
||||
@ -611,6 +612,8 @@ if env["dev_mode"]:
|
||||
if env["production"]:
|
||||
env["use_static_cpp"] = methods.get_cmdline_bool("use_static_cpp", True)
|
||||
env["debug_symbols"] = methods.get_cmdline_bool("debug_symbols", False)
|
||||
if platform_arg == "android":
|
||||
env["swappy"] = methods.get_cmdline_bool("swappy", True)
|
||||
# LTO "auto" means we handle the preferred option in each platform detect.py.
|
||||
env["lto"] = ARGUMENTS.get("lto", "auto")
|
||||
|
||||
|
@ -36,6 +36,7 @@
|
||||
#include "core/license.gen.h"
|
||||
#include "core/variant/typed_array.h"
|
||||
#include "core/version.h"
|
||||
#include "servers/rendering/rendering_device.h"
|
||||
|
||||
void Engine::set_physics_ticks_per_second(int p_ips) {
|
||||
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 {
|
||||
|
||||
void Engine::set_max_fps(int p_fps) {
|
||||
_max_fps = p_fps > 0 ? p_fps : 0;
|
||||
|
||||
RenderingDevice *rd = RenderingDevice::get_singleton();
|
||||
if (rd) {
|
||||
rd->_set_max_fps(_max_fps);
|
||||
}
|
||||
}
|
||||
|
||||
int Engine::get_max_fps() const {
|
||||
|
@ -1485,6 +1485,10 @@ ProjectSettings::ProjectSettings() {
|
||||
GLOBAL_DEF("display/window/subwindows/embed_subwindows", true);
|
||||
// Keep the enum values in sync with the `DisplayServer::VSyncMode` enum.
|
||||
custom_prop_info["display/window/vsync/vsync_mode"] = PropertyInfo(Variant::INT, "display/window/vsync/vsync_mode", PROPERTY_HINT_ENUM, "Disabled,Enabled,Adaptive,Mailbox");
|
||||
|
||||
GLOBAL_DEF("display/window/frame_pacing/android/enable_frame_pacing", true);
|
||||
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);
|
||||
|
||||
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");
|
||||
GLOBAL_DEF("physics/2d/run_on_separate_thread", false);
|
||||
GLOBAL_DEF("physics/3d/run_on_separate_thread", false);
|
||||
|
@ -816,6 +816,17 @@
|
||||
<member name="display/window/energy_saving/keep_screen_on" type="bool" setter="" getter="" default="true">
|
||||
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.
|
||||
</member>
|
||||
<member name="display/window/frame_pacing/android/enable_frame_pacing" type="bool" setter="" getter="" default="true">
|
||||
Enable Swappy for stable frame pacing on Android. Highly recommended.
|
||||
[b]Note:[/b] This option will be forced off when using OpenXR.
|
||||
</member>
|
||||
<member name="display/window/frame_pacing/android/swappy_mode" type="int" setter="" getter="" default="2">
|
||||
Swappy mode to use. The options are:
|
||||
- pipeline_forced_on: Try to honor [member Engine.max_fps]. Pipelining is always on. This is the same behavior as Desktop PC.
|
||||
- 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.
|
||||
- auto_fps_auto_pipeline: Same as auto_fps_pipeline_forced_on, but if Swappy detects that rendering is very fast (e.g. it takes < 8ms to render on a 60hz screen) Swappy will disable pipelining to minimize input latency. This is the default.
|
||||
[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).
|
||||
</member>
|
||||
<member name="display/window/handheld/orientation" type="int" setter="" getter="" default="0">
|
||||
The default screen orientation to use on mobile devices. See [enum DisplayServer.ScreenOrientation] for possible values.
|
||||
[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.
|
||||
|
@ -35,6 +35,16 @@
|
||||
#include "thirdparty/misc/smolv.h"
|
||||
#include "vulkan_hooks.h"
|
||||
|
||||
#if defined(ANDROID_ENABLED)
|
||||
#include "platform/android/java_godot_wrapper.h"
|
||||
#include "platform/android/os_android.h"
|
||||
#include "platform/android/thread_jandroid.h"
|
||||
#endif
|
||||
|
||||
#if defined(SWAPPY_FRAME_PACING_ENABLED)
|
||||
#include "thirdparty/swappy-frame-pacing/swappyVk.h"
|
||||
#endif
|
||||
|
||||
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
|
||||
|
||||
#define PRINT_NATIVE_COMMANDS 0
|
||||
@ -533,6 +543,37 @@ Error RenderingDeviceDriverVulkan::_initialize_device_extensions() {
|
||||
err = vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &device_extension_count, device_extensions.ptr());
|
||||
ERR_FAIL_COND_V(err != VK_SUCCESS, ERR_CANT_CREATE);
|
||||
|
||||
#if defined(SWAPPY_FRAME_PACING_ENABLED)
|
||||
if (swappy_frame_pacer_enable) {
|
||||
char **swappy_required_extensions;
|
||||
uint32_t swappy_required_extensions_count = 0;
|
||||
// Determine number of extensions required by Swappy frame pacer.
|
||||
SwappyVk_determineDeviceExtensions(physical_device, device_extension_count, device_extensions.ptr(), &swappy_required_extensions_count, nullptr);
|
||||
|
||||
if (swappy_required_extensions_count < device_extension_count) {
|
||||
// Determine the actual extensions.
|
||||
swappy_required_extensions = (char **)malloc(swappy_required_extensions_count * sizeof(char *));
|
||||
char *pRequiredExtensionsData = (char *)malloc(swappy_required_extensions_count * (VK_MAX_EXTENSION_NAME_SIZE + 1));
|
||||
for (uint32_t i = 0; i < swappy_required_extensions_count; i++) {
|
||||
swappy_required_extensions[i] = &pRequiredExtensionsData[i * (VK_MAX_EXTENSION_NAME_SIZE + 1)];
|
||||
}
|
||||
SwappyVk_determineDeviceExtensions(physical_device, device_extension_count,
|
||||
device_extensions.ptr(), &swappy_required_extensions_count, swappy_required_extensions);
|
||||
|
||||
// Enable extensions requested by Swappy.
|
||||
for (uint32_t i = 0; i < swappy_required_extensions_count; i++) {
|
||||
CharString extension_name(swappy_required_extensions[i]);
|
||||
if (requested_device_extensions.has(extension_name)) {
|
||||
enabled_device_extension_names.insert(extension_name);
|
||||
}
|
||||
}
|
||||
|
||||
free(pRequiredExtensionsData);
|
||||
free(swappy_required_extensions);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef DEV_ENABLED
|
||||
for (uint32_t i = 0; i < device_extension_count; i++) {
|
||||
print_verbose(String("VULKAN: Found device extension ") + String::utf8(device_extensions[i].extensionName));
|
||||
@ -1371,6 +1412,18 @@ Error RenderingDeviceDriverVulkan::initialize(uint32_t p_device_index, uint32_t
|
||||
max_descriptor_sets_per_pool = GLOBAL_GET("rendering/rendering_device/vulkan/max_descriptors_per_pool");
|
||||
breadcrumb_buffer = buffer_create(sizeof(uint32_t), BufferUsageBits::BUFFER_USAGE_TRANSFER_TO_BIT, MemoryAllocationType::MEMORY_ALLOCATION_TYPE_CPU);
|
||||
|
||||
#if defined(SWAPPY_FRAME_PACING_ENABLED)
|
||||
swappy_frame_pacer_enable = GLOBAL_GET("display/window/frame_pacing/android/enable_frame_pacing");
|
||||
swappy_mode = GLOBAL_GET("display/window/frame_pacing/android/swappy_mode");
|
||||
|
||||
if (VulkanHooks::get_singleton() != nullptr) {
|
||||
// Hooks control device creation & possibly presentation
|
||||
// (e.g. OpenXR) thus it's too risky to use Swappy.
|
||||
swappy_frame_pacer_enable = false;
|
||||
OS::get_singleton()->print("VulkanHooks detected (e.g. OpenXR): Force-disabling Swappy Frame Pacing.\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
@ -2356,6 +2409,14 @@ RDD::CommandQueueID RenderingDeviceDriverVulkan::command_queue_create(CommandQue
|
||||
|
||||
ERR_FAIL_COND_V_MSG(picked_queue_index >= queue_family.size(), CommandQueueID(), "A queue in the picked family could not be found.");
|
||||
|
||||
#if defined(SWAPPY_FRAME_PACING_ENABLED)
|
||||
if (swappy_frame_pacer_enable) {
|
||||
VkQueue selected_queue;
|
||||
vkGetDeviceQueue(vk_device, family_index, picked_queue_index, &selected_queue);
|
||||
SwappyVk_setQueueFamilyIndex(vk_device, selected_queue, family_index);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Create the virtual queue.
|
||||
CommandQueue *command_queue = memnew(CommandQueue);
|
||||
command_queue->queue_family = family_index;
|
||||
@ -2501,7 +2562,16 @@ Error RenderingDeviceDriverVulkan::command_queue_execute_and_present(CommandQueu
|
||||
present_info.pResults = results.ptr();
|
||||
|
||||
device_queue.submit_mutex.lock();
|
||||
#if defined(SWAPPY_FRAME_PACING_ENABLED)
|
||||
if (swappy_frame_pacer_enable) {
|
||||
err = SwappyVk_queuePresent(device_queue.queue, &present_info);
|
||||
} else {
|
||||
err = device_functions.QueuePresentKHR(device_queue.queue, &present_info);
|
||||
}
|
||||
#else
|
||||
err = device_functions.QueuePresentKHR(device_queue.queue, &present_info);
|
||||
#endif
|
||||
|
||||
device_queue.submit_mutex.unlock();
|
||||
|
||||
// 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.
|
||||
@ -2681,6 +2751,14 @@ void RenderingDeviceDriverVulkan::_swap_chain_release(SwapChain *swap_chain) {
|
||||
swap_chain->framebuffers.clear();
|
||||
|
||||
if (swap_chain->vk_swapchain != VK_NULL_HANDLE) {
|
||||
#if defined(SWAPPY_FRAME_PACING_ENABLED)
|
||||
if (swappy_frame_pacer_enable) {
|
||||
// Swappy has a bug where the ANativeWindow will be leaked if we call
|
||||
// SwappyVk_destroySwapchain, so we must release it by hand.
|
||||
SwappyVk_setWindow(vk_device, swap_chain->vk_swapchain, nullptr);
|
||||
SwappyVk_destroySwapchain(vk_device, swap_chain->vk_swapchain);
|
||||
}
|
||||
#endif
|
||||
device_functions.DestroySwapchainKHR(vk_device, swap_chain->vk_swapchain, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_SWAPCHAIN_KHR));
|
||||
swap_chain->vk_swapchain = VK_NULL_HANDLE;
|
||||
}
|
||||
@ -2797,6 +2875,20 @@ Error RenderingDeviceDriverVulkan::swap_chain_resize(CommandQueueID p_cmd_queue,
|
||||
VkResult err = functions.GetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, surface->vk_surface, &surface_capabilities);
|
||||
ERR_FAIL_COND_V(err != VK_SUCCESS, ERR_CANT_CREATE);
|
||||
|
||||
// No swapchain yet, this is the first time we're creating it.
|
||||
if (!swap_chain->vk_swapchain) {
|
||||
uint32_t width = surface_capabilities.currentExtent.width;
|
||||
uint32_t height = surface_capabilities.currentExtent.height;
|
||||
if (surface_capabilities.currentTransform & VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR ||
|
||||
surface_capabilities.currentTransform & VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR) {
|
||||
// Swap to get identity width and height.
|
||||
surface_capabilities.currentExtent.height = width;
|
||||
surface_capabilities.currentExtent.width = height;
|
||||
}
|
||||
|
||||
native_display_size = surface_capabilities.currentExtent;
|
||||
}
|
||||
|
||||
VkExtent2D extent;
|
||||
if (surface_capabilities.currentExtent.width == 0xFFFFFFFF) {
|
||||
// The current extent is currently undefined, so the current surface width and height will be clamped to the surface's capabilities.
|
||||
@ -2863,15 +2955,8 @@ Error RenderingDeviceDriverVulkan::swap_chain_resize(CommandQueueID p_cmd_queue,
|
||||
desired_swapchain_images = MIN(desired_swapchain_images, surface_capabilities.maxImageCount);
|
||||
}
|
||||
|
||||
// Prefer identity transform if it's supported, use the current transform otherwise.
|
||||
// This behavior is intended as Godot does not supported native rotation in platforms that use these bits.
|
||||
// Refer to the comment in command_queue_present() for more details.
|
||||
VkSurfaceTransformFlagBitsKHR surface_transform_bits;
|
||||
if (surface_capabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) {
|
||||
surface_transform_bits = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
|
||||
} else {
|
||||
surface_transform_bits = surface_capabilities.currentTransform;
|
||||
}
|
||||
VkSurfaceTransformFlagBitsKHR surface_transform_bits = surface_capabilities.currentTransform;
|
||||
|
||||
VkCompositeAlphaFlagBitsKHR composite_alpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
|
||||
if (OS::get_singleton()->is_layered_allowed() || !(surface_capabilities.supportedCompositeAlpha & composite_alpha)) {
|
||||
@ -2898,7 +2983,7 @@ Error RenderingDeviceDriverVulkan::swap_chain_resize(CommandQueueID p_cmd_queue,
|
||||
swap_create_info.minImageCount = desired_swapchain_images;
|
||||
swap_create_info.imageFormat = swap_chain->format;
|
||||
swap_create_info.imageColorSpace = swap_chain->color_space;
|
||||
swap_create_info.imageExtent = extent;
|
||||
swap_create_info.imageExtent = native_display_size;
|
||||
swap_create_info.imageArrayLayers = 1;
|
||||
swap_create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
||||
swap_create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
@ -2909,6 +2994,39 @@ Error RenderingDeviceDriverVulkan::swap_chain_resize(CommandQueueID p_cmd_queue,
|
||||
err = device_functions.CreateSwapchainKHR(vk_device, &swap_create_info, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_SWAPCHAIN_KHR), &swap_chain->vk_swapchain);
|
||||
ERR_FAIL_COND_V(err != VK_SUCCESS, ERR_CANT_CREATE);
|
||||
|
||||
#if defined(SWAPPY_FRAME_PACING_ENABLED)
|
||||
if (swappy_frame_pacer_enable) {
|
||||
const double max_fps = Engine::get_singleton()->get_max_fps();
|
||||
const uint64_t max_time = max_fps > 0 ? uint64_t((1000.0 * 1000.0 * 1000.0) / max_fps) : 0;
|
||||
|
||||
SwappyVk_initAndGetRefreshCycleDuration(get_jni_env(), static_cast<OS_Android *>(OS::get_singleton())->get_godot_java()->get_activity(), physical_device,
|
||||
vk_device, swap_chain->vk_swapchain, &swap_chain->refresh_duration);
|
||||
SwappyVk_setWindow(vk_device, swap_chain->vk_swapchain, static_cast<OS_Android *>(OS::get_singleton())->get_native_window());
|
||||
SwappyVk_setSwapIntervalNS(vk_device, swap_chain->vk_swapchain, MAX(swap_chain->refresh_duration, max_time));
|
||||
|
||||
enum SwappyModes {
|
||||
PIPELINE_FORCED_ON,
|
||||
AUTO_FPS_PIPELINE_FORCED_ON,
|
||||
AUTO_FPS_AUTO_PIPELINE,
|
||||
};
|
||||
|
||||
switch (swappy_mode) {
|
||||
case PIPELINE_FORCED_ON:
|
||||
SwappyVk_setAutoSwapInterval(true);
|
||||
SwappyVk_setAutoPipelineMode(true);
|
||||
break;
|
||||
case AUTO_FPS_PIPELINE_FORCED_ON:
|
||||
SwappyVk_setAutoSwapInterval(true);
|
||||
SwappyVk_setAutoPipelineMode(false);
|
||||
break;
|
||||
case AUTO_FPS_AUTO_PIPELINE:
|
||||
SwappyVk_setAutoSwapInterval(false);
|
||||
SwappyVk_setAutoPipelineMode(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
uint32_t image_count = 0;
|
||||
err = device_functions.GetSwapchainImagesKHR(vk_device, swap_chain->vk_swapchain, &image_count, nullptr);
|
||||
ERR_FAIL_COND_V(err != VK_SUCCESS, ERR_CANT_CREATE);
|
||||
@ -3049,6 +3167,22 @@ RDD::DataFormat RenderingDeviceDriverVulkan::swap_chain_get_format(SwapChainID p
|
||||
}
|
||||
}
|
||||
|
||||
void RenderingDeviceDriverVulkan::swap_chain_set_max_fps(SwapChainID p_swap_chain, int p_max_fps) {
|
||||
DEV_ASSERT(p_swap_chain.id != 0);
|
||||
|
||||
#ifdef SWAPPY_FRAME_PACING_ENABLED
|
||||
if (!swappy_frame_pacer_enable) {
|
||||
return;
|
||||
}
|
||||
|
||||
SwapChain *swap_chain = (SwapChain *)(p_swap_chain.id);
|
||||
if (swap_chain->vk_swapchain != VK_NULL_HANDLE) {
|
||||
const uint64_t max_time = p_max_fps > 0 ? uint64_t((1000.0 * 1000.0 * 1000.0) / p_max_fps) : 0;
|
||||
SwappyVk_setSwapIntervalNS(vk_device, swap_chain->vk_swapchain, MAX(swap_chain->refresh_duration, max_time));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void RenderingDeviceDriverVulkan::swap_chain_free(SwapChainID p_swap_chain) {
|
||||
DEV_ASSERT(p_swap_chain.id != 0);
|
||||
|
||||
|
@ -142,6 +142,11 @@ class RenderingDeviceDriverVulkan : public RenderingDeviceDriver {
|
||||
bool device_fault_support = false;
|
||||
#if defined(VK_TRACK_DEVICE_MEMORY)
|
||||
bool device_memory_report_support = false;
|
||||
#endif
|
||||
#if defined(SWAPPY_FRAME_PACING_ENABLED)
|
||||
// Swappy frame pacer for Android.
|
||||
bool swappy_frame_pacer_enable = false;
|
||||
uint8_t swappy_mode = 2; // See default value for display/window/frame_pacing/android/swappy_mode.
|
||||
#endif
|
||||
DeviceFunctions device_functions;
|
||||
|
||||
@ -350,9 +355,13 @@ private:
|
||||
LocalVector<uint32_t> command_queues_acquired_semaphores;
|
||||
RenderPassID render_pass;
|
||||
uint32_t image_index = 0;
|
||||
#ifdef ANDROID_ENABLED
|
||||
uint64_t refresh_duration = 0;
|
||||
#endif
|
||||
};
|
||||
|
||||
void _swap_chain_release(SwapChain *p_swap_chain);
|
||||
VkExtent2D native_display_size;
|
||||
|
||||
public:
|
||||
virtual SwapChainID swap_chain_create(RenderingContextDriver::SurfaceID p_surface) override final;
|
||||
@ -360,6 +369,7 @@ public:
|
||||
virtual FramebufferID swap_chain_acquire_framebuffer(CommandQueueID p_cmd_queue, SwapChainID p_swap_chain, bool &r_resize_required) override final;
|
||||
virtual RenderPassID swap_chain_get_render_pass(SwapChainID p_swap_chain) override final;
|
||||
virtual DataFormat swap_chain_get_format(SwapChainID p_swap_chain) override final;
|
||||
virtual void swap_chain_set_max_fps(SwapChainID p_swap_chain, int p_max_fps) override final;
|
||||
virtual void swap_chain_free(SwapChainID p_swap_chain) override final;
|
||||
|
||||
/*********************/
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
|
@ -358,6 +358,13 @@ public:
|
||||
|
||||
virtual void screen_set_orientation(ScreenOrientation p_orientation, int p_screen = SCREEN_OF_MAIN_WINDOW);
|
||||
virtual ScreenOrientation screen_get_orientation(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
|
||||
// Note: The "internal" current orientation is not necessarily the current orientation and will often be 0 for most platforms.
|
||||
//
|
||||
// Some Android GPUs come with a HW-based rotator which means the screen gets rotated for free to
|
||||
// whatever orientation the device is currently facing. But many Android GPUs emulate it via SW instead,
|
||||
// which costs performance and power. This value is an optimization that tells Godot's compositor how to
|
||||
// rotate the render texture before presenting to screen so that Android's compositor doesn't have to.
|
||||
virtual int screen_get_internal_current_rotation(int p_screen = SCREEN_OF_MAIN_WINDOW) const { return 0; }
|
||||
|
||||
virtual void screen_set_keep_on(bool p_enable); //disable screensaver
|
||||
virtual bool screen_is_kept_on() const;
|
||||
@ -367,6 +374,7 @@ public:
|
||||
INVALID_INDICATOR_ID = -1
|
||||
};
|
||||
|
||||
public:
|
||||
typedef int WindowID;
|
||||
typedef int IndicatorID;
|
||||
|
||||
|
@ -66,6 +66,16 @@ void RendererCompositorRD::blit_render_targets_to_screen(DisplayServer::WindowID
|
||||
RD::get_singleton()->draw_list_bind_index_array(draw_list, blit.array);
|
||||
RD::get_singleton()->draw_list_bind_uniform_set(draw_list, render_target_descriptors[rd_texture], 0);
|
||||
|
||||
// We need to invert the phone rotation.
|
||||
int screen_rotation_degrees = -DisplayServer::get_singleton()->screen_get_internal_current_rotation();
|
||||
float screen_rotation = Math::deg_to_rad((float)screen_rotation_degrees);
|
||||
|
||||
blit.push_constant.rotation_cos = Math::cos(screen_rotation);
|
||||
blit.push_constant.rotation_sin = Math::sin(screen_rotation);
|
||||
// Swap width and height when the orientation is not the native one.
|
||||
if (screen_rotation_degrees % 180 != 0) {
|
||||
SWAP(screen_size.width, screen_size.height);
|
||||
}
|
||||
blit.push_constant.src_rect[0] = p_render_targets[i].src_rect.position.x;
|
||||
blit.push_constant.src_rect[1] = p_render_targets[i].src_rect.position.y;
|
||||
blit.push_constant.src_rect[2] = p_render_targets[i].src_rect.size.width;
|
||||
@ -228,6 +238,10 @@ void RendererCompositorRD::set_boot_image(const Ref<Image> &p_image, const Color
|
||||
RD::get_singleton()->draw_list_bind_index_array(draw_list, blit.array);
|
||||
RD::get_singleton()->draw_list_bind_uniform_set(draw_list, uset, 0);
|
||||
|
||||
int screen_rotation_degrees = DisplayServer::get_singleton()->screen_get_internal_current_rotation();
|
||||
float screen_rotation = Math::deg_to_rad((float)screen_rotation_degrees);
|
||||
blit.push_constant.rotation_cos = Math::cos(screen_rotation);
|
||||
blit.push_constant.rotation_sin = Math::sin(screen_rotation);
|
||||
blit.push_constant.src_rect[0] = 0.0;
|
||||
blit.push_constant.src_rect[1] = 0.0;
|
||||
blit.push_constant.src_rect[2] = 1.0;
|
||||
|
@ -73,6 +73,10 @@ protected:
|
||||
float src_rect[4];
|
||||
float dst_rect[4];
|
||||
|
||||
float rotation_sin;
|
||||
float rotation_cos;
|
||||
float pad[2];
|
||||
|
||||
float eye_center[2];
|
||||
float k1;
|
||||
float k2;
|
||||
|
@ -8,6 +8,10 @@ layout(push_constant, std140) uniform Pos {
|
||||
vec4 src_rect;
|
||||
vec4 dst_rect;
|
||||
|
||||
float rotation_sin;
|
||||
float rotation_cos;
|
||||
vec2 pad;
|
||||
|
||||
vec2 eye_center;
|
||||
float k1;
|
||||
float k2;
|
||||
@ -15,17 +19,23 @@ layout(push_constant, std140) uniform Pos {
|
||||
float upscale;
|
||||
float aspect_ratio;
|
||||
uint layer;
|
||||
uint pad1;
|
||||
bool convert_to_srgb;
|
||||
}
|
||||
data;
|
||||
|
||||
layout(location = 0) out vec2 uv;
|
||||
|
||||
void main() {
|
||||
mat4 swapchain_transform = mat4(1.0);
|
||||
swapchain_transform[0][0] = data.rotation_cos;
|
||||
swapchain_transform[0][1] = -data.rotation_sin;
|
||||
swapchain_transform[1][0] = data.rotation_sin;
|
||||
swapchain_transform[1][1] = data.rotation_cos;
|
||||
|
||||
vec2 base_arr[4] = vec2[](vec2(0.0, 0.0), vec2(0.0, 1.0), vec2(1.0, 1.0), vec2(1.0, 0.0));
|
||||
uv = data.src_rect.xy + base_arr[gl_VertexIndex] * data.src_rect.zw;
|
||||
vec2 vtx = data.dst_rect.xy + base_arr[gl_VertexIndex] * data.dst_rect.zw;
|
||||
gl_Position = vec4(vtx * 2.0 - 1.0, 0.0, 1.0);
|
||||
gl_Position = swapchain_transform * vec4(vtx * 2.0 - 1.0, 0.0, 1.0);
|
||||
}
|
||||
|
||||
#[fragment]
|
||||
@ -38,6 +48,10 @@ layout(push_constant, std140) uniform Pos {
|
||||
vec4 src_rect;
|
||||
vec4 dst_rect;
|
||||
|
||||
float rotation_sin;
|
||||
float rotation_cos;
|
||||
vec2 pad;
|
||||
|
||||
vec2 eye_center;
|
||||
float k1;
|
||||
float k2;
|
||||
|
@ -6518,6 +6518,12 @@ void RenderingDevice::finalize() {
|
||||
ERR_FAIL_COND(reverse_dependency_map.size());
|
||||
}
|
||||
|
||||
void RenderingDevice::_set_max_fps(int p_max_fps) {
|
||||
for (const KeyValue<DisplayServer::WindowID, RDD::SwapChainID> &it : screen_swap_chains) {
|
||||
driver->swap_chain_set_max_fps(it.value, p_max_fps);
|
||||
}
|
||||
}
|
||||
|
||||
RenderingDevice *RenderingDevice::create_local_device() {
|
||||
RenderingDevice *rd = memnew(RenderingDevice);
|
||||
rd->initialize(context);
|
||||
|
@ -1421,6 +1421,8 @@ public:
|
||||
Error initialize(RenderingContextDriver *p_context, DisplayServer::WindowID p_main_window = DisplayServer::INVALID_WINDOW_ID);
|
||||
void finalize();
|
||||
|
||||
void _set_max_fps(int p_max_fps);
|
||||
|
||||
void free(RID p_id);
|
||||
|
||||
/****************/
|
||||
|
@ -456,6 +456,10 @@ public:
|
||||
// Retrieve the format used by the swap chain's framebuffers.
|
||||
virtual DataFormat swap_chain_get_format(SwapChainID p_swap_chain) = 0;
|
||||
|
||||
// Tells the swapchain the max_fps so it can use the proper frame pacing.
|
||||
// Android uses this with Swappy library. Some implementations or platforms may ignore this hint.
|
||||
virtual void swap_chain_set_max_fps(SwapChainID p_swap_chain, int p_max_fps) {}
|
||||
|
||||
// Wait until all rendering associated to the swap chain is finished before deleting it.
|
||||
virtual void swap_chain_free(SwapChainID p_swap_chain) = 0;
|
||||
|
||||
|
41
thirdparty/swappy-frame-pacing/common/gamesdk_common.h
vendored
Normal file
41
thirdparty/swappy-frame-pacing/common/gamesdk_common.h
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is the main interface to the Android Performance Tuner library, also
|
||||
* known as Tuning Fork.
|
||||
*
|
||||
* It is part of the Android Games SDK and produces best results when integrated
|
||||
* with the Swappy Frame Pacing Library.
|
||||
*
|
||||
* See the documentation at
|
||||
* https://developer.android.com/games/sdk/performance-tuner/custom-engine for
|
||||
* more information on using this library in a native Android game.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// There are separate versions for each GameSDK component that use this format:
|
||||
#define ANDROID_GAMESDK_PACKED_VERSION(MAJOR, MINOR, BUGFIX) \
|
||||
((MAJOR << 16) | (MINOR << 8) | (BUGFIX))
|
||||
// Accessors
|
||||
#define ANDROID_GAMESDK_MAJOR_VERSION(PACKED) ((PACKED) >> 16)
|
||||
#define ANDROID_GAMESDK_MINOR_VERSION(PACKED) (((PACKED) >> 8) & 0xff)
|
||||
#define ANDROID_GAMESDK_BUGFIX_VERSION(PACKED) ((PACKED) & 0xff)
|
||||
|
||||
#define AGDK_STRING_VERSION(MAJOR, MINOR, BUGFIX, GIT) \
|
||||
#MAJOR "." #MINOR "." #BUGFIX "." #GIT
|
420
thirdparty/swappy-frame-pacing/swappyVk.h
vendored
Normal file
420
thirdparty/swappy-frame-pacing/swappyVk.h
vendored
Normal file
@ -0,0 +1,420 @@
|
||||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup swappyVk Swappy for Vulkan
|
||||
* Vulkan part of Swappy.
|
||||
* @{
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "jni.h"
|
||||
#include "swappy_common.h"
|
||||
|
||||
#ifndef VK_NO_PROTOTYPES
|
||||
#define VK_NO_PROTOTYPES 1
|
||||
#endif
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Determine any Vulkan device extensions that must be enabled for a new
|
||||
* VkDevice.
|
||||
*
|
||||
* Swappy-for-Vulkan (SwappyVk) benefits from certain Vulkan device extensions
|
||||
* (e.g. VK_GOOGLE_display_timing). Before the application calls
|
||||
* vkCreateDevice, SwappyVk needs to look at the list of available extensions
|
||||
* (returned by vkEnumerateDeviceExtensionProperties) and potentially identify
|
||||
* one or more extensions that the application must add to:
|
||||
*
|
||||
* - VkDeviceCreateInfo::enabledExtensionCount
|
||||
* - VkDeviceCreateInfo::ppEnabledExtensionNames
|
||||
*
|
||||
* before the application calls vkCreateDevice. For each VkPhysicalDevice that
|
||||
* the application will call vkCreateDevice for, the application must call this
|
||||
* function, and then must add the identified extension(s) to the list that are
|
||||
* enabled for the VkDevice. Similar to many Vulkan functions, this function
|
||||
* can be called twice, once to identify the number of required extensions, and
|
||||
* again with application-allocated memory that the function can write into.
|
||||
*
|
||||
* @param[in] physicalDevice - The VkPhysicalDevice associated with
|
||||
* the available extensions.
|
||||
* @param[in] availableExtensionCount - This is the returned value of
|
||||
* pPropertyCount from vkEnumerateDeviceExtensionProperties.
|
||||
* @param[in] pAvailableExtensions - This is the returned value of
|
||||
* pProperties from vkEnumerateDeviceExtensionProperties.
|
||||
* @param[inout] pRequiredExtensionCount - If pRequiredExtensions is nullptr,
|
||||
* the function sets this to the number of extensions that are required. If
|
||||
* pRequiredExtensions is non-nullptr, this is the number of required extensions
|
||||
* that the function should write into pRequiredExtensions.
|
||||
* @param[inout] pRequiredExtensions - If non-nullptr, this is
|
||||
* application-allocated memory into which the function will write the names of
|
||||
* required extensions. It is a pointer to an array of
|
||||
* char* strings (i.e. the same as
|
||||
* VkDeviceCreateInfo::ppEnabledExtensionNames).
|
||||
*/
|
||||
void SwappyVk_determineDeviceExtensions(
|
||||
VkPhysicalDevice physicalDevice, uint32_t availableExtensionCount,
|
||||
VkExtensionProperties* pAvailableExtensions,
|
||||
uint32_t* pRequiredExtensionCount, char** pRequiredExtensions);
|
||||
|
||||
/**
|
||||
* @brief Tell Swappy the queueFamilyIndex used to create a specific VkQueue
|
||||
*
|
||||
* Swappy needs to know the queueFamilyIndex used for creating a specific
|
||||
* VkQueue so it can use it when presenting.
|
||||
*
|
||||
* @param[in] device - The VkDevice associated with the queue
|
||||
* @param[in] queue - A device queue.
|
||||
* @param[in] queueFamilyIndex - The queue family index used to create the
|
||||
* VkQueue.
|
||||
*
|
||||
*/
|
||||
void SwappyVk_setQueueFamilyIndex(VkDevice device, VkQueue queue,
|
||||
uint32_t queueFamilyIndex);
|
||||
|
||||
// TBD: For now, SwappyVk assumes only one VkSwapchainKHR per VkDevice, and that
|
||||
// applications don't re-create swapchains. Is this long-term sufficient?
|
||||
|
||||
/**
|
||||
* Internal init function. Do not call directly.
|
||||
* See SwappyVk_initAndGetRefreshCycleDuration instead.
|
||||
* @private
|
||||
*/
|
||||
bool SwappyVk_initAndGetRefreshCycleDuration_internal(
|
||||
JNIEnv* env, jobject jactivity, VkPhysicalDevice physicalDevice,
|
||||
VkDevice device, VkSwapchainKHR swapchain, uint64_t* pRefreshDuration);
|
||||
|
||||
/**
|
||||
* @brief Initialize SwappyVk for a given device and swapchain, and obtain the
|
||||
* approximate time duration between vertical-blanking periods.
|
||||
*
|
||||
* Uses JNI to query AppVsyncOffset and PresentationDeadline.
|
||||
*
|
||||
* If your application presents to more than one swapchain at a time, you must
|
||||
* call this for each swapchain before calling swappyVkSetSwapInterval() for it.
|
||||
*
|
||||
* The duration between vertical-blanking periods (an interval) is expressed as
|
||||
* the approximate number of nanoseconds between vertical-blanking periods of
|
||||
* the swapchain’s physical display.
|
||||
*
|
||||
* If the application converts this number to a fraction (e.g. 16,666,666 nsec
|
||||
* to 0.016666666) and divides one by that fraction, it will be the approximate
|
||||
* refresh rate of the display (e.g. 16,666,666 nanoseconds corresponds to a
|
||||
* 60Hz display, 11,111,111 nsec corresponds to a 90Hz display).
|
||||
*
|
||||
* @param[in] env - JNIEnv that is assumed to be from AttachCurrentThread
|
||||
* function
|
||||
* @param[in] jactivity - NativeActivity object handle, used for JNI
|
||||
* @param[in] physicalDevice - The VkPhysicalDevice associated with the
|
||||
* swapchain
|
||||
* @param[in] device - The VkDevice associated with the swapchain
|
||||
* @param[in] swapchain - The VkSwapchainKHR the application wants Swappy to
|
||||
* swap
|
||||
* @param[out] pRefreshDuration - The returned refresh cycle duration
|
||||
*
|
||||
* @return bool - true if the value returned by pRefreshDuration is
|
||||
* valid, otherwise false if an error.
|
||||
*/
|
||||
bool SwappyVk_initAndGetRefreshCycleDuration(JNIEnv* env, jobject jactivity,
|
||||
VkPhysicalDevice physicalDevice,
|
||||
VkDevice device,
|
||||
VkSwapchainKHR swapchain,
|
||||
uint64_t* pRefreshDuration);
|
||||
|
||||
/**
|
||||
* @brief Tell Swappy which ANativeWindow to use when calling to ANativeWindow_*
|
||||
* API.
|
||||
* @param[in] device - The VkDevice associated with the swapchain
|
||||
* @param[in] swapchain - The VkSwapchainKHR the application wants Swappy to
|
||||
* swap
|
||||
* @param[in] window - The ANativeWindow that was used to create the
|
||||
* VkSwapchainKHR
|
||||
*/
|
||||
void SwappyVk_setWindow(VkDevice device, VkSwapchainKHR swapchain,
|
||||
ANativeWindow* window);
|
||||
|
||||
/**
|
||||
* @brief Tell Swappy the duration of that each presented image should be
|
||||
* visible.
|
||||
*
|
||||
* If your application presents to more than one swapchain at a time, you must
|
||||
* call this for each swapchain before presenting to it.
|
||||
*
|
||||
* @param[in] device - The VkDevice associated with the swapchain
|
||||
* @param[in] swapchain - The VkSwapchainKHR the application wants Swappy to
|
||||
* swap
|
||||
* @param[in] swap_ns - The duration of that each presented image should be
|
||||
* visible in nanoseconds
|
||||
*/
|
||||
void SwappyVk_setSwapIntervalNS(VkDevice device, VkSwapchainKHR swapchain,
|
||||
uint64_t swap_ns);
|
||||
|
||||
/**
|
||||
* @brief Tell Swappy to present one or more images to corresponding swapchains.
|
||||
*
|
||||
* Swappy will call vkQueuePresentKHR for your application. Swappy may insert a
|
||||
* struct to the pNext-chain of VkPresentInfoKHR, or it may insert other Vulkan
|
||||
* commands in order to attempt to honor the desired swap interval.
|
||||
*
|
||||
* @note If your application presents to more than one swapchain at a time, and
|
||||
* if you use a different swap interval for each swapchain, Swappy will attempt
|
||||
* to honor the swap interval for each swapchain (being more successful on
|
||||
* devices that support an underlying presentation-timing extension, such as
|
||||
* VK_GOOGLE_display_timing).
|
||||
*
|
||||
* @param[in] queue - The VkQueue associated with the device and swapchain
|
||||
* @param[in] pPresentInfo - A pointer to the VkPresentInfoKHR containing the
|
||||
* information about what image(s) to present on which
|
||||
* swapchain(s).
|
||||
*/
|
||||
VkResult SwappyVk_queuePresent(VkQueue queue,
|
||||
const VkPresentInfoKHR* pPresentInfo);
|
||||
|
||||
/**
|
||||
* @brief Destroy the SwappyVk instance associated with a swapchain.
|
||||
*
|
||||
* This API is expected to be called before calling vkDestroySwapchainKHR()
|
||||
* so Swappy can cleanup its internal state.
|
||||
*
|
||||
* @param[in] device - The VkDevice associated with SwappyVk
|
||||
* @param[in] swapchain - The VkSwapchainKHR the application wants Swappy to
|
||||
* destroy
|
||||
*/
|
||||
void SwappyVk_destroySwapchain(VkDevice device, VkSwapchainKHR swapchain);
|
||||
|
||||
/**
|
||||
* @brief Destroy any swapchains associated with the device and clean up the
|
||||
* device's resources
|
||||
*
|
||||
* This function should be called after SwappyVk_destroySwapchain if you no
|
||||
* longer need the device.
|
||||
*
|
||||
* @param[in] device - The VkDevice associated with SwappyVk
|
||||
*/
|
||||
void SwappyVk_destroyDevice(VkDevice device);
|
||||
|
||||
/**
|
||||
* @brief Enables Auto-Swap-Interval feature for all instances.
|
||||
*
|
||||
* By default this feature is enabled. Changing it is completely
|
||||
* optional for fine-tuning swappy behaviour.
|
||||
*
|
||||
* @param[in] enabled - True means enable, false means disable
|
||||
*/
|
||||
void SwappyVk_setAutoSwapInterval(bool enabled);
|
||||
|
||||
/**
|
||||
* @brief Enables Auto-Pipeline-Mode feature for all instances.
|
||||
*
|
||||
* By default this feature is enabled. Changing it is completely
|
||||
* optional for fine-tuning swappy behaviour.
|
||||
*
|
||||
* @param[in] enabled - True means enable, false means disable
|
||||
*/
|
||||
void SwappyVk_setAutoPipelineMode(bool enabled);
|
||||
|
||||
/**
|
||||
* @brief Sets the maximal swap duration for all instances.
|
||||
*
|
||||
* Sets the maximal duration for Auto-Swap-Interval in milliseconds.
|
||||
* If SwappyVk is operating in Auto-Swap-Interval and the frame duration is
|
||||
* longer than the provided duration, SwappyVk will not do any pacing and just
|
||||
* submit the frame as soon as possible.
|
||||
*
|
||||
* @param[in] max_swap_ns - maximal swap duration in milliseconds.
|
||||
*/
|
||||
void SwappyVk_setMaxAutoSwapIntervalNS(uint64_t max_swap_ns);
|
||||
|
||||
/**
|
||||
* @brief The fence timeout parameter can be set for devices with faulty
|
||||
* drivers. Its default value is 50,000,000.
|
||||
*/
|
||||
void SwappyVk_setFenceTimeoutNS(uint64_t fence_timeout_ns);
|
||||
|
||||
/**
|
||||
* @brief Get the fence timeout parameter, for devices with faulty
|
||||
* drivers. Its default value is 50,000,000.
|
||||
*/
|
||||
uint64_t SwappyVk_getFenceTimeoutNS();
|
||||
|
||||
/**
|
||||
* @brief Inject callback functions to be called each frame.
|
||||
*
|
||||
* @param[in] tracer - Collection of callback functions
|
||||
*/
|
||||
void SwappyVk_injectTracer(const SwappyTracer* tracer);
|
||||
|
||||
/**
|
||||
* @brief Remove callbacks that were previously added using
|
||||
* SwappyVk_injectTracer.
|
||||
*
|
||||
* Only removes callbacks that were previously added using
|
||||
* SwappyVK_injectTracer. If SwappyVK_injectTracker was not called with the
|
||||
* tracer, then there is no effect.
|
||||
*
|
||||
* @param[in] tracer - Collection of callback functions
|
||||
*/
|
||||
void SwappyVk_uninjectTracer(const SwappyTracer* tracer);
|
||||
|
||||
/**
|
||||
* @brief A structure enabling you to provide your own Vulkan function wrappers
|
||||
* by calling ::SwappyVk_setFunctionProvider.
|
||||
*
|
||||
* Usage of this functionality is optional.
|
||||
*/
|
||||
typedef struct SwappyVkFunctionProvider {
|
||||
/**
|
||||
* @brief Callback to initialize the function provider.
|
||||
*
|
||||
* This function is called by Swappy before any functions are requested.
|
||||
* E.g. so you can call dlopen on the Vulkan library.
|
||||
*/
|
||||
bool (*init)();
|
||||
|
||||
/**
|
||||
* @brief Callback to get the address of a function.
|
||||
*
|
||||
* This function is called by Swappy to get the address of a Vulkan
|
||||
* function.
|
||||
* @param name The null-terminated name of the function.
|
||||
*/
|
||||
void* (*getProcAddr)(const char* name);
|
||||
|
||||
/**
|
||||
* @brief Callback to close any resources owned by the function provider.
|
||||
*
|
||||
* This function is called by Swappy when no more functions will be
|
||||
* requested, e.g. so you can call dlclose on the Vulkan library.
|
||||
*/
|
||||
void (*close)();
|
||||
} SwappyVkFunctionProvider;
|
||||
|
||||
/**
|
||||
* @brief Set the Vulkan function provider.
|
||||
*
|
||||
* This enables you to provide an object that will be used to look up Vulkan
|
||||
* functions, e.g. to hook usage of these functions.
|
||||
*
|
||||
* To use this functionality, you *must* call this function before any others.
|
||||
*
|
||||
* Usage of this function is entirely optional. If you do not use it, the Vulkan
|
||||
* functions required by Swappy will be dynamically loaded from libvulkan.so.
|
||||
*
|
||||
* @param[in] provider - provider object
|
||||
*/
|
||||
void SwappyVk_setFunctionProvider(
|
||||
const SwappyVkFunctionProvider* pSwappyVkFunctionProvider);
|
||||
|
||||
/**
|
||||
* @brief Get the swap interval value, in nanoseconds, for a given swapchain.
|
||||
*
|
||||
* @param[in] swapchain - the swapchain to query
|
||||
*/
|
||||
uint64_t SwappyVk_getSwapIntervalNS(VkSwapchainKHR swapchain);
|
||||
|
||||
/**
|
||||
* @brief Get the supported refresh periods of this device. Call once with
|
||||
* out_refreshrates set to nullptr to get the number of supported refresh
|
||||
* periods, then call again passing that number as allocated_entries and
|
||||
* an array of size equal to allocated_entries that will be filled with the
|
||||
* refresh periods.
|
||||
*/
|
||||
int SwappyVk_getSupportedRefreshPeriodsNS(uint64_t* out_refreshrates,
|
||||
int allocated_entries,
|
||||
VkSwapchainKHR swapchain);
|
||||
/**
|
||||
* @brief Check if Swappy is enabled for the specified swapchain.
|
||||
*
|
||||
* @return false if SwappyVk_initAndGetRefreshCycleDuration was not
|
||||
* called for the specified swapchain, true otherwise.
|
||||
*/
|
||||
bool SwappyVk_isEnabled(VkSwapchainKHR swapchain, bool* isEnabled);
|
||||
|
||||
/**
|
||||
* @brief Toggle statistics collection on/off
|
||||
*
|
||||
* By default, stats collection is off and there is no overhead related to
|
||||
* stats. An app can turn on stats collection by calling
|
||||
* `SwappyVk_enableStats(swapchain, true)`. Then, the app is expected to call
|
||||
* ::SwappyVk_recordFrameStart for each frame before starting to do any CPU
|
||||
* related work. Stats will be logged to logcat with a 'FrameStatistics' tag. An
|
||||
* app can get the stats by calling ::SwappyVk_getStats.
|
||||
*
|
||||
* SwappyVk_initAndGetRefreshCycleDuration must have been called successfully
|
||||
* before for this swapchain, otherwise there is no effect in this call. Frame
|
||||
* stats are only available if the platform supports VK_GOOGLE_display_timing
|
||||
* extension.
|
||||
*
|
||||
* @param[in] swapchain - The swapchain for which frame stat collection is
|
||||
* configured.
|
||||
* @param enabled - Whether to enable/disable frame stat collection.
|
||||
*/
|
||||
void SwappyVk_enableStats(VkSwapchainKHR swapchain, bool enabled);
|
||||
|
||||
/**
|
||||
* @brief Should be called if stats have been enabled with SwappyVk_enableStats.
|
||||
*
|
||||
* When stats collection is enabled with SwappyVk_enableStats, the app is
|
||||
* expected to call this function for each frame before starting to do any CPU
|
||||
* related work. It is assumed that this function will be called after a
|
||||
* successful call to vkAcquireNextImageKHR. See ::SwappyVk_enableStats for more
|
||||
* conditions.
|
||||
*
|
||||
* @param[in] queue - The VkQueue associated with the device and swapchain
|
||||
* @param[in] swapchain - The swapchain where the frame is presented to.
|
||||
* @param[in] image - The image in swapchain that corresponds to the frame.
|
||||
|
||||
* @see SwappyVk_enableStats.
|
||||
*/
|
||||
void SwappyVk_recordFrameStart(VkQueue queue, VkSwapchainKHR swapchain, uint32_t image);
|
||||
|
||||
/**
|
||||
* @brief Returns the stats collected, if statistics collection was toggled on.
|
||||
*
|
||||
* Given that this API uses VkSwapchainKHR and the potential for this call to be
|
||||
* done on different threads, all calls to ::SwappyVk_getStats
|
||||
* must be externally synchronized with other SwappyVk calls. Unsynchronized
|
||||
* calls may lead to undefined behavior. See ::SwappyVk_enableStats for more
|
||||
* conditions.
|
||||
*
|
||||
* @param[in] swapchain - The swapchain for which stats are being queried.
|
||||
* @param swappyStats - Pointer to a SwappyStats that will be populated with
|
||||
* the collected stats. Cannot be NULL.
|
||||
* @see SwappyStats
|
||||
*/
|
||||
void SwappyVk_getStats(VkSwapchainKHR swapchain, SwappyStats *swappyStats);
|
||||
|
||||
/**
|
||||
* @brief Clears the frame statistics collected so far.
|
||||
*
|
||||
* All the frame statistics collected are reset to 0, frame statistics are
|
||||
* collected normally after this call. See ::SwappyVk_enableStats for more
|
||||
* conditions.
|
||||
*
|
||||
* @param[in] swapchain - The swapchain for which stats are being cleared.
|
||||
*/
|
||||
void SwappyVk_clearStats(VkSwapchainKHR swapchain);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
/** @} */
|
278
thirdparty/swappy-frame-pacing/swappy_common.h
vendored
Normal file
278
thirdparty/swappy-frame-pacing/swappy_common.h
vendored
Normal file
@ -0,0 +1,278 @@
|
||||
/*
|
||||
* Copyright 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup swappy_common Swappy common tools
|
||||
* Tools to be used with Swappy for OpenGL or Swappy for Vulkan.
|
||||
* @{
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <android/native_window.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "common/gamesdk_common.h"
|
||||
|
||||
/** @brief Swap interval for 60fps, in nanoseconds. */
|
||||
#define SWAPPY_SWAP_60FPS (16666667L)
|
||||
|
||||
/** @brief Swap interval for 30fps, in nanoseconds. */
|
||||
#define SWAPPY_SWAP_30FPS (33333333L)
|
||||
|
||||
/** @brief Swap interval for 20fps, in nanoseconds. */
|
||||
#define SWAPPY_SWAP_20FPS (50000000L)
|
||||
|
||||
/**
|
||||
* The longest duration, in refresh periods, represented by the statistics.
|
||||
* @see SwappyStats
|
||||
*/
|
||||
#define MAX_FRAME_BUCKETS 6
|
||||
|
||||
/** @cond INTERNAL */
|
||||
|
||||
#define SWAPPY_SYSTEM_PROP_KEY_DISABLE "swappy.disable"
|
||||
|
||||
// Internal macros to track Swappy version, do not use directly.
|
||||
#define SWAPPY_MAJOR_VERSION 2
|
||||
#define SWAPPY_MINOR_VERSION 0
|
||||
#define SWAPPY_BUGFIX_VERSION 0
|
||||
#define SWAPPY_PACKED_VERSION \
|
||||
ANDROID_GAMESDK_PACKED_VERSION(SWAPPY_MAJOR_VERSION, SWAPPY_MINOR_VERSION, \
|
||||
SWAPPY_BUGFIX_VERSION)
|
||||
|
||||
// Internal macros to generate a symbol to track Swappy version, do not use
|
||||
// directly.
|
||||
#define SWAPPY_VERSION_CONCAT_NX(PREFIX, MAJOR, MINOR, BUGFIX, GITCOMMIT) \
|
||||
PREFIX##_##MAJOR##_##MINOR##_##BUGFIX##_##GITCOMMIT
|
||||
#define SWAPPY_VERSION_CONCAT(PREFIX, MAJOR, MINOR, BUGFIX, GITCOMMIT) \
|
||||
SWAPPY_VERSION_CONCAT_NX(PREFIX, MAJOR, MINOR, BUGFIX, GITCOMMIT)
|
||||
#define SWAPPY_VERSION_SYMBOL \
|
||||
SWAPPY_VERSION_CONCAT(Swappy_version, SWAPPY_MAJOR_VERSION, \
|
||||
SWAPPY_MINOR_VERSION, SWAPPY_BUGFIX_VERSION, \
|
||||
AGDK_GIT_COMMIT)
|
||||
|
||||
// Define this to 1 to enable all logging from Swappy, by default it is
|
||||
// disabled in a release build and enabled in a debug build.
|
||||
#ifndef ENABLE_SWAPPY_LOGGING
|
||||
#define ENABLE_SWAPPY_LOGGING 0
|
||||
#endif
|
||||
/** @endcond */
|
||||
|
||||
/** @brief Id of a thread returned by an external thread manager. */
|
||||
typedef uint64_t SwappyThreadId;
|
||||
|
||||
/**
|
||||
* @brief A structure enabling you to set how Swappy starts and joins threads by
|
||||
* calling
|
||||
* ::Swappy_setThreadFunctions.
|
||||
*
|
||||
* Usage of this functionality is optional.
|
||||
*/
|
||||
typedef struct SwappyThreadFunctions {
|
||||
/** @brief Thread start callback.
|
||||
*
|
||||
* This function is called by Swappy to start thread_func on a new thread.
|
||||
* @param user_data A value to be passed the thread function.
|
||||
* If the thread was started, this function should set the thread_id and
|
||||
* return 0. If the thread was not started, this function should return a
|
||||
* non-zero value.
|
||||
*/
|
||||
int (*start)(SwappyThreadId* thread_id, void* (*thread_func)(void*),
|
||||
void* user_data);
|
||||
|
||||
/** @brief Thread join callback.
|
||||
*
|
||||
* This function is called by Swappy to join the thread with given id.
|
||||
*/
|
||||
void (*join)(SwappyThreadId thread_id);
|
||||
|
||||
/** @brief Thread joinable callback.
|
||||
*
|
||||
* This function is called by Swappy to discover whether the thread with the
|
||||
* given id is joinable.
|
||||
*/
|
||||
bool (*joinable)(SwappyThreadId thread_id);
|
||||
} SwappyThreadFunctions;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Return the version of the Swappy library at runtime.
|
||||
*/
|
||||
uint32_t Swappy_version();
|
||||
|
||||
/**
|
||||
* @brief Call this before any other functions in order to use a custom thread
|
||||
* manager.
|
||||
*
|
||||
* Usage of this function is entirely optional. Swappy uses std::thread by
|
||||
* default.
|
||||
*
|
||||
*/
|
||||
void Swappy_setThreadFunctions(const SwappyThreadFunctions* thread_functions);
|
||||
|
||||
/**
|
||||
* @brief Return the full version of the Swappy library at runtime, e.g.
|
||||
* "1.9.0_8a85ab7c46"
|
||||
*/
|
||||
const char* Swappy_versionString();
|
||||
|
||||
/**
|
||||
* @brief Swappy frame statistics, collected if toggled on with
|
||||
* ::SwappyGL_enableStats or ::SwappyVk_enableStats.
|
||||
*/
|
||||
typedef struct SwappyStats {
|
||||
/** @brief Total frames swapped by swappy */
|
||||
uint64_t totalFrames;
|
||||
|
||||
/** @brief Histogram of the number of screen refreshes a frame waited in the
|
||||
* compositor queue after rendering was completed.
|
||||
*
|
||||
* For example:
|
||||
* if a frame waited 2 refresh periods in the compositor queue after
|
||||
* rendering was done, the frame will be counted in idleFrames[2]
|
||||
*/
|
||||
uint64_t idleFrames[MAX_FRAME_BUCKETS];
|
||||
|
||||
/** @brief Histogram of the number of screen refreshes passed between the
|
||||
* requested presentation time and the actual present time.
|
||||
*
|
||||
* For example:
|
||||
* if a frame was presented 2 refresh periods after the requested
|
||||
* timestamp swappy set, the frame will be counted in lateFrames[2]
|
||||
*/
|
||||
uint64_t lateFrames[MAX_FRAME_BUCKETS];
|
||||
|
||||
/** @brief Histogram of the number of screen refreshes passed between two
|
||||
* consecutive frames
|
||||
*
|
||||
* For example:
|
||||
* if frame N was presented 2 refresh periods after frame N-1
|
||||
* frame N will be counted in offsetFromPreviousFrame[2]
|
||||
*/
|
||||
uint64_t offsetFromPreviousFrame[MAX_FRAME_BUCKETS];
|
||||
|
||||
/** @brief Histogram of the number of screen refreshes passed between the
|
||||
* call to Swappy_recordFrameStart and the actual present time.
|
||||
*
|
||||
* For example:
|
||||
* if a frame was presented 2 refresh periods after the call to
|
||||
* `Swappy_recordFrameStart` the frame will be counted in latencyFrames[2]
|
||||
*/
|
||||
uint64_t latencyFrames[MAX_FRAME_BUCKETS];
|
||||
} SwappyStats;
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Pointer to a function that can be attached to SwappyTracer::preWait
|
||||
* @param userData Pointer to arbitrary data, see SwappyTracer::userData.
|
||||
*/
|
||||
typedef void (*SwappyPreWaitCallback)(void*);
|
||||
|
||||
/**
|
||||
* Pointer to a function that can be attached to SwappyTracer::postWait.
|
||||
* @param userData Pointer to arbitrary data, see SwappyTracer::userData.
|
||||
* @param cpu_time_ns Time for CPU processing of this frame in nanoseconds.
|
||||
* @param gpu_time_ns Time for GPU processing of previous frame in nanoseconds.
|
||||
*/
|
||||
typedef void (*SwappyPostWaitCallback)(void*, int64_t cpu_time_ns,
|
||||
int64_t gpu_time_ns);
|
||||
|
||||
/**
|
||||
* Pointer to a function that can be attached to SwappyTracer::preSwapBuffers.
|
||||
* @param userData Pointer to arbitrary data, see SwappyTracer::userData.
|
||||
*/
|
||||
typedef void (*SwappyPreSwapBuffersCallback)(void*);
|
||||
|
||||
/**
|
||||
* Pointer to a function that can be attached to SwappyTracer::postSwapBuffers.
|
||||
* @param userData Pointer to arbitrary data, see SwappyTracer::userData.
|
||||
* @param desiredPresentationTimeMillis The target time, in milliseconds, at
|
||||
* which the frame would be presented on screen.
|
||||
*/
|
||||
typedef void (*SwappyPostSwapBuffersCallback)(
|
||||
void*, int64_t desiredPresentationTimeMillis);
|
||||
|
||||
/**
|
||||
* Pointer to a function that can be attached to SwappyTracer::startFrame.
|
||||
* @param userData Pointer to arbitrary data, see SwappyTracer::userData.
|
||||
* @param desiredPresentationTimeMillis The time, in milliseconds, at which the
|
||||
* frame is scheduled to be presented.
|
||||
*/
|
||||
typedef void (*SwappyStartFrameCallback)(void*, int currentFrame,
|
||||
int64_t desiredPresentationTimeMillis);
|
||||
|
||||
/**
|
||||
* Pointer to a function that can be attached to
|
||||
* SwappyTracer::swapIntervalChanged. Call ::SwappyGL_getSwapIntervalNS or
|
||||
* ::SwappyVk_getSwapIntervalNS to get the latest swapInterval.
|
||||
* @param userData Pointer to arbitrary data, see SwappyTracer::userData.
|
||||
*/
|
||||
typedef void (*SwappySwapIntervalChangedCallback)(void*);
|
||||
|
||||
/**
|
||||
* @brief Collection of callbacks to be called each frame to trace execution.
|
||||
*
|
||||
* Injection of these is optional.
|
||||
*/
|
||||
typedef struct SwappyTracer {
|
||||
/**
|
||||
* Callback called before waiting to queue the frame to the composer.
|
||||
*/
|
||||
SwappyPreWaitCallback preWait;
|
||||
|
||||
/**
|
||||
* Callback called after wait to queue the frame to the composer is done.
|
||||
*/
|
||||
SwappyPostWaitCallback postWait;
|
||||
|
||||
/**
|
||||
* Callback called before calling the function to queue the frame to the
|
||||
* composer.
|
||||
*/
|
||||
SwappyPreSwapBuffersCallback preSwapBuffers;
|
||||
|
||||
/**
|
||||
* Callback called after calling the function to queue the frame to the
|
||||
* composer.
|
||||
*/
|
||||
SwappyPostSwapBuffersCallback postSwapBuffers;
|
||||
|
||||
/**
|
||||
* Callback called at the start of a frame.
|
||||
*/
|
||||
SwappyStartFrameCallback startFrame;
|
||||
|
||||
/**
|
||||
* Pointer to some arbitrary data that will be passed as the first argument
|
||||
* of callbacks.
|
||||
*/
|
||||
void* userData;
|
||||
|
||||
/**
|
||||
* Callback called when the swap interval was changed.
|
||||
*/
|
||||
SwappySwapIntervalChangedCallback swapIntervalChanged;
|
||||
} SwappyTracer;
|
||||
|
||||
/** @} */
|
Loading…
Reference in New Issue
Block a user