diff options
| -rw-r--r-- | src/atomic_time_point.cc | 27 | ||||
| -rw-r--r-- | src/atomic_time_point.hh | 35 | ||||
| -rw-r--r-- | src/strategies/anti_lag/device_strategy.cc | 20 | ||||
| -rw-r--r-- | src/strategies/anti_lag/device_strategy.hh | 6 | ||||
| -rw-r--r-- | src/strategies/low_latency2/swapchain_monitor.cc | 29 | ||||
| -rw-r--r-- | src/strategies/low_latency2/swapchain_monitor.hh | 5 |
6 files changed, 93 insertions, 29 deletions
diff --git a/src/atomic_time_point.cc b/src/atomic_time_point.cc new file mode 100644 index 0000000..4aaef7f --- /dev/null +++ b/src/atomic_time_point.cc @@ -0,0 +1,27 @@ +#include "atomic_time_point.hh" + +#include <cassert> + +namespace low_latency { + +AtomicTimePoint::AtomicTimePoint() {} + +AtomicTimePoint::~AtomicTimePoint() {} + +bool AtomicTimePoint::has_value() const { + return this->count.load(std::memory_order_relaxed); +} + +std::chrono::steady_clock::time_point AtomicTimePoint::get() const { + const auto result = this->count.load(std::memory_order_relaxed); + assert(result); + using namespace std::chrono; + return steady_clock::time_point{steady_clock::duration{result}}; +} + +void AtomicTimePoint::set(const std::chrono::steady_clock::time_point target) { + this->count.store(target.time_since_epoch().count(), + std::memory_order_relaxed); +} + +} // namespace low_latency
\ No newline at end of file diff --git a/src/atomic_time_point.hh b/src/atomic_time_point.hh new file mode 100644 index 0000000..13e62aa --- /dev/null +++ b/src/atomic_time_point.hh @@ -0,0 +1,35 @@ +#ifndef ATOMIC_TIME_POINT_HH_ +#define ATOMIC_TIME_POINT_HH_ + +#include <atomic> +#include <chrono> + +// The purpose of this class is to provide a simple time point which may be read +// from atomically and without locks. + +namespace low_latency { + +class AtomicTimePoint final { + private: + std::atomic<std::int64_t> count{}; + static_assert(decltype(count)::is_always_lock_free); + + public: + AtomicTimePoint(); + AtomicTimePoint(const AtomicTimePoint&) = delete; + AtomicTimePoint(AtomicTimePoint&&) = delete; + AtomicTimePoint operator=(const AtomicTimePoint&) = delete; + AtomicTimePoint operator=(AtomicTimePoint&&) = delete; + ~AtomicTimePoint(); + + public: + bool has_value() const; + + std::chrono::steady_clock::time_point get() const; + + void set(const std::chrono::steady_clock::time_point target); +}; + +} // namespace low_latency + +#endif
\ No newline at end of file diff --git a/src/strategies/anti_lag/device_strategy.cc b/src/strategies/anti_lag/device_strategy.cc index 2d1d9ad..c0ab882 100644 --- a/src/strategies/anti_lag/device_strategy.cc +++ b/src/strategies/anti_lag/device_strategy.cc @@ -19,7 +19,7 @@ void AntiLagDeviceStrategy::notify_update(const VkAntiLagDataAMD& data) { this->is_enabled = !(data.mode == VK_ANTI_LAG_MODE_OFF_AMD); - this->delay = [&]() -> std::chrono::microseconds { + this->input_delay = [&]() -> std::chrono::microseconds { using namespace std::chrono; if (!data.maxFPS) { return 0us; @@ -40,7 +40,10 @@ void AntiLagDeviceStrategy::notify_update(const VkAntiLagDataAMD& data) { // If we're at the input stage, start marking submissions as relevant. this->frame_index.emplace(data.pPresentationInfo->frameIndex); + // Grab this before we unlock the mutex. + const auto delay = this->input_delay; lock.unlock(); + // We need to collect all queue submission and wait on them in this thread. // Input stage needs to wait for all queue submissions to complete. const auto queue_frame_spans = [&]() -> auto { @@ -61,25 +64,20 @@ void AntiLagDeviceStrategy::notify_update(const VkAntiLagDataAMD& data) { return queue_frame_spans; }(); - // Wait on them and relock the mutex. + // Wait on outstanding work to complete. for (const auto& frame_span : queue_frame_spans) { if (frame_span) { // Can still be null here. frame_span->await_completed(); } } - lock.lock(); - // We might need to wait a little more time to meet our frame limit. using namespace std::chrono; - if (this->delay != 0us && this->previous_input_release.has_value()) { - lock.unlock(); - std::this_thread::sleep_until(*this->previous_input_release + - this->delay); - lock.lock(); + if (delay != 0us && this->previous_input_release.has_value()) { + const auto last = this->previous_input_release.get(); + std::this_thread::sleep_until(last + delay); } - - this->previous_input_release = steady_clock::now(); + this->previous_input_release.set(steady_clock::now()); } bool AntiLagDeviceStrategy::should_track_submissions() { diff --git a/src/strategies/anti_lag/device_strategy.hh b/src/strategies/anti_lag/device_strategy.hh index 3533647..9ff1212 100644 --- a/src/strategies/anti_lag/device_strategy.hh +++ b/src/strategies/anti_lag/device_strategy.hh @@ -1,6 +1,7 @@ #ifndef STRATEGIES_ANTI_LAG_DEVICE_STRATEGY_HH_ #define STRATEGIES_ANTI_LAG_DEVICE_STRATEGY_HH_ +#include "atomic_time_point.hh" #include "strategies/device_strategy.hh" #include <vulkan/vulkan.h> @@ -18,10 +19,11 @@ class AntiLagDeviceStrategy final : public DeviceStrategy { std::shared_mutex mutex{}; // If this is nullopt don't track the submission. std::optional<std::uint64_t> frame_index{}; - std::optional<std::chrono::steady_clock::time_point> previous_input_release{}; - std::chrono::microseconds delay{}; + std::chrono::microseconds input_delay{}; bool is_enabled{}; + AtomicTimePoint previous_input_release; + public: AntiLagDeviceStrategy(DeviceContext& device); virtual ~AntiLagDeviceStrategy(); diff --git a/src/strategies/low_latency2/swapchain_monitor.cc b/src/strategies/low_latency2/swapchain_monitor.cc index 3d1d276..c2a328b 100644 --- a/src/strategies/low_latency2/swapchain_monitor.cc +++ b/src/strategies/low_latency2/swapchain_monitor.cc @@ -53,28 +53,26 @@ void SwapchainMonitor::do_monitor(const std::stop_token stoken) { break; } - // Unlock, wait for work to finish, lock again. + // Grab mutex protected present delay before we sleep - doesn't matter + // if it's 'old'. + const auto delay = this->present_delay; lock.unlock(); + + // Wait for work to complete. for (const auto& frame_span : pending_signal.frame_spans) { if (frame_span) { frame_span->await_completed(); } } - lock.lock(); + // Wait for possible need to delay the frame. using namespace std::chrono; - if (this->present_delay != 0us) { - const auto last_time = this->last_signal_time; - const auto delay = this->present_delay; - if (last_time.has_value()) { - lock.unlock(); - std::this_thread::sleep_until(*last_time + delay); - lock.lock(); - } - this->last_signal_time.emplace(steady_clock::now()); + if (delay != 0us && this->last_signal_time.has_value()) { + const auto last = this->last_signal_time.get(); + std::this_thread::sleep_until(last + delay); } - lock.unlock(); + this->last_signal_time.set(std::chrono::steady_clock::now()); pending_signal.wakeup_semaphore.signal(this->device); } } @@ -93,8 +91,11 @@ void SwapchainMonitor::notify_semaphore(const VkSemaphore& timeline_semaphore, return; } - // Signal immediately if we have no outstanding work. - if (std::ranges::all_of(this->pending_frame_spans, + // Signal immediately if we don't need to worry about delaying the frame and + // we have no outstanding work. + using namespace std::chrono; + if (this->present_delay == 0us && + std::ranges::all_of(this->pending_frame_spans, [](const auto& frame_span) { if (!frame_span) { return true; diff --git a/src/strategies/low_latency2/swapchain_monitor.hh b/src/strategies/low_latency2/swapchain_monitor.hh index a5f8362..28771cf 100644 --- a/src/strategies/low_latency2/swapchain_monitor.hh +++ b/src/strategies/low_latency2/swapchain_monitor.hh @@ -2,8 +2,10 @@ #ifndef SWAPCHAIN_MONITOR_HH_ #define SWAPCHAIN_MONITOR_HH_ +#include "atomic_time_point.hh" #include "frame_span.hh" +#include <atomic> #include <vulkan/vulkan.h> #include <chrono> @@ -40,12 +42,11 @@ class SwapchainMonitor final { std::mutex mutex{}; std::chrono::microseconds present_delay{}; bool was_low_latency_requested{}; + AtomicTimePoint last_signal_time{}; std::condition_variable_any cv{}; std::jthread monitor_worker{}; - std::optional<std::chrono::steady_clock::time_point> last_signal_time; - void do_monitor(const std::stop_token stoken); public: |
