ferroui_browser/rendering/
browser_surface_shared.rs1use crate::interop::canvas_helper::{RENDER_TARGET_KIND_SOFTWARE, RENDER_TARGET_KIND_WEB_GL};
2use crate::interop::thread_proxy;
3use ferroui_base::PixelSize;
4use std::cell::RefCell;
5use std::collections::HashMap;
6use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU32, AtomicU64, AtomicUsize, Ordering};
7use std::sync::{Arc, Mutex, MutexGuard, OnceLock, PoisonError, Weak};
8
9pub struct BrowserSurfaceShared {
44 target_id: AtomicI32,
45 size_version: AtomicU32,
46 width: AtomicI32,
47 height: AtomicI32,
48 scaling: AtomicU64,
49 target_kind: AtomicI32,
50 disposed: AtomicBool,
51 frames: AtomicU64,
52 first_frame_thread: AtomicUsize,
56}
57
58const _: fn() = || {
59 fn assert_send_sync<T: Send + Sync>() {}
60 assert_send_sync::<BrowserSurfaceShared>();
61};
62
63#[derive(Default)]
70struct SurfaceRegistry {
71 surfaces: HashMap<i32, Weak<BrowserSurfaceShared>>,
72 reported: HashMap<i32, i32>,
77}
78
79static REGISTRY: OnceLock<Mutex<SurfaceRegistry>> = OnceLock::new();
80
81fn registry() -> MutexGuard<'static, SurfaceRegistry> {
82 REGISTRY.get_or_init(Mutex::default).lock().unwrap_or_else(PoisonError::into_inner)
83}
84
85type FirstFrameHandler = Box<dyn FnOnce()>;
87
88ferroui_base::ferro_thread_local! {
89 static FIRST_FRAME_HANDLERS: RefCell<HashMap<i32, FirstFrameHandler>> = RefCell::new(HashMap::new());
95}
96
97fn first_frame_drawn(target_id: i32) {
100 let handler = FIRST_FRAME_HANDLERS.with(|handlers| handlers.borrow_mut().remove(&target_id));
101 if let Some(handler) = handler {
102 handler();
103 }
104}
105
106impl BrowserSurfaceShared {
107 pub fn new() -> Arc<Self> {
110 Arc::new(Self {
111 target_id: AtomicI32::new(0),
112 size_version: AtomicU32::new(0),
113 width: AtomicI32::new(0),
114 height: AtomicI32::new(0),
115 scaling: AtomicU64::new(0f64.to_bits()),
116 target_kind: AtomicI32::new(0),
117 disposed: AtomicBool::new(false),
118 frames: AtomicU64::new(0),
119 first_frame_thread: AtomicUsize::new(0),
120 })
121 }
122
123 pub fn target_id(&self) -> i32 {
126 self.target_id.load(Ordering::SeqCst)
127 }
128
129 pub fn set_target_id(&self, target_id: i32) {
132 self.target_id.store(target_id, Ordering::SeqCst);
133 }
134
135 pub fn register(self: &Arc<Self>, target_id: i32) {
143 self.set_target_id(target_id);
144 let mut registry = registry();
145 registry.surfaces.insert(target_id, Arc::downgrade(self));
146 if let Some(kind) = registry.reported.remove(&target_id) {
147 self.set_target_kind(kind);
148 }
149 }
150
151 pub fn unregister(&self) {
156 let target_id = self.target_id();
157 let mut registry = registry();
158 if registry.surfaces.get(&target_id).is_some_and(|surface| std::ptr::eq(surface.as_ptr(), self)) {
159 registry.surfaces.remove(&target_id);
160 }
161 }
162
163 pub fn find(target_id: i32) -> Option<Arc<Self>> {
166 registry().surfaces.get(&target_id).and_then(Weak::upgrade)
167 }
168
169 pub fn report_target(target_id: i32, kind: i32) -> Option<Arc<Self>> {
181 assert_target_kind(kind);
182 let mut registry = registry();
183 match registry.surfaces.get(&target_id).and_then(Weak::upgrade) {
184 Some(surface) => {
185 surface.set_target_kind(kind);
186 Some(surface)
187 }
188 None => {
189 registry.surfaces.remove(&target_id);
190 registry.reported.insert(target_id, kind);
191 None
192 }
193 }
194 }
195
196 pub fn size(&self) -> (PixelSize, f64) {
199 loop {
200 let version = self.size_version.load(Ordering::SeqCst);
201 if version & 1 == 0 {
202 let width = self.width.load(Ordering::SeqCst);
203 let height = self.height.load(Ordering::SeqCst);
204 let scaling = f64::from_bits(self.scaling.load(Ordering::SeqCst));
205 if self.size_version.load(Ordering::SeqCst) == version {
206 return (PixelSize::new(width, height), scaling);
207 }
208 }
209 std::hint::spin_loop();
210 }
211 }
212
213 pub fn set_size(&self, size: PixelSize, scaling: f64) {
216 let mut version = self.size_version.load(Ordering::SeqCst);
220 loop {
221 if version & 1 == 1 {
222 std::hint::spin_loop();
223 version = self.size_version.load(Ordering::SeqCst);
224 continue;
225 }
226 match self.size_version.compare_exchange_weak(
227 version,
228 version.wrapping_add(1),
229 Ordering::SeqCst,
230 Ordering::SeqCst,
231 ) {
232 Ok(_) => break,
233 Err(current) => version = current,
234 }
235 }
236 self.width.store(size.width, Ordering::SeqCst);
237 self.height.store(size.height, Ordering::SeqCst);
238 self.scaling.store(scaling.to_bits(), Ordering::SeqCst);
239 self.size_version.store(version.wrapping_add(2), Ordering::SeqCst);
240 }
241
242 pub fn on_size_changed(&self, pixel_width: f64, pixel_height: f64, dpr: f64) {
248 self.set_size(PixelSize::new(pixel_width as i32, pixel_height as i32), dpr);
249 }
250
251 pub fn set_target_kind(&self, kind: i32) {
260 assert_target_kind(kind);
261 self.target_kind.store(kind, Ordering::SeqCst);
262 }
263
264 pub fn target_kind(&self) -> i32 {
266 self.target_kind.load(Ordering::SeqCst)
267 }
268
269 pub fn has_target(&self) -> bool {
271 self.target_kind() != 0
272 }
273
274 pub fn uses_contexts(&self) -> Option<bool> {
277 match self.target_kind() {
278 0 => None,
279 kind => Some(kind == RENDER_TARGET_KIND_WEB_GL),
280 }
281 }
282
283 pub fn dispose(&self) {
285 self.disposed.store(true, Ordering::SeqCst);
286 }
287
288 pub fn is_disposed(&self) -> bool {
290 self.disposed.load(Ordering::SeqCst)
291 }
292
293 pub fn is_ready(&self) -> bool {
296 self.has_target() && !self.is_disposed() && self.size().0 != PixelSize::default()
297 }
298
299 pub fn frames(&self) -> u64 {
301 self.frames.load(Ordering::SeqCst)
302 }
303
304 pub fn on_first_frame(&self, handler: Box<dyn FnOnce()>) {
325 let target_id = self.target_id();
326 self.first_frame_thread.store(thread_proxy::current_thread(), Ordering::SeqCst);
329 FIRST_FRAME_HANDLERS.with(|handlers| handlers.borrow_mut().insert(target_id, handler));
330 if self.frames() > 0 {
331 first_frame_drawn(target_id);
332 }
333 }
334
335 pub fn forget_first_frame(&self) {
339 let target_id = self.target_id();
340 let handler = FIRST_FRAME_HANDLERS.with(|handlers| handlers.borrow_mut().remove(&target_id));
341 drop(handler);
343 }
344
345 pub(crate) fn frame_presented(&self) {
351 if self.frames.fetch_add(1, Ordering::SeqCst) != 0 {
352 return;
353 }
354 let target_id = self.target_id();
355 let thread = self.first_frame_thread.load(Ordering::SeqCst);
356 if thread_proxy::current_thread() == thread {
357 first_frame_drawn(target_id);
358 } else {
359 thread_proxy::run_on_thread(thread, Box::new(move || first_frame_drawn(target_id)));
360 }
361 }
362}
363
364fn assert_target_kind(kind: i32) {
365 assert!(
366 kind == RENDER_TARGET_KIND_WEB_GL || kind == RENDER_TARGET_KIND_SOFTWARE,
367 "{kind} is not a kind of render target"
368 );
369}
370
371#[cfg(test)]
372mod tests {
373 use super::*;
374 use std::cell::Cell;
375 use std::rc::Rc;
376
377 #[test]
378 fn a_new_surface_has_nothing_and_is_not_ready() {
379 let shared = BrowserSurfaceShared::new();
380
381 assert_eq!(0, shared.target_id());
382 assert_eq!((PixelSize::default(), 0.0), shared.size());
383 assert!(!shared.has_target());
384 assert_eq!(None, shared.uses_contexts());
385 assert!(!shared.is_disposed());
386 assert!(!shared.is_ready());
387 }
388
389 #[test]
390 fn it_is_ready_with_a_target_and_a_size_until_it_is_disposed() {
391 let shared = BrowserSurfaceShared::new();
392 shared.set_target_id(3);
393 shared.set_target_kind(RENDER_TARGET_KIND_SOFTWARE);
394 assert_eq!(Some(false), shared.uses_contexts());
395 assert!(!shared.is_ready());
396
397 shared.on_size_changed(300.0, 180.0, 1.5);
398 assert_eq!(3, shared.target_id());
399 assert_eq!((PixelSize::new(300, 180), 1.5), shared.size());
400 assert!(shared.is_ready());
401
402 shared.set_target_kind(RENDER_TARGET_KIND_WEB_GL);
403 assert_eq!(Some(true), shared.uses_contexts());
404
405 shared.dispose();
406 assert!(shared.is_disposed());
407 assert!(!shared.is_ready());
408 }
409
410 #[test]
411 #[should_panic(expected = "is not a kind of render target")]
412 fn an_unknown_kind_is_refused() {
413 BrowserSurfaceShared::new().set_target_kind(7);
414 }
415
416 #[test]
420 fn a_registered_surface_is_found_by_its_id_while_it_is_alive() {
421 let shared = BrowserSurfaceShared::new();
422 assert!(BrowserSurfaceShared::find(9101).is_none());
423
424 shared.register(9101);
425 assert_eq!(9101, shared.target_id());
426 assert!(Arc::ptr_eq(&shared, &BrowserSurfaceShared::find(9101).expect("the surface is registered")));
427 let found = std::thread::spawn(|| BrowserSurfaceShared::find(9101)).join().unwrap();
429 assert!(Arc::ptr_eq(&shared, &found.expect("the surface is registered")));
430
431 drop(shared);
433 assert!(BrowserSurfaceShared::find(9101).is_none());
434 }
435
436 #[test]
437 fn a_reported_target_publishes_its_kind_in_the_surface_of_its_id() {
438 let shared = BrowserSurfaceShared::new();
439 shared.register(9201);
440 shared.on_size_changed(10.0, 10.0, 1.0);
441 assert!(!shared.is_ready());
442
443 let reported =
445 std::thread::spawn(|| BrowserSurfaceShared::report_target(9201, RENDER_TARGET_KIND_WEB_GL)).join().unwrap();
446
447 assert!(Arc::ptr_eq(&shared, &reported.expect("the surface is registered")));
448 assert_eq!(Some(true), shared.uses_contexts());
449 assert!(shared.is_ready());
450 }
451
452 #[test]
453 fn a_target_reported_before_its_surface_is_registered_is_kept_for_it() {
454 assert!(BrowserSurfaceShared::report_target(9301, RENDER_TARGET_KIND_SOFTWARE).is_none());
456
457 let shared = BrowserSurfaceShared::new();
458 assert!(!shared.has_target());
459 shared.register(9301);
460 assert_eq!(Some(false), shared.uses_contexts());
461
462 let next = BrowserSurfaceShared::new();
465 next.register(9301);
466 assert!(!next.has_target());
467 assert!(Arc::ptr_eq(&next, &BrowserSurfaceShared::find(9301).expect("the surface is registered")));
468 }
469
470 #[test]
471 fn an_unregistered_surface_is_not_found_and_takes_no_report() {
472 let shared = BrowserSurfaceShared::new();
473 shared.register(9401);
474 shared.unregister();
475 shared.unregister();
476
477 assert!(BrowserSurfaceShared::find(9401).is_none());
478 assert!(BrowserSurfaceShared::report_target(9401, RENDER_TARGET_KIND_WEB_GL).is_none());
479 assert!(!shared.has_target());
480
481 let next = BrowserSurfaceShared::new();
483 next.register(9401);
484 shared.unregister();
485 assert!(Arc::ptr_eq(&next, &BrowserSurfaceShared::find(9401).expect("the surface is registered")));
486 }
487
488 #[test]
489 fn the_first_frame_runs_the_handler_once() {
490 let shared = BrowserSurfaceShared::new();
491 shared.set_target_id(9801);
492 let runs = Rc::new(Cell::new(0));
493 shared.on_first_frame(Box::new({
494 let runs = runs.clone();
495 move || runs.set(runs.get() + 1)
496 }));
497 assert_eq!((0, 0), (shared.frames(), runs.get()));
498
499 shared.frame_presented();
500 assert_eq!((1, 1), (shared.frames(), runs.get()));
501 shared.frame_presented();
502 assert_eq!((2, 1), (shared.frames(), runs.get()));
503 }
504
505 #[test]
506 fn a_handler_given_after_the_first_frame_runs_at_once() {
507 let shared = BrowserSurfaceShared::new();
508 shared.set_target_id(9802);
509 shared.frame_presented();
510 let ran = Rc::new(Cell::new(false));
511 shared.on_first_frame(Box::new({
512 let ran = ran.clone();
513 move || ran.set(true)
514 }));
515
516 assert!(ran.get());
517 }
518
519 #[test]
520 fn a_forgotten_handler_does_not_run() {
521 let shared = BrowserSurfaceShared::new();
522 shared.set_target_id(9803);
523 shared.on_first_frame(Box::new(|| panic!("the view was closed before its first frame")));
524 shared.forget_first_frame();
525
526 shared.frame_presented();
527 assert_eq!(1, shared.frames());
528 }
529
530 #[test]
531 #[should_panic(expected = "is not a kind of render target")]
532 fn a_report_of_an_unknown_kind_is_refused() {
533 BrowserSurfaceShared::report_target(9501, 7);
534 }
535
536 #[test]
537 fn a_reader_on_another_thread_never_sees_half_of_a_write() {
538 let shared = BrowserSurfaceShared::new();
539 let stop = Arc::new(AtomicBool::new(false));
540 let reader = std::thread::spawn({
541 let shared = shared.clone();
542 let stop = stop.clone();
543 move || {
544 let mut reads = 0u32;
545 loop {
546 let (size, scaling) = shared.size();
547 assert_eq!(size.width, size.height);
549 assert_eq!(f64::from(size.width), scaling);
550 reads += 1;
551 if stop.load(Ordering::SeqCst) {
552 return reads;
553 }
554 }
555 }
556 });
557
558 for value in 1..=20_000 {
559 shared.set_size(PixelSize::new(value, value), f64::from(value));
560 }
561 stop.store(true, Ordering::SeqCst);
562
563 assert!(reader.join().unwrap() > 0);
564 assert_eq!((PixelSize::new(20_000, 20_000), 20_000.0), shared.size());
565 }
566}