Skip to main content

pebble/graphics/render/
targets.rs

1use crate::graphics::pipeline::texture_view::TextureView;
2
3/// One color attachment for a [`Pass`]. `attachment: None` means "the
4/// swapchain's own view" — pass a [`TextureView`] to render into your own
5/// texture instead (e.g. for post-processing).
6pub struct ColorTarget<'a> {
7    pub(crate) attachment: Option<&'a TextureView>,
8    /// `Some(color)` to clear to that color, `None` to load existing contents.
9    pub(crate) clear: Option<[f32; 4]>
10}
11
12pub struct ColorTargetBuilder<'a> {
13    target: ColorTarget<'a>
14}
15
16impl<'a> ColorTargetBuilder<'a> {
17    pub fn new() -> Self {
18        Self {
19            target: ColorTarget {
20                attachment: None,
21                clear: Some([0.0, 0.0, 0.0, 1.0])
22            }
23        }
24    }
25
26    pub fn with_attachment(mut self, view: &'a TextureView) -> Self {
27        self.target.attachment = Some(view);
28        self
29    }
30
31    /// Clear the attachment to `clear` at the start of the pass.
32    pub fn with_clear(mut self, clear: [f32; 4]) -> Self {
33        self.target.clear = Some(clear);
34        self
35    }
36
37    /// Load the attachment's existing contents instead of clearing, so a
38    /// later pass can draw on top of what an earlier one left there.
39    pub fn without_clear(mut self) -> Self {
40        self.target.clear = None;
41        self
42    }
43
44    pub fn build(self) -> ColorTarget<'a> {
45        self.target
46    }
47}
48
49/// A depth attachment for a [`Pass`] — unlike a color target, always
50/// pointed at your own texture (there's no swapchain depth buffer).
51pub struct DepthTarget<'a> {
52    pub(crate) attachment: &'a TextureView,
53    pub(crate) clear: Option<f32>,
54}
55
56pub struct DepthTargetBuilder<'a> {
57    target: DepthTarget<'a>
58}
59
60impl<'a> DepthTargetBuilder<'a> {
61    pub fn new(view: &'a TextureView) -> Self {
62        Self {
63            target: DepthTarget {
64                attachment: view,
65                clear: Some(1.0)
66            }
67        }
68    }
69
70    /// Clear the depth attachment to `clear` at the start of the pass.
71    pub fn with_clear(mut self, clear: f32) -> Self {
72        self.target.clear = Some(clear);
73        self
74    }
75
76    /// Load the depth attachment's existing contents instead of clearing —
77    /// e.g. to reuse a depth pre-pass's result in a later color pass.
78    pub fn without_clear(mut self) -> Self {
79        self.target.clear = None;
80        self
81    }
82
83    pub fn build(self) -> DepthTarget<'a> {
84        self.target
85    }
86}
87
88/// What to render into, passed to [`Frame::begin`](crate::graphics::render::frame::Frame::begin).
89/// Zero color targets plus a depth target is a valid, depth-only pass (e.g.
90/// a shadow map).
91pub struct Pass<'a> {
92    pub(crate) colors: Vec<ColorTarget<'a>>,
93    pub(crate) depth: Option<DepthTarget<'a>>
94}
95
96pub struct PassBuilder<'a> {
97    targets: Vec<ColorTarget<'a>>,
98    depth: Option<DepthTarget<'a>>,
99}
100
101impl<'a> PassBuilder<'a> {
102    pub fn new() -> Self {
103        Self { targets: Vec::new(), depth: None }
104    }
105
106    pub fn with_target(mut self, target: ColorTarget<'a>) -> Self {
107        self.targets.push(target);
108        self
109    }
110
111    pub fn with_depth(mut self, depth: DepthTarget<'a>) -> Self {
112        self.depth = Some(depth);
113        self
114    }
115
116    pub fn build(self) -> Pass<'a> {
117        Pass {
118            colors: self.targets,
119            depth: self.depth,
120        }
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    // `DepthTarget` needs a real `TextureView`, so only the color builder --
129    // whose attachment is optional -- is reachable without a GPU.
130
131    #[test]
132    fn a_color_target_clears_to_opaque_black_by_default() {
133        let target = ColorTargetBuilder::new().build();
134        assert_eq!(target.clear, Some([0.0, 0.0, 0.0, 1.0]));
135    }
136
137    #[test]
138    fn with_clear_sets_the_clear_color() {
139        let target = ColorTargetBuilder::new().with_clear([0.0, 0.0, 0.0, 0.0]).build();
140        assert_eq!(target.clear, Some([0.0, 0.0, 0.0, 0.0]));
141    }
142
143    #[test]
144    fn without_clear_loads_existing_contents() {
145        let target = ColorTargetBuilder::new().without_clear().build();
146        assert_eq!(target.clear, None);
147    }
148
149    #[test]
150    fn the_last_clear_setting_wins() {
151        assert_eq!(ColorTargetBuilder::new().with_clear([1.0; 4]).without_clear().build().clear, None);
152        assert_eq!(
153            ColorTargetBuilder::new().without_clear().with_clear([1.0; 4]).build().clear,
154            Some([1.0; 4])
155        );
156    }
157}