Skip to content

Commit d16c70e

Browse files
committed
rt: fix allocation reuse in sparse pools not taking into account alloc recreations
1 parent cde6ad6 commit d16c70e

3 files changed

Lines changed: 98 additions & 39 deletions

File tree

librashader-common/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ impl FromStr for ImageFormat {
218218
}
219219

220220
/// A size with a width and height.
221-
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
221+
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
222222
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
223223
pub struct Size<T> {
224224
pub width: T,

librashader-runtime/src/framebuffer.rs

Lines changed: 84 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
use crate::binding::{BindingRequirements, BindingUtil};
22
use bit_set::BitSet;
33
use librashader_common::map::FastHashMap;
4-
use librashader_common::Size;
4+
use librashader_common::{ImageFormat, Size};
55
use librashader_reflect::reflect::semantics::BindingMeta;
66
use std::collections::VecDeque;
77
use std::ops::{Index, IndexMut};
8-
use num_traits::AsPrimitive;
8+
9+
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
10+
pub(crate) struct FramebufferKey {
11+
pub size: Size<u32>,
12+
pub format: ImageFormat,
13+
pub mipmap: bool,
14+
}
915

1016
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
1117
pub(crate) struct Slot(i32);
@@ -37,18 +43,18 @@ pub struct FramebufferPool<F> {
3743
}
3844

3945
impl<F> FramebufferPool<F> {
40-
/// Calculate liveness assignments based on pass sizes.
41-
pub(crate) fn prepare(&mut self, sizes: &[Size<u32>]) {
46+
/// Calculate liveness assignments based on pass allocation requirements.
47+
pub(crate) fn prepare(&mut self, keys: &[FramebufferKey]) {
4248
struct Event {
4349
slot: usize,
44-
size: u64,
50+
key: FramebufferKey,
4551
next_slot: usize,
4652
}
4753

48-
let n = sizes.len();
54+
let n = keys.len();
4955

50-
// `free[size]` holds slots currently free and of that size.
51-
let mut free: FastHashMap<u64, Vec<usize>> = FastHashMap::default();
56+
// `free[key]` holds slots currently free and allocation-compatible.
57+
let mut free: FastHashMap<FramebufferKey, Vec<usize>> = FastHashMap::default();
5258

5359
let mut freed_at = vec![usize::MAX; n + 1];
5460
let mut events: Vec<Event> = Vec::with_capacity(n);
@@ -57,21 +63,21 @@ impl<F> FramebufferPool<F> {
5763
for pass in 0..n {
5864
let mut event = freed_at[pass];
5965
while event != usize::MAX {
60-
let Event { slot, size, next_slot } = events[event];
61-
free.entry(size).or_default().push(slot);
66+
let Event { slot, key, next_slot } = events[event];
67+
free.entry(key).or_default().push(slot);
6268
event = next_slot;
6369
}
6470

65-
let size = sizes[pass].as_();
66-
let slot = free.get_mut(&size).and_then(Vec::pop).unwrap_or_else(|| {
71+
let key = keys[pass];
72+
let slot = free.get_mut(&key).and_then(Vec::pop).unwrap_or_else(|| {
6773
let slot = slot_count;
6874
slot_count += 1;
6975
slot
7076
});
7177

7278
self.slots[pass] = Slot::new(slot);
7379
let free_at = self.last_use[pass] + 1;
74-
events.push(Event { slot, size, next_slot: freed_at[free_at] });
80+
events.push(Event { slot, key, next_slot: freed_at[free_at] });
7581
freed_at[free_at] = events.len() - 1;
7682
}
7783
}
@@ -254,8 +260,8 @@ fn init_feedback_framebuffers<F, I, E>(
254260

255261
#[cfg(test)]
256262
mod tests {
257-
use super::{FramebufferPool, Slot};
258-
use librashader_common::Size;
263+
use super::{FramebufferKey, FramebufferPool, Slot};
264+
use librashader_common::{ImageFormat, Size};
259265
use std::collections::HashSet;
260266

261267
fn fb(last_use: Vec<usize>) -> FramebufferPool<()> {
@@ -267,6 +273,14 @@ mod tests {
267273
}
268274
}
269275

276+
fn key(size: Size<u32>) -> FramebufferKey {
277+
FramebufferKey {
278+
size,
279+
format: ImageFormat::R8G8B8A8Unorm,
280+
mipmap: false,
281+
}
282+
}
283+
270284
fn distinct(slots: &[Slot]) -> usize {
271285
slots.iter().copied().collect::<HashSet<_>>().len()
272286
}
@@ -275,42 +289,79 @@ mod tests {
275289
fn liveness_pools_chain() {
276290
let size = |w, h| Size::<u32>::new(w, h);
277291
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)
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)
290304
];
291305

292306
let mut output = fb(last_use);
293-
output.prepare(&sizes);
307+
output.prepare(&keys);
294308

295309
// p3..=p8 (six same-size passes, each live only into the next) collapse onto two
296310
// ping-pong buffers.
297311
assert_eq!(distinct(&output.slots[3..=8]), 2);
298312

299313
// 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).
314+
// assigned two different allocation keys (the invariant that keeps it deferred-safe).
301315
assert_eq!(distinct(&output.slots), 7);
302-
let mut seen: std::collections::HashMap<Slot, Size<u32>> = Default::default();
316+
let mut seen: std::collections::HashMap<Slot, FramebufferKey> = Default::default();
303317
for (pass, &slot) in output.slots.iter().enumerate() {
304-
assert_eq!(*seen.entry(slot).or_insert(sizes[pass]), sizes[pass]);
318+
assert_eq!(*seen.entry(slot).or_insert(keys[pass]), keys[pass]);
305319
}
306320
}
307321

308322
// A uniform-size linear chain (the common case) collapses to a 2-buffer ping-pong.
309323
#[test]
310324
fn liveness_pools_uniform_chain() {
311-
let sizes = vec![Size::<u32>::new(1920, 1080); 8];
325+
let keys = vec![key(Size::<u32>::new(1920, 1080)); 8];
312326
let mut output = fb(vec![1, 2, 3, 4, 5, 6, 7, 7]);
313-
output.prepare(&sizes);
327+
output.prepare(&keys);
314328
assert_eq!(distinct(&output.slots), 2);
315329
}
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+
}
316367
}

librashader-runtime/src/scaling.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::filter_pass::FilterPassMeta;
2-
use crate::framebuffer::FramebufferPool;
2+
use crate::framebuffer::{FramebufferKey, FramebufferPool};
33
use crate::scaling;
44
use librashader_common::{ImageFormat, Size};
55
use librashader_presets::{Scale2D, ScaleFactor, ScaleType, Scaling};
@@ -324,21 +324,29 @@ where
324324
{
325325
let len = passes.len();
326326

327-
// Compute every pass's output size up front so the pool can be colored by liveness
328-
// before any buffer is touched.
327+
// Compute every pass's allocation requirements up front so the pool can be colored
328+
// by liveness before any buffer is touched.
329329
let mut sizes = Vec::with_capacity(len);
330+
let mut keys = Vec::with_capacity(len);
330331
let mut target_size = source_size;
331-
for pass in passes.iter() {
332+
for (index, pass) in passes.iter().enumerate() {
332333
target_size = target_size.scale_viewport(
333334
pass.meta().scaling.clone(),
334335
viewport_size,
335336
original_size,
336337
None,
337338
);
338339
sizes.push(target_size);
340+
keys.push(FramebufferKey {
341+
size: target_size,
342+
format: pass.get_format(),
343+
mipmap: passes
344+
.get(index + 1)
345+
.map_or(false, |p| p.meta().mipmap_input),
346+
});
339347
}
340348

341-
output.prepare(&sizes);
349+
output.prepare(&keys);
342350

343351
for index in 0..len {
344352
let scaling = passes[index].meta().scaling.clone();

0 commit comments

Comments
 (0)