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