Render artwork at correct resolution when using vello on wasm (#3416)

* Work on fixing rendering for wasm+vello

* Render vello canvas in wasm at the correct resolution

* Cleanup unused surface rendering code

* Remove vector to raster conversion

* Remove desktop changes

* Revert window.rs changes

* Don't round logical coordinates

* Fix desktop compilation + don't round logical coordinates for svg rendering

* Further cleanup

* Compute logical size from acutal physical sizes
This commit is contained in:
Dennis Kobert
2025-11-24 15:23:27 +01:00
committed by GitHub
parent 6e66c79392
commit a932eaedcf
13 changed files with 197 additions and 216 deletions

View File

@@ -55,6 +55,10 @@ pub struct NodeRuntime {
/// The current renders of the thumbnails for layer nodes.
thumbnail_renders: HashMap<NodeId, Vec<SvgSegment>>,
vector_modify: HashMap<NodeId, Vector>,
/// Cached surface for WASM viewport rendering (reused across frames)
#[cfg(all(target_family = "wasm", feature = "gpu"))]
wasm_viewport_surface: Option<wgpu_executor::WgpuSurface>,
}
/// 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,