Skip to main content

ferroui_browser/rendering/
browser_render_surface.rs

1use super::web_render_target::{get_render_target, BrowserRenderTarget};
2use super::BrowserSurfaceShared;
3use ferroui_base::platform::surfaces::{IFramebufferPlatformSurface, IFramebufferRenderTarget, IPlatformRenderSurface};
4use ferroui_opengl::surfaces::{IGlPlatformSurface, IGlPlatformSurfaceRenderTarget};
5use ferroui_opengl::IGlContext;
6use std::any::{Any, TypeId};
7use std::rc::Rc;
8use std::sync::Arc;
9
10/// The render surface of a canvas, as it is handed to the compositor.
11///
12/// Upstream hands out the render target itself (`BrowserWebGlRenderTarget`
13/// or `BrowserSoftwareRenderTarget`), an object of the thread that draws.
14/// A render surface of this port is shared between the thread of the user
15/// interface and the thread that renders, so this object holds only what
16/// the two share ([`BrowserSurfaceShared`]) and resolves the render target
17/// on the thread that asks, in the table of that thread
18/// ([`get_render_target`]):
19///
20/// - whether the surface is ready is read from the shared state, by either
21///   thread;
22/// - what kind of surface it is (an OpenGL surface, a framebuffer surface)
23///   is the kind of the target of the calling thread. A thread that has no
24///   target of the canvas (the thread of the page, when the canvas was
25///   transferred to a worker) is told that the surface is of neither kind;
26/// - the render targets it creates belong to the thread that asks for them.
27///
28/// Without a render thread the one thread of the page has the target, and
29/// the surface answers as upstream's render target does.
30pub struct BrowserRenderSurface {
31    shared: Arc<BrowserSurfaceShared>,
32}
33
34// Not from upstream: the render surface contract requires a surface to be
35// shared between threads.
36const _: fn() = || {
37    fn assert_send_sync<T: Send + Sync>() {}
38    assert_send_sync::<BrowserRenderSurface>();
39};
40
41impl BrowserRenderSurface {
42    /// Creates the render surface of the canvas `shared` describes.
43    pub fn new(shared: Arc<BrowserSurfaceShared>) -> Arc<Self> {
44        Arc::new(Self { shared })
45    }
46
47    /// What the two threads share about the canvas.
48    pub fn shared(&self) -> &Arc<BrowserSurfaceShared> {
49        &self.shared
50    }
51
52    /// The render target of the canvas on the calling thread; `None` while
53    /// no thread has reported the target, on a thread that does not have
54    /// it, and once the view is disposed (the target has left the table of
55    /// its thread then, and must not be wrapped a second time).
56    fn target(&self) -> Option<BrowserRenderTarget> {
57        if !self.shared.has_target() || self.shared.is_disposed() {
58            return None;
59        }
60        get_render_target(self.shared.target_id())
61    }
62}
63
64impl IPlatformRenderSurface for BrowserRenderSurface {
65    fn is_ready(&self) -> bool {
66        self.shared.has_target() && !self.shared.is_disposed()
67    }
68
69    fn as_framebuffer_surface(&self) -> Option<&dyn IFramebufferPlatformSurface> {
70        match self.target() {
71            Some(BrowserRenderTarget::Software(_)) => Some(self as &dyn IFramebufferPlatformSurface),
72            _ => None,
73        }
74    }
75
76    fn try_get_surface_kind(&self, kind: TypeId) -> Option<Rc<dyn Any>> {
77        if kind == TypeId::of::<dyn IGlPlatformSurface>() {
78            if let Some(BrowserRenderTarget::WebGl(_)) = self.target() {
79                // The surface kind is handed out in an `Rc` and the surface
80                // lives in an `Arc`: the answer is a second surface over the
81                // same shared state, which is all a surface is.
82                let this: Rc<dyn IGlPlatformSurface> = Rc::new(Self { shared: self.shared.clone() });
83                return Some(Rc::new(this));
84            }
85        }
86        None
87    }
88
89    fn as_any(&self) -> &dyn Any {
90        self
91    }
92}
93
94impl IFramebufferPlatformSurface for BrowserRenderSurface {
95    /// # Panics
96    /// Panics on a thread that has no software render target of the canvas.
97    fn create_framebuffer_render_target(&self) -> Rc<dyn IFramebufferRenderTarget> {
98        match self.target() {
99            Some(BrowserRenderTarget::Software(target)) => target.create_framebuffer_render_target(self.shared.clone()),
100            _ => panic!("the thread has no software render target of the canvas"),
101        }
102    }
103}
104
105impl IGlPlatformSurface for BrowserRenderSurface {
106    /// # Panics
107    /// Panics on a thread that has no WebGL render target of the canvas.
108    fn create_gl_render_target(&self, _context: &Rc<dyn IGlContext>) -> Rc<dyn IGlPlatformSurfaceRenderTarget> {
109        match self.target() {
110            Some(BrowserRenderTarget::WebGl(target)) => target.create_gl_render_target(self.shared.clone()),
111            _ => panic!("the thread has no WebGL render target of the canvas"),
112        }
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119    use crate::interop::canvas_helper::{RENDER_TARGET_KIND_SOFTWARE, RENDER_TARGET_KIND_WEB_GL};
120    use crate::interop::JsObject;
121    use crate::rendering::web_render_target::set_script_render_targets_for_unit_tests;
122    use crate::rendering::BrowserSoftwareRenderTarget;
123    use ferroui_opengl::surfaces::try_get_gl_surface;
124
125    /// A script that has a software render target under every id.
126    fn software_script(_id: i32) -> Option<BrowserRenderTarget> {
127        Some(BrowserRenderTarget::Software(BrowserSoftwareRenderTarget::new(JsObject::NULL)))
128    }
129
130    /// A script without render targets: the thread of the page, for a canvas
131    /// it transferred.
132    fn empty_script(_id: i32) -> Option<BrowserRenderTarget> {
133        None
134    }
135
136    fn surface(target_id: i32) -> Arc<BrowserRenderSurface> {
137        let shared = BrowserSurfaceShared::new();
138        shared.set_target_id(target_id);
139        BrowserRenderSurface::new(shared)
140    }
141
142    #[test]
143    fn a_surface_without_a_reported_target_is_not_ready_and_of_no_kind() {
144        // The script would have a target; nobody has reported one.
145        set_script_render_targets_for_unit_tests(software_script);
146        let surface = surface(1);
147
148        assert!(!surface.is_ready());
149        assert!(surface.as_framebuffer_surface().is_none());
150        assert!(try_get_gl_surface(&*surface).is_none());
151    }
152
153    #[test]
154    fn the_thread_that_has_the_target_sees_a_framebuffer_surface() {
155        set_script_render_targets_for_unit_tests(software_script);
156        let surface = surface(2);
157        surface.shared().set_target_kind(RENDER_TARGET_KIND_SOFTWARE);
158
159        assert!(surface.is_ready());
160        assert!(surface.as_framebuffer_surface().is_some());
161        assert!(try_get_gl_surface(&*surface).is_none());
162        assert!(surface.try_get_surface_kind(TypeId::of::<dyn IPlatformRenderSurface>()).is_none());
163    }
164
165    #[test]
166    fn a_thread_without_the_target_reads_that_it_is_ready_and_sees_no_kind() {
167        set_script_render_targets_for_unit_tests(software_script);
168        let surface = surface(3);
169        surface.shared().set_target_kind(RENDER_TARGET_KIND_SOFTWARE);
170        assert!(surface.as_framebuffer_surface().is_some());
171
172        // The surface crosses to another thread as it is; that thread has a
173        // table and a script of its own.
174        let seen = std::thread::spawn({
175            let surface = surface.clone();
176            move || {
177                set_script_render_targets_for_unit_tests(empty_script);
178                (
179                    surface.is_ready(),
180                    surface.as_framebuffer_surface().is_some(),
181                    try_get_gl_surface(&*surface).is_some(),
182                )
183            }
184        })
185        .join()
186        .unwrap();
187
188        assert_eq!((true, false, false), seen);
189    }
190
191    #[test]
192    fn the_kind_is_the_one_of_the_target_of_the_thread() {
193        // The shared state says WebGL; the target of this thread is a
194        // software one, so the surface is not handed out as an OpenGL one.
195        set_script_render_targets_for_unit_tests(software_script);
196        let surface = surface(4);
197        surface.shared().set_target_kind(RENDER_TARGET_KIND_WEB_GL);
198
199        assert!(try_get_gl_surface(&*surface).is_none());
200        assert!(surface.as_framebuffer_surface().is_some());
201    }
202
203    #[test]
204    fn a_disposed_surface_is_not_ready() {
205        let surface = surface(5);
206        surface.shared().set_target_kind(RENDER_TARGET_KIND_WEB_GL);
207        assert!(surface.is_ready());
208
209        surface.shared().dispose();
210        assert!(!surface.is_ready());
211    }
212
213    #[test]
214    #[should_panic(expected = "the thread has no software render target of the canvas")]
215    fn a_thread_without_the_target_cannot_create_a_render_target() {
216        set_script_render_targets_for_unit_tests(empty_script);
217        let surface = surface(6);
218        surface.shared().set_target_kind(RENDER_TARGET_KIND_SOFTWARE);
219
220        surface.create_framebuffer_render_target();
221    }
222
223    #[test]
224    fn the_thread_that_has_the_target_creates_a_render_target() {
225        set_script_render_targets_for_unit_tests(software_script);
226        let surface = surface(7);
227        surface.shared().set_target_kind(RENDER_TARGET_KIND_SOFTWARE);
228
229        let render_target =
230            surface.as_framebuffer_surface().expect("a framebuffer surface").create_framebuffer_render_target();
231        // Nothing was locked: there is nothing to release, and no call into
232        // the page.
233        render_target.dispose();
234    }
235}