use super::web_render_target::{get_render_target, remove_render_target, unregister_canvas};
use super::{
BrowserRenderSurface, BrowserSharedRenderLoop, BrowserSurface, BrowserSurfaceShared, RenderStatistics,
RenderWorker,
};
use crate::browser_app_builder::BrowserRenderingMode;
use crate::interop::canvas_helper::CanvasSurface;
use crate::interop::{thread_proxy, JsObject};
use ferroui_base::media::MediaContext;
use ferroui_base::platform::surfaces::IPlatformRenderSurface;
use ferroui_base::platform::{
IOptionalFeatureProvider, IPlatformGraphics, IPlatformGraphicsContext, IPlatformGraphicsReadyStateFeature,
};
use ferroui_base::rendering::composition::server::ServerCompositor;
use ferroui_base::rendering::composition::Compositor;
use ferroui_base::threading::Dispatcher;
use ferroui_base::{PixelSize, Size};
use std::any::{Any, TypeId};
use std::cell::Cell;
use std::rc::Rc;
use std::sync::Arc;
pub struct RenderTargetBrowserSurface {
base: BrowserSurface,
shared: Arc<BrowserSurfaceShared>,
render_surface: Arc<BrowserRenderSurface>,
disposed: Cell<bool>,
}
impl RenderTargetBrowserSurface {
fn new(js_surface: CanvasSurface, thread_id: i32) -> Rc<Self> {
let target_id = js_surface.target_id();
let shared = BrowserSurfaceShared::new();
shared.register(target_id);
if thread_id == 0 {
if let Some(target) = get_render_target(target_id) {
shared.set_target_kind(target.kind());
}
}
let gpu: Arc<dyn IPlatformGraphics> = Arc::new(BrowserPlatformGraphics { shared: shared.clone() });
let compositor = if thread_id == 0 {
Compositor::with_scheduler(
BrowserSharedRenderLoop::render_loop(),
Some(gpu),
false,
&MediaContext::instance().scheduler(),
Dispatcher::ui_thread(),
None,
None,
)
} else {
Compositor::with_render_thread(
BrowserSharedRenderLoop::render_loop(),
Some(gpu),
false,
&MediaContext::instance().scheduler(),
Dispatcher::ui_thread(),
None,
None,
)
};
let render_surface = BrowserRenderSurface::new(shared.clone());
let this = Rc::new(Self {
base: BrowserSurface::new(js_surface, compositor),
shared,
render_surface,
disposed: Cell::new(false),
});
if let Some((w, h, s)) = this.base.initial_size() {
this.on_size_changed(w, h, s);
}
this
}
pub fn create(container: &JsObject, modes: &[BrowserRenderingMode], top_level_id: i32) -> Rc<Self> {
let modes: Vec<i32> = modes.iter().map(|m| *m as i32).collect();
let thread_id =
if BrowserSharedRenderLoop::renders_on_render_thread() { RenderWorker::canvas_thread_id() } else { 0 };
let js = CanvasSurface::create_render_target_surface(container, &modes, top_level_id, thread_id);
Self::new(js, thread_id)
}
pub fn compositor(&self) -> Rc<Compositor> {
self.base.compositor()
}
pub fn scaling(&self) -> f64 {
self.base.scaling()
}
pub fn client_size(&self) -> Size {
self.base.client_size()
}
pub fn render_size(&self) -> PixelSize {
self.base.render_size()
}
pub fn is_valid(&self) -> bool {
self.base.is_valid()
}
pub fn size_changed(&self, handler: Rc<dyn Fn()>) -> u64 {
self.base.size_changed(handler)
}
pub fn scaling_changed(&self, handler: Rc<dyn Fn()>) -> u64 {
self.base.scaling_changed(handler)
}
pub fn get_render_surfaces(&self) -> Vec<Arc<dyn IPlatformRenderSurface>> {
let surface: Arc<dyn IPlatformRenderSurface> = self.render_surface.clone();
vec![surface]
}
pub fn shared(&self) -> &Arc<BrowserSurfaceShared> {
&self.shared
}
pub fn on_size_changed(&self, pixel_width: f64, pixel_height: f64, dpr: f64) {
self.shared.on_size_changed(pixel_width, pixel_height, dpr);
self.base.on_size_changed(pixel_width, pixel_height, dpr);
}
pub fn dispose(&self) {
if self.disposed.replace(true) {
return;
}
self.shared.forget_first_frame();
self.shared.unregister();
let shared = self.shared.clone();
let page_thread = thread_proxy::current_thread();
let compositor = self.base.compositor();
if compositor.is_confined_to_render_thread() {
let server = compositor.locked_server();
let posted = RenderWorker::post({
let shared = shared.clone();
move || {
let released = server.try_with(|server| release_canvas(server, &shared, page_thread));
if released.is_none() {
release_canvas_objects(&shared, page_thread);
}
}
});
if !posted {
if RenderWorker::thread_id() == 0 {
release_canvas_objects(&shared, page_thread);
} else {
shared.dispose();
}
}
} else {
compositor.with_server(|server| release_canvas(server, &shared, page_thread));
}
self.base.dispose();
}
}
impl Drop for RenderTargetBrowserSurface {
fn drop(&mut self) {
if !std::thread::panicking() {
self.dispose();
}
}
}
fn release_canvas(server: &ServerCompositor, shared: &Arc<BrowserSurfaceShared>, page_thread: usize) {
server.release_gpu_resources();
server.stop_rendering();
release_canvas_objects(shared, page_thread);
}
fn release_canvas_objects(shared: &Arc<BrowserSurfaceShared>, page_thread: usize) {
let target_id = shared.target_id();
shared.dispose();
remove_render_target(target_id);
RenderStatistics::canvas_released();
if thread_proxy::current_thread() == page_thread {
unregister_canvas(target_id);
} else {
thread_proxy::run_on_thread(page_thread, Box::new(move || unregister_canvas(target_id)));
}
}
struct BrowserPlatformGraphics {
shared: Arc<BrowserSurfaceShared>,
}
impl BrowserPlatformGraphics {
fn uses_contexts(&self) -> bool {
self.shared.uses_contexts().expect("the render target exists")
}
}
impl IPlatformGraphics for BrowserPlatformGraphics {
fn uses_shared_context(&self) -> bool {
self.uses_contexts()
}
fn create_context(&self) -> Rc<dyn IPlatformGraphicsContext> {
panic!("Specified method is not supported.");
}
fn get_shared_context(&self) -> Rc<dyn IPlatformGraphicsContext> {
let target = get_render_target(self.shared.target_id()).expect("the render target exists");
match target.platform_graphics_context() {
Some(context) => context,
None => panic!(
"This platform graphics instance represents software rendering mode and cant create contexts, you are supposed to query IPlatformGraphicsReadyStateFeature to know this"
),
}
}
fn as_feature_provider(&self) -> Option<&dyn IOptionalFeatureProvider> {
Some(self)
}
}
impl IOptionalFeatureProvider for BrowserPlatformGraphics {
fn try_get_feature(&self, feature_type: TypeId) -> Option<Rc<dyn Any>> {
if feature_type == TypeId::of::<dyn IPlatformGraphicsReadyStateFeature>() {
let this: Arc<dyn IPlatformGraphicsReadyStateFeature> = Arc::new(Self { shared: self.shared.clone() });
return Some(Rc::new(this));
}
None
}
}
impl IPlatformGraphicsReadyStateFeature for BrowserPlatformGraphics {
fn is_ready(&self) -> bool {
self.shared.is_ready()
}
fn uses_contexts(&self) -> bool {
BrowserPlatformGraphics::uses_contexts(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::interop::canvas_helper::{RENDER_TARGET_KIND_SOFTWARE, RENDER_TARGET_KIND_WEB_GL};
use crate::interop::JsObject;
use crate::rendering::web_render_target::{
set_script_render_targets_for_unit_tests, set_script_unregister_for_unit_tests, BrowserRenderTarget,
};
use std::cell::RefCell;
use crate::rendering::BrowserSoftwareRenderTarget;
fn software_script(_id: i32) -> Option<BrowserRenderTarget> {
Some(BrowserRenderTarget::Software(BrowserSoftwareRenderTarget::new(JsObject::NULL)))
}
fn ready_state(graphics: &BrowserPlatformGraphics) -> Arc<dyn IPlatformGraphicsReadyStateFeature> {
let features: &dyn IOptionalFeatureProvider = graphics;
features.try_get_shared::<dyn IPlatformGraphicsReadyStateFeature>().expect("the graphics has the feature")
}
#[test]
fn the_graphics_is_ready_when_a_thread_published_the_target_and_the_canvas_has_a_size() {
let shared = BrowserSurfaceShared::new();
let graphics = BrowserPlatformGraphics { shared: shared.clone() };
let feature = ready_state(&graphics);
assert!(!feature.is_ready());
shared.on_size_changed(300.0, 180.0, 1.5);
assert!(!feature.is_ready());
std::thread::spawn({
let shared = shared.clone();
move || shared.set_target_kind(RENDER_TARGET_KIND_WEB_GL)
})
.join()
.unwrap();
assert!(feature.is_ready());
assert!(feature.uses_contexts());
assert!(graphics.uses_shared_context());
shared.on_size_changed(0.0, 0.0, 1.5);
assert!(!feature.is_ready());
}
#[test]
fn a_software_target_uses_no_context() {
let shared = BrowserSurfaceShared::new();
shared.set_target_kind(RENDER_TARGET_KIND_SOFTWARE);
let graphics = BrowserPlatformGraphics { shared };
assert!(!ready_state(&graphics).uses_contexts());
assert!(!graphics.uses_shared_context());
let features: &dyn IOptionalFeatureProvider = &graphics;
assert!(features.try_get_feature(TypeId::of::<dyn IPlatformGraphics>()).is_none());
}
#[test]
#[should_panic(expected = "the render target exists")]
fn whether_contexts_are_used_is_not_known_before_the_target_exists() {
let graphics = BrowserPlatformGraphics { shared: BrowserSurfaceShared::new() };
graphics.uses_shared_context();
}
#[test]
#[should_panic(expected = "represents software rendering mode")]
fn a_software_target_has_no_shared_context() {
set_script_render_targets_for_unit_tests(software_script);
let shared = BrowserSurfaceShared::new();
shared.set_target_id(11);
shared.set_target_kind(RENDER_TARGET_KIND_SOFTWARE);
let graphics = BrowserPlatformGraphics { shared };
graphics.get_shared_context();
}
ferroui_base::ferro_thread_local! {
static UNREGISTERED: RefCell<Vec<i32>> = const { RefCell::new(Vec::new()) };
}
fn record_unregistered(id: i32) {
UNREGISTERED.with(|unregistered| unregistered.borrow_mut().push(id));
}
#[test]
fn a_released_canvas_is_disposed_leaves_the_table_of_its_thread_and_is_unregistered() {
set_script_render_targets_for_unit_tests(software_script);
set_script_unregister_for_unit_tests(record_unregistered);
let shared = BrowserSurfaceShared::new();
shared.register(9701);
shared.set_target_kind(RENDER_TARGET_KIND_SOFTWARE);
shared.on_size_changed(300.0, 180.0, 1.0);
let graphics = BrowserPlatformGraphics { shared: shared.clone() };
let feature = ready_state(&graphics);
let surface = BrowserRenderSurface::new(shared.clone());
assert!(get_render_target(9701).is_some());
assert!(feature.is_ready() && surface.is_ready());
assert!(surface.as_framebuffer_surface().is_some());
release_canvas_objects(&shared, thread_proxy::current_thread());
assert!(shared.is_disposed());
assert!(!feature.is_ready() && !surface.is_ready());
assert!(surface.as_framebuffer_surface().is_none());
assert!(!remove_render_target(9701));
assert_eq!(vec![9701], UNREGISTERED.with(|unregistered| unregistered.borrow().clone()));
}
#[test]
fn a_thread_that_did_not_create_the_canvas_does_not_tell_its_own_script() {
set_script_render_targets_for_unit_tests(software_script);
set_script_unregister_for_unit_tests(record_unregistered);
let shared = BrowserSurfaceShared::new();
shared.register(9702);
shared.set_target_kind(RENDER_TARGET_KIND_SOFTWARE);
release_canvas_objects(&shared, thread_proxy::current_thread() + 1);
assert!(shared.is_disposed());
assert!(UNREGISTERED.with(|unregistered| unregistered.borrow().is_empty()));
}
#[test]
#[should_panic(expected = "Specified method is not supported")]
fn the_graphics_creates_no_context() {
let graphics = BrowserPlatformGraphics { shared: BrowserSurfaceShared::new() };
graphics.create_context();
}
}