#![expect(
non_camel_case_types,
reason = "inline FFX bindings keep the SDK's own C type names"
)]
use std::ffi::{CStr, c_void};
use std::ptr;
use windows::Win32::Foundation::HMODULE;
use windows::Win32::Graphics::Direct3D12::*;
use windows::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryA};
use windows::core::{Interface, PCSTR};
type ffxContext = *mut c_void;
type ffxReturnCode_t = u32;
const FFX_API_RETURN_OK: u32 = 0;
#[repr(C)]
struct ffxApiHeader {
ty: u64,
p_next: *mut ffxApiHeader,
}
const FFX_API_CREATE_CONTEXT_DESC_TYPE_BACKEND_DX12: u64 = 0x0000002;
const FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE: u64 = 0x00010000;
const FFX_API_DISPATCH_DESC_TYPE_UPSCALE: u64 = 0x00010001;
const FFX_API_QUERY_DESC_TYPE_UPSCALE_GETJITTERPHASECOUNT: u64 = 0x00010004;
const FFX_API_QUERY_DESC_TYPE_UPSCALE_GETJITTEROFFSET: u64 = 0x00010005;
const FFX_API_CONFIGURE_DESC_TYPE_GLOBALDEBUG1: u64 = 0x0000001;
const FFX_API_CONFIGURE_GLOBALDEBUG_LEVEL_VERBOSE: u32 = 0xfffffff;
#[repr(C)]
struct ffxConfigureDescGlobalDebug1 {
header: ffxApiHeader,
fp_message: FfxApiMessage,
debug_level: u32,
}
const FFX_UPSCALE_ENABLE_HIGH_DYNAMIC_RANGE: u32 = 1 << 0;
const FFX_UPSCALE_ENABLE_DEPTH_INVERTED: u32 = 1 << 3;
const FFX_UPSCALE_ENABLE_DEPTH_INFINITE: u32 = 1 << 4;
const FFX_UPSCALE_ENABLE_AUTO_EXPOSURE: u32 = 1 << 5;
const FFX_API_RESOURCE_TYPE_TEXTURE2D: u32 = 2;
const FFX_API_RESOURCE_USAGE_READ_ONLY: u32 = 0;
const FFX_API_RESOURCE_USAGE_UAV: u32 = 1 << 1;
const FFX_API_RESOURCE_USAGE_DEPTHTARGET: u32 = 1 << 2;
const FFX_API_RESOURCE_STATE_UNORDERED_ACCESS: u32 = 1 << 1;
const FFX_API_RESOURCE_STATE_COMPUTE_READ: u32 = 1 << 2;
const FFX_API_SURFACE_FORMAT_R16G16B16A16_FLOAT: u32 = 4;
const FFX_API_SURFACE_FORMAT_R32_FLOAT: u32 = 28;
const FFX_API_SURFACE_FORMAT_R16G16_FLOAT: u32 = 18;
#[repr(C)]
#[derive(Clone, Copy)]
struct FfxApiDimensions2D {
width: u32,
height: u32,
}
#[repr(C)]
#[derive(Clone, Copy)]
struct FfxApiFloatCoords2D {
x: f32,
y: f32,
}
#[repr(C)]
struct FfxApiResourceDescription {
ty: u32, format: u32, width_or_size: u32,
height_or_stride: u32,
depth_or_alignment: u32,
mip_count: u32,
flags: u32,
usage: u32,
}
#[repr(C)]
struct FfxApiResource {
resource: *mut c_void, description: FfxApiResourceDescription,
state: u32, }
impl FfxApiResource {
fn empty() -> Self {
Self {
resource: ptr::null_mut(),
description: FfxApiResourceDescription {
ty: 0,
format: 0,
width_or_size: 0,
height_or_stride: 0,
depth_or_alignment: 0,
mip_count: 0,
flags: 0,
usage: 0,
},
state: 0,
}
}
}
#[repr(C)]
struct ffxCreateBackendDX12Desc {
header: ffxApiHeader,
device: *mut c_void, }
type FfxApiMessage = Option<extern "C" fn(ty: u32, message: *const u16)>;
extern "C" fn ffx_message_sink(ty: u32, message: *const u16) {
if message.is_null() {
return;
}
let mut len = 0usize;
let mut p = message;
unsafe {
while *p != 0 {
len += 1;
p = p.add(1);
}
}
let slice = unsafe { std::slice::from_raw_parts(message, len) };
let text = String::from_utf16_lossy(slice);
match ty {
0 => tracing::error!("FFX: {text}"),
1 => tracing::warn!("FFX: {text}"),
other => tracing::info!("FFX[{other}]: {text}"),
}
}
#[repr(C)]
struct ffxCreateContextDescUpscale {
header: ffxApiHeader,
flags: u32,
max_render_size: FfxApiDimensions2D,
max_upscale_size: FfxApiDimensions2D,
fp_message: FfxApiMessage,
}
#[repr(C)]
struct ffxDispatchDescUpscale {
header: ffxApiHeader,
command_list: *mut c_void, color: FfxApiResource,
depth: FfxApiResource,
motion_vectors: FfxApiResource,
exposure: FfxApiResource,
reactive: FfxApiResource,
transparency_and_composition: FfxApiResource,
output: FfxApiResource,
jitter_offset: FfxApiFloatCoords2D,
motion_vector_scale: FfxApiFloatCoords2D,
render_size: FfxApiDimensions2D,
upscale_size: FfxApiDimensions2D,
enable_sharpening: bool,
sharpness: f32,
frame_time_delta: f32,
pre_exposure: f32,
reset: bool,
camera_near: f32,
camera_far: f32,
camera_fov_angle_vertical: f32,
view_space_to_meters_factor: f32,
flags: u32,
}
#[repr(C)]
struct ffxQueryDescUpscaleGetJitterPhaseCount {
header: ffxApiHeader,
render_width: u32,
display_width: u32,
out_phase_count: *mut i32,
}
#[repr(C)]
struct ffxQueryDescUpscaleGetJitterOffset {
header: ffxApiHeader,
index: i32,
phase_count: i32,
out_x: *mut f32,
out_y: *mut f32,
}
#[repr(C)]
struct ffxAllocationCallbacks {
user_data: *mut c_void,
alloc: *mut c_void,
dealloc: *mut c_void,
}
type PfnFfxCreateContext = unsafe extern "C" fn(
context: *mut ffxContext,
desc: *mut ffxApiHeader,
mem_cb: *const ffxAllocationCallbacks,
) -> ffxReturnCode_t;
type PfnFfxDestroyContext = unsafe extern "C" fn(
context: *mut ffxContext,
mem_cb: *const ffxAllocationCallbacks,
) -> ffxReturnCode_t;
type PfnFfxQuery =
unsafe extern "C" fn(context: *mut ffxContext, desc: *mut ffxApiHeader) -> ffxReturnCode_t;
type PfnFfxDispatch =
unsafe extern "C" fn(context: *mut ffxContext, desc: *const ffxApiHeader) -> ffxReturnCode_t;
type PfnFfxConfigure =
unsafe extern "C" fn(context: *mut ffxContext, desc: *const ffxApiHeader) -> ffxReturnCode_t;
struct FfxApi {
#[expect(
dead_code,
reason = "held to keep the DLL loaded for the context's lifetime"
)]
module: HMODULE,
create_context: PfnFfxCreateContext,
destroy_context: PfnFfxDestroyContext,
configure: PfnFfxConfigure,
query: PfnFfxQuery,
dispatch: PfnFfxDispatch,
}
impl FfxApi {
fn load() -> Option<Self> {
let module =
unsafe { LoadLibraryA(PCSTR(c"amd_fidelityfx_dx12.dll".as_ptr() as *const u8)) }
.ok()?;
let resolve = |name: &CStr| -> Option<*const c_void> {
unsafe {
GetProcAddress(module, PCSTR(name.as_ptr() as *const u8))
.map(|p| p as *const c_void)
}
};
unsafe {
Some(FfxApi {
module,
create_context: std::mem::transmute::<*const c_void, PfnFfxCreateContext>(resolve(
c"ffxCreateContext",
)?),
destroy_context: std::mem::transmute::<*const c_void, PfnFfxDestroyContext>(
resolve(c"ffxDestroyContext")?,
),
configure: std::mem::transmute::<*const c_void, PfnFfxConfigure>(resolve(
c"ffxConfigure",
)?),
query: std::mem::transmute::<*const c_void, PfnFfxQuery>(resolve(c"ffxQuery")?),
dispatch: std::mem::transmute::<*const c_void, PfnFfxDispatch>(resolve(
c"ffxDispatch",
)?),
})
}
}
}
pub(in crate::directx) struct FsrUpscaler {
ffx: FfxApi,
ctx: ffxContext,
pub(in crate::directx) output: ID3D12Resource,
pub(in crate::directx) output_srv_gpu: D3D12_GPU_DESCRIPTOR_HANDLE,
pub(in crate::directx) output_uav_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
pub(in crate::directx) output_srv_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
pub(in crate::directx) upscale_scale: f32,
pub(in crate::directx) render_width: u32,
pub(in crate::directx) render_height: u32,
pub(in crate::directx) output_width: u32,
pub(in crate::directx) output_height: u32,
pub(in crate::directx) jitter_phase_count: i32,
pub(in crate::directx) reset_pending: std::cell::Cell<bool>,
pub(in crate::directx) output_is_psr: std::cell::Cell<bool>,
}
unsafe impl Send for FsrUpscaler {}
fn device_raw(device: &ID3D12Device) -> *mut c_void {
device.as_raw()
}
fn cmd_list_raw(cmd: &ID3D12GraphicsCommandList) -> *mut c_void {
cmd.as_raw()
}
fn resource_raw(res: &ID3D12Resource) -> *mut c_void {
res.as_raw()
}
impl FsrUpscaler {
pub(in crate::directx) fn try_new(
device: &ID3D12Device,
output_width: u32,
output_height: u32,
upscale_scale: f32,
output_uav_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
output_srv_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
output_srv_gpu: D3D12_GPU_DESCRIPTOR_HANDLE,
) -> Result<Option<Self>, String> {
if !cfg!(agility_sdk_configured) {
tracing::warn!(
"FidelityFX FSR3: skipping FFX init: this binary does not bundle \
Microsoft's Agility SDK, which FFX needs. Rebuild with \
CN_ENABLE_AGILITY_SDK=1 (and the `microsoft.direct3d.d3d12` NuGet \
package installed, or AGILITY_SDK_ROOT pointing at it); the \
resulting binary then only runs with its `D3D12/` directory \
beside it. Rendering at native resolution."
);
return Ok(None);
}
let ffx = match FfxApi::load() {
Some(api) => api,
None => {
if cfg!(ffx_sdk_bundled) {
tracing::warn!(
"FidelityFX FSR3: amd_fidelityfx_dx12.dll was bundled at \
build time but failed to load at runtime, falling back \
to native-resolution rendering"
);
} else {
tracing::warn!(
"FidelityFX FSR3: amd_fidelityfx_dx12.dll not found (build.rs \
did not bundle it; set FIDELITYFX_SDK_ROOT or put the DLL on \
PATH). Falling back to native-resolution rendering."
);
}
return Ok(None);
}
};
let scale = if upscale_scale > 0.0 {
upscale_scale.clamp(1.0 / 3.0, 1.0)
} else {
1.0
};
let render_width = (((output_width as f32) * scale).round() as u32).max(1);
let render_height = (((output_height as f32) * scale).round() as u32).max(1);
let mut backend = ffxCreateBackendDX12Desc {
header: ffxApiHeader {
ty: FFX_API_CREATE_CONTEXT_DESC_TYPE_BACKEND_DX12,
p_next: ptr::null_mut(),
},
device: device_raw(device),
};
let mut upscale = ffxCreateContextDescUpscale {
header: ffxApiHeader {
ty: FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE,
p_next: &mut backend.header as *mut ffxApiHeader,
},
flags: FFX_UPSCALE_ENABLE_HIGH_DYNAMIC_RANGE | FFX_UPSCALE_ENABLE_AUTO_EXPOSURE,
max_render_size: FfxApiDimensions2D {
width: render_width,
height: render_height,
},
max_upscale_size: FfxApiDimensions2D {
width: output_width,
height: output_height,
},
fp_message: Some(ffx_message_sink),
};
let mut ctx: ffxContext = ptr::null_mut();
let rc = unsafe {
(ffx.create_context)(
&mut ctx,
&mut upscale.header as *mut ffxApiHeader,
ptr::null(),
)
};
if rc != FFX_API_RETURN_OK || ctx.is_null() {
tracing::warn!(
"FidelityFX FSR3: ffxCreateContext returned {rc}; falling back to native"
);
return Ok(None);
}
let mut global_debug = ffxConfigureDescGlobalDebug1 {
header: ffxApiHeader {
ty: FFX_API_CONFIGURE_DESC_TYPE_GLOBALDEBUG1,
p_next: ptr::null_mut(),
},
fp_message: Some(ffx_message_sink),
debug_level: FFX_API_CONFIGURE_GLOBALDEBUG_LEVEL_VERBOSE,
};
let rc_dbg =
unsafe { (ffx.configure)(&mut ctx, &global_debug.header as *const ffxApiHeader) };
let _ = &mut global_debug;
if rc_dbg != FFX_API_RETURN_OK {
tracing::warn!("FidelityFX FSR3: global debug configure returned {rc_dbg} (non-fatal)");
}
tracing::info!(
"FidelityFX FSR3: context created: render {}x{} -> upscale {}x{} (scale {:.3})",
render_width,
render_height,
output_width,
output_height,
scale
);
let _ = FFX_UPSCALE_ENABLE_DEPTH_INVERTED;
let _ = FFX_UPSCALE_ENABLE_DEPTH_INFINITE;
let _ = FFX_API_RESOURCE_USAGE_DEPTHTARGET;
let _ = FFX_API_RESOURCE_USAGE_READ_ONLY;
let _ = FFX_API_SURFACE_FORMAT_R32_FLOAT;
let mut phase_count: i32 = 0;
let mut jpc_desc = ffxQueryDescUpscaleGetJitterPhaseCount {
header: ffxApiHeader {
ty: FFX_API_QUERY_DESC_TYPE_UPSCALE_GETJITTERPHASECOUNT,
p_next: ptr::null_mut(),
},
render_width,
display_width: output_width,
out_phase_count: &mut phase_count,
};
let rc = unsafe { (ffx.query)(&mut ctx, &mut jpc_desc.header as *mut ffxApiHeader) };
if rc != FFX_API_RETURN_OK || phase_count <= 0 {
tracing::warn!(
"FidelityFX FSR3: jitter-phase-count query returned {rc} (phase_count={phase_count})"
);
phase_count = 8;
}
let output = super::create_output_texture(device, output_width, output_height)?;
super::write_output_uav(device, &output, output_uav_cpu);
super::write_output_srv(device, &output, output_srv_cpu);
let _ = output_uav_cpu;
Ok(Some(FsrUpscaler {
ffx,
ctx,
output,
output_srv_gpu,
output_uav_cpu,
output_srv_cpu,
upscale_scale: scale,
render_width,
render_height,
output_width,
output_height,
jitter_phase_count: phase_count,
reset_pending: std::cell::Cell::new(true),
output_is_psr: std::cell::Cell::new(false),
}))
}
}
impl super::UpscaleBackend for FsrUpscaler {
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 upscale_scale(&self) -> f32 {
self.upscale_scale
}
fn output_srv_gpu(&self) -> D3D12_GPU_DESCRIPTOR_HANDLE {
self.output_srv_gpu
}
fn output_descriptors(
&self,
) -> (
D3D12_CPU_DESCRIPTOR_HANDLE,
D3D12_CPU_DESCRIPTOR_HANDLE,
D3D12_GPU_DESCRIPTOR_HANDLE,
) {
(
self.output_uav_cpu,
self.output_srv_cpu,
self.output_srv_gpu,
)
}
fn output_resource(&self) -> &ID3D12Resource {
&self.output
}
fn output_is_psr(&self) -> bool {
self.output_is_psr.get()
}
fn set_output_is_psr(&self, v: bool) {
self.output_is_psr.set(v);
}
fn jitter_offset(&self, frame_index: u32) -> [f32; 2] {
let mut jx = 0.0_f32;
let mut jy = 0.0_f32;
let index = (frame_index as i32).rem_euclid(self.jitter_phase_count.max(1));
let mut desc = ffxQueryDescUpscaleGetJitterOffset {
header: ffxApiHeader {
ty: FFX_API_QUERY_DESC_TYPE_UPSCALE_GETJITTEROFFSET,
p_next: ptr::null_mut(),
},
index,
phase_count: self.jitter_phase_count,
out_x: &mut jx,
out_y: &mut jy,
};
let rc = unsafe {
(self.ffx.query)(
&self.ctx as *const ffxContext as *mut ffxContext,
&mut desc.header as *mut ffxApiHeader,
)
};
if rc != FFX_API_RETURN_OK {
return [0.0, 0.0];
}
[jx, jy]
}
fn dispatch(
&self,
cmd: &ID3D12GraphicsCommandList,
inputs: super::UpscaleInputs<'_>,
camera: super::UpscaleCamera,
) -> Result<(), String> {
let super::UpscaleInputs {
color,
depth,
motion_vectors,
} = inputs;
let super::UpscaleCamera {
jitter_offset,
frame_time_delta_ms,
camera_near,
camera_far,
camera_fov_y_radians,
} = camera;
let render_size = FfxApiDimensions2D {
width: self.render_width,
height: self.render_height,
};
let upscale_size = FfxApiDimensions2D {
width: self.output_width,
height: self.output_height,
};
let mk_input =
|res: &ID3D12Resource, format: u32, usage: u32, state: u32, width: u32, height: u32| {
FfxApiResource {
resource: resource_raw(res),
description: FfxApiResourceDescription {
ty: FFX_API_RESOURCE_TYPE_TEXTURE2D,
format,
width_or_size: width,
height_or_stride: height,
depth_or_alignment: 1,
mip_count: 1,
flags: 0,
usage,
},
state,
}
};
let color_res = mk_input(
color,
FFX_API_SURFACE_FORMAT_R16G16B16A16_FLOAT,
FFX_API_RESOURCE_USAGE_READ_ONLY,
FFX_API_RESOURCE_STATE_COMPUTE_READ,
self.render_width,
self.render_height,
);
let depth_res = mk_input(
depth,
FFX_API_SURFACE_FORMAT_R32_FLOAT,
FFX_API_RESOURCE_USAGE_DEPTHTARGET,
FFX_API_RESOURCE_STATE_COMPUTE_READ,
self.render_width,
self.render_height,
);
let mv_res = mk_input(
motion_vectors,
FFX_API_SURFACE_FORMAT_R16G16_FLOAT,
FFX_API_RESOURCE_USAGE_READ_ONLY,
FFX_API_RESOURCE_STATE_COMPUTE_READ,
self.render_width,
self.render_height,
);
let output_res = mk_input(
&self.output,
FFX_API_SURFACE_FORMAT_R16G16B16A16_FLOAT,
FFX_API_RESOURCE_USAGE_UAV,
FFX_API_RESOURCE_STATE_UNORDERED_ACCESS,
self.output_width,
self.output_height,
);
let reset = self.reset_pending.replace(false);
let mut desc = ffxDispatchDescUpscale {
header: ffxApiHeader {
ty: FFX_API_DISPATCH_DESC_TYPE_UPSCALE,
p_next: ptr::null_mut(),
},
command_list: cmd_list_raw(cmd),
color: color_res,
depth: depth_res,
motion_vectors: mv_res,
exposure: FfxApiResource::empty(),
reactive: FfxApiResource::empty(),
transparency_and_composition: FfxApiResource::empty(),
output: output_res,
jitter_offset: FfxApiFloatCoords2D {
x: jitter_offset[0],
y: jitter_offset[1],
},
motion_vector_scale: FfxApiFloatCoords2D {
x: self.render_width as f32,
y: self.render_height as f32,
},
render_size,
upscale_size,
enable_sharpening: false,
sharpness: 0.0,
frame_time_delta: frame_time_delta_ms,
pre_exposure: 1.0,
reset,
camera_near,
camera_far,
camera_fov_angle_vertical: camera_fov_y_radians,
view_space_to_meters_factor: 1.0,
flags: 0,
};
let rc = unsafe {
(self.ffx.dispatch)(
&self.ctx as *const ffxContext as *mut ffxContext,
&desc.header as *const ffxApiHeader,
)
};
let _ = &mut desc.header;
if rc != FFX_API_RETURN_OK {
return Err(format!("ffxDispatch (upscale) returned {rc}"));
}
Ok(())
}
}
impl crate::directx::context::DxContext {
pub(in crate::directx) fn encode_upscale(
&self,
cmd: &windows::Win32::Graphics::Direct3D12::ID3D12GraphicsCommandList,
params: &crate::directx::graph_exec::GraphFrameParams<'_>,
) -> Result<(), String> {
use windows::Win32::Graphics::Direct3D12::*;
let upscaler = match &self.upscale.backend {
Some(u) => u,
None => return Ok(()),
};
static LOGGED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
if !LOGGED.swap(true, std::sync::atomic::Ordering::Relaxed) {
let (rw, rh) = upscaler.render_dims();
let (ow, oh) = upscaler.output_dims();
tracing::info!(
"temporal upscaling: first encode_upscale firing (render_size={rw}x{rh}, upscale_size={ow}x{oh})"
);
}
let gb = match &self.gbuffer {
Some(g) => g,
None => {
return Err(
"Upscale enabled but G-buffer resources (velocity / depth) are missing".into(),
);
}
};
let scene_res = self.post_scene_target().clone();
let mut barriers = vec![transition_barrier(
&gb.depth,
D3D12_RESOURCE_STATE_DEPTH_WRITE,
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE,
)];
if upscaler.output_is_psr() {
barriers.push(transition_barrier(
upscaler.output_resource(),
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
));
}
unsafe { cmd.ResourceBarrier(&barriers) };
let jitter = self.upscale.jitter.get();
let now = params.elapsed;
let prev = self.upscale.prev_elapsed.replace(now);
let dt_ms = ((now - prev) * 1000.0).clamp(1.0, 100.0);
let near = params.near.max(1e-3);
let far = params.far.max(near + 1.0);
let fov_y = params.fov_y_radians;
upscaler.dispatch(
cmd,
super::UpscaleInputs {
color: &scene_res,
depth: &gb.depth,
motion_vectors: &gb.velocity,
},
super::UpscaleCamera {
jitter_offset: jitter,
frame_time_delta_ms: dt_ms,
camera_near: near,
camera_far: far,
camera_fov_y_radians: fov_y,
},
)?;
let from_npsr_depth = transition_barrier(
&gb.depth,
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE,
D3D12_RESOURCE_STATE_DEPTH_WRITE,
);
let output_to_psr = transition_barrier(
upscaler.output_resource(),
D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
);
upscaler.set_output_is_psr(true);
unsafe { cmd.ResourceBarrier(&[from_npsr_depth, output_to_psr]) };
Ok(())
}
}
fn transition_barrier(
resource: &windows::Win32::Graphics::Direct3D12::ID3D12Resource,
before: windows::Win32::Graphics::Direct3D12::D3D12_RESOURCE_STATES,
after: windows::Win32::Graphics::Direct3D12::D3D12_RESOURCE_STATES,
) -> windows::Win32::Graphics::Direct3D12::D3D12_RESOURCE_BARRIER {
crate::directx::texture::transition_barrier(resource, before, after)
}
impl Drop for FsrUpscaler {
fn drop(&mut self) {
if !self.ctx.is_null() {
unsafe {
let _ = (self.ffx.destroy_context)(&mut self.ctx, ptr::null());
}
self.ctx = ptr::null_mut();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::mem::size_of;
#[test]
fn ffx_struct_sizes_match_sdk_v114() {
assert_eq!(size_of::<ffxApiHeader>(), 16);
assert_eq!(size_of::<FfxApiDimensions2D>(), 8);
assert_eq!(size_of::<FfxApiFloatCoords2D>(), 8);
assert_eq!(size_of::<FfxApiResourceDescription>(), 32);
assert_eq!(size_of::<FfxApiResource>(), 48);
}
}