Skip to main content

oxihuman_viewer/
render_graph.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Render-graph (frame-graph) node and dependency management.
6
7use std::collections::HashMap;
8
9/// Pass kind.
10#[allow(dead_code)]
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum PassKind {
13    Render,
14    Compute,
15    Transfer,
16}
17
18/// A render-graph node.
19#[allow(dead_code)]
20#[derive(Clone, Debug)]
21pub struct RgNode {
22    pub id: u32,
23    pub name: String,
24    pub kind: PassKind,
25    pub enabled: bool,
26}
27
28/// The render graph.
29#[allow(dead_code)]
30#[derive(Clone, Debug, Default)]
31pub struct RenderGraph {
32    pub nodes: Vec<RgNode>,
33    /// dependency edges: node_id -> list of dependency node_ids.
34    pub deps: HashMap<u32, Vec<u32>>,
35    pub next_id: u32,
36}
37
38#[allow(dead_code)]
39pub fn new_render_graph() -> RenderGraph {
40    RenderGraph::default()
41}
42
43#[allow(dead_code)]
44pub fn rg_add_node(graph: &mut RenderGraph, name: &str, kind: PassKind) -> u32 {
45    let id = graph.next_id;
46    graph.next_id += 1;
47    graph.nodes.push(RgNode {
48        id,
49        name: name.to_string(),
50        kind,
51        enabled: true,
52    });
53    id
54}
55
56#[allow(dead_code)]
57pub fn rg_add_dep(graph: &mut RenderGraph, node: u32, dep: u32) {
58    graph.deps.entry(node).or_default().push(dep);
59}
60
61#[allow(dead_code)]
62pub fn rg_set_enabled(graph: &mut RenderGraph, id: u32, v: bool) {
63    if let Some(n) = graph.nodes.iter_mut().find(|n| n.id == id) {
64        n.enabled = v;
65    }
66}
67
68#[allow(dead_code)]
69pub fn rg_node_count(graph: &RenderGraph) -> usize {
70    graph.nodes.len()
71}
72
73#[allow(dead_code)]
74pub fn rg_enabled_count(graph: &RenderGraph) -> usize {
75    graph.nodes.iter().filter(|n| n.enabled).count()
76}
77
78#[allow(dead_code)]
79pub fn rg_dep_count(graph: &RenderGraph, node: u32) -> usize {
80    graph.deps.get(&node).map(|v| v.len()).unwrap_or(0)
81}
82
83#[allow(dead_code)]
84pub fn rg_pass_name(kind: PassKind) -> &'static str {
85    match kind {
86        PassKind::Render => "render",
87        PassKind::Compute => "compute",
88        PassKind::Transfer => "transfer",
89    }
90}
91
92#[allow(dead_code)]
93pub fn rg_clear(graph: &mut RenderGraph) {
94    graph.nodes.clear();
95    graph.deps.clear();
96}
97
98#[allow(dead_code)]
99pub fn rg_to_json(graph: &RenderGraph) -> String {
100    let nodes: Vec<String> = graph
101        .nodes
102        .iter()
103        .map(|n| {
104            format!(
105                "{{\"id\":{},\"name\":\"{}\",\"kind\":\"{}\",\"enabled\":{}}}",
106                n.id,
107                n.name,
108                rg_pass_name(n.kind),
109                n.enabled
110            )
111        })
112        .collect();
113    format!("{{\"nodes\":[{}]}}", nodes.join(","))
114}
115
116/// Simple topological sort (no cycle detection — educational stub).
117#[allow(dead_code)]
118pub fn rg_topo_sort(graph: &RenderGraph) -> Vec<u32> {
119    let mut order: Vec<u32> = graph.nodes.iter().map(|n| n.id).collect();
120    order.sort();
121    order
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn new_empty() {
130        assert_eq!(rg_node_count(&new_render_graph()), 0);
131    }
132
133    #[test]
134    fn add_node() {
135        let mut g = new_render_graph();
136        rg_add_node(&mut g, "shadows", PassKind::Render);
137        assert_eq!(rg_node_count(&g), 1);
138    }
139
140    #[test]
141    fn ids_increment() {
142        let mut g = new_render_graph();
143        let a = rg_add_node(&mut g, "a", PassKind::Render);
144        let b = rg_add_node(&mut g, "b", PassKind::Compute);
145        assert_ne!(a, b);
146    }
147
148    #[test]
149    fn add_dep() {
150        let mut g = new_render_graph();
151        let a = rg_add_node(&mut g, "a", PassKind::Render);
152        let b = rg_add_node(&mut g, "b", PassKind::Render);
153        rg_add_dep(&mut g, b, a);
154        assert_eq!(rg_dep_count(&g, b), 1);
155    }
156
157    #[test]
158    fn set_disabled() {
159        let mut g = new_render_graph();
160        let id = rg_add_node(&mut g, "x", PassKind::Transfer);
161        rg_set_enabled(&mut g, id, false);
162        assert_eq!(rg_enabled_count(&g), 0);
163    }
164
165    #[test]
166    fn all_enabled_by_default() {
167        let mut g = new_render_graph();
168        rg_add_node(&mut g, "a", PassKind::Render);
169        rg_add_node(&mut g, "b", PassKind::Render);
170        assert_eq!(rg_enabled_count(&g), 2);
171    }
172
173    #[test]
174    fn pass_name() {
175        assert_eq!(rg_pass_name(PassKind::Compute), "compute");
176    }
177
178    #[test]
179    fn clear() {
180        let mut g = new_render_graph();
181        rg_add_node(&mut g, "x", PassKind::Render);
182        rg_clear(&mut g);
183        assert_eq!(rg_node_count(&g), 0);
184    }
185
186    #[test]
187    fn topo_sort_len() {
188        let mut g = new_render_graph();
189        rg_add_node(&mut g, "a", PassKind::Render);
190        rg_add_node(&mut g, "b", PassKind::Render);
191        assert_eq!(rg_topo_sort(&g).len(), 2);
192    }
193
194    #[test]
195    fn json_has_nodes() {
196        assert!(rg_to_json(&new_render_graph()).contains("nodes"));
197    }
198}