use concinnity_core::gfx::frustum::Frustum;
use concinnity_core::gfx::render_types::{
ClusterCamera, ClusterParams, LightUniforms, LineVertex, ShadowUniforms, TextDrawCall,
};
use concinnity_core::render::error::{RenderError, RenderResult};
use concinnity_core::render::lights;
use concinnity_core::render::pass_timing;
use concinnity_core::render::render_graph::{PassId, build_frame_graph};
use windows::Win32::Graphics::Direct3D12::*;
use super::com;
use super::context::DxContext;
use super::graph_exec::GraphFrameParams;
use crate::directx::error::map_hresult;
use stages::FrameProjection;
pub(in crate::directx) mod composite;
pub(in crate::directx) mod main;
pub(in crate::directx) mod shadow;
pub(in crate::directx) mod spot_shadow;
mod stages;
pub(in crate::directx) use concinnity_core::render::uniforms::ViewUniforms;
#[derive(Clone, Copy)]
pub(super) struct RecordFrameTargets<'a> {
pub cmd: &'a ID3D12GraphicsCommandList,
pub back_buffer: &'a ID3D12Resource,
pub back_buffer_rtv: D3D12_CPU_DESCRIPTOR_HANDLE,
pub frame_idx: usize,
}
#[derive(Clone, Copy)]
pub(super) struct RecordFrameView<'a> {
pub elapsed: f32,
pub fov_y_radians: f32,
pub near: f32,
pub view_distance: Option<f32>,
pub cam_pos: [f32; 3],
pub text_calls: &'a [TextDrawCall],
pub lines: &'a [LineVertex],
}
#[derive(Clone, Copy)]
pub(super) struct RecordFrameResolution {
pub width: u32,
pub height: u32,
pub output_width: u32,
pub output_height: u32,
}
pub(super) struct RecordedFrame {
pub pass_cmd_lists: Vec<ID3D12GraphicsCommandList>,
pub primed_model_history: bool,
}
impl DxContext {
pub(in crate::directx) fn record_frame_start(
&mut self,
frame: usize,
) -> RenderResult<ID3D12GraphicsCommandList> {
unsafe { self.commands.command_allocators[frame].Reset() }
.map_err(|e| map_hresult(e.code(), "start allocator reset"))?;
let start_cmd = self.commands.command_lists[frame].clone();
unsafe { start_cmd.Reset(&self.commands.command_allocators[frame], None) }
.map_err(|e| map_hresult(e.code(), "start cmd reset"))?;
if let Some(heap) = self.timestamps.query_heap.as_ref() {
let (start_slot, _) = pass_timing::whole_frame_pair(frame);
let block_base = (frame * pass_timing::SLOTS_PER_FRAME) as u32;
unsafe {
start_cmd.EndQuery(heap, D3D12_QUERY_TYPE_TIMESTAMP, start_slot);
let pass_count = pass_timing::SLOTS_PER_FRAME / 2 - 1;
for pass_idx in 0..pass_count as u32 {
let pair_start = block_base + 2 + 2 * pass_idx;
let pair_end = pair_start + 1;
start_cmd.EndQuery(heap, D3D12_QUERY_TYPE_TIMESTAMP, pair_end);
start_cmd.EndQuery(heap, D3D12_QUERY_TYPE_TIMESTAMP, pair_start);
}
}
}
self.rt_dynamic_update(&start_cmd, frame);
unsafe { start_cmd.Close() }.map_err(|e| map_hresult(e.code(), "start cmd close"))?;
Ok(start_cmd)
}
pub(super) fn record_frame(
&self,
targets: RecordFrameTargets<'_>,
view: RecordFrameView<'_>,
resolution: RecordFrameResolution,
world_hidden: bool,
) -> RenderResult<RecordedFrame> {
let RecordFrameTargets {
cmd: end_cmd,
back_buffer,
back_buffer_rtv,
frame_idx,
} = targets;
let RecordFrameView {
elapsed,
fov_y_radians,
near,
view_distance,
cam_pos,
text_calls,
lines,
} = view;
let RecordFrameResolution {
width,
height,
output_width,
output_height,
} = resolution;
let aspect = if height == 0 {
1.0
} else {
width as f32 / height as f32
};
unsafe {
std::ptr::copy_nonoverlapping(
&self.shadow.uniforms as *const ShadowUniforms as *const u8,
self.uniforms.shadow_ubo_ptrs[frame_idx],
std::mem::size_of::<ShadowUniforms>(),
);
}
let shadow_ubo_gva = com::gpu_va(&self.uniforms.shadow_ubo_resources[frame_idx]);
if self.uniforms.take_light_dirty(frame_idx) {
upload_light_uniforms(
self.uniforms.light_ubo_ptrs[frame_idx],
&self.uniforms.light_uniforms,
);
}
self.upload_joint_matrices(frame_idx);
self.upload_morph_weights(frame_idx);
let bindless_cull_enabled = self.cull.main_bindless_pso.is_some() && self.cull_count() > 0;
if !world_hidden && bindless_cull_enabled {
self.build_object_buffer(frame_idx);
self.build_draw_args_buffer(frame_idx, cam_pos, self.model_history_mode());
}
let clustered = lights::clustering_active(
self.uniforms.light_uniforms.num_local_lights,
self.probe.book.count(),
);
let seed_inputs = self.frame_graph_inputs(
width,
height,
bindless_cull_enabled,
clustered,
lines,
world_hidden,
);
let FrameProjection {
proj,
cur_vp,
vp_mat,
} = self.frame_projection(fov_y_radians, aspect, near, width, height);
let cluster_params = ClusterParams::for_camera(
&ClusterCamera {
view: self.state.view.matrix,
proj,
position: cam_pos,
near,
range: self.uniforms.cluster_reach.range(
cam_pos,
near,
self.probe.book.records(),
view_distance,
),
width,
height,
},
self.uniforms.light_uniforms.num_local_lights,
self.probe.book.count() as u32,
);
self.write_cluster_params(frame_idx, &cluster_params);
let reflections_enabled =
if self.reflection_composite.is_some() && self.reflection_resolve_active() {
1.0
} else {
0.0
};
let view_uni = ViewUniforms {
vp: vp_mat,
view: self.state.view.matrix,
elapsed,
reflections_enabled,
cam_pos: [cam_pos[0], cam_pos[1], cam_pos[2]],
prefilter_mip_count: self.scene.env_map.prefilter_mip_count as f32,
shade_mode: self.shade_mode(),
ambient_occlusion: 1.0,
sky_rot: self.state.view.sky_rot,
};
unsafe {
std::ptr::copy_nonoverlapping(
&view_uni as *const ViewUniforms as *const u8,
self.uniforms.view_ubo_ptrs[frame_idx],
std::mem::size_of::<ViewUniforms>(),
);
}
let frustum = Frustum::from_camera(vp_mat, view_distance);
let (view_gva, light_gva, local_lights_gva) = (
com::gpu_va(&self.uniforms.view_ubo_resources[frame_idx]),
com::gpu_va(&self.uniforms.light_ubo_resources[frame_idx]),
com::gpu_va(&self.uniforms.local_light_buffer),
);
let scene_srv = if self.upscale.backend.is_some() {
self.scene_srv_for_post()
} else {
match &self.taa {
Some(taa) => taa.output().srv_gpu(),
None => self.scene_srv_for_post(),
}
};
let cached_graph = self.graph_cache.borrow_mut().take();
let frame_graph = match cached_graph {
Some((cached_inputs, cached)) if cached_inputs == seed_inputs => cached,
_ => build_frame_graph(&seed_inputs)
.map_err(|e| RenderError::Other(format!("frame-graph compile: {e}")))?,
};
let lines = if frame_graph.pass(PassId::Lines).is_some() {
self.upload_lines(frame_idx, lines)?
} else {
None
};
let prime_model_history = frame_graph.pass(PassId::GBufferPrepass).is_some()
&& self.state.model_history.borrow_mut().take_prime();
let frame_params = GraphFrameParams {
cmd: end_cmd,
frame_idx,
back_buffer,
back_buffer_rtv,
text_calls,
lines,
world_hidden,
scene_srv,
width,
height,
output_width,
output_height,
cam_pos,
shadow_ubo_gva,
view_gva,
light_gva,
local_lights_gva,
vp_mat,
cur_vp,
frustum: &frustum,
fov_y_radians,
aspect,
elapsed,
near,
prime_model_history,
planar: self
.planar_reflection
.as_ref()
.map(|set| set.frame_plan(vp_mat))
.unwrap_or_default(),
cluster_params,
reactive: concinnity_core::render::reactive_mask::ReactiveMaskPlan::of(&seed_inputs),
};
let pass_cmd_lists = match self.execute_graph(&frame_graph, &frame_params) {
Ok(lists) => lists,
Err(e) => {
if prime_model_history {
self.state.model_history.borrow_mut().request_prime();
}
return Err(e);
}
};
*self.graph_cache.borrow_mut() = Some((seed_inputs, frame_graph));
self.advance_temporal_state(concinnity_core::render::view_history::ViewFrame {
vp: cur_vp,
elapsed,
cam_pos,
});
Ok(RecordedFrame {
pass_cmd_lists,
primed_model_history: prime_model_history,
})
}
}
pub(super) fn upload_light_uniforms(slot: *mut u8, lu: &LightUniforms) {
unsafe {
std::ptr::copy_nonoverlapping(
lu as *const LightUniforms as *const u8,
slot,
std::mem::size_of::<LightUniforms>(),
);
}
}
#[derive(Clone, Copy)]
pub(in crate::directx) struct LocalLightParams {
pub spot_buffer: u32,
pub spot_table: u32,
pub area_buffer: u32,
pub ltc_table: u32,
}
impl LocalLightParams {
pub(crate) const BINDLESS: Self = Self {
spot_buffer: 15,
spot_table: 16,
area_buffer: 17,
ltc_table: 18,
};
}
const MAIN_PROBE_CUBES_PARAM: u32 = 10;
const MAIN_PROBE_SET_PARAM: u32 = 11;
const MAIN_PROBE_RECORDS_PARAM: u32 = 19;
impl DxContext {
pub(super) fn bind_main_probe_set(
&self,
cmd: &ID3D12GraphicsCommandList,
set_cbv: u64,
records: u64,
) {
use crate::directx::descriptor_slot::DescriptorTables as _;
unsafe {
cmd.set_graphics_srv_table(MAIN_PROBE_CUBES_PARAM, self.probe_cube_table_gpu());
cmd.SetGraphicsRootConstantBufferView(MAIN_PROBE_SET_PARAM, set_cbv);
cmd.SetGraphicsRootShaderResourceView(MAIN_PROBE_RECORDS_PARAM, records);
}
}
}
pub(super) fn upload_static_records<T: Copy>(
buffer: &ID3D12Resource,
records: &[T],
label: &str,
) -> RenderResult<()> {
let bytes = std::mem::size_of_val(records);
let mut ptr = std::ptr::null_mut::<std::ffi::c_void>();
unsafe { buffer.Map(0, None, Some(&mut ptr)) }
.map_err(|e| map_hresult(e.code(), &format!("map {label} buffer")))?;
unsafe {
std::ptr::copy_nonoverlapping(records.as_ptr() as *const u8, ptr as *mut u8, bytes);
buffer.Unmap(0, None);
}
Ok(())
}