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