Skip to main content

oxihuman_viewer/
draw_command.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Draw command recorder — records and sorts GPU draw calls for submission.
6
7/// Draw primitive type.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9#[allow(dead_code)]
10pub enum DrawPrimitive {
11    Triangles,
12    Lines,
13    Points,
14    TriangleStrip,
15}
16
17/// A single draw command.
18#[derive(Debug, Clone, PartialEq)]
19#[allow(dead_code)]
20pub struct DrawCommand {
21    pub vertex_offset: u32,
22    pub index_offset: u32,
23    pub index_count: u32,
24    pub instance_count: u32,
25    pub pipeline_id: u32,
26    pub sort_key: u64,
27    pub primitive: DrawPrimitive,
28    pub enabled: bool,
29}
30
31impl Default for DrawCommand {
32    fn default() -> Self {
33        Self {
34            vertex_offset: 0,
35            index_offset: 0,
36            index_count: 0,
37            instance_count: 1,
38            pipeline_id: 0,
39            sort_key: 0,
40            primitive: DrawPrimitive::Triangles,
41            enabled: true,
42        }
43    }
44}
45
46/// Draw command recorder.
47#[derive(Debug, Clone, Default)]
48#[allow(dead_code)]
49pub struct DrawCommandRecorder {
50    pub commands: Vec<DrawCommand>,
51    pub max_commands: usize,
52}
53
54/// Create new recorder.
55#[allow(dead_code)]
56pub fn new_draw_recorder(max_commands: usize) -> DrawCommandRecorder {
57    DrawCommandRecorder {
58        commands: Vec::new(),
59        max_commands,
60    }
61}
62
63/// Record a draw command.
64#[allow(dead_code)]
65pub fn record_draw(r: &mut DrawCommandRecorder, cmd: DrawCommand) -> bool {
66    if r.commands.len() >= r.max_commands {
67        return false;
68    }
69    r.commands.push(cmd);
70    true
71}
72
73/// Clear all recorded commands.
74#[allow(dead_code)]
75pub fn clear_commands(r: &mut DrawCommandRecorder) {
76    r.commands.clear();
77}
78
79/// Count of recorded commands.
80#[allow(dead_code)]
81pub fn command_count(r: &DrawCommandRecorder) -> usize {
82    r.commands.len()
83}
84
85/// Total index count across all enabled commands.
86#[allow(dead_code)]
87pub fn total_index_count(r: &DrawCommandRecorder) -> u32 {
88    r.commands
89        .iter()
90        .filter(|c| c.enabled)
91        .map(|c| c.index_count)
92        .sum()
93}
94
95/// Total instance count across all enabled commands.
96#[allow(dead_code)]
97pub fn total_instance_count(r: &DrawCommandRecorder) -> u32 {
98    r.commands
99        .iter()
100        .filter(|c| c.enabled)
101        .map(|c| c.instance_count)
102        .sum()
103}
104
105/// Sort commands by sort key (front-to-back or back-to-front).
106#[allow(dead_code)]
107pub fn sort_commands_ascending(r: &mut DrawCommandRecorder) {
108    r.commands.sort_by_key(|c| c.sort_key);
109}
110
111/// Sort commands descending (back-to-front for transparency).
112#[allow(dead_code)]
113pub fn sort_commands_descending(r: &mut DrawCommandRecorder) {
114    r.commands.sort_by_key(|c| std::cmp::Reverse(c.sort_key));
115}
116
117/// Group commands by pipeline ID.
118#[allow(dead_code)]
119pub fn group_by_pipeline(r: &mut DrawCommandRecorder) {
120    r.commands.sort_by_key(|c| c.pipeline_id);
121}
122
123/// Check if recorder is full.
124#[allow(dead_code)]
125pub fn is_recorder_full(r: &DrawCommandRecorder) -> bool {
126    r.commands.len() >= r.max_commands
127}
128
129/// Export to JSON-like string.
130#[allow(dead_code)]
131pub fn draw_command_to_json(r: &DrawCommandRecorder) -> String {
132    format!(
133        r#"{{"command_count":{},"total_indices":{}}}"#,
134        r.commands.len(),
135        total_index_count(r)
136    )
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    #[test]
144    fn new_recorder_empty() {
145        let r = new_draw_recorder(100);
146        assert_eq!(command_count(&r), 0);
147    }
148
149    #[test]
150    fn record_returns_true() {
151        let mut r = new_draw_recorder(10);
152        assert!(record_draw(&mut r, DrawCommand::default()));
153    }
154
155    #[test]
156    fn capacity_limit() {
157        let mut r = new_draw_recorder(1);
158        record_draw(&mut r, DrawCommand::default());
159        assert!(!record_draw(&mut r, DrawCommand::default()));
160    }
161
162    #[test]
163    fn clear_empties() {
164        let mut r = new_draw_recorder(10);
165        record_draw(&mut r, DrawCommand::default());
166        clear_commands(&mut r);
167        assert_eq!(command_count(&r), 0);
168    }
169
170    #[test]
171    fn total_index_count_correct() {
172        let mut r = new_draw_recorder(10);
173        record_draw(
174            &mut r,
175            DrawCommand {
176                index_count: 300,
177                ..Default::default()
178            },
179        );
180        record_draw(
181            &mut r,
182            DrawCommand {
183                index_count: 150,
184                ..Default::default()
185            },
186        );
187        assert_eq!(total_index_count(&r), 450);
188    }
189
190    #[test]
191    fn disabled_excluded_from_total() {
192        let mut r = new_draw_recorder(10);
193        record_draw(
194            &mut r,
195            DrawCommand {
196                index_count: 100,
197                enabled: false,
198                ..Default::default()
199            },
200        );
201        assert_eq!(total_index_count(&r), 0);
202    }
203
204    #[test]
205    fn sort_ascending() {
206        let mut r = new_draw_recorder(10);
207        record_draw(
208            &mut r,
209            DrawCommand {
210                sort_key: 5,
211                ..Default::default()
212            },
213        );
214        record_draw(
215            &mut r,
216            DrawCommand {
217                sort_key: 1,
218                ..Default::default()
219            },
220        );
221        sort_commands_ascending(&mut r);
222        assert_eq!(r.commands[0].sort_key, 1);
223    }
224
225    #[test]
226    fn group_by_pipeline_sorts() {
227        let mut r = new_draw_recorder(10);
228        record_draw(
229            &mut r,
230            DrawCommand {
231                pipeline_id: 3,
232                ..Default::default()
233            },
234        );
235        record_draw(
236            &mut r,
237            DrawCommand {
238                pipeline_id: 1,
239                ..Default::default()
240            },
241        );
242        group_by_pipeline(&mut r);
243        assert_eq!(r.commands[0].pipeline_id, 1);
244    }
245
246    #[test]
247    fn is_full_check() {
248        let mut r = new_draw_recorder(1);
249        record_draw(&mut r, DrawCommand::default());
250        assert!(is_recorder_full(&r));
251    }
252
253    #[test]
254    fn json_contains_command_count() {
255        let r = new_draw_recorder(10);
256        assert!(draw_command_to_json(&r).contains("command_count"));
257    }
258}