rend3_routine/culling/
cpu.rs

1use glam::{Mat4, Vec3};
2use rend3::{
3    managers::{CameraManager, InternalObject, MaterialManager, ObjectManager},
4    types::Material,
5    util::frustum::ShaderFrustum,
6    ProfileData,
7};
8use wgpu::{
9    util::{BufferInitDescriptor, DeviceExt},
10    BufferUsages, Device, RenderPass,
11};
12
13use crate::{
14    common::{PerObjectDataAbi, Sorting},
15    culling::CulledObjectSet,
16};
17
18/// All the information needed to dispatch a CPU draw call.
19#[derive(Debug, Clone)]
20pub struct CpuDrawCall {
21    pub start_idx: u32,
22    pub end_idx: u32,
23    pub vertex_offset: i32,
24    pub material_index: u32,
25}
26
27/// Do all object culling on the CPU and upload the per-object data to the GPU.
28pub fn cull_cpu<M: Material>(
29    device: &Device,
30    camera: &CameraManager,
31    objects: &ObjectManager,
32    sorting: Option<Sorting>,
33    key: u64,
34) -> CulledObjectSet {
35    profiling::scope!("CPU Culling");
36    let frustum = ShaderFrustum::from_matrix(camera.proj());
37    let view = camera.view();
38    let view_proj = camera.view_proj();
39
40    let objects = objects.get_objects::<M>(key);
41
42    let objects = crate::common::sort_objects(objects, camera, sorting);
43
44    let (mut outputs, calls) = cull_internal(&objects, frustum, view, view_proj);
45
46    assert_eq!(calls.len(), outputs.len());
47
48    if outputs.is_empty() {
49        // Dummy data
50        outputs.push(PerObjectDataAbi {
51            model_view: Mat4::ZERO,
52            model_view_proj: Mat4::ZERO,
53            pad0: [0; 12],
54            material_idx: 0,
55            inv_squared_scale: Vec3::ZERO,
56        });
57    }
58
59    let output_buffer = device.create_buffer_init(&BufferInitDescriptor {
60        label: Some("culling output"),
61        contents: bytemuck::cast_slice(&outputs),
62        usage: BufferUsages::STORAGE,
63    });
64
65    CulledObjectSet {
66        calls: ProfileData::Cpu(calls),
67        output_buffer,
68    }
69}
70
71fn cull_internal(
72    objects: &[InternalObject],
73    frustum: ShaderFrustum,
74    view: Mat4,
75    view_proj: Mat4,
76) -> (Vec<PerObjectDataAbi>, Vec<CpuDrawCall>) {
77    let mut outputs = Vec::with_capacity(objects.len());
78    let mut calls = Vec::with_capacity(objects.len());
79
80    for object in objects {
81        let model = object.input.transform;
82        let model_view = view * model;
83
84        let transformed = object.input.bounding_sphere.apply_transform(model_view);
85        if !frustum.contains_sphere(transformed) {
86            continue;
87        }
88
89        let model_view_proj = view_proj * model;
90
91        calls.push(CpuDrawCall {
92            start_idx: object.input.start_idx,
93            end_idx: object.input.start_idx + object.input.count,
94            vertex_offset: object.input.vertex_offset,
95            material_index: object.input.material_index,
96        });
97
98        let squared_scale = Vec3::new(
99            model_view.x_axis.truncate().length_squared(),
100            model_view.y_axis.truncate().length_squared(),
101            model_view.z_axis.truncate().length_squared(),
102        );
103
104        let inv_squared_scale = squared_scale.recip();
105
106        outputs.push(PerObjectDataAbi {
107            model_view,
108            model_view_proj,
109            material_idx: 0,
110            pad0: [0; 12],
111            inv_squared_scale,
112        });
113    }
114
115    (outputs, calls)
116}
117
118/// Draw the given cpu draw calls.
119///
120/// No-op if there are 0 objects.
121pub fn draw_cpu_powered<'rpass, M: Material>(
122    rpass: &mut RenderPass<'rpass>,
123    draws: &'rpass [CpuDrawCall],
124    materials: &'rpass MaterialManager,
125    material_binding_index: u32,
126) {
127    let mut previous_mat_handle = None;
128    for (idx, draw) in draws.iter().enumerate() {
129        if previous_mat_handle != Some(draw.material_index) {
130            previous_mat_handle = Some(draw.material_index);
131            // TODO(material): only resolve the archetype lookup once
132            let (_, internal) = materials.get_internal_material_full_by_index::<M>(draw.material_index as usize);
133
134            // TODO: GL always gets linear sampling.
135
136            rpass.set_bind_group(material_binding_index, internal.bind_group.as_ref().as_cpu(), &[]);
137        }
138        let idx = idx as u32;
139        rpass.draw_indexed(draw.start_idx..draw.end_idx, draw.vertex_offset, idx..idx + 1);
140    }
141}