MetalDevice: Implement timed present

This commit is contained in:
Stenzek 2024-09-13 15:19:29 +10:00
parent 338fb278e8
commit 68b82ab55b
No known key found for this signature in database
14 changed files with 89 additions and 54 deletions

View file

@ -2167,23 +2167,28 @@ void System::FrameDone()
{ {
s_skipped_frame_count = 0; s_skipped_frame_count = 0;
const bool throttle_before_present = (s_optimal_frame_pacing && s_throttler_enabled && !IsExecutionInterrupted()); const bool scheduled_present = (s_optimal_frame_pacing && s_throttler_enabled && !IsExecutionInterrupted());
const bool explicit_present = (throttle_before_present && g_gpu_device->GetFeatures().explicit_present); const GPUDevice::Features features = g_gpu_device->GetFeatures();
if (explicit_present) 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); Throttle(current_time);
if (do_present) if (do_present)
g_gpu_device->SubmitPresent(); g_gpu_device->SubmitPresent();
} }
else else
{ {
if (throttle_before_present) if (scheduled_present)
Throttle(current_time); 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); Throttle(current_time);
} }
} }
@ -5741,7 +5746,7 @@ void System::HostDisplayResized()
g_gpu->UpdateResolutionScale(); g_gpu->UpdateResolutionScale();
} }
bool System::PresentDisplay(bool explicit_present) bool System::PresentDisplay(bool explicit_present, u64 present_time)
{ {
// acquire for IO.MousePos. // acquire for IO.MousePos.
std::atomic_thread_fence(std::memory_order_acquire); std::atomic_thread_fence(std::memory_order_acquire);
@ -5761,7 +5766,7 @@ bool System::PresentDisplay(bool explicit_present)
if (pres == GPUDevice::PresentResult::OK) if (pres == GPUDevice::PresentResult::OK)
{ {
g_gpu_device->RenderImGui(); g_gpu_device->RenderImGui();
g_gpu_device->EndPresent(explicit_present); g_gpu_device->EndPresent(explicit_present, present_time);
if (g_gpu_device->IsGPUTimingEnabled()) if (g_gpu_device->IsGPUTimingEnabled())
{ {
@ -5785,7 +5790,7 @@ bool System::PresentDisplay(bool explicit_present)
void System::InvalidateDisplay() void System::InvalidateDisplay()
{ {
PresentDisplay(false); PresentDisplay(false, 0);
if (g_gpu) if (g_gpu)
g_gpu->RestoreDeviceContext(); g_gpu->RestoreDeviceContext();

View file

@ -448,7 +448,7 @@ void RequestDisplaySize(float scale = 0.0f);
void HostDisplayResized(); void HostDisplayResized();
/// Renders the display. /// Renders the display.
bool PresentDisplay(bool explicit_present); bool PresentDisplay(bool explicit_present, u64 present_time);
void InvalidateDisplay(); void InvalidateDisplay();
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////

View file

@ -497,7 +497,7 @@ bool QtHost::SetCriticalFolders()
// the resources directory should exist, bail out if not // the resources directory should exist, bail out if not
const std::string rcc_path = Path::Combine(EmuFolders::Resources, "duckstation-qt.rcc"); 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)) || 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()) !FileSystem::DirectoryExists(EmuFolders::Resources.c_str())
#else #else
!FileSystem::IsRealDirectory(EmuFolders::Resources.c_str()) !FileSystem::IsRealDirectory(EmuFolders::Resources.c_str())
@ -1795,7 +1795,7 @@ void EmuThread::run()
System::Internal::IdlePollUpdate(); System::Internal::IdlePollUpdate();
if (g_gpu_device) if (g_gpu_device)
{ {
System::PresentDisplay(false); System::PresentDisplay(false, 0);
if (!g_gpu_device->IsVSyncModeBlocking()) if (!g_gpu_device->IsVSyncModeBlocking())
g_gpu_device->ThrottlePresentation(); g_gpu_device->ThrottlePresentation();
} }

View file

@ -187,6 +187,7 @@ void D3D11Device::SetFeatures(FeatureMask disabled_features)
m_features.partial_msaa_resolve = false; m_features.partial_msaa_resolve = false;
m_features.memory_import = false; m_features.memory_import = false;
m_features.explicit_present = false; m_features.explicit_present = false;
m_features.timed_present = false;
m_features.gpu_timing = true; m_features.gpu_timing = true;
m_features.shader_cache = true; m_features.shader_cache = true;
m_features.pipeline_cache = false; m_features.pipeline_cache = false;
@ -674,9 +675,9 @@ GPUDevice::PresentResult D3D11Device::BeginPresent(u32 clear_color)
return PresentResult::OK; 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); DebugAssert(m_num_current_render_targets == 0 && !m_current_depth_target);
if (m_vsync_mode != GPUVSyncMode::FIFO && m_gpu_timing_enabled) if (m_vsync_mode != GPUVSyncMode::FIFO && m_gpu_timing_enabled)

View file

@ -103,7 +103,7 @@ public:
float GetAndResetAccumulatedGPUTime() override; float GetAndResetAccumulatedGPUTime() override;
PresentResult BeginPresent(u32 clear_color) 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 SubmitPresent() override;
void UnbindPipeline(D3D11Pipeline* pl); void UnbindPipeline(D3D11Pipeline* pl);

View file

@ -1176,8 +1176,9 @@ GPUDevice::PresentResult D3D12Device::BeginPresent(u32 clear_color)
return PresentResult::OK; 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); DebugAssert(InRenderPass() && m_num_current_render_targets == 0 && !m_current_depth_target);
EndRenderPass(); EndRenderPass();
@ -1285,6 +1286,7 @@ void D3D12Device::SetFeatures(D3D_FEATURE_LEVEL feature_level, FeatureMask disab
m_features.partial_msaa_resolve = true; m_features.partial_msaa_resolve = true;
m_features.memory_import = false; m_features.memory_import = false;
m_features.explicit_present = true; m_features.explicit_present = true;
m_features.timed_present = false;
m_features.gpu_timing = true; m_features.gpu_timing = true;
m_features.shader_cache = true; m_features.shader_cache = true;
m_features.pipeline_cache = true; m_features.pipeline_cache = true;

View file

@ -125,7 +125,7 @@ public:
float GetAndResetAccumulatedGPUTime() override; float GetAndResetAccumulatedGPUTime() override;
PresentResult BeginPresent(u32 clear_color) 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 SubmitPresent() override;
// Global state accessors // Global state accessors

View file

@ -502,6 +502,7 @@ public:
bool partial_msaa_resolve : 1; bool partial_msaa_resolve : 1;
bool memory_import : 1; bool memory_import : 1;
bool explicit_present : 1; bool explicit_present : 1;
bool timed_present : 1;
bool gpu_timing : 1; bool gpu_timing : 1;
bool shader_cache : 1; bool shader_cache : 1;
bool pipeline_cache : 1; bool pipeline_cache : 1;
@ -710,7 +711,7 @@ public:
/// Returns false if the window was completely occluded. /// Returns false if the window was completely occluded.
virtual PresentResult BeginPresent(u32 clear_color = DEFAULT_CLEAR_COLOR) = 0; 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; virtual void SubmitPresent() = 0;
/// Renders ImGui screen elements. Call before EndPresent(). /// Renders ImGui screen elements. Call before EndPresent().

View file

@ -264,7 +264,7 @@ public:
void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override; void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override;
PresentResult BeginPresent(u32 clear_color) 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 SubmitPresent() override;
void WaitForFenceCounter(u64 counter); void WaitForFenceCounter(u64 counter);

View file

@ -17,6 +17,7 @@
#include "fmt/format.h" #include "fmt/format.h"
#include <array> #include <array>
#include <mach/mach_time.h>
#include <pthread.h> #include <pthread.h>
Log_SetChannel(MetalDevice); 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. // We need 32 here for AVX2, so 64 is also fine.
static constexpr u32 TEXTURE_UPLOAD_PITCH_ALIGNMENT = 64; 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<MTLPixelFormat, static_cast<u32>(GPUTexture::Format::MaxCount)> s_pixel_format_mapping = { static constexpr std::array<MTLPixelFormat, static_cast<u32>(GPUTexture::Format::MaxCount)> s_pixel_format_mapping = {
MTLPixelFormatInvalid, // Unknown MTLPixelFormatInvalid, // Unknown
MTLPixelFormatRGBA8Unorm, // RGBA8 MTLPixelFormatRGBA8Unorm, // RGBA8
MTLPixelFormatBGRA8Unorm, // BGRA8 MTLPixelFormatBGRA8Unorm, // BGRA8
MTLPixelFormatB5G6R5Unorm, // RGB565 MTLPixelFormatB5G6R5Unorm, // RGB565
MTLPixelFormatA1BGR5Unorm, // RGBA5551 MTLPixelFormatA1BGR5Unorm, // RGBA5551
MTLPixelFormatR8Unorm, // R8 MTLPixelFormatR8Unorm, // R8
MTLPixelFormatDepth16Unorm, // D16 MTLPixelFormatDepth16Unorm, // D16
MTLPixelFormatDepth24Unorm_Stencil8, // D24S8 MTLPixelFormatDepth24Unorm_Stencil8, // D24S8
MTLPixelFormatDepth32Float, // D32F MTLPixelFormatDepth32Float, // D32F
MTLPixelFormatDepth32Float_Stencil8, // D32FS8 MTLPixelFormatDepth32Float_Stencil8, // D32FS8
MTLPixelFormatR16Unorm, // R16 MTLPixelFormatR16Unorm, // R16
MTLPixelFormatR16Sint, // R16I MTLPixelFormatR16Sint, // R16I
MTLPixelFormatR16Uint, // R16U MTLPixelFormatR16Uint, // R16U
MTLPixelFormatR16Float, // R16F MTLPixelFormatR16Float, // R16F
MTLPixelFormatR32Sint, // R32I MTLPixelFormatR32Sint, // R32I
MTLPixelFormatR32Uint, // R32U MTLPixelFormatR32Uint, // R32U
MTLPixelFormatR32Float, // R32F MTLPixelFormatR32Float, // R32F
MTLPixelFormatRG8Unorm, // RG8 MTLPixelFormatRG8Unorm, // RG8
MTLPixelFormatRG16Unorm, // RG16 MTLPixelFormatRG16Unorm, // RG16
MTLPixelFormatRG16Float, // RG16F MTLPixelFormatRG16Float, // RG16F
MTLPixelFormatRG32Float, // RG32F MTLPixelFormatRG32Float, // RG32F
MTLPixelFormatRGBA16Unorm, // RGBA16 MTLPixelFormatRGBA16Unorm, // RGBA16
MTLPixelFormatRGBA16Float, // RGBA16F MTLPixelFormatRGBA16Float, // RGBA16F
MTLPixelFormatRGBA32Float, // RGBA32F MTLPixelFormatRGBA32Float, // RGBA32F
MTLPixelFormatBGR10A2Unorm, // RGB10A2 MTLPixelFormatBGR10A2Unorm, // RGB10A2
}; };
static NSString* StringViewToNSString(std::string_view str) 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) static void NSErrorToErrorObject(Error* errptr, std::string_view message, NSError* error)
{ {
Error::SetStringFmt(errptr, "{}NSError Code {}: {}", message, static_cast<u32>(error.code), [error.description UTF8String]); Error::SetStringFmt(errptr, "{}NSError Code {}: {}", message, static_cast<u32>(error.code),
[error.description UTF8String]);
} }
static GPUTexture::Format GetTextureFormatForMTLFormat(MTLPixelFormat fmt) static GPUTexture::Format GetTextureFormatForMTLFormat(MTLPixelFormat fmt)
@ -253,6 +263,7 @@ void MetalDevice::SetFeatures(FeatureMask disabled_features)
m_features.partial_msaa_resolve = false; m_features.partial_msaa_resolve = false;
m_features.memory_import = true; m_features.memory_import = true;
m_features.explicit_present = false; m_features.explicit_present = false;
m_features.timed_present = true;
m_features.shader_cache = true; m_features.shader_cache = true;
m_features.pipeline_cache = false; m_features.pipeline_cache = false;
m_features.prefer_unused_textures = true; m_features.prefer_unused_textures = true;
@ -2335,7 +2346,8 @@ GPUDevice::PresentResult MetalDevice::BeginPresent(u32 clear_color)
id<MTLTexture> layer_texture = [m_layer_drawable texture]; id<MTLTexture> layer_texture = [m_layer_drawable texture];
m_layer_pass_desc.colorAttachments[0].texture = layer_texture; m_layer_pass_desc.colorAttachments[0].texture = layer_texture;
m_layer_pass_desc.colorAttachments[0].loadAction = MTLLoadActionClear; 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]; m_render_encoder = [[m_render_cmdbuf renderCommandEncoderWithDescriptor:m_layer_pass_desc] retain];
s_stats.num_render_passes++; s_stats.num_render_passes++;
std::memset(m_current_render_targets.data(), 0, sizeof(m_current_render_targets)); 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); DebugAssert(!explicit_present);
// TODO: Explicit present
DebugAssert(m_num_current_render_targets == 0 && !m_current_depth_target); DebugAssert(m_num_current_render_targets == 0 && !m_current_depth_target);
EndAnyEncoding(); 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<double>(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); DeferRelease(m_layer_drawable);
m_layer_drawable = nil; m_layer_drawable = nil;
SubmitCommandBuffer(); SubmitCommandBuffer();
TrimTexturePool(); TrimTexturePool();
} }

View file

@ -491,6 +491,7 @@ bool OpenGLDevice::CheckFeatures(FeatureMask disabled_features)
m_features.partial_msaa_resolve = true; m_features.partial_msaa_resolve = true;
m_features.memory_import = true; m_features.memory_import = true;
m_features.explicit_present = false; m_features.explicit_present = false;
m_features.timed_present = false;
m_features.shader_cache = false; m_features.shader_cache = false;
@ -772,9 +773,9 @@ GPUDevice::PresentResult OpenGLDevice::BeginPresent(u32 clear_color)
return PresentResult::OK; 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); DebugAssert(m_current_fbo == 0);
if (m_gpu_timing_enabled) if (m_gpu_timing_enabled)

View file

@ -103,7 +103,7 @@ public:
void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override; void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override;
PresentResult BeginPresent(u32 clear_color) 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 SubmitPresent() override;
bool SetGPUTimingEnabled(bool enabled) override; bool SetGPUTimingEnabled(bool enabled) override;

View file

@ -2393,8 +2393,9 @@ GPUDevice::PresentResult VulkanDevice::BeginPresent(u32 clear_color)
return PresentResult::OK; 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); DebugAssert(InRenderPass() && m_num_current_render_targets == 0 && !m_current_depth_target);
EndRenderPass(); EndRenderPass();
@ -2544,6 +2545,7 @@ void VulkanDevice::SetFeatures(FeatureMask disabled_features, const VkPhysicalDe
m_features.partial_msaa_resolve = true; m_features.partial_msaa_resolve = true;
m_features.memory_import = m_optional_extensions.vk_ext_external_memory_host; m_features.memory_import = m_optional_extensions.vk_ext_external_memory_host;
m_features.explicit_present = true; m_features.explicit_present = true;
m_features.timed_present = false;
m_features.shader_cache = true; m_features.shader_cache = true;
m_features.pipeline_cache = true; m_features.pipeline_cache = true;
m_features.prefer_unused_textures = true; m_features.prefer_unused_textures = true;

View file

@ -141,7 +141,7 @@ public:
void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override; void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override;
PresentResult BeginPresent(u32 clear_color) 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 SubmitPresent() override;
// Global state accessors // Global state accessors