Skip to content

Commit 0399d1a

Browse files
committed
GPU: add support for N-many input images and not just exactly one
1 parent d0d9a35 commit 0399d1a

3 files changed

Lines changed: 91 additions & 74 deletions

File tree

node-graph/libraries/wgpu-executor/src/shader_runtime/per_pixel_adjust_runtime.rs

Lines changed: 79 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ impl PerPixelAdjustShaderRuntime {
3333
}
3434

3535
impl ShaderRuntime {
36-
pub async fn run_per_pixel_adjust<T: BufferStruct>(&self, shaders: &Shaders<'_>, textures: List<Raster<GPU>>, args: Option<&T>) -> List<Raster<GPU>> {
36+
pub async fn run_per_pixel_adjust<T: BufferStruct>(&self, shaders: &Shaders<'_>, textures: &[List<Raster<GPU>>], args: Option<&T>) -> List<Raster<GPU>> {
37+
assert_eq!(shaders.input_images, textures.len());
3738
let mut cache = self.per_pixel_adjust.pipeline_cache.lock().await;
3839
let pipeline = cache
3940
.entry(shaders.fragment_shader_name.to_owned())
@@ -54,11 +55,13 @@ impl ShaderRuntime {
5455
pub struct Shaders<'a> {
5556
pub wgsl_shader: &'a str,
5657
pub fragment_shader_name: &'a str,
58+
pub input_images: usize,
5759
pub has_uniform: bool,
5860
}
5961

6062
pub struct PerPixelAdjustGraphicsPipeline {
6163
name: String,
64+
input_images: usize,
6265
has_uniform: bool,
6366
pipeline: wgpu::RenderPipeline,
6467
}
@@ -76,46 +79,37 @@ impl PerPixelAdjustGraphicsPipeline {
7679
source: ShaderSource::Wgsl(Cow::Borrowed(info.wgsl_shader)),
7780
});
7881

79-
let entries: &[_] = if info.has_uniform {
80-
&[
81-
BindGroupLayoutEntry {
82-
binding: 0,
83-
visibility: ShaderStages::FRAGMENT,
84-
ty: BindingType::Buffer {
85-
ty: BufferBindingType::Storage { read_only: true },
86-
has_dynamic_offset: false,
87-
min_binding_size: None,
88-
},
89-
count: None,
90-
},
91-
BindGroupLayoutEntry {
92-
binding: 1,
93-
visibility: ShaderStages::FRAGMENT,
94-
ty: BindingType::Texture {
95-
sample_type: TextureSampleType::Float { filterable: false },
96-
view_dimension: TextureViewDimension::D2,
97-
multisampled: false,
98-
},
99-
count: None,
82+
let mut binding_alloc = Counter::default();
83+
let mut entries = Vec::new();
84+
if info.has_uniform {
85+
entries.push(BindGroupLayoutEntry {
86+
binding: binding_alloc.alloc(),
87+
visibility: ShaderStages::FRAGMENT,
88+
ty: BindingType::Buffer {
89+
ty: BufferBindingType::Storage { read_only: true },
90+
has_dynamic_offset: false,
91+
min_binding_size: None,
10092
},
101-
]
102-
} else {
103-
&[BindGroupLayoutEntry {
104-
binding: 0,
93+
count: None,
94+
});
95+
}
96+
for _ in 0..info.input_images {
97+
entries.push(BindGroupLayoutEntry {
98+
binding: binding_alloc.alloc(),
10599
visibility: ShaderStages::FRAGMENT,
106100
ty: BindingType::Texture {
107101
sample_type: TextureSampleType::Float { filterable: false },
108102
view_dimension: TextureViewDimension::D2,
109103
multisampled: false,
110104
},
111105
count: None,
112-
}]
113-
};
106+
});
107+
}
114108
let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
115109
label: Some(&format!("PerPixelAdjust {name} PipelineLayout")),
116110
bind_group_layouts: &[Some(&device.create_bind_group_layout(&BindGroupLayoutDescriptor {
117111
label: Some(&format!("PerPixelAdjust {name} BindGroupLayout 0")),
118-
entries,
112+
entries: &entries,
119113
}))],
120114
..Default::default()
121115
});
@@ -157,61 +151,73 @@ impl PerPixelAdjustGraphicsPipeline {
157151
pipeline,
158152
name,
159153
has_uniform: info.has_uniform,
154+
input_images: info.input_images,
160155
}
161156
}
162157

163-
pub fn dispatch(&self, context: &WgpuContext, textures: List<Raster<GPU>>, arg_buffer: Option<Buffer>) -> List<Raster<GPU>> {
158+
pub fn dispatch(&self, context: &WgpuContext, in_textures: &[List<Raster<GPU>>], arg_buffer: Option<Buffer>) -> List<Raster<GPU>> {
164159
assert_eq!(self.has_uniform, arg_buffer.is_some());
160+
assert_eq!(self.input_images, in_textures.len());
165161
let device = &context.device;
166162
let name = self.name.as_str();
167163

164+
// Assumption: when we have multiple input images to our node, each input's List of images can have a different
165+
// length. Only process the minimum between all input images, same as `impl Blend<Color> for List<Raster<CPU>>`.
166+
let dispatch_cnt = match in_textures.iter().map(|t| t.len()).min() {
167+
None => {
168+
return List::new();
169+
}
170+
Some(e) => e,
171+
};
172+
168173
let mut cmd = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
169174
label: Some(&format!("{name} cmd encoder")),
170175
});
171-
let out = (0..textures.len())
172-
.map(|index| {
173-
let element = textures.element(index).unwrap();
174-
let tex_in = &element.texture;
175-
let view_in = tex_in.create_view(&TextureViewDescriptor::default());
176-
let format = tex_in.format();
177-
178-
let entries: &[_] = if let Some(arg_buffer) = arg_buffer.as_ref() {
179-
&[
180-
BindGroupEntry {
181-
binding: 0,
182-
resource: BindingResource::Buffer(BufferBinding {
183-
buffer: arg_buffer,
184-
offset: 0,
185-
size: None,
186-
}),
187-
},
188-
BindGroupEntry {
189-
binding: 1,
190-
resource: BindingResource::TextureView(&view_in),
191-
},
192-
]
193-
} else {
194-
&[BindGroupEntry {
195-
binding: 0,
176+
let out = (0..dispatch_cnt)
177+
.map(|dispatch_id| {
178+
let mut binding_alloc = Counter::default();
179+
let mut entries = Vec::new();
180+
if let Some(arg_buffer) = arg_buffer.as_ref() {
181+
entries.push(BindGroupEntry {
182+
binding: binding_alloc.alloc(),
183+
resource: BindingResource::Buffer(BufferBinding {
184+
buffer: arg_buffer,
185+
offset: 0,
186+
size: None,
187+
}),
188+
});
189+
}
190+
let in_texture_views = in_textures.iter().map(|texture| {
191+
let element = texture.element(dispatch_id).unwrap();
192+
element.texture.create_view(&TextureViewDescriptor::default())
193+
}).collect::<Vec<_>>();
194+
for view_in in &in_texture_views {
195+
entries.push(BindGroupEntry {
196+
binding: binding_alloc.alloc(),
196197
resource: BindingResource::TextureView(&view_in),
197-
}]
198-
};
198+
});
199+
}
200+
199201
let bind_group = device.create_bind_group(&BindGroupDescriptor {
200202
label: Some(&format!("{name} bind group")),
201203
// `get_bind_group_layout` allocates unnecessary memory, we could create it manually to not do that
202204
layout: &self.pipeline.get_bind_group_layout(0),
203-
entries,
205+
entries: &entries,
204206
});
205207

208+
// Assumption: The output texture has the same size and format as the first input texture. Like the
209+
// blend node, that writes the output directly back into the first texture.
210+
let outref_list = &in_textures[0];
211+
let outref_tex = &outref_list.element(dispatch_id).unwrap().texture;
206212
let tex_out = device.create_texture(&TextureDescriptor {
207213
label: Some(&format!("{name} texture out")),
208-
size: tex_in.size(),
214+
size: outref_tex.size(),
209215
mip_level_count: 1,
210216
sample_count: 1,
211217
dimension: TextureDimension::D2,
212-
format,
218+
format: outref_tex.format(),
213219
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::RENDER_ATTACHMENT,
214-
view_formats: &[format],
220+
view_formats: &[outref_tex.format()],
215221
});
216222

217223
let view_out = tex_out.create_view(&TextureViewDescriptor::default());
@@ -233,11 +239,22 @@ impl PerPixelAdjustGraphicsPipeline {
233239
rp.set_bind_group(0, Some(&bind_group), &[]);
234240
rp.draw(0..3, 0..1);
235241

236-
let attributes = textures.clone_item_attributes(index);
242+
let attributes = outref_list.clone_item_attributes(dispatch_id);
237243
Item::from_parts(Raster::new(GPU { texture: tex_out }), attributes)
238244
})
239245
.collect::<List<_>>();
240246
context.queue.submit([cmd.finish()]);
241247
out
242248
}
243249
}
250+
251+
#[derive(Clone, Debug, Default)]
252+
pub struct Counter(pub u32);
253+
254+
impl Counter {
255+
pub fn alloc(&mut self) -> u32 {
256+
let out = self.0;
257+
self.0 += 1;
258+
out
259+
}
260+
}

node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -248,16 +248,15 @@ impl PerPixelAdjustCodegen<'_> {
248248
is_data_field: false,
249249
});
250250

251-
// find exactly one gpu_image field, runtime doesn't support more than 1 atm
252-
let gpu_image_field = {
253-
let mut iter = fields.iter().filter(|f| matches!(f.ty, ParsedFieldType::Regular(RegularParsedField { gpu_image: true, .. })));
254-
match (iter.next(), iter.next()) {
255-
(Some(v), None) => Ok(v),
256-
(Some(_), Some(more)) => Err(syn::Error::new_spanned(&more.pat_ident, "No more than one parameter must be annotated with `#[gpu_image]`")),
257-
(None, _) => Err(syn::Error::new_spanned(&self.parsed.fn_name, "At least one parameter must be annotated with `#[gpu_image]`")),
258-
}?
259-
};
260-
let gpu_image = &gpu_image_field.pat_ident.ident;
251+
// find gpu_image fields
252+
let gpu_images = fields
253+
.iter()
254+
.filter_map(|f| match f.ty {
255+
ParsedFieldType::Regular(RegularParsedField { gpu_image: true, .. }) => Some(&f.pat_ident.ident),
256+
_ => None,
257+
})
258+
.collect::<Vec<_>>();
259+
let input_images = gpu_images.len();
261260

262261
// uniform buffer struct construction
263262
let has_uniform = self.has_uniform;
@@ -287,7 +286,8 @@ impl PerPixelAdjustCodegen<'_> {
287286
wgsl_shader: crate::WGSL_SHADER,
288287
fragment_shader_name: super::#entry_point_name,
289288
has_uniform: #has_uniform,
290-
}, #gpu_image, #uniform_buffer).await
289+
input_images: #input_images,
290+
}, &[#(#gpu_images),*], #uniform_buffer).await
291291
}
292292
};
293293

node-graph/nodes/raster/src/blending_nodes.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ pub fn apply_blend_mode(foreground: Color, background: Color, blend_mode: BlendM
141141
}
142142
}
143143

144-
#[node_macro::node(category("Raster"), cfg(feature = "std"))]
144+
#[node_macro::node(category("Raster"), shader_node(PerPixelAdjust))]
145145
fn mix<T: Blend<Color> + Send>(
146146
_: impl Ctx,
147147
#[implementations(

0 commit comments

Comments
 (0)