#include "swapchain_monitor.hh" #include "device_context.hh" #include namespace low_latency { SwapchainMonitor::SwapchainMonitor(const DeviceContext& device) : device(device), monitor_worker(std::bind_front(&SwapchainMonitor::do_monitor, this)) {} SwapchainMonitor::~SwapchainMonitor() {} void SwapchainMonitor::update_params(const bool was_low_latency_requested, const std::chrono::microseconds delay) { const auto lock = std::scoped_lock{this->mutex}; this->was_low_latency_requested = was_low_latency_requested; this->present_delay = delay; } void SwapchainMonitor::do_monitor(const std::stop_token stoken) { for (;;) { auto lock = std::unique_lock{this->mutex}; this->cv.wait(lock, stoken, [&]() { return !this->pending_signals.empty(); }); // Stop only if we're stopped and we have nothing to signal. if (stoken.stop_requested() && this->pending_signals.empty()) { break; } // Grab the most recent semaphore. When work completes, signal it. const auto pending_signal = std::move(this->pending_signals.front()); this->pending_signals.pop_front(); // If we're stopping, signal the semaphore and don't worry about work // actually completing. But we MUST drain them, or we get a hang. if (stoken.stop_requested()) { pending_signal.semaphore_signal.signal(this->device); continue; } // Grab mutex protected present delay before we sleep - doesn't matter // if it's 'old'. const auto delay = this->present_delay; this->is_monitor_processing.store(true, std::memory_order_relaxed); lock.unlock(); // Wait for work to complete. for (const auto& frame_span : pending_signal.frame_spans) { if (frame_span) { frame_span->await_completed(); } } // Wait for possible need to delay the frame. using namespace std::chrono; if (delay != 0us && this->last_signal_time.has_value()) { const auto last = this->last_signal_time.get(); std::this_thread::sleep_until(last + delay); } this->last_signal_time.set(std::chrono::steady_clock::now()); this->is_monitor_processing.store(false, std::memory_order_relaxed); pending_signal.semaphore_signal.signal(this->device); } } void SwapchainMonitor::notify_semaphore( const SemaphoreSignal& semaphore_signal) { auto lock = std::unique_lock{this->mutex}; // Signal immediately if reflex is off or it's a no-op submit. if (!this->was_low_latency_requested) { semaphore_signal.signal(this->device); return; } // 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 && !this->is_monitor_processing.load(std::memory_order_relaxed) && std::ranges::all_of(this->pending_frame_spans, [](const auto& frame_span) { if (!frame_span) { return true; } return frame_span->has_completed(); })) { semaphore_signal.signal(this->device); this->pending_frame_spans.clear(); return; } this->pending_signals.emplace_back(PendingSignal{ .semaphore_signal = semaphore_signal, .frame_spans = std::move(this->pending_frame_spans), }); this->pending_frame_spans.clear(); lock.unlock(); this->cv.notify_one(); } void SwapchainMonitor::attach_work( std::vector> frame_spans) { const auto lock = std::scoped_lock{this->mutex}; if (!this->was_low_latency_requested) { return; } this->pending_frame_spans = std::move(frame_spans); } } // namespace low_latency