use ash::vk;
use concinnity_core::gfx::render_types;
use concinnity_core::render::backend_init::WorldShader;
use concinnity_core::render::error::{RenderError, RenderResult};
use super::CullPlan;
use crate::vulkan::context::{VkDescriptors, VkSceneAssets, VkTargets};
use crate::vulkan::init::InitGpu;
use crate::vulkan::material_params::{MATERIAL_PARAMS_BINDING, VkMaterialParams};
use crate::vulkan::owned::{OwnedPipeline, OwnedPipelineLayout, OwnedSetLayout};
use crate::vulkan::pipeline::*;
use crate::vulkan::post::gbuffer::{PrepassLayout, build_prepass_layout};
use crate::vulkan::resources::alloc_descriptor_sets;
use crate::vulkan::set_writes::SetWrites;
pub(super) struct BindlessPass {
pub(super) pipeline: Option<OwnedPipeline>,
pub(super) pipeline_layout: Option<OwnedPipelineLayout>,
pub(super) prepass_layout: Option<PrepassLayout>,
pub(super) bucket_targets: Option<BucketPipelineTargets>,
pub(super) prepass_pipeline: Option<OwnedPipeline>,
pub(super) set_layout: Option<OwnedSetLayout>,
pub(super) sets: Vec<vk::DescriptorSet>,
pub(super) object_buffers: Vec<crate::vulkan::allocator::PooledBuffer>,
pub(super) material_params: Option<VkMaterialParams>,
pub(super) main_spv: BindlessSpv,
}
pub(super) struct BindlessInputs<'a> {
pub(super) world_shaders: &'a [WorldShader<'a>],
pub(super) plan: &'a CullPlan,
pub(super) descriptors: &'a VkDescriptors,
pub(super) targets: &'a VkTargets,
pub(super) scene: &'a VkSceneAssets,
pub(super) material_params: &'a [render_types::GpuMaterialParams],
pub(super) swapchain_format: vk::Format,
pub(super) gbuffer_render_pass: Option<vk::RenderPass>,
}
pub(super) fn build_bindless_pass(
gpu: &InitGpu<'_>,
inputs: BindlessInputs<'_>,
) -> RenderResult<BindlessPass> {
let InitGpu {
hw,
frames,
hot_reload,
..
} = *gpu;
let (device, alloc) = (&hw.device, &hw.alloc);
let BindlessInputs {
world_shaders,
plan,
descriptors,
targets,
scene,
material_params,
swapchain_format,
gbuffer_render_pass,
} = inputs;
let CullPlan {
n_cull,
bindless_active,
bindless_pool_size,
bindless_uab,
..
} = *plan;
let (
bindless_pipeline,
bindless_pipeline_layout,
bucket_targets,
prepass_layout,
bindless_set_layout,
bindless_sets,
object_buffers,
params,
bindless_main_spv,
) = if bindless_active {
let set_bindings = [
vk::DescriptorSetLayoutBinding::default()
.binding(0)
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
.descriptor_count(1)
.stage_flags(vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT),
vk::DescriptorSetLayoutBinding::default()
.binding(1)
.descriptor_type(vk::DescriptorType::SAMPLED_IMAGE)
.descriptor_count(bindless_pool_size as u32)
.stage_flags(vk::ShaderStageFlags::FRAGMENT),
vk::DescriptorSetLayoutBinding::default()
.binding(MATERIAL_PARAMS_BINDING)
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
.descriptor_count(1)
.stage_flags(vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT),
];
let binding_flags = [
vk::DescriptorBindingFlags::empty(),
vk::DescriptorBindingFlags::UPDATE_AFTER_BIND,
vk::DescriptorBindingFlags::empty(),
];
let mut flags_info =
vk::DescriptorSetLayoutBindingFlagsCreateInfo::default().binding_flags(&binding_flags);
let mut set_info = vk::DescriptorSetLayoutCreateInfo::default().bindings(&set_bindings);
if bindless_uab {
set_info = set_info
.flags(vk::DescriptorSetLayoutCreateFlags::UPDATE_AFTER_BIND_POOL)
.push_next(&mut flags_info);
}
let set_layout = device
.create_descriptor_set_layout(&set_info)
.map_err(|e| crate::vulkan::error::map_vk_result(e, "bindless set layout"))?;
let layouts = [descriptors.global_set_layout.handle(), set_layout.handle()];
let pipeline_layout = device
.create_pipeline_layout(&vk::PipelineLayoutCreateInfo::default().set_layouts(&layouts))
.map_err(|e| crate::vulkan::error::map_vk_result(e, "bindless pipeline layout"))?;
let prepass_layout = build_prepass_layout(
device,
descriptors.global_set_layout.handle(),
set_layout.handle(),
)?;
let engine_spv = compile_bindless_shaders(hot_reload)?;
let bucket_targets = BucketPipelineTargets {
render_pass: targets.main_render_pass.handle(),
layout: pipeline_layout.handle(),
prepass: gbuffer_render_pass.map(|render_pass| PrepassTargets {
render_pass,
layout: prepass_layout.pipeline_layout.handle(),
}),
msaa_samples: targets.msaa_samples,
swapchain_format,
hot_reload,
template_generation: 0,
};
let pipelines =
build_bucket_pipeline(device, bucket_targets, 0, world_shaders[0], &engine_spv)?;
let object_buffer_size =
(n_cull * std::mem::size_of::<render_types::GpuObjectData>()) as u64;
let mut buffers = Vec::with_capacity(frames);
for _ in 0..frames {
buffers.push(alloc.create_buffer(
object_buffer_size,
vk::BufferUsageFlags::STORAGE_BUFFER,
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
)?);
}
let params = VkMaterialParams::new(alloc, material_params.to_vec(), frames)?;
let set_layouts: Vec<_> = (0..frames).map(|_| set_layout.handle()).collect();
let sets =
alloc_descriptor_sets(device, descriptors.descriptor_pool.handle(), &set_layouts)?;
let mut pool_infos: Vec<vk::DescriptorImageInfo> = scene
.textures
.iter()
.chain(scene.fallback_textures.iter())
.map(|img| {
vk::DescriptorImageInfo::default()
.image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
.image_view(img.view)
})
.collect();
if let Some(&tail) = pool_infos.last() {
pool_infos.resize(bindless_pool_size, tail);
}
for (i, &set) in sets.iter().enumerate() {
let params_info = params.descriptor(i);
SetWrites::new(set)
.storage_buffer(0, buffers[i].buffer(), object_buffer_size)
.images(1, vk::DescriptorType::SAMPLED_IMAGE, &pool_infos)
.storage_buffer(
MATERIAL_PARAMS_BINDING,
params_info.buffer,
params_info.range,
)
.apply(device);
}
(
Some(pipelines),
Some(pipeline_layout),
Some(bucket_targets),
Some(prepass_layout),
Some(set_layout),
sets,
buffers,
Some(params),
engine_spv,
)
} else {
(
None,
None,
None,
None,
None,
Vec::new(),
Vec::new(),
None,
BindlessSpv {
vert: Vec::new(),
frag: Vec::new(),
prepass_vert: Vec::new(),
prepass_frag: Vec::new(),
},
)
};
let (pipeline, prepass_pipeline) = match bindless_pipeline {
Some(p) => (Some(p.main), p.prepass),
None => (None, None),
};
Ok(BindlessPass {
pipeline,
pipeline_layout: bindless_pipeline_layout,
prepass_layout,
bucket_targets,
prepass_pipeline,
set_layout: bindless_set_layout,
sets: bindless_sets,
object_buffers,
material_params: params,
main_spv: bindless_main_spv,
})
}
pub(super) fn build_world_pipelines(
gpu: &InitGpu<'_>,
bindless: &BindlessPass,
world_shaders: &[WorldShader<'_>],
) -> RenderResult<Vec<Option<BucketPipelines>>> {
let bucket_shaders = world_shaders.get(1..).unwrap_or(&[]);
let Some(targets) = bindless
.bucket_targets
.filter(|_| !bucket_shaders.is_empty())
else {
return Ok(Vec::new());
};
let max = render_types::MAX_SHADER_BUCKETS;
if bucket_shaders.len() + 1 > max {
return Err(RenderError::Other(format!(
"world declares {} Shaders but at most {max} can be routed",
bucket_shaders.len() + 1
)));
}
build_world_pipeline_table(&gpu.hw.device, targets, bucket_shaders, &bindless.main_spv)
}