1use {
2 anyhow::{Result, ensure},
3 ash::vk,
4 std::{
5 path::{Path, PathBuf},
6 sync::Arc,
7 },
8};
9
10#[cfg(all(target_os = "windows", feature = "vk-graph"))]
11use {
12 ash::vk::Handle as _,
13 vk_graph::driver::{image::Image, instance::Instance},
14};
15
16#[cfg(nvidia_streamline_native)]
17mod ffi;
18#[cfg(nvidia_streamline_native)]
19mod native;
20#[cfg(not(nvidia_streamline_native))]
21mod stub;
22
23#[cfg(nvidia_streamline_native)]
24use native as backend;
25#[cfg(not(nvidia_streamline_native))]
26use stub as backend;
27
28pub const NATIVE_BACKEND_AVAILABLE: bool = cfg!(nvidia_streamline_native);
30
31#[derive(Clone, Copy, Debug, Default, PartialEq)]
32#[repr(C)]
33pub struct DlssRrFrameConstants {
34 pub camera_view_to_clip: [f32; 16],
35 pub clip_to_camera_view: [f32; 16],
36 pub clip_to_prev_clip: [f32; 16],
37 pub prev_clip_to_clip: [f32; 16],
38 pub world_to_camera_view: [f32; 16],
39 pub camera_view_to_world: [f32; 16],
40 pub jitter_offset: [f32; 2],
41 pub mvec_scale: [f32; 2],
42 pub camera_pos: [f32; 3],
43 pub camera_near: f32,
44 pub camera_up: [f32; 3],
45 pub camera_far: f32,
46 pub camera_right: [f32; 3],
47 pub camera_fov: f32,
48 pub camera_forward: [f32; 3],
49 pub camera_aspect_ratio: f32,
50 pub frame_index: u32,
51 pub reset: u32,
52}
53
54#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
55#[repr(u32)]
56pub enum DlssRrQuality {
57 #[default]
58 Balanced = 0,
59 Quality = 1,
60 Dlaa = 2,
61}
62
63#[derive(Clone, Copy, Debug)]
64#[repr(C)]
65pub struct DlssRrResources {
66 pub input_color: VulkanImage,
67 pub output_color: VulkanImage,
68 pub depth: VulkanImage,
69 pub motion: VulkanImage,
70 pub diffuse_albedo: VulkanImage,
71 pub specular_albedo: VulkanImage,
72 pub normal_roughness: VulkanImage,
73 pub reflection_guide: ReflectionGuide,
74}
75
76#[derive(Clone)]
77pub struct Evaluator {
78 context: Arc<backend::Context>,
79}
80
81impl Evaluator {
82 #[must_use]
83 pub fn is_active(&self) -> bool {
84 self.context.is_active()
85 }
86
87 #[must_use]
94 pub unsafe fn is_dlss_rr_supported(&self, physical_device: vk::PhysicalDevice) -> bool {
95 unsafe { self.context.is_dlss_rr_supported(physical_device) }
96 }
97
98 pub fn optimal_render_extent(
103 &self,
104 output_extent: [u32; 2],
105 quality: DlssRrQuality,
106 ) -> Result<[u32; 2]> {
107 self.context.optimal_render_extent(output_extent, quality)
108 }
109
110 pub unsafe fn evaluate_dlss_rr(
123 &self,
124 command_buffer: vk::CommandBuffer,
125 frame: &DlssRrFrameConstants,
126 resources: &DlssRrResources,
127 quality: DlssRrQuality,
128 ) -> Result<()> {
129 unsafe {
130 self.context
131 .evaluate_dlss_rr(command_buffer, frame, resources, quality)
132 }
133 }
134
135 pub unsafe fn shutdown(&self) -> Result<()> {
144 unsafe { self.context.shutdown() }
145 }
146}
147
148#[cfg(target_os = "windows")]
156pub struct ProxyInstanceOwner {
157 owner: Option<backend::ProxyOwner>,
158 evaluator: Option<Evaluator>,
159}
160
161#[cfg(target_os = "windows")]
162impl ProxyInstanceOwner {
163 #[must_use]
172 pub unsafe fn entry(&self) -> &ash::Entry {
173 self.owner
174 .as_ref()
175 .expect("proxy vulkan instance owner invariant")
176 .entry()
177 }
178
179 #[must_use]
183 pub fn evaluator(&self) -> Evaluator {
184 self.evaluator
185 .as_ref()
186 .expect("proxy vulkan instance owner invariant")
187 .clone()
188 }
189
190 #[must_use]
200 pub unsafe fn instance(&self) -> &ash::Instance {
201 self.owner
202 .as_ref()
203 .expect("proxy vulkan instance owner invariant")
204 .instance()
205 }
206}
207
208#[cfg(target_os = "windows")]
209impl Drop for ProxyInstanceOwner {
210 fn drop(&mut self) {
211 if std::thread::panicking() || self.evaluator.as_ref().is_some_and(Evaluator::is_active) {
212 if let Some(owner) = self.owner.take() {
213 std::mem::forget(owner);
214 }
215
216 if let Some(evaluator) = self.evaluator.take() {
217 std::mem::forget(evaluator);
218 }
219
220 return;
221 }
222
223 if let Some(mut owner) = self.owner.take() {
224 owner.destroy();
225 }
226
227 self.evaluator.take();
228 }
229}
230
231#[cfg(all(target_os = "windows", feature = "vk-graph"))]
238pub struct ProxyVulkanInstance {
239 instance: Option<Instance>,
240 owner: Option<backend::ProxyOwner>,
241 evaluator: Option<Evaluator>,
242}
243
244#[cfg(all(target_os = "windows", feature = "vk-graph"))]
245impl ProxyVulkanInstance {
246 #[must_use]
256 pub unsafe fn instance(&self) -> &Instance {
257 self.instance
258 .as_ref()
259 .expect("proxy vulkan instance owner invariant")
260 }
261
262 #[must_use]
266 pub fn evaluator(&self) -> Evaluator {
267 self.evaluator
268 .as_ref()
269 .expect("proxy vulkan instance owner invariant")
270 .clone()
271 }
272
273 #[must_use]
282 pub unsafe fn into_parts(mut self) -> (Instance, ProxyInstanceOwner) {
283 let instance = self
284 .instance
285 .take()
286 .expect("proxy vulkan instance owner invariant");
287 let owner = ProxyInstanceOwner {
288 owner: self.owner.take(),
289 evaluator: self.evaluator.take(),
290 };
291 (instance, owner)
292 }
293}
294
295#[cfg(all(target_os = "windows", feature = "vk-graph"))]
296impl Drop for ProxyVulkanInstance {
297 fn drop(&mut self) {
298 if std::thread::panicking() || self.evaluator.as_ref().is_some_and(Evaluator::is_active) {
299 if let Some(instance) = self.instance.take() {
300 std::mem::forget(instance);
301 }
302
303 if let Some(owner) = self.owner.take() {
304 std::mem::forget(owner);
305 }
306
307 if let Some(evaluator) = self.evaluator.take() {
308 std::mem::forget(evaluator);
309 }
310
311 return;
312 }
313
314 drop(self.instance.take());
315
316 if let Some(mut owner) = self.owner.take() {
317 owner.destroy();
318 }
319
320 self.evaluator.take();
321 }
322}
323
324#[derive(Clone, Copy, Debug, Eq, PartialEq)]
325#[repr(C)]
326pub struct ReflectionGuide {
327 image: VulkanImage,
328 kind: ReflectionGuideKind,
329 reserved: u32,
330}
331
332impl ReflectionGuide {
333 #[must_use]
334 pub fn specular_motion_vectors(image: VulkanImage) -> Self {
335 Self {
336 image,
337 kind: ReflectionGuideKind::SpecularMotionVectors,
338 reserved: 0,
339 }
340 }
341
342 pub fn specular_hit_distance(image: VulkanImage) -> Result<Self> {
346 ensure!(
347 image.format == vk::Format::R32_SFLOAT.as_raw().cast_unsigned(),
348 "a specular hit-distance guide must use vk_format_r32_sfloat"
349 );
350
351 Ok(Self {
352 image,
353 kind: ReflectionGuideKind::SpecularHitDistance,
354 reserved: 0,
355 })
356 }
357
358 #[must_use]
359 pub fn image(self) -> VulkanImage {
360 self.image
361 }
362
363 #[must_use]
364 pub fn kind(self) -> ReflectionGuideKind {
365 self.kind
366 }
367}
368
369#[derive(Clone, Copy, Debug, Eq, PartialEq)]
370#[repr(u32)]
371pub enum ReflectionGuideKind {
372 SpecularMotionVectors = 0,
373 SpecularHitDistance = 1,
374}
375
376pub struct Streamline {
377 evaluator: Evaluator,
378 runtime_dir: PathBuf,
379 #[cfg(target_os = "windows")]
380 interposer_path: PathBuf,
381 #[cfg(target_os = "windows")]
382 proxy_created: bool,
383}
384
385impl Streamline {
386 #[must_use]
388 pub fn staged_runtime_dir() -> Option<&'static Path> {
389 #[cfg(nvidia_streamline_native)]
390 {
391 Some(Path::new(env!("NVIDIA_STREAMLINE_RUNTIME_DIR")))
392 }
393 #[cfg(not(nvidia_streamline_native))]
394 {
395 None
396 }
397 }
398
399 fn validate_project_identity(project_id: &str, engine_version: &str) -> Result<()> {
400 ensure!(
401 !project_id.is_empty(),
402 "nvidia project id must not be empty"
403 );
404 ensure!(
405 !engine_version.is_empty(),
406 "nvidia engine version must not be empty"
407 );
408 ensure!(
409 !project_id.as_bytes().contains(&0),
410 "nvidia project id contains a null byte"
411 );
412 ensure!(
413 !engine_version.as_bytes().contains(&0),
414 "nvidia engine version contains a null byte"
415 );
416
417 Ok(())
418 }
419
420 pub fn initialize(
426 project_id: &str,
427 engine_version: &str,
428 runtime_dir: impl AsRef<Path>,
429 ) -> Result<Self> {
430 Self::validate_project_identity(project_id, engine_version)?;
431
432 let runtime_dir = runtime_dir.as_ref().to_owned();
433 let context = Arc::new(backend::Context::initialize(
434 project_id,
435 engine_version,
436 &runtime_dir,
437 )?);
438 #[cfg(target_os = "windows")]
439 let interposer_path = runtime_dir.join("sl.interposer.dll");
440
441 Ok(Self {
442 evaluator: Evaluator { context },
443 runtime_dir,
444 #[cfg(target_os = "windows")]
445 interposer_path,
446 #[cfg(target_os = "windows")]
447 proxy_created: false,
448 })
449 }
450
451 #[must_use]
452 pub fn runtime_dir(&self) -> &Path {
453 &self.runtime_dir
454 }
455
456 #[must_use]
457 pub fn evaluator(&self) -> Evaluator {
458 self.evaluator.clone()
459 }
460
461 #[must_use]
468 pub unsafe fn is_dlss_rr_supported(&self, physical_device: vk::PhysicalDevice) -> bool {
469 unsafe { self.evaluator.is_dlss_rr_supported(physical_device) }
470 }
471
472 pub fn optimal_render_extent(
476 &self,
477 output_extent: [u32; 2],
478 quality: DlssRrQuality,
479 ) -> Result<[u32; 2]> {
480 self.evaluator.optimal_render_extent(output_extent, quality)
481 }
482
483 pub unsafe fn evaluate_dlss_rr(
495 &self,
496 command_buffer: vk::CommandBuffer,
497 frame: &DlssRrFrameConstants,
498 resources: &DlssRrResources,
499 quality: DlssRrQuality,
500 ) -> Result<()> {
501 unsafe {
502 self.evaluator
503 .evaluate_dlss_rr(command_buffer, frame, resources, quality)
504 }
505 }
506
507 #[cfg(target_os = "windows")]
531 pub unsafe fn create_proxy_ash_instance(
532 &mut self,
533 create_info: &vk::InstanceCreateInfo<'_>,
534 ) -> Result<ProxyInstanceOwner> {
535 ensure!(
536 !self.proxy_created,
537 "a streamline proxy vulkan instance has already been created"
538 );
539
540 let owner = unsafe {
541 self.evaluator
542 .context
543 .create_proxy_ash_instance(&self.interposer_path, create_info)?
544 };
545
546 self.proxy_created = true;
547 Ok(ProxyInstanceOwner {
548 owner: Some(owner),
549 evaluator: Some(self.evaluator.clone()),
550 })
551 }
552
553 #[cfg(all(target_os = "windows", feature = "vk-graph"))]
577 pub unsafe fn create_proxy_vulkan_instance(
578 &mut self,
579 create_info: &vk::InstanceCreateInfo<'_>,
580 ) -> Result<ProxyVulkanInstance> {
581 ensure!(
582 !self.proxy_created,
583 "a streamline proxy vulkan instance has already been created"
584 );
585
586 let (instance, owner) = unsafe {
587 self.evaluator
588 .context
589 .create_proxy_vulkan_instance(&self.interposer_path, create_info)?
590 };
591
592 self.proxy_created = true;
593 Ok(ProxyVulkanInstance {
594 instance: Some(instance),
595 owner: Some(owner),
596 evaluator: Some(self.evaluator.clone()),
597 })
598 }
599
600 pub unsafe fn shutdown(&self) -> Result<()> {
608 unsafe { self.evaluator.shutdown() }
609 }
610}
611
612#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
613#[repr(C)]
614pub struct VulkanImage {
615 pub image: u64,
616 pub view: u64,
617 pub state: u32,
618 pub width: u32,
619 pub height: u32,
620 pub format: u32,
621 pub mip_levels: u32,
622 pub array_layers: u32,
623 pub flags: u32,
624 pub usage: u32,
625}
626
627impl VulkanImage {
628 #[cfg(all(target_os = "windows", feature = "vk-graph"))]
629 #[must_use]
630 pub fn with_layout(image: &Image, view: vk::ImageView, layout: vk::ImageLayout) -> Self {
631 Self {
632 image: image.handle.as_raw(),
633 view: view.as_raw(),
634 state: layout.as_raw().cast_unsigned(),
635 width: image.info.width,
636 height: image.info.height,
637 format: image.info.format.as_raw().cast_unsigned(),
638 mip_levels: image.info.mip_level_count,
639 array_layers: image.info.array_layer_count,
640 flags: image.info.flags.as_raw(),
641 usage: image.info.usage.as_raw(),
642 }
643 }
644}
645
646#[cfg(test)]
647mod tests {
648 use {
649 super::*,
650 std::mem::{align_of, offset_of, size_of},
651 };
652
653 #[test]
654 fn native_ffi_layouts_are_stable() {
655 assert_eq!(size_of::<DlssRrQuality>(), 4);
656 assert_eq!(DlssRrQuality::default(), DlssRrQuality::Balanced);
657 assert_eq!(DlssRrQuality::Balanced as u32, 0);
658 assert_eq!(DlssRrQuality::Quality as u32, 1);
659 assert_eq!(DlssRrQuality::Dlaa as u32, 2);
660
661 assert_eq!(size_of::<VulkanImage>(), 48);
662 assert_eq!(align_of::<VulkanImage>(), 8);
663 assert_eq!(offset_of!(VulkanImage, image), 0);
664 assert_eq!(offset_of!(VulkanImage, view), 8);
665 assert_eq!(offset_of!(VulkanImage, state), 16);
666 assert_eq!(offset_of!(VulkanImage, usage), 44);
667
668 assert_eq!(size_of::<ReflectionGuideKind>(), 4);
669 assert_eq!(size_of::<ReflectionGuide>(), 56);
670 assert_eq!(align_of::<ReflectionGuide>(), 8);
671 assert_eq!(offset_of!(ReflectionGuide, image), 0);
672 assert_eq!(offset_of!(ReflectionGuide, kind), 48);
673 assert_eq!(offset_of!(ReflectionGuide, reserved), 52);
674
675 assert_eq!(size_of::<DlssRrResources>(), 392);
676 assert_eq!(align_of::<DlssRrResources>(), 8);
677 assert_eq!(offset_of!(DlssRrResources, reflection_guide), 336);
678
679 assert_eq!(size_of::<DlssRrFrameConstants>(), 472);
680 assert_eq!(align_of::<DlssRrFrameConstants>(), 4);
681 assert_eq!(offset_of!(DlssRrFrameConstants, jitter_offset), 384);
682 assert_eq!(offset_of!(DlssRrFrameConstants, frame_index), 464);
683 assert_eq!(offset_of!(DlssRrFrameConstants, reset), 468);
684 }
685
686 #[test]
687 fn specular_hit_distance_requires_r32_sfloat() {
688 let invalid = VulkanImage {
689 format: vk::Format::R16_SFLOAT.as_raw().cast_unsigned(),
690 ..Default::default()
691 };
692 assert!(ReflectionGuide::specular_hit_distance(invalid).is_err());
693
694 let valid = VulkanImage {
695 format: vk::Format::R32_SFLOAT.as_raw().cast_unsigned(),
696 ..Default::default()
697 };
698 let guide = ReflectionGuide::specular_hit_distance(valid).unwrap();
699 assert_eq!(guide.kind(), ReflectionGuideKind::SpecularHitDistance);
700 }
701
702 #[test]
703 fn project_identity_requires_non_empty_c_strings() {
704 assert!(Streamline::validate_project_identity("", "1.0.0").is_err());
705 assert!(Streamline::validate_project_identity("project", "").is_err());
706 assert!(Streamline::validate_project_identity("project\0id", "1.0.0").is_err());
707 assert!(Streamline::validate_project_identity("project", "1\0.0").is_err());
708 Streamline::validate_project_identity("8e3b4a2d-44cc-4d62-9725-86e7412f4eb0", "1.0.0")
709 .unwrap();
710 }
711
712 #[cfg(not(nvidia_streamline_native))]
713 #[test]
714 fn unsupported_target_keeps_stub_backend() {
715 const { assert!(!NATIVE_BACKEND_AVAILABLE) };
716 assert!(Streamline::staged_runtime_dir().is_none());
717 assert!(Streamline::initialize("project", "1.0.0", ".").is_err());
718 let streamline = Streamline {
719 evaluator: Evaluator {
720 context: Arc::new(backend::Context),
721 },
722 runtime_dir: PathBuf::new(),
723 #[cfg(target_os = "windows")]
724 interposer_path: PathBuf::new(),
725 #[cfg(target_os = "windows")]
726 proxy_created: false,
727 };
728
729 unsafe {
731 assert!(!streamline.is_dlss_rr_supported(vk::PhysicalDevice::null()));
732 assert!(
733 !streamline
734 .evaluator()
735 .is_dlss_rr_supported(vk::PhysicalDevice::null())
736 );
737 }
738 }
739
740 #[cfg(all(target_os = "windows", not(nvidia_streamline_native)))]
741 #[test]
742 fn proxy_creation_failures_preserve_single_instance_guard() {
743 let mut streamline = Streamline {
744 evaluator: Evaluator {
745 context: Arc::new(backend::Context),
746 },
747 runtime_dir: PathBuf::new(),
748 interposer_path: PathBuf::new(),
749 proxy_created: false,
750 };
751 let create_info = vk::InstanceCreateInfo::default();
752
753 assert!(unsafe { streamline.create_proxy_ash_instance(&create_info) }.is_err());
755
756 assert!(!streamline.proxy_created);
757
758 #[cfg(feature = "vk-graph")]
759 {
760 assert!(unsafe { streamline.create_proxy_vulkan_instance(&create_info) }.is_err());
762
763 assert!(!streamline.proxy_created);
764 }
765
766 streamline.proxy_created = true;
767
768 let error = unsafe { streamline.create_proxy_ash_instance(&create_info) }
770 .err()
771 .unwrap();
772
773 assert_eq!(
774 error.to_string(),
775 "a streamline proxy vulkan instance has already been created"
776 );
777
778 #[cfg(feature = "vk-graph")]
779 {
780 let error = unsafe { streamline.create_proxy_vulkan_instance(&create_info) }
782 .err()
783 .unwrap();
784
785 assert_eq!(
786 error.to_string(),
787 "a streamline proxy vulkan instance has already been created"
788 );
789 }
790 }
791}