#![expect(
non_camel_case_types,
reason = "the XeSS bindings keep the SDK's own C type names"
)]
use std::ffi::c_void;
use std::ptr;
use concinnity_core::components::UpscaleQuality;
use concinnity_core::render::depth::{CAMERA_DEPTH, DepthMapping};
use concinnity_core::render::history_reset::UpscalerResetLatch;
use concinnity_core::render::reactive_mask::ReactiveReader;
use super::{SdkLibrary, UpscaleExtent, entry_point};
pub(crate) 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_INVERTED_DEPTH: u32 = 1 << 1;
const XESS_INIT_FLAG_RESPONSIVE_PIXEL_MASK: u32 = 1 << 3;
const XESS_INIT_FLAG_ENABLE_AUTOEXPOSURE: u32 = 1 << 8;
pub(crate) type xess_context_handle_t = *mut c_void;
#[repr(C)]
#[derive(Clone, Copy)]
pub(crate) struct xess_2d_t {
x: u32,
y: u32,
}
impl From<(u32, u32)> for xess_2d_t {
fn from((x, y): (u32, u32)) -> Self {
Self { x, y }
}
}
#[repr(C)]
pub(crate) struct XessInitHead {
output_resolution: xess_2d_t,
quality_setting: i32,
init_flags: u32,
creation_node_mask: u32,
visible_node_mask: u32,
}
impl XessInitHead {
pub(crate) fn new(extent: UpscaleExtent) -> Self {
Self {
output_resolution: extent.output.into(),
quality_setting: xess_quality(UpscaleQuality::nearest(extent.scale)),
init_flags: xess_init_flags(CAMERA_DEPTH),
creation_node_mask: 0,
visible_node_mask: 0,
}
}
pub(crate) fn init_flags(&self) -> u32 {
self.init_flags
}
}
#[repr(C)]
pub(crate) struct XessExecuteFrame {
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,
}
impl XessExecuteFrame {
fn new(jitter: [f32; 2], reset: bool, extent: UpscaleExtent) -> Self {
let origin = xess_2d_t { x: 0, y: 0 };
Self {
jitter_offset_x: jitter[0],
jitter_offset_y: jitter[1],
exposure_scale: 1.0,
reset_history: u32::from(reset),
input_width: extent.render.0,
input_height: extent.render.1,
input_color_base: origin,
input_motion_vector_base: origin,
input_depth_base: origin,
input_responsive_mask_base: origin,
reserved0: origin,
output_color_base: origin,
}
}
}
type PfnXessDestroyContext = unsafe extern "C" fn(ctx: xess_context_handle_t) -> i32;
type PfnXessSetVelocityScale =
unsafe extern "C" fn(ctx: xess_context_handle_t, x: f32, y: f32) -> i32;
type PfnXessSetMaxResponsiveMaskValue =
unsafe extern "C" fn(ctx: xess_context_handle_t, value: f32) -> i32;
fn xess_quality(quality: Option<UpscaleQuality>) -> i32 {
match quality {
None => XESS_QUALITY_SETTING_AA,
Some(UpscaleQuality::Quality) => XESS_QUALITY_SETTING_QUALITY,
Some(UpscaleQuality::Balanced) => XESS_QUALITY_SETTING_BALANCED,
Some(UpscaleQuality::Performance) => XESS_QUALITY_SETTING_PERFORMANCE,
Some(UpscaleQuality::UltraPerformance) => XESS_QUALITY_SETTING_ULTRA_PERFORMANCE,
}
}
const fn xess_init_flags(depth: DepthMapping) -> u32 {
let flags = XESS_INIT_FLAG_ENABLE_AUTOEXPOSURE | XESS_INIT_FLAG_RESPONSIVE_PIXEL_MASK;
if depth.reversed {
flags | XESS_INIT_FLAG_INVERTED_DEPTH
} else {
flags
}
}
#[derive(Clone, Copy)]
pub(crate) struct XessCommonApi {
destroy_context: PfnXessDestroyContext,
set_velocity_scale: PfnXessSetVelocityScale,
set_max_responsive_mask_value: PfnXessSetMaxResponsiveMaskValue,
}
impl XessCommonApi {
pub(crate) fn resolve(library: &impl SdkLibrary) -> Option<Self> {
unsafe {
Some(Self {
destroy_context: entry_point(library, c"xessDestroyContext")?,
set_velocity_scale: entry_point(library, c"xessSetVelocityScale")?,
set_max_responsive_mask_value: entry_point(
library,
c"xessSetMaxResponsiveMaskValue",
)?,
})
}
}
}
pub(crate) struct XessContext<L> {
handle: xess_context_handle_t,
api: XessCommonApi,
extent: UpscaleExtent,
reset: UpscalerResetLatch,
_library: L,
}
impl<L> XessContext<L> {
pub(crate) fn adopt(
library: L,
api: XessCommonApi,
handle: xess_context_handle_t,
extent: UpscaleExtent,
) -> Self {
Self {
handle,
api,
extent,
reset: UpscalerResetLatch::default(),
_library: library,
}
}
pub(crate) fn handle(&self) -> xess_context_handle_t {
self.handle
}
pub(crate) fn extent(&self) -> UpscaleExtent {
self.extent
}
pub(crate) fn frame(&self, jitter: [f32; 2]) -> XessExecuteFrame {
XessExecuteFrame::new(
jitter,
crate::upscale_reset::consume(&self.reset),
self.extent,
)
}
pub(crate) fn request_history_reset(&self) {
self.reset.request();
}
pub(crate) fn set_velocity_scale(&self, label: &str) {
let (w, h) = self.extent.render;
let rc = unsafe { (self.api.set_velocity_scale)(self.handle, w as f32, h as f32) };
if rc != XESS_RESULT_SUCCESS {
tracing::warn!("{label}: xessSetVelocityScale returned {rc} (non-fatal)");
}
}
pub(crate) fn set_responsive_mask_cap(&self, label: &str) {
let Some(cap) = ReactiveReader::Xess.cap() else {
return;
};
let rc = unsafe { (self.api.set_max_responsive_mask_value)(self.handle, cap) };
if rc != XESS_RESULT_SUCCESS {
tracing::warn!("{label}: xessSetMaxResponsiveMaskValue returned {rc} (non-fatal)");
}
}
pub(crate) fn destroy(&mut self) {
if self.handle.is_null() {
return;
}
unsafe { (self.api.destroy_context)(self.handle) };
self.handle = ptr::null_mut();
}
}
impl<L> Drop for XessContext<L> {
fn drop(&mut self) {
self.destroy();
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::mem::{offset_of, size_of};
#[test]
fn shared_xess_layouts_match_sdk_v301() {
assert_eq!(size_of::<xess_2d_t>(), 8);
assert_eq!(offset_of!(xess_2d_t, x), 0);
assert_eq!(offset_of!(xess_2d_t, y), 4);
assert_eq!(size_of::<XessInitHead>(), 24);
assert_eq!(align_of::<XessInitHead>(), 4);
assert_eq!(offset_of!(XessInitHead, output_resolution), 0);
assert_eq!(offset_of!(XessInitHead, quality_setting), 8);
assert_eq!(offset_of!(XessInitHead, init_flags), 12);
assert_eq!(offset_of!(XessInitHead, creation_node_mask), 16);
assert_eq!(offset_of!(XessInitHead, visible_node_mask), 20);
type F = XessExecuteFrame;
assert_eq!(size_of::<F>(), 72);
assert_eq!(align_of::<F>(), 4);
assert_eq!(offset_of!(F, jitter_offset_x), 0);
assert_eq!(offset_of!(F, jitter_offset_y), 4);
assert_eq!(offset_of!(F, exposure_scale), 8);
assert_eq!(offset_of!(F, reset_history), 12);
assert_eq!(offset_of!(F, input_width), 16);
assert_eq!(offset_of!(F, input_height), 20);
assert_eq!(offset_of!(F, input_color_base), 24);
assert_eq!(offset_of!(F, input_motion_vector_base), 32);
assert_eq!(offset_of!(F, input_depth_base), 40);
assert_eq!(offset_of!(F, input_responsive_mask_base), 48);
assert_eq!(offset_of!(F, reserved0), 56);
assert_eq!(offset_of!(F, output_color_base), 64);
}
#[test]
fn xess_constants_match_sdk_v301() {
assert_eq!(XESS_QUALITY_SETTING_ULTRA_PERFORMANCE, 100);
assert_eq!(XESS_QUALITY_SETTING_PERFORMANCE, 101);
assert_eq!(XESS_QUALITY_SETTING_BALANCED, 102);
assert_eq!(XESS_QUALITY_SETTING_QUALITY, 103);
assert_eq!(XESS_QUALITY_SETTING_AA, 106);
assert_eq!(XESS_INIT_FLAG_INVERTED_DEPTH, 2);
assert_eq!(XESS_INIT_FLAG_RESPONSIVE_PIXEL_MASK, 8);
assert_eq!(XESS_INIT_FLAG_ENABLE_AUTOEXPOSURE, 256);
}
#[test]
fn quality_follows_the_nearest_engine_preset() {
assert_eq!(xess_quality(None), XESS_QUALITY_SETTING_AA);
assert_eq!(
xess_quality(Some(UpscaleQuality::Quality)),
XESS_QUALITY_SETTING_QUALITY
);
assert_eq!(
xess_quality(Some(UpscaleQuality::UltraPerformance)),
XESS_QUALITY_SETTING_ULTRA_PERFORMANCE
);
let native = XessInitHead::new(UpscaleExtent::resolve((64, 64), 1.0));
assert_eq!(native.quality_setting, XESS_QUALITY_SETTING_AA);
}
#[test]
fn the_depth_flag_follows_the_depth_mapping() {
for reversed in [false, true] {
for infinite in [false, true] {
let flags = xess_init_flags(DepthMapping { reversed, infinite });
assert_eq!((flags & XESS_INIT_FLAG_INVERTED_DEPTH) != 0, reversed);
assert_ne!(flags & XESS_INIT_FLAG_ENABLE_AUTOEXPOSURE, 0);
assert_ne!(flags & XESS_INIT_FLAG_RESPONSIVE_PIXEL_MASK, 0);
}
}
assert_ne!(
xess_init_flags(CAMERA_DEPTH) & XESS_INIT_FLAG_INVERTED_DEPTH,
0
);
}
#[test]
fn a_frame_covers_the_render_extent_from_the_origin() {
let extent = UpscaleExtent::resolve((1920, 1080), 0.5);
let frame = XessExecuteFrame::new([0.25, -0.25], true, extent);
assert_eq!((frame.input_width, frame.input_height), (960, 540));
assert_eq!(frame.reset_history, 1);
assert_eq!(frame.exposure_scale, 1.0);
assert_eq!(
(frame.jitter_offset_x, frame.jitter_offset_y),
(0.25, -0.25)
);
assert_eq!(
XessExecuteFrame::new([0.0; 2], false, extent).reset_history,
0
);
}
}