Skip to main content

primase_core/
unreferenced.rs

1//! `drop-unreferenced-textures`: the texture entities nothing in the world refers to, despawned, so a program that
2//! writes only what its materials reach stops holding the rest.
3
4use crate::pass::{Pass, PassLog, PassSpec};
5use crate::{Texture, TextureFile, Transform, World};
6
7/// `drop-unreferenced-textures`: every texture entity (one holding `core.texture` or `core.texture_file`) that no
8/// component in the world refers to and nothing places (no `core.transform`) is despawned with everything it holds,
9/// counted, with the bytes of its loaded levels. A texture no material, binding or form names is reached by no writer
10/// that writes what materials bind; one a program writes on its own (a texture export) is not this pass's to drop, so
11/// the pass is the program's statement, never a writer's default.
12#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
13pub struct DropUnreferencedTextures;
14
15/// `drop-unreferenced-textures`' declaration ([`crate::pass::PassRegistry::add`]): no options.
16pub const DROP_UNREFERENCED_TEXTURES: PassSpec = PassSpec {
17    name: "drop-unreferenced-textures",
18    options: &[],
19    make: |_| Ok(Box::new(DropUnreferencedTextures)),
20};
21
22impl Pass for DropUnreferencedTextures {
23    fn name(&self) -> &'static str {
24        "drop-unreferenced-textures"
25    }
26    fn despawns_entities(&self) -> bool {
27        true
28    }
29    fn run(&self, w: &mut World, log: &mut PassLog) -> Result<(), String> {
30        let dropped: Vec<_> = w
31            .unreferenced()
32            .into_iter()
33            .filter(|&e| (w.has::<Texture>(e) || w.has::<TextureFile>(e)) && !w.has::<Transform>(e))
34            .collect();
35        let bytes: usize = dropped
36            .iter()
37            .filter_map(|&e| w.get::<Texture>(e))
38            .flat_map(|t| t.levels.iter().map(Vec::len))
39            .sum();
40        log.count("unreferenced textures dropped", dropped.len() as u64);
41        log.count("bytes of the unreferenced textures' loaded levels", bytes as u64);
42        for e in dropped {
43            crate::ledger::despawn(w, e, log, "textures nothing refers to");
44        }
45        Ok(())
46    }
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52    use crate::pass::{PassManager, PassRegistry, Pipeline, RunReport};
53    use crate::pbr::{PbrMaterial, TextureRef};
54    use crate::{ColorSpace, Name, TexFormat, TexRole};
55
56    fn texture(w: &mut World, name: &str) -> crate::Entity {
57        let e = w.spawn();
58        w.insert(e, Name(name.into()));
59        w.insert(
60            e,
61            Texture {
62                width: 1,
63                height: 1,
64                format: TexFormat::Rgba8Unorm,
65                levels: vec![vec![1, 2, 3, 4]],
66                color_space: ColorSpace::Srgb,
67                role: TexRole::Color,
68                normal: None,
69            },
70        );
71        e
72    }
73
74    /// The texture a material binds stays, as does a placed one; the one nothing names is despawned, counted with its
75    /// bytes, and declared dropped.
76    #[test]
77    fn a_texture_nothing_refers_to_is_dropped_and_counted() {
78        let mut w = World::new();
79        let bound = texture(&mut w, "bound");
80        let placed = texture(&mut w, "placed");
81        w.insert(placed, Transform::IDENTITY);
82        let loose = texture(&mut w, "loose");
83        let material = w.spawn();
84        w.insert(
85            material,
86            PbrMaterial {
87                base_color_texture: Some(TextureRef::new(bound)),
88                ..Default::default()
89            },
90        );
91        let mut report = RunReport::default();
92        PassManager::new(PassRegistry::new())
93            .with_pass_checks(true)
94            .run(
95                &mut w,
96                &Pipeline {
97                    passes: vec![Box::new(DropUnreferencedTextures)],
98                },
99                &mut report,
100            )
101            .unwrap();
102        assert!(w.is_alive(bound) && w.is_alive(placed) && !w.is_alive(loose));
103        let counts = &report.steps[0].log.counts;
104        assert_eq!(counts["unreferenced textures dropped"], 1);
105        assert_eq!(counts["bytes of the unreferenced textures' loaded levels"], 4);
106    }
107}