use concinnity_core::gfx::render_types::{MAX_SHADOWED_SPOTS, NUM_SHADOW_CASCADES};
use super::HIZ_MAX_MIPS;
use crate::directx::context::FRAMES;
use crate::directx::decal::MAX_DECALS;
use crate::directx::particle::MAX_EMITTERS;
use crate::directx::post::descriptors::POST_TARGET_SLOTS;
use crate::directx::probe_prefilter::PROBE_MAX_MIPS;
pub(in crate::directx) struct SrvHeapParams {
pub n_atlases: usize,
pub gbuffer_srv_extra: usize,
pub rt_output_srv_extra: usize,
pub planar_resolve_srv_extra: usize,
pub albedo_count: usize,
pub normal_count: usize,
}
pub(in crate::directx) struct SrvHeapLayout {
pub atlas_base_slot: usize,
pub hdr_srv_slot: usize,
pub lut_srv_slot: usize,
pub post_srv_base_slot: usize,
pub ssao_white_srv_slot: usize,
pub decal_depth_srv_slot: usize,
pub decal_srv_base_slot: usize,
pub particle_srv_base_slot: usize,
pub fog_froxel_uav_slot: usize,
pub fog_froxel_srv_slot: usize,
pub upscale_uav_slot: usize,
pub upscale_srv_slot: usize,
pub raymarch_srv_base_slot: usize,
pub hiz_srv_slot: usize,
pub hiz_uav_base_slot: usize,
pub probe_capture_srv_slot: usize,
pub probe_capture_uav_base_slot: usize,
pub probe_cube_uav_base_slot: usize,
pub probe_mip0_pair_slot: usize,
pub transparent_scene_copy_srv_slot: usize,
pub glass_reflection_srv_base_slot: usize,
pub gbuffer_srv_base_slot: usize,
pub rt_output_srv_slot: usize,
pub planar_resolve_srv_base_slot: usize,
pub flat_pool_base_slot: usize,
pub probe_cubes_srv_slot: usize,
pub spot_shadow_srv_slot: usize,
pub ltc_srv_base_slot: usize,
pub reactive_mask_srv_slot: usize,
pub srv_slots: usize,
}
const GLOBAL_SRV_COUNT: usize = 3;
impl SrvHeapLayout {
pub(in crate::directx) fn compute(p: &SrvHeapParams) -> Self {
let atlas_base_slot = GLOBAL_SRV_COUNT;
let hdr_srv_slot = atlas_base_slot + p.n_atlases.max(1);
let lut_srv_slot = hdr_srv_slot + 1;
let post_srv_base_slot = lut_srv_slot + 1;
let ssao_white_srv_slot = post_srv_base_slot + POST_TARGET_SLOTS;
let decal_depth_srv_slot = ssao_white_srv_slot + 1;
let decal_srv_base_slot = decal_depth_srv_slot + 1;
let particle_srv_base_slot = decal_srv_base_slot + MAX_DECALS;
let fog_froxel_uav_slot = particle_srv_base_slot + MAX_EMITTERS;
let fog_froxel_srv_slot = fog_froxel_uav_slot + 1;
let upscale_uav_slot = fog_froxel_srv_slot + 1;
let upscale_srv_slot = upscale_uav_slot + 1;
let raymarch_srv_base_slot = upscale_srv_slot + 1;
let hiz_srv_slot = raymarch_srv_base_slot + 4;
let hiz_uav_base_slot = hiz_srv_slot + 1;
let probe_capture_srv_slot = hiz_uav_base_slot + HIZ_MAX_MIPS;
let probe_capture_uav_base_slot = probe_capture_srv_slot + 1;
let probe_cube_uav_base_slot = probe_capture_uav_base_slot + PROBE_MAX_MIPS;
let probe_mip0_pair_slot = probe_cube_uav_base_slot + PROBE_MAX_MIPS;
let transparent_scene_copy_srv_slot = probe_mip0_pair_slot + 2;
let glass_reflection_srv_base_slot = transparent_scene_copy_srv_slot + 1;
let gbuffer_srv_base_slot = glass_reflection_srv_base_slot + GLASS_REFLECTION_SRV_SLOTS;
let rt_output_srv_slot = gbuffer_srv_base_slot + p.gbuffer_srv_extra;
let planar_resolve_srv_base_slot = rt_output_srv_slot + p.rt_output_srv_extra;
let flat_pool_base_slot = planar_resolve_srv_base_slot + p.planar_resolve_srv_extra;
let probe_cubes_srv_slot = flat_pool_base_slot + FRAMES * (p.albedo_count + p.normal_count);
let spot_shadow_srv_slot = probe_cubes_srv_slot + 1;
let ltc_srv_base_slot = spot_shadow_srv_slot + 1;
let reactive_mask_srv_slot = ltc_srv_base_slot + 2;
let srv_slots = reactive_mask_srv_slot + 1;
Self {
atlas_base_slot,
hdr_srv_slot,
lut_srv_slot,
post_srv_base_slot,
ssao_white_srv_slot,
decal_depth_srv_slot,
decal_srv_base_slot,
particle_srv_base_slot,
fog_froxel_uav_slot,
fog_froxel_srv_slot,
upscale_uav_slot,
upscale_srv_slot,
raymarch_srv_base_slot,
hiz_srv_slot,
hiz_uav_base_slot,
probe_capture_srv_slot,
probe_capture_uav_base_slot,
probe_cube_uav_base_slot,
probe_mip0_pair_slot,
transparent_scene_copy_srv_slot,
glass_reflection_srv_base_slot,
gbuffer_srv_base_slot,
rt_output_srv_slot,
planar_resolve_srv_base_slot,
flat_pool_base_slot,
probe_cubes_srv_slot,
spot_shadow_srv_slot,
ltc_srv_base_slot,
reactive_mask_srv_slot,
srv_slots,
}
}
}
pub(super) const GBUFFER_TARGETS: usize = 3;
pub(super) const RT_OUTPUT_TARGETS: usize = 1;
pub(in crate::directx) const GLASS_REFLECTION_TARGETS: usize = 2;
pub(in crate::directx) const GLASS_REFLECTION_SRV_SLOTS: usize = 6;
pub(super) const REACTIVE_MASK_RTVS: usize = 2;
pub(super) const DSV_MAIN_DEPTH_SLOT: usize = 0;
pub(super) const DSV_SHADOW_BASE_SLOT: usize = DSV_MAIN_DEPTH_SLOT + 1;
pub(super) const DSV_SPOT_SHADOW_BASE_SLOT: usize = DSV_SHADOW_BASE_SLOT + NUM_SHADOW_CASCADES;
pub(super) const DSV_GBUFFER_DEPTH_SLOT: usize = DSV_SPOT_SHADOW_BASE_SLOT + MAX_SHADOWED_SPOTS;
pub(super) const DSV_GLASS_REFLECTION_DEPTH_SLOT: usize = DSV_GBUFFER_DEPTH_SLOT + 1;
pub(super) const DSV_SLOTS: usize = DSV_GLASS_REFLECTION_DEPTH_SLOT + 1;
pub(super) struct RtvHeapLayout {
pub hdr_slot: usize,
pub post_base_slot: usize,
pub decal_resolve_slot: usize,
pub gbuffer_base_slot: usize,
pub rt_output_slot: usize,
pub glass_reflection_base_slot: usize,
pub reactive_mask_base_slot: usize,
pub rtv_slots: usize,
}
impl RtvHeapLayout {
pub(super) fn compute(msaa_samples: u32) -> Self {
let hdr_slot = FRAMES;
let post_base_slot = hdr_slot + 1;
let decal_resolve_slot = post_base_slot + POST_TARGET_SLOTS;
let gbuffer_base_slot = decal_resolve_slot + usize::from(msaa_samples > 1);
let rt_output_slot = gbuffer_base_slot + GBUFFER_TARGETS;
let glass_reflection_base_slot = rt_output_slot + RT_OUTPUT_TARGETS;
let reactive_mask_base_slot = glass_reflection_base_slot + GLASS_REFLECTION_TARGETS;
let rtv_slots = reactive_mask_base_slot + REACTIVE_MASK_RTVS;
Self {
hdr_slot,
post_base_slot,
decal_resolve_slot,
gbuffer_base_slot,
rt_output_slot,
glass_reflection_base_slot,
reactive_mask_base_slot,
rtv_slots,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_gap_free(p: &SrvHeapParams) {
let l = SrvHeapLayout::compute(p);
let blocks: [(usize, usize); 29] = [
(l.atlas_base_slot, p.n_atlases.max(1)),
(l.hdr_srv_slot, 1),
(l.lut_srv_slot, 1),
(l.post_srv_base_slot, POST_TARGET_SLOTS),
(l.ssao_white_srv_slot, 1),
(l.decal_depth_srv_slot, 1),
(l.decal_srv_base_slot, MAX_DECALS),
(l.particle_srv_base_slot, MAX_EMITTERS),
(l.fog_froxel_uav_slot, 1),
(l.fog_froxel_srv_slot, 1),
(l.upscale_uav_slot, 1),
(l.upscale_srv_slot, 1),
(l.raymarch_srv_base_slot, 4),
(l.hiz_srv_slot, 1),
(l.hiz_uav_base_slot, HIZ_MAX_MIPS),
(l.probe_capture_srv_slot, 1),
(l.probe_capture_uav_base_slot, PROBE_MAX_MIPS),
(l.probe_cube_uav_base_slot, PROBE_MAX_MIPS),
(l.probe_mip0_pair_slot, 2),
(l.transparent_scene_copy_srv_slot, 1),
(l.glass_reflection_srv_base_slot, 6),
(l.gbuffer_srv_base_slot, p.gbuffer_srv_extra),
(l.rt_output_srv_slot, p.rt_output_srv_extra),
(l.planar_resolve_srv_base_slot, p.planar_resolve_srv_extra),
(
l.flat_pool_base_slot,
FRAMES * (p.albedo_count + p.normal_count),
),
(l.probe_cubes_srv_slot, 1),
(l.spot_shadow_srv_slot, 1),
(l.ltc_srv_base_slot, 2),
(l.reactive_mask_srv_slot, 1),
];
let mut expected_base = GLOBAL_SRV_COUNT;
for (i, (base, count)) in blocks.iter().enumerate() {
assert_eq!(
*base, expected_base,
"block {i} base {base} should sit at running total {expected_base}",
);
expected_base += count;
}
assert_eq!(
l.srv_slots, expected_base,
"srv_slots must cover every block exactly",
);
assert!(l.srv_slots >= GLOBAL_SRV_COUNT);
}
#[test]
fn layout_gap_free_all_features_on() {
assert_gap_free(&SrvHeapParams {
n_atlases: 2,
gbuffer_srv_extra: 3,
rt_output_srv_extra: 1,
planar_resolve_srv_extra: 2,
albedo_count: 9,
normal_count: 4,
});
}
#[test]
fn layout_gap_free_all_features_off() {
assert_gap_free(&SrvHeapParams {
n_atlases: 0,
gbuffer_srv_extra: 0,
rt_output_srv_extra: 0,
planar_resolve_srv_extra: 0,
albedo_count: 1,
normal_count: 1,
});
}
#[test]
fn layout_gap_free_mixed_features() {
assert_gap_free(&SrvHeapParams {
n_atlases: 1,
gbuffer_srv_extra: 3,
rt_output_srv_extra: 1,
planar_resolve_srv_extra: 1,
albedo_count: 50,
normal_count: 12,
});
}
#[test]
fn first_block_clears_the_global_srvs() {
let l = SrvHeapLayout::compute(&SrvHeapParams {
n_atlases: 0,
gbuffer_srv_extra: 0,
rt_output_srv_extra: 0,
planar_resolve_srv_extra: 0,
albedo_count: 1,
normal_count: 1,
});
assert_eq!(l.atlas_base_slot, GLOBAL_SRV_COUNT);
assert!(l.hdr_srv_slot >= GLOBAL_SRV_COUNT);
}
fn assert_rtv_gap_free(msaa_samples: u32) {
let l = RtvHeapLayout::compute(msaa_samples);
let decal_resolve = if msaa_samples > 1 { 1 } else { 0 };
let blocks: [(usize, usize); 7] = [
(l.hdr_slot, 1),
(l.post_base_slot, POST_TARGET_SLOTS),
(l.decal_resolve_slot, decal_resolve),
(l.gbuffer_base_slot, 3),
(l.rt_output_slot, 1),
(l.glass_reflection_base_slot, 2),
(l.reactive_mask_base_slot, 2),
];
let mut expected_base = FRAMES;
for (i, (base, count)) in blocks.iter().enumerate() {
assert_eq!(
*base, expected_base,
"RTV block {i} base {base} should sit at running total {expected_base}",
);
expected_base += count;
}
assert_eq!(l.rtv_slots, expected_base);
}
#[test]
fn rtv_layout_gap_free_without_msaa() {
assert_rtv_gap_free(1);
}
#[test]
fn rtv_layout_gap_free_with_msaa() {
assert_rtv_gap_free(4);
}
#[test]
fn rtv_post_block_follows_the_hdr_target() {
let l = RtvHeapLayout::compute(1);
assert_eq!(l.post_base_slot, FRAMES + 1);
assert_eq!(l.decal_resolve_slot, l.gbuffer_base_slot);
}
#[test]
fn dsv_layout_covers_every_depth_view() {
assert_eq!(DSV_MAIN_DEPTH_SLOT, 0);
assert_eq!(
DSV_SPOT_SHADOW_BASE_SLOT - DSV_SHADOW_BASE_SLOT,
NUM_SHADOW_CASCADES
);
assert_eq!(
DSV_GBUFFER_DEPTH_SLOT - DSV_SPOT_SHADOW_BASE_SLOT,
MAX_SHADOWED_SPOTS
);
assert_eq!(DSV_GLASS_REFLECTION_DEPTH_SLOT, DSV_GBUFFER_DEPTH_SLOT + 1);
assert_eq!(DSV_SLOTS, 3 + NUM_SHADOW_CASCADES + MAX_SHADOWED_SPOTS);
}
}