use crate::resources::FrustumUniform;
const CULL_BGL_ENTRY_COUNT: usize = 6;
pub(super) struct CullResources {
cull_instances_pipeline: wgpu::ComputePipeline,
write_indirect_args_pipeline: wgpu::ComputePipeline,
bgl: wgpu::BindGroupLayout,
pub(super) frustum_buf: wgpu::Buffer,
pub(super) cascade_frustum_bufs: [wgpu::Buffer; 4],
}
impl CullResources {
pub(super) fn new(device: &wgpu::Device) -> Self {
let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("cull_bgl"),
entries: &Self::bgl_entries(),
});
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("cull_shader"),
source: wgpu::ShaderSource::Wgsl(
include_str!("../shaders/cull.wgsl").into(),
),
});
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("cull_pipeline_layout"),
bind_group_layouts: &[&bgl],
push_constant_ranges: &[],
});
let cull_instances_pipeline =
device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: Some("cull_instances_pipeline"),
layout: Some(&layout),
module: &shader,
entry_point: Some("cull_instances"),
compilation_options: wgpu::PipelineCompilationOptions::default(),
cache: None,
});
let write_indirect_args_pipeline =
device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: Some("write_indirect_args_pipeline"),
layout: Some(&layout),
module: &shader,
entry_point: Some("write_indirect_args"),
compilation_options: wgpu::PipelineCompilationOptions::default(),
cache: None,
});
let frustum_buf = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("cull_frustum_buf"),
size: std::mem::size_of::<FrustumUniform>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let cascade_frustum_bufs = std::array::from_fn(|i| {
device.create_buffer(&wgpu::BufferDescriptor {
label: Some(&format!("cull_cascade_frustum_buf_{i}")),
size: std::mem::size_of::<FrustumUniform>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
})
});
Self {
cull_instances_pipeline,
write_indirect_args_pipeline,
bgl,
frustum_buf,
cascade_frustum_bufs,
}
}
pub(super) fn dispatch(
&self,
encoder: &mut wgpu::CommandEncoder,
device: &wgpu::Device,
queue: &wgpu::Queue,
frustum: &FrustumUniform,
aabb_buf: &wgpu::Buffer,
meta_buf: &wgpu::Buffer,
counter_buf: &wgpu::Buffer,
vis_buf: &wgpu::Buffer,
indirect_buf: &wgpu::Buffer,
instance_count: u32,
batch_count: u32,
) {
queue.write_buffer(
&self.frustum_buf,
0,
bytemuck::cast_slice(std::slice::from_ref(frustum)),
);
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("cull_bg"),
layout: &self.bgl,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: self.frustum_buf.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: aabb_buf.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 2,
resource: meta_buf.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 3,
resource: counter_buf.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 4,
resource: vis_buf.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 5,
resource: indirect_buf.as_entire_binding(),
},
],
});
{
let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some("cull_instances_pass"),
timestamp_writes: None,
});
pass.set_pipeline(&self.cull_instances_pipeline);
pass.set_bind_group(0, &bind_group, &[]);
let groups = instance_count.div_ceil(64);
pass.dispatch_workgroups(groups, 1, 1);
}
{
let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some("write_indirect_args_pass"),
timestamp_writes: None,
});
pass.set_pipeline(&self.write_indirect_args_pipeline);
pass.set_bind_group(0, &bind_group, &[]);
let groups = batch_count.div_ceil(64);
pass.dispatch_workgroups(groups, 1, 1);
}
}
pub(super) fn dispatch_shadow(
&self,
encoder: &mut wgpu::CommandEncoder,
device: &wgpu::Device,
queue: &wgpu::Queue,
cascade_idx: usize,
frustum: &FrustumUniform,
aabb_buf: &wgpu::Buffer,
meta_buf: &wgpu::Buffer,
counter_buf: &wgpu::Buffer,
shadow_vis_buf: &wgpu::Buffer,
shadow_indirect_buf: &wgpu::Buffer,
instance_count: u32,
batch_count: u32,
) {
queue.write_buffer(
&self.cascade_frustum_bufs[cascade_idx],
0,
bytemuck::cast_slice(std::slice::from_ref(frustum)),
);
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some(&format!("shadow_cull_bg_{cascade_idx}")),
layout: &self.bgl,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: self.cascade_frustum_bufs[cascade_idx].as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: aabb_buf.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 2,
resource: meta_buf.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 3,
resource: counter_buf.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 4,
resource: shadow_vis_buf.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 5,
resource: shadow_indirect_buf.as_entire_binding(),
},
],
});
{
let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some(&format!("shadow_cull_instances_pass_{cascade_idx}")),
timestamp_writes: None,
});
pass.set_pipeline(&self.cull_instances_pipeline);
pass.set_bind_group(0, &bind_group, &[]);
pass.dispatch_workgroups(instance_count.div_ceil(64), 1, 1);
}
{
let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some(&format!("shadow_write_indirect_args_pass_{cascade_idx}")),
timestamp_writes: None,
});
pass.set_pipeline(&self.write_indirect_args_pipeline);
pass.set_bind_group(0, &bind_group, &[]);
pass.dispatch_workgroups(batch_count.div_ceil(64), 1, 1);
}
}
fn bgl_entries() -> [wgpu::BindGroupLayoutEntry; CULL_BGL_ENTRY_COUNT] {
let compute = wgpu::ShaderStages::COMPUTE;
[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: compute,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: compute,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: compute,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 3,
visibility: compute,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: false },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 4,
visibility: compute,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: false },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 5,
visibility: compute,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: false },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
]
}
}