use ash::vk;
use concinnity_core::gfx::render_types::FALLBACK_TEXTURE_COUNT;
use concinnity_core::render::uniforms::BINDLESS_POOL_SIZE;
pub(in crate::vulkan) type Binding = (u32, vk::DescriptorType, vk::ShaderStageFlags);
pub(in crate::vulkan) const VIEW_UBO_BINDING: u32 = 0;
pub(in crate::vulkan) const LIGHT_UBO_BINDING: u32 = 1;
pub(in crate::vulkan) const SHADOW_UBO_BINDING: u32 = 2;
pub(in crate::vulkan) const SHADOW_MAP_BINDING: u32 = 3;
pub(in crate::vulkan) const IRRADIANCE_CUBE_BINDING: u32 = 4;
pub(in crate::vulkan) const PREFILTER_CUBE_BINDING: u32 = 5;
pub(in crate::vulkan) const SSAO_BINDING: u32 = 6;
pub(in crate::vulkan) const PROBE_SET_UBO_BINDING: u32 = 7;
pub(in crate::vulkan) const PROBE_CUBES_BINDING: u32 = 8;
pub(in crate::vulkan) const LOCAL_LIGHT_SSBO_BINDING: u32 = 9;
pub(in crate::vulkan) const CLUSTER_PARAMS_UBO_BINDING: u32 = 10;
pub(in crate::vulkan) const CLUSTER_LIST_SSBO_BINDING: u32 = 11;
pub(in crate::vulkan) const SPOT_SHADOW_MAP_BINDING: u32 = 12;
pub(in crate::vulkan) const SPOT_SHADOW_DATA_SSBO_BINDING: u32 = 13;
pub(in crate::vulkan) const AREA_LIGHT_SSBO_BINDING: u32 = 14;
pub(in crate::vulkan) const LTC_MATRIX_BINDING: u32 = 15;
pub(in crate::vulkan) const LTC_MAGNITUDE_BINDING: u32 = 16;
pub(in crate::vulkan) const PROBE_RECORDS_SSBO_BINDING: u32 = 17;
pub(in crate::vulkan) const SHADOW_SAMPLER_BINDING: u32 = 18;
pub(in crate::vulkan) const CUBE_SAMPLER_BINDING: u32 = 19;
pub(in crate::vulkan) const LINEAR_SAMPLER_BINDING: u32 = 20;
pub(in crate::vulkan) fn global_set() -> [Binding; 21] {
use vk::DescriptorType as T;
let fs = vk::ShaderStageFlags::FRAGMENT;
let vs_fs = vk::ShaderStageFlags::VERTEX | fs;
[
(VIEW_UBO_BINDING, T::UNIFORM_BUFFER, vs_fs),
(LIGHT_UBO_BINDING, T::UNIFORM_BUFFER, fs),
(SHADOW_UBO_BINDING, T::UNIFORM_BUFFER, vs_fs),
(SHADOW_MAP_BINDING, T::SAMPLED_IMAGE, fs),
(IRRADIANCE_CUBE_BINDING, T::SAMPLED_IMAGE, fs),
(PREFILTER_CUBE_BINDING, T::SAMPLED_IMAGE, fs),
(SSAO_BINDING, T::SAMPLED_IMAGE, fs),
(PROBE_SET_UBO_BINDING, T::UNIFORM_BUFFER, fs),
(PROBE_CUBES_BINDING, T::SAMPLED_IMAGE, fs),
(LOCAL_LIGHT_SSBO_BINDING, T::STORAGE_BUFFER, fs),
(CLUSTER_PARAMS_UBO_BINDING, T::UNIFORM_BUFFER, fs),
(CLUSTER_LIST_SSBO_BINDING, T::STORAGE_BUFFER, fs),
(SPOT_SHADOW_MAP_BINDING, T::SAMPLED_IMAGE, fs),
(SPOT_SHADOW_DATA_SSBO_BINDING, T::STORAGE_BUFFER, fs),
(AREA_LIGHT_SSBO_BINDING, T::STORAGE_BUFFER, fs),
(LTC_MATRIX_BINDING, T::SAMPLED_IMAGE, fs),
(LTC_MAGNITUDE_BINDING, T::SAMPLED_IMAGE, fs),
(PROBE_RECORDS_SSBO_BINDING, T::STORAGE_BUFFER, fs),
(SHADOW_SAMPLER_BINDING, T::SAMPLER, fs),
(CUBE_SAMPLER_BINDING, T::SAMPLER, fs),
(LINEAR_SAMPLER_BINDING, T::SAMPLER, fs),
]
}
#[derive(Default)]
pub(in crate::vulkan) struct PoolSizes(Vec<vk::DescriptorPoolSize>);
impl PoolSizes {
pub(in crate::vulkan) fn sets(mut self, table: &[Binding], sets: u32) -> Self {
for &(_, ty, _) in table {
self = self.add(ty, sets);
}
self
}
pub(in crate::vulkan) fn add(mut self, ty: vk::DescriptorType, count: u32) -> Self {
match self.0.iter_mut().find(|s| s.ty == ty) {
Some(size) => size.descriptor_count += count,
None => self.0.push(vk::DescriptorPoolSize {
ty,
descriptor_count: count,
}),
}
self
}
pub(in crate::vulkan) fn build(self) -> Vec<vk::DescriptorPoolSize> {
self.0
.into_iter()
.filter(|s| s.descriptor_count > 0)
.collect()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(in crate::vulkan) struct StageDescriptors {
pub(in crate::vulkan) samplers: u32,
pub(in crate::vulkan) sampled_images: u32,
}
impl StageDescriptors {
fn plus(self, other: Self) -> Self {
Self {
samplers: self.samplers + other.samplers,
sampled_images: self.sampled_images + other.sampled_images,
}
}
fn exceeds(self, limits: StageDescriptors) -> bool {
self.samplers > limits.samplers || self.sampled_images > limits.sampled_images
}
}
pub(in crate::vulkan) fn stage_limits(limits: &vk::PhysicalDeviceLimits) -> StageDescriptors {
StageDescriptors {
samplers: limits.max_per_stage_descriptor_samplers,
sampled_images: limits.max_per_stage_descriptor_sampled_images,
}
}
fn global_fragment_descriptors() -> StageDescriptors {
let count = |ty| {
global_set()
.iter()
.filter(|&&(_, t, stage)| t == ty && stage.contains(vk::ShaderStageFlags::FRAGMENT))
.count() as u32
};
StageDescriptors {
samplers: count(vk::DescriptorType::SAMPLER),
sampled_images: count(vk::DescriptorType::SAMPLED_IMAGE),
}
}
pub(in crate::vulkan) fn world_pool_size(texture_count: usize) -> usize {
texture_count.max(1) + FALLBACK_TEXTURE_COUNT
}
fn pool_descriptors(pool_size: u32) -> StageDescriptors {
StageDescriptors {
samplers: 0,
sampled_images: pool_size,
}
}
pub(in crate::vulkan) fn bindless_pool_needs_update_after_bind(
limits: StageDescriptors,
pool_size: u32,
) -> bool {
global_fragment_descriptors()
.plus(pool_descriptors(pool_size))
.exceeds(limits)
}
pub(in crate::vulkan) fn update_after_bind_is_wanted(limits: StageDescriptors) -> bool {
bindless_pool_needs_update_after_bind(limits, BINDLESS_POOL_SIZE as u32)
}
pub(in crate::vulkan) fn shadow_global_set() -> [Binding; 1] {
[(
0,
vk::DescriptorType::UNIFORM_BUFFER,
vk::ShaderStageFlags::VERTEX,
)]
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_gap_free_and_unique(bindings: &[Binding]) {
let mut idx: Vec<u32> = bindings.iter().map(|b| b.0).collect();
idx.sort_unstable();
for (expected, &got) in idx.iter().enumerate() {
assert_eq!(
got, expected as u32,
"descriptor bindings must be 0..n gap-free and unique, got {idx:?}"
);
}
}
const MOLTENVK: StageDescriptors = StageDescriptors {
samplers: 16,
sampled_images: 256,
};
const DESKTOP: StageDescriptors = StageDescriptors {
samplers: 1_048_576,
sampled_images: 1_048_576,
};
#[test]
fn the_shadow_global_set_is_gap_free() {
assert_gap_free_and_unique(&shadow_global_set());
}
#[test]
fn the_global_set_contract_is_locked_and_gap_free() {
use vk::DescriptorType as T;
use vk::ShaderStageFlags as S;
let fs = S::FRAGMENT;
let vs_fs = S::VERTEX | S::FRAGMENT;
let table = global_set();
assert_gap_free_and_unique(&table);
assert_eq!(
table,
[
(0, T::UNIFORM_BUFFER, vs_fs),
(1, T::UNIFORM_BUFFER, fs),
(2, T::UNIFORM_BUFFER, vs_fs),
(3, T::SAMPLED_IMAGE, fs),
(4, T::SAMPLED_IMAGE, fs),
(5, T::SAMPLED_IMAGE, fs),
(6, T::SAMPLED_IMAGE, fs),
(7, T::UNIFORM_BUFFER, fs),
(8, T::SAMPLED_IMAGE, fs),
(9, T::STORAGE_BUFFER, fs),
(10, T::UNIFORM_BUFFER, fs),
(11, T::STORAGE_BUFFER, fs),
(12, T::SAMPLED_IMAGE, fs),
(13, T::STORAGE_BUFFER, fs),
(14, T::STORAGE_BUFFER, fs),
(15, T::SAMPLED_IMAGE, fs),
(16, T::SAMPLED_IMAGE, fs),
(17, T::STORAGE_BUFFER, fs),
(18, T::SAMPLER, fs),
(19, T::SAMPLER, fs),
(20, T::SAMPLER, fs),
]
);
}
fn count_of(sizes: &[vk::DescriptorPoolSize], ty: vk::DescriptorType) -> u32 {
sizes
.iter()
.filter(|s| s.ty == ty)
.map(|s| s.descriptor_count)
.sum()
}
#[test]
fn pool_sizes_scale_the_table_by_the_set_count() {
use vk::DescriptorType as T;
let sizes = PoolSizes::default().sets(&global_set(), 3).build();
assert_eq!(count_of(&sizes, T::UNIFORM_BUFFER), 15);
assert_eq!(count_of(&sizes, T::STORAGE_BUFFER), 15);
assert_eq!(count_of(&sizes, T::SAMPLED_IMAGE), 24);
assert_eq!(count_of(&sizes, T::SAMPLER), 9);
assert_eq!(sizes.len(), 4, "one entry per type");
}
#[test]
fn pool_sizes_merge_extras_and_drop_empty_types() {
use vk::DescriptorType as T;
let sizes = PoolSizes::default()
.sets(&shadow_global_set(), 2)
.add(T::UNIFORM_BUFFER, 1)
.add(T::STORAGE_BUFFER, 0)
.build();
assert_eq!(
sizes
.iter()
.map(|s| (s.ty, s.descriptor_count))
.collect::<Vec<_>>(),
[(T::UNIFORM_BUFFER, 3)]
);
assert!(
PoolSizes::default()
.sets(&global_set(), 0)
.build()
.is_empty()
);
}
#[test]
fn the_global_set_declares_eight_images_and_three_samplers() {
assert_eq!(
global_fragment_descriptors(),
StageDescriptors {
samplers: 3,
sampled_images: 8,
}
);
}
const SPEC_MINIMUM: StageDescriptors = StageDescriptors {
samplers: 16,
sampled_images: 16,
};
const TRANSPARENT_PASS: StageDescriptors = StageDescriptors {
samplers: 2,
sampled_images: 5,
};
const REFLECTION_RESOLVE: StageDescriptors = StageDescriptors {
samplers: 4,
sampled_images: 4,
};
#[test]
fn the_global_set_and_its_widest_pass_fit_the_spec_minimum() {
let widest = StageDescriptors {
samplers: TRANSPARENT_PASS.samplers.max(REFLECTION_RESOLVE.samplers),
sampled_images: TRANSPARENT_PASS
.sampled_images
.max(REFLECTION_RESOLVE.sampled_images),
};
let seat = global_fragment_descriptors().plus(widest);
assert_eq!(
seat,
StageDescriptors {
samplers: 7,
sampled_images: 13,
}
);
assert!(!seat.exceeds(SPEC_MINIMUM));
}
#[test]
fn the_pool_overflows_on_sampled_images_alone() {
assert!(!bindless_pool_needs_update_after_bind(MOLTENVK, 248));
assert!(bindless_pool_needs_update_after_bind(MOLTENVK, 249));
let few_samplers = StageDescriptors {
samplers: 3,
sampled_images: 1_048_576,
};
assert!(!bindless_pool_needs_update_after_bind(few_samplers, 65_536));
}
#[test]
fn the_pool_stays_plain_on_desktop_drivers() {
for pool_size in [2, 64, 4096, 65_536] {
assert!(!bindless_pool_needs_update_after_bind(DESKTOP, pool_size));
}
assert!(!update_after_bind_is_wanted(DESKTOP));
}
#[test]
fn moltenvk_wants_update_after_bind_for_a_full_pool() {
assert!(update_after_bind_is_wanted(MOLTENVK));
}
#[test]
fn the_stage_limits_are_the_two_per_stage_counts() {
let limits = vk::PhysicalDeviceLimits {
max_per_stage_descriptor_samplers: 16,
max_per_stage_descriptor_sampled_images: 256,
..Default::default()
};
assert_eq!(stage_limits(&limits), MOLTENVK);
}
#[test]
fn shadow_set_contract_is_locked() {
use vk::DescriptorType as T;
use vk::ShaderStageFlags as S;
assert_eq!(shadow_global_set(), [(0, T::UNIFORM_BUFFER, S::VERTEX)]);
}
#[test]
fn pool_size_counts_fallbacks() {
assert_eq!(world_pool_size(0), 3);
assert_eq!(world_pool_size(1), 3);
assert_eq!(world_pool_size(7), 9);
}
#[test]
fn world_pool_size_matches_the_uploaded_image_counts() {
for texture_count in [0usize, 1, 7, 64] {
let gpu_textures = texture_count.max(1);
assert_eq!(
world_pool_size(texture_count),
gpu_textures + FALLBACK_TEXTURE_COUNT
);
assert!(world_pool_size(texture_count) > texture_count);
}
}
}