ferroui_browser/rendering/
browser_render_surface.rs1use 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
10pub struct BrowserRenderSurface {
31 shared: Arc<BrowserSurfaceShared>,
32}
33
34const _: fn() = || {
37 fn assert_send_sync<T: Send + Sync>() {}
38 assert_send_sync::<BrowserRenderSurface>();
39};
40
41impl BrowserRenderSurface {
42 pub fn new(shared: Arc<BrowserSurfaceShared>) -> Arc<Self> {
44 Arc::new(Self { shared })
45 }
46
47 pub fn shared(&self) -> &Arc<BrowserSurfaceShared> {
49 &self.shared
50 }
51
52 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 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 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 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 fn software_script(_id: i32) -> Option<BrowserRenderTarget> {
127 Some(BrowserRenderTarget::Software(BrowserSoftwareRenderTarget::new(JsObject::NULL)))
128 }
129
130 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 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 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 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 render_target.dispose();
234 }
235}