Skip to content

Commit c894370

Browse files
committed
[dxvk] Add latency tracker based on NV_low_latency2
1 parent db8a658 commit c894370

4 files changed

+332
-0
lines changed

src/dxvk/dxvk_device.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "dxvk_device.h"
22
#include "dxvk_instance.h"
33
#include "dxvk_latency_builtin.h"
4+
#include "dxvk_latency_builtin_nv.h"
45

56
namespace dxvk {
67

@@ -311,6 +312,9 @@ namespace dxvk {
311312
if (m_options.latencySleep != Tristate::True)
312313
return nullptr;
313314

315+
if (m_features.nvLowLatency2)
316+
return new DxvkBuiltInLatencyTrackerNv(presenter);
317+
314318
return new DxvkBuiltInLatencyTracker(
315319
m_options.latencyTolerance);
316320
}

src/dxvk/dxvk_latency_builtin_nv.cpp

+213
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
#include "dxvk_latency_builtin_nv.h"
2+
3+
namespace dxvk {
4+
5+
DxvkBuiltInLatencyTrackerNv::DxvkBuiltInLatencyTrackerNv(
6+
const Rc<Presenter>& presenter)
7+
: m_presenter(presenter) {
8+
Logger::info("Latency control enabled, using VK_NV_low_latency2");
9+
auto limit = FpsLimiter::getEnvironmentOverride();
10+
11+
if (limit)
12+
m_envFpsLimit = *limit;
13+
}
14+
15+
16+
DxvkBuiltInLatencyTrackerNv::~DxvkBuiltInLatencyTrackerNv() {
17+
VkLatencySleepModeInfoNV latencyMode = { VK_STRUCTURE_TYPE_LATENCY_SLEEP_MODE_INFO_NV };
18+
latencyMode.lowLatencyMode = VK_FALSE;
19+
latencyMode.lowLatencyBoost = VK_FALSE;
20+
latencyMode.minimumIntervalUs = 0;
21+
22+
m_presenter->setLatencySleepModeNv(latencyMode);
23+
}
24+
25+
26+
void DxvkBuiltInLatencyTrackerNv::notifyCpuPresentBegin(
27+
uint64_t frameId) {
28+
// Not interesting here
29+
}
30+
31+
32+
void DxvkBuiltInLatencyTrackerNv::notifyCpuPresentEnd(
33+
uint64_t frameId) {
34+
std::unique_lock lock(m_mutex);
35+
auto frame = getFrame(frameId);
36+
37+
if (frame)
38+
frame->presentPending = VK_TRUE;
39+
}
40+
41+
42+
void DxvkBuiltInLatencyTrackerNv::notifyCsRenderBegin(
43+
uint64_t frameId) {
44+
m_presenter->setLatencyMarkerNv(frameId,
45+
VK_LATENCY_MARKER_SIMULATION_END_NV);
46+
m_presenter->setLatencyMarkerNv(frameId,
47+
VK_LATENCY_MARKER_RENDERSUBMIT_START_NV);
48+
}
49+
50+
51+
void DxvkBuiltInLatencyTrackerNv::notifyCsRenderEnd(
52+
uint64_t frameId) {
53+
m_presenter->setLatencyMarkerNv(frameId,
54+
VK_LATENCY_MARKER_RENDERSUBMIT_END_NV);
55+
}
56+
57+
58+
void DxvkBuiltInLatencyTrackerNv::notifyQueueSubmit(
59+
uint64_t frameId) {
60+
// Handled by driver
61+
}
62+
63+
64+
void DxvkBuiltInLatencyTrackerNv::notifyQueuePresentBegin(
65+
uint64_t frameId) {
66+
m_presenter->setLatencyMarkerNv(frameId,
67+
VK_LATENCY_MARKER_PRESENT_START_NV);
68+
}
69+
70+
71+
void DxvkBuiltInLatencyTrackerNv::notifyQueuePresentEnd(
72+
uint64_t frameId,
73+
VkResult status) {
74+
m_presenter->setLatencyMarkerNv(frameId,
75+
VK_LATENCY_MARKER_PRESENT_END_NV);
76+
77+
std::unique_lock lock(m_mutex);
78+
auto frame = getFrame(frameId);
79+
80+
if (frame)
81+
frame->presentResult = status;
82+
83+
m_cond.notify_one();
84+
}
85+
86+
87+
void DxvkBuiltInLatencyTrackerNv::notifyGpuExecutionBegin(
88+
uint64_t frameId) {
89+
// Handled by driver
90+
}
91+
92+
93+
void DxvkBuiltInLatencyTrackerNv::notifyGpuExecutionEnd(
94+
uint64_t frameId) {
95+
// Handled by driver
96+
}
97+
98+
99+
void DxvkBuiltInLatencyTrackerNv::notifyGpuPresentEnd(
100+
uint64_t frameId) {
101+
std::unique_lock lock(m_mutex);
102+
auto frame = getFrame(frameId);
103+
104+
if (frame)
105+
frame->frameEnd = dxvk::high_resolution_clock::now();
106+
}
107+
108+
109+
void DxvkBuiltInLatencyTrackerNv::sleepAndBeginFrame(
110+
uint64_t frameId,
111+
double maxFrameRate) {
112+
bool presentSuccessful = false;
113+
114+
duration sleepDuration(0u);
115+
116+
{ std::unique_lock lock(m_mutex);
117+
118+
// Don't try to sleep if we haven't set up
119+
// low latency mode for the swapchain yet
120+
if (m_lowLatencyEnabled) {
121+
auto curr = getFrame(frameId - 1u);
122+
123+
if (curr && curr->presentPending) {
124+
m_cond.wait(lock, [curr] {
125+
return curr->presentResult != VK_NOT_READY;
126+
});
127+
128+
presentSuccessful = curr->presentResult >= 0;
129+
}
130+
}
131+
}
132+
133+
if (presentSuccessful) {
134+
auto t0 = dxvk::high_resolution_clock::now();
135+
m_presenter->latencySleepNv(frameId - 1u);
136+
137+
sleepDuration += dxvk::high_resolution_clock::now() - t0;
138+
}
139+
140+
{ std::unique_lock lock(m_mutex);
141+
// Set up low latency mode for subsequent frames
142+
VkLatencySleepModeInfoNV latencyMode = { VK_STRUCTURE_TYPE_LATENCY_SLEEP_MODE_INFO_NV };
143+
latencyMode.lowLatencyMode = VK_TRUE;
144+
latencyMode.lowLatencyBoost = VK_TRUE;
145+
latencyMode.minimumIntervalUs = 0;
146+
147+
if (m_envFpsLimit > 0.0)
148+
maxFrameRate = m_envFpsLimit;
149+
150+
if (maxFrameRate > 0.0)
151+
latencyMode.minimumIntervalUs = uint64_t(1'000'000.0 / maxFrameRate);
152+
153+
m_presenter->setLatencySleepModeNv(latencyMode);
154+
m_presenter->setLatencyMarkerNv(frameId,
155+
VK_LATENCY_MARKER_INPUT_SAMPLE_NV);
156+
m_presenter->setLatencyMarkerNv(frameId,
157+
VK_LATENCY_MARKER_SIMULATION_START_NV);
158+
159+
auto next = initFrame(frameId);
160+
next->frameStart = dxvk::high_resolution_clock::now();
161+
next->sleepDuration = sleepDuration;
162+
163+
m_lowLatencyEnabled = true;
164+
}
165+
}
166+
167+
168+
void DxvkBuiltInLatencyTrackerNv::discardTimings() {
169+
std::unique_lock lock(m_mutex);
170+
m_lastDiscard = m_lastFrameId;
171+
}
172+
173+
174+
DxvkLatencyStats DxvkBuiltInLatencyTrackerNv::getStatistics(
175+
uint64_t frameId) {
176+
std::unique_lock lock(m_mutex);
177+
178+
auto frame = getFrame(frameId);
179+
180+
while (frame && frame->frameEnd == time_point())
181+
frame = getFrame(--frameId);
182+
183+
if (!frame)
184+
return DxvkLatencyStats();
185+
186+
DxvkLatencyStats stats = { };
187+
stats.frameLatency = std::chrono::duration_cast<std::chrono::microseconds>(frame->frameEnd - frame->frameStart);
188+
stats.sleepDuration = std::chrono::duration_cast<std::chrono::microseconds>(frame->sleepDuration);
189+
return stats;
190+
}
191+
192+
193+
DxvkLatencyFrameDataNv* DxvkBuiltInLatencyTrackerNv::initFrame(uint64_t frameId) {
194+
auto& frame = m_frames[frameId % FrameCount];
195+
196+
frame = DxvkLatencyFrameDataNv();
197+
frame.frameId = frameId;
198+
199+
m_lastFrameId = frameId;
200+
return &m_frames[frameId % FrameCount];
201+
}
202+
203+
204+
DxvkLatencyFrameDataNv* DxvkBuiltInLatencyTrackerNv::getFrame(uint64_t frameId) {
205+
auto& frame = m_frames[frameId % FrameCount];
206+
207+
if (frameId <= m_lastDiscard || frame.frameId != frameId)
208+
return nullptr;
209+
210+
return &frame;
211+
}
212+
213+
}

src/dxvk/dxvk_latency_builtin_nv.h

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
#pragma once
2+
3+
#include <array>
4+
5+
#include "dxvk_latency.h"
6+
#include "dxvk_presenter.h"
7+
8+
#include "../util/thread.h"
9+
10+
#include "../util/util_sleep.h"
11+
#include "../util/util_time.h"
12+
13+
#include "../util/config/config.h"
14+
15+
#include "../util/sync/sync_spinlock.h"
16+
17+
namespace dxvk {
18+
19+
/**
20+
* \brief Internal timers for LL2 timing
21+
*/
22+
struct DxvkLatencyFrameDataNv {
23+
using time_point = dxvk::high_resolution_clock::time_point;
24+
using duration = dxvk::high_resolution_clock::duration;
25+
26+
uint64_t frameId = 0u;
27+
time_point frameStart = time_point();
28+
time_point frameEnd = time_point();
29+
duration sleepDuration = duration(0u);
30+
VkResult presentResult = VK_NOT_READY;
31+
VkBool32 presentPending = VK_FALSE;
32+
};
33+
34+
35+
/**
36+
* \brief Built-in latency tracker based on VK_NV_low_latency2
37+
*
38+
* Implements a simple latency reduction algorithm
39+
* based on CPU timestamps received from the backend.
40+
*/
41+
class DxvkBuiltInLatencyTrackerNv : public DxvkLatencyTracker {
42+
using time_point = typename DxvkLatencyFrameDataNv::time_point;
43+
using duration = typename DxvkLatencyFrameDataNv::duration;
44+
45+
constexpr static size_t FrameCount = 8u;
46+
public:
47+
48+
DxvkBuiltInLatencyTrackerNv(
49+
const Rc<Presenter>& presenter);
50+
51+
~DxvkBuiltInLatencyTrackerNv();
52+
53+
void notifyCpuPresentBegin(
54+
uint64_t frameId);
55+
56+
void notifyCpuPresentEnd(
57+
uint64_t frameId);
58+
59+
void notifyCsRenderBegin(
60+
uint64_t frameId);
61+
62+
void notifyCsRenderEnd(
63+
uint64_t frameId);
64+
65+
void notifyQueueSubmit(
66+
uint64_t frameId);
67+
68+
void notifyQueuePresentBegin(
69+
uint64_t frameId);
70+
71+
void notifyQueuePresentEnd(
72+
uint64_t frameId,
73+
VkResult status);
74+
75+
void notifyGpuExecutionBegin(
76+
uint64_t frameId);
77+
78+
void notifyGpuExecutionEnd(
79+
uint64_t frameId);
80+
81+
void notifyGpuPresentEnd(
82+
uint64_t frameId);
83+
84+
void sleepAndBeginFrame(
85+
uint64_t frameId,
86+
double maxFrameRate);
87+
88+
void discardTimings();
89+
90+
DxvkLatencyStats getStatistics(
91+
uint64_t frameId);
92+
93+
private:
94+
95+
Rc<Presenter> m_presenter;
96+
double m_envFpsLimit = 0.0;
97+
98+
dxvk::mutex m_mutex;
99+
dxvk::condition_variable m_cond;
100+
101+
uint64_t m_lastFrameId = 0u;
102+
uint64_t m_lastDiscard = 0u;
103+
104+
bool m_lowLatencyEnabled = false;
105+
106+
std::array<DxvkLatencyFrameDataNv, FrameCount> m_frames = { };
107+
108+
DxvkLatencyFrameDataNv* initFrame(uint64_t frameId);
109+
110+
DxvkLatencyFrameDataNv* getFrame(uint64_t frameId);
111+
112+
};
113+
114+
}

src/dxvk/meson.build

+1
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ dxvk_src = [
9191
'dxvk_image.cpp',
9292
'dxvk_instance.cpp',
9393
'dxvk_latency_builtin.cpp',
94+
'dxvk_latency_builtin_nv.cpp',
9495
'dxvk_memory.cpp',
9596
'dxvk_meta_blit.cpp',
9697
'dxvk_meta_clear.cpp',

0 commit comments

Comments
 (0)