use std::cell::Cell;
use std::ffi::{CString, c_char, c_void};
use std::ptr;
use ash::vk;
use concinnity_core::gfx::jitter;
use concinnity_core::render::error::{RenderError, RenderResult};
use concinnity_core::render::reactive_mask::ReactiveReader;
use super::{
ImageViewInfo, OutputWrites, UpscaleInputs, UpscaleOutput, UpscalerGpu, VkUpscaleBackend,
copy_ext_names, open_library,
};
use crate::upscale_sdk::xess::{
XESS_RESULT_SUCCESS, XessCommonApi, XessContext, XessExecuteFrame, XessInitHead,
xess_context_handle_t,
};
use crate::upscale_sdk::{SdkLibrary, UpscaleCamera, UpscaleExtent, entry_point};
const LIBRARY: &str = if cfg!(windows) {
"libxess.dll"
} else {
"libxess.so"
};
const LABEL: &str = "XeSS (Vulkan)";
#[repr(C)]
struct xess_vk_init_params_t {
head: XessInitHead,
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: ImageViewInfo,
velocity_texture: ImageViewInfo,
depth_texture: ImageViewInfo,
exposure_scale_texture: ImageViewInfo,
responsive_pixel_mask_texture: ImageViewInfo,
output_texture: ImageViewInfo,
frame: XessExecuteFrame,
}
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;
pub(super) struct XessExtQuery {
get_instance_exts: PfnXessVKGetRequiredInstanceExtensions,
get_device_exts: PfnXessVKGetRequiredDeviceExtensions,
get_device_features: PfnXessVKGetRequiredDeviceFeatures,
_library: libloading::Library,
}
impl XessExtQuery {
pub(super) fn load() -> Option<Self> {
let library = open_library(LIBRARY)?;
unsafe {
Some(Self {
get_instance_exts: entry_point(&library, c"xessVKGetRequiredInstanceExtensions")?,
get_device_exts: entry_point(&library, c"xessVKGetRequiredDeviceExtensions")?,
get_device_features: entry_point(&library, c"xessVKGetRequiredDeviceFeatures")?,
_library: library,
})
}
}
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!("{LABEL}: 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!("{LABEL}: 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!(
"{LABEL}: xessVKGetRequiredDeviceFeatures returned {rc}; using base features"
);
return head;
}
chain
}
}
struct XessVkApi {
create_context: PfnXessVKCreateContext,
build_pipelines: PfnXessVKBuildPipelines,
init: PfnXessVKInit,
execute: PfnXessVKExecute,
common: XessCommonApi,
}
impl XessVkApi {
fn resolve(library: &impl SdkLibrary) -> Option<Self> {
unsafe {
Some(Self {
create_context: entry_point(library, c"xessVKCreateContext")?,
build_pipelines: entry_point(library, c"xessVKBuildPipelines")?,
init: entry_point(library, c"xessVKInit")?,
execute: entry_point(library, c"xessVKExecute")?,
common: XessCommonApi::resolve(library)?,
})
}
}
}
pub(super) struct XessUpscaler {
ctx: XessContext<libloading::Library>,
execute: PfnXessVKExecute,
output: UpscaleOutput,
jitter: Cell<[f32; 2]>,
}
unsafe impl Send for XessUpscaler {}
impl XessUpscaler {
pub(super) fn try_new(
gpu: UpscalerGpu<'_>,
extent: UpscaleExtent,
) -> RenderResult<Option<Self>> {
let Some(library) = open_library(LIBRARY) else {
tracing::warn!(
"{LABEL}: {LIBRARY} not found (build.rs did not bundle it; set CN_XESS_SDK or put \
the library on the search path). Trying the next backend."
);
return Ok(None);
};
let Some(api) = XessVkApi::resolve(&library) else {
tracing::warn!(
"{LABEL}: {LIBRARY} lacks a Vulkan entry point; trying the next backend"
);
return Ok(None);
};
let mut handle: xess_context_handle_t = ptr::null_mut();
let rc = unsafe {
(api.create_context)(
gpu.instance.handle(),
gpu.physical_device,
gpu.device.handle(),
&mut handle,
)
};
if rc != XESS_RESULT_SUCCESS || handle.is_null() {
tracing::warn!("{LABEL}: xessVKCreateContext returned {rc}; trying the next backend");
return Ok(None);
}
let ctx = XessContext::adopt(library, api.common, handle, extent);
let head = XessInitHead::new(extent);
let pipeline_cache = crate::vulkan::pipeline_cache::handle();
let rc =
unsafe { (api.build_pipelines)(ctx.handle(), pipeline_cache, true, head.init_flags()) };
if rc != XESS_RESULT_SUCCESS {
tracing::warn!("{LABEL}: xessVKBuildPipelines returned {rc}; trying the next backend");
return Ok(None);
}
let init_params = xess_vk_init_params_t {
head,
temp_buffer_heap: vk::DeviceMemory::null(),
buffer_heap_offset: 0,
temp_texture_heap: vk::DeviceMemory::null(),
texture_heap_offset: 0,
pipeline_cache,
};
let rc = unsafe { (api.init)(ctx.handle(), &init_params) };
if rc != XESS_RESULT_SUCCESS {
tracing::warn!("{LABEL}: xessVKInit returned {rc}; trying the next backend");
return Ok(None);
}
ctx.set_velocity_scale(LABEL);
ctx.set_responsive_mask_cap(LABEL);
let output = UpscaleOutput::create(gpu, extent.output, OutputWrites::storage())?;
tracing::info!("{LABEL}: context created: {extent}");
Ok(Some(Self {
ctx,
execute: api.execute,
output,
jitter: Cell::new([0.0, 0.0]),
}))
}
}
impl VkUpscaleBackend for XessUpscaler {
fn extent(&self) -> UpscaleExtent {
self.ctx.extent()
}
fn output(&self) -> &UpscaleOutput {
&self.output
}
fn jitter_offset(&self, frame_index: u32) -> [f32; 2] {
jitter::offset(frame_index)
}
fn jitter(&self) -> &Cell<[f32; 2]> {
&self.jitter
}
fn dispatch(
&self,
cmd: vk::CommandBuffer,
inputs: UpscaleInputs<'_>,
camera: UpscaleCamera,
) -> RenderResult<()> {
let params = xess_vk_execute_params_t {
color_texture: ImageViewInfo::of(inputs.color),
velocity_texture: ImageViewInfo::of(inputs.motion),
depth_texture: ImageViewInfo::of(inputs.depth),
exposure_scale_texture: ImageViewInfo::empty(),
responsive_pixel_mask_texture: inputs
.reactive
.map_or_else(ImageViewInfo::empty, ImageViewInfo::of),
output_texture: ImageViewInfo::of(&self.output.as_upscale_image()),
frame: self.ctx.frame(camera.jitter_offset),
};
let rc = unsafe { (self.execute)(self.ctx.handle(), cmd, ¶ms) };
if rc != XESS_RESULT_SUCCESS {
return Err(RenderError::Other(format!("xessVKExecute returned {rc}")));
}
Ok(())
}
fn request_history_reset(&self) {
self.ctx.request_history_reset();
}
fn reactive_reader(&self) -> ReactiveReader {
ReactiveReader::Xess
}
fn destroy(&mut self) {
self.ctx.destroy();
self.output.release();
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::mem::{offset_of, size_of};
#[test]
fn xess_vk_layouts_match_sdk_v301() {
type I = xess_vk_init_params_t;
assert_eq!(size_of::<I>(), 64);
assert_eq!(offset_of!(I, head), 0);
assert_eq!(offset_of!(I, temp_buffer_heap), 24);
assert_eq!(offset_of!(I, buffer_heap_offset), 32);
assert_eq!(offset_of!(I, temp_texture_heap), 40);
assert_eq!(offset_of!(I, texture_heap_offset), 48);
assert_eq!(offset_of!(I, pipeline_cache), 56);
type E = xess_vk_execute_params_t;
assert_eq!(size_of::<E>(), 360);
assert_eq!(offset_of!(E, color_texture), 0);
assert_eq!(offset_of!(E, velocity_texture), 48);
assert_eq!(offset_of!(E, depth_texture), 96);
assert_eq!(offset_of!(E, exposure_scale_texture), 144);
assert_eq!(offset_of!(E, responsive_pixel_mask_texture), 192);
assert_eq!(offset_of!(E, output_texture), 240);
assert_eq!(offset_of!(E, frame), 288);
}
}