aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorNicolas James <Eele1Ephe7uZahRie@tutanota.com>2026-03-31 13:17:09 +1100
committerNicolas James <Eele1Ephe7uZahRie@tutanota.com>2026-03-31 13:17:09 +1100
commitdf2933fd9c0ea2a99e89a6837123dfdf8b549d4a (patch)
tree42a5c73fc3735636efbe1eb7bf16adccd9aee664 /src
parent89f4c0f59a90b1a4447d171bd09235126561af91 (diff)
Split monitoring strategy between Reflex and AL2
Diffstat (limited to 'src')
-rw-r--r--src/device_context.cc6
-rw-r--r--src/device_context.hh3
-rw-r--r--src/layer.cc33
-rw-r--r--src/swapchain_monitor.cc59
-rw-r--r--src/swapchain_monitor.hh70
5 files changed, 120 insertions, 51 deletions
diff --git a/src/device_context.cc b/src/device_context.cc
index c9f1fd5..b52fec4 100644
--- a/src/device_context.cc
+++ b/src/device_context.cc
@@ -37,14 +37,14 @@ void DeviceContext::update_params(
// swapchains), just write it to everything.
if (!target.has_value()) {
for (auto& iter : this->swapchain_monitors) {
- iter.second.update_params(was_low_latency_requested, present_delay);
+ iter.second->update_params(was_low_latency_requested, present_delay);
}
return;
}
const auto iter = this->swapchain_monitors.find(*target);
assert(iter != std::end(this->swapchain_monitors));
- iter->second.update_params(was_low_latency_requested, present_delay);
+ iter->second->update_params(was_low_latency_requested, present_delay);
}
void DeviceContext::notify_present(
@@ -54,7 +54,7 @@ void DeviceContext::notify_present(
const auto iter = this->swapchain_monitors.find(swapchain);
assert(iter != std::end(this->swapchain_monitors));
- iter->second.notify_present(submissions);
+ iter->second->notify_present(submissions);
}
} // namespace low_latency \ No newline at end of file
diff --git a/src/device_context.hh b/src/device_context.hh
index 53970e5..a46f479 100644
--- a/src/device_context.hh
+++ b/src/device_context.hh
@@ -35,7 +35,8 @@ class DeviceContext final : public Context {
std::unordered_map<VkQueue, std::shared_ptr<QueueContext>> queues;
- std::unordered_map<VkSwapchainKHR, SwapchainMonitor> swapchain_monitors;
+ std::unordered_map<VkSwapchainKHR, std::unique_ptr<SwapchainMonitor>>
+ swapchain_monitors;
public:
DeviceContext(InstanceContext& parent_instance,
diff --git a/src/layer.cc b/src/layer.cc
index bc988f0..cf9f56e 100644
--- a/src/layer.cc
+++ b/src/layer.cc
@@ -20,6 +20,7 @@
#include "instance_context.hh"
#include "layer_context.hh"
#include "queue_context.hh"
+#include "swapchain_monitor.hh"
#include "timestamp_pool.hh"
namespace low_latency {
@@ -754,8 +755,17 @@ static VKAPI_ATTR VkResult VKAPI_CALL CreateSwapchainKHR(
was_low_latency_requested = slci->latencyModeEnable;
}
- const auto [_, did_emplace] = context->swapchain_monitors.try_emplace(
- *pSwapchain, *context, was_low_latency_requested);
+ auto insertion = [&]() -> std::unique_ptr<SwapchainMonitor> {
+ if (!layer_context.should_expose_reflex) {
+ return std::make_unique<AntiLagSwapchainMonitor>(
+ *context, was_low_latency_requested);
+ }
+ return std::make_unique<ReflexSwapchainMonitor>(
+ *context, was_low_latency_requested);
+ }();
+ const auto did_emplace = context->swapchain_monitors
+ .try_emplace(*pSwapchain, std::move(insertion))
+ .second;
assert(did_emplace);
return VK_SUCCESS;
@@ -801,7 +811,13 @@ AntiLagUpdateAMD(VkDevice device, const VkAntiLagDataAMD* pData) {
// and made sure that at least that one completed. I think it's more robust
// to make sure they all complete.
for (auto& iter : context->swapchain_monitors) {
- iter.second.wait_until();
+
+ // All swapchains should be of type AntiLagSwapchainMonitor here.
+ const auto ptr =
+ dynamic_cast<AntiLagSwapchainMonitor*>(iter.second.get());
+ assert(ptr);
+
+ ptr->await_submissions();
}
}
@@ -813,16 +829,19 @@ VkResult LatencySleepNV(VkDevice device, VkSwapchainKHR swapchain,
// We're associating an application-provided timeline semaphore + value with
// a swapchain that says 'signal me when we should move past input'.
- auto& swapchain_monitor = [&]() -> auto& {
+ auto swapchain_monitor_ptr = [&]() -> auto {
const auto iter = context->swapchain_monitors.find(swapchain);
assert(iter != std::end(context->swapchain_monitors));
- return iter->second;
+ const auto ptr =
+ dynamic_cast<ReflexSwapchainMonitor*>(iter->second.get());
+ assert(ptr);
+ return ptr;
}();
// Tell our swapchain monitor that if they want us to proceed they should
// signal this semaphore.
- swapchain_monitor.notify_semaphore(pSleepInfo->signalSemaphore,
- pSleepInfo->value);
+ swapchain_monitor_ptr->notify_semaphore(pSleepInfo->signalSemaphore,
+ pSleepInfo->value);
return VK_SUCCESS;
}
diff --git a/src/swapchain_monitor.cc b/src/swapchain_monitor.cc
index bcf89e1..f12bafa 100644
--- a/src/swapchain_monitor.cc
+++ b/src/swapchain_monitor.cc
@@ -11,13 +11,27 @@ namespace low_latency {
SwapchainMonitor::SwapchainMonitor(const DeviceContext& device,
const bool was_low_latency_requested)
- : device(device), was_low_latency_requested(was_low_latency_requested),
- swapchain_worker(
- std::bind_front(&SwapchainMonitor::do_swapchain_monitor, this)) {}
+ : device(device), was_low_latency_requested(was_low_latency_requested) {}
SwapchainMonitor::~SwapchainMonitor() {}
-void SwapchainMonitor::WakeupSemaphore::signal(
+void SwapchainMonitor::update_params(
+ const bool was_low_latency_requested,
+ const std::chrono::milliseconds present_delay) {
+
+ this->was_low_latency_requested = was_low_latency_requested;
+ this->present_delay = present_delay;
+}
+
+ReflexSwapchainMonitor::ReflexSwapchainMonitor(
+ const DeviceContext& device, const bool was_low_latency_requested)
+ : SwapchainMonitor(device, was_low_latency_requested),
+ monitor_worker(
+ std::bind_front(&ReflexSwapchainMonitor::do_monitor, this)) {}
+
+ReflexSwapchainMonitor::~ReflexSwapchainMonitor() {}
+
+void ReflexSwapchainMonitor::WakeupSemaphore::signal(
const DeviceContext& device) const {
const auto ssi =
@@ -27,7 +41,7 @@ void SwapchainMonitor::WakeupSemaphore::signal(
THROW_NOT_VKSUCCESS(device.vtable.SignalSemaphore(device.device, &ssi));
}
-void SwapchainMonitor::do_swapchain_monitor(const std::stop_token stoken) {
+void ReflexSwapchainMonitor::do_monitor(const std::stop_token stoken) {
for (;;) {
auto lock = std::unique_lock{this->mutex};
this->cv.wait(lock, stoken,
@@ -59,18 +73,8 @@ void SwapchainMonitor::do_swapchain_monitor(const std::stop_token stoken) {
}
}
-void SwapchainMonitor::update_params(
- const bool was_low_latency_requested,
- const std::chrono::milliseconds present_delay) {
-
- const auto lock = std::scoped_lock{this->mutex};
-
- this->was_low_latency_requested = was_low_latency_requested;
- this->present_delay = present_delay;
-}
-
-void SwapchainMonitor::notify_semaphore(const VkSemaphore& timeline_semaphore,
- const std::uint64_t& value) {
+void ReflexSwapchainMonitor::notify_semaphore(
+ const VkSemaphore& timeline_semaphore, const std::uint64_t& value) {
const auto lock = std::scoped_lock{this->mutex};
@@ -90,7 +94,7 @@ void SwapchainMonitor::notify_semaphore(const VkSemaphore& timeline_semaphore,
this->cv.notify_one();
}
-void SwapchainMonitor::notify_present(
+void ReflexSwapchainMonitor::notify_present(
const QueueContext::submissions_t& submissions) {
const auto lock = std::scoped_lock{this->mutex};
@@ -114,8 +118,23 @@ void SwapchainMonitor::notify_present(
this->cv.notify_one();
}
-void SwapchainMonitor::wait_until() {
- // No reason to lock when using VK_AMD_anti_lag.
+AntiLagSwapchainMonitor::AntiLagSwapchainMonitor(
+ const DeviceContext& device, const bool was_low_latency_requested)
+ : SwapchainMonitor(device, was_low_latency_requested) {}
+
+AntiLagSwapchainMonitor::~AntiLagSwapchainMonitor() {}
+
+void AntiLagSwapchainMonitor::notify_present(
+ const QueueContext::submissions_t& submissions) {
+
+ if (!this->was_low_latency_requested) {
+ return;
+ }
+
+ this->in_flight_submissions.emplace_back(submissions);
+}
+
+void AntiLagSwapchainMonitor::await_submissions() {
if (this->in_flight_submissions.empty()) {
return;
}
diff --git a/src/swapchain_monitor.hh b/src/swapchain_monitor.hh
index be81d59..b993b83 100644
--- a/src/swapchain_monitor.hh
+++ b/src/swapchain_monitor.hh
@@ -1,7 +1,7 @@
#ifndef SWAPCHAIN_MONITOR_HH_
#define SWAPCHAIN_MONITOR_HH_
-// The purpose of this file is to provide a SwapchainMonitor class definition.
+// The purpose of this file is to provide a SwapchainMonitor
#include <vulkan/vulkan_core.h>
@@ -17,17 +17,41 @@ namespace low_latency {
class DeviceContext;
-// A swapchain monitor's job is to provide asynchronous wakeups for threads
-// which request low_latency once the previous presentation has completed.
-// It does this by signalling a semaphore a la VK_NV_low_latency2.
+// Abstract base class for swapchain completion monitoring. Both implementations
+// currently have an option to frame pace, to disable low_latency mode
+// (become a no-op), and must track in_flight_submissions to function.
class SwapchainMonitor {
- private:
+ protected:
const DeviceContext& device;
// Configurarable params for this swapchain.
std::chrono::milliseconds present_delay = std::chrono::milliseconds{0};
bool was_low_latency_requested = false;
+ std::deque<QueueContext::submissions_t> in_flight_submissions;
+
+ public:
+ SwapchainMonitor(const DeviceContext& device,
+ const bool was_low_latency_requested);
+ SwapchainMonitor(const SwapchainMonitor&) = delete;
+ SwapchainMonitor(SwapchainMonitor&&) = delete;
+ SwapchainMonitor operator=(const SwapchainMonitor&) = delete;
+ SwapchainMonitor operator=(SwapchainMonitor&&) = delete;
+ virtual ~SwapchainMonitor();
+
+ public:
+ void update_params(const bool was_low_latency_requested,
+ const std::chrono::milliseconds present_delay);
+
+ public:
+ virtual void
+ notify_present(const QueueContext::submissions_t& submissions) = 0;
+};
+
+// Provides asynchronous monitoring of submissions and signalling of some
+// timeline semaphore via a worker thread.
+class ReflexSwapchainMonitor final : public SwapchainMonitor {
+ private:
struct WakeupSemaphore {
VkSemaphore timeline_semaphore;
std::uint64_t value;
@@ -36,36 +60,42 @@ class SwapchainMonitor {
void signal(const DeviceContext& device) const;
};
std::deque<WakeupSemaphore> wakeup_semaphores;
- std::deque<QueueContext::submissions_t> in_flight_submissions;
std::mutex mutex;
std::condition_variable_any cv;
- std::jthread swapchain_worker;
+ std::jthread monitor_worker;
private:
- void do_swapchain_monitor(const std::stop_token stoken);
+ void do_monitor(const std::stop_token stoken);
public:
- SwapchainMonitor(const DeviceContext& device,
- const bool was_low_latency_requested);
- SwapchainMonitor(const SwapchainMonitor&);
- SwapchainMonitor(SwapchainMonitor&&);
- SwapchainMonitor operator=(const SwapchainMonitor&);
- SwapchainMonitor operator=(SwapchainMonitor&&);
- ~SwapchainMonitor();
+ ReflexSwapchainMonitor(const DeviceContext& device,
+ const bool was_low_latency_requested);
+ virtual ~ReflexSwapchainMonitor();
public:
- void update_params(const bool was_low_latency_requested,
- const std::chrono::milliseconds present_delay);
-
void notify_semaphore(const VkSemaphore& timeline_semaphore,
const std::uint64_t& value);
- void notify_present(const QueueContext::submissions_t& submissions);
+ public:
+ virtual void
+ notify_present(const QueueContext::submissions_t& submissions) override;
+};
+
+// Much simpler synchronous waiting with no thread requirement.
+class AntiLagSwapchainMonitor final : public SwapchainMonitor {
+ public:
+ AntiLagSwapchainMonitor(const DeviceContext& device,
+ const bool was_low_latency_requested);
+ virtual ~AntiLagSwapchainMonitor();
public:
// Synchronously wait until all in-flight submissions have completed.
- void wait_until();
+ void await_submissions();
+
+ public:
+ virtual void
+ notify_present(const QueueContext::submissions_t& submissions) override;
};
} // namespace low_latency