Skip to main content

oxihuman_viewer/
draw_call_sort.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Draw call sorting — sorts draw calls by material, depth, and layer for efficiency.
6
7/// Draw call sort key (higher = drawn later).
8#[allow(dead_code)]
9#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
10pub struct DrawSortKey(pub u64);
11
12/// Sort strategy.
13#[allow(dead_code)]
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum SortStrategy {
16    /// Sort front-to-back (opaque).
17    FrontToBack,
18    /// Sort back-to-front (transparent).
19    BackToFront,
20    /// Sort by material id to minimise state changes.
21    ByMaterial,
22}
23
24/// A draw call entry.
25#[allow(dead_code)]
26#[derive(Debug, Clone)]
27pub struct DrawEntry {
28    pub id: u32,
29    pub material_id: u32,
30    pub depth: f32,
31    pub layer: u8,
32    pub transparent: bool,
33}
34
35#[allow(dead_code)]
36pub fn new_draw_entry(
37    id: u32,
38    material_id: u32,
39    depth: f32,
40    layer: u8,
41    transparent: bool,
42) -> DrawEntry {
43    DrawEntry {
44        id,
45        material_id,
46        depth,
47        layer,
48        transparent,
49    }
50}
51
52#[allow(dead_code)]
53pub fn dc_sort_key(entry: &DrawEntry, strategy: SortStrategy) -> DrawSortKey {
54    let depth_bits = entry.depth.to_bits();
55    let key = match strategy {
56        SortStrategy::FrontToBack => {
57            ((entry.layer as u64) << 56) | ((entry.material_id as u64) << 32) | depth_bits as u64
58        }
59        SortStrategy::BackToFront => {
60            ((entry.layer as u64) << 56) | ((!depth_bits) as u64 & 0x00FF_FFFF_FFFF_FFFF)
61        }
62        SortStrategy::ByMaterial => {
63            ((entry.layer as u64) << 56) | ((entry.material_id as u64) << 24)
64        }
65    };
66    DrawSortKey(key)
67}
68
69#[allow(dead_code)]
70pub fn dc_sort(entries: &mut [DrawEntry], strategy: SortStrategy) {
71    entries.sort_by_key(|e| dc_sort_key(e, strategy));
72}
73
74#[allow(dead_code)]
75pub fn dc_split_opaque_transparent(entries: &[DrawEntry]) -> (Vec<&DrawEntry>, Vec<&DrawEntry>) {
76    let opaque: Vec<&DrawEntry> = entries.iter().filter(|e| !e.transparent).collect();
77    let transparent: Vec<&DrawEntry> = entries.iter().filter(|e| e.transparent).collect();
78    (opaque, transparent)
79}
80
81#[allow(dead_code)]
82pub fn dc_count_by_material(entries: &[DrawEntry]) -> Vec<(u32, usize)> {
83    let mut counts: Vec<(u32, usize)> = Vec::new();
84    for e in entries {
85        if let Some(entry) = counts.iter_mut().find(|(mat, _)| *mat == e.material_id) {
86            entry.1 += 1;
87        } else {
88            counts.push((e.material_id, 1));
89        }
90    }
91    counts
92}
93
94#[allow(dead_code)]
95pub fn dc_batch_count(entries: &[DrawEntry]) -> usize {
96    if entries.is_empty() {
97        return 0;
98    }
99    let mut batches = 1usize;
100    for i in 1..entries.len() {
101        if entries[i].material_id != entries[i - 1].material_id {
102            batches += 1;
103        }
104    }
105    batches
106}
107
108#[allow(dead_code)]
109pub fn dc_to_json_summary(entries: &[DrawEntry]) -> String {
110    let (opaque, transparent) = dc_split_opaque_transparent(entries);
111    format!(
112        r#"{{"total":{},"opaque":{},"transparent":{}}}"#,
113        entries.len(),
114        opaque.len(),
115        transparent.len()
116    )
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn sort_by_material_groups() {
125        let mut entries = vec![
126            new_draw_entry(0, 2, 1.0, 0, false),
127            new_draw_entry(1, 1, 2.0, 0, false),
128            new_draw_entry(2, 2, 3.0, 0, false),
129        ];
130        dc_sort(&mut entries, SortStrategy::ByMaterial);
131        // After sorting by material the two mat=2 entries are adjacent (indices 1 and 2).
132        assert_eq!(entries[1].material_id, entries[2].material_id);
133    }
134
135    #[test]
136    fn sort_front_to_back_order() {
137        let mut entries = vec![
138            new_draw_entry(0, 0, 5.0, 0, false),
139            new_draw_entry(1, 0, 1.0, 0, false),
140            new_draw_entry(2, 0, 3.0, 0, false),
141        ];
142        dc_sort(&mut entries, SortStrategy::FrontToBack);
143        assert!(entries[0].depth <= entries[1].depth);
144    }
145
146    #[test]
147    fn split_opaque_transparent() {
148        let entries = vec![
149            new_draw_entry(0, 0, 1.0, 0, false),
150            new_draw_entry(1, 0, 2.0, 0, true),
151            new_draw_entry(2, 0, 3.0, 0, false),
152        ];
153        let (opaque, transparent) = dc_split_opaque_transparent(&entries);
154        assert_eq!(opaque.len(), 2);
155        assert_eq!(transparent.len(), 1);
156    }
157
158    #[test]
159    fn batch_count_single() {
160        let entries = vec![
161            new_draw_entry(0, 1, 1.0, 0, false),
162            new_draw_entry(1, 1, 2.0, 0, false),
163        ];
164        assert_eq!(dc_batch_count(&entries), 1);
165    }
166
167    #[test]
168    fn batch_count_multiple() {
169        let entries = vec![
170            new_draw_entry(0, 1, 1.0, 0, false),
171            new_draw_entry(1, 2, 2.0, 0, false),
172        ];
173        assert_eq!(dc_batch_count(&entries), 2);
174    }
175
176    #[test]
177    fn batch_count_empty() {
178        let entries: Vec<DrawEntry> = vec![];
179        assert_eq!(dc_batch_count(&entries), 0);
180    }
181
182    #[test]
183    fn count_by_material() {
184        let entries = vec![
185            new_draw_entry(0, 1, 1.0, 0, false),
186            new_draw_entry(1, 1, 2.0, 0, false),
187            new_draw_entry(2, 2, 3.0, 0, false),
188        ];
189        let counts = dc_count_by_material(&entries);
190        let mat1 = counts.iter().find(|(m, _)| *m == 1).map(|(_, c)| *c);
191        assert_eq!(mat1, Some(2));
192    }
193
194    #[test]
195    fn sort_key_layer_priority() {
196        let a = new_draw_entry(0, 0, 1.0, 0, false);
197        let b = new_draw_entry(1, 0, 1.0, 1, false);
198        let ka = dc_sort_key(&a, SortStrategy::ByMaterial);
199        let kb = dc_sort_key(&b, SortStrategy::ByMaterial);
200        assert!(kb > ka);
201    }
202
203    #[test]
204    fn to_json_summary() {
205        let entries = vec![new_draw_entry(0, 0, 1.0, 0, true)];
206        let j = dc_to_json_summary(&entries);
207        assert!(j.contains("transparent"));
208    }
209}