rend3_routine/culling/
cpu.rs1use 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#[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
27pub 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 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
118pub 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 let (_, internal) = materials.get_internal_material_full_by_index::<M>(draw.material_index as usize);
133
134 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}