Skip to main content

librashader_runtime/
framebuffer.rs

1use crate::binding::{BindingRequirements, BindingUtil};
2use bit_set::BitSet;
3use librashader_common::map::FastHashMap;
4use librashader_common::Size;
5use librashader_reflect::reflect::semantics::BindingMeta;
6use std::collections::VecDeque;
7use std::ops::{Index, IndexMut};
8use num_traits::AsPrimitive;
9
10#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
11pub(crate) struct Slot(i32);
12
13impl Slot {
14    pub const NONE: Slot = Slot(-1);
15
16    const fn new(index: usize) -> Slot {
17        Slot(index as i32)
18    }
19
20    /// The pool index, or `None` if this is [`Slot::NONE`].
21    const fn get(self) -> Option<usize> {
22        if self.0 < 0 {
23            None
24        } else {
25            Some(self.0 as usize)
26        }
27    }
28}
29
30/// A pool of framebuffers with internal liveness analysis, indexed by pass number.
31pub struct FramebufferPool<F> {
32    pool: Box<[F]>,
33    slots: Box<[Slot]>,
34
35    // At pass i, store the last pass that reads i (the liveness period). Empty when init for feedback.
36    last_use: Box<[usize]>,
37}
38
39impl<F> FramebufferPool<F> {
40    /// Calculate liveness assignments based on pass sizes.
41    pub(crate) fn prepare(&mut self, sizes: &[Size<u32>]) {
42        struct Event {
43            slot: usize,
44            size: u64,
45            next_slot: usize,
46        }
47
48        let n = sizes.len();
49
50        // `free[size]` holds slots currently free and of that size.
51        let mut free: FastHashMap<u64, Vec<usize>> = FastHashMap::default();
52
53        let mut freed_at = vec![usize::MAX; n + 1];
54        let mut events: Vec<Event> = Vec::with_capacity(n);
55        let mut slot_count = 0;
56
57        for pass in 0..n {
58            let mut event = freed_at[pass];
59            while event != usize::MAX {
60                let Event { slot, size, next_slot } = events[event];
61                free.entry(size).or_default().push(slot);
62                event = next_slot;
63            }
64
65            let size = sizes[pass].as_();
66            let slot = free.get_mut(&size).and_then(Vec::pop).unwrap_or_else(|| {
67                let slot = slot_count;
68                slot_count += 1;
69                slot
70            });
71
72            self.slots[pass] = Slot::new(slot);
73            let free_at = self.last_use[pass] + 1;
74            events.push(Event { slot, size, next_slot: freed_at[free_at] });
75            freed_at[free_at] = events.len() - 1;
76        }
77    }
78
79    /// Returns whether the given pass maps to a buffer.
80    pub fn contains(&self, pass: usize) -> bool {
81        self.slots.get(pass).and_then(|s| s.get()).is_some()
82    }
83}
84
85impl<F> Index<usize> for FramebufferPool<F> {
86    type Output = F;
87
88    fn index(&self, pass: usize) -> &F {
89        &self.pool[self.slots[pass].get().expect("pass has no framebuffer")]
90    }
91}
92
93impl<F> IndexMut<usize> for FramebufferPool<F> {
94    fn index_mut(&mut self, pass: usize) -> &mut F {
95        &mut self.pool[self.slots[pass].get().expect("pass has no framebuffer")]
96    }
97}
98
99/// Helper to initialize framebuffers in a graphics API agnostic way.
100pub struct FramebufferInit<'a, F, I, E> {
101    owned_generator: &'a dyn Fn() -> Result<F, E>,
102    input_generator: &'a dyn Fn() -> I,
103    requirements: BindingRequirements,
104    filters_count: usize,
105}
106
107impl<'a, F, I, E> FramebufferInit<'a, F, I, E> {
108    /// Create a new framebuffer initializer with the given
109    /// closures to create owned framebuffers and image views.
110    pub fn new(
111        filters: impl Iterator<Item = &'a BindingMeta> + ExactSizeIterator,
112        owned_generator: &'a dyn Fn() -> Result<F, E>,
113        input_generator: &'a dyn Fn() -> I,
114    ) -> Self {
115        let filters_count = filters.len();
116        let requirements = BindingMeta::calculate_requirements(filters);
117
118        Self {
119            owned_generator,
120            input_generator,
121            filters_count,
122            requirements,
123        }
124    }
125
126    /// Initialize history framebuffers and views.
127    pub fn init_history(&self) -> Result<(VecDeque<F>, Box<[I]>), E> {
128        init_history(
129            self.requirements.required_history,
130            self.owned_generator,
131            self.input_generator,
132        )
133    }
134
135    /// Initialize output framebuffers pooled by pass-output liveness.
136    pub fn init_output_framebuffers(&self) -> Result<(FramebufferPool<F>, Box<[I]>), E> {
137        init_output_framebuffers(
138            self.filters_count,
139            &self.requirements.last_use,
140            self.owned_generator,
141            self.input_generator,
142        )
143    }
144
145    /// Initialize sparse feedback framebuffers, allocating a previous-frame copy only for
146    /// passes referenced as `PassFeedback`.
147    pub fn init_feedback_framebuffers(&self) -> Result<(FramebufferPool<F>, Box<[I]>), E> {
148        init_feedback_framebuffers(
149            self.filters_count,
150            &self.requirements.feedback_mask,
151            self.owned_generator,
152            self.input_generator,
153        )
154    }
155
156    /// Get if the final pass is used as feedback.
157    pub const fn uses_final_pass_as_feedback(&self) -> bool {
158        self.requirements.uses_final_pass_as_feedback
159    }
160}
161
162fn init_history<'a, F, I, E>(
163    required_images: usize,
164    owned_generator: impl Fn() -> Result<F, E>,
165    input_generator: impl Fn() -> I,
166) -> Result<(VecDeque<F>, Box<[I]>), E> {
167    // Since OriginalHistory0 aliases source, it always gets bound if present, and we don't need to
168    // store it. However, if even OriginalHistory1 is used, then we need to store it, hence we check if
169    // required_images is less than 1, and only then do we return an empty history queue.
170    if required_images < 1 {
171        return Ok((VecDeque::new(), Box::new([])));
172    }
173
174    let mut framebuffers = VecDeque::with_capacity(required_images);
175    framebuffers.resize_with(required_images, owned_generator);
176
177    let framebuffers = framebuffers
178        .into_iter()
179        .collect::<Result<VecDeque<F>, E>>()?;
180
181    let mut history_textures = Vec::new();
182    history_textures.resize_with(required_images, input_generator);
183
184    Ok((framebuffers, history_textures.into_boxed_slice()))
185}
186
187fn init_output_framebuffers<F, I, E>(
188    filters_count: usize,
189    last_use: &[usize],
190    owned_generator: impl Fn() -> Result<F, E>,
191    input_generator: impl Fn() -> I,
192) -> Result<(FramebufferPool<F>, Box<[I]>), E> {
193    let mut pool = Vec::with_capacity(filters_count);
194    pool.resize_with(filters_count, &owned_generator);
195    let pool = pool
196        .into_iter()
197        .collect::<Result<Vec<F>, E>>()?
198        .into_boxed_slice();
199
200    let mut textures = Vec::new();
201    textures.resize_with(filters_count, input_generator);
202
203    Ok((
204        FramebufferPool {
205            pool,
206            last_use: last_use.to_vec().into_boxed_slice(),
207            slots: (0..filters_count).map(Slot::new).collect(),
208        },
209        textures.into_boxed_slice(),
210    ))
211}
212
213fn init_feedback_framebuffers<F, I, E>(
214    filters_count: usize,
215    feedback_mask: &BitSet,
216    owned_generator: impl Fn() -> Result<F, E>,
217    input_generator: impl Fn() -> I,
218) -> Result<(FramebufferPool<F>, Box<[I]>), E> {
219
220    // assign feedback slots according to the usage mask
221    fn assign_slots(mask: &BitSet, filters_count: usize) -> (usize, Box<[Slot]>) {
222        let mut slot_of_pass = vec![Slot::NONE; filters_count];
223        let mut count = 0;
224        for pass in mask.iter() {
225            if pass < filters_count {
226                slot_of_pass[pass] = Slot::new(count);
227                count += 1;
228            }
229        }
230        (count, slot_of_pass.into_boxed_slice())
231    }
232
233    let (count, slots) = assign_slots(feedback_mask, filters_count);
234
235    let mut pool = Vec::with_capacity(count);
236    pool.resize_with(count, owned_generator);
237    let pool = pool
238        .into_iter()
239        .collect::<Result<Vec<F>, E>>()?
240        .into_boxed_slice();
241
242    let mut textures = Vec::new();
243    textures.resize_with(filters_count, input_generator);
244
245    Ok((
246        FramebufferPool {
247            pool,
248            last_use: Box::new([]),
249            slots,
250        },
251        textures.into_boxed_slice(),
252    ))
253}
254
255#[cfg(test)]
256mod tests {
257    use super::{FramebufferPool, Slot};
258    use librashader_common::Size;
259    use std::collections::HashSet;
260
261    fn fb(last_use: Vec<usize>) -> FramebufferPool<()> {
262        let n = last_use.len();
263        FramebufferPool {
264            pool: vec![(); n].into_boxed_slice(),
265            last_use: last_use.into_boxed_slice(),
266            slots: vec![Slot::new(0); n].into_boxed_slice(),
267        }
268    }
269
270    fn distinct(slots: &[Slot]) -> usize {
271        slots.iter().copied().collect::<HashSet<_>>().len()
272    }
273
274    #[test]
275    fn liveness_pools_chain() {
276        let size = |w, h| Size::<u32>::new(w, h);
277        let last_use = vec![1, 2, 10, 4, 5, 6, 7, 8, 9, 10, 10];
278        let sizes = vec![
279            size(1280, 960),  // p0
280            size(1280, 960),  // p1
281            size(1280, 3360), // p2 (retained, read by p3..p10)
282            size(1280, 1680), // p3
283            size(1280, 1680), // p4
284            size(1280, 1680), // p5
285            size(1280, 1680), // p6
286            size(1280, 1680), // p7
287            size(1280, 1680), // p8
288            size(3840, 1680), // p9
289            size(1280, 720),  // p10 (final / viewport)
290        ];
291
292        let mut output = fb(last_use);
293        output.prepare(&sizes);
294
295        // p3..=p8 (six same-size passes, each live only into the next) collapse onto two
296        // ping-pong buffers.
297        assert_eq!(distinct(&output.slots[3..=8]), 2);
298
299        // The whole chain needs 7 physical buffers instead of 11, and no buffer is ever
300        // assigned two different sizes (the invariant that keeps it deferred-safe).
301        assert_eq!(distinct(&output.slots), 7);
302        let mut seen: std::collections::HashMap<Slot, Size<u32>> = Default::default();
303        for (pass, &slot) in output.slots.iter().enumerate() {
304            assert_eq!(*seen.entry(slot).or_insert(sizes[pass]), sizes[pass]);
305        }
306    }
307
308    // A uniform-size linear chain (the common case) collapses to a 2-buffer ping-pong.
309    #[test]
310    fn liveness_pools_uniform_chain() {
311        let sizes = vec![Size::<u32>::new(1920, 1080); 8];
312        let mut output = fb(vec![1, 2, 3, 4, 5, 6, 7, 7]);
313        output.prepare(&sizes);
314        assert_eq!(distinct(&output.slots), 2);
315    }
316}