1#[cfg(nvidia_nrd_native)]
2mod native;
3
4#[cfg(not(nvidia_nrd_native))]
5mod stub;
6
7#[cfg(nvidia_nrd_native)]
8pub use native::Nrd;
9
10#[cfg(not(nvidia_nrd_native))]
11pub use stub::Nrd;
12
13use {ash::vk, std::ffi::CStr};
14
15pub const QUEUED_EVALUATIONS: u8 = 3;
31
32#[cfg(nvidia_nrd_native)]
33const REQUIRED_DEVICE_EXTENSIONS: &[&CStr] = &[c"VK_KHR_push_descriptor"];
34
35pub const SDK_VERSION: &str = "4.17.3";
36
37#[cfg(any(nvidia_nrd_native, test))]
38const VULKAN_1_4: u32 = vk::make_api_version(0, 1, 4, 0);
39
40#[derive(Clone, Copy, Debug)]
41#[repr(C)]
42pub struct Frame {
43 pub view_to_clip: [f32; 16],
44 pub view_to_clip_previous: [f32; 16],
45 pub world_to_view: [f32; 16],
46 pub world_to_view_previous: [f32; 16],
47 pub motion_scale: [f32; 3],
48 pub jitter: [f32; 2],
49 pub jitter_previous: [f32; 2],
50 pub denoising_range: f32,
51 pub width: u32,
52 pub height: u32,
53 pub frame_index: u32,
54 pub reset: u32,
55 pub settings: RelaxSettings,
56}
57
58#[derive(Clone, Copy, Debug)]
59#[repr(C)]
60pub struct Image {
61 pub image: u64,
62 pub format: i32,
63 pub layout: i32,
66}
67
68impl Image {
69 #[must_use]
70 pub fn new(image: vk::Image, format: vk::Format, layout: vk::ImageLayout) -> Self {
71 use vk::Handle as _;
72
73 Self {
74 image: image.as_raw(),
75 format: format.as_raw(),
76 layout: layout.as_raw(),
77 }
78 }
79}
80
81impl Nrd {
82 #[must_use]
93 pub fn required_device_extensions(api_version: u32) -> &'static [&'static CStr] {
94 #[cfg(nvidia_nrd_native)]
95 if api_version < VULKAN_1_4 {
96 return REQUIRED_DEVICE_EXTENSIONS;
97 }
98
99 #[cfg(not(nvidia_nrd_native))]
100 let _ = api_version;
101
102 &[]
103 }
104
105 #[cfg(any(nvidia_nrd_native, test))]
106 fn validate_api_version(api_version: u32) -> anyhow::Result<()> {
107 anyhow::ensure!(
108 vk::api_version_variant(api_version) == 0
109 && vk::api_version_major(api_version) == 1
110 && (3..=u32::from(u8::MAX)).contains(&vk::api_version_minor(api_version)),
111 "nrd requires standard vulkan 1.3 or later with a minor version representable by nri"
112 );
113
114 Ok(())
115 }
116}
117
118#[derive(Clone, Copy, Debug)]
119#[repr(C)]
120pub struct RelaxSettings {
121 pub diffuse_max_accumulated_frame_num: u32,
122 pub diffuse_max_fast_accumulated_frame_num: u32,
123 pub history_fix_frame_num: u32,
124 pub atrous_iteration_num: u32,
125 pub diffuse_prepass_blur_radius: f32,
126 pub min_hit_distance_weight: f32,
127 pub diffuse_phi_luminance: f32,
128 pub depth_threshold: f32,
129 pub hit_distance_reconstruction_mode: u32,
130 pub enable_anti_firefly: u32,
131}
132
133#[derive(Clone, Copy, Debug)]
134#[repr(C)]
135pub struct Resources {
136 pub motion: Image,
137 pub normal_roughness: Image,
138 pub view_z: Image,
139 pub diffuse_sh0: Image,
140 pub diffuse_sh1: Image,
141 pub specular_sh0: Image,
142 pub specular_sh1: Image,
143 pub output_diffuse_sh0: Image,
144 pub output_diffuse_sh1: Image,
145 pub output_specular_sh0: Image,
146 pub output_specular_sh1: Image,
147}
148
149#[cfg(any(nvidia_nrd_native, test))]
150impl Resources {
151 fn validate_layouts(&self) -> anyhow::Result<()> {
152 for image in [
153 self.motion,
154 self.normal_roughness,
155 self.view_z,
156 self.diffuse_sh0,
157 self.diffuse_sh1,
158 self.specular_sh0,
159 self.specular_sh1,
160 ] {
161 anyhow::ensure!(
162 image.layout == vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL.as_raw(),
163 "nrd input images must be in shader_read_only_optimal"
164 );
165 }
166
167 for image in [
168 self.output_diffuse_sh0,
169 self.output_diffuse_sh1,
170 self.output_specular_sh0,
171 self.output_specular_sh1,
172 ] {
173 anyhow::ensure!(
174 image.layout == vk::ImageLayout::GENERAL.as_raw(),
175 "nrd output images must be in general"
176 );
177 }
178
179 Ok(())
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 #[test]
188 fn rejects_unsupported_api_versions_without_ffi() {
189 for version in [
190 vk::API_VERSION_1_0,
191 vk::API_VERSION_1_1,
192 vk::API_VERSION_1_2,
193 vk::make_api_version(1, 1, 3, 0),
194 vk::make_api_version(0, 2, 0, 0),
195 vk::make_api_version(0, 1, 256, 0),
196 ] {
197 assert!(Nrd::validate_api_version(version).is_err());
198 }
199
200 for version in [vk::API_VERSION_1_3, VULKAN_1_4] {
201 assert!(Nrd::validate_api_version(version).is_ok());
202 }
203 }
204
205 #[test]
206 fn checks_every_input_and_output_layout() {
207 let input = Image::new(
208 vk::Image::null(),
209 vk::Format::R16G16B16A16_SFLOAT,
210 vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
211 );
212 let output = Image {
213 layout: vk::ImageLayout::GENERAL.as_raw(),
214 ..input
215 };
216 let valid = Resources {
217 motion: input,
218 normal_roughness: input,
219 view_z: input,
220 diffuse_sh0: input,
221 diffuse_sh1: input,
222 specular_sh0: input,
223 specular_sh1: input,
224 output_diffuse_sh0: output,
225 output_diffuse_sh1: output,
226 output_specular_sh0: output,
227 output_specular_sh1: output,
228 };
229 assert!(valid.validate_layouts().is_ok());
230
231 for slot in 0..11 {
232 for layout in [
233 vk::ImageLayout::UNDEFINED,
234 vk::ImageLayout::TRANSFER_DST_OPTIMAL,
235 if slot < 7 {
236 vk::ImageLayout::GENERAL
237 } else {
238 vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL
239 },
240 ] {
241 let mut resources = valid;
242 let images = [
243 &mut resources.motion,
244 &mut resources.normal_roughness,
245 &mut resources.view_z,
246 &mut resources.diffuse_sh0,
247 &mut resources.diffuse_sh1,
248 &mut resources.specular_sh0,
249 &mut resources.specular_sh1,
250 &mut resources.output_diffuse_sh0,
251 &mut resources.output_diffuse_sh1,
252 &mut resources.output_specular_sh0,
253 &mut resources.output_specular_sh1,
254 ];
255 images[slot].layout = layout.as_raw();
256 assert!(resources.validate_layouts().is_err(), "slot {slot}");
257 }
258 }
259 }
260
261 #[test]
262 fn image_preserves_vulkan_handles_and_metadata() {
263 use vk::Handle as _;
264
265 let image = Image::new(
266 vk::Image::from_raw(0x1234),
267 vk::Format::R16G16B16A16_SFLOAT,
268 vk::ImageLayout::GENERAL,
269 );
270 assert_eq!(image.image, 0x1234);
271 assert_eq!(image.format, vk::Format::R16G16B16A16_SFLOAT.as_raw());
272 assert_eq!(image.layout, vk::ImageLayout::GENERAL.as_raw());
273 }
274
275 #[test]
276 fn ffi_layout_matches_native_bridge() {
277 assert_eq!(size_of::<Image>(), 16);
278 assert_eq!(size_of::<Resources>(), 176);
279 assert_eq!(size_of::<RelaxSettings>(), 40);
280 assert_eq!(size_of::<Frame>(), 344);
281 }
282
283 #[test]
284 fn pre_vulkan_1_4_requires_push_descriptors() {
285 #[cfg(nvidia_nrd_native)]
286 assert_eq!(
287 Nrd::required_device_extensions(vk::API_VERSION_1_3),
288 [c"VK_KHR_push_descriptor"]
289 );
290
291 assert!(Nrd::required_device_extensions(VULKAN_1_4).is_empty());
292 }
293}