Skip to main content

nvidia_dlss/
lib.rs

1//! Vulkan DLSS Ray Reconstruction bindings through NGX.
2//!
3//! Use [`Ngx::native_backend_available`] to check whether the native backend was built
4//! and [`Ngx::staged_runtime_dir`] to locate its staged runtime before creating a context.
5
6use {
7    ash::vk::{self, Handle as _},
8    std::path::PathBuf,
9};
10
11pub const DLSS_RUNTIME_FILE: &str = "libnvidia-ngx-dlssd.so.310.4.0";
12pub const SDK_VERSION: &str = "310.4.0";
13
14/// Color encoding fixed for the lifetime of an initialized evaluator.
15#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
16#[repr(u32)]
17pub enum DlssRrColor {
18    /// Linear HDR color (before tone mapping).
19    #[default]
20    Hdr = 0,
21    /// LDR color.
22    Ldr = 1,
23}
24
25/// Conventions copied at initialization; changing them requires shutdown and reinitialization.
26/// Dimensions and quality may still change between completed evaluations, recreating the feature.
27#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
28#[repr(C)]
29pub struct DlssRrConfig {
30    pub color: DlssRrColor,
31    pub motion_resolution: DlssRrMotionResolution,
32    pub depth: DlssRrDepth,
33}
34
35#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
36#[repr(u32)]
37pub enum DlssRrDepth {
38    /// Hardware depth: near = 1, far = 0.
39    #[default]
40    ReversedHardware = 0,
41    /// Hardware depth: near = 0, far = 1.
42    ForwardHardware = 1,
43    /// Linear view-space depth, not normalized hardware depth.
44    LinearViewSpace = 2,
45}
46
47#[derive(Clone, Copy, Debug, Default, PartialEq)]
48#[repr(C)]
49pub struct DlssRrFrameConstants {
50    pub camera_view_to_clip: [f32; 16],
51    pub clip_to_camera_view: [f32; 16],
52    pub clip_to_prev_clip: [f32; 16],
53    pub prev_clip_to_clip: [f32; 16],
54    pub world_to_camera_view: [f32; 16],
55    pub camera_view_to_world: [f32; 16],
56    pub jitter_offset: [f32; 2],
57    /// Per-axis multiplier converting stored motion vectors to render-pixel displacement.
58    /// Use `[1.0, 1.0]` for pixel-space vectors, or `[render_width, render_height]`
59    /// for UV-space vectors (with signs adjusted to the input convention).
60    /// NGX converts each zero axis (including negative zero) to an effective `1.0`.
61    /// Thus the derived default `[0.0, 0.0]` has effective unity scaling.
62    pub mvec_scale: [f32; 2],
63    pub camera_pos: [f32; 3],
64    pub camera_near: f32,
65    pub camera_up: [f32; 3],
66    pub camera_far: f32,
67    pub camera_right: [f32; 3],
68    pub camera_fov: f32,
69    pub camera_forward: [f32; 3],
70    pub camera_aspect_ratio: f32,
71    pub frame_index: u32,
72    pub reset: u32,
73}
74
75/// Resolution of the primary motion field, not the reflection guide.
76#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
77#[repr(u32)]
78pub enum DlssRrMotionResolution {
79    #[default]
80    Render = 0,
81    /// Output-sized, already dilated motion vectors (NGX `MVLowRes` disabled).
82    Output = 1,
83}
84
85#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
86#[repr(u32)]
87pub enum DlssRrQuality {
88    #[default]
89    Balanced = 0,
90    Quality = 1,
91    /// Native-resolution anti-aliasing; render and output dimensions must be equal.
92    Dlaa = 2,
93}
94
95/// Images for NGX evaluation. All inputs, including depth and the reflection guide,
96/// must be in `SHADER_READ_ONLY_OPTIMAL`; the output must be in `GENERAL`.
97/// Evaluation validates the declared layouts but does not record layout transitions.
98/// All inputs require `SAMPLED` usage; output requires `STORAGE`. Depth, albedos,
99/// packed normal/roughness and either reflection guide must match input color dimensions.
100/// Primary motion must match the configured render or output resolution.
101#[derive(Clone, Copy, Debug, Eq, PartialEq)]
102pub struct DlssRrResources {
103    pub input_color: VulkanImage,
104    pub output_color: VulkanImage,
105    pub depth: VulkanImage,
106    pub motion: VulkanImage,
107    pub diffuse_albedo: VulkanImage,
108    pub specular_albedo: VulkanImage,
109    pub normal_roughness: VulkanImage,
110    pub reflection_guide: ReflectionGuide,
111}
112
113#[cfg(any(nvidia_dlss_native, test))]
114impl DlssRrResources {
115    fn native(&self, config: DlssRrConfig) -> anyhow::Result<NativeDlssRrResources> {
116        for (name, image) in [
117            ("input_color", self.input_color),
118            ("depth", self.depth),
119            ("motion", self.motion),
120            ("diffuse_albedo", self.diffuse_albedo),
121            ("specular_albedo", self.specular_albedo),
122            ("normal_roughness", self.normal_roughness),
123            ("reflection_guide", self.reflection_guide.image()),
124        ] {
125            anyhow::ensure!(
126                image.usage().contains(vk::ImageUsageFlags::SAMPLED),
127                "ngx {name} requires vk_image_usage_sampled_bit"
128            );
129
130            let extent =
131                if name == "motion" && config.motion_resolution == DlssRrMotionResolution::Output {
132                    self.output_color.extent()
133                } else {
134                    self.input_color.extent()
135                };
136
137            anyhow::ensure!(
138                image.extent() == extent && !extent.contains(&0),
139                "ngx {name} dimensions must match the configured resolution {extent:?}"
140            );
141            anyhow::ensure!(
142                image.layout() == vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
143                "ngx {name} must be in vk_image_layout_shader_read_only_optimal"
144            );
145        }
146
147        anyhow::ensure!(
148            self.output_color
149                .usage()
150                .contains(vk::ImageUsageFlags::STORAGE),
151            "ngx output_color requires vk_image_usage_storage_bit"
152        );
153        anyhow::ensure!(
154            !self.output_color.extent().contains(&0),
155            "zero output extent"
156        );
157
158        anyhow::ensure!(
159            self.output_color.layout() == vk::ImageLayout::GENERAL,
160            "ngx output_color must be in vk_image_layout_general"
161        );
162
163        let (reflection_guide, reflection_guide_kind) = match self.reflection_guide {
164            ReflectionGuide::SpecularMotion(image) => (image, 0),
165            ReflectionGuide::SpecularHitDistance(image) => {
166                anyhow::ensure!(
167                    image.format() == vk::Format::R32_SFLOAT,
168                    "specular hit distance must use vk_format_r32_sfloat"
169                );
170
171                (image, 1)
172            }
173        };
174
175        Ok(NativeDlssRrResources {
176            input_color: self.input_color,
177            output_color: self.output_color,
178            depth: self.depth,
179            motion: self.motion,
180            diffuse_albedo: self.diffuse_albedo,
181            specular_albedo: self.specular_albedo,
182            normal_roughness: self.normal_roughness,
183            reflection_guide,
184            reflection_guide_kind,
185            reserved: 0,
186        })
187    }
188}
189
190#[derive(Clone, Debug, Eq, PartialEq)]
191pub struct Identity {
192    kind: IdentityKind,
193}
194
195impl Identity {
196    /// # Errors
197    /// Returns an error if the application ID is zero.
198    pub fn application_id(application_id: u64) -> anyhow::Result<Self> {
199        anyhow::ensure!(
200            application_id != 0,
201            "nvidia application id must not be zero"
202        );
203
204        Ok(Self {
205            kind: IdentityKind::ApplicationId(application_id),
206        })
207    }
208
209    /// # Errors
210    /// Returns an error if either string is empty or contains a null byte.
211    pub fn project(
212        project_id: impl Into<String>,
213        engine_version: impl Into<String>,
214    ) -> anyhow::Result<Self> {
215        let project_id = project_id.into();
216        let engine_version = engine_version.into();
217
218        anyhow::ensure!(
219            !project_id.is_empty(),
220            "nvidia project id must not be empty"
221        );
222        anyhow::ensure!(
223            !engine_version.is_empty(),
224            "nvidia engine version must not be empty"
225        );
226        anyhow::ensure!(
227            !project_id.as_bytes().contains(&0),
228            "nvidia project id contains a null byte"
229        );
230        anyhow::ensure!(
231            !engine_version.as_bytes().contains(&0),
232            "nvidia engine version contains a null byte"
233        );
234
235        Ok(Self {
236            kind: IdentityKind::Project {
237                project_id,
238                engine_version,
239            },
240        })
241    }
242
243    #[must_use]
244    pub fn application_id_value(&self) -> Option<u64> {
245        match self.kind {
246            IdentityKind::ApplicationId(value) => Some(value),
247            IdentityKind::Project { .. } => None,
248        }
249    }
250
251    #[must_use]
252    pub fn project_id(&self) -> Option<&str> {
253        match &self.kind {
254            IdentityKind::ApplicationId(_) => None,
255            IdentityKind::Project { project_id, .. } => Some(project_id),
256        }
257    }
258
259    #[must_use]
260    pub fn engine_version(&self) -> Option<&str> {
261        match &self.kind {
262            IdentityKind::ApplicationId(_) => None,
263            IdentityKind::Project { engine_version, .. } => Some(engine_version),
264        }
265    }
266}
267
268#[derive(Clone, Debug, Eq, PartialEq)]
269enum IdentityKind {
270    ApplicationId(u64),
271    Project {
272        project_id: String,
273        engine_version: String,
274    },
275}
276
277#[derive(Clone, Debug, Eq, PartialEq)]
278pub struct InitInfo {
279    pub identity: Identity,
280    pub application_data_path: PathBuf,
281    pub runtime_path: PathBuf,
282}
283
284impl InitInfo {
285    #[must_use]
286    pub fn new(
287        identity: Identity,
288        application_data_path: impl Into<PathBuf>,
289        runtime_path: impl Into<PathBuf>,
290    ) -> Self {
291        Self {
292            identity,
293            application_data_path: application_data_path.into(),
294            runtime_path: runtime_path.into(),
295        }
296    }
297}
298
299#[cfg(any(nvidia_dlss_native, test))]
300#[derive(Clone, Copy)]
301#[repr(C)]
302struct NativeDlssRrResources {
303    input_color: VulkanImage,
304    output_color: VulkanImage,
305    depth: VulkanImage,
306    motion: VulkanImage,
307    diffuse_albedo: VulkanImage,
308    specular_albedo: VulkanImage,
309    normal_roughness: VulkanImage,
310    reflection_guide: VulkanImage,
311    reflection_guide_kind: u32,
312    reserved: u32,
313}
314
315#[derive(Clone, Copy, Debug, Eq, PartialEq)]
316pub enum ReflectionGuide {
317    SpecularMotion(VulkanImage),
318    SpecularHitDistance(VulkanImage),
319}
320
321impl ReflectionGuide {
322    #[must_use]
323    pub fn image(self) -> VulkanImage {
324        match self {
325            Self::SpecularMotion(image) | Self::SpecularHitDistance(image) => image,
326        }
327    }
328}
329
330#[derive(Clone, Copy, Debug, Eq, PartialEq)]
331#[repr(C)]
332pub struct VulkanImage {
333    image: u64,
334    view: u64,
335    state: u32,
336    width: u32,
337    height: u32,
338    format: u32,
339    aspect_mask: u32,
340    base_mip_level: u32,
341    level_count: u32,
342    base_array_layer: u32,
343    layer_count: u32,
344    flags: u32,
345    usage: u32,
346}
347
348impl VulkanImage {
349    /// Describes a borrowed image view. `subresource_range` must match the range used
350    /// to create `view`; `extent` is its base mip's size in pixels and `format` is
351    /// the view format. This does not validate handles or transition image layouts.
352    #[allow(clippy::too_many_arguments)]
353    #[must_use]
354    pub fn from_raw(
355        image: vk::Image,
356        view: vk::ImageView,
357        layout: vk::ImageLayout,
358        extent: [u32; 2],
359        format: vk::Format,
360        subresource_range: vk::ImageSubresourceRange,
361        flags: vk::ImageCreateFlags,
362        usage: vk::ImageUsageFlags,
363    ) -> Self {
364        Self {
365            image: image.as_raw(),
366            view: view.as_raw(),
367            state: layout.as_raw().cast_unsigned(),
368            width: extent[0],
369            height: extent[1],
370            format: format.as_raw().cast_unsigned(),
371            aspect_mask: subresource_range.aspect_mask.as_raw(),
372            base_mip_level: subresource_range.base_mip_level,
373            level_count: subresource_range.level_count,
374            base_array_layer: subresource_range.base_array_layer,
375            layer_count: subresource_range.layer_count,
376            flags: flags.as_raw(),
377            usage: usage.as_raw(),
378        }
379    }
380
381    #[must_use]
382    pub fn image(self) -> vk::Image {
383        vk::Image::from_raw(self.image)
384    }
385
386    #[must_use]
387    pub fn view(self) -> vk::ImageView {
388        vk::ImageView::from_raw(self.view)
389    }
390
391    #[must_use]
392    pub fn layout(self) -> vk::ImageLayout {
393        vk::ImageLayout::from_raw(self.state.cast_signed())
394    }
395
396    #[must_use]
397    pub fn extent(self) -> [u32; 2] {
398        [self.width, self.height]
399    }
400
401    #[must_use]
402    pub fn format(self) -> vk::Format {
403        vk::Format::from_raw(self.format.cast_signed())
404    }
405
406    #[must_use]
407    pub fn usage(self) -> vk::ImageUsageFlags {
408        vk::ImageUsageFlags::from_raw(self.usage)
409    }
410
411    pub fn subresource_range(self) -> vk::ImageSubresourceRange {
412        vk::ImageSubresourceRange {
413            aspect_mask: vk::ImageAspectFlags::from_raw(self.aspect_mask),
414            base_mip_level: self.base_mip_level,
415            level_count: self.level_count,
416            base_array_layer: self.base_array_layer,
417            layer_count: self.layer_count,
418        }
419    }
420
421    /// Gets a cached graph image view and describes its actual subresource range.
422    /// The graph image must outlive GPU evaluation; `layout` must describe its
423    /// state at evaluation time. No layout transitions are recorded here.
424    ///
425    /// # Errors
426    /// Returns an error if the graph cannot create the requested view.
427    #[cfg(feature = "vk-graph")]
428    pub fn from_graph(
429        image: &vk_graph::driver::image::Image,
430        view: vk_graph::driver::image::ImageViewInfo,
431        layout: vk::ImageLayout,
432    ) -> anyhow::Result<Self> {
433        Ok(Self::from_raw(
434            image.handle,
435            image.view(view)?,
436            layout,
437            [
438                image
439                    .info
440                    .width
441                    .checked_shr(view.base_mip_level)
442                    .unwrap_or(0)
443                    .max(1),
444                image
445                    .info
446                    .height
447                    .checked_shr(view.base_mip_level)
448                    .unwrap_or(0)
449                    .max(1),
450            ],
451            view.format,
452            vk::ImageSubresourceRange {
453                aspect_mask: view.aspect_mask,
454                base_mip_level: view.base_mip_level,
455                level_count: view.mip_level_count,
456                base_array_layer: view.base_array_layer,
457                layer_count: view.array_layer_count,
458            },
459            image.info.flags,
460            image.info.usage,
461        ))
462    }
463}
464
465/// Borrowed Vulkan handles and entrypoints used to initialize NGX without a graph device.
466#[derive(Clone, Copy)]
467pub struct VulkanInitInfo {
468    pub instance: vk::Instance,
469    pub physical_device: vk::PhysicalDevice,
470    pub device: vk::Device,
471    pub get_instance_proc_addr: vk::PFN_vkGetInstanceProcAddr,
472    pub get_device_proc_addr: vk::PFN_vkGetDeviceProcAddr,
473}
474
475#[cfg(nvidia_dlss_native)]
476mod native;
477#[cfg(not(nvidia_dlss_native))]
478mod stub;
479
480#[cfg(nvidia_dlss_native)]
481pub use native::{Evaluator, Ngx};
482#[cfg(not(nvidia_dlss_native))]
483pub use stub::{Evaluator, Ngx};
484
485#[cfg(test)]
486mod tests {
487    use {
488        super::*,
489        std::mem::{align_of, offset_of, size_of},
490    };
491
492    #[test]
493    fn backend_discovery() {
494        const AVAILABLE: bool = Ngx::native_backend_available();
495        let runtime_dir: Option<&'static std::path::Path> = Ngx::staged_runtime_dir();
496        assert_eq!(AVAILABLE, cfg!(nvidia_dlss_native));
497        assert_eq!(runtime_dir.is_some(), AVAILABLE);
498
499        #[cfg(nvidia_dlss_native)]
500        assert_eq!(
501            runtime_dir,
502            Some(std::path::Path::new(env!("NVIDIA_DLSS_RUNTIME_DIR")))
503        );
504    }
505
506    #[test]
507    fn vulkan_api_signatures() {
508        let _: unsafe fn(
509            &Ngx,
510            vk::Instance,
511            vk::PhysicalDevice,
512        ) -> anyhow::Result<Vec<std::ffi::CString>> = Ngx::device_extensions_raw;
513        let _: unsafe fn(&mut Ngx, VulkanInitInfo, DlssRrConfig) -> anyhow::Result<Evaluator> =
514            Ngx::initialize_raw;
515        let _: unsafe fn(
516            &Evaluator,
517            vk::CommandBuffer,
518            &DlssRrFrameConstants,
519            &DlssRrResources,
520            DlssRrQuality,
521        ) -> anyhow::Result<()> = Evaluator::evaluate;
522        let _: unsafe fn(&Evaluator) -> anyhow::Result<()> = Evaluator::shutdown;
523        let _: unsafe fn(&mut Ngx) -> anyhow::Result<()> = Ngx::shutdown;
524
525        #[cfg(feature = "vk-graph")]
526        {
527            use vk_graph::driver::{device::Device, instance::Instance};
528
529            let _: unsafe fn(
530                &Ngx,
531                &Instance,
532                vk::PhysicalDevice,
533            ) -> anyhow::Result<Vec<std::ffi::CString>> = Ngx::device_extensions;
534            let _: unsafe fn(&mut Ngx, &Device, DlssRrConfig) -> anyhow::Result<Evaluator> =
535                Ngx::initialize;
536            let _: fn(
537                &vk_graph::driver::image::Image,
538                vk_graph::driver::image::ImageViewInfo,
539                vk::ImageLayout,
540            ) -> anyhow::Result<VulkanImage> = VulkanImage::from_graph;
541        }
542    }
543
544    #[test]
545    fn native_ffi_layouts_are_stable() {
546        assert_eq!(size_of::<DlssRrConfig>(), 12);
547        assert_eq!(align_of::<DlssRrConfig>(), 4);
548        assert_eq!(offset_of!(DlssRrConfig, motion_resolution), 4);
549        assert_eq!(offset_of!(DlssRrConfig, depth), 8);
550        assert_eq!(DlssRrColor::Hdr as u32, 0);
551        assert_eq!(DlssRrColor::Ldr as u32, 1);
552        assert_eq!(DlssRrMotionResolution::Render as u32, 0);
553        assert_eq!(DlssRrMotionResolution::Output as u32, 1);
554        assert_eq!(DlssRrDepth::ReversedHardware as u32, 0);
555        assert_eq!(DlssRrDepth::ForwardHardware as u32, 1);
556        assert_eq!(DlssRrDepth::LinearViewSpace as u32, 2);
557        assert_eq!(
558            DlssRrConfig::default(),
559            DlssRrConfig {
560                color: DlssRrColor::Hdr,
561                motion_resolution: DlssRrMotionResolution::Render,
562                depth: DlssRrDepth::ReversedHardware,
563            }
564        );
565        assert_eq!(size_of::<DlssRrQuality>(), 4);
566        assert_eq!(DlssRrQuality::default(), DlssRrQuality::Balanced);
567        assert_eq!(DlssRrQuality::Balanced as u32, 0);
568        assert_eq!(DlssRrQuality::Quality as u32, 1);
569        assert_eq!(DlssRrQuality::Dlaa as u32, 2);
570
571        assert_eq!(size_of::<VulkanImage>(), 64);
572        assert_eq!(align_of::<VulkanImage>(), 8);
573        assert_eq!(offset_of!(VulkanImage, aspect_mask), 32);
574        assert_eq!(offset_of!(VulkanImage, base_mip_level), 36);
575        assert_eq!(offset_of!(VulkanImage, level_count), 40);
576        assert_eq!(offset_of!(VulkanImage, base_array_layer), 44);
577        assert_eq!(offset_of!(VulkanImage, layer_count), 48);
578        assert_eq!(offset_of!(VulkanImage, usage), 56);
579        assert_eq!(size_of::<NativeDlssRrResources>(), 520);
580        assert_eq!(
581            offset_of!(NativeDlssRrResources, reflection_guide_kind),
582            512
583        );
584        assert_eq!(size_of::<DlssRrFrameConstants>(), 472);
585        assert_eq!(offset_of!(DlssRrFrameConstants, mvec_scale), 392);
586    }
587
588    #[test]
589    fn raw_image_preserves_view_subresources() {
590        for aspect_mask in [
591            vk::ImageAspectFlags::COLOR,
592            vk::ImageAspectFlags::DEPTH,
593            vk::ImageAspectFlags::DEPTH | vk::ImageAspectFlags::STENCIL,
594        ] {
595            for (level_count, layer_count) in [
596                (2, 3),
597                (vk::REMAINING_MIP_LEVELS, vk::REMAINING_ARRAY_LAYERS),
598            ] {
599                let image = VulkanImage::from_raw(
600                    vk::Image::from_raw(11),
601                    vk::ImageView::from_raw(12),
602                    vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
603                    [64, 32],
604                    if aspect_mask == vk::ImageAspectFlags::COLOR {
605                        vk::Format::R32_SFLOAT
606                    } else {
607                        vk::Format::D32_SFLOAT_S8_UINT
608                    },
609                    vk::ImageSubresourceRange {
610                        aspect_mask,
611                        base_mip_level: 4,
612                        level_count,
613                        base_array_layer: 5,
614                        layer_count,
615                    },
616                    vk::ImageCreateFlags::empty(),
617                    vk::ImageUsageFlags::SAMPLED,
618                );
619                let range = image.subresource_range();
620                assert_eq!(range.aspect_mask.as_raw(), aspect_mask.as_raw());
621                assert_eq!(range.base_mip_level, 4);
622                assert_eq!(range.level_count, level_count);
623                assert_eq!(range.base_array_layer, 5);
624                assert_eq!(range.layer_count, layer_count);
625                assert_eq!(image.image(), vk::Image::from_raw(11));
626                assert_eq!(image.view(), vk::ImageView::from_raw(12));
627                assert_eq!(image.extent(), [64, 32]);
628                assert_eq!(
629                    image.format().as_raw(),
630                    if aspect_mask == vk::ImageAspectFlags::COLOR {
631                        vk::Format::R32_SFLOAT
632                    } else {
633                        vk::Format::D32_SFLOAT_S8_UINT
634                    }
635                    .as_raw()
636                );
637            }
638        }
639    }
640
641    #[test]
642    fn evaluation_requires_ngx_image_layouts() {
643        let input = VulkanImage::from_raw(
644            vk::Image::from_raw(1),
645            vk::ImageView::from_raw(2),
646            vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
647            [64, 64],
648            vk::Format::R32_SFLOAT,
649            vk::ImageSubresourceRange::default()
650                .aspect_mask(vk::ImageAspectFlags::COLOR)
651                .level_count(1)
652                .layer_count(1),
653            vk::ImageCreateFlags::empty(),
654            vk::ImageUsageFlags::SAMPLED | vk::ImageUsageFlags::STORAGE,
655        );
656        for reflection_guide in [
657            ReflectionGuide::SpecularMotion(input),
658            ReflectionGuide::SpecularHitDistance(input),
659        ] {
660            let resources = DlssRrResources {
661                input_color: input,
662                output_color: VulkanImage {
663                    state: vk::ImageLayout::GENERAL.as_raw().cast_unsigned(),
664                    ..input
665                },
666                depth: input,
667                motion: input,
668                diffuse_albedo: input,
669                specular_albedo: input,
670                normal_roughness: input,
671                reflection_guide,
672            };
673            assert!(resources.native(DlssRrConfig::default()).is_ok());
674            for index in 0..8 {
675                for layout in [
676                    vk::ImageLayout::UNDEFINED,
677                    vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
678                    vk::ImageLayout::TRANSFER_DST_OPTIMAL,
679                    vk::ImageLayout::GENERAL,
680                    vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
681                ] {
682                    let mut changed = resources;
683                    let guide = match &mut changed.reflection_guide {
684                        ReflectionGuide::SpecularMotion(image)
685                        | ReflectionGuide::SpecularHitDistance(image) => image,
686                    };
687                    let images = [
688                        &mut changed.input_color,
689                        &mut changed.output_color,
690                        &mut changed.depth,
691                        &mut changed.motion,
692                        &mut changed.diffuse_albedo,
693                        &mut changed.specular_albedo,
694                        &mut changed.normal_roughness,
695                        guide,
696                    ];
697                    images[index].state = layout.as_raw().cast_unsigned();
698                    let required = if index == 1 {
699                        vk::ImageLayout::GENERAL
700                    } else {
701                        vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL
702                    };
703                    assert_eq!(
704                        changed.native(DlssRrConfig::default()).is_ok(),
705                        layout == required,
706                        "image {index}"
707                    );
708                }
709            }
710        }
711    }
712
713    #[test]
714    fn evaluation_requires_usage_and_configured_dimensions() {
715        let input = VulkanImage::from_raw(
716            vk::Image::from_raw(1),
717            vk::ImageView::from_raw(2),
718            vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
719            [64, 32],
720            vk::Format::R32_SFLOAT,
721            vk::ImageSubresourceRange::default()
722                .aspect_mask(vk::ImageAspectFlags::COLOR)
723                .level_count(1)
724                .layer_count(1),
725            vk::ImageCreateFlags::empty(),
726            vk::ImageUsageFlags::SAMPLED,
727        );
728        let output = VulkanImage {
729            width: 128,
730            height: 64,
731            state: vk::ImageLayout::GENERAL.as_raw().cast_unsigned(),
732            usage: vk::ImageUsageFlags::STORAGE.as_raw(),
733            ..input
734        };
735        for motion_resolution in [
736            DlssRrMotionResolution::Render,
737            DlssRrMotionResolution::Output,
738        ] {
739            let config = DlssRrConfig {
740                motion_resolution,
741                ..DlssRrConfig::default()
742            };
743            for reflection_guide in [
744                ReflectionGuide::SpecularMotion(input),
745                ReflectionGuide::SpecularHitDistance(input),
746            ] {
747                let resources = DlssRrResources {
748                    input_color: input,
749                    output_color: output,
750                    depth: input,
751                    motion: if motion_resolution == DlssRrMotionResolution::Render {
752                        input
753                    } else {
754                        VulkanImage {
755                            width: 128,
756                            height: 64,
757                            ..input
758                        }
759                    },
760                    diffuse_albedo: input,
761                    specular_albedo: input,
762                    normal_roughness: input,
763                    reflection_guide,
764                };
765                assert!(resources.native(config).is_ok());
766                for index in 0..8 {
767                    for invalid in 0..5 {
768                        let mut changed = resources;
769                        let guide = match &mut changed.reflection_guide {
770                            ReflectionGuide::SpecularMotion(image)
771                            | ReflectionGuide::SpecularHitDistance(image) => image,
772                        };
773                        let images = [
774                            &mut changed.input_color,
775                            &mut changed.output_color,
776                            &mut changed.depth,
777                            &mut changed.motion,
778                            &mut changed.diffuse_albedo,
779                            &mut changed.specular_albedo,
780                            &mut changed.normal_roughness,
781                            guide,
782                        ];
783                        let image = &mut *images[index];
784                        match invalid {
785                            0 => image.usage = 0,
786                            1 => {
787                                image.usage = if index == 1 {
788                                    vk::ImageUsageFlags::SAMPLED
789                                } else {
790                                    vk::ImageUsageFlags::STORAGE
791                                }
792                                .as_raw();
793                            }
794                            2 => image.width = 0,
795                            3 if index != 1 => image.width += 1,
796                            4 if index != 1 => image.height += 1,
797                            _ => continue,
798                        }
799                        assert!(
800                            changed.native(config).is_err(),
801                            "image {index}, invalid {invalid}"
802                        );
803                    }
804                }
805            }
806        }
807    }
808
809    #[test]
810    fn identity_requires_non_empty_values() {
811        assert!(Identity::application_id(0).is_err());
812        assert!(Identity::project("", "1.0").is_err());
813        assert!(Identity::project("project", "").is_err());
814        assert!(Identity::project("project", "1.0").is_ok());
815    }
816}