ferroui_browser/rendering/
render_target_browser_surface.rs1use super::web_render_target::{get_render_target, remove_render_target, unregister_canvas};
2use super::{
3 BrowserRenderSurface, BrowserSharedRenderLoop, BrowserSurface, BrowserSurfaceShared, RenderStatistics,
4 RenderWorker,
5};
6use crate::browser_app_builder::BrowserRenderingMode;
7use crate::interop::canvas_helper::CanvasSurface;
8use crate::interop::{thread_proxy, JsObject};
9use ferroui_base::media::MediaContext;
10use ferroui_base::platform::surfaces::IPlatformRenderSurface;
11use ferroui_base::platform::{
12 IOptionalFeatureProvider, IPlatformGraphics, IPlatformGraphicsContext, IPlatformGraphicsReadyStateFeature,
13};
14use ferroui_base::rendering::composition::server::ServerCompositor;
15use ferroui_base::rendering::composition::Compositor;
16use ferroui_base::threading::Dispatcher;
17use ferroui_base::{PixelSize, Size};
18use std::any::{Any, TypeId};
19use std::cell::Cell;
20use std::rc::Rc;
21use std::sync::Arc;
22
23pub struct RenderTargetBrowserSurface {
33 base: BrowserSurface,
34 shared: Arc<BrowserSurfaceShared>,
35 render_surface: Arc<BrowserRenderSurface>,
36 disposed: Cell<bool>,
38}
39
40impl RenderTargetBrowserSurface {
41 fn new(js_surface: CanvasSurface, thread_id: i32) -> Rc<Self> {
44 let target_id = js_surface.target_id();
45 let shared = BrowserSurfaceShared::new();
46 shared.register(target_id);
49 if thread_id == 0 {
50 if let Some(target) = get_render_target(target_id) {
55 shared.set_target_kind(target.kind());
56 }
57 }
58 let gpu: Arc<dyn IPlatformGraphics> = Arc::new(BrowserPlatformGraphics { shared: shared.clone() });
59 let compositor = if thread_id == 0 {
60 Compositor::with_scheduler(
61 BrowserSharedRenderLoop::render_loop(),
62 Some(gpu),
63 false,
64 &MediaContext::instance().scheduler(),
65 Dispatcher::ui_thread(),
66 None,
67 None,
68 )
69 } else {
70 Compositor::with_render_thread(
78 BrowserSharedRenderLoop::render_loop(),
79 Some(gpu),
80 false,
81 &MediaContext::instance().scheduler(),
82 Dispatcher::ui_thread(),
83 None,
84 None,
85 )
86 };
87
88 let render_surface = BrowserRenderSurface::new(shared.clone());
89 let this = Rc::new(Self {
90 base: BrowserSurface::new(js_surface, compositor),
91 shared,
92 render_surface,
93 disposed: Cell::new(false),
94 });
95 if let Some((w, h, s)) = this.base.initial_size() {
96 this.on_size_changed(w, h, s);
97 }
98 this
99 }
100
101 pub fn create(container: &JsObject, modes: &[BrowserRenderingMode], top_level_id: i32) -> Rc<Self> {
104 let modes: Vec<i32> = modes.iter().map(|m| *m as i32).collect();
105 let thread_id =
112 if BrowserSharedRenderLoop::renders_on_render_thread() { RenderWorker::canvas_thread_id() } else { 0 };
113 let js = CanvasSurface::create_render_target_surface(container, &modes, top_level_id, thread_id);
114 Self::new(js, thread_id)
115 }
116
117 pub fn compositor(&self) -> Rc<Compositor> {
119 self.base.compositor()
120 }
121
122 pub fn scaling(&self) -> f64 {
124 self.base.scaling()
125 }
126
127 pub fn client_size(&self) -> Size {
129 self.base.client_size()
130 }
131
132 pub fn render_size(&self) -> PixelSize {
134 self.base.render_size()
135 }
136
137 pub fn is_valid(&self) -> bool {
139 self.base.is_valid()
140 }
141
142 pub fn size_changed(&self, handler: Rc<dyn Fn()>) -> u64 {
144 self.base.size_changed(handler)
145 }
146
147 pub fn scaling_changed(&self, handler: Rc<dyn Fn()>) -> u64 {
149 self.base.scaling_changed(handler)
150 }
151
152 pub fn get_render_surfaces(&self) -> Vec<Arc<dyn IPlatformRenderSurface>> {
157 let surface: Arc<dyn IPlatformRenderSurface> = self.render_surface.clone();
158 vec![surface]
159 }
160
161 pub fn shared(&self) -> &Arc<BrowserSurfaceShared> {
164 &self.shared
165 }
166
167 pub fn on_size_changed(&self, pixel_width: f64, pixel_height: f64, dpr: f64) {
169 self.shared.on_size_changed(pixel_width, pixel_height, dpr);
171 self.base.on_size_changed(pixel_width, pixel_height, dpr);
172 }
173
174 pub fn dispose(&self) {
208 if self.disposed.replace(true) {
209 return;
210 }
211 self.shared.forget_first_frame();
213 self.shared.unregister();
215 let shared = self.shared.clone();
216 let page_thread = thread_proxy::current_thread();
218 let compositor = self.base.compositor();
219 if compositor.is_confined_to_render_thread() {
220 let server = compositor.locked_server();
221 let posted = RenderWorker::post({
222 let shared = shared.clone();
223 move || {
224 let released = server.try_with(|server| release_canvas(server, &shared, page_thread));
225 if released.is_none() {
226 release_canvas_objects(&shared, page_thread);
229 }
230 }
231 });
232 if !posted {
233 if RenderWorker::thread_id() == 0 {
234 release_canvas_objects(&shared, page_thread);
235 } else {
236 shared.dispose();
237 }
238 }
239 } else {
240 compositor.with_server(|server| release_canvas(server, &shared, page_thread));
241 }
242 self.base.dispose();
243 }
244}
245
246impl Drop for RenderTargetBrowserSurface {
247 fn drop(&mut self) {
248 if !std::thread::panicking() {
251 self.dispose();
252 }
253 }
254}
255
256fn release_canvas(server: &ServerCompositor, shared: &Arc<BrowserSurfaceShared>, page_thread: usize) {
277 server.release_gpu_resources();
278 server.stop_rendering();
279 release_canvas_objects(shared, page_thread);
280}
281
282fn release_canvas_objects(shared: &Arc<BrowserSurfaceShared>, page_thread: usize) {
294 let target_id = shared.target_id();
295 shared.dispose();
296 remove_render_target(target_id);
297 RenderStatistics::canvas_released();
298 if thread_proxy::current_thread() == page_thread {
299 unregister_canvas(target_id);
300 } else {
301 thread_proxy::run_on_thread(page_thread, Box::new(move || unregister_canvas(target_id)));
304 }
305}
306
307struct BrowserPlatformGraphics {
317 shared: Arc<BrowserSurfaceShared>,
318}
319
320impl BrowserPlatformGraphics {
321 fn uses_contexts(&self) -> bool {
322 self.shared.uses_contexts().expect("the render target exists")
323 }
324}
325
326impl IPlatformGraphics for BrowserPlatformGraphics {
327 fn uses_shared_context(&self) -> bool {
328 self.uses_contexts()
329 }
330
331 fn create_context(&self) -> Rc<dyn IPlatformGraphicsContext> {
332 panic!("Specified method is not supported.");
333 }
334
335 fn get_shared_context(&self) -> Rc<dyn IPlatformGraphicsContext> {
337 let target = get_render_target(self.shared.target_id()).expect("the render target exists");
338 match target.platform_graphics_context() {
339 Some(context) => context,
340 None => panic!(
341 "This platform graphics instance represents software rendering mode and cant create contexts, you are supposed to query IPlatformGraphicsReadyStateFeature to know this"
342 ),
343 }
344 }
345
346 fn as_feature_provider(&self) -> Option<&dyn IOptionalFeatureProvider> {
347 Some(self)
348 }
349}
350
351impl IOptionalFeatureProvider for BrowserPlatformGraphics {
352 fn try_get_feature(&self, feature_type: TypeId) -> Option<Rc<dyn Any>> {
353 if feature_type == TypeId::of::<dyn IPlatformGraphicsReadyStateFeature>() {
354 let this: Arc<dyn IPlatformGraphicsReadyStateFeature> = Arc::new(Self { shared: self.shared.clone() });
358 return Some(Rc::new(this));
359 }
360 None
361 }
362}
363
364impl IPlatformGraphicsReadyStateFeature for BrowserPlatformGraphics {
365 fn is_ready(&self) -> bool {
366 self.shared.is_ready()
367 }
368
369 fn uses_contexts(&self) -> bool {
370 BrowserPlatformGraphics::uses_contexts(self)
371 }
372}
373
374#[cfg(test)]
375mod tests {
376 use super::*;
377 use crate::interop::canvas_helper::{RENDER_TARGET_KIND_SOFTWARE, RENDER_TARGET_KIND_WEB_GL};
378 use crate::interop::JsObject;
379 use crate::rendering::web_render_target::{
380 set_script_render_targets_for_unit_tests, set_script_unregister_for_unit_tests, BrowserRenderTarget,
381 };
382 use std::cell::RefCell;
383 use crate::rendering::BrowserSoftwareRenderTarget;
384
385 fn software_script(_id: i32) -> Option<BrowserRenderTarget> {
386 Some(BrowserRenderTarget::Software(BrowserSoftwareRenderTarget::new(JsObject::NULL)))
387 }
388
389 fn ready_state(graphics: &BrowserPlatformGraphics) -> Arc<dyn IPlatformGraphicsReadyStateFeature> {
390 let features: &dyn IOptionalFeatureProvider = graphics;
391 features.try_get_shared::<dyn IPlatformGraphicsReadyStateFeature>().expect("the graphics has the feature")
392 }
393
394 #[test]
395 fn the_graphics_is_ready_when_a_thread_published_the_target_and_the_canvas_has_a_size() {
396 let shared = BrowserSurfaceShared::new();
397 let graphics = BrowserPlatformGraphics { shared: shared.clone() };
398 let feature = ready_state(&graphics);
399 assert!(!feature.is_ready());
400
401 shared.on_size_changed(300.0, 180.0, 1.5);
402 assert!(!feature.is_ready());
403
404 std::thread::spawn({
406 let shared = shared.clone();
407 move || shared.set_target_kind(RENDER_TARGET_KIND_WEB_GL)
408 })
409 .join()
410 .unwrap();
411 assert!(feature.is_ready());
412 assert!(feature.uses_contexts());
413 assert!(graphics.uses_shared_context());
414
415 shared.on_size_changed(0.0, 0.0, 1.5);
416 assert!(!feature.is_ready());
417 }
418
419 #[test]
420 fn a_software_target_uses_no_context() {
421 let shared = BrowserSurfaceShared::new();
422 shared.set_target_kind(RENDER_TARGET_KIND_SOFTWARE);
423 let graphics = BrowserPlatformGraphics { shared };
424
425 assert!(!ready_state(&graphics).uses_contexts());
426 assert!(!graphics.uses_shared_context());
427 let features: &dyn IOptionalFeatureProvider = &graphics;
428 assert!(features.try_get_feature(TypeId::of::<dyn IPlatformGraphics>()).is_none());
429 }
430
431 #[test]
432 #[should_panic(expected = "the render target exists")]
433 fn whether_contexts_are_used_is_not_known_before_the_target_exists() {
434 let graphics = BrowserPlatformGraphics { shared: BrowserSurfaceShared::new() };
435
436 graphics.uses_shared_context();
437 }
438
439 #[test]
440 #[should_panic(expected = "represents software rendering mode")]
441 fn a_software_target_has_no_shared_context() {
442 set_script_render_targets_for_unit_tests(software_script);
443 let shared = BrowserSurfaceShared::new();
444 shared.set_target_id(11);
445 shared.set_target_kind(RENDER_TARGET_KIND_SOFTWARE);
446 let graphics = BrowserPlatformGraphics { shared };
447
448 graphics.get_shared_context();
449 }
450
451 ferroui_base::ferro_thread_local! {
452 static UNREGISTERED: RefCell<Vec<i32>> = const { RefCell::new(Vec::new()) };
454 }
455
456 fn record_unregistered(id: i32) {
457 UNREGISTERED.with(|unregistered| unregistered.borrow_mut().push(id));
458 }
459
460 #[test]
461 fn a_released_canvas_is_disposed_leaves_the_table_of_its_thread_and_is_unregistered() {
462 set_script_render_targets_for_unit_tests(software_script);
463 set_script_unregister_for_unit_tests(record_unregistered);
464 let shared = BrowserSurfaceShared::new();
465 shared.register(9701);
466 shared.set_target_kind(RENDER_TARGET_KIND_SOFTWARE);
467 shared.on_size_changed(300.0, 180.0, 1.0);
468 let graphics = BrowserPlatformGraphics { shared: shared.clone() };
469 let feature = ready_state(&graphics);
470 let surface = BrowserRenderSurface::new(shared.clone());
471 assert!(get_render_target(9701).is_some());
472 assert!(feature.is_ready() && surface.is_ready());
473 assert!(surface.as_framebuffer_surface().is_some());
474
475 release_canvas_objects(&shared, thread_proxy::current_thread());
478
479 assert!(shared.is_disposed());
480 assert!(!feature.is_ready() && !surface.is_ready());
481 assert!(surface.as_framebuffer_surface().is_none());
484 assert!(!remove_render_target(9701));
485 assert_eq!(vec![9701], UNREGISTERED.with(|unregistered| unregistered.borrow().clone()));
486 }
487
488 #[test]
489 fn a_thread_that_did_not_create_the_canvas_does_not_tell_its_own_script() {
490 set_script_render_targets_for_unit_tests(software_script);
491 set_script_unregister_for_unit_tests(record_unregistered);
492 let shared = BrowserSurfaceShared::new();
493 shared.register(9702);
494 shared.set_target_kind(RENDER_TARGET_KIND_SOFTWARE);
495
496 release_canvas_objects(&shared, thread_proxy::current_thread() + 1);
499
500 assert!(shared.is_disposed());
501 assert!(UNREGISTERED.with(|unregistered| unregistered.borrow().is_empty()));
502 }
503
504 #[test]
505 #[should_panic(expected = "Specified method is not supported")]
506 fn the_graphics_creates_no_context() {
507 let graphics = BrowserPlatformGraphics { shared: BrowserSurfaceShared::new() };
508
509 graphics.create_context();
510 }
511}