Path: blob/main/crates/bevy_render/macros/src/as_bind_group.rs
6596 views
use bevy_macro_utils::{get_lit_bool, get_lit_str, BevyManifest, Symbol};1use proc_macro::TokenStream;2use proc_macro2::{Ident, Span};3use quote::{quote, ToTokens};4use syn::{5parenthesized,6parse::{Parse, ParseStream},7punctuated::Punctuated,8token::{Comma, DotDot},9Data, DataStruct, Error, Fields, LitInt, LitStr, Meta, MetaList, Result,10};1112const UNIFORM_ATTRIBUTE_NAME: Symbol = Symbol("uniform");13const TEXTURE_ATTRIBUTE_NAME: Symbol = Symbol("texture");14const STORAGE_TEXTURE_ATTRIBUTE_NAME: Symbol = Symbol("storage_texture");15const SAMPLER_ATTRIBUTE_NAME: Symbol = Symbol("sampler");16const STORAGE_ATTRIBUTE_NAME: Symbol = Symbol("storage");17const BIND_GROUP_DATA_ATTRIBUTE_NAME: Symbol = Symbol("bind_group_data");18const BINDLESS_ATTRIBUTE_NAME: Symbol = Symbol("bindless");19const DATA_ATTRIBUTE_NAME: Symbol = Symbol("data");20const BINDING_ARRAY_MODIFIER_NAME: Symbol = Symbol("binding_array");21const LIMIT_MODIFIER_NAME: Symbol = Symbol("limit");22const INDEX_TABLE_MODIFIER_NAME: Symbol = Symbol("index_table");23const RANGE_MODIFIER_NAME: Symbol = Symbol("range");24const BINDING_MODIFIER_NAME: Symbol = Symbol("binding");2526#[derive(Copy, Clone, Debug)]27enum BindingType {28Uniform,29Texture,30StorageTexture,31Sampler,32Storage,33}3435#[derive(Clone)]36enum BindingState<'a> {37Free,38Occupied {39binding_type: BindingType,40ident: &'a Ident,41},42OccupiedConvertedUniform,43OccupiedMergeableUniform {44uniform_fields: Vec<&'a syn::Field>,45},46}4748enum BindlessSlabResourceLimitAttr {49Auto,50Limit(LitInt),51}5253// The `bindless(index_table(range(M..N)))` attribute.54struct BindlessIndexTableRangeAttr {55start: LitInt,56end: LitInt,57}5859pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {60let manifest = BevyManifest::shared();61let render_path = manifest.get_path("bevy_render");62let image_path = manifest.get_path("bevy_image");63let asset_path = manifest.get_path("bevy_asset");64let ecs_path = manifest.get_path("bevy_ecs");6566let mut binding_states: Vec<BindingState> = Vec::new();67let mut binding_impls = Vec::new();68let mut bindless_binding_layouts = Vec::new();69let mut non_bindless_binding_layouts = Vec::new();70let mut bindless_resource_types = Vec::new();71let mut bindless_buffer_descriptors = Vec::new();72let mut attr_prepared_data_ident = None;73// After the first attribute pass, this will be `None` if the object isn't74// bindless and `Some` if it is.75let mut attr_bindless_count = None;76let mut attr_bindless_index_table_range = None;77let mut attr_bindless_index_table_binding = None;7879// `actual_bindless_slot_count` holds the actual number of bindless slots80// per bind group, taking into account whether the current platform supports81// bindless resources.82let actual_bindless_slot_count = Ident::new("actual_bindless_slot_count", Span::call_site());83let bind_group_layout_entries = Ident::new("bind_group_layout_entries", Span::call_site());8485// The `BufferBindingType` and corresponding `BufferUsages` used for86// uniforms. We need this because bindless uniforms don't exist, so in87// bindless mode we must promote uniforms to storage buffers.88let uniform_binding_type = Ident::new("uniform_binding_type", Span::call_site());89let uniform_buffer_usages = Ident::new("uniform_buffer_usages", Span::call_site());9091// Read struct-level attributes, first pass.92for attr in &ast.attrs {93if let Some(attr_ident) = attr.path().get_ident() {94if attr_ident == BIND_GROUP_DATA_ATTRIBUTE_NAME {95if let Ok(prepared_data_ident) =96attr.parse_args_with(|input: ParseStream| input.parse::<Ident>())97{98attr_prepared_data_ident = Some(prepared_data_ident);99}100} else if attr_ident == BINDLESS_ATTRIBUTE_NAME {101attr_bindless_count = Some(BindlessSlabResourceLimitAttr::Auto);102if let Meta::List(_) = attr.meta {103// Parse bindless features.104attr.parse_nested_meta(|submeta| {105if submeta.path.is_ident(&LIMIT_MODIFIER_NAME) {106let content;107parenthesized!(content in submeta.input);108let lit: LitInt = content.parse()?;109110attr_bindless_count = Some(BindlessSlabResourceLimitAttr::Limit(lit));111return Ok(());112}113114if submeta.path.is_ident(&INDEX_TABLE_MODIFIER_NAME) {115submeta.parse_nested_meta(|subsubmeta| {116if subsubmeta.path.is_ident(&RANGE_MODIFIER_NAME) {117let content;118parenthesized!(content in subsubmeta.input);119let start: LitInt = content.parse()?;120content.parse::<DotDot>()?;121let end: LitInt = content.parse()?;122attr_bindless_index_table_range =123Some(BindlessIndexTableRangeAttr { start, end });124return Ok(());125}126127if subsubmeta.path.is_ident(&BINDING_MODIFIER_NAME) {128let content;129parenthesized!(content in subsubmeta.input);130let lit: LitInt = content.parse()?;131132attr_bindless_index_table_binding = Some(lit);133return Ok(());134}135136Err(Error::new_spanned(137attr,138"Expected `range(M..N)` or `binding(N)`",139))140})?;141return Ok(());142}143144Err(Error::new_spanned(145attr,146"Expected `limit` or `index_table`",147))148})?;149}150}151}152}153154// Read struct-level attributes, second pass.155for attr in &ast.attrs {156if let Some(attr_ident) = attr.path().get_ident()157&& (attr_ident == UNIFORM_ATTRIBUTE_NAME || attr_ident == DATA_ATTRIBUTE_NAME)158{159let UniformBindingAttr {160binding_type,161binding_index,162converted_shader_type,163binding_array: binding_array_binding,164} = get_uniform_binding_attr(attr)?;165match binding_type {166UniformBindingAttrType::Uniform => {167binding_impls.push(quote! {{168use #render_path::render_resource::AsBindGroupShaderType;169let mut buffer = #render_path::render_resource::encase::UniformBuffer::new(Vec::new());170let converted: #converted_shader_type = self.as_bind_group_shader_type(&images);171buffer.write(&converted).unwrap();172(173#binding_index,174#render_path::render_resource::OwnedBindingResource::Buffer(render_device.create_buffer_with_data(175&#render_path::render_resource::BufferInitDescriptor {176label: None,177usage: #uniform_buffer_usages,178contents: buffer.as_ref(),179},180))181)182}});183184match (&binding_array_binding, &attr_bindless_count) {185(&None, &Some(_)) => {186return Err(Error::new_spanned(187attr,188"Must specify `binding_array(...)` with `#[uniform]` if the \189object is bindless",190));191}192(&Some(_), &None) => {193return Err(Error::new_spanned(194attr,195"`binding_array(...)` with `#[uniform]` requires the object to \196be bindless",197));198}199_ => {}200}201202let binding_array_binding = binding_array_binding.unwrap_or(0);203bindless_binding_layouts.push(quote! {204#bind_group_layout_entries.push(205#render_path::render_resource::BindGroupLayoutEntry {206binding: #binding_array_binding,207visibility: #render_path::render_resource::ShaderStages::FRAGMENT | #render_path::render_resource::ShaderStages::VERTEX | #render_path::render_resource::ShaderStages::COMPUTE,208ty: #render_path::render_resource::BindingType::Buffer {209ty: #uniform_binding_type,210has_dynamic_offset: false,211min_binding_size: Some(<#converted_shader_type as #render_path::render_resource::ShaderType>::min_size()),212},213count: #actual_bindless_slot_count,214}215);216});217218add_bindless_resource_type(219&render_path,220&mut bindless_resource_types,221binding_index,222quote! { #render_path::render_resource::BindlessResourceType::Buffer },223);224}225226UniformBindingAttrType::Data => {227binding_impls.push(quote! {{228use #render_path::render_resource::AsBindGroupShaderType;229use #render_path::render_resource::encase::{ShaderType, internal::WriteInto};230let mut buffer: Vec<u8> = Vec::new();231let converted: #converted_shader_type = self.as_bind_group_shader_type(&images);232converted.write_into(233&mut #render_path::render_resource::encase::internal::Writer::new(234&converted,235&mut buffer,2360,237).unwrap(),238);239let min_size = <#converted_shader_type as #render_path::render_resource::ShaderType>::min_size().get() as usize;240while buffer.len() < min_size {241buffer.push(0);242}243(244#binding_index,245#render_path::render_resource::OwnedBindingResource::Data(246#render_path::render_resource::OwnedData(buffer)247)248)249}});250251let binding_array_binding = binding_array_binding.unwrap_or(0);252bindless_binding_layouts.push(quote! {253#bind_group_layout_entries.push(254#render_path::render_resource::BindGroupLayoutEntry {255binding: #binding_array_binding,256visibility: #render_path::render_resource::ShaderStages::FRAGMENT | #render_path::render_resource::ShaderStages::VERTEX | #render_path::render_resource::ShaderStages::COMPUTE,257ty: #render_path::render_resource::BindingType::Buffer {258ty: #uniform_binding_type,259has_dynamic_offset: false,260min_binding_size: Some(<#converted_shader_type as #render_path::render_resource::ShaderType>::min_size()),261},262count: None,263}264);265});266267add_bindless_resource_type(268&render_path,269&mut bindless_resource_types,270binding_index,271quote! { #render_path::render_resource::BindlessResourceType::DataBuffer },272);273}274}275276// Push the non-bindless binding layout.277278non_bindless_binding_layouts.push(quote!{279#bind_group_layout_entries.push(280#render_path::render_resource::BindGroupLayoutEntry {281binding: #binding_index,282visibility: #render_path::render_resource::ShaderStages::FRAGMENT | #render_path::render_resource::ShaderStages::VERTEX | #render_path::render_resource::ShaderStages::COMPUTE,283ty: #render_path::render_resource::BindingType::Buffer {284ty: #uniform_binding_type,285has_dynamic_offset: false,286min_binding_size: Some(<#converted_shader_type as #render_path::render_resource::ShaderType>::min_size()),287},288count: None,289}290);291});292293bindless_buffer_descriptors.push(quote! {294#render_path::render_resource::BindlessBufferDescriptor {295// Note that, because this is bindless, *binding296// index* here refers to the index in the297// bindless index table (`bindless_index`), and298// the actual binding number is the *binding299// array binding*.300binding_number: #render_path::render_resource::BindingNumber(301#binding_array_binding302),303bindless_index:304#render_path::render_resource::BindlessIndex(#binding_index),305size: Some(306<307#converted_shader_type as308#render_path::render_resource::ShaderType309>::min_size().get() as usize310),311}312});313314let required_len = binding_index as usize + 1;315if required_len > binding_states.len() {316binding_states.resize(required_len, BindingState::Free);317}318binding_states[binding_index as usize] = BindingState::OccupiedConvertedUniform;319}320}321322let fields = match &ast.data {323Data::Struct(DataStruct {324fields: Fields::Named(fields),325..326}) => &fields.named,327_ => {328return Err(Error::new_spanned(329ast,330"Expected a struct with named fields",331));332}333};334335// Count the number of sampler fields needed. We might have to disable336// bindless if bindless arrays take the GPU over the maximum number of337// samplers.338let mut sampler_binding_count: u32 = 0;339340// Read field-level attributes341for field in fields {342// Search ahead for texture attributes so we can use them with any343// corresponding sampler attribute.344let mut tex_attrs = None;345for attr in &field.attrs {346let Some(attr_ident) = attr.path().get_ident() else {347continue;348};349if attr_ident == TEXTURE_ATTRIBUTE_NAME {350let (_binding_index, nested_meta_items) = get_binding_nested_attr(attr)?;351tex_attrs = Some(get_texture_attrs(nested_meta_items)?);352}353}354355for attr in &field.attrs {356let Some(attr_ident) = attr.path().get_ident() else {357continue;358};359360let binding_type = if attr_ident == UNIFORM_ATTRIBUTE_NAME {361BindingType::Uniform362} else if attr_ident == TEXTURE_ATTRIBUTE_NAME {363BindingType::Texture364} else if attr_ident == STORAGE_TEXTURE_ATTRIBUTE_NAME {365BindingType::StorageTexture366} else if attr_ident == SAMPLER_ATTRIBUTE_NAME {367BindingType::Sampler368} else if attr_ident == STORAGE_ATTRIBUTE_NAME {369BindingType::Storage370} else {371continue;372};373374let (binding_index, nested_meta_items) = get_binding_nested_attr(attr)?;375376let field_name = field.ident.as_ref().unwrap();377let required_len = binding_index as usize + 1;378if required_len > binding_states.len() {379binding_states.resize(required_len, BindingState::Free);380}381382match &mut binding_states[binding_index as usize] {383value @ BindingState::Free => {384*value = match binding_type {385BindingType::Uniform => BindingState::OccupiedMergeableUniform {386uniform_fields: vec![field],387},388_ => {389// only populate bind group entries for non-uniforms390// uniform entries are deferred until the end391BindingState::Occupied {392binding_type,393ident: field_name,394}395}396}397}398BindingState::Occupied {399binding_type,400ident: occupied_ident,401} => {402return Err(Error::new_spanned(403attr,404format!("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:?}.")405));406}407BindingState::OccupiedConvertedUniform => {408return Err(Error::new_spanned(409attr,410format!("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.")411));412}413BindingState::OccupiedMergeableUniform { uniform_fields } => match binding_type {414BindingType::Uniform => {415uniform_fields.push(field);416}417_ => {418return Err(Error::new_spanned(419attr,420format!("The '{field_name}' field cannot be assigned to binding {binding_index} because it is already occupied by a {:?}.", BindingType::Uniform)421));422}423},424}425426match binding_type {427BindingType::Uniform => {428if attr_bindless_count.is_some() {429return Err(Error::new_spanned(430attr,431"Only structure-level `#[uniform]` attributes are supported in \432bindless mode",433));434}435436// uniform codegen is deferred to account for combined uniform bindings437}438439BindingType::Storage => {440let StorageAttrs {441visibility,442binding_array: binding_array_binding,443read_only,444buffer,445} = get_storage_binding_attr(nested_meta_items)?;446let visibility =447visibility.hygienic_quote("e! { #render_path::render_resource });448449let field_name = field.ident.as_ref().unwrap();450451if buffer {452binding_impls.push(quote! {453(454#binding_index,455#render_path::render_resource::OwnedBindingResource::Buffer({456self.#field_name.clone()457})458)459});460} else {461binding_impls.push(quote! {462(463#binding_index,464#render_path::render_resource::OwnedBindingResource::Buffer({465let handle: &#asset_path::Handle<#render_path::storage::ShaderStorageBuffer> = (&self.#field_name);466storage_buffers.get(handle).ok_or_else(|| #render_path::render_resource::AsBindGroupError::RetryNextUpdate)?.buffer.clone()467})468)469});470}471472non_bindless_binding_layouts.push(quote! {473#bind_group_layout_entries.push(474#render_path::render_resource::BindGroupLayoutEntry {475binding: #binding_index,476visibility: #visibility,477ty: #render_path::render_resource::BindingType::Buffer {478ty: #render_path::render_resource::BufferBindingType::Storage { read_only: #read_only },479has_dynamic_offset: false,480min_binding_size: None,481},482count: #actual_bindless_slot_count,483}484);485});486487if let Some(binding_array_binding) = binding_array_binding {488// Add the storage buffer to the `BindlessResourceType` list489// in the bindless descriptor.490let bindless_resource_type = quote! {491#render_path::render_resource::BindlessResourceType::Buffer492};493add_bindless_resource_type(494&render_path,495&mut bindless_resource_types,496binding_index,497bindless_resource_type,498);499500// Push the buffer descriptor.501bindless_buffer_descriptors.push(quote! {502#render_path::render_resource::BindlessBufferDescriptor {503// Note that, because this is bindless, *binding504// index* here refers to the index in the bindless505// index table (`bindless_index`), and the actual506// binding number is the *binding array binding*.507binding_number: #render_path::render_resource::BindingNumber(508#binding_array_binding509),510bindless_index:511#render_path::render_resource::BindlessIndex(#binding_index),512size: None,513}514});515516// Declare the binding array.517bindless_binding_layouts.push(quote!{518#bind_group_layout_entries.push(519#render_path::render_resource::BindGroupLayoutEntry {520binding: #binding_array_binding,521visibility: #render_path::render_resource::ShaderStages::FRAGMENT | #render_path::render_resource::ShaderStages::VERTEX | #render_path::render_resource::ShaderStages::COMPUTE,522ty: #render_path::render_resource::BindingType::Buffer {523ty: #render_path::render_resource::BufferBindingType::Storage {524read_only: #read_only525},526has_dynamic_offset: false,527min_binding_size: None,528},529count: #actual_bindless_slot_count,530}531);532});533}534}535536BindingType::StorageTexture => {537if attr_bindless_count.is_some() {538return Err(Error::new_spanned(539attr,540"Storage textures are unsupported in bindless mode",541));542}543544let StorageTextureAttrs {545dimension,546image_format,547access,548visibility,549} = get_storage_texture_binding_attr(nested_meta_items)?;550551let visibility =552visibility.hygienic_quote("e! { #render_path::render_resource });553554let fallback_image = get_fallback_image(&render_path, dimension);555556// insert fallible texture-based entries at 0 so that if we fail here, we exit before allocating any buffers557binding_impls.insert(0, quote! {558( #binding_index,559#render_path::render_resource::OwnedBindingResource::TextureView(560#render_path::render_resource::#dimension,561{562let handle: Option<&#asset_path::Handle<#image_path::Image>> = (&self.#field_name).into();563if let Some(handle) = handle {564images.get(handle).ok_or_else(|| #render_path::render_resource::AsBindGroupError::RetryNextUpdate)?.texture_view.clone()565} else {566#fallback_image.texture_view.clone()567}568}569)570)571});572573non_bindless_binding_layouts.push(quote! {574#bind_group_layout_entries.push(575#render_path::render_resource::BindGroupLayoutEntry {576binding: #binding_index,577visibility: #visibility,578ty: #render_path::render_resource::BindingType::StorageTexture {579access: #render_path::render_resource::StorageTextureAccess::#access,580format: #render_path::render_resource::TextureFormat::#image_format,581view_dimension: #render_path::render_resource::#dimension,582},583count: #actual_bindless_slot_count,584}585);586});587}588589BindingType::Texture => {590let TextureAttrs {591dimension,592sample_type,593multisampled,594visibility,595} = tex_attrs.as_ref().unwrap();596597let visibility =598visibility.hygienic_quote("e! { #render_path::render_resource });599600let fallback_image = get_fallback_image(&render_path, *dimension);601602// insert fallible texture-based entries at 0 so that if we fail here, we exit before allocating any buffers603binding_impls.insert(0, quote! {604(605#binding_index,606#render_path::render_resource::OwnedBindingResource::TextureView(607#render_path::render_resource::#dimension,608{609let handle: Option<&#asset_path::Handle<#image_path::Image>> = (&self.#field_name).into();610if let Some(handle) = handle {611images.get(handle).ok_or_else(|| #render_path::render_resource::AsBindGroupError::RetryNextUpdate)?.texture_view.clone()612} else {613#fallback_image.texture_view.clone()614}615}616)617)618});619620sampler_binding_count += 1;621622non_bindless_binding_layouts.push(quote! {623#bind_group_layout_entries.push(624#render_path::render_resource::BindGroupLayoutEntry {625binding: #binding_index,626visibility: #visibility,627ty: #render_path::render_resource::BindingType::Texture {628multisampled: #multisampled,629sample_type: #render_path::render_resource::#sample_type,630view_dimension: #render_path::render_resource::#dimension,631},632count: #actual_bindless_slot_count,633}634);635});636637let bindless_resource_type = match *dimension {638BindingTextureDimension::D1 => {639quote! {640#render_path::render_resource::BindlessResourceType::Texture1d641}642}643BindingTextureDimension::D2 => {644quote! {645#render_path::render_resource::BindlessResourceType::Texture2d646}647}648BindingTextureDimension::D2Array => {649quote! {650#render_path::render_resource::BindlessResourceType::Texture2dArray651}652}653BindingTextureDimension::Cube => {654quote! {655#render_path::render_resource::BindlessResourceType::TextureCube656}657}658BindingTextureDimension::CubeArray => {659quote! {660#render_path::render_resource::BindlessResourceType::TextureCubeArray661}662}663BindingTextureDimension::D3 => {664quote! {665#render_path::render_resource::BindlessResourceType::Texture3d666}667}668};669670// Add the texture to the `BindlessResourceType` list in the671// bindless descriptor.672add_bindless_resource_type(673&render_path,674&mut bindless_resource_types,675binding_index,676bindless_resource_type,677);678}679680BindingType::Sampler => {681let SamplerAttrs {682sampler_binding_type,683visibility,684..685} = get_sampler_attrs(nested_meta_items)?;686let TextureAttrs { dimension, .. } = tex_attrs687.as_ref()688.expect("sampler attribute must have matching texture attribute");689690let visibility =691visibility.hygienic_quote("e! { #render_path::render_resource });692693let fallback_image = get_fallback_image(&render_path, *dimension);694695let expected_samplers = match sampler_binding_type {696SamplerBindingType::Filtering => {697quote!( [#render_path::render_resource::TextureSampleType::Float { filterable: true }] )698}699SamplerBindingType::NonFiltering => quote!([700#render_path::render_resource::TextureSampleType::Float { filterable: false },701#render_path::render_resource::TextureSampleType::Sint,702#render_path::render_resource::TextureSampleType::Uint,703]),704SamplerBindingType::Comparison => {705quote!( [#render_path::render_resource::TextureSampleType::Depth] )706}707};708709// insert fallible texture-based entries at 0 so that if we fail here, we exit before allocating any buffers710binding_impls.insert(0, quote! {711(712#binding_index,713#render_path::render_resource::OwnedBindingResource::Sampler(714// TODO: Support other types.715#render_path::render_resource::WgpuSamplerBindingType::Filtering,716{717let handle: Option<&#asset_path::Handle<#image_path::Image>> = (&self.#field_name).into();718if let Some(handle) = handle {719let image = images.get(handle).ok_or_else(|| #render_path::render_resource::AsBindGroupError::RetryNextUpdate)?;720721let Some(sample_type) = image.texture_format.sample_type(None, Some(render_device.features())) else {722return Err(#render_path::render_resource::AsBindGroupError::InvalidSamplerType(723#binding_index,724"None".to_string(),725format!("{:?}", #expected_samplers),726));727};728729let valid = #expected_samplers.contains(&sample_type);730731if !valid {732return Err(#render_path::render_resource::AsBindGroupError::InvalidSamplerType(733#binding_index,734format!("{:?}", sample_type),735format!("{:?}", #expected_samplers),736));737}738image.sampler.clone()739} else {740#fallback_image.sampler.clone()741}742})743)744});745746sampler_binding_count += 1;747748non_bindless_binding_layouts.push(quote!{749#bind_group_layout_entries.push(750#render_path::render_resource::BindGroupLayoutEntry {751binding: #binding_index,752visibility: #visibility,753ty: #render_path::render_resource::BindingType::Sampler(#render_path::render_resource::#sampler_binding_type),754count: #actual_bindless_slot_count,755}756);757});758759// Add the sampler to the `BindlessResourceType` list in the760// bindless descriptor.761//762// TODO: Support other types of samplers.763add_bindless_resource_type(764&render_path,765&mut bindless_resource_types,766binding_index,767quote! {768#render_path::render_resource::BindlessResourceType::SamplerFiltering769},770);771}772}773}774}775776// Produce impls for fields with uniform bindings777let struct_name = &ast.ident;778let struct_name_literal = struct_name.to_string();779let struct_name_literal = struct_name_literal.as_str();780let mut field_struct_impls = Vec::new();781782let uniform_binding_type_declarations = match attr_bindless_count {783Some(_) => {784quote! {785let (#uniform_binding_type, #uniform_buffer_usages) =786if Self::bindless_supported(render_device) && !force_no_bindless {787(788#render_path::render_resource::BufferBindingType::Storage { read_only: true },789#render_path::render_resource::BufferUsages::STORAGE,790)791} else {792(793#render_path::render_resource::BufferBindingType::Uniform,794#render_path::render_resource::BufferUsages::UNIFORM,795)796};797}798}799None => {800quote! {801let (#uniform_binding_type, #uniform_buffer_usages) = (802#render_path::render_resource::BufferBindingType::Uniform,803#render_path::render_resource::BufferUsages::UNIFORM,804);805}806}807};808809for (binding_index, binding_state) in binding_states.iter().enumerate() {810let binding_index = binding_index as u32;811if let BindingState::OccupiedMergeableUniform { uniform_fields } = binding_state {812// single field uniform bindings for a given index can use a straightforward binding813if uniform_fields.len() == 1 {814let field = &uniform_fields[0];815let field_name = field.ident.as_ref().unwrap();816let field_ty = &field.ty;817binding_impls.push(quote! {{818let mut buffer = #render_path::render_resource::encase::UniformBuffer::new(Vec::new());819buffer.write(&self.#field_name).unwrap();820(821#binding_index,822#render_path::render_resource::OwnedBindingResource::Buffer(render_device.create_buffer_with_data(823&#render_path::render_resource::BufferInitDescriptor {824label: None,825usage: #uniform_buffer_usages,826contents: buffer.as_ref(),827},828))829)830}});831832non_bindless_binding_layouts.push(quote!{833#bind_group_layout_entries.push(834#render_path::render_resource::BindGroupLayoutEntry {835binding: #binding_index,836visibility: #render_path::render_resource::ShaderStages::FRAGMENT | #render_path::render_resource::ShaderStages::VERTEX | #render_path::render_resource::ShaderStages::COMPUTE,837ty: #render_path::render_resource::BindingType::Buffer {838ty: #uniform_binding_type,839has_dynamic_offset: false,840min_binding_size: Some(<#field_ty as #render_path::render_resource::ShaderType>::min_size()),841},842count: #actual_bindless_slot_count,843}844);845});846// multi-field uniform bindings for a given index require an intermediate struct to derive ShaderType847} else {848let uniform_struct_name = Ident::new(849&format!("_{struct_name}AsBindGroupUniformStructBindGroup{binding_index}"),850Span::call_site(),851);852853let field_name = uniform_fields.iter().map(|f| f.ident.as_ref().unwrap());854let field_type = uniform_fields.iter().map(|f| &f.ty);855field_struct_impls.push(quote! {856#[derive(#render_path::render_resource::ShaderType)]857struct #uniform_struct_name<'a> {858#(#field_name: &'a #field_type,)*859}860});861862let field_name = uniform_fields.iter().map(|f| f.ident.as_ref().unwrap());863binding_impls.push(quote! {{864let mut buffer = #render_path::render_resource::encase::UniformBuffer::new(Vec::new());865buffer.write(&#uniform_struct_name {866#(#field_name: &self.#field_name,)*867}).unwrap();868(869#binding_index,870#render_path::render_resource::OwnedBindingResource::Buffer(render_device.create_buffer_with_data(871&#render_path::render_resource::BufferInitDescriptor {872label: None,873usage: #uniform_buffer_usages,874contents: buffer.as_ref(),875},876))877)878}});879880non_bindless_binding_layouts.push(quote!{881#bind_group_layout_entries.push(#render_path::render_resource::BindGroupLayoutEntry {882binding: #binding_index,883visibility: #render_path::render_resource::ShaderStages::FRAGMENT | #render_path::render_resource::ShaderStages::VERTEX | #render_path::render_resource::ShaderStages::COMPUTE,884ty: #render_path::render_resource::BindingType::Buffer {885ty: #uniform_binding_type,886has_dynamic_offset: false,887min_binding_size: Some(<#uniform_struct_name as #render_path::render_resource::ShaderType>::min_size()),888},889count: #actual_bindless_slot_count,890});891});892}893}894}895896let generics = ast.generics;897let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();898899let (prepared_data, get_prepared_data) = if let Some(prepared) = attr_prepared_data_ident {900let get_prepared_data = quote! { self.into() };901(quote! {#prepared}, get_prepared_data)902} else {903let prepared_data = quote! { () };904(prepared_data.clone(), prepared_data)905};906907// Calculate the number of samplers that we need, so that we don't go over908// the limit on certain platforms. See909// https://github.com/bevyengine/bevy/issues/16988.910let bindless_count_syntax = match attr_bindless_count {911Some(BindlessSlabResourceLimitAttr::Auto) => {912quote! { #render_path::render_resource::AUTO_BINDLESS_SLAB_RESOURCE_LIMIT }913}914Some(BindlessSlabResourceLimitAttr::Limit(ref count)) => {915quote! { #count }916}917None => quote! { 0 },918};919920// Calculate the actual bindless index table range, taking the921// `#[bindless(index_table(range(M..N)))]` attribute into account.922let bindless_index_table_range = match attr_bindless_index_table_range {923None => {924let resource_count = bindless_resource_types.len() as u32;925quote! {926#render_path::render_resource::BindlessIndex(0)..927#render_path::render_resource::BindlessIndex(#resource_count)928}929}930Some(BindlessIndexTableRangeAttr { start, end }) => {931quote! {932#render_path::render_resource::BindlessIndex(#start)..933#render_path::render_resource::BindlessIndex(#end)934}935}936};937938// Calculate the actual binding number of the bindless index table, taking939// the `#[bindless(index_table(binding(B)))]` into account.940let bindless_index_table_binding_number = match attr_bindless_index_table_binding {941None => quote! { #render_path::render_resource::BindingNumber(0) },942Some(binding_number) => {943quote! { #render_path::render_resource::BindingNumber(#binding_number) }944}945};946947// Calculate the actual number of bindless slots, taking hardware948// limitations into account.949let (bindless_slot_count, actual_bindless_slot_count_declaration, bindless_descriptor_syntax) =950match attr_bindless_count {951Some(ref bindless_count) => {952let bindless_supported_syntax = quote! {953fn bindless_supported(954render_device: &#render_path::renderer::RenderDevice955) -> bool {956render_device.features().contains(957#render_path::settings::WgpuFeatures::BUFFER_BINDING_ARRAY |958#render_path::settings::WgpuFeatures::TEXTURE_BINDING_ARRAY959) &&960render_device.limits().max_storage_buffers_per_shader_stage > 0 &&961render_device.limits().max_samplers_per_shader_stage >=962(#sampler_binding_count * #bindless_count_syntax)963}964};965let actual_bindless_slot_count_declaration = quote! {966let #actual_bindless_slot_count = if Self::bindless_supported(render_device) &&967!force_no_bindless {968::core::num::NonZeroU32::new(#bindless_count_syntax)969} else {970None971};972};973let bindless_slot_count_declaration = match bindless_count {974BindlessSlabResourceLimitAttr::Auto => {975quote! {976fn bindless_slot_count() -> Option<977#render_path::render_resource::BindlessSlabResourceLimit978> {979Some(#render_path::render_resource::BindlessSlabResourceLimit::Auto)980}981}982}983BindlessSlabResourceLimitAttr::Limit(lit) => {984quote! {985fn bindless_slot_count() -> Option<986#render_path::render_resource::BindlessSlabResourceLimit987> {988Some(#render_path::render_resource::BindlessSlabResourceLimit::Custom(#lit))989}990}991}992};993994let bindless_buffer_descriptor_count = bindless_buffer_descriptors.len();995996// We use `LazyLock` so that we can call `min_size`, which isn't997// a `const fn`.998let bindless_descriptor_syntax = quote! {999static RESOURCES: &[#render_path::render_resource::BindlessResourceType] = &[1000#(#bindless_resource_types),*1001];1002static BUFFERS: ::std::sync::LazyLock<[1003#render_path::render_resource::BindlessBufferDescriptor;1004#bindless_buffer_descriptor_count1005]> = ::std::sync::LazyLock::new(|| {1006[#(#bindless_buffer_descriptors),*]1007});1008static INDEX_TABLES: &[1009#render_path::render_resource::BindlessIndexTableDescriptor1010] = &[1011#render_path::render_resource::BindlessIndexTableDescriptor {1012indices: #bindless_index_table_range,1013binding_number: #bindless_index_table_binding_number,1014}1015];1016Some(#render_path::render_resource::BindlessDescriptor {1017resources: ::std::borrow::Cow::Borrowed(RESOURCES),1018buffers: ::std::borrow::Cow::Borrowed(&*BUFFERS),1019index_tables: ::std::borrow::Cow::Borrowed(&*INDEX_TABLES),1020})1021};10221023(1024quote! {1025#bindless_slot_count_declaration1026#bindless_supported_syntax1027},1028actual_bindless_slot_count_declaration,1029bindless_descriptor_syntax,1030)1031}1032None => (1033TokenStream::new().into(),1034quote! { let #actual_bindless_slot_count: Option<::core::num::NonZeroU32> = None; },1035quote! { None },1036),1037};10381039Ok(TokenStream::from(quote! {1040#(#field_struct_impls)*10411042impl #impl_generics #render_path::render_resource::AsBindGroup for #struct_name #ty_generics #where_clause {1043type Data = #prepared_data;10441045type Param = (1046#ecs_path::system::lifetimeless::SRes<#render_path::render_asset::RenderAssets<#render_path::texture::GpuImage>>,1047#ecs_path::system::lifetimeless::SRes<#render_path::texture::FallbackImage>,1048#ecs_path::system::lifetimeless::SRes<#render_path::render_asset::RenderAssets<#render_path::storage::GpuShaderStorageBuffer>>,1049);10501051#bindless_slot_count10521053fn label() -> Option<&'static str> {1054Some(#struct_name_literal)1055}10561057fn unprepared_bind_group(1058&self,1059layout: &#render_path::render_resource::BindGroupLayout,1060render_device: &#render_path::renderer::RenderDevice,1061(images, fallback_image, storage_buffers): &mut #ecs_path::system::SystemParamItem<'_, '_, Self::Param>,1062force_no_bindless: bool,1063) -> Result<#render_path::render_resource::UnpreparedBindGroup, #render_path::render_resource::AsBindGroupError> {1064#uniform_binding_type_declarations10651066let bindings = #render_path::render_resource::BindingResources(vec![#(#binding_impls,)*]);10671068Ok(#render_path::render_resource::UnpreparedBindGroup {1069bindings,1070})1071}10721073#[allow(clippy::unused_unit)]1074fn bind_group_data(&self) -> Self::Data {1075#get_prepared_data1076}10771078fn bind_group_layout_entries(1079render_device: &#render_path::renderer::RenderDevice,1080force_no_bindless: bool1081) -> Vec<#render_path::render_resource::BindGroupLayoutEntry> {1082#actual_bindless_slot_count_declaration1083#uniform_binding_type_declarations10841085let mut #bind_group_layout_entries = Vec::new();1086match #actual_bindless_slot_count {1087Some(bindless_slot_count) => {1088let bindless_index_table_range = #bindless_index_table_range;1089#bind_group_layout_entries.extend(1090#render_path::render_resource::create_bindless_bind_group_layout_entries(1091bindless_index_table_range.end.0 -1092bindless_index_table_range.start.0,1093bindless_slot_count.into(),1094#bindless_index_table_binding_number,1095).into_iter()1096);1097#(#bindless_binding_layouts)*;1098}1099None => {1100#(#non_bindless_binding_layouts)*;1101}1102};1103#bind_group_layout_entries1104}11051106fn bindless_descriptor() -> Option<#render_path::render_resource::BindlessDescriptor> {1107#bindless_descriptor_syntax1108}1109}1110}))1111}11121113/// Adds a bindless resource type to the `BindlessResourceType` array in the1114/// bindless descriptor we're building up.1115///1116/// See the `bevy_render::render_resource::bindless::BindlessResourceType`1117/// documentation for more information.1118fn add_bindless_resource_type(1119render_path: &syn::Path,1120bindless_resource_types: &mut Vec<proc_macro2::TokenStream>,1121binding_index: u32,1122bindless_resource_type: proc_macro2::TokenStream,1123) {1124// If we need to grow the array, pad the unused fields with1125// `BindlessResourceType::None`.1126if bindless_resource_types.len() < (binding_index as usize + 1) {1127bindless_resource_types.resize_with(binding_index as usize + 1, || {1128quote! { #render_path::render_resource::BindlessResourceType::None }1129});1130}11311132// Assign the `BindlessResourceType`.1133bindless_resource_types[binding_index as usize] = bindless_resource_type;1134}11351136fn get_fallback_image(1137render_path: &syn::Path,1138dimension: BindingTextureDimension,1139) -> proc_macro2::TokenStream {1140quote! {1141match #render_path::render_resource::#dimension {1142#render_path::render_resource::TextureViewDimension::D1 => &fallback_image.d1,1143#render_path::render_resource::TextureViewDimension::D2 => &fallback_image.d2,1144#render_path::render_resource::TextureViewDimension::D2Array => &fallback_image.d2_array,1145#render_path::render_resource::TextureViewDimension::Cube => &fallback_image.cube,1146#render_path::render_resource::TextureViewDimension::CubeArray => &fallback_image.cube_array,1147#render_path::render_resource::TextureViewDimension::D3 => &fallback_image.d3,1148}1149}1150}11511152/// Represents the arguments for the `uniform` binding attribute.1153///1154/// If parsed, represents an attribute1155/// like `#[uniform(LitInt, Ident)]`1156struct UniformBindingMeta {1157lit_int: LitInt,1158ident: Ident,1159binding_array: Option<LitInt>,1160}11611162/// The parsed structure-level `#[uniform]` or `#[data]` attribute.1163///1164/// The corresponding syntax is `#[uniform(BINDING_INDEX, CONVERTED_SHADER_TYPE,1165/// binding_array(BINDING_ARRAY)]`, optionally replacing `uniform` with `data`.1166struct UniformBindingAttr {1167/// Whether the declaration is `#[uniform]` or `#[data]`.1168binding_type: UniformBindingAttrType,1169/// The binding index.1170binding_index: u32,1171/// The uniform data type.1172converted_shader_type: Ident,1173/// The binding number of the binding array, if this is a bindless material.1174binding_array: Option<u32>,1175}11761177/// Whether a structure-level shader type declaration is `#[uniform]` or1178/// `#[data]`.1179enum UniformBindingAttrType {1180/// `#[uniform]`: i.e. in bindless mode, we need a separate buffer per data1181/// instance.1182Uniform,1183/// `#[data]`: i.e. in bindless mode, we concatenate all instance data into1184/// a single buffer.1185Data,1186}11871188/// Represents the arguments for any general binding attribute.1189///1190/// If parsed, represents an attribute1191/// like `#[foo(LitInt, ...)]` where the rest is optional [`Meta`].1192enum BindingMeta {1193IndexOnly(LitInt),1194IndexWithOptions(BindingIndexOptions),1195}11961197/// Represents the arguments for an attribute with a list of arguments.1198///1199/// This represents, for example, `#[texture(0, dimension = "2d_array")]`.1200struct BindingIndexOptions {1201lit_int: LitInt,1202_comma: Comma,1203meta_list: Punctuated<Meta, Comma>,1204}12051206impl Parse for BindingMeta {1207fn parse(input: ParseStream) -> Result<Self> {1208if input.peek2(Comma) {1209input.parse().map(Self::IndexWithOptions)1210} else {1211input.parse().map(Self::IndexOnly)1212}1213}1214}12151216impl Parse for BindingIndexOptions {1217fn parse(input: ParseStream) -> Result<Self> {1218Ok(Self {1219lit_int: input.parse()?,1220_comma: input.parse()?,1221meta_list: input.parse_terminated(Meta::parse, Comma)?,1222})1223}1224}12251226impl Parse for UniformBindingMeta {1227// Parse syntax like `#[uniform(0, StandardMaterial, binding_array(10))]`.1228fn parse(input: ParseStream) -> Result<Self> {1229let lit_int = input.parse()?;1230input.parse::<Comma>()?;1231let ident = input.parse()?;12321233// Look for a `binding_array(BINDING_NUMBER)` declaration.1234let mut binding_array: Option<LitInt> = None;1235if input.parse::<Comma>().is_ok() {1236if input1237.parse::<syn::Path>()?1238.get_ident()1239.is_none_or(|ident| *ident != BINDING_ARRAY_MODIFIER_NAME)1240{1241return Err(Error::new_spanned(ident, "Expected `binding_array`"));1242}1243let parser;1244parenthesized!(parser in input);1245binding_array = Some(parser.parse()?);1246}12471248Ok(Self {1249lit_int,1250ident,1251binding_array,1252})1253}1254}12551256/// Parses a structure-level `#[uniform]` attribute (not a field-level1257/// `#[uniform]` attribute).1258fn get_uniform_binding_attr(attr: &syn::Attribute) -> Result<UniformBindingAttr> {1259let attr_ident = attr1260.path()1261.get_ident()1262.expect("Shouldn't be here if we didn't have an attribute");12631264let uniform_binding_meta = attr.parse_args_with(UniformBindingMeta::parse)?;12651266let binding_index = uniform_binding_meta.lit_int.base10_parse()?;1267let ident = uniform_binding_meta.ident;1268let binding_array = match uniform_binding_meta.binding_array {1269None => None,1270Some(binding_array) => Some(binding_array.base10_parse()?),1271};12721273Ok(UniformBindingAttr {1274binding_type: if attr_ident == UNIFORM_ATTRIBUTE_NAME {1275UniformBindingAttrType::Uniform1276} else {1277UniformBindingAttrType::Data1278},1279binding_index,1280converted_shader_type: ident,1281binding_array,1282})1283}12841285fn get_binding_nested_attr(attr: &syn::Attribute) -> Result<(u32, Vec<Meta>)> {1286let binding_meta = attr.parse_args_with(BindingMeta::parse)?;12871288match binding_meta {1289BindingMeta::IndexOnly(lit_int) => Ok((lit_int.base10_parse()?, Vec::new())),1290BindingMeta::IndexWithOptions(BindingIndexOptions {1291lit_int,1292_comma: _,1293meta_list,1294}) => Ok((lit_int.base10_parse()?, meta_list.into_iter().collect())),1295}1296}12971298#[derive(Default)]1299enum ShaderStageVisibility {1300#[default]1301All,1302None,1303Flags(VisibilityFlags),1304}13051306#[derive(Default)]1307struct VisibilityFlags {1308vertex: bool,1309fragment: bool,1310compute: bool,1311}13121313impl ShaderStageVisibility {1314fn vertex_fragment() -> Self {1315Self::Flags(VisibilityFlags::vertex_fragment())1316}13171318fn compute() -> Self {1319Self::Flags(VisibilityFlags::compute())1320}1321}13221323impl VisibilityFlags {1324fn vertex_fragment() -> Self {1325Self {1326vertex: true,1327fragment: true,1328..Default::default()1329}1330}13311332fn compute() -> Self {1333Self {1334compute: true,1335..Default::default()1336}1337}1338}13391340impl ShaderStageVisibility {1341fn hygienic_quote(&self, path: &proc_macro2::TokenStream) -> proc_macro2::TokenStream {1342match self {1343ShaderStageVisibility::All => quote! {1344if cfg!(feature = "webgpu") {1345todo!("Please use a more specific shader stage: https://github.com/gfx-rs/wgpu/issues/7708")1346} else {1347#path::ShaderStages::all()1348}1349},1350ShaderStageVisibility::None => quote! { #path::ShaderStages::NONE },1351ShaderStageVisibility::Flags(flags) => {1352let mut quoted = Vec::new();13531354if flags.vertex {1355quoted.push(quote! { #path::ShaderStages::VERTEX });1356}1357if flags.fragment {1358quoted.push(quote! { #path::ShaderStages::FRAGMENT });1359}1360if flags.compute {1361quoted.push(quote! { #path::ShaderStages::COMPUTE });1362}13631364quote! { #(#quoted)|* }1365}1366}1367}1368}13691370const VISIBILITY: Symbol = Symbol("visibility");1371const VISIBILITY_VERTEX: Symbol = Symbol("vertex");1372const VISIBILITY_FRAGMENT: Symbol = Symbol("fragment");1373const VISIBILITY_COMPUTE: Symbol = Symbol("compute");1374const VISIBILITY_ALL: Symbol = Symbol("all");1375const VISIBILITY_NONE: Symbol = Symbol("none");13761377fn get_visibility_flag_value(meta_list: &MetaList) -> Result<ShaderStageVisibility> {1378let mut flags = Vec::new();13791380meta_list.parse_nested_meta(|meta| {1381flags.push(meta.path);1382Ok(())1383})?;13841385if flags.is_empty() {1386return Err(Error::new_spanned(1387meta_list,1388"Invalid visibility format. Must be `visibility(flags)`, flags can be `all`, `none`, or a list-combination of `vertex`, `fragment` and/or `compute`."1389));1390}13911392if flags.len() == 11393&& let Some(flag) = flags.first()1394{1395if flag == VISIBILITY_ALL {1396return Ok(ShaderStageVisibility::All);1397} else if flag == VISIBILITY_NONE {1398return Ok(ShaderStageVisibility::None);1399}1400}14011402let mut visibility = VisibilityFlags::default();14031404for flag in flags {1405if flag == VISIBILITY_VERTEX {1406visibility.vertex = true;1407} else if flag == VISIBILITY_FRAGMENT {1408visibility.fragment = true;1409} else if flag == VISIBILITY_COMPUTE {1410visibility.compute = true;1411} else {1412return Err(Error::new_spanned(1413flag,1414"Not a valid visibility flag. Must be `all`, `none`, or a list-combination of `vertex`, `fragment` and/or `compute`."1415));1416}1417}14181419Ok(ShaderStageVisibility::Flags(visibility))1420}14211422// Returns the `binding_array(10)` part of a field-level declaration like1423// `#[storage(binding_array(10))]`.1424fn get_binding_array_flag_value(meta_list: &MetaList) -> Result<u32> {1425meta_list1426.parse_args_with(|input: ParseStream| input.parse::<LitInt>())?1427.base10_parse()1428}14291430#[derive(Clone, Copy, Default)]1431enum BindingTextureDimension {1432D1,1433#[default]1434D2,1435D2Array,1436Cube,1437CubeArray,1438D3,1439}14401441enum BindingTextureSampleType {1442Float { filterable: bool },1443Depth,1444Sint,1445Uint,1446}14471448impl ToTokens for BindingTextureDimension {1449fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {1450tokens.extend(match self {1451BindingTextureDimension::D1 => quote! { TextureViewDimension::D1 },1452BindingTextureDimension::D2 => quote! { TextureViewDimension::D2 },1453BindingTextureDimension::D2Array => quote! { TextureViewDimension::D2Array },1454BindingTextureDimension::Cube => quote! { TextureViewDimension::Cube },1455BindingTextureDimension::CubeArray => quote! { TextureViewDimension::CubeArray },1456BindingTextureDimension::D3 => quote! { TextureViewDimension::D3 },1457});1458}1459}14601461impl ToTokens for BindingTextureSampleType {1462fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {1463tokens.extend(match self {1464BindingTextureSampleType::Float { filterable } => {1465quote! { TextureSampleType::Float { filterable: #filterable } }1466}1467BindingTextureSampleType::Depth => quote! { TextureSampleType::Depth },1468BindingTextureSampleType::Sint => quote! { TextureSampleType::Sint },1469BindingTextureSampleType::Uint => quote! { TextureSampleType::Uint },1470});1471}1472}14731474struct TextureAttrs {1475dimension: BindingTextureDimension,1476sample_type: BindingTextureSampleType,1477multisampled: bool,1478visibility: ShaderStageVisibility,1479}14801481impl Default for BindingTextureSampleType {1482fn default() -> Self {1483BindingTextureSampleType::Float { filterable: true }1484}1485}14861487impl Default for TextureAttrs {1488fn default() -> Self {1489Self {1490dimension: Default::default(),1491sample_type: Default::default(),1492multisampled: true,1493visibility: Default::default(),1494}1495}1496}14971498struct StorageTextureAttrs {1499dimension: BindingTextureDimension,1500// Parsing of the image_format parameter is deferred to the type checker,1501// which will error if the format is not member of the TextureFormat enum.1502image_format: proc_macro2::TokenStream,1503// Parsing of the access parameter is deferred to the type checker,1504// which will error if the access is not member of the StorageTextureAccess enum.1505access: proc_macro2::TokenStream,1506visibility: ShaderStageVisibility,1507}15081509impl Default for StorageTextureAttrs {1510fn default() -> Self {1511Self {1512dimension: Default::default(),1513image_format: quote! { Rgba8Unorm },1514access: quote! { ReadWrite },1515visibility: ShaderStageVisibility::compute(),1516}1517}1518}15191520fn get_storage_texture_binding_attr(metas: Vec<Meta>) -> Result<StorageTextureAttrs> {1521let mut storage_texture_attrs = StorageTextureAttrs::default();15221523for meta in metas {1524use syn::Meta::{List, NameValue};1525match meta {1526// Parse #[storage_texture(0, dimension = "...")].1527NameValue(m) if m.path == DIMENSION => {1528let value = get_lit_str(DIMENSION, &m.value)?;1529storage_texture_attrs.dimension = get_texture_dimension_value(value)?;1530}1531// Parse #[storage_texture(0, format = ...))].1532NameValue(m) if m.path == IMAGE_FORMAT => {1533storage_texture_attrs.image_format = m.value.into_token_stream();1534}1535// Parse #[storage_texture(0, access = ...))].1536NameValue(m) if m.path == ACCESS => {1537storage_texture_attrs.access = m.value.into_token_stream();1538}1539// Parse #[storage_texture(0, visibility(...))].1540List(m) if m.path == VISIBILITY => {1541storage_texture_attrs.visibility = get_visibility_flag_value(&m)?;1542}1543NameValue(m) => {1544return Err(Error::new_spanned(1545m.path,1546"Not a valid name. Available attributes: `dimension`, `image_format`, `access`.",1547));1548}1549_ => {1550return Err(Error::new_spanned(1551meta,1552"Not a name value pair: `foo = \"...\"`",1553));1554}1555}1556}15571558Ok(storage_texture_attrs)1559}15601561const DIMENSION: Symbol = Symbol("dimension");1562const IMAGE_FORMAT: Symbol = Symbol("image_format");1563const ACCESS: Symbol = Symbol("access");1564const SAMPLE_TYPE: Symbol = Symbol("sample_type");1565const FILTERABLE: Symbol = Symbol("filterable");1566const MULTISAMPLED: Symbol = Symbol("multisampled");15671568// Values for `dimension` attribute.1569const DIM_1D: &str = "1d";1570const DIM_2D: &str = "2d";1571const DIM_3D: &str = "3d";1572const DIM_2D_ARRAY: &str = "2d_array";1573const DIM_CUBE: &str = "cube";1574const DIM_CUBE_ARRAY: &str = "cube_array";15751576// Values for sample `type` attribute.1577const FLOAT: &str = "float";1578const DEPTH: &str = "depth";1579const S_INT: &str = "s_int";1580const U_INT: &str = "u_int";15811582fn get_texture_attrs(metas: Vec<Meta>) -> Result<TextureAttrs> {1583let mut dimension = Default::default();1584let mut sample_type = Default::default();1585let mut multisampled = Default::default();1586let mut filterable = None;1587let mut filterable_ident = None;15881589let mut visibility = ShaderStageVisibility::vertex_fragment();15901591for meta in metas {1592use syn::Meta::{List, NameValue};1593match meta {1594// Parse #[texture(0, dimension = "...")].1595NameValue(m) if m.path == DIMENSION => {1596let value = get_lit_str(DIMENSION, &m.value)?;1597dimension = get_texture_dimension_value(value)?;1598}1599// Parse #[texture(0, sample_type = "...")].1600NameValue(m) if m.path == SAMPLE_TYPE => {1601let value = get_lit_str(SAMPLE_TYPE, &m.value)?;1602sample_type = get_texture_sample_type_value(value)?;1603}1604// Parse #[texture(0, multisampled = "...")].1605NameValue(m) if m.path == MULTISAMPLED => {1606multisampled = get_lit_bool(MULTISAMPLED, &m.value)?;1607}1608// Parse #[texture(0, filterable = "...")].1609NameValue(m) if m.path == FILTERABLE => {1610filterable = get_lit_bool(FILTERABLE, &m.value)?.into();1611filterable_ident = m.path.into();1612}1613// Parse #[texture(0, visibility(...))].1614List(m) if m.path == VISIBILITY => {1615visibility = get_visibility_flag_value(&m)?;1616}1617NameValue(m) => {1618return Err(Error::new_spanned(1619m.path,1620"Not a valid name. Available attributes: `dimension`, `sample_type`, `multisampled`, or `filterable`."1621));1622}1623_ => {1624return Err(Error::new_spanned(1625meta,1626"Not a name value pair: `foo = \"...\"`",1627));1628}1629}1630}16311632// Resolve `filterable` since the float1633// sample type is the one that contains the value.1634if let Some(filterable) = filterable {1635let path = filterable_ident.unwrap();1636match sample_type {1637BindingTextureSampleType::Float { filterable: _ } => {1638sample_type = BindingTextureSampleType::Float { filterable }1639}1640_ => {1641return Err(Error::new_spanned(1642path,1643"Type must be `float` to use the `filterable` attribute.",1644));1645}1646};1647}16481649Ok(TextureAttrs {1650dimension,1651sample_type,1652multisampled,1653visibility,1654})1655}16561657fn get_texture_dimension_value(lit_str: &LitStr) -> Result<BindingTextureDimension> {1658match lit_str.value().as_str() {1659DIM_1D => Ok(BindingTextureDimension::D1),1660DIM_2D => Ok(BindingTextureDimension::D2),1661DIM_2D_ARRAY => Ok(BindingTextureDimension::D2Array),1662DIM_3D => Ok(BindingTextureDimension::D3),1663DIM_CUBE => Ok(BindingTextureDimension::Cube),1664DIM_CUBE_ARRAY => Ok(BindingTextureDimension::CubeArray),16651666_ => Err(Error::new_spanned(1667lit_str,1668"Not a valid dimension. Must be `1d`, `2d`, `2d_array`, `3d`, `cube` or `cube_array`.",1669)),1670}1671}16721673fn get_texture_sample_type_value(lit_str: &LitStr) -> Result<BindingTextureSampleType> {1674match lit_str.value().as_str() {1675FLOAT => Ok(BindingTextureSampleType::Float { filterable: true }),1676DEPTH => Ok(BindingTextureSampleType::Depth),1677S_INT => Ok(BindingTextureSampleType::Sint),1678U_INT => Ok(BindingTextureSampleType::Uint),16791680_ => Err(Error::new_spanned(1681lit_str,1682"Not a valid sample type. Must be `float`, `depth`, `s_int` or `u_int`.",1683)),1684}1685}16861687#[derive(Default)]1688struct SamplerAttrs {1689sampler_binding_type: SamplerBindingType,1690visibility: ShaderStageVisibility,1691}16921693#[derive(Default)]1694enum SamplerBindingType {1695#[default]1696Filtering,1697NonFiltering,1698Comparison,1699}17001701impl ToTokens for SamplerBindingType {1702fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {1703tokens.extend(match self {1704SamplerBindingType::Filtering => quote! { SamplerBindingType::Filtering },1705SamplerBindingType::NonFiltering => quote! { SamplerBindingType::NonFiltering },1706SamplerBindingType::Comparison => quote! { SamplerBindingType::Comparison },1707});1708}1709}17101711const SAMPLER_TYPE: Symbol = Symbol("sampler_type");17121713const FILTERING: &str = "filtering";1714const NON_FILTERING: &str = "non_filtering";1715const COMPARISON: &str = "comparison";17161717fn get_sampler_attrs(metas: Vec<Meta>) -> Result<SamplerAttrs> {1718let mut sampler_binding_type = Default::default();1719let mut visibility = ShaderStageVisibility::vertex_fragment();17201721for meta in metas {1722use syn::Meta::{List, NameValue};1723match meta {1724// Parse #[sampler(0, sampler_type = "..."))].1725NameValue(m) if m.path == SAMPLER_TYPE => {1726let value = get_lit_str(DIMENSION, &m.value)?;1727sampler_binding_type = get_sampler_binding_type_value(value)?;1728}1729// Parse #[sampler(0, visibility(...))].1730List(m) if m.path == VISIBILITY => {1731visibility = get_visibility_flag_value(&m)?;1732}1733NameValue(m) => {1734return Err(Error::new_spanned(1735m.path,1736"Not a valid name. Available attributes: `sampler_type`.",1737));1738}1739_ => {1740return Err(Error::new_spanned(1741meta,1742"Not a name value pair: `foo = \"...\"`",1743));1744}1745}1746}17471748Ok(SamplerAttrs {1749sampler_binding_type,1750visibility,1751})1752}17531754fn get_sampler_binding_type_value(lit_str: &LitStr) -> Result<SamplerBindingType> {1755match lit_str.value().as_str() {1756FILTERING => Ok(SamplerBindingType::Filtering),1757NON_FILTERING => Ok(SamplerBindingType::NonFiltering),1758COMPARISON => Ok(SamplerBindingType::Comparison),17591760_ => Err(Error::new_spanned(1761lit_str,1762"Not a valid dimension. Must be `filtering`, `non_filtering`, or `comparison`.",1763)),1764}1765}17661767#[derive(Default)]1768struct StorageAttrs {1769visibility: ShaderStageVisibility,1770binding_array: Option<u32>,1771read_only: bool,1772buffer: bool,1773}17741775const READ_ONLY: Symbol = Symbol("read_only");1776const BUFFER: Symbol = Symbol("buffer");17771778fn get_storage_binding_attr(metas: Vec<Meta>) -> Result<StorageAttrs> {1779let mut visibility = ShaderStageVisibility::vertex_fragment();1780let mut binding_array = None;1781let mut read_only = false;1782let mut buffer = false;17831784for meta in metas {1785use syn::Meta::{List, Path};1786match meta {1787// Parse #[storage(0, visibility(...))].1788List(m) if m.path == VISIBILITY => {1789visibility = get_visibility_flag_value(&m)?;1790}1791// Parse #[storage(0, binding_array(...))] for bindless mode.1792List(m) if m.path == BINDING_ARRAY_MODIFIER_NAME => {1793binding_array = Some(get_binding_array_flag_value(&m)?);1794}1795Path(path) if path == READ_ONLY => {1796read_only = true;1797}1798Path(path) if path == BUFFER => {1799buffer = true;1800}1801_ => {1802return Err(Error::new_spanned(1803meta,1804"Not a valid attribute. Available attributes: `read_only`, `visibility`",1805));1806}1807}1808}18091810Ok(StorageAttrs {1811visibility,1812binding_array,1813read_only,1814buffer,1815})1816}181718181819