Skip to main content

oxihuman_viewer/
draw_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 utilities: front-to-back, back-to-front, by material, etc.
6
7/// Sort strategy for draw calls.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9#[allow(dead_code)]
10pub enum SortStrategy {
11    /// Opaque objects: front-to-back to maximise early-z rejection.
12    FrontToBack,
13    /// Transparent objects: back-to-front for correct blending.
14    BackToFront,
15    /// Group by material id to minimise state changes.
16    ByMaterial,
17    /// Group by pipeline/shader id.
18    ByPipeline,
19    /// No sorting — preserve submission order.
20    None,
21}
22
23/// A lightweight key used to sort a draw call.
24#[derive(Debug, Clone, PartialEq)]
25#[allow(dead_code)]
26pub struct DrawSortKey {
27    /// Distance from the camera (used for depth sorting).
28    pub depth: f32,
29    /// Material id (lower = earlier).
30    pub material_id: u32,
31    /// Pipeline id.
32    pub pipeline_id: u32,
33    /// Render layer (higher = later).
34    pub layer: u8,
35}
36
37impl DrawSortKey {
38    /// Create a new sort key.
39    #[allow(dead_code)]
40    pub fn new(depth: f32, material_id: u32, pipeline_id: u32, layer: u8) -> Self {
41        Self {
42            depth,
43            material_id,
44            pipeline_id,
45            layer,
46        }
47    }
48}
49
50/// A draw call with its sort key and an opaque handle index.
51#[derive(Debug, Clone)]
52#[allow(dead_code)]
53pub struct SortedDraw {
54    pub key: DrawSortKey,
55    pub handle: usize,
56}
57
58/// Sort a slice of `SortedDraw` in-place according to `strategy`.
59#[allow(dead_code)]
60pub fn sort_draws(draws: &mut [SortedDraw], strategy: SortStrategy) {
61    match strategy {
62        SortStrategy::FrontToBack => draws.sort_by(|a, b| {
63            a.key
64                .depth
65                .partial_cmp(&b.key.depth)
66                .unwrap_or(std::cmp::Ordering::Equal)
67        }),
68        SortStrategy::BackToFront => draws.sort_by(|a, b| {
69            b.key
70                .depth
71                .partial_cmp(&a.key.depth)
72                .unwrap_or(std::cmp::Ordering::Equal)
73        }),
74        SortStrategy::ByMaterial => draws.sort_by_key(|d| (d.key.layer, d.key.material_id)),
75        SortStrategy::ByPipeline => draws.sort_by_key(|d| (d.key.layer, d.key.pipeline_id)),
76        SortStrategy::None => {}
77    }
78}
79
80/// Build a sort key optimised for opaque rendering.
81#[allow(dead_code)]
82pub fn opaque_key(depth: f32, material_id: u32, pipeline_id: u32) -> DrawSortKey {
83    DrawSortKey::new(depth, material_id, pipeline_id, 0)
84}
85
86/// Build a sort key for transparent rendering.
87#[allow(dead_code)]
88pub fn transparent_key(depth: f32, material_id: u32, pipeline_id: u32) -> DrawSortKey {
89    DrawSortKey::new(depth, material_id, pipeline_id, 128)
90}
91
92/// Return true when the draw list is sorted front-to-back.
93#[allow(dead_code)]
94pub fn is_sorted_front_to_back(draws: &[SortedDraw]) -> bool {
95    draws.windows(2).all(|w| w[0].key.depth <= w[1].key.depth)
96}
97
98/// Return true when the draw list is sorted back-to-front.
99#[allow(dead_code)]
100pub fn is_sorted_back_to_front(draws: &[SortedDraw]) -> bool {
101    draws.windows(2).all(|w| w[0].key.depth >= w[1].key.depth)
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    fn make_draw(depth: f32, mat: u32, pipe: u32, layer: u8, handle: usize) -> SortedDraw {
109        SortedDraw {
110            key: DrawSortKey::new(depth, mat, pipe, layer),
111            handle,
112        }
113    }
114
115    #[test]
116    fn front_to_back_sorts_ascending() {
117        let mut draws = vec![
118            make_draw(3.0, 0, 0, 0, 0),
119            make_draw(1.0, 0, 0, 0, 1),
120            make_draw(2.0, 0, 0, 0, 2),
121        ];
122        sort_draws(&mut draws, SortStrategy::FrontToBack);
123        assert!(is_sorted_front_to_back(&draws));
124    }
125
126    #[test]
127    fn back_to_front_sorts_descending() {
128        let mut draws = vec![
129            make_draw(1.0, 0, 0, 0, 0),
130            make_draw(3.0, 0, 0, 0, 1),
131            make_draw(2.0, 0, 0, 0, 2),
132        ];
133        sort_draws(&mut draws, SortStrategy::BackToFront);
134        assert!(is_sorted_back_to_front(&draws));
135    }
136
137    #[test]
138    fn by_material_groups() {
139        let mut draws = vec![
140            make_draw(1.0, 2, 0, 0, 0),
141            make_draw(2.0, 1, 0, 0, 1),
142            make_draw(3.0, 2, 0, 0, 2),
143        ];
144        sort_draws(&mut draws, SortStrategy::ByMaterial);
145        assert!(draws[0].key.material_id <= draws[1].key.material_id);
146    }
147
148    #[test]
149    fn by_pipeline_groups() {
150        let mut draws = vec![make_draw(1.0, 0, 3, 0, 0), make_draw(2.0, 0, 1, 0, 1)];
151        sort_draws(&mut draws, SortStrategy::ByPipeline);
152        assert!(draws[0].key.pipeline_id <= draws[1].key.pipeline_id);
153    }
154
155    #[test]
156    fn none_preserves_order() {
157        let mut draws = vec![make_draw(5.0, 0, 0, 0, 0), make_draw(1.0, 0, 0, 0, 1)];
158        sort_draws(&mut draws, SortStrategy::None);
159        assert_eq!(draws[0].handle, 0);
160        assert_eq!(draws[1].handle, 1);
161    }
162
163    #[test]
164    fn opaque_key_layer_zero() {
165        let k = opaque_key(1.0, 0, 0);
166        assert_eq!(k.layer, 0);
167    }
168
169    #[test]
170    fn transparent_key_layer_nonzero() {
171        let k = transparent_key(1.0, 0, 0);
172        assert!(k.layer > 0);
173    }
174
175    #[test]
176    fn empty_slice_sorts_cleanly() {
177        let mut draws: Vec<SortedDraw> = vec![];
178        sort_draws(&mut draws, SortStrategy::FrontToBack);
179        assert!(draws.is_empty());
180    }
181
182    #[test]
183    fn is_sorted_single_element() {
184        let draws = vec![make_draw(1.0, 0, 0, 0, 0)];
185        assert!(is_sorted_front_to_back(&draws));
186        assert!(is_sorted_back_to_front(&draws));
187    }
188}