use super::web_render_target::update_size;
use super::{BrowserSurfaceShared, RenderStatistics};
use crate::interop::canvas_helper::{GlInfo, RENDER_TARGET_KIND_WEB_GL};
use crate::interop::{thread_proxy, JsObject};
use ferroui_base::platform::surfaces::IPlatformRenderSurfaceRenderTarget;
use ferroui_base::platform::{IOptionalFeatureProvider, IPlatformGraphicsContext, RenderTargetSceneInfo};
use ferroui_base::reactive::{Disposable, IDisposable};
use ferroui_base::PixelSize;
use ferroui_opengl::gl_consts;
use ferroui_opengl::surfaces::{IGlPlatformSurfaceRenderTarget, IGlPlatformSurfaceRenderingSession};
use ferroui_opengl::{GetProcAddress, GlInterface, GlProfileType, GlVersion, IGlContext, OpenGlException};
use ferroui_skia::gpu::open_gl::IGlSkiaSpecificOptionsFeature;
use std::any::{Any, TypeId};
use std::cell::Cell;
use std::ffi::c_void;
use std::rc::{Rc, Weak};
use std::sync::Arc;
use std::thread::{self, ThreadId};
use wasm_bindgen::prelude::*;
#[wasm_bindgen(raw_module = "./ferroui.js")]
extern "C" {
type JsWebGlRenderTarget;
#[wasm_bindgen(method, getter, js_name = contextHandle)]
fn context_handle(this: &JsWebGlRenderTarget) -> i32;
#[wasm_bindgen(method, getter, js_name = fboId)]
fn fbo_id(this: &JsWebGlRenderTarget) -> u32;
#[wasm_bindgen(method, getter)]
fn stencil(this: &JsWebGlRenderTarget) -> i32;
#[wasm_bindgen(method, getter)]
fn sample(this: &JsWebGlRenderTarget) -> i32;
#[wasm_bindgen(method, getter)]
fn depth(this: &JsWebGlRenderTarget) -> i32;
#[wasm_bindgen(method, getter)]
fn attrs(this: &JsWebGlRenderTarget) -> JsWebGlAttributes;
#[cfg(feature = "vello")]
#[wasm_bindgen(method, getter)]
fn canvas(this: &JsWebGlRenderTarget) -> JsValue;
type JsWebGlAttributes;
#[wasm_bindgen(method, getter, js_name = majorVersion)]
fn major_version(this: &JsWebGlAttributes) -> i32;
#[wasm_bindgen(js_namespace = WebGlRenderTarget, js_name = getCurrentContext)]
fn get_current_context() -> i32;
#[wasm_bindgen(js_namespace = WebGlRenderTarget, js_name = makeContextCurrent)]
fn make_context_current(context: i32) -> bool;
}
pub struct BrowserWebGlRenderTarget {
js: JsObject,
gl_info: GlInfo,
gl_context: Rc<WebGlContext>,
}
impl BrowserWebGlRenderTarget {
pub fn new(js: JsObject) -> Rc<Self> {
let target = JsCast::unchecked_ref::<JsWebGlRenderTarget>(&js);
let gl_info = GlInfo {
context_id: target.context_handle(),
fbo_id: target.fbo_id(),
stencils: target.stencil(),
samples: target.sample(),
depth: target.depth(),
};
let context_id = target.context_handle();
let version = target.attrs().major_version();
let gl_context = WebGlContext::new(
context_id,
GlVersion::new(GlProfileType::OpenGLES, if version > 1 { 3 } else { 2 }, 0),
gl_info.samples,
gl_info.stencils,
#[cfg(feature = "vello")]
(version > 1).then(|| target.canvas()),
);
Rc::new(Self { js, gl_info, gl_context })
}
pub fn gl_context(&self) -> Rc<WebGlContext> {
self.gl_context.clone()
}
pub fn create_gl_render_target(
self: &Rc<Self>,
shared: Arc<BrowserSurfaceShared>,
) -> Rc<dyn IGlPlatformSurfaceRenderTarget> {
Rc::new(GlSurface { target: self.clone(), shared })
}
fn update_size(&self, size: PixelSize) {
update_size(&self.js, size);
}
}
struct GlSession {
restore_context: Cell<Option<Rc<dyn IDisposable>>>,
context: Rc<dyn IGlContext>,
size: PixelSize,
scaling: f64,
shared: Arc<BrowserSurfaceShared>,
}
impl IGlPlatformSurfaceRenderingSession for GlSession {
fn context(&self) -> Rc<dyn IGlContext> {
self.context.clone()
}
fn size(&self) -> PixelSize {
self.size
}
fn scaling(&self) -> f64 {
self.scaling
}
fn is_y_flipped(&self) -> bool {
false
}
fn dispose(&self) {
if let Some(restore_context) = self.restore_context.take() {
RenderStatistics::frame_presented(
thread_proxy::current_thread(),
RENDER_TARGET_KIND_WEB_GL,
self.context.version().major(),
self.size.width,
self.size.height,
);
restore_context.dispose();
self.shared.frame_presented();
}
}
}
struct GlSurface {
target: Rc<BrowserWebGlRenderTarget>,
shared: Arc<BrowserSurfaceShared>,
}
impl IPlatformRenderSurfaceRenderTarget for GlSurface {}
impl IGlPlatformSurfaceRenderTarget for GlSurface {
fn begin_draw(&self, _scene_info: &RenderTargetSceneInfo) -> Rc<dyn IGlPlatformSurfaceRenderingSession> {
let (size, scaling) = self.shared.size();
self.target.update_size(size);
let restore_context = self.target.gl_context.ensure_current();
self.target
.gl_context
.gl_interface()
.bind_framebuffer(gl_consts::GL_FRAMEBUFFER, self.target.gl_info.fbo_id as i32);
Rc::new(GlSession {
restore_context: Cell::new(Some(restore_context)),
context: self.target.gl_context.clone(),
size,
scaling,
shared: self.shared.clone(),
})
}
fn dispose(&self) {
}
}
#[cfg(target_os = "emscripten")]
mod native {
use std::ffi::{c_char, c_void, CString};
extern "C" {
fn emscripten_GetProcAddress(name: *const c_char) -> *const c_void;
}
pub(super) fn get_proc_address(name: &str) -> *const c_void {
let Ok(name) = CString::new(name) else { return std::ptr::null() };
unsafe { emscripten_GetProcAddress(name.as_ptr()) }
}
}
#[cfg(not(target_os = "emscripten"))]
mod native {
use std::ffi::c_void;
pub(super) fn get_proc_address(_name: &str) -> *const c_void {
std::ptr::null()
}
}
pub struct WebGlContext {
this: Weak<WebGlContext>,
context_id: i32,
thread: ThreadId,
version: GlVersion,
gl_interface: Rc<GlInterface>,
sample_count: i32,
stencil_size: i32,
#[cfg(feature = "vello")]
canvas: Option<JsValue>,
}
struct RestoreContext {
context_id: Cell<Option<i32>>,
}
impl IDisposable for RestoreContext {
fn dispose(&self) {
if let Some(context_id) = self.context_id.take() {
make_context_current(context_id);
}
}
}
impl WebGlContext {
pub fn new(
context_id: i32,
version: GlVersion,
sample_count: i32,
stencil_size: i32,
#[cfg(feature = "vello")] canvas: Option<JsValue>,
) -> Rc<Self> {
let old = get_current_context();
if !make_context_current(context_id) {
panic!("{}", OpenGlException::new("Unable to make the context current"));
}
let get_proc_address: GetProcAddress = Rc::new(|name: &str| -> *const c_void { native::get_proc_address(name) });
let gl_interface = Rc::new(unsafe { GlInterface::new(version, get_proc_address) });
make_context_current(old);
Rc::new_cyclic(|this| Self {
this: this.clone(),
context_id,
thread: thread::current().id(),
version,
gl_interface,
sample_count,
stencil_size,
#[cfg(feature = "vello")]
canvas,
})
}
fn verify_access(&self) {
if self.thread != thread::current().id() {
panic!("Call from invalid thread");
}
}
}
impl IOptionalFeatureProvider for WebGlContext {
fn try_get_feature(&self, feature_type: TypeId) -> Option<Rc<dyn Any>> {
if feature_type == TypeId::of::<dyn IGlContext>() {
let this: Rc<dyn IGlContext> = self.this.upgrade()?;
return Some(Rc::new(this));
}
if feature_type == TypeId::of::<dyn IGlSkiaSpecificOptionsFeature>() {
let this: Rc<dyn IGlSkiaSpecificOptionsFeature> = self.this.upgrade()?;
return Some(Rc::new(this));
}
#[cfg(feature = "vello")]
if feature_type == TypeId::of::<dyn ferroui_vello::web_gl::IWebGlCanvasFeature>() && self.canvas.is_some() {
let this: Rc<dyn ferroui_vello::web_gl::IWebGlCanvasFeature> = self.this.upgrade()?;
return Some(Rc::new(this));
}
None
}
}
impl IPlatformGraphicsContext for WebGlContext {
fn is_lost(&self) -> bool {
false
}
fn ensure_current(&self) -> Rc<dyn IDisposable> {
self.verify_access();
if get_current_context() == self.context_id {
return Disposable::empty();
}
self.make_current()
}
fn dispose(&self) {
}
fn as_any(&self) -> &dyn Any {
self
}
}
impl IGlContext for WebGlContext {
fn version(&self) -> GlVersion {
self.version
}
fn gl_interface(&self) -> Rc<GlInterface> {
self.gl_interface.clone()
}
fn sample_count(&self) -> i32 {
self.sample_count
}
fn stencil_size(&self) -> i32 {
self.stencil_size
}
fn make_current(&self) -> Rc<dyn IDisposable> {
self.verify_access();
let old = get_current_context();
if !make_context_current(self.context_id) {
panic!("{}", OpenGlException::new("Unable to make the context current"));
}
Rc::new(RestoreContext { context_id: Cell::new(Some(old)) })
}
fn is_shared_with(&self, _context: &dyn IGlContext) -> bool {
false
}
fn can_create_shared_context(&self) -> bool {
false
}
fn create_shared_context(&self, _preferred_versions: Option<&[GlVersion]>) -> Option<Rc<dyn IGlContext>> {
panic!("Specified method is not supported.");
}
}
#[cfg(feature = "vello")]
impl ferroui_vello::web_gl::IWebGlCanvasFeature for WebGlContext {
fn canvas(&self) -> JsValue {
self.verify_access();
self.canvas.clone().expect("the feature is handed out for a context with a canvas")
}
}
impl IGlSkiaSpecificOptionsFeature for WebGlContext {
fn use_native_skia_gr_gl_interface(&self) -> bool {
true
}
}