@@ -55,6 +55,10 @@ pub struct NodeRuntime {
5555 /// The current renders of the thumbnails for layer nodes.
5656 thumbnail_renders : HashMap < NodeId , Vec < SvgSegment > > ,
5757 vector_modify : HashMap < NodeId , Vector > ,
58+
59+ /// Cached surface for WASM viewport rendering (reused across frames)
60+ #[ cfg( all( target_family = "wasm" , feature = "gpu" ) ) ]
61+ wasm_viewport_surface : Option < wgpu_executor:: WgpuSurface > ,
5862}
5963
6064/// Messages passed from the editor thread to the node runtime thread.
@@ -131,6 +135,8 @@ impl NodeRuntime {
131135 thumbnail_renders : Default :: default ( ) ,
132136 vector_modify : Default :: default ( ) ,
133137 inspect_state : None ,
138+ #[ cfg( all( target_family = "wasm" , feature = "gpu" ) ) ]
139+ wasm_viewport_surface : None ,
134140 }
135141 }
136142
@@ -259,6 +265,83 @@ impl NodeRuntime {
259265 None ,
260266 )
261267 }
268+ #[ cfg( all( target_family = "wasm" , feature = "gpu" ) ) ]
269+ Ok ( TaggedValue :: RenderOutput ( RenderOutput {
270+ data : RenderOutputType :: Texture ( image_texture) ,
271+ metadata,
272+ } ) ) if !render_config. for_export => {
273+ // On WASM, for viewport rendering, blit the texture to a surface and return a CanvasFrame
274+ let app_io = self . editor_api . application_io . as_ref ( ) . unwrap ( ) ;
275+ let executor = app_io. gpu_executor ( ) . expect ( "GPU executor should be available when we receive a texture" ) ;
276+
277+ // Get or create the cached surface
278+ if self . wasm_viewport_surface . is_none ( ) {
279+ let surface_handle = app_io. create_window ( ) ;
280+ let wasm_surface = executor
281+ . create_surface ( graphene_std:: wasm_application_io:: WasmSurfaceHandle {
282+ surface : surface_handle. surface . clone ( ) ,
283+ window_id : surface_handle. window_id ,
284+ } )
285+ . expect ( "Failed to create surface" ) ;
286+ self . wasm_viewport_surface = Some ( Arc :: new ( wasm_surface) ) ;
287+ }
288+
289+ let surface = self . wasm_viewport_surface . as_ref ( ) . unwrap ( ) ;
290+
291+ // Use logical resolution for CSS sizing, physical resolution for the actual surface/texture
292+ let logical_resolution = render_config. viewport . resolution ;
293+ let physical_resolution = ( logical_resolution. as_dvec2 ( ) * render_config. scale ) . as_uvec2 ( ) ;
294+
295+ // Blit the texture to the surface
296+ let mut encoder = executor. context . device . create_command_encoder ( & vello:: wgpu:: CommandEncoderDescriptor {
297+ label : Some ( "Texture to Surface Blit" ) ,
298+ } ) ;
299+
300+ // Configure the surface at physical resolution (for HiDPI displays)
301+ let surface_inner = & surface. surface . inner ;
302+ let surface_caps = surface_inner. get_capabilities ( & executor. context . adapter ) ;
303+ surface_inner. configure (
304+ & executor. context . device ,
305+ & vello:: wgpu:: SurfaceConfiguration {
306+ usage : vello:: wgpu:: TextureUsages :: RENDER_ATTACHMENT | vello:: wgpu:: TextureUsages :: COPY_DST ,
307+ format : vello:: wgpu:: TextureFormat :: Rgba8Unorm ,
308+ width : physical_resolution. x ,
309+ height : physical_resolution. y ,
310+ present_mode : surface_caps. present_modes [ 0 ] ,
311+ alpha_mode : vello:: wgpu:: CompositeAlphaMode :: Opaque ,
312+ view_formats : vec ! [ ] ,
313+ desired_maximum_frame_latency : 2 ,
314+ } ,
315+ ) ;
316+
317+ let surface_texture = surface_inner. get_current_texture ( ) . expect ( "Failed to get surface texture" ) ;
318+
319+ // Blit the rendered texture to the surface
320+ surface. surface . blitter . copy (
321+ & executor. context . device ,
322+ & mut encoder,
323+ & image_texture. texture . create_view ( & vello:: wgpu:: TextureViewDescriptor :: default ( ) ) ,
324+ & surface_texture. texture . create_view ( & vello:: wgpu:: TextureViewDescriptor :: default ( ) ) ,
325+ ) ;
326+
327+ executor. context . queue . submit ( [ encoder. finish ( ) ] ) ;
328+ surface_texture. present ( ) ;
329+
330+ let frame = graphene_std:: application_io:: SurfaceFrame {
331+ surface_id : surface. window_id ,
332+ resolution : logical_resolution,
333+ physical_resolution,
334+ transform : glam:: DAffine2 :: IDENTITY ,
335+ } ;
336+
337+ (
338+ Ok ( TaggedValue :: RenderOutput ( RenderOutput {
339+ data : RenderOutputType :: CanvasFrame ( frame) ,
340+ metadata,
341+ } ) ) ,
342+ None ,
343+ )
344+ }
262345 Ok ( TaggedValue :: RenderOutput ( RenderOutput {
263346 data : RenderOutputType :: Texture ( texture) ,
264347 metadata,
0 commit comments