Merge e82fdc778c
into 49ebdaaae3
This commit is contained in:
commit
4c6637630d
|
@ -0,0 +1,51 @@
|
|||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
// atomic<shared_ptr<T>> is heavy and has limited compiler availability.
|
||||
// atomic<unique_ptr<T>> isn't feasible.
|
||||
|
||||
// This class provides something similar to a would-be atomic unique_ptr.
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
|
||||
namespace Common
|
||||
{
|
||||
|
||||
template <typename T>
|
||||
class AtomicUniquePtr
|
||||
{
|
||||
public:
|
||||
using UniquePtr = std::unique_ptr<T>;
|
||||
using RawPtr = T*;
|
||||
|
||||
AtomicUniquePtr() = default;
|
||||
~AtomicUniquePtr() { Store(nullptr); }
|
||||
|
||||
AtomicUniquePtr(const AtomicUniquePtr&) = delete;
|
||||
void operator=(const AtomicUniquePtr&) = delete;
|
||||
|
||||
explicit AtomicUniquePtr(std::nullptr_t) {}
|
||||
explicit AtomicUniquePtr(UniquePtr ptr) { Store(std::move(ptr)); }
|
||||
|
||||
void operator=(std::nullptr_t) { Store(nullptr); }
|
||||
void operator=(UniquePtr ptr) { Store(std::move(ptr)); }
|
||||
|
||||
void Store(UniquePtr desired)
|
||||
{
|
||||
// A unique_ptr is returned and appropriately destructed here.
|
||||
Exchange(std::move(desired));
|
||||
}
|
||||
|
||||
UniquePtr Exchange(UniquePtr desired)
|
||||
{
|
||||
return UniquePtr{m_ptr.exchange(desired.release(), std::memory_order_acq_rel)};
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<RawPtr> m_ptr = nullptr;
|
||||
};
|
||||
|
||||
} // namespace Common
|
|
@ -14,6 +14,7 @@ add_library(common
|
|||
Assembler/GekkoParser.cpp
|
||||
Assembler/GekkoParser.h
|
||||
Assert.h
|
||||
AtomicUniquePtr.h
|
||||
BitField.h
|
||||
BitSet.h
|
||||
BitUtils.h
|
||||
|
|
|
@ -324,6 +324,7 @@ const Info<int> MAIN_ISO_PATH_COUNT{{System::Main, "General", "ISOPaths"}, 0};
|
|||
const Info<std::string> MAIN_SKYLANDERS_PATH{{System::Main, "General", "SkylandersCollectionPath"},
|
||||
""};
|
||||
const Info<bool> MAIN_TIME_TRACKING{{System::Main, "General", "EnablePlayTimeTracking"}, true};
|
||||
const Info<bool> MAIN_SYNC_REFRESH_RATE{{System::Main, "General", "SyncToHostRefreshRate"}, true};
|
||||
|
||||
static Info<std::string> MakeISOPathConfigInfo(size_t idx)
|
||||
{
|
||||
|
|
|
@ -190,6 +190,7 @@ extern const Info<bool> MAIN_RENDER_WINDOW_AUTOSIZE;
|
|||
extern const Info<bool> MAIN_KEEP_WINDOW_ON_TOP;
|
||||
extern const Info<bool> MAIN_DISABLE_SCREENSAVER;
|
||||
extern const Info<bool> MAIN_TIME_TRACKING;
|
||||
extern const Info<bool> MAIN_SYNC_REFRESH_RATE;
|
||||
|
||||
// Main.General
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include "VideoCommon/PerformanceMetrics.h"
|
||||
#include "VideoCommon/VideoBackendBase.h"
|
||||
#include "VideoCommon/VideoConfig.h"
|
||||
#include "VideoCommon/VideoEvents.h"
|
||||
|
||||
namespace CoreTiming
|
||||
{
|
||||
|
@ -103,6 +104,19 @@ void CoreTimingManager::Init()
|
|||
|
||||
m_event_fifo_id = 0;
|
||||
m_ev_lost = RegisterEvent("_lost_event", &EmptyTimedCallback);
|
||||
|
||||
m_after_frame_hook = AfterPresentEvent::Register(
|
||||
[this](const PresentInfo& info) {
|
||||
const bool sync_to_host_active =
|
||||
Config::Get(Config::MAIN_SYNC_REFRESH_RATE) && g_ActiveConfig.bVSyncActive;
|
||||
|
||||
const auto presentation_time = PresentationTime{.ticks = info.emulated_timestamp,
|
||||
.time = Clock::now(),
|
||||
.sync_to_host_active = sync_to_host_active};
|
||||
|
||||
m_last_presentation.Store(std::make_unique<PresentationTime>(presentation_time));
|
||||
},
|
||||
"CoreTiming AfterPresentEvent");
|
||||
}
|
||||
|
||||
void CoreTimingManager::Shutdown()
|
||||
|
@ -112,6 +126,8 @@ void CoreTimingManager::Shutdown()
|
|||
ClearPendingEvents();
|
||||
UnregisterAllEvents();
|
||||
CPUThreadConfigCallback::RemoveConfigChangedCallback(m_registered_config_callback_id);
|
||||
m_after_frame_hook.reset();
|
||||
m_last_presentation = nullptr;
|
||||
}
|
||||
|
||||
void CoreTimingManager::RefreshConfig()
|
||||
|
@ -201,6 +217,7 @@ void CoreTimingManager::DoState(PointerWrap& p)
|
|||
// The stave state has changed the time, so our previous Throttle targets are invalid.
|
||||
// Especially when global_time goes down; So we create a fake throttle update.
|
||||
ResetThrottle(m_globals.global_timer);
|
||||
m_last_presentation = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -399,6 +416,14 @@ void CoreTimingManager::SleepUntil(TimePoint time_point)
|
|||
|
||||
void CoreTimingManager::Throttle(const s64 target_cycle)
|
||||
{
|
||||
// Adjust throttle based on last presentation if "Sync to Host Refresh Rate" was active.
|
||||
const auto last_presentation = m_last_presentation.Exchange(nullptr);
|
||||
if (last_presentation && last_presentation->sync_to_host_active)
|
||||
{
|
||||
m_throttle_last_cycle = last_presentation->ticks;
|
||||
m_throttle_deadline = last_presentation->time;
|
||||
}
|
||||
|
||||
// Based on number of cycles and emulation speed, increase the target deadline
|
||||
const s64 cycles = target_cycle - m_throttle_last_cycle;
|
||||
m_throttle_last_cycle = target_cycle;
|
||||
|
|
|
@ -22,7 +22,9 @@
|
|||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/AtomicUniquePtr.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/HookableEvent.h"
|
||||
#include "Common/SPSCQueue.h"
|
||||
#include "Common/Timer.h"
|
||||
#include "Core/CPUThreadConfigCallback.h"
|
||||
|
@ -219,6 +221,16 @@ private:
|
|||
std::atomic_bool m_use_precision_timer = false;
|
||||
Common::PrecisionTimer m_precision_cpu_timer;
|
||||
Common::PrecisionTimer m_precision_gpu_timer;
|
||||
|
||||
struct PresentationTime
|
||||
{
|
||||
u64 ticks;
|
||||
Clock::time_point time;
|
||||
bool sync_to_host_active;
|
||||
};
|
||||
Common::AtomicUniquePtr<PresentationTime> m_last_presentation;
|
||||
|
||||
Common::EventHook m_after_frame_hook;
|
||||
};
|
||||
|
||||
} // namespace CoreTiming
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
<ClInclude Include="Common\Assembler\GekkoIRGen.h" />
|
||||
<ClInclude Include="Common\Assembler\GekkoLexer.h" />
|
||||
<ClInclude Include="Common\Assembler\GekkoParser.h" />
|
||||
<ClInclude Include="Common\AtomicUniquePtr.h" />
|
||||
<ClInclude Include="Common\BitField.h" />
|
||||
<ClInclude Include="Common\BitSet.h" />
|
||||
<ClInclude Include="Common\BitUtils.h" />
|
||||
|
|
|
@ -88,6 +88,19 @@ void AdvancedPane::CreateLayout()
|
|||
"needed.<br><br><dolphin_emphasis>If unsure, leave this unchecked.</dolphin_emphasis>"));
|
||||
cpu_options_group_layout->addWidget(m_accurate_cpu_cache_checkbox);
|
||||
|
||||
auto* const timing_group = new QGroupBox(tr("Timing"));
|
||||
main_layout->addWidget(timing_group);
|
||||
auto* timing_group_layout = new QVBoxLayout{timing_group};
|
||||
auto* const sync_to_host_refresh =
|
||||
new ConfigBool{tr("Sync to Host Refresh Rate"), Config::MAIN_SYNC_REFRESH_RATE};
|
||||
sync_to_host_refresh->SetDescription(
|
||||
tr("Adjusts emulation speed to match host refresh rate when V-Sync is enabled."
|
||||
"<br>This can make 59.94 FPS games run at 60 FPS."
|
||||
"<br><br>Not needed or recommended for users with variable refresh rate displays."
|
||||
"<br><br>Has no effect when Immediate XFB is in use."
|
||||
"<br><br><dolphin_emphasis>If unsure, leave this unchecked.</dolphin_emphasis>"));
|
||||
timing_group_layout->addWidget(sync_to_host_refresh);
|
||||
|
||||
auto* clock_override = new QGroupBox(tr("Clock Override"));
|
||||
auto* clock_override_layout = new QVBoxLayout();
|
||||
clock_override->setLayout(clock_override_layout);
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "VideoBackends/Vulkan/CommandBufferManager.h"
|
||||
#include "VideoBackends/Vulkan/ObjectCache.h"
|
||||
#include "VideoBackends/Vulkan/StateTracker.h"
|
||||
|
@ -316,6 +317,9 @@ void VKGfx::PresentBackbuffer()
|
|||
// End drawing to backbuffer
|
||||
StateTracker::GetInstance()->EndRenderPass();
|
||||
|
||||
const bool wait_for_completion =
|
||||
Config::Get(Config::MAIN_SYNC_REFRESH_RATE) && g_ActiveConfig.bVSyncActive;
|
||||
|
||||
if (m_swap_chain->IsCurrentImageValid())
|
||||
{
|
||||
// Transition the backbuffer to PRESENT_SRC to ensure all commands drawing
|
||||
|
@ -327,12 +331,13 @@ void VKGfx::PresentBackbuffer()
|
|||
// Because this final command buffer is rendering to the swap chain, we need to wait for
|
||||
// the available semaphore to be signaled before executing the buffer. This final submission
|
||||
// can happen off-thread in the background while we're preparing the next frame.
|
||||
g_command_buffer_mgr->SubmitCommandBuffer(true, false, true, m_swap_chain->GetSwapChain(),
|
||||
g_command_buffer_mgr->SubmitCommandBuffer(true, wait_for_completion, true,
|
||||
m_swap_chain->GetSwapChain(),
|
||||
m_swap_chain->GetCurrentImageIndex());
|
||||
}
|
||||
else
|
||||
{
|
||||
g_command_buffer_mgr->SubmitCommandBuffer(true, false, true);
|
||||
g_command_buffer_mgr->SubmitCommandBuffer(true, wait_for_completion, true);
|
||||
}
|
||||
|
||||
// New cmdbuffer, so invalidate state.
|
||||
|
|
|
@ -97,7 +97,12 @@ void VideoBackendBase::Video_OutputXFB(u32 xfb_addr, u32 fb_width, u32 fb_stride
|
|||
auto& system = Core::System::GetInstance();
|
||||
system.GetFifo().SyncGPU(Fifo::SyncGPUReason::Swap);
|
||||
|
||||
const TimePoint presentation_time = system.GetCoreTiming().GetTargetHostTime(ticks);
|
||||
const bool sync_to_host_refresh =
|
||||
Config::Get(Config::MAIN_SYNC_REFRESH_RATE) && g_ActiveConfig.bVSyncActive;
|
||||
|
||||
const TimePoint presentation_time =
|
||||
sync_to_host_refresh ? Clock::now() : system.GetCoreTiming().GetTargetHostTime(ticks);
|
||||
|
||||
AsyncRequests::GetInstance()->PushEvent([=] {
|
||||
g_presenter->ViSwap(xfb_addr, fb_width, fb_stride, fb_height, ticks, presentation_time);
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue