diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp
index 565a97342571..73bf7fb1edaa 100644
--- a/core/config/project_settings.cpp
+++ b/core/config/project_settings.cpp
@@ -1620,6 +1620,7 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "rendering/rendering_device/vsync/frame_queue_size", PROPERTY_HINT_RANGE, "2,3,1"), 2);
GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "rendering/rendering_device/vsync/swapchain_image_count", PROPERTY_HINT_RANGE, "2,4,1"), 3);
+ GLOBAL_DEF(PropertyInfo(Variant::BOOL, "rendering/rendering_device/vsync/wait_for_present"), true);
GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/rendering_device/staging_buffer/block_size_kb", PROPERTY_HINT_RANGE, "4,2048,1,or_greater"), 256);
GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/rendering_device/staging_buffer/max_size_mb", PROPERTY_HINT_RANGE, "1,1024,1,or_greater"), 128);
GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/rendering_device/staging_buffer/texture_upload_region_size_px", PROPERTY_HINT_RANGE, "1,256,1,or_greater"), 64);
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 863fef324281..78c7ed4a9870 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -3205,6 +3205,10 @@
[b]Note:[/b] This property is only read when the project starts. There is currently no way to change this value at run-time.
[b]Note:[/b] Some platforms may restrict the actual value.
+
+ Instructs Godot to use Waitable Swapchains when supported. Normally Godot allows the GPU to get [member rendering/rendering_device/vsync/frame_queue_size] ahead of the GPU. Assuming frame_queue_size = 2, Godot normally starts working on frame N+2 with the CPU when the GPU is done working on frame N. This setting makes Godot start frame N+2 when frame N is [i]done presenting[/i].
+ This gap between when the GPU finishes its work and when that work actually appears on screen can increase latency. [b]Enabling this setting thus reduces latency[/b] at the cost of some framerate (framerate may decrease because the CPU has to wait more time doing nothing).
+
The number of descriptors per pool. Godot's Vulkan backend uses linear pools for descriptors that will be created and destroyed within a single frame. Instead of destroying every single descriptor every frame, they all can be destroyed at once by resetting the pool they belong to.
A larger number is more efficient up to a limit, after that it will only waste RAM (maximum efficiency is achieved when there is no more than 1 pool per frame). A small number could end up with one pool per descriptor, which negatively impacts performance.
diff --git a/doc/classes/RenderingDevice.xml b/doc/classes/RenderingDevice.xml
index 67a265698d6d..ea6de04410fb 100644
--- a/doc/classes/RenderingDevice.xml
+++ b/doc/classes/RenderingDevice.xml
@@ -680,6 +680,11 @@
This is only used by Vulkan in debug builds. Godot must also be started with the [code]--extra-gpu-memory-tracking[/code] [url=$DOCS_URL/tutorials/editor/command_line_tutorial.html]command line argument[/url].
+
+
+
+
+
@@ -792,6 +797,13 @@
[b]Note:[/b] Resource names are only set when the engine runs in verbose mode ([method OS.is_stdout_verbose] = [code]true[/code]), or when using an engine build compiled with the [code]dev_mode=yes[/code] SCons option. The graphics driver must also support the [code]VK_EXT_DEBUG_UTILS_EXTENSION_NAME[/code] Vulkan extension for named resources to work.
+
+
+
+
+ See [member ProjectSettings.rendering/rendering_device/vsync/wait_for_present].
+
+
diff --git a/drivers/d3d12/rendering_device_driver_d3d12.cpp b/drivers/d3d12/rendering_device_driver_d3d12.cpp
index b964cb85b6fd..aef92c351f62 100644
--- a/drivers/d3d12/rendering_device_driver_d3d12.cpp
+++ b/drivers/d3d12/rendering_device_driver_d3d12.cpp
@@ -2400,6 +2400,11 @@ void RenderingDeviceDriverD3D12::_swap_chain_release_buffers(SwapChain *p_swap_c
p_swap_chain->render_targets.clear();
p_swap_chain->render_targets_info.clear();
+ if (p_swap_chain->waitable_object) {
+ CloseHandle(p_swap_chain->waitable_object);
+ p_swap_chain->waitable_object = nullptr;
+ }
+
for (RDD::FramebufferID framebuffer : p_swap_chain->framebuffers) {
framebuffer_free(framebuffer);
}
@@ -2457,6 +2462,7 @@ Error RenderingDeviceDriverD3D12::swap_chain_resize(CommandQueueID p_cmd_queue,
case DisplayServer::VSYNC_ENABLED: {
sync_interval = 1;
present_flags = 0;
+ creation_flags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT;
} break;
case DisplayServer::VSYNC_DISABLED: {
sync_interval = 0;
@@ -2467,6 +2473,7 @@ Error RenderingDeviceDriverD3D12::swap_chain_resize(CommandQueueID p_cmd_queue,
default:
sync_interval = 1;
present_flags = 0;
+ creation_flags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT;
break;
}
@@ -2509,6 +2516,11 @@ Error RenderingDeviceDriverD3D12::swap_chain_resize(CommandQueueID p_cmd_queue,
ERR_FAIL_COND_V(!SUCCEEDED(res), ERR_CANT_CREATE);
}
+ if (creation_flags & DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT) {
+ swap_chain->d3d_swap_chain->SetMaximumFrameLatency(UINT(frames.size()));
+ swap_chain->waitable_object = swap_chain->d3d_swap_chain->GetFrameLatencyWaitableObject();
+ }
+
if (surface->composition_device.Get() == nullptr) {
using PFN_DCompositionCreateDevice = HRESULT(WINAPI *)(IDXGIDevice *, REFIID, void **);
PFN_DCompositionCreateDevice pfn_DCompositionCreateDevice = (PFN_DCompositionCreateDevice)(void *)GetProcAddress(context_driver->lib_dcomp, "DCompositionCreateDevice");
@@ -2625,6 +2637,42 @@ void RenderingDeviceDriverD3D12::swap_chain_free(SwapChainID p_swap_chain) {
memdelete(swap_chain);
}
+Error RenderingDeviceDriverD3D12::swap_chain_wait_for_present(SwapChainID p_swap_chain, uint32_t p_max_frame_delay) {
+ SwapChain *swap_chain = (SwapChain *)(p_swap_chain.id);
+ if (swap_chain->waitable_object != NULL) {
+ UINT timeout = 1000u;
+
+ HRESULT res;
+
+ {
+ UINT current_frame_latency = 0u;
+ res = swap_chain->d3d_swap_chain->GetMaximumFrameLatency(¤t_frame_latency);
+
+ ERR_FAIL_COND_V_MSG(!SUCCEEDED(res), FAILED, "GetMaximumFrameLatency failed with error " + vformat("0x%08ux", (uint64_t)res) + ".");
+
+ if (p_max_frame_delay != current_frame_latency) {
+ swap_chain->d3d_swap_chain->SetMaximumFrameLatency(UINT(p_max_frame_delay));
+ }
+ }
+
+ do {
+ res = WaitForSingleObjectEx(swap_chain->waitable_object, timeout, FALSE);
+ } while (res == WAIT_IO_COMPLETION);
+
+ if (res == WAIT_TIMEOUT) {
+ ERR_FAIL_COND_V_MSG(!SUCCEEDED(res), ERR_TIMEOUT, "swap_chain_wait_for_present timeout exceeded.");
+ } else if (res == (HRESULT)WAIT_FAILED) {
+ DWORD error = GetLastError();
+ ERR_FAIL_COND_V_MSG(!SUCCEEDED(res), FAILED, "WaitForSingleObjectEx failed with error " + vformat("0x%08ux", (uint64_t)error) + ".");
+ } else if (res != WAIT_OBJECT_0) {
+ ERR_FAIL_COND_V_MSG(!SUCCEEDED(res), FAILED, "WaitForSingleObjectEx returned " + vformat("0x%08ux", (uint64_t)res) + ".");
+ }
+ return OK;
+ } else {
+ return ERR_UNAVAILABLE;
+ }
+}
+
/*********************/
/**** FRAMEBUFFER ****/
/*********************/
diff --git a/drivers/d3d12/rendering_device_driver_d3d12.h b/drivers/d3d12/rendering_device_driver_d3d12.h
index bc5ccc012350..301a483a3926 100644
--- a/drivers/d3d12/rendering_device_driver_d3d12.h
+++ b/drivers/d3d12/rendering_device_driver_d3d12.h
@@ -465,6 +465,7 @@ class RenderingDeviceDriverD3D12 : public RenderingDeviceDriver {
struct SwapChain {
ComPtr d3d_swap_chain;
+ HANDLE waitable_object;
RenderingContextDriver::SurfaceID surface = RenderingContextDriver::SurfaceID();
UINT present_flags = 0;
UINT sync_interval = 1;
@@ -486,6 +487,7 @@ class RenderingDeviceDriverD3D12 : public RenderingDeviceDriver {
virtual RenderPassID swap_chain_get_render_pass(SwapChainID p_swap_chain) override;
virtual DataFormat swap_chain_get_format(SwapChainID p_swap_chain) override;
virtual void swap_chain_free(SwapChainID p_swap_chain) override;
+ virtual Error swap_chain_wait_for_present(SwapChainID p_swap_chain, uint32_t p_max_frame_delay) override final;
/*********************/
/**** FRAMEBUFFER ****/
diff --git a/drivers/metal/rendering_device_driver_metal.h b/drivers/metal/rendering_device_driver_metal.h
index b4e5b6e7a37f..a5025ec36219 100644
--- a/drivers/metal/rendering_device_driver_metal.h
+++ b/drivers/metal/rendering_device_driver_metal.h
@@ -224,6 +224,7 @@ class API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) RenderingDeviceDriverMet
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;
+ virtual Error swap_chain_wait_for_present(SwapChainID p_swap_chain, uint32_t p_max_frame_delay) override final;
#pragma mark - Frame Buffer
diff --git a/drivers/metal/rendering_device_driver_metal.mm b/drivers/metal/rendering_device_driver_metal.mm
index cdae0f93331a..b015dcbea5fc 100644
--- a/drivers/metal/rendering_device_driver_metal.mm
+++ b/drivers/metal/rendering_device_driver_metal.mm
@@ -1052,6 +1052,10 @@ static const API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) MTLSamplerBorderC
memdelete(swap_chain);
}
+Error RenderingDeviceDriverMetal::swap_chain_wait_for_present(SwapChainID p_swap_chain, uint32_t p_max_frame_delay) {
+ return ERR_UNAVAILABLE;
+}
+
#pragma mark - Frame buffer
RDD::FramebufferID RenderingDeviceDriverMetal::framebuffer_create(RenderPassID p_render_pass, VectorView p_attachments, uint32_t p_width, uint32_t p_height) {
diff --git a/drivers/vulkan/rendering_device_driver_vulkan.cpp b/drivers/vulkan/rendering_device_driver_vulkan.cpp
index b62d39d18f3c..1f35d19fb73e 100644
--- a/drivers/vulkan/rendering_device_driver_vulkan.cpp
+++ b/drivers/vulkan/rendering_device_driver_vulkan.cpp
@@ -531,6 +531,8 @@ Error RenderingDeviceDriverVulkan::_initialize_device_extensions() {
_register_requested_device_extension(VK_EXT_ASTC_DECODE_MODE_EXTENSION_NAME, false);
_register_requested_device_extension(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME, false);
_register_requested_device_extension(VK_EXT_TEXTURE_COMPRESSION_ASTC_HDR_EXTENSION_NAME, false);
+ _register_requested_device_extension(VK_KHR_PRESENT_ID_EXTENSION_NAME, false);
+ _register_requested_device_extension(VK_KHR_PRESENT_WAIT_EXTENSION_NAME, false);
// We don't actually use this extension, but some runtime components on some platforms
// can and will fill the validation layers with useless info otherwise if not enabled.
@@ -758,6 +760,8 @@ Error RenderingDeviceDriverVulkan::_check_device_capabilities() {
VkPhysicalDevice16BitStorageFeaturesKHR storage_feature = {};
VkPhysicalDeviceMultiviewFeatures multiview_features = {};
VkPhysicalDevicePipelineCreationCacheControlFeatures pipeline_cache_control_features = {};
+ VkPhysicalDevicePresentIdFeaturesKHR present_id_features = {};
+ VkPhysicalDevicePresentWaitFeaturesKHR present_wait_features = {};
const bool use_1_2_features = physical_device_properties.apiVersion >= VK_API_VERSION_1_2;
if (use_1_2_features) {
@@ -807,6 +811,18 @@ Error RenderingDeviceDriverVulkan::_check_device_capabilities() {
next_features = &pipeline_cache_control_features;
}
+ if (enabled_device_extension_names.has(VK_KHR_PRESENT_ID_EXTENSION_NAME)) {
+ present_id_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_FEATURES_KHR;
+ present_id_features.pNext = next_features;
+ next_features = &present_id_features;
+ }
+
+ if (enabled_device_extension_names.has(VK_KHR_PRESENT_WAIT_EXTENSION_NAME)) {
+ present_wait_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_WAIT_FEATURES_KHR;
+ present_wait_features.pNext = next_features;
+ next_features = &present_wait_features;
+ }
+
VkPhysicalDeviceFeatures2 device_features_2 = {};
device_features_2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
device_features_2.pNext = next_features;
@@ -866,6 +882,10 @@ Error RenderingDeviceDriverVulkan::_check_device_capabilities() {
pipeline_cache_control_support = pipeline_cache_control_features.pipelineCreationCacheControl;
}
+ if (enabled_device_extension_names.has(VK_KHR_PRESENT_ID_EXTENSION_NAME) && enabled_device_extension_names.has(VK_KHR_PRESENT_WAIT_EXTENSION_NAME)) {
+ waitable_swapchain_support = present_id_features.presentId && present_wait_features.presentWait;
+ }
+
if (enabled_device_extension_names.has(VK_EXT_DEVICE_FAULT_EXTENSION_NAME)) {
device_fault_support = true;
}
@@ -1103,6 +1123,19 @@ Error RenderingDeviceDriverVulkan::_initialize_device(const LocalVector swapchains;
thread_local LocalVector image_indices;
thread_local LocalVector results;
+#if !defined(SWAPPY_FRAME_PACING_ENABLED)
+ thread_local LocalVector present_ids;
+#endif
swapchains.clear();
image_indices.clear();
@@ -2789,6 +2828,18 @@ Error RenderingDeviceDriverVulkan::command_queue_execute_and_present(CommandQueu
err = device_functions.QueuePresentKHR(device_queue.queue, &present_info);
}
#else
+ VkPresentIdKHR present_id = {};
+ if (waitable_swapchain_support) {
+ present_ids.resize(swapchains.size());
+ ++current_present_id;
+ for (uint64_t &id : present_ids) {
+ id = current_present_id;
+ }
+ present_id.sType = VK_STRUCTURE_TYPE_PRESENT_ID_KHR;
+ present_id.pPresentIds = present_ids.ptr();
+ present_id.swapchainCount = present_ids.size();
+ present_info.pNext = &present_id;
+ }
err = device_functions.QueuePresentKHR(device_queue.queue, &present_info);
#endif
@@ -3488,6 +3539,28 @@ void RenderingDeviceDriverVulkan::swap_chain_free(SwapChainID p_swap_chain) {
memdelete(swap_chain);
}
+Error RenderingDeviceDriverVulkan::swap_chain_wait_for_present(SwapChainID p_swap_chain, uint32_t p_max_frame_delay) {
+ if (!waitable_swapchain_support) {
+ return ERR_UNAVAILABLE;
+ }
+
+ if (current_present_id <= p_max_frame_delay) {
+ return OK;
+ }
+
+ SwapChain *swap_chain = (SwapChain *)(p_swap_chain.id);
+
+ constexpr uint64_t wait_timeout = 100'000'000;
+ VkResult err = device_functions.WaitForPresentKHR(vk_device, swap_chain->vk_swapchain, current_present_id - p_max_frame_delay, wait_timeout);
+
+ if (err == VK_TIMEOUT) {
+ ERR_FAIL_COND_V_MSG(err, ERR_TIMEOUT, "vkWaitForPresentKHR timeout exceeded.");
+ } else if (err != VK_SUCCESS && err != VK_SUBOPTIMAL_KHR) {
+ ERR_FAIL_COND_V_MSG(err, FAILED, "vkWaitForPresentKHR failed with error " + itos(err) + ".");
+ }
+ return OK;
+}
+
/*********************/
/**** FRAMEBUFFER ****/
/*********************/
diff --git a/drivers/vulkan/rendering_device_driver_vulkan.h b/drivers/vulkan/rendering_device_driver_vulkan.h
index d3a7bd3a9a2d..bcdfc2a6d02c 100644
--- a/drivers/vulkan/rendering_device_driver_vulkan.h
+++ b/drivers/vulkan/rendering_device_driver_vulkan.h
@@ -98,6 +98,7 @@ class RenderingDeviceDriverVulkan : public RenderingDeviceDriver {
PFN_vkQueuePresentKHR QueuePresentKHR = nullptr;
PFN_vkCreateRenderPass2KHR CreateRenderPass2KHR = nullptr;
PFN_vkCmdEndRenderPass2KHR EndRenderPass2KHR = nullptr;
+ PFN_vkWaitForPresentKHR WaitForPresentKHR = nullptr;
// Debug marker extensions.
PFN_vkCmdDebugMarkerBeginEXT CmdDebugMarkerBeginEXT = nullptr;
@@ -115,6 +116,7 @@ class RenderingDeviceDriverVulkan : public RenderingDeviceDriver {
RenderingContextDriverVulkan *context_driver = nullptr;
RenderingContextDriver::Device context_device = {};
uint32_t frame_count = 1;
+ uint64_t current_present_id = 0ul;
VkPhysicalDevice physical_device = VK_NULL_HANDLE;
VkPhysicalDeviceProperties physical_device_properties = {};
VkPhysicalDeviceFeatures physical_device_features = {};
@@ -141,6 +143,7 @@ class RenderingDeviceDriverVulkan : public RenderingDeviceDriver {
bool swappy_frame_pacer_enable = false;
uint8_t swappy_mode = 2; // See default value for display/window/frame_pacing/android/swappy_mode.
#endif
+ bool waitable_swapchain_support = false;
DeviceFunctions device_functions;
void _register_requested_device_extension(const CharString &p_extension_name, bool p_required);
@@ -383,6 +386,7 @@ class RenderingDeviceDriverVulkan : public RenderingDeviceDriver {
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;
+ virtual Error swap_chain_wait_for_present(SwapChainID p_swap_chain, uint32_t p_max_frame_delay) override final;
private:
/*********************/
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 05af591d84bc..073fc24851fa 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -415,6 +415,9 @@ void EditorNode::_update_from_settings() {
}
_update_title();
+ const bool wait_for_present = GLOBAL_GET("rendering/rendering_device/vsync/wait_for_present");
+ RD::get_singleton()->set_wait_for_present(wait_for_present);
+
int current_filter = GLOBAL_GET("rendering/textures/canvas_textures/default_texture_filter");
if (current_filter != scene_root->get_default_canvas_item_texture_filter()) {
Viewport::DefaultCanvasItemTextureFilter tf = (Viewport::DefaultCanvasItemTextureFilter)current_filter;
diff --git a/main/main.cpp b/main/main.cpp
index 0b8655a624d2..26605ec9fd33 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -4605,6 +4605,12 @@ static uint64_t navigation_process_max = 0;
bool Main::iteration() {
iterating++;
+ if (RD::get_singleton()) {
+ // We must do this right before input polling (i.e. DisplayServer**::process_events()).
+ // But we also must do this outside of timing measurements, so this is the 2nd best place.
+ RD::get_singleton()->_wait_for_present();
+ }
+
const uint64_t ticks = OS::get_singleton()->get_ticks_usec();
Engine::get_singleton()->_frame_ticks = ticks;
main_timer_sync.set_cpu_ticks_usec(ticks);
diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp
index 1e25fc872102..4c74446340cb 100644
--- a/servers/rendering/rendering_device.cpp
+++ b/servers/rendering/rendering_device.cpp
@@ -6352,6 +6352,25 @@ void RenderingDevice::_free_pending_resources(int p_frame) {
}
}
+void RenderingDevice::set_wait_for_present(bool p_wait_for_present) {
+ wait_for_present = p_wait_for_present;
+}
+
+bool RenderingDevice::get_wait_for_present() const {
+ return wait_for_present;
+}
+
+void RenderingDevice::_wait_for_present() {
+ if (!wait_for_present) {
+ return;
+ }
+ HashMap::ConstIterator it = screen_swap_chains.find(DisplayServer::MAIN_WINDOW_ID);
+ if (it != screen_swap_chains.end()) {
+ const uint32_t max_frame_delay = frames.size();
+ driver->swap_chain_wait_for_present(it->value, max_frame_delay);
+ }
+}
+
uint32_t RenderingDevice::get_frame_delay() const {
return frames.size();
}
@@ -6674,6 +6693,8 @@ Error RenderingDevice::initialize(RenderingContextDriver *p_context, DisplayServ
frames.resize(frame_count);
max_timestamp_query_elements = GLOBAL_GET("debug/settings/profiler/max_timestamp_query_elements");
+ wait_for_present = GLOBAL_GET("rendering/rendering_device/vsync/wait_for_present");
+
device = context->device_get(device_index);
err = driver->initialize(device_index, frame_count);
ERR_FAIL_COND_V_MSG(err != OK, FAILED, "Failed to initialize driver for device.");
@@ -7425,6 +7446,8 @@ void RenderingDevice::_bind_methods() {
ClassDB::bind_method(D_METHOD("has_feature", "feature"), &RenderingDevice::has_feature);
ClassDB::bind_method(D_METHOD("limit_get", "limit"), &RenderingDevice::limit_get);
+ ClassDB::bind_method(D_METHOD("set_wait_for_present", "wait_for_present"), &RenderingDevice::set_wait_for_present);
+ ClassDB::bind_method(D_METHOD("get_wait_for_present"), &RenderingDevice::get_wait_for_present);
ClassDB::bind_method(D_METHOD("get_frame_delay"), &RenderingDevice::get_frame_delay);
ClassDB::bind_method(D_METHOD("submit"), &RenderingDevice::submit);
ClassDB::bind_method(D_METHOD("sync"), &RenderingDevice::sync);
diff --git a/servers/rendering/rendering_device.h b/servers/rendering/rendering_device.h
index 5eb89538ae61..3ad141018edb 100644
--- a/servers/rendering/rendering_device.h
+++ b/servers/rendering/rendering_device.h
@@ -194,6 +194,9 @@ class RenderingDevice : public RenderingDeviceCommons {
Error _buffer_initialize(Buffer *p_buffer, const uint8_t *p_data, size_t p_data_size, uint32_t p_required_align = 32);
void update_perf_report();
+
+ bool wait_for_present = false;
+
// Flag for batching descriptor sets.
bool descriptor_set_batching = true;
// When true, the final draw call that copies our offscreen result into the Swapchain is put into its
@@ -1623,6 +1626,11 @@ class RenderingDevice : public RenderingDeviceCommons {
void swap_buffers(bool p_present);
+ void set_wait_for_present(bool p_wait_for_present);
+ bool get_wait_for_present() const;
+
+ void _wait_for_present();
+
uint32_t get_frame_delay() const;
void submit();
diff --git a/servers/rendering/rendering_device_driver.h b/servers/rendering/rendering_device_driver.h
index 31517ea58204..af0b5104f86f 100644
--- a/servers/rendering/rendering_device_driver.h
+++ b/servers/rendering/rendering_device_driver.h
@@ -484,6 +484,8 @@ class RenderingDeviceDriver : public RenderingDeviceCommons {
// Wait until all rendering associated to the swap chain is finished before deleting it.
virtual void swap_chain_free(SwapChainID p_swap_chain) = 0;
+ virtual Error swap_chain_wait_for_present(SwapChainID p_swap_chain, uint32_t p_max_frame_delay) = 0;
+
/*********************/
/**** FRAMEBUFFER ****/
/*********************/