From ad00530d6fc642ca67b344c07582b4c7db86c4a7 Mon Sep 17 00:00:00 2001 From: Timon Date: Sun, 22 Mar 2026 23:07:15 +0000 Subject: [PATCH] Remove surface and window from ApplicationIo --- Cargo.lock | 1 - desktop/wrapper/src/lib.rs | 2 +- .../node_graph/document_node_definitions.rs | 88 ------- editor/src/node_graph_executor.rs | 11 +- editor/src/node_graph_executor/runtime.rs | 226 ++++++++++++------ node-graph/graph-craft/src/document/value.rs | 44 ++-- .../graph-craft/src/wasm_application_io.rs | 160 +------------ .../interpreted-executor/src/node_registry.rs | 19 +- .../libraries/application-io/src/lib.rs | 170 ++----------- node-graph/libraries/wgpu-executor/Cargo.toml | 1 - node-graph/libraries/wgpu-executor/src/lib.rs | 48 +--- node-graph/nodes/gstd/src/pixel_preview.rs | 6 +- node-graph/nodes/gstd/src/render_cache.rs | 8 +- node-graph/nodes/gstd/src/render_node.rs | 4 +- .../nodes/gstd/src/wasm_application_io.rs | 98 -------- 15 files changed, 220 insertions(+), 666 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4aae673311..d8f984368a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6932,7 +6932,6 @@ dependencies = [ "vello", "web-sys", "wgpu", - "winit", ] [[package]] diff --git a/desktop/wrapper/src/lib.rs b/desktop/wrapper/src/lib.rs index 343d924a86..ae7ae9695e 100644 --- a/desktop/wrapper/src/lib.rs +++ b/desktop/wrapper/src/lib.rs @@ -51,7 +51,7 @@ impl DesktopWrapper { pub async fn execute_node_graph() -> NodeGraphExecutionResult { let result = graphite_editor::node_graph_executor::run_node_graph().await; match result { - (true, texture) => NodeGraphExecutionResult::HasRun(texture.map(|t| t.texture)), + (true, texture) => NodeGraphExecutionResult::HasRun(texture.map(Into::into)), (false, _) => NodeGraphExecutionResult::NotRun, } } diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index b7db96ec65..0498c6a37c 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -1057,94 +1057,6 @@ fn document_node_definitions() -> HashMap { - let matrix = format_transform_matrix(frame.transform); - let transform = if matrix.is_empty() { String::new() } else { format!(" transform=\"{matrix}\"") }; + #[cfg(target_family = "wasm")] + RenderOutputType::CanvasFrame { canvas_id, resolution } => { let svg = format!( - r#"
"#, - frame.resolution.x, frame.resolution.y, frame.surface_id.0, + r#"
"#, + resolution.x, resolution.y, canvas_id, ); responses.add(FrontendMessage::UpdateDocumentArtwork { svg }); } diff --git a/editor/src/node_graph_executor/runtime.rs b/editor/src/node_graph_executor/runtime.rs index 26102cda07..64a7681481 100644 --- a/editor/src/node_graph_executor/runtime.rs +++ b/editor/src/node_graph_executor/runtime.rs @@ -58,7 +58,7 @@ pub struct NodeRuntime { /// Cached surface for Wasm viewport rendering (reused across frames) #[cfg(all(target_family = "wasm", feature = "gpu"))] - wasm_viewport_surface: Option, + wasm_canvas_cache: wasm::CanvasSurfaceCache, /// Currently displayed texture, the runtime keeps a reference to it to avoid the texture getting destroyed while it is still in use. #[cfg(all(target_family = "wasm", feature = "gpu"))] current_viewport_texture: Option, @@ -146,7 +146,7 @@ impl NodeRuntime { vector_modify: Default::default(), inspect_state: None, #[cfg(all(target_family = "wasm", feature = "gpu"))] - wasm_viewport_surface: None, + wasm_canvas_cache: wasm::CanvasSurfaceCache::new(), #[cfg(all(target_family = "wasm", feature = "gpu"))] current_viewport_texture: None, } @@ -280,7 +280,7 @@ impl NodeRuntime { .gpu_executor() .expect("GPU executor should be available when we receive a texture"); - let raster_cpu = Raster::new_gpu(image_texture.texture.as_ref().clone()).convert(Footprint::BOUNDLESS, executor).await; + let raster_cpu = Raster::new_gpu(image_texture.as_ref().clone()).convert(Footprint::BOUNDLESS, executor).await; let (data, width, height) = raster_cpu.to_flat_u8(); @@ -304,7 +304,7 @@ impl NodeRuntime { .gpu_executor() .expect("GPU executor should be available when we receive a texture"); - let raster_cpu = Raster::new_gpu(image_texture.texture.as_ref().clone()).convert(Footprint::BOUNDLESS, executor).await; + let raster_cpu = Raster::new_gpu(image_texture.as_ref().clone()).convert(Footprint::BOUNDLESS, executor).await; self.sender.send_eyedropper_preview(raster_cpu); continue; @@ -318,83 +318,19 @@ impl NodeRuntime { data: RenderOutputType::Texture(image_texture), metadata, })) if !render_config.for_export => { - // On Wasm, for viewport rendering, blit the texture to a surface and return a CanvasFrame - let app_io = self.editor_api.application_io.as_ref().unwrap(); - let executor = app_io.gpu_executor().expect("GPU executor should be available when we receive a texture"); - - // Get or create the cached surface - if self.wasm_viewport_surface.is_none() { - let surface_handle = app_io.create_window(); - let wasm_surface = executor - .create_surface(graphene_std::wasm_application_io::WasmSurfaceHandle { - surface: surface_handle.surface.clone(), - window_id: surface_handle.window_id, - }) - .expect("Failed to create surface"); - self.wasm_viewport_surface = Some(Arc::new(wasm_surface)); - } - - let surface = self.wasm_viewport_surface.as_ref().unwrap(); - - // Use logical resolution for CSS sizing, physical resolution for the actual surface/texture - let physical_resolution = render_config.viewport.resolution; - let logical_resolution = physical_resolution.as_dvec2() / render_config.scale; - - // Blit the texture to the surface - let mut encoder = executor.context.device.create_command_encoder(&vello::wgpu::CommandEncoderDescriptor { - label: Some("Texture to Surface Blit"), - }); - - // Configure the surface at physical resolution (for HiDPI displays) - let surface_inner = &surface.surface.inner; - let surface_caps = surface_inner.get_capabilities(&executor.context.adapter); - surface_inner.configure( - &executor.context.device, - &vello::wgpu::SurfaceConfiguration { - usage: vello::wgpu::TextureUsages::RENDER_ATTACHMENT | vello::wgpu::TextureUsages::COPY_DST, - format: vello::wgpu::TextureFormat::Rgba8Unorm, - width: physical_resolution.x, - height: physical_resolution.y, - present_mode: surface_caps.present_modes[0], - alpha_mode: vello::wgpu::CompositeAlphaMode::PreMultiplied, - view_formats: vec![], - desired_maximum_frame_latency: 2, - }, - ); - - let surface_texture = surface_inner.get_current_texture().expect("Failed to get surface texture"); self.current_viewport_texture = Some(image_texture.clone()); - encoder.copy_texture_to_texture( - vello::wgpu::TexelCopyTextureInfoBase { - texture: image_texture.texture.as_ref(), - mip_level: 0, - origin: Default::default(), - aspect: Default::default(), - }, - vello::wgpu::TexelCopyTextureInfoBase { - texture: &surface_texture.texture, - mip_level: 0, - origin: Default::default(), - aspect: Default::default(), - }, - image_texture.texture.size(), - ); - - executor.context.queue.submit([encoder.finish()]); - surface_texture.present(); - - // TODO: Figure out if we can explicityl destroy the wgpu texture here to reduce the allocation pressure. We might also be able to use a texture allocation pool - - let frame = graphene_std::application_io::SurfaceFrame { - surface_id: surface.window_id, - resolution: logical_resolution, - transform: glam::DAffine2::IDENTITY, - }; + let app_io = self.editor_api.application_io.as_ref().unwrap(); + let executor = app_io.gpu_executor().expect("GPU executor should be available when we receive a texture"); + let canvas_id = self.wasm_canvas_cache.blit_to_canvas(&image_texture, executor); + let logical_resolution = render_config.viewport.resolution.as_dvec2() / render_config.scale; ( Ok(TaggedValue::RenderOutput(RenderOutput { - data: RenderOutputType::CanvasFrame(frame), + data: RenderOutputType::CanvasFrame { + canvas_id, + resolution: logical_resolution, + }, metadata, })), None, @@ -672,3 +608,141 @@ impl InspectState { }) } } + +#[cfg(all(target_family = "wasm", feature = "gpu"))] +mod wasm { + use graphene_std::application_io::ImageTexture; + use js_sys::{Object, Reflect}; + use std::sync::Arc; + use std::sync::atomic::{AtomicU64, Ordering}; + use vello::wgpu; + use wasm_bindgen::JsCast; + use wasm_bindgen::JsValue; + use web_sys::{HtmlCanvasElement, window}; + use wgpu_executor::WgpuExecutor; + + pub type CanvasId = u64; + + static CANVAS_IDS: AtomicU64 = AtomicU64::new(0); + + /// Cache for the canvas surface to reuse it across frames + pub struct CanvasSurfaceCache(Option>); + impl CanvasSurfaceCache { + pub fn new() -> Self { + Self(None) + } + pub fn blit_to_canvas(&mut self, image_texture: &ImageTexture, executor: &WgpuExecutor) -> CanvasId { + let source_texture: &wgpu::Texture = image_texture.as_ref(); + + let surface = self.get(executor); + + // Blit the texture to the surface + let mut encoder = executor.context.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Texture to Surface Blit"), + }); + + let size = source_texture.size(); + + // Configure the surface at physical resolution (for HiDPI displays) + let surface_caps = surface.surface.get_capabilities(&executor.context.adapter); + surface.surface.configure( + &executor.context.device, + &wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST, + format: wgpu::TextureFormat::Rgba8Unorm, + width: size.width, + height: size.height, + present_mode: surface_caps.present_modes[0], + alpha_mode: wgpu::CompositeAlphaMode::PreMultiplied, + view_formats: vec![], + desired_maximum_frame_latency: 2, + }, + ); + + let surface_texture = surface.surface.get_current_texture().expect("Failed to get surface texture"); + + encoder.copy_texture_to_texture( + wgpu::TexelCopyTextureInfoBase { + texture: source_texture, + mip_level: 0, + origin: Default::default(), + aspect: Default::default(), + }, + wgpu::TexelCopyTextureInfoBase { + texture: &surface_texture.texture, + mip_level: 0, + origin: Default::default(), + aspect: Default::default(), + }, + source_texture.size(), + ); + + executor.context.queue.submit([encoder.finish()]); + surface_texture.present(); + + surface.canvas_id + } + + fn get(&mut self, executor: &WgpuExecutor) -> &CanvasSurface { + if self.0.is_none() { + self.0 = Some(Arc::new(create_canvas_surface(executor))); + } + self.0.as_ref().unwrap() + } + } + + /// A wgpu surface backed by an HTML canvas element. + /// Holds a reference to the canvas to prevent garbage collection. + struct CanvasSurface { + surface: wgpu::Surface<'static>, + canvas_id: u64, + /// Keep canvas alive + _canvas: HtmlCanvasElement, + } + + // SAFETY: WASM is single-threaded, so Send/Sync are safe + unsafe impl Send for CanvasSurface {} + unsafe impl Sync for CanvasSurface {} + + /// Creates a new HTML canvas element, stores it in the global `imageCanvases` object, and creates a wgpu surface from it. + fn create_canvas_surface(executor: &WgpuExecutor) -> CanvasSurface { + let wrapper = || { + let document = window().expect("should have a window in this context").document().expect("window should have a document"); + + let canvas: HtmlCanvasElement = document.create_element("canvas")?.dyn_into::()?; + let canvas_id = CANVAS_IDS.fetch_add(1, Ordering::SeqCst); + + // Store the canvas in the global scope so it doesn't get garbage collected + let window = window().expect("should have a window in this context"); + let window_obj = Object::from(window); + + let image_canvases_key = JsValue::from_str("imageCanvases"); + + let mut canvases = Reflect::get(&window_obj, &image_canvases_key); + if canvases.is_err() { + Reflect::set(&JsValue::from(web_sys::window().unwrap()), &image_canvases_key, &Object::new()).unwrap(); + canvases = Reflect::get(&window_obj, &image_canvases_key); + } + + // Convert key and value to JsValue + let js_key = JsValue::from_str(canvas_id.to_string().as_str()); + let js_value = JsValue::from(canvas.clone()); + + let canvases = Object::from(canvases.unwrap()); + + // Use Reflect API to set property + Reflect::set(&canvases, &js_key, &js_value)?; + + // Create the wgpu surface from the canvas + let surface = executor + .context + .instance + .create_surface(wgpu::SurfaceTarget::Canvas(canvas.clone())) + .expect("Failed to create surface from canvas"); + + Ok::<_, JsValue>(CanvasSurface { surface, canvas_id, _canvas: canvas }) + }; + + wrapper().expect("should be able to create surface") + } +} diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index 91b6831809..56972081a0 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -10,7 +10,6 @@ use dyn_any::DynAny; pub use dyn_any::StaticType; use glam::{Affine2, Vec2}; pub use glam::{DAffine2, DVec2, IVec2, UVec2}; -use graphene_application_io::{ImageTexture, SurfaceFrame}; use graphic_types::Artboard; use graphic_types::Graphic; use graphic_types::Vector; @@ -40,7 +39,6 @@ macro_rules! tagged_value { None, $( $(#[$meta] ) *$identifier( $ty ), )* RenderOutput(RenderOutput), - SurfaceFrame(SurfaceFrame), #[serde(skip)] EditorApi(Arc) } @@ -54,7 +52,6 @@ macro_rules! tagged_value { Self::None => {} $( Self::$identifier(x) => {x.hash(state)}),* Self::RenderOutput(x) => x.hash(state), - Self::SurfaceFrame(x) => x.hash(state), Self::EditorApi(x) => x.hash(state), } } @@ -66,7 +63,6 @@ macro_rules! tagged_value { Self::None => Box::new(()), $( Self::$identifier(x) => Box::new(x), )* Self::RenderOutput(x) => Box::new(x), - Self::SurfaceFrame(x) => Box::new(x), Self::EditorApi(x) => Box::new(x), } } @@ -76,7 +72,6 @@ macro_rules! tagged_value { Self::None => Arc::new(()), $( Self::$identifier(x) => Arc::new(x), )* Self::RenderOutput(x) => Arc::new(x), - Self::SurfaceFrame(x) => Arc::new(x), Self::EditorApi(x) => Arc::new(x), } } @@ -86,7 +81,6 @@ macro_rules! tagged_value { Self::None => concrete!(()), $( Self::$identifier(_) => concrete!($ty), )* Self::RenderOutput(_) => concrete!(RenderOutput), - Self::SurfaceFrame(_) => concrete!(SurfaceFrame), Self::EditorApi(_) => concrete!(&WasmEditorApi) } } @@ -99,8 +93,6 @@ macro_rules! tagged_value { x if x == TypeId::of::<()>() => Ok(TaggedValue::None), $( x if x == TypeId::of::<$ty>() => Ok(TaggedValue::$identifier(*downcast(input).unwrap())), )* x if x == TypeId::of::() => Ok(TaggedValue::RenderOutput(*downcast(input).unwrap())), - x if x == TypeId::of::() => Ok(TaggedValue::SurfaceFrame(*downcast(input).unwrap())), - _ => Err(format!("Cannot convert {:?} to TaggedValue", DynAny::type_name(input.as_ref()))), } @@ -113,8 +105,7 @@ macro_rules! tagged_value { x if x == TypeId::of::<()>() => Ok(TaggedValue::None), $( x if x == TypeId::of::<$ty>() => Ok(TaggedValue::$identifier(<$ty as Clone>::clone(input.downcast_ref().unwrap()))), )* x if x == TypeId::of::() => Ok(TaggedValue::RenderOutput(RenderOutput::clone(input.downcast_ref().unwrap()))), - x if x == TypeId::of::() => Ok(TaggedValue::SurfaceFrame(SurfaceFrame::clone(input.downcast_ref().unwrap()))), - _ => Err(format!("Cannot convert {:?} to TaggedValue",std::any::type_name_of_val(input))), + _ => Err(format!("Cannot convert {:?} to TaggedValue", std::any::type_name_of_val(input))), } } /// Returns a TaggedValue from the type, where that value is its type's `Default::default()` @@ -145,7 +136,6 @@ macro_rules! tagged_value { Self::None => "()".to_string(), $( Self::$identifier(x) => format!("{:?}", x), )* Self::RenderOutput(_) => "RenderOutput".to_string(), - Self::SurfaceFrame(_) => "SurfaceFrame".to_string(), Self::EditorApi(_) => "WasmEditorApi".to_string(), } } @@ -477,11 +467,10 @@ pub struct RenderOutput { pub metadata: RenderMetadata, } -#[derive(Debug, Clone, Hash, PartialEq, dyn_any::DynAny, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, PartialEq, dyn_any::DynAny, serde::Serialize, serde::Deserialize)] pub enum RenderOutputType { - CanvasFrame(SurfaceFrame), #[serde(skip)] - Texture(ImageTexture), + Texture(graphene_application_io::ImageTexture), #[serde(skip)] Buffer { data: Vec, @@ -492,8 +481,35 @@ pub enum RenderOutputType { svg: String, image_data: Vec<(u64, Image)>, }, + #[cfg(target_family = "wasm")] + CanvasFrame { + canvas_id: u64, + resolution: DVec2, + }, } +impl Hash for RenderOutputType { + fn hash(&self, state: &mut H) { + match self { + Self::Texture(texture) => { + texture.hash(state); + } + Self::Buffer { data, width, height } => { + data.hash(state); + width.hash(state); + height.hash(state); + } + Self::Svg { svg, image_data } => { + svg.hash(state); + image_data.hash(state); + } + #[cfg(target_family = "wasm")] + Self::CanvasFrame { canvas_id, .. } => { + canvas_id.hash(state); + } + } + } +} impl Hash for RenderOutput { fn hash(&self, state: &mut H) { self.data.hash(state) diff --git a/node-graph/graph-craft/src/wasm_application_io.rs b/node-graph/graph-craft/src/wasm_application_io.rs index 311bf532e1..603d471a44 100644 --- a/node-graph/graph-craft/src/wasm_application_io.rs +++ b/node-graph/graph-craft/src/wasm_application_io.rs @@ -1,69 +1,20 @@ use dyn_any::StaticType; -use graphene_application_io::{ApplicationError, ApplicationIo, ResourceFuture, SurfaceHandle, SurfaceId}; -#[cfg(target_family = "wasm")] -use js_sys::{Object, Reflect}; +use graphene_application_io::{ApplicationError, ApplicationIo, ResourceFuture}; use std::collections::HashMap; use std::hash::Hash; use std::sync::Arc; -#[cfg(target_family = "wasm")] -use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering; #[cfg(feature = "tokio")] use tokio::io::AsyncReadExt; #[cfg(target_family = "wasm")] use wasm_bindgen::JsCast; -#[cfg(target_family = "wasm")] -use wasm_bindgen::JsValue; -#[cfg(target_family = "wasm")] -use web_sys::HtmlCanvasElement; -#[cfg(target_family = "wasm")] -use web_sys::window; #[cfg(feature = "wgpu")] use wgpu_executor::WgpuExecutor; -#[derive(Debug)] -struct WindowWrapper { - #[cfg(target_family = "wasm")] - window: SurfaceHandle, - #[cfg(not(target_family = "wasm"))] - window: SurfaceHandle>, -} - -#[cfg(target_family = "wasm")] -impl Drop for WindowWrapper { - fn drop(&mut self) { - let window = window().expect("should have a window in this context"); - let window = Object::from(window); - - let image_canvases_key = JsValue::from_str("imageCanvases"); - - let wrapper = || { - if let Ok(canvases) = Reflect::get(&window, &image_canvases_key) { - // Convert key and value to JsValue - let js_key = JsValue::from_str(self.window.window_id.to_string().as_str()); - - // Use Reflect API to set property - Reflect::delete_property(&canvases.into(), &js_key)?; - } - Ok::<_, JsValue>(()) - }; - - wrapper().expect("should be able to set canvas in global scope") - } -} - -#[cfg(target_family = "wasm")] -unsafe impl Sync for WindowWrapper {} -#[cfg(target_family = "wasm")] -unsafe impl Send for WindowWrapper {} - #[derive(Debug, Default)] pub struct WasmApplicationIo { - #[cfg(target_family = "wasm")] - ids: AtomicU64, #[cfg(feature = "wgpu")] pub(crate) gpu_executor: Option, - windows: Vec, pub resources: HashMap>, } @@ -109,15 +60,10 @@ impl WasmApplicationIo { WGPU_AVAILABLE.store(wgpu_available as i8, Ordering::SeqCst); let mut io = Self { - #[cfg(target_family = "wasm")] - ids: AtomicU64::new(0), #[cfg(feature = "wgpu")] gpu_executor: executor, - windows: Vec::new(), resources: HashMap::new(), }; - let window = io.create_window(); - io.windows.push(WindowWrapper { window }); io.resources.insert("null".to_string(), Arc::from(include_bytes!("null.png").to_vec())); io @@ -134,11 +80,8 @@ impl WasmApplicationIo { WGPU_AVAILABLE.store(wgpu_available as i8, Ordering::SeqCst); let mut io = Self { - #[cfg(target_family = "wasm")] - ids: AtomicU64::new(0), #[cfg(feature = "wgpu")] gpu_executor: executor, - windows: Vec::new(), resources: HashMap::new(), }; @@ -159,7 +102,6 @@ impl WasmApplicationIo { let mut io = Self { gpu_executor: executor, - windows: Vec::new(), resources: HashMap::new(), }; @@ -188,102 +130,11 @@ impl<'a> From<&'a WasmApplicationIo> for &'a WgpuExecutor { pub type WasmEditorApi = graphene_application_io::EditorApi; impl ApplicationIo for WasmApplicationIo { - #[cfg(target_family = "wasm")] - type Surface = HtmlCanvasElement; - #[cfg(not(target_family = "wasm"))] - type Surface = Arc; #[cfg(feature = "wgpu")] type Executor = WgpuExecutor; #[cfg(not(feature = "wgpu"))] type Executor = (); - #[cfg(target_family = "wasm")] - fn create_window(&self) -> SurfaceHandle { - let wrapper = || { - let document = window().expect("should have a window in this context").document().expect("window should have a document"); - - let canvas: HtmlCanvasElement = document.create_element("canvas")?.dyn_into::()?; - let id = self.ids.fetch_add(1, Ordering::SeqCst); - // store the canvas in the global scope so it doesn't get garbage collected - let window = window().expect("should have a window in this context"); - let window = Object::from(window); - - let image_canvases_key = JsValue::from_str("imageCanvases"); - - let mut canvases = Reflect::get(&window, &image_canvases_key); - if canvases.is_err() { - Reflect::set(&JsValue::from(web_sys::window().unwrap()), &image_canvases_key, &Object::new()).unwrap(); - canvases = Reflect::get(&window, &image_canvases_key); - } - - // Convert key and value to JsValue - let js_key = JsValue::from_str(id.to_string().as_str()); - let js_value = JsValue::from(canvas.clone()); - - let canvases = Object::from(canvases.unwrap()); - - // Use Reflect API to set property - Reflect::set(&canvases, &js_key, &js_value)?; - Ok::<_, JsValue>(SurfaceHandle { - window_id: SurfaceId(id), - surface: canvas, - }) - }; - - wrapper().expect("should be able to set canvas in global scope") - } - #[cfg(not(target_family = "wasm"))] - fn create_window(&self) -> SurfaceHandle { - todo!("winit api changed, calling create_window on EventLoop is deprecated"); - - // log::trace!("Spawning window"); - - // #[cfg(all(not(test), target_os = "linux", feature = "wayland"))] - // use winit::platform::wayland::EventLoopBuilderExtWayland; - - // #[cfg(all(not(test), target_os = "linux", feature = "wayland"))] - // let event_loop = winit::event_loop::EventLoopBuilder::new().with_any_thread(true).build().unwrap(); - // #[cfg(not(all(not(test), target_os = "linux", feature = "wayland")))] - // let event_loop = winit::event_loop::EventLoop::new().unwrap(); - - // let window = event_loop - // .create_window( - // winit::window::WindowAttributes::default() - // .with_title("Graphite") - // .with_inner_size(winit::dpi::PhysicalSize::new(800, 600)), - // ) - // .unwrap(); - - // SurfaceHandle { - // window_id: SurfaceId(window.id().into()), - // surface: Arc::new(window), - // } - } - - #[cfg(target_family = "wasm")] - fn destroy_window(&self, surface_id: SurfaceId) { - let window = window().expect("should have a window in this context"); - let window = Object::from(window); - - let image_canvases_key = JsValue::from_str("imageCanvases"); - - let wrapper = || { - if let Ok(canvases) = Reflect::get(&window, &image_canvases_key) { - // Convert key and value to JsValue - let js_key = JsValue::from_str(surface_id.0.to_string().as_str()); - - // Use Reflect API to set property - Reflect::delete_property(&canvases.into(), &js_key)?; - } - Ok::<_, JsValue>(()) - }; - - wrapper().expect("should be able to set canvas in global scope") - } - - #[cfg(not(target_family = "wasm"))] - fn destroy_window(&self, _surface_id: SurfaceId) {} - #[cfg(feature = "wgpu")] fn gpu_executor(&self) -> Option<&Self::Executor> { self.gpu_executor.as_ref() @@ -325,17 +176,8 @@ impl ApplicationIo for WasmApplicationIo { _ => Err(ApplicationError::NotFound), } } - - fn window(&self) -> Option> { - self.windows.first().map(|wrapper| wrapper.window.clone()) - } } -#[cfg(feature = "wgpu")] -pub type WasmSurfaceHandle = SurfaceHandle; -#[cfg(feature = "wgpu")] -pub type WasmSurfaceHandleFrame = graphene_application_io::SurfaceHandleFrame; - #[cfg_attr(feature = "wasm", derive(tsify::Tsify))] #[derive(Clone, Debug, PartialEq, Hash, serde::Serialize, serde::Deserialize)] pub struct EditorPreferences { diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 74b2733afa..e4349cf609 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -4,7 +4,7 @@ use graph_craft::document::DocumentNode; use graph_craft::document::value::RenderOutput; use graph_craft::proto::{NodeConstructor, TypeErasedBox}; use graphene_std::any::DynAnyNode; -use graphene_std::application_io::{ImageTexture, SurfaceFrame}; +use graphene_std::application_io::ImageTexture; use graphene_std::brush::brush_cache::BrushCache; use graphene_std::brush::brush_stroke::BrushStroke; use graphene_std::gradient::GradientStops; @@ -19,16 +19,11 @@ use graphene_std::transform::Footprint; use graphene_std::uuid::NodeId; use graphene_std::vector::Vector; use graphene_std::wasm_application_io::WasmEditorApi; -#[cfg(feature = "gpu")] -use graphene_std::wasm_application_io::WasmSurfaceHandle; use graphene_std::{Artboard, Context, Graphic, NodeIO, NodeIOTypes, ProtoNodeIdentifier, concrete, fn_type_fut, future}; use node_registry_macros::{async_node, convert_node, into_node}; use std::collections::HashMap; #[cfg(feature = "gpu")] -use std::sync::Arc; -#[cfg(feature = "gpu")] use wgpu_executor::WgpuExecutor; -use wgpu_executor::{WgpuSurface, WindowHandle}; // TODO: turn into hashmap fn node_registry() -> HashMap> { @@ -137,13 +132,8 @@ fn node_registry() -> HashMap, input: Context, fn_params: [Context => &WasmEditorApi, Context => graphene_std::ContextFeatures]), - #[cfg(feature = "gpu")] - async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => Arc, Context => graphene_std::ContextFeatures]), async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => RenderIntermediate, Context => graphene_std::ContextFeatures]), async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => RenderOutput, Context => graphene_std::ContextFeatures]), - async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => WgpuSurface, Context => graphene_std::ContextFeatures]), - async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => Option, Context => graphene_std::ContextFeatures]), - async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => WindowHandle, Context => graphene_std::ContextFeatures]), // ========== // MEMO NODES // ========== @@ -161,11 +151,6 @@ fn node_registry() -> HashMap, input: Context, fn_params: [Context => Vec]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec]), - #[cfg(feature = "gpu")] - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Arc]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => WindowHandle]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => SurfaceFrame]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => f64]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => f32]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => u32]), @@ -177,8 +162,6 @@ fn node_registry() -> HashMap, input: Context, fn_params: [Context => RenderOutput]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => &WasmEditorApi]), #[cfg(feature = "gpu")] - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => WgpuSurface]), - #[cfg(feature = "gpu")] async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table>]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option]), diff --git a/node-graph/libraries/application-io/src/lib.rs b/node-graph/libraries/application-io/src/lib.rs index 0689a53a3d..f0e593d4d8 100644 --- a/node-graph/libraries/application-io/src/lib.rs +++ b/node-graph/libraries/application-io/src/lib.rs @@ -1,6 +1,6 @@ use core_types::transform::Footprint; use dyn_any::{DynAny, StaticType, StaticTypeSized}; -use glam::{DAffine2, DVec2, UVec2}; +use glam::DVec2; use std::fmt::Debug; use std::future::Future; use std::hash::{Hash, Hasher}; @@ -11,145 +11,36 @@ use std::time::Duration; use text_nodes::FontCache; use vector_types::vector::style::RenderMode; -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] -pub struct SurfaceId(pub u64); - -impl std::fmt::Display for SurfaceId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!("{}", self.0)) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct SurfaceFrame { - pub surface_id: SurfaceId, - /// Logical resolution in CSS pixels (used for foreignObject dimensions) - pub resolution: DVec2, - pub transform: DAffine2, -} - -impl Hash for SurfaceFrame { - fn hash(&self, state: &mut H) { - self.surface_id.hash(state); - self.transform.to_cols_array().iter().for_each(|x| x.to_bits().hash(state)); - } -} - -unsafe impl StaticType for SurfaceFrame { - type Static = SurfaceFrame; -} - -pub trait Size { - fn size(&self) -> UVec2; -} - -#[cfg(target_family = "wasm")] -impl Size for web_sys::HtmlCanvasElement { - fn size(&self) -> UVec2 { - UVec2::new(self.width(), self.height()) - } -} - -#[derive(Debug, Clone, DynAny)] -pub struct ImageTexture { - #[cfg(feature = "wgpu")] - pub texture: Arc, - #[cfg(not(feature = "wgpu"))] - pub texture: (), -} - -impl<'a> serde::Deserialize<'a> for ImageTexture { - fn deserialize(_: D) -> Result - where - D: serde::Deserializer<'a>, - { - unimplemented!("attempted to serialize a texture") - } -} - -impl Hash for ImageTexture { - #[cfg(feature = "wgpu")] - fn hash(&self, state: &mut H) { - self.texture.hash(state); - } - #[cfg(not(feature = "wgpu"))] - fn hash(&self, _state: &mut H) {} -} - -impl PartialEq for ImageTexture { - fn eq(&self, other: &Self) -> bool { - #[cfg(feature = "wgpu")] - { - self.texture == other.texture - } - #[cfg(not(feature = "wgpu"))] - { - self.texture == other.texture - } +#[cfg(feature = "wgpu")] +#[derive(Debug, Clone, Hash, PartialEq, Eq, DynAny)] +pub struct ImageTexture(Arc); +#[cfg(feature = "wgpu")] +impl AsRef for ImageTexture { + fn as_ref(&self) -> &wgpu::Texture { + &self.0 } } - #[cfg(feature = "wgpu")] -impl Size for ImageTexture { - fn size(&self) -> UVec2 { - UVec2::new(self.texture.width(), self.texture.height()) +impl From for ImageTexture { + fn from(texture: wgpu::Texture) -> Self { + Self(Arc::new(texture)) } } - -impl From> for SurfaceFrame { - fn from(x: SurfaceHandleFrame) -> Self { - let size = x.surface_handle.surface.size(); - Self { - surface_id: x.surface_handle.window_id, - transform: x.transform, - resolution: size.into(), - } +#[cfg(feature = "wgpu")] +impl From> for ImageTexture { + fn from(texture: Arc) -> Self { + Self(texture) } } - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct SurfaceHandle { - pub window_id: SurfaceId, - pub surface: Surface, -} - -// #[cfg(target_family = "wasm")] -// unsafe impl Send for SurfaceHandle {} -// #[cfg(target_family = "wasm")] -// unsafe impl Sync for SurfaceHandle {} - -impl Size for SurfaceHandle { - fn size(&self) -> UVec2 { - self.surface.size() +#[cfg(feature = "wgpu")] +impl From for Arc { + fn from(image_texture: ImageTexture) -> Self { + image_texture.0 } } - -unsafe impl StaticType for SurfaceHandle { - type Static = SurfaceHandle; -} - -#[derive(Clone, Debug, PartialEq)] -pub struct SurfaceHandleFrame { - pub surface_handle: Arc>, - pub transform: DAffine2, -} - -unsafe impl StaticType for SurfaceHandleFrame { - type Static = SurfaceHandleFrame; -} - -#[cfg(feature = "wasm")] -pub type WasmSurfaceHandle = SurfaceHandle; -#[cfg(feature = "wasm")] -pub type WasmSurfaceHandleFrame = SurfaceHandleFrame; - -// TODO: think about how to automatically clean up memory -/* -impl<'a, Surface> Drop for SurfaceHandle<'a, Surface> { - fn drop(&mut self) { - self.application_io.destroy_surface(self.surface_id) - } -}*/ +#[cfg(not(feature = "wgpu"))] +#[derive(Debug, Clone, Hash, PartialEq, Eq, DynAny)] +pub struct ImageTexture; #[cfg(target_family = "wasm")] pub type ResourceFuture = Pin, ApplicationError>>>>; @@ -157,11 +48,7 @@ pub type ResourceFuture = Pin, Applicat pub type ResourceFuture = Pin, ApplicationError>> + Send>>; pub trait ApplicationIo { - type Surface; type Executor; - fn window(&self) -> Option>; - fn create_window(&self) -> SurfaceHandle; - fn destroy_window(&self, surface_id: SurfaceId); fn gpu_executor(&self) -> Option<&Self::Executor> { None } @@ -169,21 +56,8 @@ pub trait ApplicationIo { } impl ApplicationIo for &T { - type Surface = T::Surface; type Executor = T::Executor; - fn window(&self) -> Option> { - (**self).window() - } - - fn create_window(&self) -> SurfaceHandle { - (**self).create_window() - } - - fn destroy_window(&self, surface_id: SurfaceId) { - (**self).destroy_window(surface_id) - } - fn gpu_executor(&self) -> Option<&T::Executor> { (**self).gpu_executor() } diff --git a/node-graph/libraries/wgpu-executor/Cargo.toml b/node-graph/libraries/wgpu-executor/Cargo.toml index 23ae4fd5d3..8565d8682d 100644 --- a/node-graph/libraries/wgpu-executor/Cargo.toml +++ b/node-graph/libraries/wgpu-executor/Cargo.toml @@ -20,6 +20,5 @@ anyhow = { workspace = true } wgpu = { workspace = true } futures = { workspace = true } web-sys = { workspace = true } -winit = { workspace = true } vello = { workspace = true } bytemuck = { workspace = true } diff --git a/node-graph/libraries/wgpu-executor/src/lib.rs b/node-graph/libraries/wgpu-executor/src/lib.rs index e98869238d..e8633c5cf7 100644 --- a/node-graph/libraries/wgpu-executor/src/lib.rs +++ b/node-graph/libraries/wgpu-executor/src/lib.rs @@ -7,13 +7,10 @@ use crate::resample::Resampler; use crate::shader_runtime::ShaderRuntime; use anyhow::Result; use core_types::Color; -use dyn_any::StaticType; use futures::lock::Mutex; use glam::UVec2; -use graphene_application_io::{ApplicationIo, EditorApi, SurfaceHandle, SurfaceId}; -use std::sync::Arc; +use graphene_application_io::{ApplicationIo, EditorApi}; use vello::{AaConfig, AaSupport, RenderParams, Renderer, RendererOptions, Scene}; -use wgpu::util::TextureBlitter; use wgpu::{Origin3d, TextureAspect}; pub use context::Context as WgpuContext; @@ -42,15 +39,6 @@ impl<'a, T: ApplicationIo> From<&'a EditorApi> for & } } -pub type WgpuSurface = Arc>; -pub type WgpuWindow = Arc>; - -pub struct Surface { - pub inner: wgpu::Surface<'static>, - pub target_texture: Mutex>, - pub blitter: TextureBlitter, -} - #[derive(Clone, Debug)] pub struct TargetTexture { texture: wgpu::Texture, @@ -103,15 +91,6 @@ impl TargetTexture { } } -#[cfg(target_family = "wasm")] -pub type Window = web_sys::HtmlCanvasElement; -#[cfg(not(target_family = "wasm"))] -pub type Window = Arc; - -unsafe impl StaticType for Surface { - type Static = Surface; -} - const VELLO_SURFACE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm; impl WgpuExecutor { @@ -160,29 +139,6 @@ impl WgpuExecutor { pub fn resample_texture(&self, source: &wgpu::Texture, target_size: UVec2, transform: &glam::DAffine2) -> wgpu::Texture { self.resampler.resample(&self.context, source, target_size, transform) } - - #[cfg(target_family = "wasm")] - pub fn create_surface(&self, canvas: graphene_application_io::WasmSurfaceHandle) -> Result> { - let surface = self.context.instance.create_surface(wgpu::SurfaceTarget::Canvas(canvas.surface))?; - self.create_surface_inner(surface, canvas.window_id) - } - #[cfg(not(target_family = "wasm"))] - pub fn create_surface(&self, window: SurfaceHandle) -> Result> { - let surface = self.context.instance.create_surface(wgpu::SurfaceTarget::Window(Box::new(window.surface)))?; - self.create_surface_inner(surface, window.window_id) - } - - pub fn create_surface_inner(&self, surface: wgpu::Surface<'static>, window_id: SurfaceId) -> Result> { - let blitter = TextureBlitter::new(&self.context.device, VELLO_SURFACE_FORMAT); - Ok(SurfaceHandle { - window_id, - surface: Surface { - inner: surface, - target_texture: Mutex::new(None), - blitter, - }, - }) - } } impl WgpuExecutor { @@ -213,5 +169,3 @@ impl WgpuExecutor { }) } } - -pub type WindowHandle = Arc>; diff --git a/node-graph/nodes/gstd/src/pixel_preview.rs b/node-graph/nodes/gstd/src/pixel_preview.rs index 5628477586..4a348b84ec 100644 --- a/node-graph/nodes/gstd/src/pixel_preview.rs +++ b/node-graph/nodes/gstd/src/pixel_preview.rs @@ -4,7 +4,7 @@ use core_types::{CloneVarArgs, Context, Ctx, ExtractAll, OwnedContextImpl}; use glam::{DAffine2, DVec2, UVec2}; use graph_craft::document::value::RenderOutput; use graph_craft::wasm_application_io::WasmEditorApi; -use graphene_application_io::{ApplicationIo, ImageTexture}; +use graphene_application_io::ApplicationIo; use rendering::{RenderOutputType as RenderOutputTypeRequest, RenderParams}; use vector_types::vector::style::RenderMode; @@ -59,9 +59,9 @@ pub async fn pixel_preview<'a: 'n>( let transform = DAffine2::from_translation(-upstream_min) * footprint.transform.inverse() * DAffine2::from_scale(logical_resolution); let exec = editor_api.application_io.as_ref().unwrap().gpu_executor().unwrap(); - let resampled = exec.resample_texture(&source_texture.texture, physical_resolution, &transform); + let resampled = exec.resample_texture(source_texture.as_ref(), physical_resolution, &transform); - result.data = RenderOutputType::Texture(ImageTexture { texture: resampled.into() }); + result.data = RenderOutputType::Texture(resampled.into()); result .metadata diff --git a/node-graph/nodes/gstd/src/render_cache.rs b/node-graph/nodes/gstd/src/render_cache.rs index 1244c8a8be..5e18803217 100644 --- a/node-graph/nodes/gstd/src/render_cache.rs +++ b/node-graph/nodes/gstd/src/render_cache.rs @@ -6,7 +6,7 @@ use core_types::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractAnimationTime, E use glam::{DAffine2, DVec2, IVec2, UVec2}; use graph_craft::document::value::RenderOutput; use graph_craft::wasm_application_io::WasmEditorApi; -use graphene_application_io::{ApplicationIo, ImageTexture}; +use graphene_application_io::ApplicationIo; use rendering::{RenderOutputType as RenderOutputTypeRequest, RenderParams}; use std::collections::HashSet; use std::hash::Hash; @@ -460,7 +460,7 @@ pub async fn render_output_cache<'a: 'n>( let combined_metadata = composite_cached_regions(&all_regions, output_texture.as_ref(), &device_origin_offset, &footprint.transform, exec); RenderOutput { - data: RenderOutputType::Texture(ImageTexture { texture: output_texture }), + data: RenderOutputType::Texture(output_texture.into()), metadata: combined_metadata, } } @@ -506,7 +506,7 @@ where let memory_size = (region_pixel_size.x * region_pixel_size.y) as usize * BYTES_PER_PIXEL; CachedRegion { - texture: rendered_texture.texture.as_ref().clone(), + texture: rendered_texture.as_ref().clone(), texture_size: region_pixel_size, tiles: region.tiles.clone(), metadata: result.metadata, @@ -558,7 +558,7 @@ fn composite_cached_regions( aspect: wgpu::TextureAspect::All, }, wgpu::TexelCopyTextureInfo { - texture: &output_texture, + texture: output_texture, mip_level: 0, origin: wgpu::Origin3d { x: dst_x, y: dst_y, z: 0 }, aspect: wgpu::TextureAspect::All, diff --git a/node-graph/nodes/gstd/src/render_node.rs b/node-graph/nodes/gstd/src/render_node.rs index 4575cf2f04..4d4c28f7d7 100644 --- a/node-graph/nodes/gstd/src/render_node.rs +++ b/node-graph/nodes/gstd/src/render_node.rs @@ -5,7 +5,7 @@ use core_types::{Color, Context, Ctx, ExtractFootprint, OwnedContextImpl, WasmNo use graph_craft::document::value::RenderOutput; pub use graph_craft::document::value::RenderOutputType; pub use graph_craft::wasm_application_io::*; -use graphene_application_io::{ApplicationIo, ExportFormat, ImageTexture, RenderConfig}; +use graphene_application_io::{ApplicationIo, ExportFormat, RenderConfig}; use graphic_types::raster_types::Image; use graphic_types::raster_types::{CPU, Raster}; use graphic_types::{Artboard, Graphic, Vector}; @@ -202,7 +202,7 @@ async fn render<'a: 'n>(ctx: impl Ctx + ExtractFootprint + ExtractVarArgs, edito .expect("Failed to render Vello scene"), ); - RenderOutputType::Texture(ImageTexture { texture }) + RenderOutputType::Texture(texture.into()) } _ => unreachable!("Render node did not receive its requested data type"), }; diff --git a/node-graph/nodes/gstd/src/wasm_application_io.rs b/node-graph/nodes/gstd/src/wasm_application_io.rs index 805436a326..d97c45cef1 100644 --- a/node-graph/nodes/gstd/src/wasm_application_io.rs +++ b/node-graph/nodes/gstd/src/wasm_application_io.rs @@ -1,38 +1,11 @@ -#[cfg(target_family = "wasm")] -use base64::Engine; -#[cfg(target_family = "wasm")] -use core_types::WasmNotSend; -#[cfg(target_family = "wasm")] -use core_types::math::bbox::Bbox; use core_types::table::Table; -#[cfg(target_family = "wasm")] -use core_types::transform::Footprint; use core_types::{Color, Ctx}; pub use graph_craft::document::value::RenderOutputType; pub use graph_craft::wasm_application_io::*; use graphene_application_io::ApplicationIo; -#[cfg(target_family = "wasm")] -use graphic_types::Graphic; -#[cfg(target_family = "wasm")] -use graphic_types::Vector; use graphic_types::raster_types::Image; use graphic_types::raster_types::{CPU, Raster}; -#[cfg(target_family = "wasm")] -use graphic_types::vector_types::gradient::GradientStops; -#[cfg(target_family = "wasm")] -use rendering::{Render, RenderParams, RenderSvgSegmentList, SvgRender}; use std::sync::Arc; -#[cfg(target_family = "wasm")] -use wasm_bindgen::JsCast; -#[cfg(target_family = "wasm")] -use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement}; - -/// Allocates GPU memory and a rendering context for vector-to-raster conversion. -#[cfg(feature = "wgpu")] -#[node_macro::node(category(""))] -async fn create_surface<'a: 'n>(_: impl Ctx, editor: &'a WasmEditorApi) -> Arc { - Arc::new(editor.application_io.as_ref().unwrap().create_window()) -} fn parse_headers(headers: &str) -> reqwest::header::HeaderMap { use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; @@ -167,74 +140,3 @@ fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> Table> { Table::new_from_element(Raster::new_cpu(image)) } - -/// Renders a view of the input graphic within an area defined by the *Footprint*. -#[cfg(target_family = "wasm")] -#[node_macro::node(category(""))] -async fn rasterize( - _: impl Ctx, - #[implementations( - Table, - Table>, - Table, - Table, - Table, - )] - mut data: Table, - footprint: Footprint, - surface_handle: Arc>, -) -> Table> -where - Table: Render, -{ - use core_types::table::TableRow; - - if footprint.transform.matrix2.determinant() == 0. { - log::trace!("Invalid footprint received for rasterization"); - return Table::new(); - } - - let mut render = SvgRender::new(); - let aabb = Bbox::from_transform(footprint.transform).to_axis_aligned_bbox(); - let size = aabb.size(); - let resolution = footprint.resolution; - let render_params = RenderParams { - footprint, - for_export: true, - ..Default::default() - }; - - for row in data.iter_mut() { - *row.transform = glam::DAffine2::from_translation(-aabb.start) * *row.transform; - } - data.render_svg(&mut render, &render_params); - render.format_svg(glam::DVec2::ZERO, size); - let svg_string = render.svg.to_svg_string(); - - let canvas = &surface_handle.surface; - canvas.set_width(resolution.x); - canvas.set_height(resolution.y); - - let context = canvas.get_context("2d").unwrap().unwrap().dyn_into::().unwrap(); - - let preamble = "data:image/svg+xml;base64,"; - let mut base64_string = String::with_capacity(preamble.len() + svg_string.len() * 4); - base64_string.push_str(preamble); - base64::engine::general_purpose::STANDARD.encode_string(svg_string, &mut base64_string); - - let image_data = web_sys::HtmlImageElement::new().unwrap(); - image_data.set_src(base64_string.as_str()); - wasm_bindgen_futures::JsFuture::from(image_data.decode()).await.unwrap(); - context - .draw_image_with_html_image_element_and_dw_and_dh(&image_data, 0., 0., resolution.x as f64, resolution.y as f64) - .unwrap(); - - let rasterized = context.get_image_data(0., 0., resolution.x as f64, resolution.y as f64).unwrap(); - - let image = Image::from_image_data(&rasterized.data().0, resolution.x as u32, resolution.y as u32); - Table::new_from_row(TableRow { - element: Raster::new_cpu(image), - transform: footprint.transform, - ..Default::default() - }) -}