use concinnity_core::gfx::frustum::Frustum;
use concinnity_core::gfx::render_types::{ClusterParams, TextDrawCall};
use concinnity_core::render::error::{RenderError, RenderResult};
use concinnity_core::render::pass_timing;
use concinnity_core::render::planar_reflection::PlanarFramePlan;
use concinnity_core::render::render_graph;
use concinnity_core::render::render_graph::{
BarrierOp, CompiledGraph, CompiledPass, GraphResourceClass, PassId, final_states,
};
use concinnity_core::render::uniforms::PassCamera;
use concinnity_core::transform::mat4_inverse;
use concinnity_host::thread::jobs;
use std::sync::Mutex;
use windows::Win32::Graphics::Direct3D12::*;
use super::barrier_translate::{DxBarrier, d3d12_barrier, d3d12_restore};
use super::context::DxContext;
use super::parallel_encoder::{ParallelCtxRef, SendableCmdList, pool_index};
use super::texture::{aliasing_barrier, transition_barrier, uav_barrier};
use crate::directx::descriptor_slot::SrvSlot;
use crate::directx::line::LineUpload;
struct DxBarrierTarget<'a> {
object: DxTargetObject<'a>,
class: GraphResourceClass,
resting: D3D12_RESOURCE_STATES,
}
enum DxTargetObject<'a> {
One(&'a ID3D12Resource),
Set { first: usize, count: usize },
}
struct DxBarrierRegistry<'a> {
targets: Vec<Option<DxBarrierTarget<'a>>>,
resources: Vec<&'a ID3D12Resource>,
}
impl<'a> DxBarrierRegistry<'a> {
fn target(&self, resource_index: usize) -> Option<&DxBarrierTarget<'a>> {
self.targets.get(resource_index)?.as_ref()
}
fn objects<'r>(&'r self, target: &'r DxBarrierTarget<'a>) -> &'r [&'a ID3D12Resource] {
match target.object {
DxTargetObject::One(ref r) => std::slice::from_ref(r),
DxTargetObject::Set { first, count } => &self.resources[first..first + count],
}
}
}
unsafe impl Sync for DxBarrierRegistry<'_> {}
struct DxAliasBarrier<'a> {
pass: usize,
resource_index: usize,
resource: &'a ID3D12Resource,
resting: D3D12_RESOURCE_STATES,
}
struct DxAliasBarriers<'a>(Vec<DxAliasBarrier<'a>>);
unsafe impl Sync for DxAliasBarriers<'_> {}
impl<'a> DxAliasBarriers<'a> {
fn for_pass(&self, pass: usize) -> &[DxAliasBarrier<'a>] {
let start = self.0.partition_point(|a| a.pass < pass);
let end = self.0.partition_point(|a| a.pass <= pass);
&self.0[start..end]
}
}
const DISCARD_STATE: D3D12_RESOURCE_STATES = D3D12_RESOURCE_STATE_RENDER_TARGET;
const BARRIER_BATCH: usize = 16;
struct BarrierBatch<'a> {
cmd: &'a ID3D12GraphicsCommandList,
entries: [D3D12_RESOURCE_BARRIER; BARRIER_BATCH],
len: usize,
}
impl<'a> BarrierBatch<'a> {
fn new(cmd: &'a ID3D12GraphicsCommandList) -> Self {
Self {
cmd,
entries: std::array::from_fn(|_| D3D12_RESOURCE_BARRIER::default()),
len: 0,
}
}
fn push(&mut self, barrier: D3D12_RESOURCE_BARRIER) {
if self.len == BARRIER_BATCH {
self.flush();
}
self.entries[self.len] = barrier;
self.len += 1;
}
fn flush(&mut self) {
if self.len == 0 {
return;
}
unsafe {
self.cmd.ResourceBarrier(&self.entries[..self.len]);
}
self.len = 0;
}
}
fn stage_graph_barrier(
batch: &mut BarrierBatch,
registry: &DxBarrierRegistry,
op: &BarrierOp,
opened_at: Option<D3D12_RESOURCE_STATES>,
) {
let Some(target) = registry.target(op.resource_index()) else {
return;
};
let Some(barrier) = d3d12_barrier(
target.class,
opened_at.unwrap_or(target.resting),
op.source_state(),
op.to_state(),
op.read_stages(),
) else {
return;
};
for resource in registry.objects(target) {
batch.push(match barrier {
DxBarrier::Transition(before, after) => transition_barrier(resource, before, after),
DxBarrier::Uav => uav_barrier(resource),
});
}
}
fn frame_slot(
slots: &[ID3D12Resource],
frame_idx: usize,
resting: D3D12_RESOURCE_STATES,
) -> Option<(&ID3D12Resource, D3D12_RESOURCE_STATES)> {
Some((slots.get(frame_idx)?, resting))
}
fn graph_takes_over(
registry: &DxBarrierRegistry,
pass: &CompiledPass,
resource_index: usize,
) -> bool {
registry.target(resource_index).is_some()
&& pass
.barriers_before
.iter()
.any(|op| op.resource_index() == resource_index)
}
fn emit_pass_prologue(
cmd: &ID3D12GraphicsCommandList,
registry: &DxBarrierRegistry,
alias: &DxAliasBarriers,
idx: usize,
pass: &CompiledPass,
) {
let aliased = alias.for_pass(idx);
let mut batch = BarrierBatch::new(cmd);
for a in aliased {
batch.push(aliasing_barrier(a.resource));
if a.resting != DISCARD_STATE {
batch.push(transition_barrier(a.resource, a.resting, DISCARD_STATE));
}
}
for op in &pass.barriers_before {
if aliased
.iter()
.any(|a| a.resource_index == op.resource_index())
{
continue;
}
stage_graph_barrier(&mut batch, registry, op, None);
}
batch.flush();
if aliased.is_empty() {
return;
}
for a in aliased {
unsafe {
cmd.DiscardResource(a.resource, None);
}
}
for op in &pass.barriers_before {
if aliased
.iter()
.any(|a| a.resource_index == op.resource_index())
{
debug_assert_eq!(
op.source_state(),
render_graph::ResourceState::Undefined,
"a reclaimed transient is transitioned from a state the discard replaced"
);
stage_graph_barrier(&mut batch, registry, op, Some(DISCARD_STATE));
}
}
for a in aliased {
if a.resting != DISCARD_STATE && !graph_takes_over(registry, pass, a.resource_index) {
batch.push(transition_barrier(a.resource, DISCARD_STATE, a.resting));
}
}
batch.flush();
}
fn emit_pass_epilogue(
cmd: &ID3D12GraphicsCommandList,
registry: &DxBarrierRegistry,
pass: &CompiledPass,
) {
if pass.barriers_after.is_empty() {
return;
}
let mut batch = BarrierBatch::new(cmd);
for op in &pass.barriers_after {
stage_graph_barrier(&mut batch, registry, op, None);
}
batch.flush();
}
fn emit_graph_restores(
cmd: &ID3D12GraphicsCommandList,
registry: &DxBarrierRegistry,
graph: &CompiledGraph,
) {
let mut batch = BarrierBatch::new(cmd);
for (idx, (state, stages)) in final_states(graph).into_iter().enumerate() {
let Some(target) = registry.target(idx) else {
continue;
};
let Some((before, after)) = d3d12_restore(target.class, target.resting, state, stages)
else {
continue;
};
for resource in registry.objects(target) {
batch.push(transition_barrier(resource, before, after));
}
}
batch.flush();
}
#[cfg(debug_assertions)]
fn debug_assert_graph_drives(graph: &CompiledGraph, registry: &DxBarrierRegistry) {
use super::barrier_translate::d3d12_state;
use concinnity_core::render::render_graph::{ResourceState, barrier_coverage_gaps_for_driven};
let driven: Vec<bool> = registry.targets.iter().map(|t| t.is_some()).collect();
let gaps = barrier_coverage_gaps_for_driven(graph, &driven);
assert!(
gaps.is_empty(),
"render graph (directx): uncovered accesses on graph-driven resources: {}",
gaps.iter()
.map(|g| g.to_string())
.collect::<Vec<_>>()
.join(", ")
);
for pass in &graph.passes {
for op in &pass.barriers_after {
assert_eq!(
(op.source_state(), op.to_state()),
(ResourceState::Write, ResourceState::Read),
"render graph (directx): {:?} records a {:?} -> {:?} on {} after its own work",
pass.id,
op.source_state(),
op.to_state(),
graph.resources[op.resource_index()].label,
);
assert!(
pass.writes
.iter()
.any(|w| w.resource_index() == op.resource_index()),
"render graph (directx): {:?} records a transition on {}, which it does not write",
pass.id,
graph.resources[op.resource_index()].label,
);
}
}
for (idx, (state, stages)) in final_states(graph).into_iter().enumerate() {
let Some(target) = registry.target(idx) else {
continue;
};
if state == ResourceState::Undefined {
continue;
}
let restored = match d3d12_restore(target.class, target.resting, state, stages) {
Some((_, after)) => after,
None => d3d12_state(target.class, state, stages),
};
assert_eq!(
restored.0, target.resting.0,
"render graph (directx): {} rests in {:?} but the frame leaves it in {:?}",
graph.resources[idx].label, target.resting, restored,
);
}
}
#[derive(Clone, Copy)]
pub(in crate::directx) struct CompositeRenderTarget<'a> {
pub back_buffer: &'a ID3D12Resource,
pub back_buffer_rtv: D3D12_CPU_DESCRIPTOR_HANDLE,
}
#[derive(Clone, Copy)]
pub(in crate::directx) struct CompositeResolution {
pub width: u32,
pub height: u32,
}
#[derive(Clone, Copy)]
pub(in crate::directx) struct MainPassExtent {
pub width: u32,
pub height: u32,
}
#[derive(Clone, Copy)]
pub(in crate::directx) struct FrameGpuBuffers {
pub view_gva: u64,
pub light_gva: u64,
pub local_lights_gva: u64,
pub shadow_ubo_gva: u64,
}
pub(in crate::directx) struct GraphFrameParams<'a> {
pub cmd: &'a ID3D12GraphicsCommandList,
pub frame_idx: usize,
pub back_buffer: &'a ID3D12Resource,
pub back_buffer_rtv: D3D12_CPU_DESCRIPTOR_HANDLE,
pub text_calls: &'a [TextDrawCall],
pub lines: Option<LineUpload>,
pub world_hidden: bool,
pub scene_srv: SrvSlot,
pub width: u32,
pub height: u32,
pub output_width: u32,
pub output_height: u32,
pub cam_pos: [f32; 3],
pub shadow_ubo_gva: u64,
pub view_gva: u64,
pub light_gva: u64,
pub local_lights_gva: u64,
pub vp_mat: [[f32; 4]; 4],
pub cur_vp: [[f32; 4]; 4],
pub frustum: &'a Frustum,
pub fov_y_radians: f32,
pub aspect: f32,
pub elapsed: f32,
pub near: f32,
pub prime_model_history: bool,
pub planar: PlanarFramePlan,
pub cluster_params: ClusterParams,
pub reactive: concinnity_core::render::reactive_mask::ReactiveMaskPlan,
}
impl DxContext {
pub(in crate::directx) fn execute_graph(
&self,
graph: &CompiledGraph,
params: &GraphFrameParams<'_>,
) -> RenderResult<Vec<ID3D12GraphicsCommandList>> {
let particle_frame = self.prepare_particle_pass(params.frame_idx, params.elapsed);
let particle_ref = particle_frame.as_ref();
let composite_idx = graph.passes.iter().position(|p| p.id == PassId::Composite);
let worker_slots: Mutex<Vec<Option<SendableCmdList>>> =
Mutex::new((0..graph.passes.len()).map(|_| None).collect());
let first_error: Mutex<Option<RenderError>> = Mutex::new(None);
let ctx_ref = ParallelCtxRef::new(self);
let registry = self.build_barrier_registry(graph, params.frame_idx);
#[cfg(debug_assertions)]
debug_assert_graph_drives(graph, ®istry);
#[cfg(debug_assertions)]
render_graph::assert_slot_aliasing_sound(
graph,
self.targets.transient_pool.slot_labels(),
"directx",
);
#[cfg(debug_assertions)]
render_graph::assert_serial_order_honors_schedule(graph, "directx");
let registry_ref = ®istry;
let alias_barriers = self.build_alias_barriers(graph, ®istry);
let alias_barriers_ref = &alias_barriers;
let frame_idx = params.frame_idx;
jobs::pool().install(|| {
rayon::scope(|scope| {
for (idx, pass) in graph.passes.iter().enumerate() {
if Some(idx) == composite_idx {
continue;
}
let pass_id = pass.id;
let first_error_ref = &first_error;
let worker_slots_ref = &worker_slots;
scope.spawn(move |_| {
let ctx = ctx_ref.as_ctx();
let pool_idx = pool_index(frame_idx, pass_id);
let alloc = &ctx.commands.pass_allocators[pool_idx];
let cmd = &ctx.commands.pass_cmd_lists[pool_idx];
if let Err(e) = unsafe { alloc.Reset() } {
let mut lock = first_error_ref.lock().unwrap();
if lock.is_none() {
*lock = Some(super::error::map_hresult(
e.code(),
&format!("per-pass allocator reset ({})", pass_id.name()),
));
}
return;
}
if let Err(e) = unsafe { cmd.Reset(alloc, None) } {
let mut lock = first_error_ref.lock().unwrap();
if lock.is_none() {
*lock = Some(super::error::map_hresult(
e.code(),
&format!("per-pass cmd list reset ({})", pass_id.name()),
));
}
return;
}
if let Some(heap) = ctx.timestamps.query_heap.as_ref() {
let (start_slot, _) = pass_timing::pass_pair(frame_idx, pass_id);
unsafe {
cmd.EndQuery(heap, D3D12_QUERY_TYPE_TIMESTAMP, start_slot);
}
}
emit_pass_prologue(cmd, registry_ref, alias_barriers_ref, idx, pass);
let encode_result =
ctx.encode_pass_into(pass_id, cmd, params, particle_ref);
if encode_result.is_ok() {
emit_pass_epilogue(cmd, registry_ref, pass);
}
if let Some(heap) = ctx.timestamps.query_heap.as_ref() {
let (_, end_slot) = pass_timing::pass_pair(frame_idx, pass_id);
unsafe {
cmd.EndQuery(heap, D3D12_QUERY_TYPE_TIMESTAMP, end_slot);
}
}
if let Err(e) = unsafe { cmd.Close() } {
let mut lock = first_error_ref.lock().unwrap();
if lock.is_none() {
*lock = Some(super::error::map_hresult(
e.code(),
&format!("per-pass cmd list close ({})", pass_id.name()),
));
}
return;
}
match encode_result {
Ok(()) => {
let mut lock = worker_slots_ref.lock().unwrap();
lock[idx] = Some(SendableCmdList(cmd.clone()));
}
Err(e) => {
let mut lock = first_error_ref.lock().unwrap();
if lock.is_none() {
*lock = Some(e);
}
}
}
});
}
});
});
if let Some(err) = first_error.into_inner().unwrap_or(None) {
return Err(err);
}
if let Some(idx) = composite_idx {
if let Some(heap) = self.timestamps.query_heap.as_ref() {
let (start_slot, _) = pass_timing::pass_pair(frame_idx, PassId::Composite);
unsafe {
params
.cmd
.EndQuery(heap, D3D12_QUERY_TYPE_TIMESTAMP, start_slot);
}
}
emit_pass_prologue(
params.cmd,
®istry,
&alias_barriers,
idx,
&graph.passes[idx],
);
self.encode_composite_and_text(
params.cmd,
params.frame_idx,
CompositeRenderTarget {
back_buffer: params.back_buffer,
back_buffer_rtv: params.back_buffer_rtv,
},
params.text_calls,
crate::directx::draw::composite::CompositeSources {
scene_srv: params.scene_srv,
reactive_valid: params.reactive.readable,
},
CompositeResolution {
width: params.output_width,
height: params.output_height,
},
)?;
emit_pass_epilogue(params.cmd, ®istry, &graph.passes[idx]);
if let Some(heap) = self.timestamps.query_heap.as_ref() {
let (_, end_slot) = pass_timing::pass_pair(frame_idx, PassId::Composite);
unsafe {
params
.cmd
.EndQuery(heap, D3D12_QUERY_TYPE_TIMESTAMP, end_slot);
}
}
}
emit_graph_restores(params.cmd, ®istry, graph);
let slots = worker_slots.into_inner().map_err(|_| {
RenderError::Other("graph executor (directx): worker slot mutex poisoned".into())
})?;
let mut ordered = Vec::with_capacity(graph.passes.len());
for cb in slots.into_iter().flatten() {
ordered.push(cb.0);
}
Ok(ordered)
}
fn build_barrier_registry(
&self,
graph: &CompiledGraph,
frame_idx: usize,
) -> DxBarrierRegistry<'_> {
let mut registry = DxBarrierRegistry {
targets: Vec::with_capacity(graph.resources.len()),
resources: Vec::new(),
};
for res in &graph.resources {
let target = res.class().and_then(|class| {
let (object, resting) =
self.barrier_objects_for_label(res.label, frame_idx, &mut registry.resources)?;
Some(DxBarrierTarget {
object,
class,
resting,
})
});
registry.targets.push(target);
}
registry
}
fn build_alias_barriers<'a>(
&'a self,
graph: &CompiledGraph,
registry: &DxBarrierRegistry<'a>,
) -> DxAliasBarriers<'a> {
let mut table = Vec::new();
for (idx, res) in graph.resources.iter().enumerate() {
if self
.targets
.transient_pool
.alias_predecessor(res.label)
.is_none()
{
continue;
}
if let Some(r) = self.targets.transient_pool.resource_for(res.label)
&& res.lifetime.first < graph.passes.len()
{
table.push(DxAliasBarrier {
pass: res.lifetime.first,
resource_index: idx,
resource: r,
resting: match registry.target(idx) {
Some(target) => target.resting,
None => D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
},
});
}
}
table.sort_by_key(|a| a.pass);
DxAliasBarriers(table)
}
fn barrier_objects_for_label<'a>(
&'a self,
label: &str,
frame_idx: usize,
arena: &mut Vec<&'a ID3D12Resource>,
) -> Option<(DxTargetObject<'a>, D3D12_RESOURCE_STATES)> {
const SAMPLED: D3D12_RESOURCE_STATES = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
if label == "particle_pool" {
let first = arena.len();
arena.extend(
self.particle
.emitter_state
.iter()
.flatten()
.map(|gpu| &gpu.pool),
);
let count = arena.len() - first;
return (count > 0).then_some((
DxTargetObject::Set { first, count },
D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
));
}
let one = |r: Option<(&'a ID3D12Resource, D3D12_RESOURCE_STATES)>| {
r.map(|(resource, resting)| (DxTargetObject::One(resource), resting))
};
one(match label {
"draw_args" => frame_slot(
&self.cull.indirect_cmd_buffers,
frame_idx,
D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT,
),
"draw_args2" => frame_slot(
&self.cull.indirect_cmd_buffers_2,
frame_idx,
D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT,
),
"cull_status" => frame_slot(
&self.cull.cull_status_buffers,
frame_idx,
D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
),
"cluster_lists" => Some((&self.light_cull.cluster_buffer, SAMPLED)),
"ao_output" => self
.targets
.transient_pool
.resource_for("ao_output")
.map(|r| (r, SAMPLED)),
"bloom_top" => self
.targets
.transient_pool
.resource_for("bloom_top")
.map(|r| (r, SAMPLED)),
"shadow_map" => self
.shadow
.resource
.as_ref()
.filter(|_| !self.shadow.dsvs.is_empty())
.map(|s| (&s.resource, SAMPLED)),
"spot_shadow_map" => self
.spot_shadow
.resource
.as_ref()
.filter(|_| !self.spot_shadow.dsvs.is_empty())
.map(|s| (&s.resource, SAMPLED)),
"fog_froxel_volume" => self
.fog
.resources
.as_ref()
.map(|f| (&f.volume_resource, SAMPLED)),
"hdr_depth" => Some((
&self.targets.depth.resource,
D3D12_RESOURCE_STATE_DEPTH_WRITE,
)),
"hdr_color" => self
.targets
.hdr
.resolve
.is_some()
.then_some((&self.targets.hdr.color, D3D12_RESOURCE_STATE_RENDER_TARGET)),
"hdr_resolve" => Some(match &self.targets.hdr.resolve {
Some(resolve) => (resolve, SAMPLED),
None => (&self.targets.hdr.color, D3D12_RESOURCE_STATE_RENDER_TARGET),
}),
"scene_pre_taa" => self
.reflection_composite
.as_ref()
.map(|rc| (rc.output().resource(), SAMPLED)),
"scene_color" => self
.taa
.as_ref()
.filter(|_| self.upscale.backend.is_none())
.map(|taa| (&taa.output().resource, SAMPLED)),
"gbuffer_normal_depth" => self.gbuffer.as_ref().map(|gb| (&gb.normal_depth, SAMPLED)),
"gbuffer_roughness" => self.gbuffer.as_ref().map(|gb| (&gb.roughness, SAMPLED)),
"gbuffer_velocity" => self.gbuffer.as_ref().map(|gb| (&gb.velocity, SAMPLED)),
"reactive_mask" => Some((&self.targets.reactive_mask.resource, SAMPLED)),
"hiz_pyramid" => self.cull.hiz.as_ref().map(|h| (&h.texture, h.rest_state)),
_ => None,
})
}
fn build_raymarch_view(&self, params: &GraphFrameParams<'_>) -> super::raymarch::RaymarchView {
super::raymarch::RaymarchView::new(&self.pass_camera(params, params.vp_mat))
}
fn build_transparent_view(
&self,
params: &GraphFrameParams<'_>,
) -> super::transparent::TransparentView {
super::transparent::TransparentView::new(
&self.pass_camera(params, params.vp_mat),
&self.uniforms.light_uniforms,
)
}
fn pass_camera(&self, params: &GraphFrameParams<'_>, vp: [[f32; 4]; 4]) -> PassCamera {
PassCamera {
vp,
inv_vp: mat4_inverse(vp),
cam_pos: params.cam_pos,
viewport: [params.width as f32, params.height as f32],
time: params.elapsed,
prefilter_mip_count: self.scene.env_map.prefilter_mip_count as f32,
sky_rot: self.state.view.sky_rot,
}
}
fn encode_pass_into(
&self,
pass_id: PassId,
cmd: &ID3D12GraphicsCommandList,
params: &GraphFrameParams<'_>,
particle_frame: Option<&super::particle::ParticleFrame>,
) -> RenderResult<()> {
match pass_id {
PassId::Cull => {
self.encode_cull(cmd, params.frame_idx, params.frustum, params.cam_pos);
self.encode_skin(cmd, params.frame_idx);
}
PassId::SsaoBlur => {
self.encode_ssao(cmd, params.frame_idx, params.fov_y_radians, params.aspect)?;
}
PassId::SsaoDepth | PassId::SsaoKernel => {
return Err(RenderError::Other(format!(
"graph executor (directx): pass {} is bundled inside SsaoBlur \
(encode_ssao encodes the SSAO depth copy, kernel and blur); it \
should not appear as its own graph node",
pass_id.name()
)));
}
PassId::Sky => {
return Err(RenderError::Other(format!(
"graph executor (directx): pass {} is drawn inline by the opaque \
scene passes and should not appear as a graph node",
pass_id.name()
)));
}
PassId::ReflectionComposite => {
return Err(RenderError::Other(format!(
"graph executor (directx): pass {} is a Metal-only inline \
reflection composite and should not appear as a graph node",
pass_id.name()
)));
}
PassId::LightCull => {
self.encode_light_cull(
cmd,
params.frame_idx,
self.main_cluster_grid(params.frame_idx),
);
}
PassId::Shadow => {
let raymarch_view = self
.raymarch
.as_ref()
.filter(|rm| rm.any_shadow_casters())
.map(|_| self.build_raymarch_view(params));
self.encode_shadow_pass(
cmd,
params.frame_idx,
params.shadow_ubo_gva,
params.cam_pos,
raymarch_view.as_ref(),
);
}
PassId::SpotShadow => {
self.encode_spot_shadow_pass(cmd, params.frame_idx, params.cam_pos);
}
PassId::AutoExposure => {
self.encode_auto_exposure(cmd, params.frame_idx);
}
PassId::Main => {
self.encode_main_pass(
cmd,
params.frame_idx,
MainPassExtent {
width: params.width,
height: params.height,
},
FrameGpuBuffers {
view_gva: params.view_gva,
light_gva: params.light_gva,
local_lights_gva: params.local_lights_gva,
shadow_ubo_gva: params.shadow_ubo_gva,
},
params.world_hidden,
);
}
PassId::Decals => {
self.encode_decals(cmd, params.frame_idx, params.vp_mat, params.frustum);
}
PassId::Lines => {
self.encode_lines(cmd, params.frame_idx, params.vp_mat, params.lines);
}
PassId::Fog => {
self.encode_fog(cmd, params.frame_idx, params.vp_mat, params.cam_pos);
}
PassId::ParticlesSim => {
if let Some(frame) = particle_frame {
self.encode_particles_sim(cmd, frame);
}
}
PassId::ParticlesDraw => {
self.targets
.reactive_mask
.clear(cmd, params.reactive.particles);
if let Some(frame) = particle_frame {
self.encode_particles_draw(
cmd,
params.frame_idx,
frame,
params.vp_mat,
params.frustum,
params.reactive.particles,
);
}
}
PassId::SsrResolve => {
self.encode_ssr_resolve(
cmd,
params.frame_idx,
params.fov_y_radians,
params.aspect,
params.cam_pos,
);
}
PassId::TaaResolve => {
self.encode_taa(cmd, params.frame_idx, params.reactive.readable);
}
PassId::Bloom => {
self.encode_bloom(cmd, params.frame_idx, params.scene_srv)?;
}
PassId::Composite => {
return Err(RenderError::Other(
"graph executor (directx): Composite must run on the outer cmd \
list: encode_pass_into is not the right entry point"
.to_string(),
));
}
PassId::Raymarch => {
let view = self.build_raymarch_view(params);
self.encode_raymarch(cmd, params.frame_idx, &view)?;
}
PassId::FogFroxel => {
self.encode_fog_froxel(
cmd,
params.frame_idx,
params.near,
params.vp_mat,
params.cam_pos,
params.shadow_ubo_gva,
);
}
PassId::Upscale => {
self.encode_upscale(cmd, params)?;
}
PassId::PlanarReflection => {
self.encode_planar_reflections(cmd, params)?;
}
PassId::Transparent => {
self.targets
.reactive_mask
.clear(cmd, params.reactive.transparent);
let view = self.build_transparent_view(params);
self.encode_transparent(
cmd,
params.frame_idx,
&view,
params.fov_y_radians,
params.aspect,
params.reactive.transparent,
)?;
}
PassId::HizBuild | PassId::HizFinal => {
self.encode_hiz_build(cmd);
}
PassId::Cull2 => {
self.encode_cull_phase2(cmd, params.frame_idx, params.frustum, params.cur_vp);
}
PassId::Main2 => {
self.encode_main_pass_phase2(
cmd,
params.frame_idx,
params.width,
params.height,
FrameGpuBuffers {
view_gva: params.view_gva,
light_gva: params.light_gva,
local_lights_gva: params.local_lights_gva,
shadow_ubo_gva: params.shadow_ubo_gva,
},
);
}
PassId::Ssgi => {
self.encode_ssgi(cmd, params.frame_idx, params.fov_y_radians, params.aspect);
}
PassId::RtReflections => {
self.encode_rt_reflections(
cmd,
params.frame_idx,
params.fov_y_radians,
params.aspect,
params.cam_pos,
);
}
PassId::GBufferPrepass => {
self.encode_gbuffer_prepass(
cmd,
params.frame_idx,
crate::directx::post::gbuffer::GbufferPrepassView {
jittered_vp: params.vp_mat,
cur_vp: params.cur_vp,
elapsed: params.elapsed,
cam_pos: params.cam_pos,
},
crate::directx::post::gbuffer::GbufferPrepassFrame {
velocity_active: self.reads_motion(),
prime_history: params.prime_model_history,
},
);
}
}
Ok(())
}
}