Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_render/macros/src/as_bind_group.rs
6596 views
1
use bevy_macro_utils::{get_lit_bool, get_lit_str, BevyManifest, Symbol};
2
use proc_macro::TokenStream;
3
use proc_macro2::{Ident, Span};
4
use quote::{quote, ToTokens};
5
use syn::{
6
parenthesized,
7
parse::{Parse, ParseStream},
8
punctuated::Punctuated,
9
token::{Comma, DotDot},
10
Data, DataStruct, Error, Fields, LitInt, LitStr, Meta, MetaList, Result,
11
};
12
13
const UNIFORM_ATTRIBUTE_NAME: Symbol = Symbol("uniform");
14
const TEXTURE_ATTRIBUTE_NAME: Symbol = Symbol("texture");
15
const STORAGE_TEXTURE_ATTRIBUTE_NAME: Symbol = Symbol("storage_texture");
16
const SAMPLER_ATTRIBUTE_NAME: Symbol = Symbol("sampler");
17
const STORAGE_ATTRIBUTE_NAME: Symbol = Symbol("storage");
18
const BIND_GROUP_DATA_ATTRIBUTE_NAME: Symbol = Symbol("bind_group_data");
19
const BINDLESS_ATTRIBUTE_NAME: Symbol = Symbol("bindless");
20
const DATA_ATTRIBUTE_NAME: Symbol = Symbol("data");
21
const BINDING_ARRAY_MODIFIER_NAME: Symbol = Symbol("binding_array");
22
const LIMIT_MODIFIER_NAME: Symbol = Symbol("limit");
23
const INDEX_TABLE_MODIFIER_NAME: Symbol = Symbol("index_table");
24
const RANGE_MODIFIER_NAME: Symbol = Symbol("range");
25
const BINDING_MODIFIER_NAME: Symbol = Symbol("binding");
26
27
#[derive(Copy, Clone, Debug)]
28
enum BindingType {
29
Uniform,
30
Texture,
31
StorageTexture,
32
Sampler,
33
Storage,
34
}
35
36
#[derive(Clone)]
37
enum BindingState<'a> {
38
Free,
39
Occupied {
40
binding_type: BindingType,
41
ident: &'a Ident,
42
},
43
OccupiedConvertedUniform,
44
OccupiedMergeableUniform {
45
uniform_fields: Vec<&'a syn::Field>,
46
},
47
}
48
49
enum BindlessSlabResourceLimitAttr {
50
Auto,
51
Limit(LitInt),
52
}
53
54
// The `bindless(index_table(range(M..N)))` attribute.
55
struct BindlessIndexTableRangeAttr {
56
start: LitInt,
57
end: LitInt,
58
}
59
60
pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
61
let manifest = BevyManifest::shared();
62
let render_path = manifest.get_path("bevy_render");
63
let image_path = manifest.get_path("bevy_image");
64
let asset_path = manifest.get_path("bevy_asset");
65
let ecs_path = manifest.get_path("bevy_ecs");
66
67
let mut binding_states: Vec<BindingState> = Vec::new();
68
let mut binding_impls = Vec::new();
69
let mut bindless_binding_layouts = Vec::new();
70
let mut non_bindless_binding_layouts = Vec::new();
71
let mut bindless_resource_types = Vec::new();
72
let mut bindless_buffer_descriptors = Vec::new();
73
let mut attr_prepared_data_ident = None;
74
// After the first attribute pass, this will be `None` if the object isn't
75
// bindless and `Some` if it is.
76
let mut attr_bindless_count = None;
77
let mut attr_bindless_index_table_range = None;
78
let mut attr_bindless_index_table_binding = None;
79
80
// `actual_bindless_slot_count` holds the actual number of bindless slots
81
// per bind group, taking into account whether the current platform supports
82
// bindless resources.
83
let actual_bindless_slot_count = Ident::new("actual_bindless_slot_count", Span::call_site());
84
let bind_group_layout_entries = Ident::new("bind_group_layout_entries", Span::call_site());
85
86
// The `BufferBindingType` and corresponding `BufferUsages` used for
87
// uniforms. We need this because bindless uniforms don't exist, so in
88
// bindless mode we must promote uniforms to storage buffers.
89
let uniform_binding_type = Ident::new("uniform_binding_type", Span::call_site());
90
let uniform_buffer_usages = Ident::new("uniform_buffer_usages", Span::call_site());
91
92
// Read struct-level attributes, first pass.
93
for attr in &ast.attrs {
94
if let Some(attr_ident) = attr.path().get_ident() {
95
if attr_ident == BIND_GROUP_DATA_ATTRIBUTE_NAME {
96
if let Ok(prepared_data_ident) =
97
attr.parse_args_with(|input: ParseStream| input.parse::<Ident>())
98
{
99
attr_prepared_data_ident = Some(prepared_data_ident);
100
}
101
} else if attr_ident == BINDLESS_ATTRIBUTE_NAME {
102
attr_bindless_count = Some(BindlessSlabResourceLimitAttr::Auto);
103
if let Meta::List(_) = attr.meta {
104
// Parse bindless features.
105
attr.parse_nested_meta(|submeta| {
106
if submeta.path.is_ident(&LIMIT_MODIFIER_NAME) {
107
let content;
108
parenthesized!(content in submeta.input);
109
let lit: LitInt = content.parse()?;
110
111
attr_bindless_count = Some(BindlessSlabResourceLimitAttr::Limit(lit));
112
return Ok(());
113
}
114
115
if submeta.path.is_ident(&INDEX_TABLE_MODIFIER_NAME) {
116
submeta.parse_nested_meta(|subsubmeta| {
117
if subsubmeta.path.is_ident(&RANGE_MODIFIER_NAME) {
118
let content;
119
parenthesized!(content in subsubmeta.input);
120
let start: LitInt = content.parse()?;
121
content.parse::<DotDot>()?;
122
let end: LitInt = content.parse()?;
123
attr_bindless_index_table_range =
124
Some(BindlessIndexTableRangeAttr { start, end });
125
return Ok(());
126
}
127
128
if subsubmeta.path.is_ident(&BINDING_MODIFIER_NAME) {
129
let content;
130
parenthesized!(content in subsubmeta.input);
131
let lit: LitInt = content.parse()?;
132
133
attr_bindless_index_table_binding = Some(lit);
134
return Ok(());
135
}
136
137
Err(Error::new_spanned(
138
attr,
139
"Expected `range(M..N)` or `binding(N)`",
140
))
141
})?;
142
return Ok(());
143
}
144
145
Err(Error::new_spanned(
146
attr,
147
"Expected `limit` or `index_table`",
148
))
149
})?;
150
}
151
}
152
}
153
}
154
155
// Read struct-level attributes, second pass.
156
for attr in &ast.attrs {
157
if let Some(attr_ident) = attr.path().get_ident()
158
&& (attr_ident == UNIFORM_ATTRIBUTE_NAME || attr_ident == DATA_ATTRIBUTE_NAME)
159
{
160
let UniformBindingAttr {
161
binding_type,
162
binding_index,
163
converted_shader_type,
164
binding_array: binding_array_binding,
165
} = get_uniform_binding_attr(attr)?;
166
match binding_type {
167
UniformBindingAttrType::Uniform => {
168
binding_impls.push(quote! {{
169
use #render_path::render_resource::AsBindGroupShaderType;
170
let mut buffer = #render_path::render_resource::encase::UniformBuffer::new(Vec::new());
171
let converted: #converted_shader_type = self.as_bind_group_shader_type(&images);
172
buffer.write(&converted).unwrap();
173
(
174
#binding_index,
175
#render_path::render_resource::OwnedBindingResource::Buffer(render_device.create_buffer_with_data(
176
&#render_path::render_resource::BufferInitDescriptor {
177
label: None,
178
usage: #uniform_buffer_usages,
179
contents: buffer.as_ref(),
180
},
181
))
182
)
183
}});
184
185
match (&binding_array_binding, &attr_bindless_count) {
186
(&None, &Some(_)) => {
187
return Err(Error::new_spanned(
188
attr,
189
"Must specify `binding_array(...)` with `#[uniform]` if the \
190
object is bindless",
191
));
192
}
193
(&Some(_), &None) => {
194
return Err(Error::new_spanned(
195
attr,
196
"`binding_array(...)` with `#[uniform]` requires the object to \
197
be bindless",
198
));
199
}
200
_ => {}
201
}
202
203
let binding_array_binding = binding_array_binding.unwrap_or(0);
204
bindless_binding_layouts.push(quote! {
205
#bind_group_layout_entries.push(
206
#render_path::render_resource::BindGroupLayoutEntry {
207
binding: #binding_array_binding,
208
visibility: #render_path::render_resource::ShaderStages::FRAGMENT | #render_path::render_resource::ShaderStages::VERTEX | #render_path::render_resource::ShaderStages::COMPUTE,
209
ty: #render_path::render_resource::BindingType::Buffer {
210
ty: #uniform_binding_type,
211
has_dynamic_offset: false,
212
min_binding_size: Some(<#converted_shader_type as #render_path::render_resource::ShaderType>::min_size()),
213
},
214
count: #actual_bindless_slot_count,
215
}
216
);
217
});
218
219
add_bindless_resource_type(
220
&render_path,
221
&mut bindless_resource_types,
222
binding_index,
223
quote! { #render_path::render_resource::BindlessResourceType::Buffer },
224
);
225
}
226
227
UniformBindingAttrType::Data => {
228
binding_impls.push(quote! {{
229
use #render_path::render_resource::AsBindGroupShaderType;
230
use #render_path::render_resource::encase::{ShaderType, internal::WriteInto};
231
let mut buffer: Vec<u8> = Vec::new();
232
let converted: #converted_shader_type = self.as_bind_group_shader_type(&images);
233
converted.write_into(
234
&mut #render_path::render_resource::encase::internal::Writer::new(
235
&converted,
236
&mut buffer,
237
0,
238
).unwrap(),
239
);
240
let min_size = <#converted_shader_type as #render_path::render_resource::ShaderType>::min_size().get() as usize;
241
while buffer.len() < min_size {
242
buffer.push(0);
243
}
244
(
245
#binding_index,
246
#render_path::render_resource::OwnedBindingResource::Data(
247
#render_path::render_resource::OwnedData(buffer)
248
)
249
)
250
}});
251
252
let binding_array_binding = binding_array_binding.unwrap_or(0);
253
bindless_binding_layouts.push(quote! {
254
#bind_group_layout_entries.push(
255
#render_path::render_resource::BindGroupLayoutEntry {
256
binding: #binding_array_binding,
257
visibility: #render_path::render_resource::ShaderStages::FRAGMENT | #render_path::render_resource::ShaderStages::VERTEX | #render_path::render_resource::ShaderStages::COMPUTE,
258
ty: #render_path::render_resource::BindingType::Buffer {
259
ty: #uniform_binding_type,
260
has_dynamic_offset: false,
261
min_binding_size: Some(<#converted_shader_type as #render_path::render_resource::ShaderType>::min_size()),
262
},
263
count: None,
264
}
265
);
266
});
267
268
add_bindless_resource_type(
269
&render_path,
270
&mut bindless_resource_types,
271
binding_index,
272
quote! { #render_path::render_resource::BindlessResourceType::DataBuffer },
273
);
274
}
275
}
276
277
// Push the non-bindless binding layout.
278
279
non_bindless_binding_layouts.push(quote!{
280
#bind_group_layout_entries.push(
281
#render_path::render_resource::BindGroupLayoutEntry {
282
binding: #binding_index,
283
visibility: #render_path::render_resource::ShaderStages::FRAGMENT | #render_path::render_resource::ShaderStages::VERTEX | #render_path::render_resource::ShaderStages::COMPUTE,
284
ty: #render_path::render_resource::BindingType::Buffer {
285
ty: #uniform_binding_type,
286
has_dynamic_offset: false,
287
min_binding_size: Some(<#converted_shader_type as #render_path::render_resource::ShaderType>::min_size()),
288
},
289
count: None,
290
}
291
);
292
});
293
294
bindless_buffer_descriptors.push(quote! {
295
#render_path::render_resource::BindlessBufferDescriptor {
296
// Note that, because this is bindless, *binding
297
// index* here refers to the index in the
298
// bindless index table (`bindless_index`), and
299
// the actual binding number is the *binding
300
// array binding*.
301
binding_number: #render_path::render_resource::BindingNumber(
302
#binding_array_binding
303
),
304
bindless_index:
305
#render_path::render_resource::BindlessIndex(#binding_index),
306
size: Some(
307
<
308
#converted_shader_type as
309
#render_path::render_resource::ShaderType
310
>::min_size().get() as usize
311
),
312
}
313
});
314
315
let required_len = binding_index as usize + 1;
316
if required_len > binding_states.len() {
317
binding_states.resize(required_len, BindingState::Free);
318
}
319
binding_states[binding_index as usize] = BindingState::OccupiedConvertedUniform;
320
}
321
}
322
323
let fields = match &ast.data {
324
Data::Struct(DataStruct {
325
fields: Fields::Named(fields),
326
..
327
}) => &fields.named,
328
_ => {
329
return Err(Error::new_spanned(
330
ast,
331
"Expected a struct with named fields",
332
));
333
}
334
};
335
336
// Count the number of sampler fields needed. We might have to disable
337
// bindless if bindless arrays take the GPU over the maximum number of
338
// samplers.
339
let mut sampler_binding_count: u32 = 0;
340
341
// Read field-level attributes
342
for field in fields {
343
// Search ahead for texture attributes so we can use them with any
344
// corresponding sampler attribute.
345
let mut tex_attrs = None;
346
for attr in &field.attrs {
347
let Some(attr_ident) = attr.path().get_ident() else {
348
continue;
349
};
350
if attr_ident == TEXTURE_ATTRIBUTE_NAME {
351
let (_binding_index, nested_meta_items) = get_binding_nested_attr(attr)?;
352
tex_attrs = Some(get_texture_attrs(nested_meta_items)?);
353
}
354
}
355
356
for attr in &field.attrs {
357
let Some(attr_ident) = attr.path().get_ident() else {
358
continue;
359
};
360
361
let binding_type = if attr_ident == UNIFORM_ATTRIBUTE_NAME {
362
BindingType::Uniform
363
} else if attr_ident == TEXTURE_ATTRIBUTE_NAME {
364
BindingType::Texture
365
} else if attr_ident == STORAGE_TEXTURE_ATTRIBUTE_NAME {
366
BindingType::StorageTexture
367
} else if attr_ident == SAMPLER_ATTRIBUTE_NAME {
368
BindingType::Sampler
369
} else if attr_ident == STORAGE_ATTRIBUTE_NAME {
370
BindingType::Storage
371
} else {
372
continue;
373
};
374
375
let (binding_index, nested_meta_items) = get_binding_nested_attr(attr)?;
376
377
let field_name = field.ident.as_ref().unwrap();
378
let required_len = binding_index as usize + 1;
379
if required_len > binding_states.len() {
380
binding_states.resize(required_len, BindingState::Free);
381
}
382
383
match &mut binding_states[binding_index as usize] {
384
value @ BindingState::Free => {
385
*value = match binding_type {
386
BindingType::Uniform => BindingState::OccupiedMergeableUniform {
387
uniform_fields: vec![field],
388
},
389
_ => {
390
// only populate bind group entries for non-uniforms
391
// uniform entries are deferred until the end
392
BindingState::Occupied {
393
binding_type,
394
ident: field_name,
395
}
396
}
397
}
398
}
399
BindingState::Occupied {
400
binding_type,
401
ident: occupied_ident,
402
} => {
403
return Err(Error::new_spanned(
404
attr,
405
format!("The '{field_name}' field cannot be assigned to binding {binding_index} because it is already occupied by the field '{occupied_ident}' of type {binding_type:?}.")
406
));
407
}
408
BindingState::OccupiedConvertedUniform => {
409
return Err(Error::new_spanned(
410
attr,
411
format!("The '{field_name}' field cannot be assigned to binding {binding_index} because it is already occupied by a struct-level uniform binding at the same index.")
412
));
413
}
414
BindingState::OccupiedMergeableUniform { uniform_fields } => match binding_type {
415
BindingType::Uniform => {
416
uniform_fields.push(field);
417
}
418
_ => {
419
return Err(Error::new_spanned(
420
attr,
421
format!("The '{field_name}' field cannot be assigned to binding {binding_index} because it is already occupied by a {:?}.", BindingType::Uniform)
422
));
423
}
424
},
425
}
426
427
match binding_type {
428
BindingType::Uniform => {
429
if attr_bindless_count.is_some() {
430
return Err(Error::new_spanned(
431
attr,
432
"Only structure-level `#[uniform]` attributes are supported in \
433
bindless mode",
434
));
435
}
436
437
// uniform codegen is deferred to account for combined uniform bindings
438
}
439
440
BindingType::Storage => {
441
let StorageAttrs {
442
visibility,
443
binding_array: binding_array_binding,
444
read_only,
445
buffer,
446
} = get_storage_binding_attr(nested_meta_items)?;
447
let visibility =
448
visibility.hygienic_quote(&quote! { #render_path::render_resource });
449
450
let field_name = field.ident.as_ref().unwrap();
451
452
if buffer {
453
binding_impls.push(quote! {
454
(
455
#binding_index,
456
#render_path::render_resource::OwnedBindingResource::Buffer({
457
self.#field_name.clone()
458
})
459
)
460
});
461
} else {
462
binding_impls.push(quote! {
463
(
464
#binding_index,
465
#render_path::render_resource::OwnedBindingResource::Buffer({
466
let handle: &#asset_path::Handle<#render_path::storage::ShaderStorageBuffer> = (&self.#field_name);
467
storage_buffers.get(handle).ok_or_else(|| #render_path::render_resource::AsBindGroupError::RetryNextUpdate)?.buffer.clone()
468
})
469
)
470
});
471
}
472
473
non_bindless_binding_layouts.push(quote! {
474
#bind_group_layout_entries.push(
475
#render_path::render_resource::BindGroupLayoutEntry {
476
binding: #binding_index,
477
visibility: #visibility,
478
ty: #render_path::render_resource::BindingType::Buffer {
479
ty: #render_path::render_resource::BufferBindingType::Storage { read_only: #read_only },
480
has_dynamic_offset: false,
481
min_binding_size: None,
482
},
483
count: #actual_bindless_slot_count,
484
}
485
);
486
});
487
488
if let Some(binding_array_binding) = binding_array_binding {
489
// Add the storage buffer to the `BindlessResourceType` list
490
// in the bindless descriptor.
491
let bindless_resource_type = quote! {
492
#render_path::render_resource::BindlessResourceType::Buffer
493
};
494
add_bindless_resource_type(
495
&render_path,
496
&mut bindless_resource_types,
497
binding_index,
498
bindless_resource_type,
499
);
500
501
// Push the buffer descriptor.
502
bindless_buffer_descriptors.push(quote! {
503
#render_path::render_resource::BindlessBufferDescriptor {
504
// Note that, because this is bindless, *binding
505
// index* here refers to the index in the bindless
506
// index table (`bindless_index`), and the actual
507
// binding number is the *binding array binding*.
508
binding_number: #render_path::render_resource::BindingNumber(
509
#binding_array_binding
510
),
511
bindless_index:
512
#render_path::render_resource::BindlessIndex(#binding_index),
513
size: None,
514
}
515
});
516
517
// Declare the binding array.
518
bindless_binding_layouts.push(quote!{
519
#bind_group_layout_entries.push(
520
#render_path::render_resource::BindGroupLayoutEntry {
521
binding: #binding_array_binding,
522
visibility: #render_path::render_resource::ShaderStages::FRAGMENT | #render_path::render_resource::ShaderStages::VERTEX | #render_path::render_resource::ShaderStages::COMPUTE,
523
ty: #render_path::render_resource::BindingType::Buffer {
524
ty: #render_path::render_resource::BufferBindingType::Storage {
525
read_only: #read_only
526
},
527
has_dynamic_offset: false,
528
min_binding_size: None,
529
},
530
count: #actual_bindless_slot_count,
531
}
532
);
533
});
534
}
535
}
536
537
BindingType::StorageTexture => {
538
if attr_bindless_count.is_some() {
539
return Err(Error::new_spanned(
540
attr,
541
"Storage textures are unsupported in bindless mode",
542
));
543
}
544
545
let StorageTextureAttrs {
546
dimension,
547
image_format,
548
access,
549
visibility,
550
} = get_storage_texture_binding_attr(nested_meta_items)?;
551
552
let visibility =
553
visibility.hygienic_quote(&quote! { #render_path::render_resource });
554
555
let fallback_image = get_fallback_image(&render_path, dimension);
556
557
// insert fallible texture-based entries at 0 so that if we fail here, we exit before allocating any buffers
558
binding_impls.insert(0, quote! {
559
( #binding_index,
560
#render_path::render_resource::OwnedBindingResource::TextureView(
561
#render_path::render_resource::#dimension,
562
{
563
let handle: Option<&#asset_path::Handle<#image_path::Image>> = (&self.#field_name).into();
564
if let Some(handle) = handle {
565
images.get(handle).ok_or_else(|| #render_path::render_resource::AsBindGroupError::RetryNextUpdate)?.texture_view.clone()
566
} else {
567
#fallback_image.texture_view.clone()
568
}
569
}
570
)
571
)
572
});
573
574
non_bindless_binding_layouts.push(quote! {
575
#bind_group_layout_entries.push(
576
#render_path::render_resource::BindGroupLayoutEntry {
577
binding: #binding_index,
578
visibility: #visibility,
579
ty: #render_path::render_resource::BindingType::StorageTexture {
580
access: #render_path::render_resource::StorageTextureAccess::#access,
581
format: #render_path::render_resource::TextureFormat::#image_format,
582
view_dimension: #render_path::render_resource::#dimension,
583
},
584
count: #actual_bindless_slot_count,
585
}
586
);
587
});
588
}
589
590
BindingType::Texture => {
591
let TextureAttrs {
592
dimension,
593
sample_type,
594
multisampled,
595
visibility,
596
} = tex_attrs.as_ref().unwrap();
597
598
let visibility =
599
visibility.hygienic_quote(&quote! { #render_path::render_resource });
600
601
let fallback_image = get_fallback_image(&render_path, *dimension);
602
603
// insert fallible texture-based entries at 0 so that if we fail here, we exit before allocating any buffers
604
binding_impls.insert(0, quote! {
605
(
606
#binding_index,
607
#render_path::render_resource::OwnedBindingResource::TextureView(
608
#render_path::render_resource::#dimension,
609
{
610
let handle: Option<&#asset_path::Handle<#image_path::Image>> = (&self.#field_name).into();
611
if let Some(handle) = handle {
612
images.get(handle).ok_or_else(|| #render_path::render_resource::AsBindGroupError::RetryNextUpdate)?.texture_view.clone()
613
} else {
614
#fallback_image.texture_view.clone()
615
}
616
}
617
)
618
)
619
});
620
621
sampler_binding_count += 1;
622
623
non_bindless_binding_layouts.push(quote! {
624
#bind_group_layout_entries.push(
625
#render_path::render_resource::BindGroupLayoutEntry {
626
binding: #binding_index,
627
visibility: #visibility,
628
ty: #render_path::render_resource::BindingType::Texture {
629
multisampled: #multisampled,
630
sample_type: #render_path::render_resource::#sample_type,
631
view_dimension: #render_path::render_resource::#dimension,
632
},
633
count: #actual_bindless_slot_count,
634
}
635
);
636
});
637
638
let bindless_resource_type = match *dimension {
639
BindingTextureDimension::D1 => {
640
quote! {
641
#render_path::render_resource::BindlessResourceType::Texture1d
642
}
643
}
644
BindingTextureDimension::D2 => {
645
quote! {
646
#render_path::render_resource::BindlessResourceType::Texture2d
647
}
648
}
649
BindingTextureDimension::D2Array => {
650
quote! {
651
#render_path::render_resource::BindlessResourceType::Texture2dArray
652
}
653
}
654
BindingTextureDimension::Cube => {
655
quote! {
656
#render_path::render_resource::BindlessResourceType::TextureCube
657
}
658
}
659
BindingTextureDimension::CubeArray => {
660
quote! {
661
#render_path::render_resource::BindlessResourceType::TextureCubeArray
662
}
663
}
664
BindingTextureDimension::D3 => {
665
quote! {
666
#render_path::render_resource::BindlessResourceType::Texture3d
667
}
668
}
669
};
670
671
// Add the texture to the `BindlessResourceType` list in the
672
// bindless descriptor.
673
add_bindless_resource_type(
674
&render_path,
675
&mut bindless_resource_types,
676
binding_index,
677
bindless_resource_type,
678
);
679
}
680
681
BindingType::Sampler => {
682
let SamplerAttrs {
683
sampler_binding_type,
684
visibility,
685
..
686
} = get_sampler_attrs(nested_meta_items)?;
687
let TextureAttrs { dimension, .. } = tex_attrs
688
.as_ref()
689
.expect("sampler attribute must have matching texture attribute");
690
691
let visibility =
692
visibility.hygienic_quote(&quote! { #render_path::render_resource });
693
694
let fallback_image = get_fallback_image(&render_path, *dimension);
695
696
let expected_samplers = match sampler_binding_type {
697
SamplerBindingType::Filtering => {
698
quote!( [#render_path::render_resource::TextureSampleType::Float { filterable: true }] )
699
}
700
SamplerBindingType::NonFiltering => quote!([
701
#render_path::render_resource::TextureSampleType::Float { filterable: false },
702
#render_path::render_resource::TextureSampleType::Sint,
703
#render_path::render_resource::TextureSampleType::Uint,
704
]),
705
SamplerBindingType::Comparison => {
706
quote!( [#render_path::render_resource::TextureSampleType::Depth] )
707
}
708
};
709
710
// insert fallible texture-based entries at 0 so that if we fail here, we exit before allocating any buffers
711
binding_impls.insert(0, quote! {
712
(
713
#binding_index,
714
#render_path::render_resource::OwnedBindingResource::Sampler(
715
// TODO: Support other types.
716
#render_path::render_resource::WgpuSamplerBindingType::Filtering,
717
{
718
let handle: Option<&#asset_path::Handle<#image_path::Image>> = (&self.#field_name).into();
719
if let Some(handle) = handle {
720
let image = images.get(handle).ok_or_else(|| #render_path::render_resource::AsBindGroupError::RetryNextUpdate)?;
721
722
let Some(sample_type) = image.texture_format.sample_type(None, Some(render_device.features())) else {
723
return Err(#render_path::render_resource::AsBindGroupError::InvalidSamplerType(
724
#binding_index,
725
"None".to_string(),
726
format!("{:?}", #expected_samplers),
727
));
728
};
729
730
let valid = #expected_samplers.contains(&sample_type);
731
732
if !valid {
733
return Err(#render_path::render_resource::AsBindGroupError::InvalidSamplerType(
734
#binding_index,
735
format!("{:?}", sample_type),
736
format!("{:?}", #expected_samplers),
737
));
738
}
739
image.sampler.clone()
740
} else {
741
#fallback_image.sampler.clone()
742
}
743
})
744
)
745
});
746
747
sampler_binding_count += 1;
748
749
non_bindless_binding_layouts.push(quote!{
750
#bind_group_layout_entries.push(
751
#render_path::render_resource::BindGroupLayoutEntry {
752
binding: #binding_index,
753
visibility: #visibility,
754
ty: #render_path::render_resource::BindingType::Sampler(#render_path::render_resource::#sampler_binding_type),
755
count: #actual_bindless_slot_count,
756
}
757
);
758
});
759
760
// Add the sampler to the `BindlessResourceType` list in the
761
// bindless descriptor.
762
//
763
// TODO: Support other types of samplers.
764
add_bindless_resource_type(
765
&render_path,
766
&mut bindless_resource_types,
767
binding_index,
768
quote! {
769
#render_path::render_resource::BindlessResourceType::SamplerFiltering
770
},
771
);
772
}
773
}
774
}
775
}
776
777
// Produce impls for fields with uniform bindings
778
let struct_name = &ast.ident;
779
let struct_name_literal = struct_name.to_string();
780
let struct_name_literal = struct_name_literal.as_str();
781
let mut field_struct_impls = Vec::new();
782
783
let uniform_binding_type_declarations = match attr_bindless_count {
784
Some(_) => {
785
quote! {
786
let (#uniform_binding_type, #uniform_buffer_usages) =
787
if Self::bindless_supported(render_device) && !force_no_bindless {
788
(
789
#render_path::render_resource::BufferBindingType::Storage { read_only: true },
790
#render_path::render_resource::BufferUsages::STORAGE,
791
)
792
} else {
793
(
794
#render_path::render_resource::BufferBindingType::Uniform,
795
#render_path::render_resource::BufferUsages::UNIFORM,
796
)
797
};
798
}
799
}
800
None => {
801
quote! {
802
let (#uniform_binding_type, #uniform_buffer_usages) = (
803
#render_path::render_resource::BufferBindingType::Uniform,
804
#render_path::render_resource::BufferUsages::UNIFORM,
805
);
806
}
807
}
808
};
809
810
for (binding_index, binding_state) in binding_states.iter().enumerate() {
811
let binding_index = binding_index as u32;
812
if let BindingState::OccupiedMergeableUniform { uniform_fields } = binding_state {
813
// single field uniform bindings for a given index can use a straightforward binding
814
if uniform_fields.len() == 1 {
815
let field = &uniform_fields[0];
816
let field_name = field.ident.as_ref().unwrap();
817
let field_ty = &field.ty;
818
binding_impls.push(quote! {{
819
let mut buffer = #render_path::render_resource::encase::UniformBuffer::new(Vec::new());
820
buffer.write(&self.#field_name).unwrap();
821
(
822
#binding_index,
823
#render_path::render_resource::OwnedBindingResource::Buffer(render_device.create_buffer_with_data(
824
&#render_path::render_resource::BufferInitDescriptor {
825
label: None,
826
usage: #uniform_buffer_usages,
827
contents: buffer.as_ref(),
828
},
829
))
830
)
831
}});
832
833
non_bindless_binding_layouts.push(quote!{
834
#bind_group_layout_entries.push(
835
#render_path::render_resource::BindGroupLayoutEntry {
836
binding: #binding_index,
837
visibility: #render_path::render_resource::ShaderStages::FRAGMENT | #render_path::render_resource::ShaderStages::VERTEX | #render_path::render_resource::ShaderStages::COMPUTE,
838
ty: #render_path::render_resource::BindingType::Buffer {
839
ty: #uniform_binding_type,
840
has_dynamic_offset: false,
841
min_binding_size: Some(<#field_ty as #render_path::render_resource::ShaderType>::min_size()),
842
},
843
count: #actual_bindless_slot_count,
844
}
845
);
846
});
847
// multi-field uniform bindings for a given index require an intermediate struct to derive ShaderType
848
} else {
849
let uniform_struct_name = Ident::new(
850
&format!("_{struct_name}AsBindGroupUniformStructBindGroup{binding_index}"),
851
Span::call_site(),
852
);
853
854
let field_name = uniform_fields.iter().map(|f| f.ident.as_ref().unwrap());
855
let field_type = uniform_fields.iter().map(|f| &f.ty);
856
field_struct_impls.push(quote! {
857
#[derive(#render_path::render_resource::ShaderType)]
858
struct #uniform_struct_name<'a> {
859
#(#field_name: &'a #field_type,)*
860
}
861
});
862
863
let field_name = uniform_fields.iter().map(|f| f.ident.as_ref().unwrap());
864
binding_impls.push(quote! {{
865
let mut buffer = #render_path::render_resource::encase::UniformBuffer::new(Vec::new());
866
buffer.write(&#uniform_struct_name {
867
#(#field_name: &self.#field_name,)*
868
}).unwrap();
869
(
870
#binding_index,
871
#render_path::render_resource::OwnedBindingResource::Buffer(render_device.create_buffer_with_data(
872
&#render_path::render_resource::BufferInitDescriptor {
873
label: None,
874
usage: #uniform_buffer_usages,
875
contents: buffer.as_ref(),
876
},
877
))
878
)
879
}});
880
881
non_bindless_binding_layouts.push(quote!{
882
#bind_group_layout_entries.push(#render_path::render_resource::BindGroupLayoutEntry {
883
binding: #binding_index,
884
visibility: #render_path::render_resource::ShaderStages::FRAGMENT | #render_path::render_resource::ShaderStages::VERTEX | #render_path::render_resource::ShaderStages::COMPUTE,
885
ty: #render_path::render_resource::BindingType::Buffer {
886
ty: #uniform_binding_type,
887
has_dynamic_offset: false,
888
min_binding_size: Some(<#uniform_struct_name as #render_path::render_resource::ShaderType>::min_size()),
889
},
890
count: #actual_bindless_slot_count,
891
});
892
});
893
}
894
}
895
}
896
897
let generics = ast.generics;
898
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
899
900
let (prepared_data, get_prepared_data) = if let Some(prepared) = attr_prepared_data_ident {
901
let get_prepared_data = quote! { self.into() };
902
(quote! {#prepared}, get_prepared_data)
903
} else {
904
let prepared_data = quote! { () };
905
(prepared_data.clone(), prepared_data)
906
};
907
908
// Calculate the number of samplers that we need, so that we don't go over
909
// the limit on certain platforms. See
910
// https://github.com/bevyengine/bevy/issues/16988.
911
let bindless_count_syntax = match attr_bindless_count {
912
Some(BindlessSlabResourceLimitAttr::Auto) => {
913
quote! { #render_path::render_resource::AUTO_BINDLESS_SLAB_RESOURCE_LIMIT }
914
}
915
Some(BindlessSlabResourceLimitAttr::Limit(ref count)) => {
916
quote! { #count }
917
}
918
None => quote! { 0 },
919
};
920
921
// Calculate the actual bindless index table range, taking the
922
// `#[bindless(index_table(range(M..N)))]` attribute into account.
923
let bindless_index_table_range = match attr_bindless_index_table_range {
924
None => {
925
let resource_count = bindless_resource_types.len() as u32;
926
quote! {
927
#render_path::render_resource::BindlessIndex(0)..
928
#render_path::render_resource::BindlessIndex(#resource_count)
929
}
930
}
931
Some(BindlessIndexTableRangeAttr { start, end }) => {
932
quote! {
933
#render_path::render_resource::BindlessIndex(#start)..
934
#render_path::render_resource::BindlessIndex(#end)
935
}
936
}
937
};
938
939
// Calculate the actual binding number of the bindless index table, taking
940
// the `#[bindless(index_table(binding(B)))]` into account.
941
let bindless_index_table_binding_number = match attr_bindless_index_table_binding {
942
None => quote! { #render_path::render_resource::BindingNumber(0) },
943
Some(binding_number) => {
944
quote! { #render_path::render_resource::BindingNumber(#binding_number) }
945
}
946
};
947
948
// Calculate the actual number of bindless slots, taking hardware
949
// limitations into account.
950
let (bindless_slot_count, actual_bindless_slot_count_declaration, bindless_descriptor_syntax) =
951
match attr_bindless_count {
952
Some(ref bindless_count) => {
953
let bindless_supported_syntax = quote! {
954
fn bindless_supported(
955
render_device: &#render_path::renderer::RenderDevice
956
) -> bool {
957
render_device.features().contains(
958
#render_path::settings::WgpuFeatures::BUFFER_BINDING_ARRAY |
959
#render_path::settings::WgpuFeatures::TEXTURE_BINDING_ARRAY
960
) &&
961
render_device.limits().max_storage_buffers_per_shader_stage > 0 &&
962
render_device.limits().max_samplers_per_shader_stage >=
963
(#sampler_binding_count * #bindless_count_syntax)
964
}
965
};
966
let actual_bindless_slot_count_declaration = quote! {
967
let #actual_bindless_slot_count = if Self::bindless_supported(render_device) &&
968
!force_no_bindless {
969
::core::num::NonZeroU32::new(#bindless_count_syntax)
970
} else {
971
None
972
};
973
};
974
let bindless_slot_count_declaration = match bindless_count {
975
BindlessSlabResourceLimitAttr::Auto => {
976
quote! {
977
fn bindless_slot_count() -> Option<
978
#render_path::render_resource::BindlessSlabResourceLimit
979
> {
980
Some(#render_path::render_resource::BindlessSlabResourceLimit::Auto)
981
}
982
}
983
}
984
BindlessSlabResourceLimitAttr::Limit(lit) => {
985
quote! {
986
fn bindless_slot_count() -> Option<
987
#render_path::render_resource::BindlessSlabResourceLimit
988
> {
989
Some(#render_path::render_resource::BindlessSlabResourceLimit::Custom(#lit))
990
}
991
}
992
}
993
};
994
995
let bindless_buffer_descriptor_count = bindless_buffer_descriptors.len();
996
997
// We use `LazyLock` so that we can call `min_size`, which isn't
998
// a `const fn`.
999
let bindless_descriptor_syntax = quote! {
1000
static RESOURCES: &[#render_path::render_resource::BindlessResourceType] = &[
1001
#(#bindless_resource_types),*
1002
];
1003
static BUFFERS: ::std::sync::LazyLock<[
1004
#render_path::render_resource::BindlessBufferDescriptor;
1005
#bindless_buffer_descriptor_count
1006
]> = ::std::sync::LazyLock::new(|| {
1007
[#(#bindless_buffer_descriptors),*]
1008
});
1009
static INDEX_TABLES: &[
1010
#render_path::render_resource::BindlessIndexTableDescriptor
1011
] = &[
1012
#render_path::render_resource::BindlessIndexTableDescriptor {
1013
indices: #bindless_index_table_range,
1014
binding_number: #bindless_index_table_binding_number,
1015
}
1016
];
1017
Some(#render_path::render_resource::BindlessDescriptor {
1018
resources: ::std::borrow::Cow::Borrowed(RESOURCES),
1019
buffers: ::std::borrow::Cow::Borrowed(&*BUFFERS),
1020
index_tables: ::std::borrow::Cow::Borrowed(&*INDEX_TABLES),
1021
})
1022
};
1023
1024
(
1025
quote! {
1026
#bindless_slot_count_declaration
1027
#bindless_supported_syntax
1028
},
1029
actual_bindless_slot_count_declaration,
1030
bindless_descriptor_syntax,
1031
)
1032
}
1033
None => (
1034
TokenStream::new().into(),
1035
quote! { let #actual_bindless_slot_count: Option<::core::num::NonZeroU32> = None; },
1036
quote! { None },
1037
),
1038
};
1039
1040
Ok(TokenStream::from(quote! {
1041
#(#field_struct_impls)*
1042
1043
impl #impl_generics #render_path::render_resource::AsBindGroup for #struct_name #ty_generics #where_clause {
1044
type Data = #prepared_data;
1045
1046
type Param = (
1047
#ecs_path::system::lifetimeless::SRes<#render_path::render_asset::RenderAssets<#render_path::texture::GpuImage>>,
1048
#ecs_path::system::lifetimeless::SRes<#render_path::texture::FallbackImage>,
1049
#ecs_path::system::lifetimeless::SRes<#render_path::render_asset::RenderAssets<#render_path::storage::GpuShaderStorageBuffer>>,
1050
);
1051
1052
#bindless_slot_count
1053
1054
fn label() -> Option<&'static str> {
1055
Some(#struct_name_literal)
1056
}
1057
1058
fn unprepared_bind_group(
1059
&self,
1060
layout: &#render_path::render_resource::BindGroupLayout,
1061
render_device: &#render_path::renderer::RenderDevice,
1062
(images, fallback_image, storage_buffers): &mut #ecs_path::system::SystemParamItem<'_, '_, Self::Param>,
1063
force_no_bindless: bool,
1064
) -> Result<#render_path::render_resource::UnpreparedBindGroup, #render_path::render_resource::AsBindGroupError> {
1065
#uniform_binding_type_declarations
1066
1067
let bindings = #render_path::render_resource::BindingResources(vec![#(#binding_impls,)*]);
1068
1069
Ok(#render_path::render_resource::UnpreparedBindGroup {
1070
bindings,
1071
})
1072
}
1073
1074
#[allow(clippy::unused_unit)]
1075
fn bind_group_data(&self) -> Self::Data {
1076
#get_prepared_data
1077
}
1078
1079
fn bind_group_layout_entries(
1080
render_device: &#render_path::renderer::RenderDevice,
1081
force_no_bindless: bool
1082
) -> Vec<#render_path::render_resource::BindGroupLayoutEntry> {
1083
#actual_bindless_slot_count_declaration
1084
#uniform_binding_type_declarations
1085
1086
let mut #bind_group_layout_entries = Vec::new();
1087
match #actual_bindless_slot_count {
1088
Some(bindless_slot_count) => {
1089
let bindless_index_table_range = #bindless_index_table_range;
1090
#bind_group_layout_entries.extend(
1091
#render_path::render_resource::create_bindless_bind_group_layout_entries(
1092
bindless_index_table_range.end.0 -
1093
bindless_index_table_range.start.0,
1094
bindless_slot_count.into(),
1095
#bindless_index_table_binding_number,
1096
).into_iter()
1097
);
1098
#(#bindless_binding_layouts)*;
1099
}
1100
None => {
1101
#(#non_bindless_binding_layouts)*;
1102
}
1103
};
1104
#bind_group_layout_entries
1105
}
1106
1107
fn bindless_descriptor() -> Option<#render_path::render_resource::BindlessDescriptor> {
1108
#bindless_descriptor_syntax
1109
}
1110
}
1111
}))
1112
}
1113
1114
/// Adds a bindless resource type to the `BindlessResourceType` array in the
1115
/// bindless descriptor we're building up.
1116
///
1117
/// See the `bevy_render::render_resource::bindless::BindlessResourceType`
1118
/// documentation for more information.
1119
fn add_bindless_resource_type(
1120
render_path: &syn::Path,
1121
bindless_resource_types: &mut Vec<proc_macro2::TokenStream>,
1122
binding_index: u32,
1123
bindless_resource_type: proc_macro2::TokenStream,
1124
) {
1125
// If we need to grow the array, pad the unused fields with
1126
// `BindlessResourceType::None`.
1127
if bindless_resource_types.len() < (binding_index as usize + 1) {
1128
bindless_resource_types.resize_with(binding_index as usize + 1, || {
1129
quote! { #render_path::render_resource::BindlessResourceType::None }
1130
});
1131
}
1132
1133
// Assign the `BindlessResourceType`.
1134
bindless_resource_types[binding_index as usize] = bindless_resource_type;
1135
}
1136
1137
fn get_fallback_image(
1138
render_path: &syn::Path,
1139
dimension: BindingTextureDimension,
1140
) -> proc_macro2::TokenStream {
1141
quote! {
1142
match #render_path::render_resource::#dimension {
1143
#render_path::render_resource::TextureViewDimension::D1 => &fallback_image.d1,
1144
#render_path::render_resource::TextureViewDimension::D2 => &fallback_image.d2,
1145
#render_path::render_resource::TextureViewDimension::D2Array => &fallback_image.d2_array,
1146
#render_path::render_resource::TextureViewDimension::Cube => &fallback_image.cube,
1147
#render_path::render_resource::TextureViewDimension::CubeArray => &fallback_image.cube_array,
1148
#render_path::render_resource::TextureViewDimension::D3 => &fallback_image.d3,
1149
}
1150
}
1151
}
1152
1153
/// Represents the arguments for the `uniform` binding attribute.
1154
///
1155
/// If parsed, represents an attribute
1156
/// like `#[uniform(LitInt, Ident)]`
1157
struct UniformBindingMeta {
1158
lit_int: LitInt,
1159
ident: Ident,
1160
binding_array: Option<LitInt>,
1161
}
1162
1163
/// The parsed structure-level `#[uniform]` or `#[data]` attribute.
1164
///
1165
/// The corresponding syntax is `#[uniform(BINDING_INDEX, CONVERTED_SHADER_TYPE,
1166
/// binding_array(BINDING_ARRAY)]`, optionally replacing `uniform` with `data`.
1167
struct UniformBindingAttr {
1168
/// Whether the declaration is `#[uniform]` or `#[data]`.
1169
binding_type: UniformBindingAttrType,
1170
/// The binding index.
1171
binding_index: u32,
1172
/// The uniform data type.
1173
converted_shader_type: Ident,
1174
/// The binding number of the binding array, if this is a bindless material.
1175
binding_array: Option<u32>,
1176
}
1177
1178
/// Whether a structure-level shader type declaration is `#[uniform]` or
1179
/// `#[data]`.
1180
enum UniformBindingAttrType {
1181
/// `#[uniform]`: i.e. in bindless mode, we need a separate buffer per data
1182
/// instance.
1183
Uniform,
1184
/// `#[data]`: i.e. in bindless mode, we concatenate all instance data into
1185
/// a single buffer.
1186
Data,
1187
}
1188
1189
/// Represents the arguments for any general binding attribute.
1190
///
1191
/// If parsed, represents an attribute
1192
/// like `#[foo(LitInt, ...)]` where the rest is optional [`Meta`].
1193
enum BindingMeta {
1194
IndexOnly(LitInt),
1195
IndexWithOptions(BindingIndexOptions),
1196
}
1197
1198
/// Represents the arguments for an attribute with a list of arguments.
1199
///
1200
/// This represents, for example, `#[texture(0, dimension = "2d_array")]`.
1201
struct BindingIndexOptions {
1202
lit_int: LitInt,
1203
_comma: Comma,
1204
meta_list: Punctuated<Meta, Comma>,
1205
}
1206
1207
impl Parse for BindingMeta {
1208
fn parse(input: ParseStream) -> Result<Self> {
1209
if input.peek2(Comma) {
1210
input.parse().map(Self::IndexWithOptions)
1211
} else {
1212
input.parse().map(Self::IndexOnly)
1213
}
1214
}
1215
}
1216
1217
impl Parse for BindingIndexOptions {
1218
fn parse(input: ParseStream) -> Result<Self> {
1219
Ok(Self {
1220
lit_int: input.parse()?,
1221
_comma: input.parse()?,
1222
meta_list: input.parse_terminated(Meta::parse, Comma)?,
1223
})
1224
}
1225
}
1226
1227
impl Parse for UniformBindingMeta {
1228
// Parse syntax like `#[uniform(0, StandardMaterial, binding_array(10))]`.
1229
fn parse(input: ParseStream) -> Result<Self> {
1230
let lit_int = input.parse()?;
1231
input.parse::<Comma>()?;
1232
let ident = input.parse()?;
1233
1234
// Look for a `binding_array(BINDING_NUMBER)` declaration.
1235
let mut binding_array: Option<LitInt> = None;
1236
if input.parse::<Comma>().is_ok() {
1237
if input
1238
.parse::<syn::Path>()?
1239
.get_ident()
1240
.is_none_or(|ident| *ident != BINDING_ARRAY_MODIFIER_NAME)
1241
{
1242
return Err(Error::new_spanned(ident, "Expected `binding_array`"));
1243
}
1244
let parser;
1245
parenthesized!(parser in input);
1246
binding_array = Some(parser.parse()?);
1247
}
1248
1249
Ok(Self {
1250
lit_int,
1251
ident,
1252
binding_array,
1253
})
1254
}
1255
}
1256
1257
/// Parses a structure-level `#[uniform]` attribute (not a field-level
1258
/// `#[uniform]` attribute).
1259
fn get_uniform_binding_attr(attr: &syn::Attribute) -> Result<UniformBindingAttr> {
1260
let attr_ident = attr
1261
.path()
1262
.get_ident()
1263
.expect("Shouldn't be here if we didn't have an attribute");
1264
1265
let uniform_binding_meta = attr.parse_args_with(UniformBindingMeta::parse)?;
1266
1267
let binding_index = uniform_binding_meta.lit_int.base10_parse()?;
1268
let ident = uniform_binding_meta.ident;
1269
let binding_array = match uniform_binding_meta.binding_array {
1270
None => None,
1271
Some(binding_array) => Some(binding_array.base10_parse()?),
1272
};
1273
1274
Ok(UniformBindingAttr {
1275
binding_type: if attr_ident == UNIFORM_ATTRIBUTE_NAME {
1276
UniformBindingAttrType::Uniform
1277
} else {
1278
UniformBindingAttrType::Data
1279
},
1280
binding_index,
1281
converted_shader_type: ident,
1282
binding_array,
1283
})
1284
}
1285
1286
fn get_binding_nested_attr(attr: &syn::Attribute) -> Result<(u32, Vec<Meta>)> {
1287
let binding_meta = attr.parse_args_with(BindingMeta::parse)?;
1288
1289
match binding_meta {
1290
BindingMeta::IndexOnly(lit_int) => Ok((lit_int.base10_parse()?, Vec::new())),
1291
BindingMeta::IndexWithOptions(BindingIndexOptions {
1292
lit_int,
1293
_comma: _,
1294
meta_list,
1295
}) => Ok((lit_int.base10_parse()?, meta_list.into_iter().collect())),
1296
}
1297
}
1298
1299
#[derive(Default)]
1300
enum ShaderStageVisibility {
1301
#[default]
1302
All,
1303
None,
1304
Flags(VisibilityFlags),
1305
}
1306
1307
#[derive(Default)]
1308
struct VisibilityFlags {
1309
vertex: bool,
1310
fragment: bool,
1311
compute: bool,
1312
}
1313
1314
impl ShaderStageVisibility {
1315
fn vertex_fragment() -> Self {
1316
Self::Flags(VisibilityFlags::vertex_fragment())
1317
}
1318
1319
fn compute() -> Self {
1320
Self::Flags(VisibilityFlags::compute())
1321
}
1322
}
1323
1324
impl VisibilityFlags {
1325
fn vertex_fragment() -> Self {
1326
Self {
1327
vertex: true,
1328
fragment: true,
1329
..Default::default()
1330
}
1331
}
1332
1333
fn compute() -> Self {
1334
Self {
1335
compute: true,
1336
..Default::default()
1337
}
1338
}
1339
}
1340
1341
impl ShaderStageVisibility {
1342
fn hygienic_quote(&self, path: &proc_macro2::TokenStream) -> proc_macro2::TokenStream {
1343
match self {
1344
ShaderStageVisibility::All => quote! {
1345
if cfg!(feature = "webgpu") {
1346
todo!("Please use a more specific shader stage: https://github.com/gfx-rs/wgpu/issues/7708")
1347
} else {
1348
#path::ShaderStages::all()
1349
}
1350
},
1351
ShaderStageVisibility::None => quote! { #path::ShaderStages::NONE },
1352
ShaderStageVisibility::Flags(flags) => {
1353
let mut quoted = Vec::new();
1354
1355
if flags.vertex {
1356
quoted.push(quote! { #path::ShaderStages::VERTEX });
1357
}
1358
if flags.fragment {
1359
quoted.push(quote! { #path::ShaderStages::FRAGMENT });
1360
}
1361
if flags.compute {
1362
quoted.push(quote! { #path::ShaderStages::COMPUTE });
1363
}
1364
1365
quote! { #(#quoted)|* }
1366
}
1367
}
1368
}
1369
}
1370
1371
const VISIBILITY: Symbol = Symbol("visibility");
1372
const VISIBILITY_VERTEX: Symbol = Symbol("vertex");
1373
const VISIBILITY_FRAGMENT: Symbol = Symbol("fragment");
1374
const VISIBILITY_COMPUTE: Symbol = Symbol("compute");
1375
const VISIBILITY_ALL: Symbol = Symbol("all");
1376
const VISIBILITY_NONE: Symbol = Symbol("none");
1377
1378
fn get_visibility_flag_value(meta_list: &MetaList) -> Result<ShaderStageVisibility> {
1379
let mut flags = Vec::new();
1380
1381
meta_list.parse_nested_meta(|meta| {
1382
flags.push(meta.path);
1383
Ok(())
1384
})?;
1385
1386
if flags.is_empty() {
1387
return Err(Error::new_spanned(
1388
meta_list,
1389
"Invalid visibility format. Must be `visibility(flags)`, flags can be `all`, `none`, or a list-combination of `vertex`, `fragment` and/or `compute`."
1390
));
1391
}
1392
1393
if flags.len() == 1
1394
&& let Some(flag) = flags.first()
1395
{
1396
if flag == VISIBILITY_ALL {
1397
return Ok(ShaderStageVisibility::All);
1398
} else if flag == VISIBILITY_NONE {
1399
return Ok(ShaderStageVisibility::None);
1400
}
1401
}
1402
1403
let mut visibility = VisibilityFlags::default();
1404
1405
for flag in flags {
1406
if flag == VISIBILITY_VERTEX {
1407
visibility.vertex = true;
1408
} else if flag == VISIBILITY_FRAGMENT {
1409
visibility.fragment = true;
1410
} else if flag == VISIBILITY_COMPUTE {
1411
visibility.compute = true;
1412
} else {
1413
return Err(Error::new_spanned(
1414
flag,
1415
"Not a valid visibility flag. Must be `all`, `none`, or a list-combination of `vertex`, `fragment` and/or `compute`."
1416
));
1417
}
1418
}
1419
1420
Ok(ShaderStageVisibility::Flags(visibility))
1421
}
1422
1423
// Returns the `binding_array(10)` part of a field-level declaration like
1424
// `#[storage(binding_array(10))]`.
1425
fn get_binding_array_flag_value(meta_list: &MetaList) -> Result<u32> {
1426
meta_list
1427
.parse_args_with(|input: ParseStream| input.parse::<LitInt>())?
1428
.base10_parse()
1429
}
1430
1431
#[derive(Clone, Copy, Default)]
1432
enum BindingTextureDimension {
1433
D1,
1434
#[default]
1435
D2,
1436
D2Array,
1437
Cube,
1438
CubeArray,
1439
D3,
1440
}
1441
1442
enum BindingTextureSampleType {
1443
Float { filterable: bool },
1444
Depth,
1445
Sint,
1446
Uint,
1447
}
1448
1449
impl ToTokens for BindingTextureDimension {
1450
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
1451
tokens.extend(match self {
1452
BindingTextureDimension::D1 => quote! { TextureViewDimension::D1 },
1453
BindingTextureDimension::D2 => quote! { TextureViewDimension::D2 },
1454
BindingTextureDimension::D2Array => quote! { TextureViewDimension::D2Array },
1455
BindingTextureDimension::Cube => quote! { TextureViewDimension::Cube },
1456
BindingTextureDimension::CubeArray => quote! { TextureViewDimension::CubeArray },
1457
BindingTextureDimension::D3 => quote! { TextureViewDimension::D3 },
1458
});
1459
}
1460
}
1461
1462
impl ToTokens for BindingTextureSampleType {
1463
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
1464
tokens.extend(match self {
1465
BindingTextureSampleType::Float { filterable } => {
1466
quote! { TextureSampleType::Float { filterable: #filterable } }
1467
}
1468
BindingTextureSampleType::Depth => quote! { TextureSampleType::Depth },
1469
BindingTextureSampleType::Sint => quote! { TextureSampleType::Sint },
1470
BindingTextureSampleType::Uint => quote! { TextureSampleType::Uint },
1471
});
1472
}
1473
}
1474
1475
struct TextureAttrs {
1476
dimension: BindingTextureDimension,
1477
sample_type: BindingTextureSampleType,
1478
multisampled: bool,
1479
visibility: ShaderStageVisibility,
1480
}
1481
1482
impl Default for BindingTextureSampleType {
1483
fn default() -> Self {
1484
BindingTextureSampleType::Float { filterable: true }
1485
}
1486
}
1487
1488
impl Default for TextureAttrs {
1489
fn default() -> Self {
1490
Self {
1491
dimension: Default::default(),
1492
sample_type: Default::default(),
1493
multisampled: true,
1494
visibility: Default::default(),
1495
}
1496
}
1497
}
1498
1499
struct StorageTextureAttrs {
1500
dimension: BindingTextureDimension,
1501
// Parsing of the image_format parameter is deferred to the type checker,
1502
// which will error if the format is not member of the TextureFormat enum.
1503
image_format: proc_macro2::TokenStream,
1504
// Parsing of the access parameter is deferred to the type checker,
1505
// which will error if the access is not member of the StorageTextureAccess enum.
1506
access: proc_macro2::TokenStream,
1507
visibility: ShaderStageVisibility,
1508
}
1509
1510
impl Default for StorageTextureAttrs {
1511
fn default() -> Self {
1512
Self {
1513
dimension: Default::default(),
1514
image_format: quote! { Rgba8Unorm },
1515
access: quote! { ReadWrite },
1516
visibility: ShaderStageVisibility::compute(),
1517
}
1518
}
1519
}
1520
1521
fn get_storage_texture_binding_attr(metas: Vec<Meta>) -> Result<StorageTextureAttrs> {
1522
let mut storage_texture_attrs = StorageTextureAttrs::default();
1523
1524
for meta in metas {
1525
use syn::Meta::{List, NameValue};
1526
match meta {
1527
// Parse #[storage_texture(0, dimension = "...")].
1528
NameValue(m) if m.path == DIMENSION => {
1529
let value = get_lit_str(DIMENSION, &m.value)?;
1530
storage_texture_attrs.dimension = get_texture_dimension_value(value)?;
1531
}
1532
// Parse #[storage_texture(0, format = ...))].
1533
NameValue(m) if m.path == IMAGE_FORMAT => {
1534
storage_texture_attrs.image_format = m.value.into_token_stream();
1535
}
1536
// Parse #[storage_texture(0, access = ...))].
1537
NameValue(m) if m.path == ACCESS => {
1538
storage_texture_attrs.access = m.value.into_token_stream();
1539
}
1540
// Parse #[storage_texture(0, visibility(...))].
1541
List(m) if m.path == VISIBILITY => {
1542
storage_texture_attrs.visibility = get_visibility_flag_value(&m)?;
1543
}
1544
NameValue(m) => {
1545
return Err(Error::new_spanned(
1546
m.path,
1547
"Not a valid name. Available attributes: `dimension`, `image_format`, `access`.",
1548
));
1549
}
1550
_ => {
1551
return Err(Error::new_spanned(
1552
meta,
1553
"Not a name value pair: `foo = \"...\"`",
1554
));
1555
}
1556
}
1557
}
1558
1559
Ok(storage_texture_attrs)
1560
}
1561
1562
const DIMENSION: Symbol = Symbol("dimension");
1563
const IMAGE_FORMAT: Symbol = Symbol("image_format");
1564
const ACCESS: Symbol = Symbol("access");
1565
const SAMPLE_TYPE: Symbol = Symbol("sample_type");
1566
const FILTERABLE: Symbol = Symbol("filterable");
1567
const MULTISAMPLED: Symbol = Symbol("multisampled");
1568
1569
// Values for `dimension` attribute.
1570
const DIM_1D: &str = "1d";
1571
const DIM_2D: &str = "2d";
1572
const DIM_3D: &str = "3d";
1573
const DIM_2D_ARRAY: &str = "2d_array";
1574
const DIM_CUBE: &str = "cube";
1575
const DIM_CUBE_ARRAY: &str = "cube_array";
1576
1577
// Values for sample `type` attribute.
1578
const FLOAT: &str = "float";
1579
const DEPTH: &str = "depth";
1580
const S_INT: &str = "s_int";
1581
const U_INT: &str = "u_int";
1582
1583
fn get_texture_attrs(metas: Vec<Meta>) -> Result<TextureAttrs> {
1584
let mut dimension = Default::default();
1585
let mut sample_type = Default::default();
1586
let mut multisampled = Default::default();
1587
let mut filterable = None;
1588
let mut filterable_ident = None;
1589
1590
let mut visibility = ShaderStageVisibility::vertex_fragment();
1591
1592
for meta in metas {
1593
use syn::Meta::{List, NameValue};
1594
match meta {
1595
// Parse #[texture(0, dimension = "...")].
1596
NameValue(m) if m.path == DIMENSION => {
1597
let value = get_lit_str(DIMENSION, &m.value)?;
1598
dimension = get_texture_dimension_value(value)?;
1599
}
1600
// Parse #[texture(0, sample_type = "...")].
1601
NameValue(m) if m.path == SAMPLE_TYPE => {
1602
let value = get_lit_str(SAMPLE_TYPE, &m.value)?;
1603
sample_type = get_texture_sample_type_value(value)?;
1604
}
1605
// Parse #[texture(0, multisampled = "...")].
1606
NameValue(m) if m.path == MULTISAMPLED => {
1607
multisampled = get_lit_bool(MULTISAMPLED, &m.value)?;
1608
}
1609
// Parse #[texture(0, filterable = "...")].
1610
NameValue(m) if m.path == FILTERABLE => {
1611
filterable = get_lit_bool(FILTERABLE, &m.value)?.into();
1612
filterable_ident = m.path.into();
1613
}
1614
// Parse #[texture(0, visibility(...))].
1615
List(m) if m.path == VISIBILITY => {
1616
visibility = get_visibility_flag_value(&m)?;
1617
}
1618
NameValue(m) => {
1619
return Err(Error::new_spanned(
1620
m.path,
1621
"Not a valid name. Available attributes: `dimension`, `sample_type`, `multisampled`, or `filterable`."
1622
));
1623
}
1624
_ => {
1625
return Err(Error::new_spanned(
1626
meta,
1627
"Not a name value pair: `foo = \"...\"`",
1628
));
1629
}
1630
}
1631
}
1632
1633
// Resolve `filterable` since the float
1634
// sample type is the one that contains the value.
1635
if let Some(filterable) = filterable {
1636
let path = filterable_ident.unwrap();
1637
match sample_type {
1638
BindingTextureSampleType::Float { filterable: _ } => {
1639
sample_type = BindingTextureSampleType::Float { filterable }
1640
}
1641
_ => {
1642
return Err(Error::new_spanned(
1643
path,
1644
"Type must be `float` to use the `filterable` attribute.",
1645
));
1646
}
1647
};
1648
}
1649
1650
Ok(TextureAttrs {
1651
dimension,
1652
sample_type,
1653
multisampled,
1654
visibility,
1655
})
1656
}
1657
1658
fn get_texture_dimension_value(lit_str: &LitStr) -> Result<BindingTextureDimension> {
1659
match lit_str.value().as_str() {
1660
DIM_1D => Ok(BindingTextureDimension::D1),
1661
DIM_2D => Ok(BindingTextureDimension::D2),
1662
DIM_2D_ARRAY => Ok(BindingTextureDimension::D2Array),
1663
DIM_3D => Ok(BindingTextureDimension::D3),
1664
DIM_CUBE => Ok(BindingTextureDimension::Cube),
1665
DIM_CUBE_ARRAY => Ok(BindingTextureDimension::CubeArray),
1666
1667
_ => Err(Error::new_spanned(
1668
lit_str,
1669
"Not a valid dimension. Must be `1d`, `2d`, `2d_array`, `3d`, `cube` or `cube_array`.",
1670
)),
1671
}
1672
}
1673
1674
fn get_texture_sample_type_value(lit_str: &LitStr) -> Result<BindingTextureSampleType> {
1675
match lit_str.value().as_str() {
1676
FLOAT => Ok(BindingTextureSampleType::Float { filterable: true }),
1677
DEPTH => Ok(BindingTextureSampleType::Depth),
1678
S_INT => Ok(BindingTextureSampleType::Sint),
1679
U_INT => Ok(BindingTextureSampleType::Uint),
1680
1681
_ => Err(Error::new_spanned(
1682
lit_str,
1683
"Not a valid sample type. Must be `float`, `depth`, `s_int` or `u_int`.",
1684
)),
1685
}
1686
}
1687
1688
#[derive(Default)]
1689
struct SamplerAttrs {
1690
sampler_binding_type: SamplerBindingType,
1691
visibility: ShaderStageVisibility,
1692
}
1693
1694
#[derive(Default)]
1695
enum SamplerBindingType {
1696
#[default]
1697
Filtering,
1698
NonFiltering,
1699
Comparison,
1700
}
1701
1702
impl ToTokens for SamplerBindingType {
1703
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
1704
tokens.extend(match self {
1705
SamplerBindingType::Filtering => quote! { SamplerBindingType::Filtering },
1706
SamplerBindingType::NonFiltering => quote! { SamplerBindingType::NonFiltering },
1707
SamplerBindingType::Comparison => quote! { SamplerBindingType::Comparison },
1708
});
1709
}
1710
}
1711
1712
const SAMPLER_TYPE: Symbol = Symbol("sampler_type");
1713
1714
const FILTERING: &str = "filtering";
1715
const NON_FILTERING: &str = "non_filtering";
1716
const COMPARISON: &str = "comparison";
1717
1718
fn get_sampler_attrs(metas: Vec<Meta>) -> Result<SamplerAttrs> {
1719
let mut sampler_binding_type = Default::default();
1720
let mut visibility = ShaderStageVisibility::vertex_fragment();
1721
1722
for meta in metas {
1723
use syn::Meta::{List, NameValue};
1724
match meta {
1725
// Parse #[sampler(0, sampler_type = "..."))].
1726
NameValue(m) if m.path == SAMPLER_TYPE => {
1727
let value = get_lit_str(DIMENSION, &m.value)?;
1728
sampler_binding_type = get_sampler_binding_type_value(value)?;
1729
}
1730
// Parse #[sampler(0, visibility(...))].
1731
List(m) if m.path == VISIBILITY => {
1732
visibility = get_visibility_flag_value(&m)?;
1733
}
1734
NameValue(m) => {
1735
return Err(Error::new_spanned(
1736
m.path,
1737
"Not a valid name. Available attributes: `sampler_type`.",
1738
));
1739
}
1740
_ => {
1741
return Err(Error::new_spanned(
1742
meta,
1743
"Not a name value pair: `foo = \"...\"`",
1744
));
1745
}
1746
}
1747
}
1748
1749
Ok(SamplerAttrs {
1750
sampler_binding_type,
1751
visibility,
1752
})
1753
}
1754
1755
fn get_sampler_binding_type_value(lit_str: &LitStr) -> Result<SamplerBindingType> {
1756
match lit_str.value().as_str() {
1757
FILTERING => Ok(SamplerBindingType::Filtering),
1758
NON_FILTERING => Ok(SamplerBindingType::NonFiltering),
1759
COMPARISON => Ok(SamplerBindingType::Comparison),
1760
1761
_ => Err(Error::new_spanned(
1762
lit_str,
1763
"Not a valid dimension. Must be `filtering`, `non_filtering`, or `comparison`.",
1764
)),
1765
}
1766
}
1767
1768
#[derive(Default)]
1769
struct StorageAttrs {
1770
visibility: ShaderStageVisibility,
1771
binding_array: Option<u32>,
1772
read_only: bool,
1773
buffer: bool,
1774
}
1775
1776
const READ_ONLY: Symbol = Symbol("read_only");
1777
const BUFFER: Symbol = Symbol("buffer");
1778
1779
fn get_storage_binding_attr(metas: Vec<Meta>) -> Result<StorageAttrs> {
1780
let mut visibility = ShaderStageVisibility::vertex_fragment();
1781
let mut binding_array = None;
1782
let mut read_only = false;
1783
let mut buffer = false;
1784
1785
for meta in metas {
1786
use syn::Meta::{List, Path};
1787
match meta {
1788
// Parse #[storage(0, visibility(...))].
1789
List(m) if m.path == VISIBILITY => {
1790
visibility = get_visibility_flag_value(&m)?;
1791
}
1792
// Parse #[storage(0, binding_array(...))] for bindless mode.
1793
List(m) if m.path == BINDING_ARRAY_MODIFIER_NAME => {
1794
binding_array = Some(get_binding_array_flag_value(&m)?);
1795
}
1796
Path(path) if path == READ_ONLY => {
1797
read_only = true;
1798
}
1799
Path(path) if path == BUFFER => {
1800
buffer = true;
1801
}
1802
_ => {
1803
return Err(Error::new_spanned(
1804
meta,
1805
"Not a valid attribute. Available attributes: `read_only`, `visibility`",
1806
));
1807
}
1808
}
1809
}
1810
1811
Ok(StorageAttrs {
1812
visibility,
1813
binding_array,
1814
read_only,
1815
buffer,
1816
})
1817
}
1818
1819