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