ferroui_base/rendering/
swapchain_base.rs1use std::cell::RefCell;
2use std::rc::Rc;
3
4use crate::reactive::{Disposable, IDisposable};
5use crate::PixelSize;
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
9pub enum SwapchainImagePresentStatus {
10 Pending,
12 RanToCompletion,
14 Canceled,
16 Faulted,
18}
19
20pub trait ISwapchainImage {
22 fn size(&self) -> PixelSize;
23
24 fn last_present(&self) -> Option<SwapchainImagePresentStatus>;
27
28 fn begin_draw(&self);
29
30 fn present(&self);
31
32 fn dispose(&self);
34}
35
36pub trait SwapchainImageFactory<TImage: ISwapchainImage + ?Sized> {
39 fn create_image(&self, size: PixelSize) -> Rc<TImage>;
40}
41
42pub struct SwapchainBase<TImage: ISwapchainImage + ?Sized + 'static> {
47 pending_images: Rc<RefCell<Vec<Rc<TImage>>>>,
48}
49
50impl<TImage: ISwapchainImage + ?Sized + 'static> Default for SwapchainBase<TImage> {
51 fn default() -> Self {
52 Self::new()
53 }
54}
55
56impl<TImage: ISwapchainImage + ?Sized + 'static> SwapchainBase<TImage> {
57 pub fn new() -> Self {
58 Self { pending_images: Rc::new(RefCell::new(Vec::new())) }
59 }
60
61 fn is_broken(image: &TImage) -> bool {
62 image.last_present() == Some(SwapchainImagePresentStatus::Faulted)
63 }
64
65 fn is_ready(image: &TImage) -> bool {
66 matches!(image.last_present(), None | Some(SwapchainImagePresentStatus::RanToCompletion))
67 }
68
69 fn cleanup_and_find_next_image(&self, size: PixelSize) -> Option<Rc<TImage>> {
70 let mut first_found: Option<Rc<TImage>> = None;
71 let mut found_multiple = false;
72
73 let mut c = self.pending_images.borrow().len();
74 while c > 0 {
75 c -= 1;
76 let image = self.pending_images.borrow()[c].clone();
78 let ready = Self::is_ready(&image);
79 let matches = image.size() == size;
80 if Self::is_broken(&image) || (!matches && ready) {
81 image.dispose();
82 self.pending_images.borrow_mut().remove(c);
83 }
84
85 if matches && ready {
86 if first_found.is_none() {
87 first_found = Some(image);
88 } else {
89 found_multiple = true;
90 }
91 }
92 }
93
94 if found_multiple {
97 first_found
98 } else {
99 None
100 }
101 }
102
103 pub fn begin_draw_core(
107 &self,
108 factory: &(impl SwapchainImageFactory<TImage> + ?Sized),
109 size: PixelSize,
110 ) -> (Rc<dyn IDisposable>, Rc<TImage>) {
111 let img = match self.cleanup_and_find_next_image(size) {
112 Some(img) => img,
113 None => factory.create_image(size),
114 };
115
116 img.begin_draw();
117 {
118 let mut pending_images = self.pending_images.borrow_mut();
119 if let Some(index) = pending_images.iter().position(|pending| Rc::ptr_eq(pending, &img)) {
120 pending_images.remove(index);
121 }
122 }
123
124 let image = img.clone();
125 let pending_images = self.pending_images.clone();
126 let session = Disposable::create(move || {
127 img.present();
128 pending_images.borrow_mut().push(img);
129 });
130 (session, image)
131 }
132
133 pub fn dispose(&self) {
135 let pending_images: Vec<Rc<TImage>> = self.pending_images.borrow().clone();
136 for img in pending_images {
137 img.dispose();
138 }
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145 use std::cell::Cell;
146
147 struct Image {
148 id: i32,
149 size: PixelSize,
150 last_present: Cell<Option<SwapchainImagePresentStatus>>,
151 begin_draws: Cell<i32>,
152 presents: Cell<i32>,
153 disposed: Cell<bool>,
154 }
155
156 impl ISwapchainImage for Image {
157 fn size(&self) -> PixelSize {
158 self.size
159 }
160
161 fn last_present(&self) -> Option<SwapchainImagePresentStatus> {
162 self.last_present.get()
163 }
164
165 fn begin_draw(&self) {
166 self.begin_draws.set(self.begin_draws.get() + 1);
167 }
168
169 fn present(&self) {
170 self.presents.set(self.presents.get() + 1);
171 self.last_present.set(Some(SwapchainImagePresentStatus::Pending));
172 }
173
174 fn dispose(&self) {
175 self.disposed.set(true);
176 }
177 }
178
179 #[derive(Default)]
180 struct Swapchain {
181 base: SwapchainBase<Image>,
182 created: RefCell<Vec<Rc<Image>>>,
183 }
184
185 impl SwapchainImageFactory<Image> for Swapchain {
186 fn create_image(&self, size: PixelSize) -> Rc<Image> {
187 let image = Rc::new(Image {
188 id: self.created.borrow().len() as i32,
189 size,
190 last_present: Cell::new(None),
191 begin_draws: Cell::new(0),
192 presents: Cell::new(0),
193 disposed: Cell::new(false),
194 });
195 self.created.borrow_mut().push(image.clone());
196 image
197 }
198 }
199
200 impl Swapchain {
201 fn begin_draw(&self, size: PixelSize) -> (Rc<dyn IDisposable>, Rc<Image>) {
202 self.base.begin_draw_core(self, size)
203 }
204
205 fn frame(&self, size: PixelSize) -> i32 {
207 let (session, image) = self.begin_draw(size);
208 session.dispose();
209 image.id
210 }
211
212 fn complete_all(&self) {
213 for image in self.created.borrow().iter() {
214 if image.last_present.get() == Some(SwapchainImagePresentStatus::Pending) {
215 image.last_present.set(Some(SwapchainImagePresentStatus::RanToCompletion));
216 }
217 }
218 }
219
220 fn pending(&self) -> Vec<i32> {
221 self.base.pending_images.borrow().iter().map(|image| image.id).collect()
222 }
223 }
224
225 const SIZE: PixelSize = PixelSize::new(100, 50);
226
227 #[test]
228 fn draw_session_begins_draws_and_presents_on_dispose() {
229 let swapchain = Swapchain::default();
230 let (session, image) = swapchain.begin_draw(SIZE);
231 assert_eq!(image.size, SIZE);
232 assert_eq!((image.begin_draws.get(), image.presents.get()), (1, 0));
233 assert!(swapchain.pending().is_empty());
235
236 session.dispose();
237 assert_eq!(image.presents.get(), 1);
238 assert_eq!(swapchain.pending(), [0]);
239
240 session.dispose();
242 assert_eq!(image.presents.get(), 1);
243 assert_eq!(swapchain.pending(), [0]);
244 }
245
246 #[test]
247 fn images_are_not_reused_while_their_presentation_is_pending() {
248 let swapchain = Swapchain::default();
249 assert_eq!(swapchain.frame(SIZE), 0);
250 assert_eq!(swapchain.frame(SIZE), 1);
251 assert_eq!(swapchain.frame(SIZE), 2);
252 assert_eq!(swapchain.pending(), [0, 1, 2]);
253 }
254
255 #[test]
256 fn an_image_is_reused_only_when_another_ready_image_of_the_size_exists() {
257 let swapchain = Swapchain::default();
258 assert_eq!(swapchain.frame(SIZE), 0);
259 swapchain.complete_all();
260 assert_eq!(swapchain.frame(SIZE), 1);
262 swapchain.complete_all();
263 assert_eq!(swapchain.frame(SIZE), 1);
265 assert_eq!(swapchain.created.borrow().len(), 2);
266 assert_eq!(swapchain.pending(), [0, 1]);
267 assert_eq!(swapchain.created.borrow()[1].begin_draws.get(), 2);
268
269 assert_eq!(swapchain.frame(SIZE), 2);
271 assert_eq!(swapchain.pending(), [0, 1, 2]);
272 }
273
274 #[test]
275 fn ready_images_of_another_size_are_disposed() {
276 let swapchain = Swapchain::default();
277 swapchain.frame(SIZE);
278 swapchain.frame(SIZE);
279 swapchain.complete_all();
280 swapchain.frame(SIZE);
282
283 let other = PixelSize::new(200, 50);
284 assert_eq!(swapchain.frame(other), 2);
285 let created = swapchain.created.borrow().clone();
286 assert!(created[0].disposed.get());
288 assert!(!created[1].disposed.get());
289 assert_eq!(swapchain.pending(), [1, 2]);
290
291 swapchain.complete_all();
292 assert_eq!(swapchain.frame(other), 3);
293 assert!(created[1].disposed.get());
294 assert_eq!(swapchain.pending(), [2, 3]);
295 }
296
297 #[test]
298 fn broken_images_are_disposed() {
299 let swapchain = Swapchain::default();
300 swapchain.frame(SIZE);
301 swapchain.frame(SIZE);
302 let created = swapchain.created.borrow().clone();
303 created[0].last_present.set(Some(SwapchainImagePresentStatus::Faulted));
304 created[1].last_present.set(Some(SwapchainImagePresentStatus::Canceled));
305
306 assert_eq!(swapchain.frame(SIZE), 2);
307 assert!(created[0].disposed.get());
308 assert!(!created[1].disposed.get());
310 assert_eq!(swapchain.pending(), [1, 2]);
311 }
312
313 #[test]
314 fn dispose_releases_the_pending_images() {
315 let swapchain = Swapchain::default();
316 swapchain.frame(SIZE);
317 let (_session, drawing) = swapchain.begin_draw(SIZE);
318 swapchain.base.dispose();
319 let created = swapchain.created.borrow().clone();
320 assert!(created[0].disposed.get());
321 assert!(!drawing.disposed.get());
323 }
324}