use std::cell::Cell;
use std::ffi::{CString, c_char, c_void};
use std::ptr;
use ash::vk;
use crate::vulkan::owned::VkDevice;
use super::{UpscaleCamera, UpscaleInputs, VkUpscaleBackend};
use crate::vulkan::context::HDR_FORMAT;
use crate::vulkan::texture::{GpuImage, create_image, create_image_view, one_shot_submit};
const NVSDK_NGX_RESULT_FAIL: u32 = 0xBAD0_0000;
fn ngx_succeeded(v: u32) -> bool {
(v & 0xFFF0_0000) != NVSDK_NGX_RESULT_FAIL
}
const NVSDK_NGX_VERSION_API: i32 = 0x0000_0015; const NVSDK_NGX_ENGINE_TYPE_CUSTOM: i32 = 0;
const NVSDK_NGX_FEATURE_SUPERSAMPLING: i32 = 1;
const NVSDK_NGX_RESOURCE_VK_TYPE_VK_IMAGEVIEW: i32 = 0;
const PERF_MAX_PERF: i32 = 0;
const PERF_BALANCED: i32 = 1;
const PERF_MAX_QUALITY: i32 = 2;
const PERF_ULTRA_PERFORMANCE: i32 = 3;
const PERF_DLAA: i32 = 5;
const DLSS_FLAG_IS_HDR: i32 = 1 << 0;
const P_WIDTH: &[u8] = b"Width\0";
const P_HEIGHT: &[u8] = b"Height\0";
const P_OUT_WIDTH: &[u8] = b"OutWidth\0";
const P_OUT_HEIGHT: &[u8] = b"OutHeight\0";
const P_PERF_QUALITY: &[u8] = b"PerfQualityValue\0";
const P_CREATE_FLAGS: &[u8] = b"DLSS.Feature.Create.Flags\0";
const P_ENABLE_OUTPUT_SUBRECTS: &[u8] = b"DLSS.Enable.Output.Subrects\0";
const P_CREATION_NODE_MASK: &[u8] = b"CreationNodeMask\0";
const P_VISIBILITY_NODE_MASK: &[u8] = b"VisibilityNodeMask\0";
const P_SUPERSAMPLING_AVAILABLE: &[u8] = b"SuperSampling.Available\0";
const P_COLOR: &[u8] = b"Color\0";
const P_OUTPUT: &[u8] = b"Output\0";
const P_DEPTH: &[u8] = b"Depth\0";
const P_MOTION_VECTORS: &[u8] = b"MotionVectors\0";
const P_EXPOSURE_TEXTURE: &[u8] = b"ExposureTexture\0";
const P_JITTER_X: &[u8] = b"Jitter.Offset.X\0";
const P_JITTER_Y: &[u8] = b"Jitter.Offset.Y\0";
const P_MV_SCALE_X: &[u8] = b"MV.Scale.X\0";
const P_MV_SCALE_Y: &[u8] = b"MV.Scale.Y\0";
const P_RESET: &[u8] = b"Reset\0";
const P_SUBRECT_WIDTH: &[u8] = b"DLSS.Render.Subrect.Dimensions.Width\0";
const P_SUBRECT_HEIGHT: &[u8] = b"DLSS.Render.Subrect.Dimensions.Height\0";
const P_SHARPNESS: &[u8] = b"Sharpness\0";
const PROJECT_ID: &[u8] = b"5f2e1a64-9c3b-4d7e-8a1f-2b6c0d9e7f30\0";
const ENGINE_VERSION: &[u8] = b"1.0.0\0";
#[repr(C)]
#[derive(Clone, Copy)]
struct NVSDK_NGX_ImageViewInfo_VK {
image_view: vk::ImageView,
image: vk::Image,
subresource_range: vk::ImageSubresourceRange,
format: vk::Format,
width: u32,
height: u32,
}
#[repr(C)]
struct NVSDK_NGX_Resource_VK {
image_view_info: NVSDK_NGX_ImageViewInfo_VK,
ty: i32,
read_write: bool,
}
fn make_resource(
img_view: vk::ImageView,
image: vk::Image,
aspect: vk::ImageAspectFlags,
format: vk::Format,
width: u32,
height: u32,
read_write: bool,
) -> NVSDK_NGX_Resource_VK {
NVSDK_NGX_Resource_VK {
image_view_info: NVSDK_NGX_ImageViewInfo_VK {
image_view: img_view,
image,
subresource_range: vk::ImageSubresourceRange {
aspect_mask: aspect,
base_mip_level: 0,
level_count: 1,
base_array_layer: 0,
layer_count: 1,
},
format,
width,
height,
},
ty: NVSDK_NGX_RESOURCE_VK_TYPE_VK_IMAGEVIEW,
read_write,
}
}
#[derive(Clone, Copy)]
struct ClearedInputSpec {
width: u32,
height: u32,
format: vk::Format,
clear: vk::ClearColorValue,
}
fn create_cleared_input(
gpu: super::UpscalerGpu<'_>,
spec: ClearedInputSpec,
) -> Result<GpuImage, String> {
let super::UpscalerGpu {
alloc,
device,
command_pool,
queue,
..
} = gpu;
let ClearedInputSpec {
width,
height,
format,
clear,
} = spec;
let pooled = create_image(
alloc,
&crate::vulkan::texture::ImageSpec {
width: width.max(1),
height: height.max(1),
format,
tiling: vk::ImageTiling::OPTIMAL,
usage: vk::ImageUsageFlags::SAMPLED
| vk::ImageUsageFlags::STORAGE
| vk::ImageUsageFlags::TRANSFER_DST,
mem_props: vk::MemoryPropertyFlags::DEVICE_LOCAL,
samples: vk::SampleCountFlags::TYPE_1,
},
)?;
let image = pooled.image();
let view = create_image_view(device, image, format, vk::ImageAspectFlags::COLOR)?;
let range = vk::ImageSubresourceRange {
aspect_mask: vk::ImageAspectFlags::COLOR,
base_mip_level: 0,
level_count: 1,
base_array_layer: 0,
layer_count: 1,
};
one_shot_submit(device, command_pool, queue, |cmd| {
super::image_barrier(
device,
cmd,
image,
vk::ImageAspectFlags::COLOR,
super::LayoutTransition {
from: vk::ImageLayout::UNDEFINED,
to: vk::ImageLayout::TRANSFER_DST_OPTIMAL,
},
super::BarrierSync {
src_stage: vk::PipelineStageFlags::TOP_OF_PIPE,
src_access: vk::AccessFlags::empty(),
dst_stage: vk::PipelineStageFlags::TRANSFER,
dst_access: vk::AccessFlags::TRANSFER_WRITE,
},
);
unsafe {
device.cmd_clear_color_image(
cmd,
image,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
&clear,
std::slice::from_ref(&range),
);
}
super::image_barrier(
device,
cmd,
image,
vk::ImageAspectFlags::COLOR,
super::LayoutTransition {
from: vk::ImageLayout::TRANSFER_DST_OPTIMAL,
to: vk::ImageLayout::GENERAL,
},
super::BarrierSync {
src_stage: vk::PipelineStageFlags::TRANSFER,
src_access: vk::AccessFlags::TRANSFER_WRITE,
dst_stage: vk::PipelineStageFlags::COMPUTE_SHADER,
dst_access: vk::AccessFlags::SHADER_READ,
},
);
})?;
Ok(GpuImage::from_pooled(pooled, view))
}
unsafe extern "C" {
fn NVSDK_NGX_VULKAN_Init_with_ProjectID(
project_id: *const u8,
engine_type: i32,
engine_version: *const u8,
app_data_path: *const u16,
instance: vk::Instance,
physical_device: vk::PhysicalDevice,
device: vk::Device,
gipa: *const c_void,
gdpa: *const c_void,
feature_info: *const c_void,
sdk_version: i32,
) -> u32;
fn NVSDK_NGX_VULKAN_Shutdown1(device: vk::Device) -> u32;
fn NVSDK_NGX_VULKAN_GetCapabilityParameters(out_params: *mut *mut c_void) -> u32;
fn NVSDK_NGX_VULKAN_DestroyParameters(params: *mut c_void) -> u32;
fn NVSDK_NGX_VULKAN_CreateFeature1(
device: vk::Device,
cmd: vk::CommandBuffer,
feature_id: i32,
params: *const c_void,
out_handle: *mut *mut c_void,
) -> u32;
fn NVSDK_NGX_VULKAN_ReleaseFeature(handle: *mut c_void) -> u32;
fn NVSDK_NGX_VULKAN_EvaluateFeature_C(
cmd: vk::CommandBuffer,
handle: *const c_void,
params: *const c_void,
callback: *const c_void,
) -> u32;
fn NVSDK_NGX_VULKAN_RequiredExtensions(
out_inst_count: *mut u32,
out_inst_exts: *mut *const *const c_char,
out_dev_count: *mut u32,
out_dev_exts: *mut *const *const c_char,
) -> u32;
fn NVSDK_NGX_Parameter_SetUI(params: *mut c_void, name: *const u8, value: u32);
fn NVSDK_NGX_Parameter_SetI(params: *mut c_void, name: *const u8, value: i32);
fn NVSDK_NGX_Parameter_SetF(params: *mut c_void, name: *const u8, value: f32);
fn NVSDK_NGX_Parameter_SetVoidPointer(params: *mut c_void, name: *const u8, value: *mut c_void);
fn NVSDK_NGX_Parameter_GetUI(params: *mut c_void, name: *const u8, out: *mut u32) -> u32;
}
pub(super) fn required_extensions() -> Option<(Vec<CString>, Vec<CString>)> {
let mut inst_count: u32 = 0;
let mut inst_exts: *const *const c_char = ptr::null();
let mut dev_count: u32 = 0;
let mut dev_exts: *const *const c_char = ptr::null();
let rc = unsafe {
NVSDK_NGX_VULKAN_RequiredExtensions(
&mut inst_count,
&mut inst_exts,
&mut dev_count,
&mut dev_exts,
)
};
if !ngx_succeeded(rc) {
tracing::warn!("DLSS: NVSDK_NGX_VULKAN_RequiredExtensions returned {rc:#x}");
return None;
}
let inst = unsafe { super::copy_ext_names(inst_count, inst_exts) };
let dev = unsafe { super::copy_ext_names(dev_count, dev_exts) };
Some((inst, dev))
}
fn perf_quality_from_scale(scale: f32) -> i32 {
if scale >= 0.99 {
PERF_DLAA
} else if scale >= 0.62 {
PERF_MAX_QUALITY
} else if scale >= 0.55 {
PERF_BALANCED
} else if scale >= 0.42 {
PERF_MAX_PERF
} else {
PERF_ULTRA_PERFORMANCE
}
}
pub(in crate::vulkan) struct DlssUpscaler {
device: vk::Device,
params: *mut c_void,
handle: *mut c_void,
output: GpuImage,
output_layout: Cell<vk::ImageLayout>,
exposure: GpuImage,
render_width: u32,
render_height: u32,
output_width: u32,
output_height: u32,
upscale_scale: f32,
jitter: Cell<[f32; 2]>,
reset_pending: Cell<bool>,
}
unsafe impl Send for DlssUpscaler {}
impl DlssUpscaler {
pub(super) fn try_new(
gpu: super::UpscalerGpu<'_>,
output_width: u32,
output_height: u32,
upscale_scale: f32,
) -> Result<Option<Self>, String> {
let super::UpscalerGpu {
alloc,
instance,
device,
physical_device,
command_pool,
queue,
} = gpu;
let (render_width, render_height, scale) =
super::resolve_render_dims(output_width, output_height, upscale_scale);
let app_path: Vec<u16> = ".".encode_utf16().chain(std::iter::once(0)).collect();
let rc = unsafe {
NVSDK_NGX_VULKAN_Init_with_ProjectID(
PROJECT_ID.as_ptr(),
NVSDK_NGX_ENGINE_TYPE_CUSTOM,
ENGINE_VERSION.as_ptr(),
app_path.as_ptr(),
instance.handle(),
physical_device,
device.handle(),
ptr::null(),
ptr::null(),
ptr::null(),
NVSDK_NGX_VERSION_API,
)
};
if !ngx_succeeded(rc) {
tracing::warn!(
"DLSS (Vulkan): NVSDK_NGX_VULKAN_Init returned {rc:#x} (NGX unavailable / not \
RTX). Trying the next backend."
);
return Ok(None);
}
let mut params: *mut c_void = ptr::null_mut();
let rc = unsafe { NVSDK_NGX_VULKAN_GetCapabilityParameters(&mut params) };
if !ngx_succeeded(rc) || params.is_null() {
tracing::warn!(
"DLSS (Vulkan): GetCapabilityParameters returned {rc:#x}; trying next backend"
);
unsafe { NVSDK_NGX_VULKAN_Shutdown1(device.handle()) };
return Ok(None);
}
let mut available: u32 = 0;
let rc = unsafe {
NVSDK_NGX_Parameter_GetUI(params, P_SUPERSAMPLING_AVAILABLE.as_ptr(), &mut available)
};
if !ngx_succeeded(rc) || available == 0 {
tracing::warn!(
"DLSS (Vulkan): SuperSampling not available on this GPU; trying next backend"
);
unsafe {
NVSDK_NGX_VULKAN_DestroyParameters(params);
NVSDK_NGX_VULKAN_Shutdown1(device.handle());
}
return Ok(None);
}
unsafe {
NVSDK_NGX_Parameter_SetUI(params, P_WIDTH.as_ptr(), render_width);
NVSDK_NGX_Parameter_SetUI(params, P_HEIGHT.as_ptr(), render_height);
NVSDK_NGX_Parameter_SetUI(params, P_OUT_WIDTH.as_ptr(), output_width);
NVSDK_NGX_Parameter_SetUI(params, P_OUT_HEIGHT.as_ptr(), output_height);
NVSDK_NGX_Parameter_SetI(
params,
P_PERF_QUALITY.as_ptr(),
perf_quality_from_scale(scale),
);
NVSDK_NGX_Parameter_SetI(params, P_CREATE_FLAGS.as_ptr(), DLSS_FLAG_IS_HDR);
NVSDK_NGX_Parameter_SetI(params, P_ENABLE_OUTPUT_SUBRECTS.as_ptr(), 0);
NVSDK_NGX_Parameter_SetUI(params, P_CREATION_NODE_MASK.as_ptr(), 1);
NVSDK_NGX_Parameter_SetUI(params, P_VISIBILITY_NODE_MASK.as_ptr(), 1);
}
let mut handle: *mut c_void = ptr::null_mut();
let mut create_rc: u32 = NVSDK_NGX_RESULT_FAIL;
one_shot_submit(device, command_pool, queue, |cmd| {
create_rc = unsafe {
NVSDK_NGX_VULKAN_CreateFeature1(
device.handle(),
cmd,
NVSDK_NGX_FEATURE_SUPERSAMPLING,
params,
&mut handle,
)
};
})?;
if !ngx_succeeded(create_rc) || handle.is_null() {
tracing::warn!(
"DLSS (Vulkan): CreateFeature returned {create_rc:#x}; trying next backend"
);
unsafe {
NVSDK_NGX_VULKAN_DestroyParameters(params);
NVSDK_NGX_VULKAN_Shutdown1(device.handle());
}
return Ok(None);
}
let output = match super::create_output_image(
alloc,
device,
command_pool,
queue,
output_width,
output_height,
) {
Ok(img) => img,
Err(e) => {
unsafe {
NVSDK_NGX_VULKAN_ReleaseFeature(handle);
NVSDK_NGX_VULKAN_DestroyParameters(params);
NVSDK_NGX_VULKAN_Shutdown1(device.handle());
}
return Err(e);
}
};
let exposure = match create_cleared_input(
super::UpscalerGpu {
alloc,
instance,
device,
physical_device,
command_pool,
queue,
},
ClearedInputSpec {
width: 1,
height: 1,
format: vk::Format::R32_SFLOAT,
clear: vk::ClearColorValue {
float32: [1.0, 0.0, 0.0, 0.0],
},
},
) {
Ok(img) => img,
Err(e) => {
unsafe {
NVSDK_NGX_VULKAN_ReleaseFeature(handle);
NVSDK_NGX_VULKAN_DestroyParameters(params);
NVSDK_NGX_VULKAN_Shutdown1(device.handle());
}
drop(output);
return Err(e);
}
};
tracing::info!(
"DLSS (Vulkan): feature created: render {render_width}x{render_height} -> upscale \
{output_width}x{output_height} (scale {scale:.3})"
);
Ok(Some(DlssUpscaler {
device: device.handle(),
params,
handle,
output,
output_layout: Cell::new(vk::ImageLayout::GENERAL),
exposure,
render_width,
render_height,
output_width,
output_height,
upscale_scale: scale,
jitter: Cell::new([0.0, 0.0]),
reset_pending: Cell::new(true),
}))
}
}
impl VkUpscaleBackend for DlssUpscaler {
fn render_dims(&self) -> (u32, u32) {
(self.render_width, self.render_height)
}
fn output_dims(&self) -> (u32, u32) {
(self.output_width, self.output_height)
}
fn scale(&self) -> f32 {
self.upscale_scale
}
fn output_image(&self) -> &GpuImage {
&self.output
}
fn output_layout(&self) -> vk::ImageLayout {
self.output_layout.get()
}
fn set_output_layout(&self, layout: vk::ImageLayout) {
self.output_layout.set(layout);
}
fn set_jitter(&self, offset: [f32; 2]) {
self.jitter.set(offset);
}
fn jitter(&self) -> [f32; 2] {
self.jitter.get()
}
fn jitter_offset(&self, frame_index: u32) -> [f32; 2] {
super::halton_jitter_offset(frame_index)
}
fn dispatch(
&self,
cmd: vk::CommandBuffer,
inputs: UpscaleInputs<'_>,
camera: UpscaleCamera,
) -> Result<(), String> {
let UpscaleInputs {
color,
depth,
motion,
} = inputs;
let jitter_offset = camera.jitter_offset;
let reset = self.reset_pending.replace(false);
let mut color_res = make_resource(
color.view,
color.image,
color.aspect,
color.format,
color.width,
color.height,
false,
);
let mut depth_res = make_resource(
depth.view,
depth.image,
depth.aspect,
depth.format,
depth.width,
depth.height,
false,
);
let mut motion_res = make_resource(
motion.view,
motion.image,
motion.aspect,
motion.format,
motion.width,
motion.height,
false,
);
let mut output_res = make_resource(
self.output.view,
self.output.image,
vk::ImageAspectFlags::COLOR,
HDR_FORMAT,
self.output_width,
self.output_height,
true,
);
let mut exposure_res = make_resource(
self.exposure.view,
self.exposure.image,
vk::ImageAspectFlags::COLOR,
vk::Format::R32_SFLOAT,
1,
1,
false,
);
unsafe {
let p = self.params;
NVSDK_NGX_Parameter_SetVoidPointer(
p,
P_COLOR.as_ptr(),
&mut color_res as *mut _ as *mut c_void,
);
NVSDK_NGX_Parameter_SetVoidPointer(
p,
P_OUTPUT.as_ptr(),
&mut output_res as *mut _ as *mut c_void,
);
NVSDK_NGX_Parameter_SetVoidPointer(
p,
P_DEPTH.as_ptr(),
&mut depth_res as *mut _ as *mut c_void,
);
NVSDK_NGX_Parameter_SetVoidPointer(
p,
P_MOTION_VECTORS.as_ptr(),
&mut motion_res as *mut _ as *mut c_void,
);
NVSDK_NGX_Parameter_SetVoidPointer(
p,
P_EXPOSURE_TEXTURE.as_ptr(),
&mut exposure_res as *mut _ as *mut c_void,
);
NVSDK_NGX_Parameter_SetF(p, P_JITTER_X.as_ptr(), jitter_offset[0]);
NVSDK_NGX_Parameter_SetF(p, P_JITTER_Y.as_ptr(), jitter_offset[1]);
NVSDK_NGX_Parameter_SetF(p, P_MV_SCALE_X.as_ptr(), self.render_width as f32);
NVSDK_NGX_Parameter_SetF(p, P_MV_SCALE_Y.as_ptr(), self.render_height as f32);
NVSDK_NGX_Parameter_SetI(p, P_RESET.as_ptr(), if reset { 1 } else { 0 });
NVSDK_NGX_Parameter_SetUI(p, P_SUBRECT_WIDTH.as_ptr(), self.render_width);
NVSDK_NGX_Parameter_SetUI(p, P_SUBRECT_HEIGHT.as_ptr(), self.render_height);
NVSDK_NGX_Parameter_SetF(p, P_SHARPNESS.as_ptr(), 0.0);
}
let rc = unsafe {
NVSDK_NGX_VULKAN_EvaluateFeature_C(cmd, self.handle, self.params, ptr::null())
};
if !ngx_succeeded(rc) {
return Err(format!("NVSDK_NGX_VULKAN_EvaluateFeature returned {rc:#x}"));
}
Ok(())
}
fn destroy(&mut self, _device: &VkDevice) {
unsafe {
if !self.handle.is_null() {
NVSDK_NGX_VULKAN_ReleaseFeature(self.handle);
self.handle = ptr::null_mut();
}
if !self.params.is_null() {
NVSDK_NGX_VULKAN_DestroyParameters(self.params);
self.params = ptr::null_mut();
}
NVSDK_NGX_VULKAN_Shutdown1(self.device);
}
self.output = GpuImage::null();
self.exposure = GpuImage::null();
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::mem::{offset_of, size_of};
#[test]
fn ngx_vk_resource_layout_matches_sdk() {
assert_eq!(size_of::<NVSDK_NGX_ImageViewInfo_VK>(), 48);
assert_eq!(offset_of!(NVSDK_NGX_ImageViewInfo_VK, image_view), 0);
assert_eq!(offset_of!(NVSDK_NGX_ImageViewInfo_VK, image), 8);
assert_eq!(
offset_of!(NVSDK_NGX_ImageViewInfo_VK, subresource_range),
16
);
assert_eq!(offset_of!(NVSDK_NGX_ImageViewInfo_VK, format), 36);
assert_eq!(offset_of!(NVSDK_NGX_ImageViewInfo_VK, width), 40);
assert_eq!(offset_of!(NVSDK_NGX_ImageViewInfo_VK, height), 44);
assert_eq!(size_of::<NVSDK_NGX_Resource_VK>(), 56);
assert_eq!(offset_of!(NVSDK_NGX_Resource_VK, ty), 48);
assert_eq!(offset_of!(NVSDK_NGX_Resource_VK, read_write), 52);
}
#[test]
fn ngx_constants_match_sdk() {
assert!(ngx_succeeded(0x1)); assert!(!ngx_succeeded(0xBAD0_0005)); assert_eq!(NVSDK_NGX_VERSION_API, 0x0000_0015);
assert_eq!(NVSDK_NGX_FEATURE_SUPERSAMPLING, 1);
assert_eq!(NVSDK_NGX_RESOURCE_VK_TYPE_VK_IMAGEVIEW, 0);
assert_eq!(PERF_MAX_PERF, 0);
assert_eq!(PERF_MAX_QUALITY, 2);
assert_eq!(PERF_ULTRA_PERFORMANCE, 3);
assert_eq!(PERF_DLAA, 5);
assert_eq!(DLSS_FLAG_IS_HDR, 1);
}
#[test]
fn dlss_perf_quality_mapping_by_scale() {
assert_eq!(perf_quality_from_scale(1.0), PERF_DLAA);
assert_eq!(perf_quality_from_scale(2.0 / 3.0), PERF_MAX_QUALITY);
assert_eq!(perf_quality_from_scale(0.587), PERF_BALANCED);
assert_eq!(perf_quality_from_scale(0.5), PERF_MAX_PERF);
assert_eq!(perf_quality_from_scale(1.0 / 3.0), PERF_ULTRA_PERFORMANCE);
}
}