@@ -52,21 +52,38 @@ impl Canvas for CanvasHandle {
5252}
5353
5454#[ cfg( feature = "wgpu" ) ]
55- pub struct CanvasSurfaceHandle ( CanvasHandle , Option < Arc < wgpu:: Surface < ' static > > > ) ;
55+ struct SurfaceState {
56+ surface : Arc < wgpu:: Surface < ' static > > ,
57+ format : wgpu:: TextureFormat ,
58+ blitter : wgpu_executor:: cached_blitter:: CachedBlitter ,
59+ }
60+
61+ #[ cfg( feature = "wgpu" ) ]
62+ pub struct CanvasSurfaceHandle ( CanvasHandle , Option < SurfaceState > ) ;
5663#[ cfg( feature = "wgpu" ) ]
5764impl CanvasSurfaceHandle {
5865 pub fn new ( ) -> Self {
5966 Self ( CanvasHandle :: new ( ) , None )
6067 }
61- fn surface ( & mut self , executor : & WgpuExecutor ) -> & wgpu :: Surface < ' _ > {
68+ fn state ( & mut self , executor : & WgpuExecutor ) -> & SurfaceState {
6269 if self . 1 . is_none ( ) {
6370 let canvas = self . 0 . get ( ) . canvas . clone ( ) ;
6471 let surface = executor
6572 . context
6673 . instance
6774 . create_surface ( wgpu:: SurfaceTarget :: Canvas ( canvas) )
6875 . expect ( "Failed to create surface from canvas" ) ;
69- self . 1 = Some ( Arc :: new ( surface) ) ;
76+
77+ // Use the surface's preferred format (Firefox WebGL prefers Bgra8Unorm, Chrome prefers Rgba8Unorm)
78+ let surface_caps = surface. get_capabilities ( & executor. context . adapter ) ;
79+ let surface_format = surface_caps. formats . iter ( ) . copied ( ) . find ( |f| f. is_srgb ( ) ) . unwrap_or ( surface_caps. formats [ 0 ] ) ;
80+ let blitter = wgpu_executor:: cached_blitter:: CachedBlitter :: new ( & executor. context . device , surface_format) ;
81+
82+ self . 1 = Some ( SurfaceState {
83+ surface : Arc :: new ( surface) ,
84+ format : surface_format,
85+ blitter,
86+ } ) ;
7087 }
7188 self . 1 . as_ref ( ) . unwrap ( )
7289 }
@@ -87,25 +104,21 @@ impl Canvas for CanvasSurfaceHandle {
87104impl CanvasSurface for CanvasSurfaceHandle {
88105 fn present ( & mut self , image_texture : & ImageTexture , executor : & WgpuExecutor ) {
89106 let source_texture: & wgpu:: Texture = image_texture. as_ref ( ) ;
107+ let state = self . state ( executor) ;
90108
91- let surface = self . surface ( executor) ;
92-
93- // Blit the texture to the surface
94109 let mut encoder = executor. context . device . create_command_encoder ( & wgpu:: CommandEncoderDescriptor {
95110 label : Some ( "Texture to Surface Blit" ) ,
96111 } ) ;
97112
98113 let size = source_texture. size ( ) ;
99114
100- // Configure the surface using the surface's preferred format
101- // (Firefox WebGL prefers Bgra8Unorm, Chrome prefers Rgba8Unorm)
102- let surface_caps = surface. get_capabilities ( & executor. context . adapter ) ;
103- let surface_format = surface_caps. formats . iter ( ) . copied ( ) . find ( |f| f. is_srgb ( ) ) . unwrap_or ( surface_caps. formats [ 0 ] ) ;
104- surface. configure (
115+ // Configure the surface at the detected preferred format
116+ let surface_caps = state. surface . get_capabilities ( & executor. context . adapter ) ;
117+ state. surface . configure (
105118 & executor. context . device ,
106119 & wgpu:: SurfaceConfiguration {
107120 usage : wgpu:: TextureUsages :: RENDER_ATTACHMENT | wgpu:: TextureUsages :: COPY_DST ,
108- format : surface_format ,
121+ format : state . format ,
109122 width : size. width ,
110123 height : size. height ,
111124 present_mode : surface_caps. present_modes [ 0 ] ,
@@ -115,11 +128,11 @@ impl CanvasSurface for CanvasSurfaceHandle {
115128 } ,
116129 ) ;
117130
118- let surface_texture = surface. get_current_texture ( ) . expect ( "Failed to get surface texture" ) ;
131+ let surface_texture = state . surface . get_current_texture ( ) . expect ( "Failed to get surface texture" ) ;
119132
120- // If the surface format matches the source, use a direct copy; otherwise use a shader-based blit
121- // to handle format conversion (e.g., Rgba8Unorm source to Bgra8Unorm surface on Firefox)
122- if surface_format == source_texture. format ( ) {
133+ // If the surface format matches the source, use a direct copy; otherwise use the cached blitter
134+ // for format conversion (e.g., Rgba8Unorm source to Bgra8Unorm surface on Firefox)
135+ if state . format == source_texture. format ( ) {
123136 encoder. copy_texture_to_texture (
124137 wgpu:: TexelCopyTextureInfoBase {
125138 texture : source_texture,
@@ -136,10 +149,8 @@ impl CanvasSurface for CanvasSurfaceHandle {
136149 source_texture. size ( ) ,
137150 ) ;
138151 } else {
139- // Different format (e.g., Firefox's Bgra8Unorm) — use a shader-based blit for format conversion
140- let source_view = source_texture. create_view ( & wgpu:: TextureViewDescriptor :: default ( ) ) ;
141152 let target_view = surface_texture. texture . create_view ( & wgpu:: TextureViewDescriptor :: default ( ) ) ;
142- blit_texture_with_conversion ( & executor. context . device , & executor . context . queue , & mut encoder, & source_view , & target_view, surface_format ) ;
153+ state . blitter . copy ( & executor. context . device , & mut encoder, source_texture , & target_view) ;
143154 }
144155
145156 executor. context . queue . submit ( [ encoder. finish ( ) ] ) ;
@@ -198,145 +209,6 @@ impl CanvasImpl {
198209 }
199210}
200211
201- /// Blit a texture to a render target with format conversion using a fullscreen shader pass.
202- /// Used when the surface format differs from the source (e.g., Rgba8Unorm -> Bgra8Unorm on Firefox).
203- #[ cfg( feature = "wgpu" ) ]
204- fn blit_texture_with_conversion (
205- device : & wgpu:: Device ,
206- _queue : & wgpu:: Queue ,
207- encoder : & mut wgpu:: CommandEncoder ,
208- source : & wgpu:: TextureView ,
209- target : & wgpu:: TextureView ,
210- target_format : wgpu:: TextureFormat ,
211- ) {
212- let shader = device. create_shader_module ( wgpu:: ShaderModuleDescriptor {
213- label : Some ( "Blit Shader" ) ,
214- source : wgpu:: ShaderSource :: Wgsl (
215- r"
216- @group(0) @binding(0) var src: texture_2d<f32>;
217- @group(0) @binding(1) var src_sampler: sampler;
218-
219- struct VertexOutput {
220- @builtin(position) position: vec4<f32>,
221- @location(0) uv: vec2<f32>,
222- }
223-
224- @vertex
225- fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
226- var positions = array<vec2<f32>, 3>(
227- vec2<f32>(-1.0, -1.0),
228- vec2<f32>(3.0, -1.0),
229- vec2<f32>(-1.0, 3.0),
230- );
231- var out: VertexOutput;
232- let pos = positions[vertex_index];
233- out.position = vec4<f32>(pos, 0.0, 1.0);
234- out.uv = vec2<f32>(pos.x * 0.5 + 0.5, 1.0 - (pos.y * 0.5 + 0.5));
235- return out;
236- }
237-
238- @fragment
239- fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
240- return textureSample(src, src_sampler, in.uv);
241- }
242- "
243- . into ( ) ,
244- ) ,
245- } ) ;
246-
247- let bind_group_layout = device. create_bind_group_layout ( & wgpu:: BindGroupLayoutDescriptor {
248- label : Some ( "Blit Bind Group Layout" ) ,
249- entries : & [
250- wgpu:: BindGroupLayoutEntry {
251- binding : 0 ,
252- visibility : wgpu:: ShaderStages :: FRAGMENT ,
253- ty : wgpu:: BindingType :: Texture {
254- sample_type : wgpu:: TextureSampleType :: Float { filterable : true } ,
255- view_dimension : wgpu:: TextureViewDimension :: D2 ,
256- multisampled : false ,
257- } ,
258- count : None ,
259- } ,
260- wgpu:: BindGroupLayoutEntry {
261- binding : 1 ,
262- visibility : wgpu:: ShaderStages :: FRAGMENT ,
263- ty : wgpu:: BindingType :: Sampler ( wgpu:: SamplerBindingType :: Filtering ) ,
264- count : None ,
265- } ,
266- ] ,
267- } ) ;
268-
269- let pipeline_layout = device. create_pipeline_layout ( & wgpu:: PipelineLayoutDescriptor {
270- label : Some ( "Blit Pipeline Layout" ) ,
271- bind_group_layouts : & [ & bind_group_layout] ,
272- push_constant_ranges : & [ ] ,
273- } ) ;
274-
275- let pipeline = device. create_render_pipeline ( & wgpu:: RenderPipelineDescriptor {
276- label : Some ( "Blit Pipeline" ) ,
277- layout : Some ( & pipeline_layout) ,
278- vertex : wgpu:: VertexState {
279- module : & shader,
280- entry_point : Some ( "vs_main" ) ,
281- buffers : & [ ] ,
282- compilation_options : Default :: default ( ) ,
283- } ,
284- fragment : Some ( wgpu:: FragmentState {
285- module : & shader,
286- entry_point : Some ( "fs_main" ) ,
287- targets : & [ Some ( wgpu:: ColorTargetState {
288- format : target_format,
289- blend : None ,
290- write_mask : wgpu:: ColorWrites :: ALL ,
291- } ) ] ,
292- compilation_options : Default :: default ( ) ,
293- } ) ,
294- primitive : wgpu:: PrimitiveState :: default ( ) ,
295- depth_stencil : None ,
296- multisample : wgpu:: MultisampleState :: default ( ) ,
297- multiview : None ,
298- cache : None ,
299- } ) ;
300-
301- let sampler = device. create_sampler ( & wgpu:: SamplerDescriptor {
302- mag_filter : wgpu:: FilterMode :: Nearest ,
303- min_filter : wgpu:: FilterMode :: Nearest ,
304- ..Default :: default ( )
305- } ) ;
306-
307- let bind_group = device. create_bind_group ( & wgpu:: BindGroupDescriptor {
308- label : Some ( "Blit Bind Group" ) ,
309- layout : & bind_group_layout,
310- entries : & [
311- wgpu:: BindGroupEntry {
312- binding : 0 ,
313- resource : wgpu:: BindingResource :: TextureView ( source) ,
314- } ,
315- wgpu:: BindGroupEntry {
316- binding : 1 ,
317- resource : wgpu:: BindingResource :: Sampler ( & sampler) ,
318- } ,
319- ] ,
320- } ) ;
321-
322- let mut render_pass = encoder. begin_render_pass ( & wgpu:: RenderPassDescriptor {
323- label : Some ( "Blit Render Pass" ) ,
324- color_attachments : & [ Some ( wgpu:: RenderPassColorAttachment {
325- view : target,
326- resolve_target : None ,
327- ops : wgpu:: Operations {
328- load : wgpu:: LoadOp :: Clear ( wgpu:: Color :: BLACK ) ,
329- store : wgpu:: StoreOp :: Store ,
330- } ,
331- } ) ] ,
332- ..Default :: default ( )
333- } ) ;
334-
335- render_pass. set_pipeline ( & pipeline) ;
336- render_pass. set_bind_group ( 0 , & bind_group, & [ ] ) ;
337- render_pass. draw ( 0 ..3 , 0 ..1 ) ;
338- }
339-
340212impl Drop for CanvasImpl {
341213 fn drop ( & mut self ) {
342214 let canvas_id = self . canvas_id ;
0 commit comments