use concinnity_core::components::{GlassPanel, WaterSurface};
use concinnity_core::gfx::auto_exposure;
use concinnity_core::gfx::render_types::{DrawObject, LightUniforms};
use concinnity_core::render::backend_init::{PlanarBudget, PostSettings, SdfVolumeSource, WorldFx};
use concinnity_core::render::decal::{self, DecalRecord};
use concinnity_core::render::error::{RenderError, RenderResult};
use concinnity_core::render::lights;
use concinnity_core::render::particles::{self, ParticleEmitterRecord};
use concinnity_core::render::planar_reflection::PlanarReflectors;
use concinnity_core::render::post::device::PostExtent;
use concinnity_core::render::post::ssao::settings::SsaoSettings;
use concinnity_core::render::volumetric_fog::FogSettings;
use windows::Win32::Graphics::Direct3D12::*;
use super::heap_layout::{DSV_GBUFFER_DEPTH_SLOT, DSV_GLASS_REFLECTION_DEPTH_SLOT, RtvHeapLayout};
use super::{Features, InitGpu, heaps};
use crate::directx::auto_exposure::AutoExposureState;
use crate::directx::context::{
DxDescriptors, DxSceneAssets, DxTargets, FRAMES, SwapchainState, dump_on_err,
};
use crate::directx::decal::DecalState;
use crate::directx::descriptor_slot::SamplerSlot;
use crate::directx::draw::shadow::ShadowState;
use crate::directx::fog::FogState;
use crate::directx::particle::ParticleState;
use crate::directx::planar::PlanarReflectionSet;
use crate::directx::post::descriptors::PostDescriptors;
use crate::directx::post::gbuffer::{GbufferResources, GbufferSlots};
use crate::directx::post::post_device::DxPostDevice;
use crate::directx::post::ssao::{SsaoResources, SsaoState};
use crate::directx::post::ssgi::SsgiResources;
use crate::directx::post::ssr::SsrResources;
use crate::directx::post::taa::TaaResources;
use crate::directx::post::upscale::UpscaleState;
use crate::directx::quality::QualitySlotHandles;
use crate::directx::raymarch::RaymarchResources;
use crate::directx::texture::{create_fallback_white_resource, write_texture_srv};
use crate::directx::transparent::TransparentResources;
pub(super) fn build_upscale(
gpu: &InitGpu<'_>,
descriptors: &DxDescriptors,
output: (u32, u32),
post: &PostSettings,
) -> RenderResult<UpscaleState> {
let hw = gpu.hw;
let layout = &descriptors.layout;
let (width, height) = output;
let mut requested = crate::upscale_sdk::UpscaleRequest::from_post(post);
let upscaler = if post.temporal_upscaling {
crate::directx::post::upscale::build_upscaler(
crate::directx::post::upscale::UpscaleDevice {
device: &hw.device,
command_queue: &hw.command_queue,
},
(width, height),
post.upscale_scale,
crate::directx::post::upscale::UpscalerDescriptors {
uav_cpu: descriptors.slot_cpu(layout.upscale_uav_slot),
srv_cpu: descriptors.slot_cpu(layout.upscale_srv_slot),
srv_gpu: descriptors.slot_gpu(layout.upscale_srv_slot),
},
&mut requested,
)?
} else {
None
};
if let Some(u) = &upscaler {
let (render_w, render_h) = u.extent().render;
tracing::info!(
"DirectX: temporal upscaling active: scene render {}x{}, drawable {}x{}",
render_w,
render_h,
width,
height
);
}
Ok(UpscaleState {
backend: upscaler,
requested,
jitter: std::cell::Cell::new([0.0, 0.0]),
})
}
pub(super) fn build_quality_slots(
gpu: &InitGpu<'_>,
descriptors: &DxDescriptors,
swapchain: &SwapchainState,
targets: &DxTargets,
rtv: &RtvHeapLayout,
) -> QualitySlotHandles {
let layout = &descriptors.layout;
let srv = |slot| (descriptors.slot_cpu(slot), descriptors.slot_gpu(slot));
let dsv_descriptor_size =
heaps::descriptor_size(&gpu.hw.device, D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
QualitySlotHandles {
rt_output_rtv: swapchain.rtv(rtv.rt_output_slot),
rt_output_srv: srv(layout.rt_output_srv_slot),
gbuffer: GbufferSlots {
normal_depth_rtv: swapchain.rtv(rtv.gbuffer_base_slot),
normal_depth_srv: srv(layout.gbuffer_srv_base_slot),
roughness_rtv: swapchain.rtv(rtv.gbuffer_base_slot + 1),
roughness_srv: srv(layout.gbuffer_srv_base_slot + 1),
velocity_rtv: swapchain.rtv(rtv.gbuffer_base_slot + 2),
velocity_srv: srv(layout.gbuffer_srv_base_slot + 2),
depth_dsv: heaps::cpu_handle(
&targets.depth.heap,
dsv_descriptor_size,
DSV_GBUFFER_DEPTH_SLOT,
),
},
glass_reflection: {
let base = layout.glass_reflection_srv_base_slot;
crate::directx::transparent::GlassReflectionSlots {
rtv: [
swapchain.rtv(rtv.glass_reflection_base_slot),
swapchain.rtv(rtv.glass_reflection_base_slot + 1),
],
dsv: heaps::cpu_handle(
&targets.depth.heap,
dsv_descriptor_size,
DSV_GLASS_REFLECTION_DEPTH_SLOT,
),
srv_cpu: std::array::from_fn(|i| descriptors.slot_cpu(base + i)),
windows: std::array::from_fn(|w| descriptors.slot_gpu(base + 2 * w)),
}
},
}
}
pub(super) fn post_device<'a>(
gpu: &InitGpu<'a>,
descriptors: &'a DxDescriptors,
post: &'a PostDescriptors,
) -> DxPostDevice<'a> {
DxPostDevice {
device: &gpu.hw.device,
descriptors: post,
srv_heap: &descriptors.srv_heap,
info_queue: gpu.hw.info_queue.as_ref(),
probes: None,
hot_reload: gpu.hot_reload,
}
}
pub(super) fn build_taa(
post_device: &DxPostDevice<'_>,
features: &Features,
targets: &DxTargets,
) -> RenderResult<Option<TaaResources>> {
let taa = if features.taa_enabled {
Some(TaaResources::new(
post_device,
targets.extent.render_width,
targets.extent.render_height,
)?)
} else {
None
};
Ok(taa)
}
pub(super) fn build_ssao(
gpu: &InitGpu<'_>,
post_device: &DxPostDevice<'_>,
descriptors: &DxDescriptors,
targets: &DxTargets,
settings: Option<SsaoSettings>,
) -> RenderResult<SsaoState> {
let hw = gpu.hw;
let device = hw.alloc.device();
let white_slot = descriptors.layout.ssao_white_srv_slot;
let ssao_white = create_fallback_white_resource(&hw.alloc)?;
write_texture_srv(device, &ssao_white, descriptors.slot_cpu(white_slot));
let ssao = match settings {
Some(settings) => {
let ao_output = targets
.transient_pool
.resource_for("ao_output")
.ok_or_else(|| {
RenderError::Other(
"transient pool missing ao_output while SSAO is enabled".into(),
)
})?;
Some(SsaoResources::new(
post_device,
settings,
PostExtent {
width: targets.extent.render_width,
height: targets.extent.render_height,
},
ao_output,
)?)
}
None => None,
};
Ok(SsaoState {
resources: ssao,
white: ssao_white,
white_srv_gpu: descriptors.slot_gpu(white_slot),
})
}
pub(super) fn build_ssr(
post_device: &DxPostDevice<'_>,
targets: &DxTargets,
post: &PostSettings,
) -> RenderResult<Option<SsrResources>> {
let ssr = if post.ssr.is_some() || post.ssgi.is_some() || post.rt_reflections.is_some() {
Some(SsrResources::new(
post_device,
targets.extent.render_width,
targets.extent.render_height,
post.ssr,
)?)
} else {
None
};
Ok(ssr)
}
pub(super) fn build_ssgi(
post_device: &DxPostDevice<'_>,
targets: &DxTargets,
post: &PostSettings,
) -> RenderResult<Option<SsgiResources>> {
let ssgi = match post.ssgi {
Some(settings) => Some(SsgiResources::new(
post_device,
targets.extent.render_width,
targets.extent.render_height,
settings,
)?),
None => None,
};
Ok(ssgi)
}
pub(super) fn build_gbuffer(
gpu: &InitGpu<'_>,
targets: &DxTargets,
slots: &QualitySlotHandles,
gbuffer_enabled: bool,
) -> RenderResult<Option<GbufferResources>> {
let gbuffer = if gbuffer_enabled {
let pooled = targets.transient_pool.gbuffer_pooled().ok_or_else(|| {
RenderError::Other("transient pool missing the gbuffer color targets".into())
})?;
Some(GbufferResources::new(
crate::directx::post::gbuffer::GbufferDeviceCtx {
alloc: &gpu.hw.alloc,
info_queue: gpu.hw.info_queue.as_ref(),
hot_reload: gpu.hot_reload,
},
crate::directx::post::gbuffer::GbufferExtent {
width: targets.extent.render_width,
height: targets.extent.render_height,
},
slots.gbuffer,
&pooled,
)?)
} else {
None
};
Ok(gbuffer)
}
pub(super) fn build_decals(
gpu: &InitGpu<'_>,
descriptors: &DxDescriptors,
targets: &DxTargets,
scene: &DxSceneAssets,
decals: Vec<DecalRecord>,
) -> RenderResult<DecalState> {
let hw = gpu.hw;
let decal_srv_base_slot = descriptors.layout.decal_srv_base_slot;
let decals_state = Some(crate::directx::decal::DecalResources::new(
&hw.alloc,
targets.hdr.msaa_samples,
decal_srv_base_slot,
targets.main_depth_srv_gpu,
hw.info_queue.as_ref(),
gpu.hot_reload,
)?);
if decals.len() > crate::directx::decal::MAX_DECALS {
return Err(RenderError::Other(format!(
"decals: {} authored decals exceed MAX_DECALS ({})",
decals.len(),
crate::directx::decal::MAX_DECALS
)));
}
let last_tex = scene.textures.len().saturating_sub(1);
for (i, rec) in decals.iter().enumerate() {
let tex_idx = rec.texture_slot.min(last_tex);
write_texture_srv(
&hw.device,
&scene.textures[tex_idx],
descriptors.slot_cpu(decal_srv_base_slot + i),
);
}
let mut decal_set = decal::DecalSet::new(crate::directx::decal::MAX_DECALS, FRAMES);
for record in decals {
decal_set.insert(record).map_err(|_| {
RenderError::Other("decals: authored decals exceed MAX_DECALS".to_string())
})?;
}
Ok(DecalState {
state: decals_state,
set: decal_set,
})
}
pub(super) fn build_fog(
gpu: &InitGpu<'_>,
descriptors: &DxDescriptors,
targets: &DxTargets,
shadow: &ShadowState,
settings: Option<FogSettings>,
light_uniforms: &LightUniforms,
) -> RenderResult<FogState> {
let hw = gpu.hw;
let layout = &descriptors.layout;
let fog_resources = if settings.is_some() {
Some(crate::directx::fog::FogResources::new(
&hw.alloc,
crate::directx::fog::FogVolumeDescriptors {
uav_cpu: descriptors.slot_cpu(layout.fog_froxel_uav_slot),
uav_gpu: descriptors.slot_gpu(layout.fog_froxel_uav_slot),
srv_cpu: descriptors.slot_cpu(layout.fog_froxel_srv_slot),
srv_gpu: descriptors.slot_gpu(layout.fog_froxel_srv_slot),
},
crate::directx::fog::FogShaderResourceHandles {
depth_srv_gpu: targets.main_depth_srv_gpu,
shadow_srv_gpu: shadow.srv_gpu,
},
crate::directx::fog::FogDeviceParams {
msaa_samples: targets.hdr.msaa_samples,
hot_reload: gpu.hot_reload,
},
hw.info_queue.as_ref(),
)?)
} else {
None
};
Ok(FogState {
resources: fog_resources,
settings,
sun_dir: shadow.light_dir,
sun_color: lights::sun_color(light_uniforms),
})
}
pub(super) fn build_particles(
gpu: &InitGpu<'_>,
descriptors: &DxDescriptors,
targets: &DxTargets,
scene: &DxSceneAssets,
particles: Vec<ParticleEmitterRecord>,
) -> RenderResult<ParticleState> {
let hw = gpu.hw;
let particle_srv_base_slot = descriptors.layout.particle_srv_base_slot;
if particles.len() > crate::directx::particle::MAX_EMITTERS {
return Err(RenderError::Other(format!(
"particles: {} authored emitters exceed MAX_EMITTERS ({})",
particles.len(),
crate::directx::particle::MAX_EMITTERS
)));
}
let (particle_resources, particle_records, particle_emitter_states) = if !particles.is_empty() {
let resources = crate::directx::particle::ParticleResources::new(
&hw.alloc,
particle_srv_base_slot,
targets.hdr.msaa_samples,
targets.main_depth_srv_gpu,
hw.info_queue.as_ref(),
gpu.hot_reload,
)?;
let mut states: Vec<Option<crate::directx::particle::ParticleEmitterGpuState>> =
Vec::with_capacity(particles.len());
let last_tex = scene.textures.len().saturating_sub(1);
for (i, rec) in particles.iter().enumerate() {
let state = crate::directx::particle::build_emitter_gpu_state(&hw.alloc, rec)?;
states.push(Some(state));
let tex_idx = rec.texture_slot.min(last_tex);
write_texture_srv(
&hw.device,
&scene.textures[tex_idx],
descriptors.slot_cpu(particle_srv_base_slot + i),
);
}
let recs: Vec<Option<particles::ParticleEmitterRecord>> =
particles.into_iter().map(Some).collect();
(Some(resources), recs, states)
} else {
(None, Vec::new(), Vec::new())
};
Ok(ParticleState {
resources: particle_resources,
records: particle_records,
emitter_state: particle_emitter_states,
free_slots: Vec::new(),
srv_base_slot: particle_srv_base_slot,
last_elapsed: std::cell::Cell::new(0.0),
frame_index: std::cell::Cell::new(0),
})
}
pub(super) fn build_auto_exposure(
gpu: &InitGpu<'_>,
post: &PostSettings,
) -> RenderResult<AutoExposureState> {
let hw = gpu.hw;
let (resources, adaptation) = if let Some(settings) = post.auto_exposure.as_ref() {
let resources = dump_on_err(
hw.info_queue.as_ref(),
crate::directx::auto_exposure::AutoExposureResources::new(&hw.alloc, gpu.hot_reload),
)?;
let adaptation =
auto_exposure::ExposureAdaptation::new(*settings, post.auto_exposure_bias_ev);
(Some(resources), Some(adaptation))
} else {
(None, None)
};
Ok(AutoExposureState {
resources,
adaptation,
last_elapsed: 0.0,
})
}
pub(super) fn build_raymarch(
gpu: &InitGpu<'_>,
descriptors: &DxDescriptors,
targets: &DxTargets,
shadow: &ShadowState,
scene: &DxSceneAssets,
sdf_volumes: &[SdfVolumeSource],
) -> RenderResult<Option<RaymarchResources>> {
let hw = gpu.hw;
let raymarch_srv_base_slot = descriptors.layout.raymarch_srv_base_slot;
let sampler_descriptor_size =
heaps::descriptor_size(&hw.device, D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER);
let raymarch = RaymarchResources::try_new(
crate::directx::raymarch::RaymarchDeviceContext {
alloc: &hw.alloc,
info_queue: hw.info_queue.as_ref(),
},
crate::directx::raymarch::RaymarchTargetConfig {
width: targets.extent.render_width,
height: targets.extent.render_height,
msaa_samples: targets.hdr.msaa_samples,
},
crate::directx::raymarch::RaymarchSharedBindings {
shadow_resource: shadow.resource.as_ref().map(|r| &r.resource),
shadow_layers: shadow.layers,
irradiance_resource: &scene.env_map.irradiance.resource,
prefilter_resource: &scene.env_map.prefilter.resource,
},
crate::directx::raymarch::RaymarchDescriptorHandles {
srv_base_cpu: descriptors.slot_cpu(raymarch_srv_base_slot),
srv_base_gpu: descriptors.slot_gpu(raymarch_srv_base_slot),
srv_descriptor_size: descriptors.srv_descriptor_size,
sampler_base_cpu: heaps::cpu_handle(
&descriptors.sampler_heap,
sampler_descriptor_size,
heaps::RAYMARCH_SAMPLER_BASE_SLOT,
),
sampler_base_gpu: SamplerSlot::at(
&descriptors.sampler_heap,
sampler_descriptor_size,
heaps::RAYMARCH_SAMPLER_BASE_SLOT,
),
sampler_descriptor_size,
},
sdf_volumes,
gpu.hot_reload,
)?;
Ok(raymarch)
}
pub(super) fn plan_planar(fx: &WorldFx, budget: PlanarBudget) -> PlanarReflectors {
let planar = PlanarReflectors::plan(&fx.water_surfaces, &fx.glass_panels, budget);
if planar.overflow() > 0 {
tracing::warn!(
"planar reflection: {} reflector plane(s) exceed the budget and fall back \
to the box-projected probe cube",
planar.overflow()
);
}
planar
}
pub(super) fn build_planar_reflection(
gpu: &InitGpu<'_>,
descriptors: &DxDescriptors,
targets: &DxTargets,
planar: PlanarReflectors,
n_cull: usize,
clear_color: [f32; 4],
) -> RenderResult<Option<PlanarReflectionSet>> {
let planar_resolve_srv_base_slot = descriptors.layout.planar_resolve_srv_base_slot;
let planar_reflection = if planar.planes().is_empty() {
None
} else {
let resolve_srv_cpu: Vec<_> = (0..planar.planes().len())
.map(|i| descriptors.slot_cpu(planar_resolve_srv_base_slot + i))
.collect();
let resolve_srv_gpu: Vec<_> = (0..planar.planes().len())
.map(|i| descriptors.slot_gpu(planar_resolve_srv_base_slot + i))
.collect();
Some(PlanarReflectionSet::new(
&gpu.hw.alloc,
crate::directx::planar::PlanarConfig {
sample_count: targets.hdr.msaa_samples,
width: targets.extent.render_width,
height: targets.extent.render_height,
n_cull,
},
planar,
crate::directx::planar::PlanarTargets {
resolve_srv_cpu: &resolve_srv_cpu,
resolve_srv_gpu: &resolve_srv_gpu,
clear_color,
},
)?)
};
Ok(planar_reflection)
}
pub(super) struct TransparentInputs<'a> {
pub(super) descriptors: &'a DxDescriptors,
pub(super) targets: &'a DxTargets,
pub(super) reflection_slots: crate::directx::transparent::GlassReflectionSlots,
pub(super) reflection_divisor: u32,
pub(super) planar_slots: &'a [Option<usize>],
pub(super) glass_panels: &'a [GlassPanel],
pub(super) water_surfaces: &'a [WaterSurface],
pub(super) draw_objects: &'a [DrawObject],
}
pub(super) fn build_transparent(
gpu: &InitGpu<'_>,
inputs: TransparentInputs<'_>,
) -> RenderResult<Option<TransparentResources>> {
let TransparentInputs {
descriptors,
targets,
reflection_slots,
reflection_divisor,
planar_slots,
glass_panels,
water_surfaces,
draw_objects,
} = inputs;
let hw = gpu.hw;
let scene_copy_slot = descriptors.layout.transparent_scene_copy_srv_slot;
let seethrough_mesh_indices: Vec<usize> = draw_objects
.iter()
.enumerate()
.filter(|(_, o)| o.material.transparent != 0 && o.material.see_through != 0)
.map(|(i, _)| i)
.collect();
let has_seethrough_meshes = !seethrough_mesh_indices.is_empty() && hw.rt_capable;
let transparent =
if glass_panels.is_empty() && water_surfaces.is_empty() && !has_seethrough_meshes {
None
} else {
let (water_planar_slots, glass_planar_slots) =
planar_slots.split_at(water_surfaces.len());
Some(TransparentResources::new(
crate::directx::transparent::TransparentDeviceCtx { alloc: &hw.alloc },
crate::directx::transparent::TransparentBuildConfig {
msaa_samples: targets.hdr.msaa_samples,
width: targets.extent.render_width,
height: targets.extent.render_height,
hot_reload: gpu.hot_reload,
reflection_divisor,
},
crate::directx::transparent::TransparentSceneTargets {
scene_copy_srv_cpu: descriptors.slot_cpu(scene_copy_slot),
scene_copy_srv_gpu: descriptors.slot_gpu(scene_copy_slot),
depth_srv_gpu: targets.main_depth_srv_gpu,
reflection_slots,
},
crate::directx::transparent::TransparentContent {
glass_panels,
glass_planar_slots,
water_surfaces,
water_planar_slots,
seethrough_mesh_indices: &seethrough_mesh_indices,
},
hw.info_queue.as_ref(),
)?)
};
Ok(transparent)
}