summaryrefslogtreecommitdiff
path: root/rust/wasm/src/gpu.rs
diff options
context:
space:
mode:
Diffstat (limited to 'rust/wasm/src/gpu.rs')
-rw-r--r--rust/wasm/src/gpu.rs123
1 files changed, 123 insertions, 0 deletions
diff --git a/rust/wasm/src/gpu.rs b/rust/wasm/src/gpu.rs
new file mode 100644
index 00000000..70f3a241
--- /dev/null
+++ b/rust/wasm/src/gpu.rs
@@ -0,0 +1,123 @@
+#![cfg(target_arch = "wasm32")]
+
+use std::cell::RefCell;
+
+use effects::EffectPipeline;
+use gpu::{GpuContext, wgpu};
+use js_sys::{Object, Reflect};
+use masks::MaskFeatherPipeline;
+use serde::Deserialize;
+use wasm_bindgen::{JsCast, JsValue, prelude::wasm_bindgen};
+
+pub(crate) struct GpuRuntime {
+ pub(crate) context: GpuContext,
+ pub(crate) effects: EffectPipeline,
+ pub(crate) masks: MaskFeatherPipeline,
+}
+
+thread_local! {
+ static GPU_RUNTIME: RefCell<Option<GpuRuntime>> = const { RefCell::new(None) };
+}
+
+#[wasm_bindgen(js_name = initializeGpu)]
+pub async fn initialize_gpu() -> Result<(), JsValue> {
+ if GPU_RUNTIME.with(|runtime| runtime.borrow().is_some()) {
+ return Ok(());
+ }
+
+ let context = GpuContext::new()
+ .await
+ .map_err(|error| JsValue::from_str(&error.to_string()))?;
+ let effects = EffectPipeline::new(&context);
+ let masks = MaskFeatherPipeline::new(&context);
+
+ GPU_RUNTIME.with(|runtime| {
+ runtime.replace(Some(GpuRuntime {
+ context,
+ effects,
+ masks,
+ }));
+ });
+
+ Ok(())
+}
+
+pub(crate) fn with_gpu_runtime<T>(
+ action: impl FnOnce(&GpuRuntime) -> Result<T, JsValue>,
+) -> Result<T, JsValue> {
+ GPU_RUNTIME.with(|runtime| {
+ let borrow = runtime.borrow();
+ let Some(gpu_runtime) = borrow.as_ref() else {
+ return Err(JsValue::from_str(
+ "GPU context not initialized. Call initializeGpu() first.",
+ ));
+ };
+ action(gpu_runtime)
+ })
+}
+
+pub(crate) fn import_canvas_texture(
+ context: &GpuContext,
+ canvas: &wgpu::web_sys::OffscreenCanvas,
+ width: u32,
+ height: u32,
+ label: &'static str,
+) -> wgpu::Texture {
+ context.import_offscreen_canvas_texture(canvas, width, height, label)
+}
+
+pub(crate) fn render_texture_to_canvas(
+ context: &GpuContext,
+ texture: &wgpu::Texture,
+ width: u32,
+ height: u32,
+) -> Result<wgpu::web_sys::OffscreenCanvas, JsValue> {
+ let canvas = wgpu::web_sys::OffscreenCanvas::new(width, height)?;
+ context
+ .render_texture_to_offscreen_canvas(texture, &canvas, width, height)
+ .map_err(|error| JsValue::from_str(&error.to_string()))?;
+ Ok(canvas)
+}
+
+pub(crate) fn read_property(object: &Object, name: &str) -> Result<JsValue, JsValue> {
+ Reflect::get(object, &JsValue::from_str(name))
+ .map_err(|_| JsValue::from_str(&format!("Missing property '{name}'")))
+}
+
+pub(crate) fn read_offscreen_canvas_property(
+ object: &Object,
+ name: &str,
+) -> Result<wgpu::web_sys::OffscreenCanvas, JsValue> {
+ read_property(object, name)?
+ .dyn_into::<wgpu::web_sys::OffscreenCanvas>()
+ .map_err(|_| JsValue::from_str(&format!("Property '{name}' must be an OffscreenCanvas")))
+}
+
+pub(crate) fn read_u32_property(object: &Object, name: &str) -> Result<u32, JsValue> {
+ let value = read_property(object, name)?;
+ let Some(number) = value.as_f64() else {
+ return Err(JsValue::from_str(&format!(
+ "Property '{name}' must be a number"
+ )));
+ };
+ Ok(number as u32)
+}
+
+pub(crate) fn read_f32_property(object: &Object, name: &str) -> Result<f32, JsValue> {
+ let value = read_property(object, name)?;
+ let Some(number) = value.as_f64() else {
+ return Err(JsValue::from_str(&format!(
+ "Property '{name}' must be a number"
+ )));
+ };
+ Ok(number as f32)
+}
+
+pub(crate) fn read_serde_property<T>(object: &Object, name: &str) -> Result<T, JsValue>
+where
+ T: for<'de> Deserialize<'de>,
+{
+ let value = read_property(object, name)?;
+ serde_wasm_bindgen::from_value(value)
+ .map_err(|error| JsValue::from_str(&format!("Invalid property '{name}': {error}")))
+}