open_sesame/render/
pipeline.rs

1//! Composable render pipeline
2
3use crate::render::context::RenderContext;
4use crate::util::Result;
5
6/// A single render pass in the pipeline
7pub trait RenderPass {
8    /// Execute this render pass
9    fn render(&self, context: &mut RenderContext) -> Result<()>;
10}
11
12/// A composable pipeline of render passes
13pub struct RenderPipeline {
14    passes: Vec<Box<dyn RenderPass>>,
15}
16
17impl RenderPipeline {
18    /// Create a new empty pipeline
19    pub fn new() -> Self {
20        Self { passes: Vec::new() }
21    }
22
23    /// Add a render pass to the pipeline
24    pub fn add_pass<P: RenderPass + 'static>(mut self, pass: P) -> Self {
25        self.passes.push(Box::new(pass));
26        self
27    }
28
29    /// Execute all passes in order
30    pub fn render(&self, context: &mut RenderContext) -> Result<()> {
31        for pass in &self.passes {
32            pass.render(context)?;
33        }
34        Ok(())
35    }
36}
37
38impl Default for RenderPipeline {
39    fn default() -> Self {
40        Self::new()
41    }
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47
48    #[allow(dead_code)]
49    struct TestPass {
50        executed: std::cell::Cell<bool>,
51    }
52
53    #[allow(dead_code)]
54    impl RenderPass for TestPass {
55        fn render(&self, _context: &mut RenderContext) -> Result<()> {
56            self.executed.set(true);
57            Ok(())
58        }
59    }
60
61    #[test]
62    fn test_pipeline_creation() {
63        let pipeline = RenderPipeline::new();
64        assert!(pipeline.passes.is_empty());
65    }
66}