use ash::vk;
use concinnity_core::bake::texture::TextureImage;
use concinnity_core::gfx::render_types::clone_reserve;
use concinnity_core::render::backend_init::{SceneData, WorldShader};
use concinnity_core::render::error::RenderResult;
use concinnity_core::transform::IDENTITY;
use super::InitGpu;
use crate::vulkan::context::{
VkCull, VkCullKernels, VkDescriptors, VkSceneAssets, VkShadow, VkTargets,
};
use crate::vulkan::post::gbuffer::GbufferResources;
use crate::vulkan::probe_prefilter::ProbePrefilterPipelines;
mod bindless;
mod compute;
mod gbuffer;
mod shadow;
mod two_pass;
pub(super) struct CullPlan {
pub(super) n_instances: usize,
pub(super) n_cull: usize,
pub(super) bindless_active: bool,
pub(super) bindless_pool_size: usize,
pub(super) bindless_uab: bool,
}
pub(super) fn plan_cull(
gpu: &InitGpu<'_>,
world: &SceneData<'_>,
textures: &[TextureImage],
) -> CullPlan {
let hw = gpu.hw;
let draw_objects = &world.draw_objects;
let n_instances: usize = world
.instanced_clusters
.iter()
.map(|c| c.instances.len())
.sum();
let n_cull = draw_objects.len()
+ n_instances
+ world.n_chunk_max
+ clone_reserve(draw_objects.len())
+ world.n_skinned;
let bindless_active = n_cull > 0;
let bindless_pool_size = if bindless_active {
crate::vulkan::descriptor_layout::world_pool_size(textures.len())
} else {
0
};
let limits = super::descriptors::stage_limits(hw);
let pool_overflows = bindless_active
&& crate::vulkan::descriptor_layout::bindless_pool_needs_update_after_bind(
limits,
bindless_pool_size as u32,
);
if pool_overflows && !hw.update_after_bind {
tracing::warn!(
"bindless texture pool: {bindless_pool_size} images exceed the device's \
per-stage budget ({limits:?}) and update-after-bind is unavailable"
);
}
let bindless_uab = pool_overflows && hw.update_after_bind;
CullPlan {
n_instances,
n_cull,
bindless_active,
bindless_pool_size,
bindless_uab,
}
}
pub(super) struct CullInputs<'a> {
pub(super) world: &'a SceneData<'a>,
pub(super) world_shaders: &'a [WorldShader<'a>],
pub(super) plan: &'a CullPlan,
pub(super) occlusion_two_pass: bool,
pub(super) descriptors: &'a VkDescriptors,
pub(super) targets: &'a VkTargets,
pub(super) scene: &'a VkSceneAssets,
pub(super) shadow: &'a VkShadow,
pub(super) spot_slices: usize,
pub(super) gbuffer: Option<&'a GbufferResources>,
pub(super) swapchain_format: vk::Format,
}
pub(super) fn build_cull(
gpu: &InitGpu<'_>,
inputs: CullInputs<'_>,
) -> RenderResult<(VkCull, Option<ProbePrefilterPipelines>)> {
let CullInputs {
world,
world_shaders,
plan,
occlusion_two_pass,
descriptors,
targets,
scene,
shadow,
spot_slices,
gbuffer,
swapchain_format,
} = inputs;
let bindless = bindless::build_bindless_pass(
gpu,
bindless::BindlessInputs {
world_shaders,
plan,
descriptors,
targets,
scene,
material_params: &world.material_params,
swapchain_format,
gbuffer_render_pass: gbuffer.map(|gb| gb.prepass_render_pass.handle()),
},
)?;
let world_pipelines = bindless::build_world_pipelines(gpu, &bindless, world_shaders)?;
let shader_bucket_count = 1 + world_pipelines.len();
let compute = compute::build_compute_cull(
gpu,
compute::ComputeInputs {
world,
scene,
targets,
descriptors,
plan,
bindless: &bindless,
shader_bucket_count,
occlusion_two_pass,
},
)?;
let shadow_cull = shadow::build_shadow_cull(
gpu,
&bindless,
&compute,
shadow,
descriptors,
plan,
spot_slices,
)?;
let gbuffer_pass =
gbuffer::build_gbuffer_pass(gpu, &bindless, &compute, gbuffer, descriptors, plan)?;
let probe_prefilter = match compute.kernels.is_some() {
true => Some(ProbePrefilterPipelines::new(
&gpu.hw.device,
gpu.hot_reload,
)?),
false => None,
};
let two_pass = two_pass::build_two_pass_cull(
gpu,
&bindless,
&compute,
plan,
shader_bucket_count,
targets.msaa_samples,
occlusion_two_pass,
)?;
let cull_kernels = compute.kernels.map(|kernels| VkCullKernels {
pipeline_phase2: two_pass.pipeline,
..kernels
});
let cull = VkCull {
bindless_pipeline: bindless.pipeline,
bindless_pipeline_layout: bindless.pipeline_layout,
bindless_set_layout: bindless.set_layout,
bindless_pool_size: plan.bindless_pool_size,
bindless_update_after_bind: plan.bindless_uab,
world_pipelines: concinnity_core::render::world_pipelines::WorldPipelines::new(
world_pipelines,
),
bucket_stride: plan.n_cull,
bindless_main_spv: bindless.main_spv,
prepass_layout: bindless.prepass_layout,
prepass_pipeline: bindless.prepass_pipeline,
bindless_sets: bindless.sets,
object_buffers: bindless.object_buffers,
material_params: bindless.material_params,
cull_kernels,
cull_sets: compute.sets,
draw_args_buffers: compute.draw_args_buffers,
indirect_buffers: compute.indirect_buffers,
cull_status_buffers: compute.status_buffers,
occlusion_two_pass,
cull_sets2: two_pass.sets,
_two_pass_pool: two_pass.pool,
indirect_buffers2: two_pass.indirect_buffers,
main_render_pass_phase1: two_pass.main_render_pass_phase1,
main_render_pass_phase2: two_pass.main_render_pass_phase2,
hiz: compute.hiz,
hiz_valid: false,
hiz_prev_view_proj: IDENTITY,
shadow_cull_pipeline: shadow_cull.cull_pipeline,
shadow_cull_pipeline_layout: shadow_cull.cull_pipeline_layout,
_shadow_cull_set_layout: shadow_cull.set_layout,
shadow_cull_sets: shadow_cull.sets,
shadow_bindless_pipeline: shadow_cull.bindless_pipeline,
shadow_bindless_pipeline_layout: shadow_cull.bindless_pipeline_layout,
shadow_indirect_buffers: shadow_cull.indirect_buffers,
spot_cull_sets: shadow_cull.spot_cull_sets,
spot_indirect_buffers: shadow_cull.spot_indirect_buffers,
gbuffer_sets: gbuffer_pass.sets,
prev_model_buffers: gbuffer_pass.prev_model_buffers,
model_history: gbuffer_pass.model_history,
};
Ok((cull, probe_prefilter))
}