Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/config/project_settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions doc/classes/ProjectSettings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
</member>
<member name="rendering/rendering_device/vsync/wait_for_present" type="bool" setter="" getter="" default="true">
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).
Comment on lines +3209 to +3210
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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).
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 [code]frame_queue_size = 2[/code], Godot normally starts working on frame [code]N + 2[/code] with the CPU when the GPU is done working on frame [code]N[/code]. This setting makes Godot start frame [code]N + 2[/code] when frame [code]N[/code] 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).

</member>
<member name="rendering/rendering_device/vulkan/max_descriptors_per_pool" type="int" setter="" getter="" default="64">
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.
Expand Down
12 changes: 12 additions & 0 deletions doc/classes/RenderingDevice.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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].
</description>
</method>
<method name="get_wait_for_present" qualifiers="const">
<return type="bool" />
<description>
</description>
</method>
<method name="has_feature" qualifiers="const">
<return type="bool" />
<param index="0" name="feature" type="int" enum="RenderingDevice.Features" />
Expand Down Expand Up @@ -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.
</description>
</method>
<method name="set_wait_for_present">
<return type="void" />
<param index="0" name="wait_for_present" type="bool" />
<description>
See [member ProjectSettings.rendering/rendering_device/vsync/wait_for_present].
</description>
</method>
<method name="shader_compile_binary_from_spirv">
<return type="PackedByteArray" />
<param index="0" name="spirv_data" type="RDShaderSPIRV" />
Expand Down
48 changes: 48 additions & 0 deletions drivers/d3d12/rendering_device_driver_d3d12.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}

Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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(&current_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 ****/
/*********************/
Expand Down
2 changes: 2 additions & 0 deletions drivers/d3d12/rendering_device_driver_d3d12.h
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,7 @@ class RenderingDeviceDriverD3D12 : public RenderingDeviceDriver {

struct SwapChain {
ComPtr<IDXGISwapChain3> d3d_swap_chain;
HANDLE waitable_object;
RenderingContextDriver::SurfaceID surface = RenderingContextDriver::SurfaceID();
UINT present_flags = 0;
UINT sync_interval = 1;
Expand All @@ -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 ****/
Expand Down
1 change: 1 addition & 0 deletions drivers/metal/rendering_device_driver_metal.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 4 additions & 0 deletions drivers/metal/rendering_device_driver_metal.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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<TextureID> p_attachments, uint32_t p_width, uint32_t p_height) {
Expand Down
73 changes: 73 additions & 0 deletions drivers/vulkan/rendering_device_driver_vulkan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -1103,6 +1123,19 @@ Error RenderingDeviceDriverVulkan::_initialize_device(const LocalVector<VkDevice
create_info_next = &pipeline_cache_control_features;
}

VkPhysicalDevicePresentIdFeaturesKHR present_id_features = {};
VkPhysicalDevicePresentWaitFeaturesKHR present_wait_features = {};
if (waitable_swapchain_support) {
present_id_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_FEATURES_KHR;
present_id_features.pNext = create_info_next;
present_id_features.presentId = VK_TRUE;
create_info_next = &present_id_features;
present_wait_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_WAIT_FEATURES_KHR;
present_wait_features.pNext = create_info_next;
present_wait_features.presentWait = VK_TRUE;
create_info_next = &present_wait_features;
}

VkPhysicalDeviceFaultFeaturesEXT device_fault_features = {};
if (device_fault_support) {
device_fault_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FAULT_FEATURES_EXT;
Expand Down Expand Up @@ -1195,6 +1228,9 @@ Error RenderingDeviceDriverVulkan::_initialize_device(const LocalVector<VkDevice
device_functions.GetSwapchainImagesKHR = PFN_vkGetSwapchainImagesKHR(functions.GetDeviceProcAddr(vk_device, "vkGetSwapchainImagesKHR"));
device_functions.AcquireNextImageKHR = PFN_vkAcquireNextImageKHR(functions.GetDeviceProcAddr(vk_device, "vkAcquireNextImageKHR"));
device_functions.QueuePresentKHR = PFN_vkQueuePresentKHR(functions.GetDeviceProcAddr(vk_device, "vkQueuePresentKHR"));
if (waitable_swapchain_support) {
device_functions.WaitForPresentKHR = PFN_vkWaitForPresentKHR(functions.GetDeviceProcAddr(vk_device, "vkWaitForPresentKHR"));
}

if (enabled_device_extension_names.has(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME)) {
device_functions.CreateRenderPass2KHR = PFN_vkCreateRenderPass2KHR(functions.GetDeviceProcAddr(vk_device, "vkCreateRenderPass2KHR"));
Expand Down Expand Up @@ -2760,6 +2796,9 @@ Error RenderingDeviceDriverVulkan::command_queue_execute_and_present(CommandQueu
thread_local LocalVector<VkSwapchainKHR> swapchains;
thread_local LocalVector<uint32_t> image_indices;
thread_local LocalVector<VkResult> results;
#if !defined(SWAPPY_FRAME_PACING_ENABLED)
thread_local LocalVector<uint64_t> present_ids;
#endif
swapchains.clear();
image_indices.clear();

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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 ****/
/*********************/
Expand Down
4 changes: 4 additions & 0 deletions drivers/vulkan/rendering_device_driver_vulkan.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 = {};
Expand All @@ -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);
Expand Down Expand Up @@ -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:
/*********************/
Expand Down
3 changes: 3 additions & 0 deletions editor/editor_node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 6 additions & 0 deletions main/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading
Loading