summaryrefslogtreecommitdiff
path: root/rust/wasm
diff options
context:
space:
mode:
Diffstat (limited to 'rust/wasm')
-rw-r--r--rust/wasm/Cargo.toml4
-rw-r--r--rust/wasm/src/compositor.rs174
-rw-r--r--rust/wasm/src/gpu.rs2
-rw-r--r--rust/wasm/src/wasm.rs4
4 files changed, 183 insertions, 1 deletions
diff --git a/rust/wasm/Cargo.toml b/rust/wasm/Cargo.toml
index 88f92a5c..b7a8fdf0 100644
--- a/rust/wasm/Cargo.toml
+++ b/rust/wasm/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "opencut-wasm"
-version = "0.2.3"
+version = "0.2.4"
edition = "2024"
description = "Shared video editor logic compiled to WebAssembly"
repository = "https://github.com/opencut/opencut"
@@ -12,6 +12,8 @@ crate-type = ["cdylib", "rlib"]
[dependencies]
bridge = { version = "0.1.0", path = "../crates/bridge" }
+compositor = { version = "0.1.0", path = "../crates/compositor" }
+console_error_panic_hook = "0.1.7"
effects = { version = "0.1.0", path = "../crates/effects" }
gpu = { version = "0.1.0", path = "../crates/gpu", features = ["wasm"] }
js-sys = "0.3.93"
diff --git a/rust/wasm/src/compositor.rs b/rust/wasm/src/compositor.rs
new file mode 100644
index 00000000..55756a21
--- /dev/null
+++ b/rust/wasm/src/compositor.rs
@@ -0,0 +1,174 @@
+#![cfg(target_arch = "wasm32")]
+
+use std::cell::RefCell;
+
+use compositor::{Compositor, FrameDescriptor, RenderFrameOptions};
+use gpu::wgpu;
+use js_sys::Object;
+use wasm_bindgen::{JsCast, JsValue, prelude::wasm_bindgen};
+
+use crate::gpu::{
+ import_canvas_texture, read_offscreen_canvas_property, read_serde_property, read_u32_property,
+ with_gpu_runtime,
+};
+
+struct CompositorRuntime {
+ canvas: web_sys::HtmlCanvasElement,
+ compositor: Compositor,
+}
+
+thread_local! {
+ static COMPOSITOR_RUNTIME: RefCell<Option<CompositorRuntime>> = const { RefCell::new(None) };
+}
+
+#[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"))?;
+ canvas.set_width(width);
+ canvas.set_height(height);
+
+ let compositor = Compositor::new(&gpu_runtime.context);
+
+ COMPOSITOR_RUNTIME.with(|runtime| {
+ runtime.replace(Some(CompositorRuntime { canvas, compositor }));
+ });
+
+ Ok(())
+ })
+}
+
+#[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(())
+ })
+}
+
+#[wasm_bindgen(js_name = getCompositorCanvas)]
+pub fn get_compositor_canvas() -> Result<web_sys::HtmlCanvasElement, JsValue> {
+ COMPOSITOR_RUNTIME.with(|runtime| {
+ let borrow = runtime.borrow();
+ let Some(runtime) = borrow.as_ref() else {
+ return Err(JsValue::from_str(
+ "Compositor is not initialized. Call initCompositor() first.",
+ ));
+ };
+ Ok(runtime.canvas.clone())
+ })
+}
+
+#[wasm_bindgen(js_name = uploadTexture)]
+pub fn upload_texture(options: JsValue) -> Result<(), JsValue> {
+ let UploadTextureOptions {
+ id,
+ source,
+ width,
+ height,
+ } = parse_upload_texture_options(options)?;
+
+ 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.",
+ ));
+ };
+
+ let texture = import_canvas_texture(
+ &gpu_runtime.context,
+ &source,
+ width,
+ height,
+ "compositor-upload-texture",
+ );
+ runtime.compositor.upsert_texture(id, texture);
+ Ok(())
+ })
+ })
+}
+
+#[wasm_bindgen(js_name = releaseTexture)]
+pub fn release_texture(id: String) -> 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.compositor.release_texture(&id);
+ Ok(())
+ })
+}
+
+#[wasm_bindgen(js_name = renderFrame)]
+pub fn render_frame(options: JsValue) -> Result<(), JsValue> {
+ let frame: FrameDescriptor = serde_wasm_bindgen::from_value(options)
+ .map_err(|error| JsValue::from_str(&format!("Invalid frame descriptor: {error}")))?;
+
+ 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.",
+ ));
+ };
+
+ let surface = gpu_runtime
+ .context
+ .instance()
+ .create_surface(wgpu::SurfaceTarget::Canvas(runtime.canvas.clone()))
+ .map_err(|error| JsValue::from_str(&error.to_string()))?;
+
+ runtime
+ .compositor
+ .render_frame(
+ &gpu_runtime.context,
+ RenderFrameOptions {
+ frame: &frame,
+ surface: &surface,
+ },
+ )
+ .map_err(|error| JsValue::from_str(&error.to_string()))
+ })
+ })
+}
+
+#[derive(Debug)]
+struct UploadTextureOptions {
+ id: String,
+ source: wgpu::web_sys::OffscreenCanvas,
+ width: u32,
+ height: u32,
+}
+
+fn parse_upload_texture_options(value: JsValue) -> Result<UploadTextureOptions, JsValue> {
+ let object: Object = value
+ .dyn_into()
+ .map_err(|_| JsValue::from_str("uploadTexture expects an options object"))?;
+
+ Ok(UploadTextureOptions {
+ id: read_serde_property(&object, "id")?,
+ source: read_offscreen_canvas_property(&object, "source")?,
+ width: read_u32_property(&object, "width")?,
+ height: read_u32_property(&object, "height")?,
+ })
+}
+
diff --git a/rust/wasm/src/gpu.rs b/rust/wasm/src/gpu.rs
index 70f3a241..4e65978c 100644
--- a/rust/wasm/src/gpu.rs
+++ b/rust/wasm/src/gpu.rs
@@ -21,6 +21,8 @@ thread_local! {
#[wasm_bindgen(js_name = initializeGpu)]
pub async fn initialize_gpu() -> Result<(), JsValue> {
+ console_error_panic_hook::set_once();
+
if GPU_RUNTIME.with(|runtime| runtime.borrow().is_some()) {
return Ok(());
}
diff --git a/rust/wasm/src/wasm.rs b/rust/wasm/src/wasm.rs
index 27ece766..1a33092a 100644
--- a/rust/wasm/src/wasm.rs
+++ b/rust/wasm/src/wasm.rs
@@ -1,4 +1,6 @@
#[cfg(target_arch = "wasm32")]
+mod compositor;
+#[cfg(target_arch = "wasm32")]
mod effects;
#[cfg(target_arch = "wasm32")]
mod gpu;
@@ -6,6 +8,8 @@ mod gpu;
mod masks;
#[cfg(target_arch = "wasm32")]
+pub use compositor::*;
+#[cfg(target_arch = "wasm32")]
pub use effects::*;
#[cfg(target_arch = "wasm32")]
pub use gpu::*;