ferroui_browser/rendering/
browser_web_gl_render_target.rs1use super::web_render_target::update_size;
2use super::{BrowserSurfaceShared, RenderStatistics};
3use crate::interop::canvas_helper::{GlInfo, RENDER_TARGET_KIND_WEB_GL};
4use crate::interop::{thread_proxy, JsObject};
5use ferroui_base::platform::surfaces::IPlatformRenderSurfaceRenderTarget;
6use ferroui_base::platform::{IOptionalFeatureProvider, IPlatformGraphicsContext, RenderTargetSceneInfo};
7use ferroui_base::reactive::{Disposable, IDisposable};
8use ferroui_base::PixelSize;
9use ferroui_opengl::gl_consts;
10use ferroui_opengl::surfaces::{IGlPlatformSurfaceRenderTarget, IGlPlatformSurfaceRenderingSession};
11use ferroui_opengl::{GetProcAddress, GlInterface, GlProfileType, GlVersion, IGlContext, OpenGlException};
12use ferroui_skia::gpu::open_gl::IGlSkiaSpecificOptionsFeature;
13use std::any::{Any, TypeId};
14use std::cell::Cell;
15use std::ffi::c_void;
16use std::rc::{Rc, Weak};
17use std::sync::Arc;
18use std::thread::{self, ThreadId};
19use wasm_bindgen::prelude::*;
20
21#[wasm_bindgen(raw_module = "./ferroui.js")]
22extern "C" {
23 type JsWebGlRenderTarget;
25
26 #[wasm_bindgen(method, getter, js_name = contextHandle)]
27 fn context_handle(this: &JsWebGlRenderTarget) -> i32;
28
29 #[wasm_bindgen(method, getter, js_name = fboId)]
30 fn fbo_id(this: &JsWebGlRenderTarget) -> u32;
31
32 #[wasm_bindgen(method, getter)]
33 fn stencil(this: &JsWebGlRenderTarget) -> i32;
34
35 #[wasm_bindgen(method, getter)]
36 fn sample(this: &JsWebGlRenderTarget) -> i32;
37
38 #[wasm_bindgen(method, getter)]
39 fn depth(this: &JsWebGlRenderTarget) -> i32;
40
41 #[wasm_bindgen(method, getter)]
42 fn attrs(this: &JsWebGlRenderTarget) -> JsWebGlAttributes;
43
44 #[cfg(feature = "vello")]
47 #[wasm_bindgen(method, getter)]
48 fn canvas(this: &JsWebGlRenderTarget) -> JsValue;
49
50 type JsWebGlAttributes;
52
53 #[wasm_bindgen(method, getter, js_name = majorVersion)]
54 fn major_version(this: &JsWebGlAttributes) -> i32;
55
56 #[wasm_bindgen(js_namespace = WebGlRenderTarget, js_name = getCurrentContext)]
57 fn get_current_context() -> i32;
58
59 #[wasm_bindgen(js_namespace = WebGlRenderTarget, js_name = makeContextCurrent)]
60 fn make_context_current(context: i32) -> bool;
61}
62
63pub struct BrowserWebGlRenderTarget {
74 js: JsObject,
75 gl_info: GlInfo,
76 gl_context: Rc<WebGlContext>,
77}
78
79impl BrowserWebGlRenderTarget {
80 pub fn new(js: JsObject) -> Rc<Self> {
84 let target = JsCast::unchecked_ref::<JsWebGlRenderTarget>(&js);
85 let gl_info = GlInfo {
86 context_id: target.context_handle(),
87 fbo_id: target.fbo_id(),
88 stencils: target.stencil(),
89 samples: target.sample(),
90 depth: target.depth(),
91 };
92 let context_id = target.context_handle();
93 let version = target.attrs().major_version();
94 let gl_context = WebGlContext::new(
95 context_id,
96 GlVersion::new(GlProfileType::OpenGLES, if version > 1 { 3 } else { 2 }, 0),
97 gl_info.samples,
98 gl_info.stencils,
99 #[cfg(feature = "vello")]
102 (version > 1).then(|| target.canvas()),
103 );
104 Rc::new(Self { js, gl_info, gl_context })
105 }
106
107 pub fn gl_context(&self) -> Rc<WebGlContext> {
109 self.gl_context.clone()
110 }
111
112 pub fn create_gl_render_target(
115 self: &Rc<Self>,
116 shared: Arc<BrowserSurfaceShared>,
117 ) -> Rc<dyn IGlPlatformSurfaceRenderTarget> {
118 Rc::new(GlSurface { target: self.clone(), shared })
119 }
120
121 fn update_size(&self, size: PixelSize) {
122 update_size(&self.js, size);
123 }
124}
125
126struct GlSession {
127 restore_context: Cell<Option<Rc<dyn IDisposable>>>,
128 context: Rc<dyn IGlContext>,
129 size: PixelSize,
130 scaling: f64,
131 shared: Arc<BrowserSurfaceShared>,
132}
133
134impl IGlPlatformSurfaceRenderingSession for GlSession {
135 fn context(&self) -> Rc<dyn IGlContext> {
136 self.context.clone()
137 }
138
139 fn size(&self) -> PixelSize {
140 self.size
141 }
142
143 fn scaling(&self) -> f64 {
146 self.scaling
147 }
148
149 fn is_y_flipped(&self) -> bool {
150 false
151 }
152
153 fn dispose(&self) {
154 if let Some(restore_context) = self.restore_context.take() {
155 RenderStatistics::frame_presented(
159 thread_proxy::current_thread(),
160 RENDER_TARGET_KIND_WEB_GL,
161 self.context.version().major(),
162 self.size.width,
163 self.size.height,
164 );
165 restore_context.dispose();
166 self.shared.frame_presented();
168 }
169 }
170}
171
172struct GlSurface {
173 target: Rc<BrowserWebGlRenderTarget>,
174 shared: Arc<BrowserSurfaceShared>,
175}
176
177impl IPlatformRenderSurfaceRenderTarget for GlSurface {}
178
179impl IGlPlatformSurfaceRenderTarget for GlSurface {
180 fn begin_draw(&self, _scene_info: &RenderTargetSceneInfo) -> Rc<dyn IGlPlatformSurfaceRenderingSession> {
181 let (size, scaling) = self.shared.size();
185 self.target.update_size(size);
186 let restore_context = self.target.gl_context.ensure_current();
187 self.target
188 .gl_context
189 .gl_interface()
190 .bind_framebuffer(gl_consts::GL_FRAMEBUFFER, self.target.gl_info.fbo_id as i32);
191 Rc::new(GlSession {
192 restore_context: Cell::new(Some(restore_context)),
193 context: self.target.gl_context.clone(),
194 size,
195 scaling,
196 shared: self.shared.clone(),
197 })
198 }
199
200 fn dispose(&self) {
201 }
203}
204
205#[cfg(target_os = "emscripten")]
206mod native {
207 use std::ffi::{c_char, c_void, CString};
208
209 extern "C" {
210 fn emscripten_GetProcAddress(name: *const c_char) -> *const c_void;
211 }
212
213 pub(super) fn get_proc_address(name: &str) -> *const c_void {
216 let Ok(name) = CString::new(name) else { return std::ptr::null() };
217 unsafe { emscripten_GetProcAddress(name.as_ptr()) }
220 }
221}
222
223#[cfg(not(target_os = "emscripten"))]
224mod native {
225 use std::ffi::c_void;
226
227 pub(super) fn get_proc_address(_name: &str) -> *const c_void {
229 std::ptr::null()
230 }
231}
232
233pub struct WebGlContext {
235 this: Weak<WebGlContext>,
236 context_id: i32,
237 thread: ThreadId,
238 version: GlVersion,
239 gl_interface: Rc<GlInterface>,
240 sample_count: i32,
241 stencil_size: i32,
242 #[cfg(feature = "vello")]
245 canvas: Option<JsValue>,
246}
247
248struct RestoreContext {
249 context_id: Cell<Option<i32>>,
250}
251
252impl IDisposable for RestoreContext {
253 fn dispose(&self) {
254 if let Some(context_id) = self.context_id.take() {
255 make_context_current(context_id);
256 }
257 }
258}
259
260impl WebGlContext {
261 pub fn new(
263 context_id: i32,
264 version: GlVersion,
265 sample_count: i32,
266 stencil_size: i32,
267 #[cfg(feature = "vello")] canvas: Option<JsValue>,
268 ) -> Rc<Self> {
269 let old = get_current_context();
270 if !make_context_current(context_id) {
271 panic!("{}", OpenGlException::new("Unable to make the context current"));
272 }
273 let get_proc_address: GetProcAddress = Rc::new(|name: &str| -> *const c_void { native::get_proc_address(name) });
274 let gl_interface = Rc::new(unsafe { GlInterface::new(version, get_proc_address) });
285 make_context_current(old);
286
287 Rc::new_cyclic(|this| Self {
288 this: this.clone(),
289 context_id,
290 thread: thread::current().id(),
291 version,
292 gl_interface,
293 sample_count,
294 stencil_size,
295 #[cfg(feature = "vello")]
296 canvas,
297 })
298 }
299
300 fn verify_access(&self) {
301 if self.thread != thread::current().id() {
302 panic!("Call from invalid thread");
303 }
304 }
305}
306
307impl IOptionalFeatureProvider for WebGlContext {
308 fn try_get_feature(&self, feature_type: TypeId) -> Option<Rc<dyn Any>> {
309 if feature_type == TypeId::of::<dyn IGlContext>() {
310 let this: Rc<dyn IGlContext> = self.this.upgrade()?;
311 return Some(Rc::new(this));
312 }
313 if feature_type == TypeId::of::<dyn IGlSkiaSpecificOptionsFeature>() {
314 let this: Rc<dyn IGlSkiaSpecificOptionsFeature> = self.this.upgrade()?;
315 return Some(Rc::new(this));
316 }
317 #[cfg(feature = "vello")]
320 if feature_type == TypeId::of::<dyn ferroui_vello::web_gl::IWebGlCanvasFeature>() && self.canvas.is_some() {
321 let this: Rc<dyn ferroui_vello::web_gl::IWebGlCanvasFeature> = self.this.upgrade()?;
322 return Some(Rc::new(this));
323 }
324 None
325 }
326}
327
328impl IPlatformGraphicsContext for WebGlContext {
329 fn is_lost(&self) -> bool {
331 false
332 }
333
334 fn ensure_current(&self) -> Rc<dyn IDisposable> {
335 self.verify_access();
336 if get_current_context() == self.context_id {
337 return Disposable::empty();
338 }
339 self.make_current()
340 }
341
342 fn dispose(&self) {
343 }
345
346 fn as_any(&self) -> &dyn Any {
347 self
348 }
349}
350
351impl IGlContext for WebGlContext {
352 fn version(&self) -> GlVersion {
353 self.version
354 }
355
356 fn gl_interface(&self) -> Rc<GlInterface> {
357 self.gl_interface.clone()
358 }
359
360 fn sample_count(&self) -> i32 {
361 self.sample_count
362 }
363
364 fn stencil_size(&self) -> i32 {
365 self.stencil_size
366 }
367
368 fn make_current(&self) -> Rc<dyn IDisposable> {
369 self.verify_access();
370 let old = get_current_context();
371 if !make_context_current(self.context_id) {
372 panic!("{}", OpenGlException::new("Unable to make the context current"));
373 }
374 Rc::new(RestoreContext { context_id: Cell::new(Some(old)) })
375 }
376
377 fn is_shared_with(&self, _context: &dyn IGlContext) -> bool {
378 false
379 }
380
381 fn can_create_shared_context(&self) -> bool {
382 false
383 }
384
385 fn create_shared_context(&self, _preferred_versions: Option<&[GlVersion]>) -> Option<Rc<dyn IGlContext>> {
386 panic!("Specified method is not supported.");
387 }
388}
389
390#[cfg(feature = "vello")]
391impl ferroui_vello::web_gl::IWebGlCanvasFeature for WebGlContext {
392 fn canvas(&self) -> JsValue {
393 self.verify_access();
394 self.canvas.clone().expect("the feature is handed out for a context with a canvas")
395 }
396}
397
398impl IGlSkiaSpecificOptionsFeature for WebGlContext {
399 fn use_native_skia_gr_gl_interface(&self) -> bool {
400 true
401 }
402}