1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
#[cfg(feature = "vk-graph")]
use vk_graph::driver::device::Device;
use {
crate::{Frame, Resources},
anyhow::Context as _,
ash::vk::{self, Handle as _},
std::{ffi::c_void, ptr::NonNull, sync::Mutex},
};
static API_LOCK: Mutex<()> = Mutex::new(());
unsafe extern "C" {
fn nvidia_nrd_create(
instance: *mut c_void,
physical_device: *mut c_void,
device: *mut c_void,
queue_family_index: u32,
vulkan_minor_version: u8,
queued_evaluations: u8,
output: *mut *mut c_void,
) -> i32;
fn nvidia_nrd_evaluate(
context: *mut c_void,
command_buffer: *mut c_void,
frame: *const Frame,
resources: *const Resources,
) -> i32;
fn nvidia_nrd_destroy(context: *mut c_void);
}
pub struct Nrd {
context: Option<NonNull<c_void>>,
#[cfg(feature = "vk-graph")]
device: Option<Device>,
}
impl Nrd {
fn check_result(result: i32) -> anyhow::Result<()> {
match result {
0 => Ok(()),
-1 => anyhow::bail!("nrd native exception"),
-2 => anyhow::bail!("nrd invalid argument"),
-3 => anyhow::bail!("nrd out of memory"),
value => anyhow::bail!("nrd failed with result {value}"),
}
}
/// Creates a runtime retaining the graph device until this runtime is dropped.
///
/// # Errors
/// Returns an error if the API version is unsupported or native context creation fails.
///
/// # Safety
///
/// The queue family, enabled extensions, and synchronization must satisfy
/// [`Self::from_raw`]. Device ownership does not establish these requirements.
#[cfg(feature = "vk-graph")]
pub unsafe fn new(device: &Device, queue_family_index: u32) -> anyhow::Result<Self> {
let instance = &device.physical.instance;
let api_version = instance
.info
.api_version
.to_vk_api_version()
.min(device.physical.properties_v1_0.api_version);
// The caller establishes queue/extension requirements. The retained graph device
// keeps the borrowed Vulkan handles alive.
let mut nrd = unsafe {
Self::from_raw(
instance.handle(),
device.physical.handle,
device.handle(),
queue_family_index,
api_version,
)?
};
nrd.device = Some(device.clone());
Ok(nrd)
}
/// Creates a runtime borrowing raw Vulkan handles without taking ownership.
///
/// # Errors
/// Returns an error if the API version is unsupported, handles cannot be represented
/// by native pointers, or native context creation fails.
///
/// # Safety
///
/// The device must belong to the physical device and instance. All handles must remain
/// valid until [`Self::shutdown`] returns. `queue_family_index` must identify a graphics
/// and compute capable family with queue 0 created on the device; command buffers passed
/// to [`Self::evaluate`] must belong to that family. The caller must externally synchronize
/// queue access and NRD work. `api_version` must be standard Vulkan 1.3 or later,
/// enabled for the instance and supported by the device. Enable the extensions and
/// device features documented in [`Self::required_device_extensions`]. Unsupported
/// API versions are rejected before accessing the handles.
/// Complete all NRD GPU work before resolution changes or shutdown, and call
/// shutdown before destroying the device or instance. Dropping alone leaks the native context.
pub unsafe fn from_raw(
instance: vk::Instance,
physical_device: vk::PhysicalDevice,
device: vk::Device,
queue_family_index: u32,
api_version: u32,
) -> anyhow::Result<Self> {
Self::validate_api_version(api_version)?;
let _guard = API_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let mut context = std::ptr::null_mut();
let result = unsafe {
nvidia_nrd_create(
usize::try_from(instance.as_raw())? as *mut c_void,
usize::try_from(physical_device.as_raw())? as *mut c_void,
usize::try_from(device.as_raw())? as *mut c_void,
queue_family_index,
u8::try_from(vk::api_version_minor(api_version))?,
crate::QUEUED_EVALUATIONS,
&raw mut context,
)
};
Self::check_result(result).context("creating nrd context")?;
Ok(Self {
context: Some(NonNull::new(context).context("nrd returned a null context")?),
#[cfg(feature = "vk-graph")]
device: None,
})
}
/// Records RELAX SH work into an active command buffer.
///
/// Inputs must declare `SHADER_READ_ONLY_OPTIMAL`, and outputs `GENERAL`.
/// Other layouts return an error before recording work or changing runtime state.
///
/// # Errors
/// Returns an error for invalid layouts, a shut-down runtime, an unrepresentable
/// command-buffer handle, or native evaluation failure. Pre-FFI errors do not
/// consume slots; native failures after `NewFrame` can. Errors do not reliably
/// indicate whether native advancement occurred. Follow the conservative
/// retirement rule in [`crate::QUEUED_EVALUATIONS`] even when calls fail.
///
/// # Safety
///
/// All images must belong to this instance's device and remain alive until execution completes.
/// Transition them to their declared layouts and synchronize prior writes for compute shader
/// sampled reads (inputs) or storage reads/writes (outputs) before NRD executes.
/// NRD restores the declared layouts after its dispatches.
/// Before each call, retire all older work according to
/// [`crate::QUEUED_EVALUATIONS`], counting every call including errors, not
/// `frame.frame_index`. Do not infer native slot indices from this count.
/// The caller must synchronize shutdown and resolution changes with
/// all previously submitted NRD work.
/// The command buffer must be recording on the device and queue family supplied at creation.
pub unsafe fn evaluate(
&mut self,
command_buffer: vk::CommandBuffer,
frame: &Frame,
resources: &Resources,
) -> anyhow::Result<()> {
resources.validate_layouts()?;
let context = self.context.context("nrd has been shut down")?;
let _guard = API_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let result = unsafe {
nvidia_nrd_evaluate(
context.as_ptr(),
usize::try_from(command_buffer.as_raw())? as *mut c_void,
frame,
resources,
)
};
Self::check_result(result).context("evaluating nrd relax sh")
}
/// Destroys the native context after all submitted work has completed.
///
/// # Safety
///
/// The Vulkan device must be idle with respect to every NRD dispatch.
pub unsafe fn shutdown(&mut self) {
let Some(context) = self.context.take() else {
return;
};
let _guard = API_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
unsafe { nvidia_nrd_destroy(context.as_ptr()) };
}
}
impl Drop for Nrd {
fn drop(&mut self) {
if self.context.is_some() && !std::thread::panicking() {
log::error!("nrd dropped without gpu-safe shutdown; leaking the native context");
}
}
}
// Native API access is serialized by API_LOCK, including after moving between threads.
unsafe impl Send for Nrd {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn native_bridge_rejects_vulkan_1_2() {
// Creation only stores these handles; rejection must not access Vulkan.
let handle = NonNull::<c_void>::dangling().as_ptr();
let mut context = std::ptr::null_mut();
let result = unsafe {
nvidia_nrd_create(
handle,
handle,
handle,
0,
2,
crate::QUEUED_EVALUATIONS,
&raw mut context,
)
};
if !context.is_null() {
unsafe { nvidia_nrd_destroy(context) };
}
assert_eq!(result, -2);
}
#[test]
fn native_bridge_uses_public_evaluation_capacity() {
let handle = NonNull::<c_void>::dangling().as_ptr();
let mut context = std::ptr::null_mut();
assert_eq!(
unsafe { nvidia_nrd_create(handle, handle, handle, 0, 3, 0, &raw mut context) },
-2
);
assert!(context.is_null());
assert_eq!(
unsafe {
nvidia_nrd_create(
handle,
handle,
handle,
0,
3,
crate::QUEUED_EVALUATIONS,
&raw mut context,
)
},
0
);
assert!(!context.is_null());
// Creation only stores handles; no Vulkan resources exist before evaluation.
unsafe { nvidia_nrd_destroy(context) };
}
}