diff options
| author | Maze Winther <mazewinther@gmail.com> | 2026-04-26 20:44:07 +0200 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2026-04-26 20:44:07 +0200 |
| commit | 6b1f38a9a31a0eedaa7abea70f232960469351d5 (patch) | |
| tree | 4050d69056c1d9f14075b784535e2b33719f477b /rust/wasm/src/compositor.rs | |
| parent | 56ca09692a308b22455420d11376ff8d41244c47 (diff) | |
| parent | 0b7597b31fa956c7f3d4838923b545ba5e341422 (diff) | |
| download | opencut-6b1f38a9a31a0eedaa7abea70f232960469351d5.tar.gz | |
refactor: split animation helpers by domain
Diffstat (limited to 'rust/wasm/src/compositor.rs')
| -rw-r--r-- | rust/wasm/src/compositor.rs | 116 |
1 files changed, 82 insertions, 34 deletions
diff --git a/rust/wasm/src/compositor.rs b/rust/wasm/src/compositor.rs index 126c7468..7652b3f3 100644 --- a/rust/wasm/src/compositor.rs +++ b/rust/wasm/src/compositor.rs @@ -11,10 +11,13 @@ use crate::gpu::{ import_canvas_texture, read_offscreen_canvas_property, read_serde_property, read_u32_property, with_gpu_runtime, }; +use crate::perf; struct CompositorRuntime { canvas: web_sys::HtmlCanvasElement, compositor: Compositor, + surface: wgpu::Surface<'static>, + surface_size: (u32, u32), } thread_local! { @@ -24,20 +27,42 @@ thread_local! { #[wasm_bindgen(js_name = initCompositor)] pub fn init_compositor(width: u32, height: u32) -> Result<(), JsValue> { with_gpu_runtime(|gpu_runtime| { - let document = web_sys::window() - .and_then(|window| window.document()) - .ok_or_else(|| JsValue::from_str("Document is not available"))?; - let canvas: web_sys::HtmlCanvasElement = document - .create_element("canvas")? - .dyn_into() - .map_err(|_| JsValue::from_str("Failed to create compositor canvas"))?; + // On WebGL, wgpu is bound to a specific canvas; reuse it so the UI + // can mount the output directly instead of copying pixels through + // an intermediate 2D canvas every frame. On WebGPU, surface rendering + // works against any canvas so we create a fresh one. + let canvas = if let Some(gl_canvas) = gpu_runtime.context.gl_canvas() { + gl_canvas.clone() + } else { + let document = web_sys::window() + .and_then(|window| window.document()) + .ok_or_else(|| JsValue::from_str("Document is not available"))?; + document + .create_element("canvas")? + .dyn_into::<web_sys::HtmlCanvasElement>() + .map_err(|_| JsValue::from_str("Failed to create compositor canvas"))? + }; canvas.set_width(width); canvas.set_height(height); let compositor = Compositor::new(&gpu_runtime.context); + let surface = gpu_runtime + .context + .instance() + .create_surface(wgpu::SurfaceTarget::Canvas(canvas.clone())) + .map_err(|error| JsValue::from_str(&error.to_string()))?; + gpu_runtime + .context + .configure_surface(&surface, width, height) + .map_err(|error| JsValue::from_str(&error.to_string()))?; COMPOSITOR_RUNTIME.with(|runtime| { - runtime.replace(Some(CompositorRuntime { canvas, compositor })); + runtime.replace(Some(CompositorRuntime { + canvas, + compositor, + surface, + surface_size: (width, height), + })); }); Ok(()) @@ -46,16 +71,25 @@ pub fn init_compositor(width: u32, height: u32) -> Result<(), JsValue> { #[wasm_bindgen(js_name = resizeCompositor)] pub fn resize_compositor(width: u32, height: u32) -> Result<(), JsValue> { - COMPOSITOR_RUNTIME.with(|runtime| { - let mut borrow = runtime.borrow_mut(); - let Some(runtime) = borrow.as_mut() else { - return Err(JsValue::from_str( - "Compositor is not initialized. Call initCompositor() first.", - )); - }; - runtime.canvas.set_width(width); - runtime.canvas.set_height(height); - Ok(()) + with_gpu_runtime(|gpu_runtime| { + COMPOSITOR_RUNTIME.with(|runtime| { + let mut borrow = runtime.borrow_mut(); + let Some(runtime) = borrow.as_mut() else { + return Err(JsValue::from_str( + "Compositor is not initialized. Call initCompositor() first.", + )); + }; + runtime.canvas.set_width(width); + runtime.canvas.set_height(height); + if runtime.surface_size != (width, height) { + gpu_runtime + .context + .configure_surface(&runtime.surface, width, height) + .map_err(|error| JsValue::from_str(&error.to_string()))?; + runtime.surface_size = (width, height); + } + Ok(()) + }) }) } @@ -119,8 +153,12 @@ pub fn release_texture(id: String) -> Result<(), JsValue> { #[wasm_bindgen(js_name = renderFrame)] pub fn render_frame(options: JsValue) -> Result<(), JsValue> { + perf::reset(); + + let t_deserialize = perf::now_ms(); let frame: FrameDescriptor = serde_wasm_bindgen::from_value(options) .map_err(|error| JsValue::from_str(&format!("Invalid frame descriptor: {error}")))?; + perf::record("wasm.deserialize", perf::now_ms() - t_deserialize); with_gpu_runtime(|gpu_runtime| { COMPOSITOR_RUNTIME.with(|runtime| { @@ -131,40 +169,50 @@ pub fn render_frame(options: JsValue) -> Result<(), JsValue> { )); }; - if gpu_runtime.context.supports_surface_rendering() { - let surface = gpu_runtime + if runtime.surface_size != (frame.width, frame.height) { + runtime.canvas.set_width(frame.width); + runtime.canvas.set_height(frame.height); + let t_surface = perf::now_ms(); + gpu_runtime .context - .instance() - .create_surface(wgpu::SurfaceTarget::Canvas(runtime.canvas.clone())) + .configure_surface(&runtime.surface, frame.width, frame.height) .map_err(|error| JsValue::from_str(&error.to_string()))?; + perf::record("wasm.surfaceConfigure", perf::now_ms() - t_surface); + runtime.surface_size = (frame.width, frame.height); + } - runtime + if gpu_runtime.context.supports_surface_rendering() { + let t_render = perf::now_ms(); + let result = runtime .compositor .render_frame( &gpu_runtime.context, RenderFrameOptions { frame: &frame, - surface: &surface, + surface: &runtime.surface, }, ) - .map_err(|error| JsValue::from_str(&error.to_string())) + .map_err(|error| JsValue::from_str(&error.to_string())); + perf::record("wasm.renderFrameToSurface", perf::now_ms() - t_render); + result } else { - // WebGL cannot surface-render to an arbitrary canvas element. - // Composite to a texture, then blit to the canvas via the 2D context. + // WebGL still needs a separate composition pass, but the output + // surface is now persistent just like the WebGPU path. + let t_composite = perf::now_ms(); let texture = runtime .compositor .render_frame_to_texture(&gpu_runtime.context, &frame) .map_err(|error| JsValue::from_str(&error.to_string()))?; + perf::record("wasm.compositeToTexture", perf::now_ms() - t_composite); + let t_present = perf::now_ms(); gpu_runtime .context - .render_texture_via_gl_canvas( - &texture, - &runtime.canvas, - frame.width, - frame.height, - ) - .map_err(|error| JsValue::from_str(&error.to_string())) + .present_texture_to_surface(&texture, &runtime.surface) + .map_err(|error| JsValue::from_str(&error.to_string()))?; + perf::record("wasm.presentToSurface", perf::now_ms() - t_present); + + Ok(()) } }) }) |
