Skip to content

Commit b644004

Browse files
committed
Merge branch 'master' into feat-allign-buttons-on-path-tool
1 parent 08c181f commit b644004

2 files changed

Lines changed: 31 additions & 158 deletions

File tree

  • node-graph/libraries

node-graph/libraries/canvas-utils/src/wasm.rs

Lines changed: 30 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -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")]
5764
impl 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 {
87104
impl 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-
340212
impl Drop for CanvasImpl {
341213
fn drop(&mut self) {
342214
let canvas_id = self.canvas_id;

node-graph/libraries/wgpu-executor/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
pub mod cached_blitter;
12
mod context;
23
mod resample;
34
pub mod shader_runtime;

0 commit comments

Comments
 (0)