diff --git a/desktop/src/window.rs b/desktop/src/window.rs index 6a6b2dce71..61692bc6ee 100644 --- a/desktop/src/window.rs +++ b/desktop/src/window.rs @@ -4,7 +4,6 @@ use winit::window::{Window as WinitWindow, WindowAttributes}; use crate::consts::APP_NAME; use crate::event::AppEventScheduler; -use crate::window::mac::NativeWindowImpl; use crate::wrapper::messages::MenuItem; pub(crate) trait NativeWindow { @@ -37,7 +36,7 @@ pub(crate) struct Window { impl Window { pub(crate) fn init() { - NativeWindowImpl::init(); + native::NativeWindowImpl::init(); } pub(crate) fn new(event_loop: &dyn ActiveEventLoop, app_event_scheduler: AppEventScheduler) -> Self { 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 2da1a34fed..eae239ff85 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 @@ -882,70 +882,6 @@ fn static_nodes() -> Vec { properties: None, }, #[cfg(feature = "gpu")] - DocumentNodeDefinition { - identifier: "Create GPU Surface", - category: "Debug: GPU", - node_template: NodeTemplate { - document_node: DocumentNode { - implementation: DocumentNodeImplementation::Network(NodeNetwork { - exports: vec![NodeInput::node(NodeId(1), 0)], - nodes: [ - DocumentNode { - inputs: vec![NodeInput::scope("editor-api")], - implementation: DocumentNodeImplementation::ProtoNode(wgpu_executor::create_gpu_surface::IDENTIFIER), - ..Default::default() - }, - DocumentNode { - inputs: vec![NodeInput::node(NodeId(0), 0)], - implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER), - ..Default::default() - }, - ] - .into_iter() - .enumerate() - .map(|(id, node)| (NodeId(id as u64), node)) - .collect(), - ..Default::default() - }), - ..Default::default() - }, - persistent_node_metadata: DocumentNodePersistentMetadata { - output_names: vec!["GPU Surface".to_string()], - network_metadata: Some(NodeNetworkMetadata { - persistent_metadata: NodeNetworkPersistentMetadata { - node_metadata: [ - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Create GPU Surface".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)), - ..Default::default() - }, - ..Default::default() - }, - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Cache".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)), - ..Default::default() - }, - ..Default::default() - }, - ] - .into_iter() - .enumerate() - .map(|(id, node)| (NodeId(id as u64), node)) - .collect(), - ..Default::default() - }, - ..Default::default() - }), - ..Default::default() - }, - }, - description: Cow::Borrowed("TODO"), - properties: None, - }, - #[cfg(feature = "gpu")] DocumentNodeDefinition { identifier: "Upload Texture", category: "Debug: GPU", diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index a1364c03bc..e770c9d61e 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -21,6 +21,7 @@ use crate::messages::prelude::*; use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::utility_functions::make_path_editable_is_allowed; use crate::messages::tool::utility_types::{HintData, HintGroup, ToolType}; +use crate::messages::viewport::ToPhysical; use crate::node_graph_executor::{ExportConfig, NodeGraphExecutor}; use derivative::*; use glam::{DAffine2, DVec2}; @@ -364,12 +365,13 @@ impl MessageHandler> for Portfolio let node_to_inspect = self.node_to_inspect(); let scale = viewport.scale(); - let resolution = viewport.size().into_dvec2().round().as_uvec2(); + // Use exact physical dimensions from browser (via ResizeObserver's devicePixelContentBoxSize) + let physical_resolution = viewport.size().to_physical().into_dvec2().round().as_uvec2(); if let Ok(message) = self.executor.submit_node_graph_evaluation( self.documents.get_mut(document_id).expect("Tried to render non-existent document"), *document_id, - resolution, + physical_resolution, scale, timing_information, node_to_inspect, @@ -970,11 +972,12 @@ impl MessageHandler> for Portfolio }; let scale = viewport.scale(); - let resolution = viewport.size().into_dvec2().round().as_uvec2(); + // Use exact physical dimensions from browser (via ResizeObserver's devicePixelContentBoxSize) + let physical_resolution = viewport.size().to_physical().into_dvec2().round().as_uvec2(); let result = self .executor - .submit_node_graph_evaluation(document, document_id, resolution, scale, timing_information, node_to_inspect, ignore_hash); + .submit_node_graph_evaluation(document, document_id, physical_resolution, scale, timing_information, node_to_inspect, ignore_hash); match result { Err(description) => { diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index bc2be65f44..fb9b2eb37d 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -421,8 +421,8 @@ impl NodeGraphExecutor { let matrix = format_transform_matrix(frame.transform); let transform = if matrix.is_empty() { String::new() } else { format!(" transform=\"{matrix}\"") }; let svg = format!( - r#"
"#, - frame.resolution.x, frame.resolution.y, frame.surface_id.0 + r#"
"#, + frame.resolution.x, frame.resolution.y, frame.surface_id.0, ); self.last_svg_canvas = Some(frame); responses.add(FrontendMessage::UpdateDocumentArtwork { svg }); diff --git a/editor/src/node_graph_executor/runtime.rs b/editor/src/node_graph_executor/runtime.rs index 8f306dc46f..3be389f7c0 100644 --- a/editor/src/node_graph_executor/runtime.rs +++ b/editor/src/node_graph_executor/runtime.rs @@ -55,6 +55,10 @@ pub struct NodeRuntime { /// The current renders of the thumbnails for layer nodes. thumbnail_renders: HashMap>, vector_modify: HashMap, + + /// Cached surface for WASM viewport rendering (reused across frames) + #[cfg(all(target_family = "wasm", feature = "gpu"))] + wasm_viewport_surface: Option, } /// Messages passed from the editor thread to the node runtime thread. @@ -131,6 +135,8 @@ impl NodeRuntime { thumbnail_renders: Default::default(), vector_modify: Default::default(), inspect_state: None, + #[cfg(all(target_family = "wasm", feature = "gpu"))] + wasm_viewport_surface: None, } } @@ -259,6 +265,82 @@ impl NodeRuntime { None, ) } + #[cfg(all(target_family = "wasm", feature = "gpu"))] + Ok(TaggedValue::RenderOutput(RenderOutput { + 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::Opaque, + view_formats: vec![], + desired_maximum_frame_latency: 2, + }, + ); + + let surface_texture = surface_inner.get_current_texture().expect("Failed to get surface texture"); + + // Blit the rendered texture to the surface + surface.surface.blitter.copy( + &executor.context.device, + &mut encoder, + &image_texture.texture.create_view(&vello::wgpu::TextureViewDescriptor::default()), + &surface_texture.texture.create_view(&vello::wgpu::TextureViewDescriptor::default()), + ); + + executor.context.queue.submit([encoder.finish()]); + surface_texture.present(); + + let frame = graphene_std::application_io::SurfaceFrame { + surface_id: surface.window_id, + resolution: logical_resolution, + transform: glam::DAffine2::IDENTITY, + }; + + ( + Ok(TaggedValue::RenderOutput(RenderOutput { + data: RenderOutputType::CanvasFrame(frame), + metadata, + })), + None, + ) + } Ok(TaggedValue::RenderOutput(RenderOutput { data: RenderOutputType::Texture(texture), metadata, diff --git a/frontend/src/components/panels/Document.svelte b/frontend/src/components/panels/Document.svelte index 3f2a96ff65..3f679690b2 100644 --- a/frontend/src/components/panels/Document.svelte +++ b/frontend/src/components/panels/Document.svelte @@ -1,5 +1,5 @@ e.preventDefault()} on:drop={dropFile}> diff --git a/frontend/src/io-managers/input.ts b/frontend/src/io-managers/input.ts index eaf79bb285..5054bb4525 100644 --- a/frontend/src/io-managers/input.ts +++ b/frontend/src/io-managers/input.ts @@ -10,7 +10,6 @@ import { makeKeyboardModifiersBitfield, textInputCleanup, getLocalizedScanCode } import { operatingSystem } from "@graphite/utility-functions/platform"; import { extractPixelData } from "@graphite/utility-functions/rasterization"; import { stripIndents } from "@graphite/utility-functions/strip-indents"; -import { updateBoundsOfViewports } from "@graphite/utility-functions/viewports"; const BUTTON_LEFT = 0; const BUTTON_MIDDLE = 1; @@ -43,7 +42,6 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli // eslint-disable-next-line @typescript-eslint/no-explicit-any const listeners: { target: EventListenerTarget; eventName: EventName; action: (event: any) => void; options?: AddEventListenerOptions }[] = [ - { target: window, eventName: "resize", action: () => updateBoundsOfViewports(editor) }, { target: window, eventName: "beforeunload", action: (e: BeforeUnloadEvent) => onBeforeUnload(e) }, { target: window, eventName: "keyup", action: (e: KeyboardEvent) => onKeyUp(e) }, { target: window, eventName: "keydown", action: (e: KeyboardEvent) => onKeyDown(e) }, @@ -529,8 +527,6 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli // Bind the event listeners bindListeners(); - // Resize on creation - updateBoundsOfViewports(editor); // Return the destructor return unbindListeners; diff --git a/frontend/src/utility-functions/viewports.ts b/frontend/src/utility-functions/viewports.ts index 8fe6a32311..8323d4b149 100644 --- a/frontend/src/utility-functions/viewports.ts +++ b/frontend/src/utility-functions/viewports.ts @@ -1,12 +1,57 @@ import { type Editor } from "@graphite/editor"; -export function updateBoundsOfViewports(editor: Editor) { - const viewports = Array.from(window.document.querySelectorAll("[data-viewport-container]")); +let resizeObserver: ResizeObserver | undefined; + +export function setupViewportResizeObserver(editor: Editor) { + // Clean up existing observer if any + if (resizeObserver) { + resizeObserver.disconnect(); + } + const viewports = Array.from(window.document.querySelectorAll("[data-viewport-container]")); if (viewports.length <= 0) return; - const bounds = viewports[0].getBoundingClientRect(); - const scale = window.devicePixelRatio || 1; + const viewport = viewports[0] as HTMLElement; + + resizeObserver = new ResizeObserver((entries) => { + for (const entry of entries) { + const devicePixelRatio = window.devicePixelRatio || 1; + + // Get exact device pixel dimensions from the browser + // Use devicePixelContentBoxSize for pixel-perfect rendering with fallback for Safari + let physicalWidth: number; + let physicalHeight: number; + + if (entry.devicePixelContentBoxSize && entry.devicePixelContentBoxSize.length > 0) { + // Modern browsers (Chrome, Firefox): get exact device pixels from the browser + physicalWidth = entry.devicePixelContentBoxSize[0].inlineSize; + physicalHeight = entry.devicePixelContentBoxSize[0].blockSize; + } else { + // Fallback for Safari: calculate from contentBoxSize and devicePixelRatio + physicalWidth = entry.contentBoxSize[0].inlineSize * devicePixelRatio; + physicalHeight = entry.contentBoxSize[0].blockSize * devicePixelRatio; + } + + // Compute the logical size which corresponds to the physical size + const logicalWidth = physicalWidth / devicePixelRatio; + const logicalHeight = physicalHeight / devicePixelRatio; + + // Get viewport position + const bounds = entry.target.getBoundingClientRect(); + + // TODO: Consider passing physical sizes as well to eliminate pixel inaccuracies since width and height could be rounded differently + const scale = physicalWidth / logicalWidth; + + editor.handle.updateViewport(bounds.x, bounds.y, logicalWidth, logicalHeight, scale); + } + }); + + resizeObserver.observe(viewport); +} - editor.handle.updateViewport(bounds.x, bounds.y, bounds.width, bounds.height, scale); +export function cleanupViewportResizeObserver() { + if (resizeObserver) { + resizeObserver.disconnect(); + resizeObserver = undefined; + } } diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 1e252a5db6..9fb82e29a7 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -6,8 +6,6 @@ use graph_craft::proto::{NodeConstructor, TypeErasedBox}; use graphene_std::Artboard; use graphene_std::Context; use graphene_std::Graphic; -#[cfg(feature = "gpu")] -use graphene_std::any::DowncastBothNode; use graphene_std::any::DynAnyNode; use graphene_std::application_io::{ImageTexture, SurfaceFrame}; use graphene_std::brush::brush_cache::BrushCache; @@ -234,28 +232,6 @@ fn node_registry() -> HashMap, input: Context, fn_params: [Context => path_bool_nodes::BooleanOperation]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::text::TextAlign]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RenderIntermediate]), - // ======================= - // CREATE GPU SURFACE NODE - // ======================= - #[cfg(feature = "gpu")] - ( - ProtoNodeIdentifier::new(stringify!(wgpu_executor::CreateGpuSurfaceNode<_>)), - |args| { - Box::pin(async move { - let editor_api: DowncastBothNode = DowncastBothNode::new(args[0].clone()); - let node = >::new(editor_api); - let any: DynAnyNode = DynAnyNode::new(node); - Box::new(any) as TypeErasedBox - }) - }, - { - let node = >::new(graphene_std::any::PanicNode::>::new()); - let params = vec![fn_type_fut!(Context, &WasmEditorApi)]; - let mut node_io = as NodeIO<'_, Context>>::to_async_node_io(&node, params); - node_io.call_argument = concrete!(::Static); - node_io - }, - ), ]; // ============= // CONVERT NODES diff --git a/node-graph/interpreted-executor/src/util.rs b/node-graph/interpreted-executor/src/util.rs index bcfccdb6c6..21704e3125 100644 --- a/node-graph/interpreted-executor/src/util.rs +++ b/node-graph/interpreted-executor/src/util.rs @@ -26,21 +26,10 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc 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: x.surface_handle.surface.size(), + resolution: size.into(), } } } diff --git a/node-graph/nodes/gstd/src/render_node.rs b/node-graph/nodes/gstd/src/render_node.rs index c5d8bc9bea..462ee85985 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, SurfaceFrame}; +use graphene_application_io::{ApplicationIo, ExportFormat, ImageTexture, RenderConfig}; use graphic_types::Artboard; use graphic_types::Graphic; use graphic_types::Vector; @@ -120,12 +120,7 @@ async fn create_context<'a: 'n>( } #[node_macro::node(category(""))] -async fn render<'a: 'n>( - ctx: impl Ctx + ExtractFootprint + ExtractVarArgs, - editor_api: &'a WasmEditorApi, - data: RenderIntermediate, - _surface_handle: impl Node, Output = Option>, -) -> RenderOutput { +async fn render<'a: 'n>(ctx: impl Ctx + ExtractFootprint + ExtractVarArgs, editor_api: &'a WasmEditorApi, data: RenderIntermediate) -> RenderOutput { let footprint = ctx.footprint(); let render_params = ctx .vararg(0) @@ -136,6 +131,10 @@ async fn render<'a: 'n>( render_params.footprint = *footprint; let render_params = &render_params; + let scale = render_params.scale; + let physical_resolution = render_params.footprint.resolution; + let logical_resolution = render_params.footprint.resolution.as_dvec2() / scale; + let RenderIntermediate { ty, mut metadata, contains_artboard } = data; metadata.apply_transform(footprint.transform); @@ -146,8 +145,8 @@ async fn render<'a: 'n>( rendering.leaf_tag("rect", |attributes| { attributes.push("x", "0"); attributes.push("y", "0"); - attributes.push("width", footprint.resolution.x.to_string()); - attributes.push("height", footprint.resolution.y.to_string()); + attributes.push("width", logical_resolution.x.to_string()); + attributes.push("height", logical_resolution.y.to_string()); let matrix = format_transform_matrix(footprint.transform.inverse()); if !matrix.is_empty() { attributes.push("transform", matrix); @@ -159,7 +158,7 @@ async fn render<'a: 'n>( rendering.image_data = svg_data.1.clone(); rendering.svg_defs = svg_data.2.clone(); - rendering.wrap_with_transform(footprint.transform, Some(footprint.resolution.as_dvec2())); + rendering.wrap_with_transform(footprint.transform, Some(logical_resolution)); RenderOutputType::Svg { svg: rendering.svg.to_svg_string(), image_data: rendering.image_data, @@ -171,15 +170,6 @@ async fn render<'a: 'n>( }; let (child, context) = Arc::as_ref(vello_data); - let surface_handle = if cfg!(all(feature = "vello", target_family = "wasm")) { - _surface_handle.eval(None).await - } else { - None - }; - - // When rendering to a surface, we do not want to apply the scale - let scale = if surface_handle.is_none() { render_params.scale } else { 1. }; - let scale_transform = glam::DAffine2::from_scale(glam::DVec2::splat(scale)); let footprint_transform = scale_transform * footprint.transform; let footprint_transform_vello = vello::kurbo::Affine::new(footprint_transform.to_cols_array()); @@ -187,11 +177,9 @@ async fn render<'a: 'n>( let mut scene = vello::Scene::new(); scene.append(child, Some(footprint_transform_vello)); - let resolution = (footprint.resolution.as_dvec2() * scale).as_uvec2(); - // We now replace all transforms which are supposed to be infinite with a transform which covers the entire viewport // See for more detail - let scaled_infinite_transform = vello::kurbo::Affine::scale_non_uniform(resolution.x as f64, resolution.y as f64); + let scaled_infinite_transform = vello::kurbo::Affine::scale_non_uniform(physical_resolution.x as f64, physical_resolution.y as f64); let encoding = scene.encoding_mut(); for transform in encoding.transforms.iter_mut() { if transform.matrix[0] == f32::INFINITY { @@ -204,25 +192,12 @@ async fn render<'a: 'n>( background = Color::WHITE; } - if let Some(surface_handle) = surface_handle { - exec.render_vello_scene(&scene, &surface_handle, resolution, context, background) - .await - .expect("Failed to render Vello scene"); - - let frame = SurfaceFrame { - surface_id: surface_handle.window_id, - // TODO: Find a cleaner way to get the unscaled resolution here. - // This is done because the surface frame (canvas) is in logical pixels, not physical pixels. - resolution, - transform: glam::DAffine2::IDENTITY, - }; + let texture = exec + .render_vello_scene_to_texture(&scene, physical_resolution, context, background) + .await + .expect("Failed to render Vello scene"); - RenderOutputType::CanvasFrame(frame) - } else { - let texture = exec.render_vello_scene_to_texture(&scene, resolution, context, background).await.expect("Failed to render Vello scene"); - - RenderOutputType::Texture(ImageTexture { texture }) - } + RenderOutputType::Texture(ImageTexture { texture }) } _ => unreachable!("Render node did not receive its requested data type"), }; diff --git a/node-graph/wgpu-executor/src/lib.rs b/node-graph/wgpu-executor/src/lib.rs index a0f65bb88a..868c73f60e 100644 --- a/node-graph/wgpu-executor/src/lib.rs +++ b/node-graph/wgpu-executor/src/lib.rs @@ -4,7 +4,7 @@ pub mod texture_conversion; use crate::shader_runtime::ShaderRuntime; use anyhow::Result; -use core_types::{Color, Ctx}; +use core_types::Color; use dyn_any::StaticType; use futures::lock::Mutex; use glam::UVec2; @@ -13,7 +13,7 @@ pub use rendering::RenderContext; use std::sync::Arc; use vello::{AaConfig, AaSupport, RenderParams, Renderer, RendererOptions, Scene}; use wgpu::util::TextureBlitter; -use wgpu::{Origin3d, SurfaceConfiguration, TextureAspect}; +use wgpu::{Origin3d, TextureAspect}; pub use context::Context as WgpuContext; pub use context::ContextBuilder as WgpuContextBuilder; @@ -66,41 +66,6 @@ unsafe impl StaticType for Surface { const VELLO_SURFACE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm; impl WgpuExecutor { - pub async fn render_vello_scene(&self, scene: &Scene, surface: &WgpuSurface, size: UVec2, context: &RenderContext, background: Color) -> Result<()> { - let mut guard = surface.surface.target_texture.lock().await; - - let surface_inner = &surface.surface.inner; - let surface_caps = surface_inner.get_capabilities(&self.context.adapter); - surface_inner.configure( - &self.context.device, - &SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::STORAGE_BINDING, - format: VELLO_SURFACE_FORMAT, - width: size.x, - height: size.y, - present_mode: surface_caps.present_modes[0], - alpha_mode: wgpu::CompositeAlphaMode::Opaque, - view_formats: vec![], - desired_maximum_frame_latency: 2, - }, - ); - - self.render_vello_scene_to_target_texture(scene, size, context, background, &mut guard).await?; - - let surface_texture = surface_inner.get_current_texture()?; - let mut encoder = self.context.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("Surface Blit") }); - surface.surface.blitter.copy( - &self.context.device, - &mut encoder, - &guard.as_ref().unwrap().view, - &surface_texture.texture.create_view(&wgpu::TextureViewDescriptor::default()), - ); - self.context.queue.submit([encoder.finish()]); - surface_texture.present(); - - Ok(()) - } - pub async fn render_vello_scene_to_texture(&self, scene: &Scene, size: UVec2, context: &RenderContext, background: Color) -> Result { let mut output = None; self.render_vello_scene_to_target_texture(scene, size, context, background, &mut output).await?; @@ -211,10 +176,3 @@ impl WgpuExecutor { } pub type WindowHandle = Arc>; - -#[node_macro::node(skip_impl)] -fn create_gpu_surface<'a: 'n, Io: ApplicationIo + 'a + Send + Sync>(_: impl Ctx + 'a, editor_api: &'a EditorApi) -> Option { - let canvas = editor_api.application_io.as_ref()?.window()?; - let executor = editor_api.application_io.as_ref()?.gpu_executor()?; - Some(Arc::new(executor.create_surface(canvas).ok()?)) -}