Skip to main content

ferroui_base/platform/
retained_framebuffer.rs

1use super::internal::UnmanagedBlob;
2use super::{AlphaFormat, ILockedFramebuffer, PixelFormat};
3use crate::{PixelSize, Vector};
4use std::cell::RefCell;
5use std::rc::Rc;
6
7/// Pixel memory that outlives the frames drawn into it.
8///
9/// A platform without a native surface to lock (a script-driven canvas, a
10/// test harness) keeps one of these per render target: each frame locks it,
11/// the renderer draws, and unlocking hands the pixels to `blit`.
12pub struct RetainedFramebuffer {
13    size: PixelSize,
14    row_bytes: i32,
15    format: PixelFormat,
16    alpha_format: AlphaFormat,
17    blob: RefCell<Option<Rc<UnmanagedBlob>>>,
18}
19
20fn validate_known_format(format: PixelFormat) -> PixelFormat {
21    if format.bits_per_pixel() % 8 == 0 {
22        format
23    } else {
24        panic!("Specified argument was out of the range of valid values. (Parameter 'format')");
25    }
26}
27
28impl RetainedFramebuffer {
29    /// Creates a framebuffer whose rows are exactly as long as their pixels.
30    ///
31    /// # Panics
32    /// Panics when a pixel of `format` does not occupy whole bytes or when
33    /// `size` is empty.
34    pub fn new(size: PixelSize, format: PixelFormat, alpha_format: AlphaFormat) -> Rc<Self> {
35        let format = validate_known_format(format);
36        Self::with_row_bytes(size, format, alpha_format, format.bits_per_pixel() as i32 / 8 * size.width)
37    }
38
39    /// Creates a framebuffer with the given row length.
40    ///
41    /// # Panics
42    /// Panics when `size` is empty or `row_bytes` is too small for a row of
43    /// pixels.
44    pub fn with_row_bytes(size: PixelSize, format: PixelFormat, alpha_format: AlphaFormat, row_bytes: i32) -> Rc<Self> {
45        if size.width <= 0 || size.height <= 0 {
46            panic!("Specified argument was out of the range of valid values. (Parameter 'size')");
47        }
48        if size.width * (format.bits_per_pixel() as i32 / 8) > row_bytes {
49            panic!("Specified argument was out of the range of valid values. (Parameter 'rowBytes')");
50        }
51        Rc::new(Self {
52            size,
53            row_bytes,
54            format,
55            alpha_format,
56            blob: RefCell::new(Some(Rc::new(UnmanagedBlob::new(row_bytes * size.height)))),
57        })
58    }
59
60    /// The size in device pixels.
61    pub fn size(&self) -> PixelSize {
62        self.size
63    }
64
65    /// The number of bytes per row.
66    pub fn row_bytes(&self) -> i32 {
67        self.row_bytes
68    }
69
70    /// The pixel format.
71    pub fn format(&self) -> PixelFormat {
72        self.format
73    }
74
75    /// The alpha format.
76    pub fn alpha_format(&self) -> AlphaFormat {
77        self.alpha_format
78    }
79
80    /// The address of the first pixel.
81    ///
82    /// # Panics
83    /// Panics when the framebuffer has been disposed.
84    pub fn address(&self) -> *mut u8 {
85        self.blob().address()
86    }
87
88    fn blob(&self) -> Rc<UnmanagedBlob> {
89        match self.blob.borrow().as_ref() {
90            Some(blob) => blob.clone(),
91            None => panic!("Cannot access a disposed object: RetainedFramebuffer"),
92        }
93    }
94
95    /// Locks the framebuffer for one frame. `blit` runs whenever the
96    /// returned framebuffer is disposed and receives this framebuffer.
97    ///
98    /// # Panics
99    /// Panics when the framebuffer has been disposed.
100    pub fn lock(self: &Rc<Self>, dpi: Vector, blit: impl Fn(&Rc<RetainedFramebuffer>) + 'static) -> Rc<dyn ILockedFramebuffer> {
101        let blob = self.blob();
102        let this = self.clone();
103        Rc::new(RetainedLockedFramebuffer {
104            address: blob.address(),
105            blob,
106            size: self.size,
107            row_bytes: self.row_bytes,
108            dpi,
109            format: self.format,
110            alpha_format: self.alpha_format,
111            on_dispose: Box::new(move || blit(&this)),
112        })
113    }
114
115    /// Releases the pixel memory. Further calls do nothing.
116    pub fn dispose(&self) {
117        if let Some(blob) = self.blob.borrow_mut().take() {
118            blob.dispose();
119        }
120    }
121}
122
123/// A lock of a [`RetainedFramebuffer`].
124struct RetainedLockedFramebuffer {
125    address: *mut u8,
126    blob: Rc<UnmanagedBlob>,
127    size: PixelSize,
128    row_bytes: i32,
129    dpi: Vector,
130    format: PixelFormat,
131    alpha_format: AlphaFormat,
132    on_dispose: Box<dyn Fn()>,
133}
134
135impl ILockedFramebuffer for RetainedLockedFramebuffer {
136    fn address(&self) -> *mut u8 {
137        self.address
138    }
139
140    fn with_data(&self, access: &mut dyn FnMut(&mut [u8])) {
141        if !self.blob.with_data(access) {
142            access(&mut []);
143        }
144    }
145
146    fn size(&self) -> PixelSize {
147        self.size
148    }
149
150    fn row_bytes(&self) -> i32 {
151        self.row_bytes
152    }
153
154    fn dpi(&self) -> Vector {
155        self.dpi
156    }
157
158    fn format(&self) -> PixelFormat {
159        self.format
160    }
161
162    fn alpha_format(&self) -> AlphaFormat {
163        self.alpha_format
164    }
165
166    fn dispose(&self) {
167        (self.on_dispose)();
168    }
169}
170
171#[cfg(test)]
172mod tests {
173    use super::*;
174    use crate::platform::PixelFormats;
175    use std::cell::Cell;
176
177    #[test]
178    fn row_bytes_default_to_the_width_in_bytes() {
179        let framebuffer = RetainedFramebuffer::new(PixelSize::new(10, 4), PixelFormats::RGBA8888, AlphaFormat::Premul);
180
181        assert_eq!(PixelSize::new(10, 4), framebuffer.size());
182        assert_eq!(40, framebuffer.row_bytes());
183        assert_eq!(PixelFormats::RGBA8888, framebuffer.format());
184        assert_eq!(AlphaFormat::Premul, framebuffer.alpha_format());
185        assert!(!framebuffer.address().is_null());
186    }
187
188    #[test]
189    fn a_sixteen_bit_format_takes_two_bytes_per_pixel() {
190        let framebuffer = RetainedFramebuffer::new(PixelSize::new(3, 2), PixelFormats::RGB565, AlphaFormat::Opaque);
191
192        assert_eq!(6, framebuffer.row_bytes());
193    }
194
195    #[test]
196    fn lock_describes_the_retained_memory_and_blits_on_every_dispose() {
197        let framebuffer = RetainedFramebuffer::with_row_bytes(
198            PixelSize::new(2, 2),
199            PixelFormats::BGRA8888,
200            AlphaFormat::Premul,
201            12,
202        );
203        let blitted = Rc::new(Cell::new(0));
204        let seen = blitted.clone();
205        let expected = framebuffer.address();
206
207        let locked = framebuffer.lock(Vector::new(96.0, 192.0), move |fb| {
208            assert_eq!(12, fb.row_bytes());
209            seen.set(seen.get() + 1);
210        });
211
212        assert_eq!(expected, locked.address());
213        assert_eq!(PixelSize::new(2, 2), locked.size());
214        assert_eq!(12, locked.row_bytes());
215        assert_eq!(Vector::new(96.0, 192.0), locked.dpi());
216        assert_eq!(PixelFormats::BGRA8888, locked.format());
217        assert_eq!(AlphaFormat::Premul, locked.alpha_format());
218        let mut length = 0;
219        locked.with_data(&mut |data| {
220            length = data.len();
221            data[0] = 7;
222        });
223        assert_eq!(24, length);
224        assert_eq!(0, blitted.get());
225
226        locked.dispose();
227        assert_eq!(1, blitted.get());
228
229        // Every unlock presents the pixels again.
230        locked.dispose();
231        assert_eq!(2, blitted.get());
232    }
233
234    #[test]
235    fn pixels_survive_between_locks() {
236        let framebuffer = RetainedFramebuffer::new(PixelSize::new(1, 1), PixelFormats::RGBA8888, AlphaFormat::Premul);
237
238        let first = framebuffer.lock(Vector::new(96.0, 96.0), |_| {});
239        first.with_data(&mut |data| data.copy_from_slice(&[1, 2, 3, 4]));
240        first.dispose();
241
242        let second = framebuffer.lock(Vector::new(96.0, 96.0), |_| {});
243        let mut read = Vec::new();
244        second.with_data(&mut |data| read = data.to_vec());
245        assert_eq!(vec![1, 2, 3, 4], read);
246    }
247
248    #[test]
249    #[should_panic(expected = "Parameter 'format'")]
250    fn a_format_with_partial_bytes_is_rejected() {
251        RetainedFramebuffer::new(PixelSize::new(8, 8), PixelFormats::BLACK_WHITE, AlphaFormat::Opaque);
252    }
253
254    #[test]
255    #[should_panic(expected = "Parameter 'size'")]
256    fn an_empty_size_is_rejected() {
257        RetainedFramebuffer::new(PixelSize::new(0, 8), PixelFormats::RGBA8888, AlphaFormat::Premul);
258    }
259
260    #[test]
261    #[should_panic(expected = "Parameter 'rowBytes'")]
262    fn rows_shorter_than_their_pixels_are_rejected() {
263        RetainedFramebuffer::with_row_bytes(PixelSize::new(4, 4), PixelFormats::RGBA8888, AlphaFormat::Premul, 15);
264    }
265
266    #[test]
267    #[should_panic(expected = "Cannot access a disposed object: RetainedFramebuffer")]
268    fn a_disposed_framebuffer_cannot_be_locked() {
269        let framebuffer = RetainedFramebuffer::new(PixelSize::new(1, 1), PixelFormats::RGBA8888, AlphaFormat::Premul);
270        framebuffer.dispose();
271        framebuffer.dispose();
272        framebuffer.lock(Vector::new(96.0, 96.0), |_| {});
273    }
274}