Skip to main content

ferroui_browser/rendering/
browser_software_render_target.rs

1use super::web_render_target::update_size;
2use super::{BrowserSurfaceShared, RenderStatistics};
3use crate::interop::canvas_helper::RENDER_TARGET_KIND_SOFTWARE;
4use crate::interop::{thread_proxy, JsObject};
5use ferroui_base::platform::surfaces::{
6    FramebufferLockProperties, IFramebufferRenderTarget, IPlatformRenderSurfaceRenderTarget,
7};
8use ferroui_base::platform::{
9    AlphaFormat, ILockedFramebuffer, PixelFormats, RenderTargetSceneInfo, RetainedFramebuffer,
10};
11use ferroui_base::Vector;
12use std::cell::RefCell;
13use std::rc::Rc;
14use std::sync::Arc;
15use wasm_bindgen::prelude::*;
16
17#[wasm_bindgen(raw_module = "./ferroui.js")]
18extern "C" {
19    #[wasm_bindgen(js_namespace = SoftwareRenderTarget, js_name = staticPutPixelData)]
20    fn put_pixel_data(js: &JsObject, address: u32, size: i32, width: i32, height: i32);
21}
22
23/// A canvas that is rendered to in memory, as the thread that draws to it
24/// holds it: each frame is drawn into a retained framebuffer and copied to
25/// the 2D context of the canvas through the object of the script side of
26/// that thread.
27///
28/// Upstream's class is also the render surface of the canvas. Here the
29/// surface is [`BrowserRenderSurface`](super::BrowserRenderSurface), which
30/// both threads hold and which holds nothing of a thread; this object stays
31/// in the table of the thread that renders
32/// ([`get_render_target`](super::get_render_target)) and is what the surface
33/// resolves there.
34pub struct BrowserSoftwareRenderTarget {
35    js: JsObject,
36}
37
38impl BrowserSoftwareRenderTarget {
39    /// Wraps the software render target of the script side.
40    ///
41    /// The thread that calls this is the one the target belongs to.
42    pub fn new(js: JsObject) -> Rc<Self> {
43        Rc::new(Self { js })
44    }
45
46    /// Creates the framebuffer render target of the canvas. `shared` is
47    /// where each frame reads the size the canvas has to have.
48    pub fn create_framebuffer_render_target(
49        self: &Rc<Self>,
50        shared: Arc<BrowserSurfaceShared>,
51    ) -> Rc<dyn IFramebufferRenderTarget> {
52        Rc::new(FramebufferRenderTarget { parent: self.clone(), shared, fb: RefCell::new(None) })
53    }
54
55    fn blit(&self, fb: &RetainedFramebuffer) {
56        let size = fb.size();
57        put_pixel_data(&self.js, fb.address() as usize as u32, size.width * size.height * 4, size.width, size.height);
58        // Not from upstream: where frames are drawn is otherwise invisible
59        // to the thread of the page.
60        RenderStatistics::frame_presented(
61            thread_proxy::current_thread(),
62            RENDER_TARGET_KIND_SOFTWARE,
63            0,
64            size.width,
65            size.height,
66        );
67    }
68}
69
70struct FramebufferRenderTarget {
71    parent: Rc<BrowserSoftwareRenderTarget>,
72    shared: Arc<BrowserSurfaceShared>,
73    fb: RefCell<Option<Rc<RetainedFramebuffer>>>,
74}
75
76impl IPlatformRenderSurfaceRenderTarget for FramebufferRenderTarget {}
77
78impl IFramebufferRenderTarget for FramebufferRenderTarget {
79    fn lock(&self, _scene_info: &RenderTargetSceneInfo) -> (Rc<dyn ILockedFramebuffer>, FramebufferLockProperties) {
80        let properties = FramebufferLockProperties::default();
81        // The size as the thread of the user interface last wrote it. The
82        // canvas is given that size here, by the thread that draws to it:
83        // for a canvas that was transferred to a worker no other can.
84        let (size, scaling) = self.shared.size();
85        update_size(&self.parent.js, size);
86
87        let mut fb = self.fb.borrow_mut();
88        if fb.as_ref().is_none_or(|fb| fb.size() != size) {
89            if let Some(old) = fb.take() {
90                old.dispose();
91            }
92            *fb = Some(RetainedFramebuffer::new(size, PixelFormats::RGBA8888, AlphaFormat::Premul));
93        }
94
95        let blit_target = Rc::downgrade(&self.parent);
96        let shared = self.shared.clone();
97        let locked = fb.as_ref().expect("the framebuffer was just created").lock(
98            Vector::new(scaling * 96.0, scaling * 96.0),
99            move |fb| {
100                if let Some(parent) = blit_target.upgrade() {
101                    parent.blit(fb);
102                    // The frame is on the canvas: whoever waits for the
103                    // first one of this canvas is told.
104                    shared.frame_presented();
105                }
106            },
107        );
108        (locked, properties)
109    }
110
111    fn dispose(&self) {
112        if let Some(fb) = self.fb.borrow_mut().take() {
113            fb.dispose();
114        }
115    }
116}