1use crate::{Mat4, Mesh, shaders::LitSettings};
3use std::{
4 cell::RefCell,
5 rc::{Rc, Weak},
6};
7
8const MAX_MESHES: usize = 1024;
9const MAX_BYTES: usize = 64 * 1024 * 1024;
10const MAX_INSTANCES: usize = 65536;
11#[derive(Clone)]
12pub struct ResidentMesh(Rc<Geometry>);
13pub(crate) struct Geometry {
14 pub id: u32,
15 pub vertices: Vec<[f32; 10]>,
16 opaque: bool,
17 bounds: ferravex_graphics::Bounds,
18}
19#[derive(Default)]
20struct Registry {
21 next: u32,
22 meshes: Vec<Weak<Geometry>>,
23}
24thread_local! {static REGISTRY: RefCell<Registry> = RefCell::new(Registry::default());}
25impl ResidentMesh {
26 pub fn new(mesh: &Mesh) -> Result<Self, &'static str> {
28 if mesh.indices.is_empty() || !mesh.indices.len().is_multiple_of(3) {
29 return Err("mesh requires complete triangles");
30 }
31 if mesh.indices.len() > MAX_BYTES / 40 {
32 return Err("mesh exceeds retained storage budget");
33 }
34 if mesh.indices.iter().any(|i| {
35 mesh.vertices
36 .get(*i as usize)
37 .is_none_or(|v| !v.position.is_finite() || !v.normal.is_finite())
38 }) {
39 return Err("invalid retained mesh geometry");
40 }
41 REGISTRY.with(|registry| {
42 let mut r = registry.borrow_mut();
43 r.meshes.retain(|m| m.strong_count() > 0);
44 let bytes: usize = r
45 .meshes
46 .iter()
47 .filter_map(Weak::upgrade)
48 .map(|m| m.vertices.len() * 40)
49 .sum();
50 if r.meshes.len() >= MAX_MESHES || bytes + mesh.indices.len() * 40 > MAX_BYTES {
51 return Err("retained storage capacity exceeded");
52 }
53 r.next = r.next.checked_add(1).ok_or("mesh identity exhausted")?;
54 let data = Rc::new(Geometry {
55 id: r.next,
56 bounds: mesh
57 .indices
58 .iter()
59 .map(|i| mesh.vertices[*i as usize].position)
60 .fold(
61 ferravex_graphics::Bounds {
62 min: crate::Vec3::splat(f32::INFINITY),
63 max: crate::Vec3::splat(f32::NEG_INFINITY),
64 },
65 |b, p| ferravex_graphics::Bounds {
66 min: b.min.min(p),
67 max: b.max.max(p),
68 },
69 ),
70 opaque: mesh
71 .indices
72 .iter()
73 .all(|i| mesh.vertices[*i as usize].color[3] == 255),
74 vertices: mesh
75 .indices
76 .iter()
77 .map(|i| {
78 let v = mesh.vertices[*i as usize];
79 [
80 v.position.x,
81 v.position.y,
82 v.position.z,
83 v.normal.x,
84 v.normal.y,
85 v.normal.z,
86 v.color[0] as f32 / 255.,
87 v.color[1] as f32 / 255.,
88 v.color[2] as f32 / 255.,
89 v.color[3] as f32 / 255.,
90 ]
91 })
92 .collect(),
93 });
94 r.meshes.push(Rc::downgrade(&data));
95 Ok(Self(data))
96 })
97 }
98 pub fn id(&self) -> u32 {
99 self.0.id
100 }
101 pub fn vertex_count(&self) -> usize {
102 self.0.vertices.len()
103 }
104 pub(crate) fn is_opaque(&self) -> bool {
105 self.0.opaque
106 }
107 pub fn bounds(&self) -> ferravex_graphics::Bounds {
108 self.0.bounds
109 }
110}
111pub(crate) fn geometry(id: u32) -> Option<Rc<Geometry>> {
112 REGISTRY.with(|r| {
113 r.borrow()
114 .meshes
115 .iter()
116 .filter_map(Weak::upgrade)
117 .find(|m| m.id == id)
118 })
119}
120#[cfg(not(target_arch = "wasm32"))]
121pub(crate) fn is_live(id: u32) -> bool {
122 geometry(id).is_some()
123}
124#[repr(C)]
126#[derive(Clone, Copy, Debug)]
127pub struct Command {
128 pub kind: u32,
129 pub index: u32,
130}
131#[repr(C)]
133#[derive(Clone, Copy, Debug, PartialEq)]
134pub struct RetainedBatch {
135 pub mesh: u32,
136 pub first: u32,
137 pub count: u32,
138 pub depth: u32,
140 pub camera: [f32; 16],
141 pub light: [f32; 16],
142 pub params: [f32; 16],
143 pub material: u32,
144 pub revision: u32,
145 pub reserved: [u32; 2],
146}
147#[derive(Default)]
148pub(crate) struct Frame {
149 pub commands: Vec<Command>,
150 pub batches: Vec<RetainedBatch>,
151 pub instances: Vec<[f32; 28]>,
152 pub environment: crate::lighting::Environment,
153 scratch_instances: Vec<[f32; 28]>,
154 scratch_batches: Vec<RetainedBatch>,
155 keep_alive: Vec<ResidentMesh>,
156 keep_materials: Vec<crate::material::ShaderMaterial>,
157}
158#[derive(Default, Clone, Copy)]
159pub(crate) struct DrawStyle<'a> {
160 pub light: Option<LitSettings>,
161 pub material: Option<&'a crate::material::ShaderMaterial>,
162 pub params: [f32; 16],
163 pub shadow_only: bool,
164 pub skip_shadow: bool,
165}
166impl RetainedBatch {
167 fn same_pass(&self, other: &Self) -> bool {
168 self.camera == other.camera
169 && self.light == other.light
170 && self.params == other.params
171 && self.material == other.material
172 && self.revision == other.revision
173 && self.depth & 7 == other.depth & 7
174 }
175 fn compatible(&self, other: &Self) -> bool {
176 self.mesh == other.mesh && self.depth == other.depth && self.same_pass(other)
177 }
178}
179impl Frame {
180 pub fn clear(&mut self) {
181 self.commands.clear();
182 self.batches.clear();
183 self.instances.clear();
184 self.keep_alive.clear();
185 self.keep_materials.clear();
186 }
187 #[cfg(test)]
188 pub fn record(
189 &mut self,
190 mesh: &ResidentMesh,
191 model: Mat4,
192 camera: Mat4,
193 depth: bool,
194 light: Option<LitSettings>,
195 ) -> bool {
196 self.record_style(
197 mesh,
198 model,
199 camera,
200 depth,
201 DrawStyle {
202 light,
203 ..Default::default()
204 },
205 )
206 }
207 pub fn record_style(
208 &mut self,
209 mesh: &ResidentMesh,
210 model: Mat4,
211 camera: Mat4,
212 depth: bool,
213 style: DrawStyle<'_>,
214 ) -> bool {
215 let light = style.light;
216 if self.instances.len() >= MAX_INSTANCES || !model.is_finite() || !camera.is_finite() {
217 return false;
218 }
219 let Some(inverse) = model.try_inverse() else {
220 return false;
221 };
222 if model.to_cols_array()[3] != 0.
223 || model.to_cols_array()[7] != 0.
224 || model.to_cols_array()[11] != 0.
225 || model.to_cols_array()[15] != 1.
226 {
227 return false;
228 }
229 let normal = inverse.transpose().to_cols_array();
230 let mut instance = [0.; 28];
231 instance[..16].copy_from_slice(&model.to_cols_array());
232 for i in 0..3 {
233 instance[16 + i * 4..19 + i * 4].copy_from_slice(&normal[i * 4..i * 4 + 3]);
234 }
235 let l = light.unwrap_or_default().sanitized();
236 let lighting = [
237 l.light_direction.x,
238 l.light_direction.y,
239 l.light_direction.z,
240 if light.is_some() { 1. } else { 0. },
241 l.camera_position.x,
242 l.camera_position.y,
243 l.camera_position.z,
244 l.ambient,
245 l.fog_color.r,
246 l.fog_color.g,
247 l.fog_color.b,
248 l.fog_color.a,
249 l.fog_start,
250 l.fog_end,
251 0.,
252 if mesh.0.opaque
253 && style
254 .material
255 .is_none_or(|m| m.0.options.blend == crate::material::BlendMode::Opaque)
256 {
257 1.
258 } else {
259 0.
260 },
261 ];
262 let mut depth_flags = depth as u32;
263 if let Some(m) = style.material {
264 if m.0.options.blend != crate::material::BlendMode::Opaque {
265 depth_flags |= 2;
266 }
267 if m.0.options.blend == crate::material::BlendMode::Additive {
268 depth_flags |= 4;
269 }
270 if !m.0.options.casts_shadows {
271 depth_flags |= 8;
272 }
273 if !m.0.options.receives_shadows {
274 depth_flags |= 16;
275 }
276 }
277 if style.skip_shadow {
278 depth_flags |= 8;
279 }
280 if style.shadow_only {
281 depth_flags |= 32;
282 }
283 let batch = RetainedBatch {
284 mesh: mesh.id(),
285 first: self.instances.len() as u32,
286 count: 1,
287 depth: depth_flags,
288 camera: camera.to_cols_array(),
289 light: lighting,
290 params: style.params,
291 material: style.material.map_or(0, |m| m.id()),
292 revision: style.material.map_or(0, |m| m.0.revision.get()),
293 reserved: [0; 2],
294 };
295 if let Some(last) = self.batches.last_mut().filter(|last| {
296 self.commands.last().is_some_and(|c| c.kind == 1) && last.compatible(&batch)
297 }) {
298 last.count += 1;
299 } else {
300 self.commands.push(Command {
301 kind: 1,
302 index: self.batches.len() as u32,
303 });
304 self.batches.push(batch);
305 self.keep_alive.push(mesh.clone());
306 if let Some(material) = style.material {
307 self.keep_materials.push(material.clone());
308 }
309 }
310 self.instances.push(instance);
311 true
312 }
313 pub fn finalize(&mut self) {
316 let opaque = |c: &Command| {
317 c.kind == 1
318 && self.batches[c.index as usize].depth & 7 == 1
319 && self.batches[c.index as usize].light[15] > 0.5
320 };
321 let mut begin = 0;
322 while begin < self.commands.len() {
323 if !opaque(&self.commands[begin]) {
324 begin += 1;
325 continue;
326 }
327 let first = self.batches[self.commands[begin].index as usize];
328 let mut end = begin + 1;
329 while end < self.commands.len() && opaque(&self.commands[end]) {
330 let b = self.batches[self.commands[end].index as usize];
331 if !b.same_pass(&first) {
332 break;
333 }
334 end += 1;
335 }
336 self.commands[begin..end].sort_unstable_by_key(|c| {
337 let b = self.batches[c.index as usize];
338 (b.mesh, b.depth)
339 });
340 begin = end;
341 }
342 self.scratch_instances.clear();
343 self.scratch_batches.clear();
344 let mut written = 0;
345 let mut previous_retained = false;
346 for i in 0..self.commands.len() {
347 let c = self.commands[i];
348 if c.kind == 0 {
349 self.commands[written] = c;
350 written += 1;
351 previous_retained = false;
352 continue;
353 }
354 let mut batch = self.batches[c.index as usize];
355 let source =
356 &self.instances[batch.first as usize..(batch.first + batch.count) as usize];
357 batch.first = self.scratch_instances.len() as u32;
358 self.scratch_instances.extend_from_slice(source);
359 if let Some(last) = self
360 .scratch_batches
361 .last_mut()
362 .filter(|last| previous_retained && last.compatible(&batch))
363 {
364 last.count += batch.count;
365 } else {
366 self.commands[written] = Command {
367 kind: 1,
368 index: self.scratch_batches.len() as u32,
369 };
370 written += 1;
371 self.scratch_batches.push(batch);
372 }
373 previous_retained = true;
374 }
375 self.commands.truncate(written);
376 std::mem::swap(&mut self.instances, &mut self.scratch_instances);
377 std::mem::swap(&mut self.batches, &mut self.scratch_batches);
378 }
379}
380
381pub const VERTEX_BODY: &str = r#"
383attribute vec3 a_position;
384attribute vec3 a_normal;
385attribute vec4 a_color;
386attribute vec4 a_model0; attribute vec4 a_model1; attribute vec4 a_model2; attribute vec4 a_model3;
387attribute vec4 a_normal0; attribute vec4 a_normal1; attribute vec4 a_normal2;
388uniform mat4 u_camera;
389uniform vec4 u_light[4];
390uniform vec4 u_params[4];
391varying vec4 v_color;
392varying vec3 v_world;
393varying vec3 v_normal;
394vec3 ferravex_vertex(vec3 position);
395vec3 ferravex_normal(vec3 normal);
396void main(){
397 mat4 model=mat4(a_model0,a_model1,a_model2,a_model3);
398 vec4 world=model*vec4(ferravex_vertex(a_position),1.0);
399 vec3 normal=mat3(a_normal0.xyz,a_normal1.xyz,a_normal2.xyz)*ferravex_normal(a_normal);
400 float n=length(normal);normal=n>0.0?normal/n:vec3(0.0);
401 gl_Position=u_camera*world;v_color=a_color;v_world=world.xyz;v_normal=normal;
402}
403"#;
404#[cfg(test)]
405mod tests {
406 use super::*;
407 use crate::{Color, Vec3, mesh::Mesh3d};
408 fn mesh() -> ResidentMesh {
409 ResidentMesh::new(&Mesh3d::cuboid(Vec3::ONE, Color::new(1., 1., 1., 1.)).mesh).unwrap()
410 }
411 #[test]
412 fn compatible_instances_merge_but_camera_and_order_are_preserved() {
413 let mesh = mesh();
414 let mut f = Frame::default();
415 assert!(f.record(&mesh, Mat4::IDENTITY, Mat4::IDENTITY, true, None));
416 f.record(
417 &mesh,
418 Mat4::from_translation(Vec3::X),
419 Mat4::IDENTITY,
420 true,
421 None,
422 );
423 assert_eq!(f.batches.len(), 1);
424 assert_eq!(f.batches[0].count, 2);
425 f.commands.push(Command { kind: 0, index: 0 });
426 f.record(&mesh, Mat4::IDENTITY, Mat4::IDENTITY, true, None);
427 f.record(
428 &mesh,
429 Mat4::IDENTITY,
430 Mat4::from_translation(Vec3::X),
431 true,
432 None,
433 );
434 assert_eq!(f.batches.len(), 3);
435 assert_eq!(f.commands.len(), 4);
436 assert!(!f.record(
437 &mesh,
438 Mat4::from_scale(Vec3::ZERO),
439 Mat4::IDENTITY,
440 true,
441 None
442 ));
443 }
444 #[test]
445 fn lifetime_keeps_draws_valid_and_ids_never_recycle() {
446 let mut f = Frame::default();
447 let id;
448 {
449 let mesh = mesh();
450 id = mesh.id();
451 f.record(&mesh, Mat4::IDENTITY, Mat4::IDENTITY, true, None);
452 }
453 assert!(is_live(id));
454 f.clear();
455 assert!(!is_live(id));
456 assert_ne!(id, mesh().id());
457 }
458 #[test]
459 fn wire_layout_and_normal_matrix_are_defined() {
460 assert_eq!(std::mem::size_of::<Command>(), 8);
461 assert_eq!(std::mem::size_of::<RetainedBatch>(), 224);
462 assert_eq!(std::mem::offset_of!(RetainedBatch, camera), 16);
463 assert_eq!(std::mem::offset_of!(RetainedBatch, light), 80);
464 assert_eq!(std::mem::offset_of!(RetainedBatch, params), 144);
465 assert_eq!(std::mem::offset_of!(RetainedBatch, material), 208);
466 let mut f = Frame::default();
467 f.record(
468 &mesh(),
469 Mat4::from_scale(Vec3::new(2., 4., 8.)),
470 Mat4::IDENTITY,
471 true,
472 None,
473 );
474 assert_eq!(f.instances[0][16], 0.5);
475 assert_eq!(f.instances[0][21], 0.25);
476 assert_eq!(f.instances[0][26], 0.125);
477 }
478 #[test]
479 fn material_and_parameter_changes_cannot_be_merged() {
480 use crate::material::{MaterialOptions, ShaderMaterial};
481 let mesh = mesh();
482 let a = ShaderMaterial::new(
483 Default::default(),
484 MaterialOptions {
485 blend: crate::material::BlendMode::Opaque,
486 ..Default::default()
487 },
488 )
489 .unwrap();
490 let b = ShaderMaterial::new(
491 Default::default(),
492 MaterialOptions {
493 blend: crate::material::BlendMode::Opaque,
494 ..Default::default()
495 },
496 )
497 .unwrap();
498 let mut f = Frame::default();
499 for (m, v) in [(&a, 1.), (&b, 1.), (&b, 2.)] {
500 f.record_style(
501 &mesh,
502 Mat4::IDENTITY,
503 Mat4::IDENTITY,
504 true,
505 DrawStyle {
506 material: Some(m),
507 params: [v; 16],
508 ..Default::default()
509 },
510 );
511 }
512 f.finalize();
513 assert_eq!(f.batches.len(), 3);
514 assert_eq!(
515 f.batches
516 .iter()
517 .map(|b| (b.material, b.params[0]))
518 .collect::<Vec<_>>(),
519 [(a.id(), 1.), (b.id(), 1.), (b.id(), 2.)]
520 );
521 }
522 #[test]
523 fn opaque_reordering_copies_instances_and_stops_at_ui_barriers() {
524 let a = mesh();
525 let b = mesh();
526 let mut f = Frame::default();
527 for (i, m) in [&a, &b, &a].into_iter().enumerate() {
528 f.record(
529 m,
530 Mat4::from_translation(Vec3::X * i as f32),
531 Mat4::IDENTITY,
532 true,
533 None,
534 );
535 }
536 f.commands.push(Command { kind: 0, index: 0 });
537 f.record(&a, Mat4::IDENTITY, Mat4::IDENTITY, true, None);
538 f.finalize();
539 assert_eq!(f.commands.len(), 4);
540 assert_eq!(f.batches[0].count, 2);
541 assert_eq!(
542 f.instances.iter().map(|i| i[12]).collect::<Vec<_>>(),
543 [0., 2., 1., 0.]
544 );
545 }
546}