use concinnity_core::components::{GlassPanel, WaterSurface};
use concinnity_core::gfx::mesh_payload::Vertex;
use concinnity_core::gfx::render_types::RtParams;
use concinnity_core::render::depth::DEPTH_CLEAR;
use concinnity_core::render::error::RenderResult;
use concinnity_core::render::post::rt_reflections::RtParamsInputs;
use concinnity_core::render::reactive_mask::ReactiveWrite;
use windows::Win32::Foundation::RECT;
use windows::Win32::Graphics::Direct3D::D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
use windows::Win32::Graphics::Direct3D12::*;
use windows::Win32::Graphics::Dxgi::Common::*;
use super::allocator::{DeviceAllocator, PooledBuffer};
use super::com;
use crate::directx::context::{DxContext, FRAMES, align256, dump_on_err};
use crate::directx::descriptor_slot::DescriptorTables;
use crate::directx::descriptor_slot::SrvSlot;
use crate::directx::error::map_hresult;
use crate::directx::init::heap_layout::GLASS_REFLECTION_SRV_SLOTS;
use crate::directx::pipeline::main_input_layout;
use crate::directx::pso::{Blend, Depth, GraphicsPso, Raster};
use crate::directx::root_sig::{Range, RootSig, SamplerState, Visibility};
use crate::directx::texture::{
HDR_FORMAT, create_hdr_resolve_target, create_main_depth_texture, create_rt_target,
transition_barrier, upload_buffer, write_format_rtv,
};
const RT_PARAMS_UBO_SIZE: u64 = 144;
use concinnity_core::render::transparent::SeeThroughMesh;
use concinnity_core::render::uniforms::GlassMeshParams;
pub(in crate::directx) use concinnity_core::render::uniforms::TransparentView;
pub(in crate::directx) use concinnity_core::render::transparent::Producer;
pub(in crate::directx) struct TransparentRecord {
#[expect(
dead_code,
reason = "held to keep the GPU memory alive; the encoder binds through vertex_buffer_view"
)]
vertex_buffer: PooledBuffer,
vertex_buffer_view: D3D12_VERTEX_BUFFER_VIEW,
#[expect(
dead_code,
reason = "held to keep the GPU memory alive; the encoder binds through index_buffer_view"
)]
index_buffer: PooledBuffer,
index_buffer_view: D3D12_INDEX_BUFFER_VIEW,
index_count: u32,
#[expect(
dead_code,
reason = "held to keep the GPU memory alive; the encoder binds through params_cbuffer_gva"
)]
params_cbuffer: PooledBuffer,
params_cbuffer_gva: u64,
visible: bool,
center: [f32; 3],
planar_slot: Option<usize>,
}
pub(in crate::directx) struct RecordUpload<'a> {
pub vertices: &'a [Vertex],
pub indices: &'a [u16],
pub params: &'a [u8],
pub visible: bool,
pub center: [f32; 3],
pub planar_slot: Option<usize>,
}
impl TransparentRecord {
pub(in crate::directx) fn upload(
alloc: &DeviceAllocator,
upload: RecordUpload<'_>,
) -> RenderResult<Self> {
let vbytes = bytemuck::cast_slice(upload.vertices);
let ibytes = bytemuck::cast_slice(upload.indices);
let vertex_buffer = upload_buffer(
alloc,
vbytes,
D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER,
)?;
let index_buffer = upload_buffer(alloc, ibytes, D3D12_RESOURCE_STATE_INDEX_BUFFER)?;
let vertex_buffer_view = D3D12_VERTEX_BUFFER_VIEW {
BufferLocation: com::gpu_va(&vertex_buffer),
SizeInBytes: vbytes.len() as u32,
StrideInBytes: std::mem::size_of::<Vertex>() as u32,
};
let index_buffer_view = D3D12_INDEX_BUFFER_VIEW {
BufferLocation: com::gpu_va(&index_buffer),
SizeInBytes: ibytes.len() as u32,
Format: DXGI_FORMAT_R16_UINT,
};
let params_cbuffer = alloc.alloc_buffer(
align256(upload.params.len() as u64),
D3D12_HEAP_TYPE_UPLOAD,
D3D12_RESOURCE_STATE_GENERIC_READ,
)?;
let mut p = std::ptr::null_mut::<std::ffi::c_void>();
unsafe { params_cbuffer.Map(0, None, Some(&mut p)) }
.map_err(|e| map_hresult(e.code(), "map transparent params cb"))?;
unsafe {
std::ptr::copy_nonoverlapping(upload.params.as_ptr(), p as *mut u8, upload.params.len())
};
let params_cbuffer_gva = com::gpu_va(¶ms_cbuffer);
Ok(Self {
vertex_buffer,
vertex_buffer_view,
index_buffer,
index_buffer_view,
index_count: upload.indices.len() as u32,
params_cbuffer,
params_cbuffer_gva,
visible: upload.visible,
center: upload.center,
planar_slot: upload.planar_slot,
})
}
}
pub(in crate::directx) struct TransparentProducer {
pub pso: ID3D12PipelineState,
pub flat_rt_pso: Option<ID3D12PipelineState>,
pub textured_rt_pso: Option<ID3D12PipelineState>,
pub reflection_flat_pso: Option<ID3D12PipelineState>,
pub reflection_textured_pso: Option<ID3D12PipelineState>,
pub records: Vec<TransparentRecord>,
}
impl TransparentProducer {
fn pipeline(&self, rt_live: bool, textured: bool) -> &ID3D12PipelineState {
match (rt_live, textured) {
(true, true) => self
.textured_rt_pso
.as_ref()
.expect("rt_textured_ready gated the frame on every producer's textured PSO"),
(true, false) => self
.flat_rt_pso
.as_ref()
.expect("rt_pipelines_ready gated the frame on every producer's flat RT PSO"),
_ => &self.pso,
}
}
fn reflection_pipeline(&self, textured: bool) -> Option<&ID3D12PipelineState> {
match textured {
true => self.reflection_textured_pso.as_ref(),
false => self.reflection_flat_pso.as_ref(),
}
}
}
pub(in crate::directx) struct TracedGlassPsos {
pub shade_flat: ID3D12PipelineState,
pub shade_textured: ID3D12PipelineState,
pub reflection_flat: ID3D12PipelineState,
pub reflection_textured: ID3D12PipelineState,
}
pub(in crate::directx) struct GlassMeshProducer {
flat_rt_pso: ID3D12PipelineState,
textured_rt_pso: Option<ID3D12PipelineState>,
reflection_flat_pso: ID3D12PipelineState,
reflection_textured_pso: ID3D12PipelineState,
object_indices: Vec<usize>,
params_ring: Vec<PooledBuffer>,
params_ptrs: Vec<*mut u8>,
}
struct GlassMeshDraw {
index_offset: u32,
index_count: u32,
base_vertex: i32,
params_gva: u64,
center: [f32; 3],
}
impl GlassMeshProducer {
pub(in crate::directx) fn new(
alloc: &DeviceAllocator,
psos: TracedGlassPsos,
object_indices: Vec<usize>,
) -> RenderResult<Self> {
let block = align256(std::mem::size_of::<GlassMeshParams>() as u64);
let ring_size = block * object_indices.len().max(1) as u64;
let mut params_ring: Vec<PooledBuffer> = Vec::with_capacity(FRAMES);
let mut params_ptrs: Vec<*mut u8> = Vec::with_capacity(FRAMES);
for _ in 0..FRAMES {
let buf = alloc.alloc_buffer(
ring_size,
D3D12_HEAP_TYPE_UPLOAD,
D3D12_RESOURCE_STATE_GENERIC_READ,
)?;
let mut ptr = std::ptr::null_mut::<std::ffi::c_void>();
unsafe { buf.Map(0, None, Some(&mut ptr)) }
.map_err(|e| map_hresult(e.code(), "map glass mesh params ring"))?;
params_ptrs.push(ptr as *mut u8);
params_ring.push(buf);
}
Ok(Self {
flat_rt_pso: psos.shade_flat,
textured_rt_pso: Some(psos.shade_textured),
reflection_flat_pso: psos.reflection_flat,
reflection_textured_pso: psos.reflection_textured,
object_indices,
params_ring,
params_ptrs,
})
}
fn pipeline(&self, textured: bool) -> &ID3D12PipelineState {
match textured {
true => self
.textured_rt_pso
.as_ref()
.expect("rt_textured_ready gated the frame on every producer's textured PSO"),
false => &self.flat_rt_pso,
}
}
fn reflection_pipeline(&self, textured: bool) -> &ID3D12PipelineState {
match textured {
true => &self.reflection_textured_pso,
false => &self.reflection_flat_pso,
}
}
}
pub(in crate::directx) struct TransparentResources {
root_sig: ID3D12RootSignature,
glass: Option<TransparentProducer>,
water: Option<TransparentProducer>,
glass_mesh: Option<GlassMeshProducer>,
view_ubo_resources: Vec<PooledBuffer>,
view_ubo_ptrs: Vec<*mut u8>,
scene_copy: ID3D12Resource,
scene_copy_srv_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
scene_copy_srv_gpu: SrvSlot,
depth_srv_gpu: SrvSlot,
rt_root_sig: Option<ID3D12RootSignature>,
rt_params_ubo_resources: Vec<PooledBuffer>,
rt_params_ubo_ptrs: Vec<*mut u8>,
reflection_slots: GlassReflectionSlots,
reflection: Option<GlassReflectionLayers>,
reflection_divisor: u32,
}
unsafe impl Send for TransparentResources {}
unsafe impl Sync for TransparentResources {}
use concinnity_core::render::transparent::ordered_visible;
const PLANAR_ROOT_BASE: u32 = 7;
const PLANAR_ROOT_RT: u32 = 15;
const GLASS_REFLECTION_ROOT_RT: u32 = 16;
const PROBE_RECORDS_ROOT_BASE: u32 = 8;
const PROBE_RECORDS_ROOT_RT: u32 = 17;
fn create_transparent_root_signature(device: &ID3D12Device) -> RenderResult<ID3D12RootSignature> {
use Visibility::{All, Pixel};
let sig = RootSig::new()
.cbv(0, All) .cbv(1, All) .srv_table(0, 1, Pixel) .srv_table(1, 1, Pixel) .srv_table(2, 1, Pixel) .srv_table(20, 1, Pixel) .cbv(4, Pixel) .srv_table(3, 1, Pixel)
.srv(21, Pixel) .cbv(6, Pixel) .srv(22, Pixel) .static_sampler(SamplerState::LinearClamp, 0, Pixel)
.static_sampler(SamplerState::LinearClamp, 2, Pixel)
.input_layout();
debug_assert_eq!(sig.len() - 4, PLANAR_ROOT_BASE);
debug_assert_eq!(sig.len() - 3, PROBE_RECORDS_ROOT_BASE);
sig.build(device, "transparent root sig")
}
pub(in crate::directx) fn create_transparent_pso(
device: &ID3D12Device,
root_sig: &ID3D12RootSignature,
vs: &[u8],
ps: &[u8],
) -> RenderResult<ID3D12PipelineState> {
transparent_pso(device, root_sig, (vs, ps), TransparentOutput::Scene)
}
pub(in crate::directx) fn create_glass_reflection_pso(
device: &ID3D12Device,
root_sig: &ID3D12RootSignature,
vs: &[u8],
ps: &[u8],
) -> RenderResult<ID3D12PipelineState> {
transparent_pso(
device,
root_sig,
(vs, ps),
TransparentOutput::ReflectionLayer,
)
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum TransparentOutput {
Scene,
ReflectionLayer,
}
fn transparent_pso(
device: &ID3D12Device,
root_sig: &ID3D12RootSignature,
(vs, ps): (&[u8], &[u8]),
output: TransparentOutput,
) -> RenderResult<ID3D12PipelineState> {
let (blend, depth_format, depth) = match output {
TransparentOutput::Scene => (Blend::AlphaOver, DXGI_FORMAT_UNKNOWN, Depth::Off),
TransparentOutput::ReflectionLayer => {
(Blend::Opaque, DXGI_FORMAT_D32_FLOAT, Depth::write())
}
};
let layout = main_input_layout();
let pso = GraphicsPso::new(root_sig, vs, ps)
.input_layout(&layout)
.target(HDR_FORMAT, blend);
let pso = match output {
TransparentOutput::Scene => crate::directx::reactive_mask::mask_target(pso),
TransparentOutput::ReflectionLayer => pso,
};
pso.depth(depth_format, depth)
.raster(Raster {
depth_clip: false,
..Raster::default()
})
.build(device, "transparent")
}
fn create_transparent_rt_root_signature(
device: &ID3D12Device,
) -> RenderResult<ID3D12RootSignature> {
use Visibility::{All, Pixel};
let sig = RootSig::new()
.cbv(0, All) .cbv(1, All) .srv_table(0, 1, Pixel) .srv_table(1, 1, Pixel) .srv_table(2, 1, Pixel) .srv_table(20, 1, Pixel) .cbv(4, Pixel) .cbv(5, Pixel) .srv(4, Pixel) .srv(5, Pixel) .srv(6, Pixel) .srv(10, Pixel) .srv(8, Pixel) .srv(9, Pixel) .table(&[Range::bindless_srv(1)], Pixel) .srv_table(3, 1, Pixel) .srv_table(11, 2, Pixel) .srv(21, Pixel) .cbv(6, Pixel) .srv(22, Pixel) .static_sampler(SamplerState::LinearClamp, 0, Pixel) .static_sampler(SamplerState::LinearWrap, 1, Pixel) .static_sampler(SamplerState::LinearClamp, 2, Pixel) .input_layout();
debug_assert_eq!(sig.len() - 5, PLANAR_ROOT_RT);
debug_assert_eq!(sig.len() - 4, GLASS_REFLECTION_ROOT_RT);
debug_assert_eq!(sig.len() - 3, PROBE_RECORDS_ROOT_RT);
sig.build(device, "transparent rt root sig")
}
type RtParamsRing = (Vec<PooledBuffer>, Vec<*mut u8>);
fn build_rt_params_ring(alloc: &DeviceAllocator) -> RenderResult<RtParamsRing> {
let params_size = align256(RT_PARAMS_UBO_SIZE);
let mut resources: Vec<PooledBuffer> = Vec::with_capacity(FRAMES);
let mut ptrs: Vec<*mut u8> = Vec::with_capacity(FRAMES);
for _ in 0..FRAMES {
let buf = alloc.alloc_buffer(
params_size,
D3D12_HEAP_TYPE_UPLOAD,
D3D12_RESOURCE_STATE_GENERIC_READ,
)?;
let mut ptr = std::ptr::null_mut::<std::ffi::c_void>();
unsafe { buf.Map(0, None, Some(&mut ptr)) }
.map_err(|e| map_hresult(e.code(), "map transparent rt params ubo"))?;
ptrs.push(ptr as *mut u8);
resources.push(buf);
}
Ok((resources, ptrs))
}
fn write_scene_copy_srv(
device: &ID3D12Device,
scene_copy: &ID3D12Resource,
srv_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
) {
let desc = D3D12_SHADER_RESOURCE_VIEW_DESC {
Format: HDR_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.CreateShaderResourceView(scene_copy, Some(&desc), srv_cpu) };
}
#[derive(Clone, Copy)]
pub(in crate::directx) struct GlassReflectionSlots {
pub rtv: [D3D12_CPU_DESCRIPTOR_HANDLE; 2],
pub dsv: D3D12_CPU_DESCRIPTOR_HANDLE,
pub srv_cpu: [D3D12_CPU_DESCRIPTOR_HANDLE; GLASS_REFLECTION_SRV_SLOTS],
pub windows: [SrvSlot; 3],
}
struct GlassReflectionLayers {
layers: [ID3D12Resource; 2],
_depth: ID3D12Resource,
extent: (u32, u32),
}
impl GlassReflectionLayers {
fn new(
device: &ID3D12Device,
slots: &GlassReflectionSlots,
extent: (u32, u32),
) -> RenderResult<Self> {
let (w, h) = extent;
let layers = [
create_rt_target(device, w, h, HDR_FORMAT)?,
create_rt_target(device, w, h, HDR_FORMAT)?,
];
for (layer, rtv) in layers.iter().zip(slots.rtv) {
write_format_rtv(device, layer, rtv, HDR_FORMAT);
}
let depth = create_main_depth_texture(device, w, h, slots.dsv, 1, false)?;
Ok(Self {
layers,
_depth: depth,
extent,
})
}
}
fn write_reflection_windows(
device: &ID3D12Device,
slots: &GlassReflectionSlots,
layers: Option<&GlassReflectionLayers>,
) {
let [front, back] = layers.map_or([None, None], |l| [Some(&l.layers[0]), Some(&l.layers[1])]);
let contents = [None, None, front, None, front, back];
let desc = D3D12_SHADER_RESOURCE_VIEW_DESC {
Format: HDR_FORMAT,
ViewDimension: D3D12_SRV_DIMENSION_TEXTURE2D,
Shader4ComponentMapping: D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
Anonymous: D3D12_SHADER_RESOURCE_VIEW_DESC_0 {
Texture2D: D3D12_TEX2D_SRV {
MipLevels: 1,
..Default::default()
},
},
};
for (resource, cpu) in contents.into_iter().zip(slots.srv_cpu) {
unsafe { device.CreateShaderResourceView(resource, Some(&desc), cpu) };
}
}
#[derive(Clone, Copy)]
pub(in crate::directx) struct TransparentDeviceCtx<'a> {
pub alloc: &'a DeviceAllocator,
}
#[derive(Clone, Copy)]
pub(in crate::directx) struct TransparentBuildConfig {
pub msaa_samples: u32,
pub width: u32,
pub height: u32,
pub hot_reload: bool,
pub reflection_divisor: u32,
}
#[derive(Clone, Copy)]
pub(in crate::directx) struct TransparentSceneTargets {
pub scene_copy_srv_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
pub scene_copy_srv_gpu: SrvSlot,
pub depth_srv_gpu: SrvSlot,
pub reflection_slots: GlassReflectionSlots,
}
#[derive(Clone, Copy)]
pub(in crate::directx) struct TransparentContent<'a> {
pub glass_panels: &'a [GlassPanel],
pub glass_planar_slots: &'a [Option<usize>],
pub water_surfaces: &'a [WaterSurface],
pub water_planar_slots: &'a [Option<usize>],
pub seethrough_mesh_indices: &'a [usize],
}
impl TransparentResources {
pub(in crate::directx) fn new(
device_ctx: TransparentDeviceCtx,
config: TransparentBuildConfig,
scene: TransparentSceneTargets,
content: TransparentContent,
info_queue: Option<&ID3D12InfoQueue>,
) -> RenderResult<Self> {
let TransparentDeviceCtx { alloc } = device_ctx;
let device = alloc.device();
let TransparentBuildConfig {
msaa_samples,
width,
height,
hot_reload,
reflection_divisor,
} = config;
let TransparentSceneTargets {
scene_copy_srv_cpu,
scene_copy_srv_gpu,
depth_srv_gpu,
reflection_slots,
} = scene;
let root_sig = dump_on_err(info_queue, create_transparent_root_signature(device))?;
let rt = if crate::directx::raytrace::raytracing_supported(device) {
match dump_on_err(info_queue, create_transparent_rt_root_signature(device))
.and_then(|sig| build_rt_params_ring(alloc).map(|ring| (sig, ring)))
{
Ok((sig, ring)) => Some((sig, ring)),
Err(e) => {
tracing::warn!(
"transparent RT reflection setup failed ({e}); \
using the probe/planar path"
);
None
}
}
} else {
None
};
let (rt_root_sig, rt_params_ubo_resources, rt_params_ubo_ptrs) = match rt {
Some((sig, (res, ptrs))) => (Some(sig), res, ptrs),
None => (None, Vec::new(), Vec::new()),
};
let glass = if content.glass_panels.is_empty() {
None
} else {
Some(super::glass::build_glass_producer(
super::glass::GlassBuild {
alloc,
root_sig: &root_sig,
rt_root_sig: rt_root_sig.as_ref(),
msaa_samples,
hot_reload,
info_queue,
},
content.glass_panels,
content.glass_planar_slots,
)?)
};
let water = if content.water_surfaces.is_empty() {
None
} else {
Some(super::water::build_water_producer(
super::water::WaterBuild {
alloc,
root_sig: &root_sig,
rt_root_sig: rt_root_sig.as_ref(),
msaa_samples,
hot_reload,
info_queue,
},
content.water_surfaces,
content.water_planar_slots,
)?)
};
let glass_mesh = match (content.seethrough_mesh_indices.is_empty(), &rt_root_sig) {
(false, Some(sig)) => {
match super::glass::build_glass_mesh_producer(
super::glass::GlassMeshBuild {
alloc,
rt_root_sig: sig,
msaa_samples,
hot_reload,
info_queue,
},
content.seethrough_mesh_indices,
) {
Ok(p) => Some(p),
Err(e) => {
tracing::warn!(
"see-through glass mesh pipeline build failed ({e}); those meshes render opaque"
);
None
}
}
}
_ => None,
};
let view_size = align256(std::mem::size_of::<TransparentView>() as u64);
let mut view_ubo_resources: Vec<PooledBuffer> = Vec::with_capacity(FRAMES);
let mut view_ubo_ptrs: Vec<*mut u8> = Vec::with_capacity(FRAMES);
for _ in 0..FRAMES {
let buf = alloc.alloc_buffer(
view_size,
D3D12_HEAP_TYPE_UPLOAD,
D3D12_RESOURCE_STATE_GENERIC_READ,
)?;
let mut ptr = std::ptr::null_mut::<std::ffi::c_void>();
unsafe { buf.Map(0, None, Some(&mut ptr)) }
.map_err(|e| map_hresult(e.code(), "map transparent view ubo"))?;
view_ubo_ptrs.push(ptr as *mut u8);
view_ubo_resources.push(buf);
}
let scene_copy = create_hdr_resolve_target(device, width.max(1), height.max(1))?;
write_scene_copy_srv(device, &scene_copy, scene_copy_srv_cpu);
let mut me = Self {
root_sig,
glass,
water,
glass_mesh,
view_ubo_resources,
view_ubo_ptrs,
scene_copy,
scene_copy_srv_cpu,
scene_copy_srv_gpu,
depth_srv_gpu,
rt_root_sig,
rt_params_ubo_resources,
rt_params_ubo_ptrs,
reflection_slots,
reflection: None,
reflection_divisor,
};
me.rebuild_reflection_layers(device, width, height)?;
Ok(me)
}
pub(in crate::directx) fn set_reflection_divisor(
&mut self,
device: &ID3D12Device,
(width, height): (u32, u32),
divisor: u32,
) -> RenderResult<()> {
if divisor == self.reflection_divisor {
return Ok(());
}
self.reflection_divisor = divisor;
self.rebuild_reflection_layers(device, width, height)
}
fn rebuild_reflection_layers(
&mut self,
device: &ID3D12Device,
width: u32,
height: u32,
) -> RenderResult<()> {
let traced = self
.glass
.as_ref()
.is_some_and(|p| p.reflection_flat_pso.is_some())
|| self.glass_mesh.is_some();
let d = self.reflection_divisor;
self.reflection = None;
if d > 1 && traced {
let extent = ((width / d).max(1), (height / d).max(1));
self.reflection = Some(GlassReflectionLayers::new(
device,
&self.reflection_slots,
extent,
)?);
}
write_reflection_windows(device, &self.reflection_slots, self.reflection.as_ref());
Ok(())
}
pub(in crate::directx) fn root_sig(&self) -> &ID3D12RootSignature {
&self.root_sig
}
pub(in crate::directx) fn swap_pipelines(
&mut self,
glass_pso: Option<ID3D12PipelineState>,
water_pso: Option<ID3D12PipelineState>,
) {
if let (Some(pso), Some(p)) = (glass_pso, self.glass.as_mut()) {
p.pso = pso;
}
if let (Some(pso), Some(p)) = (water_pso, self.water.as_mut()) {
p.pso = pso;
}
}
pub(in crate::directx) fn has_glass(&self) -> bool {
self.glass.is_some()
}
pub(in crate::directx) fn has_water(&self) -> bool {
self.water.is_some()
}
pub(in crate::directx) fn rt_pipelines_ready(&self) -> bool {
self.rt_root_sig.is_some()
&& self.glass.as_ref().is_none_or(|p| p.flat_rt_pso.is_some())
&& self.water.as_ref().is_none_or(|p| p.flat_rt_pso.is_some())
}
pub(in crate::directx) fn rt_textured_ready(&self) -> bool {
self.glass
.as_ref()
.is_none_or(|p| p.textured_rt_pso.is_some())
&& self
.water
.as_ref()
.is_none_or(|p| p.textured_rt_pso.is_some())
&& self
.glass_mesh
.as_ref()
.is_none_or(|p| p.textured_rt_pso.is_some())
}
pub(in crate::directx) fn resize_to(
&mut self,
device: &ID3D12Device,
width: u32,
height: u32,
) -> RenderResult<()> {
self.scene_copy = create_hdr_resolve_target(device, width.max(1), height.max(1))?;
write_scene_copy_srv(device, &self.scene_copy, self.scene_copy_srv_cpu);
self.rebuild_reflection_layers(device, width, height)
}
pub(in crate::directx) fn water_planar_slot_live(&self) -> bool {
self.water.as_ref().is_some_and(|p| {
p.records
.iter()
.any(|r| r.visible && r.planar_slot.is_some())
})
}
pub(in crate::directx) fn any_visible(&self) -> bool {
let live = |p: &Option<TransparentProducer>| {
p.as_ref()
.is_some_and(|p| p.records.iter().any(|r| r.visible))
};
live(&self.glass) || live(&self.water)
}
pub(in crate::directx) fn seethrough_mesh_indices(&self) -> &[usize] {
self.glass_mesh
.as_ref()
.map(|p| p.object_indices.as_slice())
.unwrap_or_default()
}
pub(in crate::directx) fn mesh_pipelines_ready(&self) -> bool {
self.glass_mesh.is_some()
}
fn draw_order(&self, meshes: &[[f32; 3]], cam: [f32; 3]) -> Vec<(Producer, usize)> {
let centers = |p: &Option<TransparentProducer>| -> Vec<([f32; 3], bool)> {
p.as_ref()
.map(|p| p.records.iter().map(|r| (r.center, r.visible)).collect())
.unwrap_or_default()
};
ordered_visible(¢ers(&self.glass), ¢ers(&self.water), meshes, cam)
}
fn record(&self, kind: Producer, index: usize) -> &TransparentRecord {
&self.producer(kind).records[index]
}
fn producer(&self, kind: Producer) -> &TransparentProducer {
let producer = match kind {
Producer::Glass => self.glass.as_ref(),
Producer::Water => self.water.as_ref(),
Producer::GlassMesh => {
unreachable!("mesh draws are per-frame and never resolve to a static record")
}
};
producer.expect("the draw order only names live producers")
}
}
impl DxContext {
fn collect_mesh_draws(
&self,
transparent: &TransparentResources,
frame_idx: usize,
cam: [f32; 3],
) -> Vec<GlassMeshDraw> {
let Some(producer) = transparent.glass_mesh.as_ref() else {
return Vec::new();
};
let block = align256(std::mem::size_of::<GlassMeshParams>() as u64);
let ring_base = com::gpu_va(&producer.params_ring[frame_idx]);
let ring_ptr = producer.params_ptrs[frame_idx];
let prefilter_mip_count = self.scene.env_map.prefilter_mip_count as f32;
let mut draws = Vec::with_capacity(producer.object_indices.len());
for (slot, &idx) in producer.object_indices.iter().enumerate() {
let Some(obj) = self.state.draw.objects.get(idx) else {
continue;
};
if !obj.visible || !obj.resident || obj.material.see_through == 0 {
continue;
}
let mesh = SeeThroughMesh::new(obj, cam, prefilter_mip_count);
let offset = slot as u64 * block;
unsafe {
std::ptr::copy_nonoverlapping(
&mesh.params as *const GlassMeshParams as *const u8,
ring_ptr.add(offset as usize),
std::mem::size_of::<GlassMeshParams>(),
);
}
draws.push(GlassMeshDraw {
index_offset: mesh.index_offset as u32,
index_count: mesh.index_count as u32,
base_vertex: mesh.base_vertex,
params_gva: ring_base + offset,
center: mesh.center,
});
}
draws
}
pub(in crate::directx) fn encode_transparent(
&self,
cmd: &ID3D12GraphicsCommandList,
frame_idx: usize,
view: &TransparentView,
fov_y_radians: f32,
aspect: f32,
reactive: ReactiveWrite,
) -> RenderResult<()> {
let transparent = match &self.transparent {
Some(t) => t,
None => return Ok(()),
};
let cam = [view.camera_pos[0], view.camera_pos[1], view.camera_pos[2]];
let rt_live = self.rt_transparent_active();
let textured =
rt_live && self.cull.main_bindless_pso.is_some() && transparent.rt_textured_ready();
let mesh_draws = if rt_live {
self.collect_mesh_draws(transparent, frame_idx, cam)
} else {
Vec::new()
};
let mesh_centers: Vec<[f32; 3]> = mesh_draws.iter().map(|d| d.center).collect();
let order = transparent.draw_order(&mesh_centers, cam);
if order.is_empty() {
return Ok(());
}
let reflection_layers = transparent.reflection.as_ref().filter(|_| {
rt_live
&& order
.iter()
.any(|&(kind, _)| matches!(kind, Producer::Glass | Producer::GlassMesh))
});
unsafe {
std::ptr::copy_nonoverlapping(
view as *const TransparentView as *const u8,
transparent.view_ubo_ptrs[frame_idx],
std::mem::size_of::<TransparentView>(),
);
}
let view_gva = com::gpu_va(&transparent.view_ubo_resources[frame_idx]);
let rt_params_gva = if rt_live {
let rt = self.rt_reflections.as_ref().expect("rt_reflections_active");
let v = self.state.view.matrix;
let inv_view_rot = [
[v[0][0], v[1][0], v[2][0], 0.0],
[v[0][1], v[1][1], v[2][1], 0.0],
[v[0][2], v[1][2], v[2][2], 0.0],
[0.0, 0.0, 0.0, 1.0],
];
let params = rt.settings.params(RtParamsInputs {
fov_y_radians,
aspect,
inv_view_rot,
cam_pos: cam,
sun_dir: self.fog.sun_dir,
sun_color: self.fog.sun_color,
prefilter_mip_count: self.scene.env_map.prefilter_mip_count as f32,
sky_rot: self.state.view.sky_rot,
});
let params = RtParams {
trace_divisor: if reflection_layers.is_some() {
params.trace_divisor
} else {
1.0
},
..params
};
unsafe {
std::ptr::copy_nonoverlapping(
¶ms as *const RtParams as *const u8,
transparent.rt_params_ubo_ptrs[frame_idx],
std::mem::size_of::<RtParams>(),
);
}
Some(com::gpu_va(&transparent.rt_params_ubo_resources[frame_idx]))
} else {
None
};
let scene_res = self.post_scene_target();
let scene_rtv = self.post_scene_rtv();
let scene_to_copy = transition_barrier(
self.post_scene_target(),
D3D12_RESOURCE_STATE_RENDER_TARGET,
D3D12_RESOURCE_STATE_COPY_SOURCE,
);
let copy_to_dst = transition_barrier(
&transparent.scene_copy,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
D3D12_RESOURCE_STATE_COPY_DEST,
);
unsafe {
cmd.ResourceBarrier(&[scene_to_copy, copy_to_dst]);
cmd.CopyResource(&transparent.scene_copy, scene_res);
}
let scene_to_rt = transition_barrier(
self.post_scene_target(),
D3D12_RESOURCE_STATE_COPY_SOURCE,
D3D12_RESOURCE_STATE_RENDER_TARGET,
);
let copy_to_psr = transition_barrier(
&transparent.scene_copy,
D3D12_RESOURCE_STATE_COPY_DEST,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
);
unsafe { cmd.ResourceBarrier(&[scene_to_rt, copy_to_psr]) };
let w = self.targets.extent.render_width;
let h = self.targets.extent.render_height;
unsafe {
cmd.IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
cmd.SetDescriptorHeaps(&[Some(self.descriptors.srv_heap.clone())]);
}
let prefilter_srv = self.prefilter_cube_srv_gpu();
let probes = self.probe_bindings(frame_idx);
let (planar_root, probe_records_root) = if rt_live {
(PLANAR_ROOT_RT, PROBE_RECORDS_ROOT_RT)
} else {
(PLANAR_ROOT_BASE, PROBE_RECORDS_ROOT_BASE)
};
let root_sig = if rt_live {
transparent
.rt_root_sig
.as_ref()
.expect("rt_live built the rt root sig")
} else {
&transparent.root_sig
};
unsafe {
cmd.SetGraphicsRootSignature(root_sig);
cmd.SetGraphicsRootConstantBufferView(0, view_gva);
cmd.set_graphics_srv_table(2, transparent.scene_copy_srv_gpu);
cmd.set_graphics_srv_table(3, transparent.depth_srv_gpu);
cmd.set_graphics_srv_table(4, prefilter_srv);
cmd.set_graphics_srv_table(5, probes.cube_table);
cmd.SetGraphicsRootConstantBufferView(6, probes.set_cbv);
cmd.SetGraphicsRootShaderResourceView(probe_records_root, probes.records);
cmd.SetGraphicsRootConstantBufferView(probe_records_root + 1, probes.cluster_cbv);
cmd.SetGraphicsRootShaderResourceView(probe_records_root + 2, probes.cluster_list);
}
if rt_live {
let rt_params_gva = rt_params_gva.expect("rt_live uploaded RtParams");
let accel = self.rt.accel.as_ref().expect("rt_reflections_active");
unsafe {
cmd.SetGraphicsRootConstantBufferView(7, rt_params_gva);
cmd.SetGraphicsRootShaderResourceView(8, accel.tlas_gva());
cmd.SetGraphicsRootShaderResourceView(
9,
com::gpu_va(&self.scene.geometry.vertex_buffer),
);
cmd.SetGraphicsRootShaderResourceView(
10,
com::gpu_va(&self.scene.geometry.index_buffer),
);
cmd.SetGraphicsRootShaderResourceView(11, accel.geom_table_gva());
cmd.SetGraphicsRootShaderResourceView(12, accel.deformed_verts_gva());
cmd.SetGraphicsRootShaderResourceView(13, accel.skinned_index_gva());
if textured {
cmd.set_graphics_srv_table(14, self.cull.bindless_pool_gpu[self.current_frame]);
}
}
}
unsafe { cmd.set_graphics_srv_table(planar_root, transparent.scene_copy_srv_gpu) };
let frame = TransparentFrame {
cmd,
planar_root,
textured,
mesh_draws: &mesh_draws,
};
if let Some(layers) = reflection_layers {
self.encode_glass_reflection_layers(transparent, layers, &frame, &order);
}
if rt_live {
unsafe {
cmd.set_graphics_srv_table(
GLASS_REFLECTION_ROOT_RT,
transparent.reflection_slots.windows[2],
);
}
}
unsafe {
let rtvs = [scene_rtv, self.targets.reactive_mask.rtv(reactive)];
cmd.OMSetRenderTargets(2, Some(rtvs.as_ptr()), false, None);
set_viewport(cmd, (w, h));
}
let mut bound: Option<Producer> = None;
for &(kind, i) in &order {
if bound != Some(kind) {
let pso = match kind {
Producer::GlassMesh => transparent
.glass_mesh
.as_ref()
.expect("the draw order only names live producers")
.pipeline(textured),
_ => transparent.producer(kind).pipeline(rt_live, textured),
};
unsafe { cmd.SetPipelineState(pso) };
bound = Some(kind);
}
self.draw_transparent_entry(transparent, &frame, kind, i);
}
Ok(())
}
fn encode_glass_reflection_layers(
&self,
transparent: &TransparentResources,
layers: &GlassReflectionLayers,
frame: &TransparentFrame,
order: &[(Producer, usize)],
) {
let cmd = frame.cmd;
let slots = &transparent.reflection_slots;
for layer in 0..2 {
let target = &layers.layers[layer];
unsafe {
cmd.ResourceBarrier(&[transition_barrier(
target,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
D3D12_RESOURCE_STATE_RENDER_TARGET,
)]);
cmd.OMSetRenderTargets(1, Some(&slots.rtv[layer]), false, Some(&slots.dsv));
cmd.ClearRenderTargetView(slots.rtv[layer], &[0.0; 4], None);
cmd.ClearDepthStencilView(slots.dsv, D3D12_CLEAR_FLAG_DEPTH, DEPTH_CLEAR, 0, None);
set_viewport(cmd, layers.extent);
cmd.set_graphics_srv_table(GLASS_REFLECTION_ROOT_RT, slots.windows[layer]);
}
for &(kind, i) in order {
let pso = match kind {
Producer::GlassMesh => transparent
.glass_mesh
.as_ref()
.map(|m| m.reflection_pipeline(frame.textured)),
_ => transparent
.producer(kind)
.reflection_pipeline(frame.textured),
};
let Some(pso) = pso else {
continue;
};
unsafe { cmd.SetPipelineState(pso) };
self.draw_transparent_entry(transparent, frame, kind, i);
}
unsafe {
cmd.ResourceBarrier(&[transition_barrier(
target,
D3D12_RESOURCE_STATE_RENDER_TARGET,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
)]);
}
}
}
fn draw_transparent_entry(
&self,
transparent: &TransparentResources,
frame: &TransparentFrame,
kind: Producer,
i: usize,
) {
let cmd = frame.cmd;
if kind == Producer::GlassMesh {
let d = &frame.mesh_draws[i];
unsafe {
cmd.IASetVertexBuffers(0, Some(&[self.scene.geometry.vertex_buffer_view]));
cmd.IASetIndexBuffer(Some(&self.scene.geometry.index_buffer_view));
cmd.SetGraphicsRootConstantBufferView(1, d.params_gva);
cmd.DrawIndexedInstanced(d.index_count, 1, d.index_offset, d.base_vertex, 0);
}
} else {
let r = transparent.record(kind, i);
let planar_srv = r
.planar_slot
.and_then(|s| {
self.planar_reflection
.as_ref()
.map(|set| set.resolve_srv_gpu(s))
})
.unwrap_or(transparent.scene_copy_srv_gpu);
unsafe {
cmd.IASetVertexBuffers(0, Some(&[r.vertex_buffer_view]));
cmd.IASetIndexBuffer(Some(&r.index_buffer_view));
cmd.SetGraphicsRootConstantBufferView(1, r.params_cbuffer_gva);
cmd.set_graphics_srv_table(frame.planar_root, planar_srv);
cmd.DrawIndexedInstanced(r.index_count, 1, 0, 0, 0);
}
}
self.inc_draw_calls(1);
}
}
struct TransparentFrame<'a> {
cmd: &'a ID3D12GraphicsCommandList,
planar_root: u32,
textured: bool,
mesh_draws: &'a [GlassMeshDraw],
}
unsafe fn set_viewport(cmd: &ID3D12GraphicsCommandList, (w, h): (u32, u32)) {
let vp = D3D12_VIEWPORT {
TopLeftX: 0.0,
TopLeftY: 0.0,
Width: w as f32,
Height: h as f32,
MinDepth: 0.0,
MaxDepth: 1.0,
};
let scissor = RECT {
left: 0,
top: 0,
right: w as i32,
bottom: h as i32,
};
unsafe {
cmd.RSSetViewports(&[vp]);
cmd.RSSetScissorRects(&[scissor]);
}
}