use std::cell::Cell;
use std::ffi::{CStr, c_void};
use concinnity_core::render::dlss::DlssPreset;
use concinnity_core::render::error::{RenderError, RenderResult};
use concinnity_core::render::reactive_mask::ReactiveReader;
use windows::Win32::Foundation::HMODULE;
use windows::Win32::Graphics::Direct3D12::*;
use windows::Win32::Graphics::Dxgi::Common::*;
use windows::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryA};
use windows::core::PCSTR;
use crate::directx::descriptor_slot::SrvSlot;
use crate::directx::error::map_hresult;
use crate::directx::graph_exec::GraphFrameParams;
use crate::directx::texture::transition_barrier;
use crate::upscale_sdk::{
Availability, ResolvedBackend, SdkLibrary, UpscaleCamera, UpscaleExtent, UpscaleRequest,
};
#[cfg(ngx_sdk_bundled)]
mod dlss;
mod fsr;
mod xess;
pub(in crate::directx) struct UpscaleState {
pub backend: Option<Box<dyn UpscaleBackend>>,
pub requested: UpscaleRequest,
pub jitter: Cell<[f32; 2]>,
}
pub(in crate::directx) trait UpscaleBackend: Send {
fn extent(&self) -> UpscaleExtent;
fn output(&self) -> &UpscaleOutput;
fn jitter_offset(&self, frame_index: u32) -> [f32; 2];
fn dispatch(
&self,
cmd: &ID3D12GraphicsCommandList,
inputs: UpscaleInputs<'_>,
camera: UpscaleCamera,
) -> RenderResult<()>;
fn request_history_reset(&self);
fn dlss_preset(&self) -> Option<DlssPreset> {
None
}
fn reactive_reader(&self) -> ReactiveReader;
}
#[derive(Clone, Copy)]
pub(in crate::directx) struct UpscaleInputs<'a> {
pub color: &'a ID3D12Resource,
pub depth: &'a ID3D12Resource,
pub motion_vectors: &'a ID3D12Resource,
pub reactive: Option<&'a ID3D12Resource>,
}
#[derive(Clone, Copy)]
pub(in crate::directx) struct UpscalerDescriptors {
pub uav_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
pub srv_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
pub srv_gpu: SrvSlot,
}
#[derive(Clone, Copy)]
pub(in crate::directx) struct UpscaleDevice<'a> {
pub device: &'a ID3D12Device,
#[cfg_attr(
not(ngx_sdk_bundled),
expect(dead_code, reason = "only NGX feature creation submits work")
)]
pub command_queue: &'a ID3D12CommandQueue,
}
#[derive(Clone, Copy)]
struct UpscalerTarget<'a> {
gpu: UpscaleDevice<'a>,
extent: UpscaleExtent,
descriptors: UpscalerDescriptors,
#[cfg_attr(
not(ngx_sdk_bundled),
expect(dead_code, reason = "only DLSS has render presets")
)]
dlss_preset: DlssPreset,
}
const UPSCALE_OUTPUT_FORMAT: DXGI_FORMAT = DXGI_FORMAT_R16G16B16A16_FLOAT;
pub(in crate::directx) struct UpscaleOutput {
resource: ID3D12Resource,
descriptors: UpscalerDescriptors,
is_psr: Cell<bool>,
}
impl UpscaleOutput {
fn create(
device: &ID3D12Device,
(width, height): (u32, u32),
descriptors: UpscalerDescriptors,
) -> RenderResult<Self> {
let heap_props = D3D12_HEAP_PROPERTIES {
Type: D3D12_HEAP_TYPE_DEFAULT,
..Default::default()
};
let desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_TEXTURE2D,
Width: width as u64,
Height: height,
DepthOrArraySize: 1,
MipLevels: 1,
Format: UPSCALE_OUTPUT_FORMAT,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Flags: D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS,
..Default::default()
};
let mut created: Option<ID3D12Resource> = None;
unsafe {
device.CreateCommittedResource(
&heap_props,
D3D12_HEAP_FLAG_NONE,
&desc,
D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
None,
&mut created,
)
}
.map_err(|e| map_hresult(e.code(), "create upscale output texture"))?;
let resource = created.ok_or_else(|| {
RenderError::Other("create upscale output texture returned None".into())
})?;
let uav = D3D12_UNORDERED_ACCESS_VIEW_DESC {
Format: UPSCALE_OUTPUT_FORMAT,
ViewDimension: D3D12_UAV_DIMENSION_TEXTURE2D,
Anonymous: D3D12_UNORDERED_ACCESS_VIEW_DESC_0 {
Texture2D: D3D12_TEX2D_UAV {
MipSlice: 0,
PlaneSlice: 0,
},
},
};
let srv = D3D12_SHADER_RESOURCE_VIEW_DESC {
Format: UPSCALE_OUTPUT_FORMAT,
ViewDimension: D3D12_SRV_DIMENSION_TEXTURE2D,
Shader4ComponentMapping: D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
Anonymous: D3D12_SHADER_RESOURCE_VIEW_DESC_0 {
Texture2D: D3D12_TEX2D_SRV {
MostDetailedMip: 0,
MipLevels: 1,
PlaneSlice: 0,
ResourceMinLODClamp: 0.0,
},
},
};
unsafe {
device.CreateUnorderedAccessView(&resource, None, Some(&uav), descriptors.uav_cpu);
device.CreateShaderResourceView(&resource, Some(&srv), descriptors.srv_cpu);
}
Ok(Self {
resource,
descriptors,
is_psr: Cell::new(false),
})
}
pub(in crate::directx) fn resource(&self) -> &ID3D12Resource {
&self.resource
}
pub(in crate::directx) fn descriptors(&self) -> UpscalerDescriptors {
self.descriptors
}
pub(in crate::directx) fn srv_gpu(&self) -> SrvSlot {
self.descriptors.srv_gpu
}
}
struct Dll(HMODULE);
impl Dll {
fn open(name: &CStr) -> Option<Self> {
unsafe { LoadLibraryA(PCSTR(name.as_ptr().cast())) }
.ok()
.map(Self)
}
}
impl SdkLibrary for Dll {
fn symbol(&self, name: &CStr) -> Option<*const c_void> {
unsafe { GetProcAddress(self.0, PCSTR(name.as_ptr().cast())) }.map(|f| f as *const c_void)
}
}
fn availability() -> Availability {
Availability {
dlss: cfg!(ngx_sdk_bundled),
xess: cfg!(xess_sdk_bundled),
fsr: cfg!(agility_sdk_configured),
}
}
pub(in crate::directx) fn build_upscaler(
gpu: UpscaleDevice<'_>,
output: (u32, u32),
upscale_scale: f32,
descriptors: UpscalerDescriptors,
requested: &mut UpscaleRequest,
) -> RenderResult<Option<Box<dyn UpscaleBackend>>> {
let target = |dlss_preset| UpscalerTarget {
gpu,
extent: UpscaleExtent::resolve(output, upscale_scale),
descriptors,
dlss_preset,
};
let (built, _) =
requested.build_first_available(availability(), output, |candidate, preset| {
let target = target(preset);
Ok(match candidate {
ResolvedBackend::Fsr => fsr::FsrUpscaler::try_new(target)?.map(boxed),
ResolvedBackend::Xess => xess::XessUpscaler::try_new(target)?.map(boxed),
#[cfg(ngx_sdk_bundled)]
ResolvedBackend::Dlss => dlss::DlssUpscaler::try_new(target)?.map(boxed),
_ => None,
})
})?;
requested.adopt_running_preset(built.as_deref().and_then(|u| u.dlss_preset()));
Ok(built)
}
fn boxed(backend: impl UpscaleBackend + 'static) -> Box<dyn UpscaleBackend> {
Box::new(backend)
}
impl crate::directx::context::DxContext {
pub(in crate::directx) fn encode_upscale(
&self,
cmd: &ID3D12GraphicsCommandList,
params: &GraphFrameParams<'_>,
) -> RenderResult<()> {
let Some(upscaler) = &self.upscale.backend else {
return Ok(());
};
static LOGGED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
if !LOGGED.swap(true, std::sync::atomic::Ordering::Relaxed) {
tracing::info!(
"temporal upscaling: first encode_upscale firing ({})",
upscaler.extent()
);
}
let gb = self.gbuffer.as_ref().ok_or_else(|| {
RenderError::Other(
"Upscale enabled but G-buffer resources (velocity / depth) are missing".into(),
)
})?;
let output = upscaler.output();
let scene = self.post_scene_target().clone();
let barriers = [
transition_barrier(
&gb.depth,
D3D12_RESOURCE_STATE_DEPTH_WRITE,
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE,
),
transition_barrier(
output.resource(),
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
),
];
let count = if output.is_psr.get() { 2 } else { 1 };
unsafe { cmd.ResourceBarrier(&barriers[..count]) };
let mask = &self.targets.reactive_mask;
let local_mask = !params.reactive.readable && upscaler.reactive_reader().requires();
if local_mask {
mask.clear_outside_graph(cmd);
}
let reactive = (params.reactive.readable || local_mask).then_some(&mask.resource);
let dispatched = upscaler.dispatch(
cmd,
UpscaleInputs {
color: &scene,
depth: &gb.depth,
motion_vectors: &gb.velocity,
reactive,
},
UpscaleCamera::new(
self.upscale.jitter.get(),
params.elapsed,
params.near,
params.fov_y_radians,
),
);
if local_mask {
mask.rest_after_compute_read(cmd);
}
dispatched?;
let after = [
transition_barrier(
&gb.depth,
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE,
D3D12_RESOURCE_STATE_DEPTH_WRITE,
),
transition_barrier(
output.resource(),
D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
),
];
output.is_psr.set(true);
unsafe { cmd.ResourceBarrier(&after) };
Ok(())
}
}