diff --git a/src/core/system.cpp b/src/core/system.cpp index 05cace0a3..07bb9c7e8 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -2167,23 +2167,28 @@ void System::FrameDone() { s_skipped_frame_count = 0; - const bool throttle_before_present = (s_optimal_frame_pacing && s_throttler_enabled && !IsExecutionInterrupted()); - const bool explicit_present = (throttle_before_present && g_gpu_device->GetFeatures().explicit_present); - if (explicit_present) + const bool scheduled_present = (s_optimal_frame_pacing && s_throttler_enabled && !IsExecutionInterrupted()); + const GPUDevice::Features features = g_gpu_device->GetFeatures(); + if (scheduled_present && features.timed_present) { - const bool do_present = PresentDisplay(true); + PresentDisplay(false, s_next_frame_time); + Throttle(current_time); + } + else if (scheduled_present && features.explicit_present) + { + const bool do_present = PresentDisplay(true, 0); Throttle(current_time); if (do_present) g_gpu_device->SubmitPresent(); } else { - if (throttle_before_present) + if (scheduled_present) Throttle(current_time); - PresentDisplay(false); + PresentDisplay(false, 0); - if (!throttle_before_present && s_throttler_enabled && !IsExecutionInterrupted()) + if (!scheduled_present && s_throttler_enabled && !IsExecutionInterrupted()) Throttle(current_time); } } @@ -5741,7 +5746,7 @@ void System::HostDisplayResized() g_gpu->UpdateResolutionScale(); } -bool System::PresentDisplay(bool explicit_present) +bool System::PresentDisplay(bool explicit_present, u64 present_time) { // acquire for IO.MousePos. std::atomic_thread_fence(std::memory_order_acquire); @@ -5761,7 +5766,7 @@ bool System::PresentDisplay(bool explicit_present) if (pres == GPUDevice::PresentResult::OK) { g_gpu_device->RenderImGui(); - g_gpu_device->EndPresent(explicit_present); + g_gpu_device->EndPresent(explicit_present, present_time); if (g_gpu_device->IsGPUTimingEnabled()) { @@ -5785,7 +5790,7 @@ bool System::PresentDisplay(bool explicit_present) void System::InvalidateDisplay() { - PresentDisplay(false); + PresentDisplay(false, 0); if (g_gpu) g_gpu->RestoreDeviceContext(); diff --git a/src/core/system.h b/src/core/system.h index 5ad538506..4a8e9e77b 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -448,7 +448,7 @@ void RequestDisplaySize(float scale = 0.0f); void HostDisplayResized(); /// Renders the display. -bool PresentDisplay(bool explicit_present); +bool PresentDisplay(bool explicit_present, u64 present_time); void InvalidateDisplay(); ////////////////////////////////////////////////////////////////////////// diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index 2b3c9cef9..054b955ca 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -497,7 +497,7 @@ bool QtHost::SetCriticalFolders() // the resources directory should exist, bail out if not const std::string rcc_path = Path::Combine(EmuFolders::Resources, "duckstation-qt.rcc"); if (!FileSystem::FileExists(rcc_path.c_str()) || !QResource::registerResource(QString::fromStdString(rcc_path)) || -#ifdef _WIN32 +#if defined(_WIN32) || defined(__APPLE__) !FileSystem::DirectoryExists(EmuFolders::Resources.c_str()) #else !FileSystem::IsRealDirectory(EmuFolders::Resources.c_str()) @@ -1795,7 +1795,7 @@ void EmuThread::run() System::Internal::IdlePollUpdate(); if (g_gpu_device) { - System::PresentDisplay(false); + System::PresentDisplay(false, 0); if (!g_gpu_device->IsVSyncModeBlocking()) g_gpu_device->ThrottlePresentation(); } diff --git a/src/util/d3d11_device.cpp b/src/util/d3d11_device.cpp index 1da0abad5..51dd88007 100644 --- a/src/util/d3d11_device.cpp +++ b/src/util/d3d11_device.cpp @@ -187,6 +187,7 @@ void D3D11Device::SetFeatures(FeatureMask disabled_features) m_features.partial_msaa_resolve = false; m_features.memory_import = false; m_features.explicit_present = false; + m_features.timed_present = false; m_features.gpu_timing = true; m_features.shader_cache = true; m_features.pipeline_cache = false; @@ -674,9 +675,9 @@ GPUDevice::PresentResult D3D11Device::BeginPresent(u32 clear_color) return PresentResult::OK; } -void D3D11Device::EndPresent(bool explicit_present) +void D3D11Device::EndPresent(bool explicit_present, u64 present_time) { - DebugAssert(!explicit_present); + DebugAssert(!explicit_present && present_time == 0); DebugAssert(m_num_current_render_targets == 0 && !m_current_depth_target); if (m_vsync_mode != GPUVSyncMode::FIFO && m_gpu_timing_enabled) diff --git a/src/util/d3d11_device.h b/src/util/d3d11_device.h index f029bb992..b4f66ba07 100644 --- a/src/util/d3d11_device.h +++ b/src/util/d3d11_device.h @@ -103,7 +103,7 @@ public: float GetAndResetAccumulatedGPUTime() override; PresentResult BeginPresent(u32 clear_color) override; - void EndPresent(bool explicit_present) override; + void EndPresent(bool explicit_present, u64 present_time) override; void SubmitPresent() override; void UnbindPipeline(D3D11Pipeline* pl); diff --git a/src/util/d3d12_device.cpp b/src/util/d3d12_device.cpp index fb72a2ef0..9bd4a9967 100644 --- a/src/util/d3d12_device.cpp +++ b/src/util/d3d12_device.cpp @@ -1176,8 +1176,9 @@ GPUDevice::PresentResult D3D12Device::BeginPresent(u32 clear_color) return PresentResult::OK; } -void D3D12Device::EndPresent(bool explicit_present) +void D3D12Device::EndPresent(bool explicit_present, u64 present_time) { + DebugAssert(present_time == 0); DebugAssert(InRenderPass() && m_num_current_render_targets == 0 && !m_current_depth_target); EndRenderPass(); @@ -1285,6 +1286,7 @@ void D3D12Device::SetFeatures(D3D_FEATURE_LEVEL feature_level, FeatureMask disab m_features.partial_msaa_resolve = true; m_features.memory_import = false; m_features.explicit_present = true; + m_features.timed_present = false; m_features.gpu_timing = true; m_features.shader_cache = true; m_features.pipeline_cache = true; diff --git a/src/util/d3d12_device.h b/src/util/d3d12_device.h index 74dc675f7..48689b2e6 100644 --- a/src/util/d3d12_device.h +++ b/src/util/d3d12_device.h @@ -125,7 +125,7 @@ public: float GetAndResetAccumulatedGPUTime() override; PresentResult BeginPresent(u32 clear_color) override; - void EndPresent(bool explicit_present) override; + void EndPresent(bool explicit_present, u64 present_time) override; void SubmitPresent() override; // Global state accessors diff --git a/src/util/gpu_device.h b/src/util/gpu_device.h index c051ad740..858aec34c 100644 --- a/src/util/gpu_device.h +++ b/src/util/gpu_device.h @@ -502,6 +502,7 @@ public: bool partial_msaa_resolve : 1; bool memory_import : 1; bool explicit_present : 1; + bool timed_present : 1; bool gpu_timing : 1; bool shader_cache : 1; bool pipeline_cache : 1; @@ -710,7 +711,7 @@ public: /// Returns false if the window was completely occluded. virtual PresentResult BeginPresent(u32 clear_color = DEFAULT_CLEAR_COLOR) = 0; - virtual void EndPresent(bool explicit_submit) = 0; + virtual void EndPresent(bool explicit_submit, u64 submit_time = 0) = 0; virtual void SubmitPresent() = 0; /// Renders ImGui screen elements. Call before EndPresent(). diff --git a/src/util/metal_device.h b/src/util/metal_device.h index 21a7e35c7..9c5c0415c 100644 --- a/src/util/metal_device.h +++ b/src/util/metal_device.h @@ -264,7 +264,7 @@ public: void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override; PresentResult BeginPresent(u32 clear_color) override; - void EndPresent(bool explicit_submit) override; + void EndPresent(bool explicit_submit, u64 present_time) override; void SubmitPresent() override; void WaitForFenceCounter(u64 counter); diff --git a/src/util/metal_device.mm b/src/util/metal_device.mm index d50882c06..d8a3626cd 100644 --- a/src/util/metal_device.mm +++ b/src/util/metal_device.mm @@ -17,6 +17,7 @@ #include "fmt/format.h" #include +#include #include Log_SetChannel(MetalDevice); @@ -31,32 +32,40 @@ static constexpr u32 TEXTURE_UPLOAD_ALIGNMENT = 64; // We need 32 here for AVX2, so 64 is also fine. static constexpr u32 TEXTURE_UPLOAD_PITCH_ALIGNMENT = 64; +// Used for present timing. +static const struct mach_timebase_info s_timebase_info = []() { + struct mach_timebase_info val; + const kern_return_t res = mach_timebase_info(&val); + Assert(res == KERN_SUCCESS); + return val; +}(); + static constexpr std::array(GPUTexture::Format::MaxCount)> s_pixel_format_mapping = { - MTLPixelFormatInvalid, // Unknown - MTLPixelFormatRGBA8Unorm, // RGBA8 - MTLPixelFormatBGRA8Unorm, // BGRA8 - MTLPixelFormatB5G6R5Unorm, // RGB565 - MTLPixelFormatA1BGR5Unorm, // RGBA5551 - MTLPixelFormatR8Unorm, // R8 - MTLPixelFormatDepth16Unorm, // D16 + MTLPixelFormatInvalid, // Unknown + MTLPixelFormatRGBA8Unorm, // RGBA8 + MTLPixelFormatBGRA8Unorm, // BGRA8 + MTLPixelFormatB5G6R5Unorm, // RGB565 + MTLPixelFormatA1BGR5Unorm, // RGBA5551 + MTLPixelFormatR8Unorm, // R8 + MTLPixelFormatDepth16Unorm, // D16 MTLPixelFormatDepth24Unorm_Stencil8, // D24S8 - MTLPixelFormatDepth32Float, // D32F + MTLPixelFormatDepth32Float, // D32F MTLPixelFormatDepth32Float_Stencil8, // D32FS8 - MTLPixelFormatR16Unorm, // R16 - MTLPixelFormatR16Sint, // R16I - MTLPixelFormatR16Uint, // R16U - MTLPixelFormatR16Float, // R16F - MTLPixelFormatR32Sint, // R32I - MTLPixelFormatR32Uint, // R32U - MTLPixelFormatR32Float, // R32F - MTLPixelFormatRG8Unorm, // RG8 - MTLPixelFormatRG16Unorm, // RG16 - MTLPixelFormatRG16Float, // RG16F - MTLPixelFormatRG32Float, // RG32F - MTLPixelFormatRGBA16Unorm, // RGBA16 - MTLPixelFormatRGBA16Float, // RGBA16F - MTLPixelFormatRGBA32Float, // RGBA32F - MTLPixelFormatBGR10A2Unorm, // RGB10A2 + MTLPixelFormatR16Unorm, // R16 + MTLPixelFormatR16Sint, // R16I + MTLPixelFormatR16Uint, // R16U + MTLPixelFormatR16Float, // R16F + MTLPixelFormatR32Sint, // R32I + MTLPixelFormatR32Uint, // R32U + MTLPixelFormatR32Float, // R32F + MTLPixelFormatRG8Unorm, // RG8 + MTLPixelFormatRG16Unorm, // RG16 + MTLPixelFormatRG16Float, // RG16F + MTLPixelFormatRG32Float, // RG32F + MTLPixelFormatRGBA16Unorm, // RGBA16 + MTLPixelFormatRGBA16Float, // RGBA16F + MTLPixelFormatRGBA32Float, // RGBA32F + MTLPixelFormatBGR10A2Unorm, // RGB10A2 }; static NSString* StringViewToNSString(std::string_view str) @@ -78,7 +87,8 @@ static void LogNSError(NSError* error, std::string_view message) static void NSErrorToErrorObject(Error* errptr, std::string_view message, NSError* error) { - Error::SetStringFmt(errptr, "{}NSError Code {}: {}", message, static_cast(error.code), [error.description UTF8String]); + Error::SetStringFmt(errptr, "{}NSError Code {}: {}", message, static_cast(error.code), + [error.description UTF8String]); } static GPUTexture::Format GetTextureFormatForMTLFormat(MTLPixelFormat fmt) @@ -253,6 +263,7 @@ void MetalDevice::SetFeatures(FeatureMask disabled_features) m_features.partial_msaa_resolve = false; m_features.memory_import = true; m_features.explicit_present = false; + m_features.timed_present = true; m_features.shader_cache = true; m_features.pipeline_cache = false; m_features.prefer_unused_textures = true; @@ -2335,7 +2346,8 @@ GPUDevice::PresentResult MetalDevice::BeginPresent(u32 clear_color) id layer_texture = [m_layer_drawable texture]; m_layer_pass_desc.colorAttachments[0].texture = layer_texture; m_layer_pass_desc.colorAttachments[0].loadAction = MTLLoadActionClear; - m_layer_pass_desc.colorAttachments[0].clearColor = MTLClearColorMake(clear_color_v.r, clear_color_v.g, clear_color_v.g, clear_color_v.a); + m_layer_pass_desc.colorAttachments[0].clearColor = + MTLClearColorMake(clear_color_v.r, clear_color_v.g, clear_color_v.g, clear_color_v.a); m_render_encoder = [[m_render_cmdbuf renderCommandEncoderWithDescriptor:m_layer_pass_desc] retain]; s_stats.num_render_passes++; std::memset(m_current_render_targets.data(), 0, sizeof(m_current_render_targets)); @@ -2349,17 +2361,28 @@ GPUDevice::PresentResult MetalDevice::BeginPresent(u32 clear_color) } } -void MetalDevice::EndPresent(bool explicit_present) +void MetalDevice::EndPresent(bool explicit_present, u64 present_time) { DebugAssert(!explicit_present); - - // TODO: Explicit present DebugAssert(m_num_current_render_targets == 0 && !m_current_depth_target); EndAnyEncoding(); - [m_render_cmdbuf presentDrawable:m_layer_drawable]; + Common::Timer::Value current_time; + if (present_time != 0 && (current_time = Common::Timer::GetCurrentValue()) < present_time) + { + // Need to convert to mach absolute time. Time values should already be in nanoseconds. + const u64 mach_time_nanoseconds = ((mach_absolute_time() * s_timebase_info.numer) / s_timebase_info.denom); + const double mach_present_time = static_cast(mach_time_nanoseconds + (present_time - current_time)) / 1e+9; + [m_render_cmdbuf presentDrawable:m_layer_drawable atTime:mach_present_time]; + } + else + { + [m_render_cmdbuf presentDrawable:m_layer_drawable]; + } + DeferRelease(m_layer_drawable); m_layer_drawable = nil; + SubmitCommandBuffer(); TrimTexturePool(); } diff --git a/src/util/opengl_device.cpp b/src/util/opengl_device.cpp index 7b03c40f7..31daf9c49 100644 --- a/src/util/opengl_device.cpp +++ b/src/util/opengl_device.cpp @@ -491,6 +491,7 @@ bool OpenGLDevice::CheckFeatures(FeatureMask disabled_features) m_features.partial_msaa_resolve = true; m_features.memory_import = true; m_features.explicit_present = false; + m_features.timed_present = false; m_features.shader_cache = false; @@ -772,9 +773,9 @@ GPUDevice::PresentResult OpenGLDevice::BeginPresent(u32 clear_color) return PresentResult::OK; } -void OpenGLDevice::EndPresent(bool explicit_present) +void OpenGLDevice::EndPresent(bool explicit_present, u64 present_time) { - DebugAssert(!explicit_present); + DebugAssert(!explicit_present && present_time == 0); DebugAssert(m_current_fbo == 0); if (m_gpu_timing_enabled) diff --git a/src/util/opengl_device.h b/src/util/opengl_device.h index a54140553..44b3db867 100644 --- a/src/util/opengl_device.h +++ b/src/util/opengl_device.h @@ -103,7 +103,7 @@ public: void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override; PresentResult BeginPresent(u32 clear_color) override; - void EndPresent(bool explicit_present) override; + void EndPresent(bool explicit_present, u64 present_time) override; void SubmitPresent() override; bool SetGPUTimingEnabled(bool enabled) override; diff --git a/src/util/vulkan_device.cpp b/src/util/vulkan_device.cpp index 517417b97..b9a2daf2a 100644 --- a/src/util/vulkan_device.cpp +++ b/src/util/vulkan_device.cpp @@ -2393,8 +2393,9 @@ GPUDevice::PresentResult VulkanDevice::BeginPresent(u32 clear_color) return PresentResult::OK; } -void VulkanDevice::EndPresent(bool explicit_present) +void VulkanDevice::EndPresent(bool explicit_present, u64 present_time) { + DebugAssert(present_time == 0); DebugAssert(InRenderPass() && m_num_current_render_targets == 0 && !m_current_depth_target); EndRenderPass(); @@ -2544,6 +2545,7 @@ void VulkanDevice::SetFeatures(FeatureMask disabled_features, const VkPhysicalDe m_features.partial_msaa_resolve = true; m_features.memory_import = m_optional_extensions.vk_ext_external_memory_host; m_features.explicit_present = true; + m_features.timed_present = false; m_features.shader_cache = true; m_features.pipeline_cache = true; m_features.prefer_unused_textures = true; diff --git a/src/util/vulkan_device.h b/src/util/vulkan_device.h index 48b620f0a..22c595f8e 100644 --- a/src/util/vulkan_device.h +++ b/src/util/vulkan_device.h @@ -141,7 +141,7 @@ public: void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override; PresentResult BeginPresent(u32 clear_color) override; - void EndPresent(bool explicit_present) override; + void EndPresent(bool explicit_present, u64 present_time) override; void SubmitPresent() override; // Global state accessors