#![expect(
non_camel_case_types,
reason = "inline XeSS bindings keep the SDK's own C type names"
)]
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, UpscaleImage, UpscaleInputs, VkUpscaleBackend, copy_ext_names};
use crate::vulkan::context::HDR_FORMAT;
use crate::vulkan::texture::GpuImage;
const XESS_RESULT_SUCCESS: i32 = 0;
const XESS_QUALITY_SETTING_ULTRA_PERFORMANCE: i32 = 100;
const XESS_QUALITY_SETTING_PERFORMANCE: i32 = 101;
const XESS_QUALITY_SETTING_BALANCED: i32 = 102;
const XESS_QUALITY_SETTING_QUALITY: i32 = 103;
const XESS_QUALITY_SETTING_AA: i32 = 106;
const XESS_INIT_FLAG_ENABLE_AUTOEXPOSURE: u32 = 1 << 8;
type xess_context_handle_t = *mut c_void;
#[repr(C)]
#[derive(Clone, Copy)]
struct xess_2d_t {
x: u32,
y: u32,
}
#[repr(C)]
#[derive(Clone, Copy)]
struct xess_vk_image_view_info {
image_view: vk::ImageView,
image: vk::Image,
subresource_range: vk::ImageSubresourceRange,
format: vk::Format,
width: u32,
height: u32,
}
impl xess_vk_image_view_info {
fn empty() -> Self {
Self {
image_view: vk::ImageView::null(),
image: vk::Image::null(),
subresource_range: vk::ImageSubresourceRange {
aspect_mask: vk::ImageAspectFlags::empty(),
base_mip_level: 0,
level_count: 0,
base_array_layer: 0,
layer_count: 0,
},
format: vk::Format::UNDEFINED,
width: 0,
height: 0,
}
}
fn from_input(img: &UpscaleImage) -> Self {
Self {
image_view: img.view,
image: img.image,
subresource_range: vk::ImageSubresourceRange {
aspect_mask: img.aspect,
base_mip_level: 0,
level_count: 1,
base_array_layer: 0,
layer_count: 1,
},
format: img.format,
width: img.width,
height: img.height,
}
}
}
#[repr(C)]
struct xess_vk_init_params_t {
output_resolution: xess_2d_t,
quality_setting: i32,
init_flags: u32,
creation_node_mask: u32,
visible_node_mask: u32,
temp_buffer_heap: vk::DeviceMemory,
buffer_heap_offset: u64,
temp_texture_heap: vk::DeviceMemory,
texture_heap_offset: u64,
pipeline_cache: vk::PipelineCache,
}
#[repr(C)]
struct xess_vk_execute_params_t {
color_texture: xess_vk_image_view_info,
velocity_texture: xess_vk_image_view_info,
depth_texture: xess_vk_image_view_info,
exposure_scale_texture: xess_vk_image_view_info,
responsive_pixel_mask_texture: xess_vk_image_view_info,
output_texture: xess_vk_image_view_info,
jitter_offset_x: f32,
jitter_offset_y: f32,
exposure_scale: f32,
reset_history: u32,
input_width: u32,
input_height: u32,
input_color_base: xess_2d_t,
input_motion_vector_base: xess_2d_t,
input_depth_base: xess_2d_t,
input_responsive_mask_base: xess_2d_t,
reserved0: xess_2d_t,
output_color_base: xess_2d_t,
}
type PfnXessVKGetRequiredInstanceExtensions =
unsafe extern "C" fn(*mut u32, *mut *const *const c_char, *mut u32) -> i32;
type PfnXessVKGetRequiredDeviceExtensions = unsafe extern "C" fn(
vk::Instance,
vk::PhysicalDevice,
*mut u32,
*mut *const *const c_char,
) -> i32;
type PfnXessVKGetRequiredDeviceFeatures =
unsafe extern "C" fn(vk::Instance, vk::PhysicalDevice, *mut *mut c_void) -> i32;
type PfnXessVKCreateContext = unsafe extern "C" fn(
vk::Instance,
vk::PhysicalDevice,
vk::Device,
*mut xess_context_handle_t,
) -> i32;
type PfnXessVKBuildPipelines =
unsafe extern "C" fn(xess_context_handle_t, vk::PipelineCache, bool, u32) -> i32;
type PfnXessVKInit =
unsafe extern "C" fn(xess_context_handle_t, *const xess_vk_init_params_t) -> i32;
type PfnXessVKExecute = unsafe extern "C" fn(
xess_context_handle_t,
vk::CommandBuffer,
*const xess_vk_execute_params_t,
) -> i32;
type PfnXessDestroyContext = unsafe extern "C" fn(xess_context_handle_t) -> i32;
type PfnXessSetVelocityScale = unsafe extern "C" fn(xess_context_handle_t, f32, f32) -> i32;
fn lib_name() -> &'static str {
if cfg!(windows) {
"libxess.dll"
} else {
"libxess.so"
}
}
pub(super) struct XessExtQuery {
_lib: libloading::Library,
get_instance_exts: PfnXessVKGetRequiredInstanceExtensions,
get_device_exts: PfnXessVKGetRequiredDeviceExtensions,
get_device_features: PfnXessVKGetRequiredDeviceFeatures,
}
impl XessExtQuery {
pub(super) fn load() -> Option<Self> {
unsafe {
let lib = libloading::Library::new(lib_name()).ok()?;
let get_instance_exts = *lib
.get::<PfnXessVKGetRequiredInstanceExtensions>(
b"xessVKGetRequiredInstanceExtensions\0",
)
.ok()?;
let get_device_exts = *lib
.get::<PfnXessVKGetRequiredDeviceExtensions>(b"xessVKGetRequiredDeviceExtensions\0")
.ok()?;
let get_device_features = *lib
.get::<PfnXessVKGetRequiredDeviceFeatures>(b"xessVKGetRequiredDeviceFeatures\0")
.ok()?;
Some(XessExtQuery {
_lib: lib,
get_instance_exts,
get_device_exts,
get_device_features,
})
}
}
pub(super) fn instance_extensions(&self) -> (Vec<CString>, u32) {
let mut count: u32 = 0;
let mut exts: *const *const c_char = ptr::null();
let mut min_api: u32 = 0;
let rc = unsafe { (self.get_instance_exts)(&mut count, &mut exts, &mut min_api) };
if rc != XESS_RESULT_SUCCESS {
tracing::warn!("XeSS: xessVKGetRequiredInstanceExtensions returned {rc}");
return (Vec::new(), 0);
}
(unsafe { copy_ext_names(count, exts) }, min_api)
}
pub(super) fn device_extensions(
&self,
instance: &ash::Instance,
physical_device: vk::PhysicalDevice,
) -> Vec<CString> {
let mut count: u32 = 0;
let mut exts: *const *const c_char = ptr::null();
let rc = unsafe {
(self.get_device_exts)(instance.handle(), physical_device, &mut count, &mut exts)
};
if rc != XESS_RESULT_SUCCESS {
tracing::warn!("XeSS: xessVKGetRequiredDeviceExtensions returned {rc}");
return Vec::new();
}
unsafe { copy_ext_names(count, exts) }
}
pub(super) fn device_features(
&self,
instance: &ash::Instance,
physical_device: vk::PhysicalDevice,
head: *mut c_void,
) -> *mut c_void {
let mut chain = head;
let rc =
unsafe { (self.get_device_features)(instance.handle(), physical_device, &mut chain) };
if rc != XESS_RESULT_SUCCESS {
tracing::warn!(
"XeSS: xessVKGetRequiredDeviceFeatures returned {rc}; using base features"
);
return head;
}
chain
}
}
struct XessApi {
_lib: libloading::Library,
create_context: PfnXessVKCreateContext,
build_pipelines: PfnXessVKBuildPipelines,
init: PfnXessVKInit,
execute: PfnXessVKExecute,
destroy_context: PfnXessDestroyContext,
set_velocity_scale: PfnXessSetVelocityScale,
}
impl XessApi {
fn load() -> Option<Self> {
unsafe {
let lib = libloading::Library::new(lib_name()).ok()?;
let create_context = *lib
.get::<PfnXessVKCreateContext>(b"xessVKCreateContext\0")
.ok()?;
let build_pipelines = *lib
.get::<PfnXessVKBuildPipelines>(b"xessVKBuildPipelines\0")
.ok()?;
let init = *lib.get::<PfnXessVKInit>(b"xessVKInit\0").ok()?;
let execute = *lib.get::<PfnXessVKExecute>(b"xessVKExecute\0").ok()?;
let destroy_context = *lib
.get::<PfnXessDestroyContext>(b"xessDestroyContext\0")
.ok()?;
let set_velocity_scale = *lib
.get::<PfnXessSetVelocityScale>(b"xessSetVelocityScale\0")
.ok()?;
Some(XessApi {
_lib: lib,
create_context,
build_pipelines,
init,
execute,
destroy_context,
set_velocity_scale,
})
}
}
}
fn quality_from_scale(scale: f32) -> i32 {
if scale >= 0.99 {
XESS_QUALITY_SETTING_AA
} else if scale >= 0.62 {
XESS_QUALITY_SETTING_QUALITY
} else if scale >= 0.55 {
XESS_QUALITY_SETTING_BALANCED
} else if scale >= 0.42 {
XESS_QUALITY_SETTING_PERFORMANCE
} else {
XESS_QUALITY_SETTING_ULTRA_PERFORMANCE
}
}
pub(in crate::vulkan) struct XessUpscaler {
xess: XessApi,
ctx: xess_context_handle_t,
output: GpuImage,
output_layout: Cell<vk::ImageLayout>,
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 XessUpscaler {}
impl XessUpscaler {
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 xess = match XessApi::load() {
Some(api) => api,
None => {
tracing::warn!(
"XeSS (Vulkan): libxess.dll not found (build.rs did not bundle it; set \
XESS_SDK_ROOT or put the DLL on PATH). Trying the next backend."
);
return Ok(None);
}
};
let (render_width, render_height, scale) =
super::resolve_render_dims(output_width, output_height, upscale_scale);
let mut ctx: xess_context_handle_t = ptr::null_mut();
let rc = unsafe {
(xess.create_context)(
instance.handle(),
physical_device,
device.handle(),
&mut ctx,
)
};
if rc != XESS_RESULT_SUCCESS || ctx.is_null() {
tracing::warn!(
"XeSS (Vulkan): xessVKCreateContext returned {rc}; trying the next backend"
);
return Ok(None);
}
let init_flags = XESS_INIT_FLAG_ENABLE_AUTOEXPOSURE;
let rc = unsafe {
(xess.build_pipelines)(
ctx,
crate::vulkan::pipeline_cache::handle(),
true,
init_flags,
)
};
if rc != XESS_RESULT_SUCCESS {
tracing::warn!(
"XeSS (Vulkan): xessVKBuildPipelines returned {rc}; trying the next backend"
);
unsafe { (xess.destroy_context)(ctx) };
return Ok(None);
}
let init_params = xess_vk_init_params_t {
output_resolution: xess_2d_t {
x: output_width,
y: output_height,
},
quality_setting: quality_from_scale(scale),
init_flags,
creation_node_mask: 0,
visible_node_mask: 0,
temp_buffer_heap: vk::DeviceMemory::null(),
buffer_heap_offset: 0,
temp_texture_heap: vk::DeviceMemory::null(),
texture_heap_offset: 0,
pipeline_cache: crate::vulkan::pipeline_cache::handle(),
};
let rc = unsafe { (xess.init)(ctx, &init_params) };
if rc != XESS_RESULT_SUCCESS {
tracing::warn!("XeSS (Vulkan): xessVKInit returned {rc}; trying the next backend");
unsafe { (xess.destroy_context)(ctx) };
return Ok(None);
}
let rc =
unsafe { (xess.set_velocity_scale)(ctx, render_width as f32, render_height as f32) };
if rc != XESS_RESULT_SUCCESS {
tracing::warn!("XeSS (Vulkan): xessSetVelocityScale returned {rc} (non-fatal)");
}
let output = match super::create_output_image(
alloc,
device,
command_pool,
queue,
output_width,
output_height,
) {
Ok(img) => img,
Err(e) => {
unsafe { (xess.destroy_context)(ctx) };
return Err(e);
}
};
tracing::info!(
"XeSS (Vulkan): context created: render {render_width}x{render_height} -> upscale \
{output_width}x{output_height} (scale {scale:.3})"
);
Ok(Some(XessUpscaler {
xess,
ctx,
output,
output_layout: Cell::new(vk::ImageLayout::GENERAL),
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 XessUpscaler {
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 zero = xess_2d_t { x: 0, y: 0 };
let output_view = xess_vk_image_view_info {
image_view: self.output.view,
image: self.output.image,
subresource_range: vk::ImageSubresourceRange {
aspect_mask: vk::ImageAspectFlags::COLOR,
base_mip_level: 0,
level_count: 1,
base_array_layer: 0,
layer_count: 1,
},
format: HDR_FORMAT,
width: self.output_width,
height: self.output_height,
};
let params = xess_vk_execute_params_t {
color_texture: xess_vk_image_view_info::from_input(color),
velocity_texture: xess_vk_image_view_info::from_input(motion),
depth_texture: xess_vk_image_view_info::from_input(depth),
exposure_scale_texture: xess_vk_image_view_info::empty(),
responsive_pixel_mask_texture: xess_vk_image_view_info::empty(),
output_texture: output_view,
jitter_offset_x: jitter_offset[0],
jitter_offset_y: jitter_offset[1],
exposure_scale: 1.0,
reset_history: if reset { 1 } else { 0 },
input_width: self.render_width,
input_height: self.render_height,
input_color_base: zero,
input_motion_vector_base: zero,
input_depth_base: zero,
input_responsive_mask_base: zero,
reserved0: zero,
output_color_base: zero,
};
let rc = unsafe { (self.xess.execute)(self.ctx, cmd, ¶ms) };
if rc != XESS_RESULT_SUCCESS {
return Err(format!("xessVKExecute returned {rc}"));
}
Ok(())
}
fn destroy(&mut self, _device: &VkDevice) {
if !self.ctx.is_null() {
unsafe {
let _ = (self.xess.destroy_context)(self.ctx);
}
self.ctx = ptr::null_mut();
}
self.output = GpuImage::null();
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::mem::{offset_of, size_of};
#[test]
fn xess_vk_struct_sizes_match_sdk_v301() {
assert_eq!(size_of::<xess_vk_image_view_info>(), 48);
assert_eq!(offset_of!(xess_vk_image_view_info, image_view), 0);
assert_eq!(offset_of!(xess_vk_image_view_info, image), 8);
assert_eq!(offset_of!(xess_vk_image_view_info, subresource_range), 16);
assert_eq!(offset_of!(xess_vk_image_view_info, format), 36);
assert_eq!(offset_of!(xess_vk_image_view_info, width), 40);
assert_eq!(offset_of!(xess_vk_image_view_info, height), 44);
assert_eq!(size_of::<xess_2d_t>(), 8);
assert_eq!(size_of::<xess_vk_init_params_t>(), 64);
assert_eq!(offset_of!(xess_vk_init_params_t, quality_setting), 8);
assert_eq!(offset_of!(xess_vk_init_params_t, init_flags), 12);
assert_eq!(offset_of!(xess_vk_init_params_t, temp_buffer_heap), 24);
assert_eq!(offset_of!(xess_vk_init_params_t, pipeline_cache), 56);
assert_eq!(size_of::<xess_vk_execute_params_t>(), 360);
assert_eq!(offset_of!(xess_vk_execute_params_t, output_texture), 240);
assert_eq!(offset_of!(xess_vk_execute_params_t, jitter_offset_x), 288);
assert_eq!(offset_of!(xess_vk_execute_params_t, reset_history), 300);
assert_eq!(offset_of!(xess_vk_execute_params_t, input_width), 304);
assert_eq!(offset_of!(xess_vk_execute_params_t, input_color_base), 312);
assert_eq!(offset_of!(xess_vk_execute_params_t, output_color_base), 352);
}
#[test]
fn xess_quality_mapping_is_monotonic_by_scale() {
assert_eq!(quality_from_scale(1.0), XESS_QUALITY_SETTING_AA);
assert_eq!(quality_from_scale(2.0 / 3.0), XESS_QUALITY_SETTING_QUALITY);
assert_eq!(quality_from_scale(0.587), XESS_QUALITY_SETTING_BALANCED);
assert_eq!(quality_from_scale(0.5), XESS_QUALITY_SETTING_PERFORMANCE);
assert_eq!(
quality_from_scale(1.0 / 3.0),
XESS_QUALITY_SETTING_ULTRA_PERFORMANCE
);
}
}