1use crate::binding::BindingResource;
13use crate::buffer::BufferRef;
14use crate::buffer::{CpuBuffer, GpuData};
15use crate::texture::TextureSource;
16use crate::uniforms::WgslStruct;
17use std::fmt::Write;
18use wgpu::ShaderStages;
19
20type WgslStructGenerator = fn(&str) -> String;
21
22pub enum WgslStructName {
24 Generator(WgslStructGenerator),
26 Name(String),
28}
29
30pub enum BindingDesc {
39 Buffer {
41 ty: wgpu::BufferBindingType,
42 has_dynamic_offset: bool,
43 min_binding_size: Option<std::num::NonZeroU64>,
44 },
45 Texture {
52 sample_type: wgpu::TextureSampleType,
53 view_dimension: wgpu::TextureViewDimension,
54 sampler_binding_type: wgpu::SamplerBindingType,
56 },
57}
58
59pub struct Binding<'a> {
64 pub resource: BindingResource<'a>,
66 pub visibility: ShaderStages,
68 pub name: &'a str,
70 pub struct_name: Option<WgslStructName>,
72 pub desc: BindingDesc,
74}
75
76pub struct ResourceBuilder<'a> {
87 pub bindings: Vec<Binding<'a>>,
88}
89
90impl Default for ResourceBuilder<'_> {
91 fn default() -> Self {
92 Self::new()
93 }
94}
95
96impl<'a> ResourceBuilder<'a> {
97 #[must_use]
98 pub fn new() -> Self {
99 Self {
100 bindings: Vec::new(),
101 }
102 }
103
104 pub fn add_uniform_buffer(
105 &mut self,
106 name: &'a str,
107 buffer: &BufferRef,
108 data: Option<&'a [u8]>,
109 visibility: ShaderStages,
110 has_dynamic_offset: bool,
111 min_binding_size: Option<std::num::NonZeroU64>,
112 struct_name: Option<WgslStructName>,
113 ) {
114 self.bindings.push(Binding {
115 resource: BindingResource::Buffer {
116 buffer: buffer.clone(),
117 offset: 0,
118 size: min_binding_size.as_ref().map(|s| s.get()),
119 data,
120 },
121 visibility,
122 name,
123 struct_name,
124 desc: BindingDesc::Buffer {
125 ty: wgpu::BufferBindingType::Uniform,
126 has_dynamic_offset,
127 min_binding_size,
128 },
129 });
130 }
131
132 pub fn add_uniform<T: WgslStruct + GpuData>(
133 &mut self,
134 name: &'a str,
135 cpu_buffer: &'a CpuBuffer<T>,
136 visibility: ShaderStages,
137 ) {
138 self.add_uniform_buffer(
139 name,
140 &cpu_buffer.handle(),
141 None,
142 visibility,
143 false,
144 None,
145 Some(WgslStructName::Generator(T::wgsl_struct_def)),
146 );
147 }
148
149 pub fn add_dynamic_uniform<T: WgslStruct>(
150 &mut self,
151 name: &'a str,
152 buffer_ref: &BufferRef,
153 data: Option<&'a [u8]>,
154 min_binding_size: std::num::NonZeroU64,
155 visibility: ShaderStages,
156 ) {
157 self.add_uniform_buffer(
158 name,
159 buffer_ref,
160 data,
161 visibility,
162 true,
163 Some(min_binding_size),
164 Some(WgslStructName::Generator(T::wgsl_struct_def)),
165 );
166 }
167
168 pub fn add_texture(
175 &mut self,
176 name: &'a str,
177 source: Option<impl Into<TextureSource>>,
178 sample_type: wgpu::TextureSampleType,
179 view_dimension: wgpu::TextureViewDimension,
180 visibility: ShaderStages,
181 ) {
182 let sampler_binding_type = if matches!(sample_type, wgpu::TextureSampleType::Depth) {
183 wgpu::SamplerBindingType::Comparison
184 } else {
185 wgpu::SamplerBindingType::Filtering
186 };
187 self.bindings.push(Binding {
188 resource: BindingResource::Texture(source.map(std::convert::Into::into)),
189 visibility,
190 name,
191 struct_name: None,
192 desc: BindingDesc::Texture {
193 sample_type,
194 view_dimension,
195 sampler_binding_type,
196 },
197 });
198 }
199
200 pub fn add_storage_buffer(
201 &mut self,
202 name: &'a str,
203 buffer: &BufferRef,
204 data: Option<&'a [u8]>,
205 read_only: bool,
206 visibility: ShaderStages,
207 struct_name: Option<WgslStructName>,
208 ) {
209 self.bindings.push(Binding {
210 resource: BindingResource::Buffer {
211 buffer: buffer.clone(),
212 offset: 0,
213 size: None,
214 data,
215 },
216 visibility,
217 name,
218 struct_name,
219 desc: BindingDesc::Buffer {
220 ty: wgpu::BufferBindingType::Storage { read_only },
221 has_dynamic_offset: false,
222 min_binding_size: None,
223 },
224 });
225 }
226
227 pub fn add_storage<T: WgslStruct>(
228 &mut self,
229 name: &'a str,
230 buffer: &BufferRef,
231 data: Option<&'a [u8]>,
232 read_only: bool,
233 visibility: ShaderStages,
234 ) {
235 self.add_storage_buffer(
236 name,
237 buffer,
238 data,
239 read_only,
240 visibility,
241 Some(WgslStructName::Generator(T::wgsl_struct_def)),
242 );
243 }
244
245 #[must_use]
250 pub fn generate_layout_entries(&self) -> Vec<wgpu::BindGroupLayoutEntry> {
251 let capacity = self
253 .bindings
254 .iter()
255 .map(|b| match &b.desc {
256 BindingDesc::Buffer { .. } => 1,
257 BindingDesc::Texture { .. } => 2,
258 })
259 .sum();
260 let mut entries = Vec::with_capacity(capacity);
261 let mut idx = 0u32;
262 for b in &self.bindings {
263 match &b.desc {
264 BindingDesc::Buffer {
265 ty,
266 has_dynamic_offset,
267 min_binding_size,
268 } => {
269 entries.push(wgpu::BindGroupLayoutEntry {
270 binding: idx,
271 visibility: b.visibility,
272 ty: wgpu::BindingType::Buffer {
273 ty: *ty,
274 has_dynamic_offset: *has_dynamic_offset,
275 min_binding_size: *min_binding_size,
276 },
277 count: None,
278 });
279 idx += 1;
280 }
281 BindingDesc::Texture {
282 sample_type,
283 view_dimension,
284 sampler_binding_type,
285 } => {
286 entries.push(wgpu::BindGroupLayoutEntry {
287 binding: idx,
288 visibility: b.visibility,
289 ty: wgpu::BindingType::Texture {
290 sample_type: *sample_type,
291 view_dimension: *view_dimension,
292 multisampled: false,
293 },
294 count: None,
295 });
296 idx += 1;
297 entries.push(wgpu::BindGroupLayoutEntry {
298 binding: idx,
299 visibility: b.visibility,
300 ty: wgpu::BindingType::Sampler(*sampler_binding_type),
301 count: None,
302 });
303 idx += 1;
304 }
305 }
306 }
307 entries
308 }
309
310 #[must_use]
316 pub fn generate_wgsl(&self, group_index: u32) -> String {
317 let mut bindings_code = String::new();
318 let mut struct_defs = String::new();
319 let mut idx = 0u32;
320
321 for b in &self.bindings {
322 let name = b.name;
323
324 let struct_type_name = if let Some(generator) = &b.struct_name {
325 let sn = match generator {
326 WgslStructName::Generator(g) => {
327 let auto = format!("Struct_{name}");
328 writeln!(struct_defs, "{}", g(&auto)).unwrap();
329 auto
330 }
331 WgslStructName::Name(n) => n.clone(),
332 };
333 Some(sn)
334 } else {
335 None
336 };
337
338 match &b.desc {
339 BindingDesc::Buffer { ty, .. } => {
340 match ty {
341 wgpu::BufferBindingType::Uniform => {
342 writeln!(
343 bindings_code,
344 "@group({group_index}) @binding({idx}) var<uniform> u_{name}: {};",
345 struct_type_name
346 .as_deref()
347 .expect("buffer binding needs a struct name")
348 )
349 .unwrap();
350 }
351 wgpu::BufferBindingType::Storage { read_only } => {
352 let access = if *read_only { "read" } else { "read_write" };
353 let stn = struct_type_name
354 .as_deref()
355 .expect("storage binding needs a struct name");
356 writeln!(
357 bindings_code,
358 "@group({group_index}) @binding({idx}) var<storage, {access}> st_{name}: array<{stn}>;"
359 ).unwrap();
360 }
361 }
362 idx += 1;
363 }
364 BindingDesc::Texture {
365 sample_type,
366 view_dimension,
367 sampler_binding_type,
368 } => {
369 let type_str = match (view_dimension, sample_type) {
371 (wgpu::TextureViewDimension::D2, wgpu::TextureSampleType::Depth) => {
372 "texture_depth_2d"
373 }
374 (wgpu::TextureViewDimension::D2Array, wgpu::TextureSampleType::Depth) => {
375 "texture_depth_2d_array"
376 }
377 (
378 wgpu::TextureViewDimension::Cube,
379 wgpu::TextureSampleType::Float { .. },
380 ) => "texture_cube<f32>",
381 (
382 wgpu::TextureViewDimension::D2Array,
383 wgpu::TextureSampleType::Float { .. },
384 ) => "texture_2d_array<f32>",
385 _ => "texture_2d<f32>",
386 };
387 writeln!(
388 bindings_code,
389 "@group({group_index}) @binding({idx}) var t_{name}: {type_str};"
390 )
391 .unwrap();
392 idx += 1;
393
394 let sampler_type_str = match sampler_binding_type {
396 wgpu::SamplerBindingType::Comparison => "sampler_comparison",
397 _ => "sampler",
398 };
399 writeln!(
400 bindings_code,
401 "@group({group_index}) @binding({idx}) var s_{name}: {sampler_type_str};"
402 )
403 .unwrap();
404 idx += 1;
405 }
406 }
407 }
408 format!(
409 "// --- Auto Generated Bindings (Group {group_index}) ---\n{struct_defs}\n{bindings_code}\n"
410 )
411 }
412}