summaryrefslogtreecommitdiff
path: root/rust/wasm/src/effects.rs
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-04-07 01:09:13 +0200
committerMaze Winther <mazewinther@gmail.com>2026-04-07 01:09:13 +0200
commite4b67094e757917588fb624a30aa046667403add (patch)
tree55452692eac69a5a63b58f2a5cdb701dd7023d68 /rust/wasm/src/effects.rs
parent79df73643185c02493364a077402726d1fa7b9ec (diff)
downloadopencut-e4b67094e757917588fb624a30aa046667403add.tar.gz
refactor: split effects and masks into dedicated rust crates, introduce MediaTime and FrameRate
Diffstat (limited to 'rust/wasm/src/effects.rs')
-rw-r--r--rust/wasm/src/effects.rs101
1 files changed, 101 insertions, 0 deletions
diff --git a/rust/wasm/src/effects.rs b/rust/wasm/src/effects.rs
new file mode 100644
index 00000000..1f5c2947
--- /dev/null
+++ b/rust/wasm/src/effects.rs
@@ -0,0 +1,101 @@
+#![cfg(target_arch = "wasm32")]
+
+use effects::{ApplyEffectsOptions, EffectPass, UniformValue};
+use gpu::wgpu;
+use js_sys::Object;
+use serde::Deserialize;
+use wasm_bindgen::{JsCast, JsValue, prelude::wasm_bindgen};
+
+use crate::gpu::{
+ import_canvas_texture, read_offscreen_canvas_property, read_serde_property, read_u32_property,
+ render_texture_to_canvas, with_gpu_runtime,
+};
+
+struct ApplyEffectPassesOptions {
+ source: wgpu::web_sys::OffscreenCanvas,
+ width: u32,
+ height: u32,
+ passes: Vec<EffectPassInput>,
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct EffectPassInput {
+ shader: String,
+ uniforms: Vec<EffectUniformInput>,
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct EffectUniformInput {
+ name: String,
+ value: Vec<f32>,
+}
+
+#[wasm_bindgen(js_name = applyEffectPasses)]
+pub fn apply_effect_passes(options: JsValue) -> Result<wgpu::web_sys::OffscreenCanvas, JsValue> {
+ let ApplyEffectPassesOptions {
+ source,
+ width,
+ height,
+ passes,
+ } = parse_apply_effect_passes_options(options)?;
+
+ with_gpu_runtime(|runtime| {
+ let source_texture = import_canvas_texture(
+ &runtime.context,
+ &source,
+ width,
+ height,
+ "effects-input-texture",
+ );
+ let effect_passes = map_effect_passes(passes);
+ let result_texture = runtime
+ .effects
+ .apply(
+ &runtime.context,
+ ApplyEffectsOptions {
+ source: &source_texture,
+ width,
+ height,
+ passes: &effect_passes,
+ },
+ )
+ .map_err(|error| JsValue::from_str(&error.to_string()))?;
+ render_texture_to_canvas(&runtime.context, &result_texture, width, height)
+ })
+}
+
+fn map_effect_passes(effect_passes: Vec<EffectPassInput>) -> Vec<EffectPass> {
+ effect_passes
+ .into_iter()
+ .map(|pass| EffectPass {
+ shader: pass.shader,
+ uniforms: pass
+ .uniforms
+ .into_iter()
+ .map(|uniform| {
+ let value = if uniform.value.len() == 1 {
+ UniformValue::Number(uniform.value[0])
+ } else {
+ UniformValue::Vector(uniform.value)
+ };
+ (uniform.name, value)
+ })
+ .collect(),
+ })
+ .collect()
+}
+
+fn parse_apply_effect_passes_options(value: JsValue) -> Result<ApplyEffectPassesOptions, JsValue> {
+ let object: Object = value
+ .dyn_into()
+ .map_err(|_| JsValue::from_str("applyEffectPasses expects an options object"))?;
+
+ Ok(ApplyEffectPassesOptions {
+ source: read_offscreen_canvas_property(&object, "source")?,
+ width: read_u32_property(&object, "width")?,
+ height: read_u32_property(&object, "height")?,
+ passes: read_serde_property(&object, "passes")?,
+ })
+}