Skip to main content

nvidia_nrd/
lib.rs

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
15/// Number of evaluation resource slots retained by NRD.
16///
17/// Conservative retirement rule: number every [`Nrd::evaluate`] call on a runtime,
18/// including errors, independently of [`Frame::frame_index`]. Before call N, retire
19/// every call through N - `QUEUED_EVALUATIONS`: its GPU work must have completed,
20/// and its command buffer must never be submitted again. This also bounds work
21/// recorded but not yet submitted; execute and wait for it, or discard it, before
22/// its retirement deadline.
23///
24/// Pre-FFI errors (invalid layouts or a shut-down runtime) do not consume slots.
25/// Native failures after `NewFrame` can consume slots, and returned errors do not
26/// distinguish all failures before and after advancement. Counting all calls may
27/// retire work early, which is safe. Use call age to retire all older work, not
28/// call count modulo this capacity to infer which native slot is being reused.
29/// NRD does not wait for GPU completion when recycling slots.
30pub 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    /// Initial and restored layout. Inputs must use `SHADER_READ_ONLY_OPTIMAL`;
64    /// outputs must use `GENERAL`. Other declarations are rejected by `evaluate`.
65    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    /// Vulkan device extensions that must be enabled before creating the NRD runtime.
83    ///
84    /// NRD requires standard Vulkan 1.3 or later; this list does not imply support for
85    /// earlier API versions. Enable `synchronization2`, `dynamicRendering`,
86    /// `maintenance4`, `timelineSemaphore`, and `descriptorBindingPartiallyBound` on
87    /// the logical device. Also enable `bufferDeviceAddress` when physically supported:
88    /// NRI infers its use from physical-device support, not the enabled feature chain.
89    /// For Vulkan 1.4 or later, enable the core `pushDescriptor`, `maintenance5`, and
90    /// `maintenance6` features; the push-descriptor extension is no longer needed.
91    /// Raw handles cannot be used to verify enabled device features.
92    #[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}