use bevy_macro_utils::{get_lit_bool, get_lit_str, BevyManifest, Symbol}; use proc_macro::TokenStream; use proc_macro2::{Ident, Span}; use quote::{quote, ToTokens}; use syn::{ parse::{Parse, ParseStream}, punctuated::Punctuated, Data, DataStruct, Error, Fields, LitInt, LitStr, NestedMeta, Result, Token, }; const UNIFORM_ATTRIBUTE_NAME: Symbol = Symbol("uniform"); const TEXTURE_ATTRIBUTE_NAME: Symbol = Symbol("texture"); const SAMPLER_ATTRIBUTE_NAME: Symbol = Symbol("sampler"); const BIND_GROUP_DATA_ATTRIBUTE_NAME: Symbol = Symbol("bind_group_data"); #[derive(Copy, Clone, Debug)] enum BindingType { Uniform, Texture, Sampler, } #[derive(Clone)] enum BindingState<'a> { Free, Occupied { binding_type: BindingType, ident: &'a Ident, }, OccupiedConvertedUniform, OccupiedMergableUniform { uniform_fields: Vec<&'a syn::Field>, }, } pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { let manifest = BevyManifest::default(); let render_path = manifest.get_path("bevy_render"); let asset_path = manifest.get_path("bevy_asset"); let mut binding_states: Vec = Vec::new(); let mut binding_impls = Vec::new(); let mut bind_group_entries = Vec::new(); let mut binding_layouts = Vec::new(); let mut attr_prepared_data_ident = None; // Read struct-level attributes for attr in &ast.attrs { if let Some(attr_ident) = attr.path.get_ident() { if attr_ident == BIND_GROUP_DATA_ATTRIBUTE_NAME { if let Ok(prepared_data_ident) = attr.parse_args_with(|input: ParseStream| input.parse::()) { attr_prepared_data_ident = Some(prepared_data_ident); } } else if attr_ident == UNIFORM_ATTRIBUTE_NAME { let (binding_index, converted_shader_type) = get_uniform_binding_attr(attr)?; binding_impls.push(quote! {{ use #render_path::render_resource::AsBindGroupShaderType; let mut buffer = #render_path::render_resource::encase::UniformBuffer::new(Vec::new()); let converted: #converted_shader_type = self.as_bind_group_shader_type(images); buffer.write(&converted).unwrap(); #render_path::render_resource::OwnedBindingResource::Buffer(render_device.create_buffer_with_data( &#render_path::render_resource::BufferInitDescriptor { label: None, usage: #render_path::render_resource::BufferUsages::COPY_DST | #render_path::render_resource::BufferUsages::UNIFORM, contents: buffer.as_ref(), }, )) }}); binding_layouts.push(quote!{ #render_path::render_resource::BindGroupLayoutEntry { binding: #binding_index, visibility: #render_path::render_resource::ShaderStages::all(), ty: #render_path::render_resource::BindingType::Buffer { ty: #render_path::render_resource::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: Some(<#converted_shader_type as #render_path::render_resource::ShaderType>::min_size()), }, count: None, } }); let binding_vec_index = bind_group_entries.len(); bind_group_entries.push(quote! { #render_path::render_resource::BindGroupEntry { binding: #binding_index, resource: bindings[#binding_vec_index].get_binding(), } }); let required_len = binding_index as usize + 1; if required_len > binding_states.len() { binding_states.resize(required_len, BindingState::Free); } binding_states[binding_index as usize] = BindingState::OccupiedConvertedUniform; } } } let fields = match &ast.data { Data::Struct(DataStruct { fields: Fields::Named(fields), .. }) => &fields.named, _ => { return Err(Error::new_spanned( ast, "Expected a struct with named fields", )); } }; // Read field-level attributes for field in fields.iter() { for attr in &field.attrs { let attr_ident = if let Some(ident) = attr.path.get_ident() { ident } else { continue; }; let binding_type = if attr_ident == UNIFORM_ATTRIBUTE_NAME { BindingType::Uniform } else if attr_ident == TEXTURE_ATTRIBUTE_NAME { BindingType::Texture } else if attr_ident == SAMPLER_ATTRIBUTE_NAME { BindingType::Sampler } else { continue; }; let (binding_index, nested_meta_items) = get_binding_nested_attr(attr)?; let field_name = field.ident.as_ref().unwrap(); let required_len = binding_index as usize + 1; if required_len > binding_states.len() { binding_states.resize(required_len, BindingState::Free); } match &mut binding_states[binding_index as usize] { value @ BindingState::Free => { *value = match binding_type { BindingType::Uniform => BindingState::OccupiedMergableUniform { uniform_fields: vec![field], }, _ => { // only populate bind group entries for non-uniforms // uniform entries are deferred until the end let binding_vec_index = bind_group_entries.len(); bind_group_entries.push(quote! { #render_path::render_resource::BindGroupEntry { binding: #binding_index, resource: bindings[#binding_vec_index].get_binding(), } }); BindingState::Occupied { binding_type, ident: field_name, } } } } BindingState::Occupied { binding_type, ident: occupied_ident, } => { return Err(Error::new_spanned( attr, 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:?}.") )); } BindingState::OccupiedConvertedUniform => { return Err(Error::new_spanned( attr, 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.") )); } BindingState::OccupiedMergableUniform { uniform_fields } => match binding_type { BindingType::Uniform => { uniform_fields.push(field); } _ => { return Err(Error::new_spanned( attr, format!("The '{field_name}' field cannot be assigned to binding {binding_index} because it is already occupied by a {:?}.", BindingType::Uniform) )); } }, } match binding_type { BindingType::Uniform => { /* uniform codegen is deferred to account for combined uniform bindings */ } BindingType::Texture => { let TextureAttrs { dimension, sample_type, multisampled, visibility, } = get_texture_attrs(nested_meta_items)?; let visibility = visibility.hygenic_quote("e! { #render_path::render_resource }); binding_impls.push(quote! { #render_path::render_resource::OwnedBindingResource::TextureView({ let handle: Option<&#asset_path::Handle<#render_path::texture::Image>> = (&self.#field_name).into(); if let Some(handle) = handle { images.get(handle).ok_or_else(|| #render_path::render_resource::AsBindGroupError::RetryNextUpdate)?.texture_view.clone() } else { fallback_image.texture_view.clone() } }) }); binding_layouts.push(quote! { #render_path::render_resource::BindGroupLayoutEntry { binding: #binding_index, visibility: #visibility, ty: #render_path::render_resource::BindingType::Texture { multisampled: #multisampled, sample_type: #render_path::render_resource::#sample_type, view_dimension: #render_path::render_resource::#dimension, }, count: None, } }); } BindingType::Sampler => { let SamplerAttrs { sampler_binding_type, visibility, } = get_sampler_attrs(nested_meta_items)?; let visibility = visibility.hygenic_quote("e! { #render_path::render_resource }); binding_impls.push(quote! { #render_path::render_resource::OwnedBindingResource::Sampler({ let handle: Option<&#asset_path::Handle<#render_path::texture::Image>> = (&self.#field_name).into(); if let Some(handle) = handle { images.get(handle).ok_or_else(|| #render_path::render_resource::AsBindGroupError::RetryNextUpdate)?.sampler.clone() } else { fallback_image.sampler.clone() } }) }); binding_layouts.push(quote!{ #render_path::render_resource::BindGroupLayoutEntry { binding: #binding_index, visibility: #visibility, ty: #render_path::render_resource::BindingType::Sampler(#render_path::render_resource::#sampler_binding_type), count: None, } }); } } } } // Produce impls for fields with uniform bindings let struct_name = &ast.ident; let mut field_struct_impls = Vec::new(); for (binding_index, binding_state) in binding_states.iter().enumerate() { let binding_index = binding_index as u32; if let BindingState::OccupiedMergableUniform { uniform_fields } = binding_state { let binding_vec_index = bind_group_entries.len(); bind_group_entries.push(quote! { #render_path::render_resource::BindGroupEntry { binding: #binding_index, resource: bindings[#binding_vec_index].get_binding(), } }); // single field uniform bindings for a given index can use a straightforward binding if uniform_fields.len() == 1 { let field = &uniform_fields[0]; let field_name = field.ident.as_ref().unwrap(); let field_ty = &field.ty; binding_impls.push(quote! {{ let mut buffer = #render_path::render_resource::encase::UniformBuffer::new(Vec::new()); buffer.write(&self.#field_name).unwrap(); #render_path::render_resource::OwnedBindingResource::Buffer(render_device.create_buffer_with_data( &#render_path::render_resource::BufferInitDescriptor { label: None, usage: #render_path::render_resource::BufferUsages::COPY_DST | #render_path::render_resource::BufferUsages::UNIFORM, contents: buffer.as_ref(), }, )) }}); binding_layouts.push(quote!{ #render_path::render_resource::BindGroupLayoutEntry { binding: #binding_index, visibility: #render_path::render_resource::ShaderStages::all(), ty: #render_path::render_resource::BindingType::Buffer { ty: #render_path::render_resource::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: Some(<#field_ty as #render_path::render_resource::ShaderType>::min_size()), }, count: None, } }); // multi-field uniform bindings for a given index require an intermediate struct to derive ShaderType } else { let uniform_struct_name = Ident::new( &format!("_{struct_name}AsBindGroupUniformStructBindGroup{binding_index}"), Span::call_site(), ); let field_name = uniform_fields.iter().map(|f| f.ident.as_ref().unwrap()); let field_type = uniform_fields.iter().map(|f| &f.ty); field_struct_impls.push(quote! { #[derive(#render_path::render_resource::ShaderType)] struct #uniform_struct_name<'a> { #(#field_name: &'a #field_type,)* } }); let field_name = uniform_fields.iter().map(|f| f.ident.as_ref().unwrap()); binding_impls.push(quote! {{ let mut buffer = #render_path::render_resource::encase::UniformBuffer::new(Vec::new()); buffer.write(&#uniform_struct_name { #(#field_name: &self.#field_name,)* }).unwrap(); #render_path::render_resource::OwnedBindingResource::Buffer(render_device.create_buffer_with_data( &#render_path::render_resource::BufferInitDescriptor { label: None, usage: #render_path::render_resource::BufferUsages::COPY_DST | #render_path::render_resource::BufferUsages::UNIFORM, contents: buffer.as_ref(), }, )) }}); binding_layouts.push(quote!{ #render_path::render_resource::BindGroupLayoutEntry { binding: #binding_index, visibility: #render_path::render_resource::ShaderStages::all(), ty: #render_path::render_resource::BindingType::Buffer { ty: #render_path::render_resource::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: Some(<#uniform_struct_name as #render_path::render_resource::ShaderType>::min_size()), }, count: None, } }); } } } let generics = ast.generics; let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); let (prepared_data, get_prepared_data) = if let Some(prepared) = attr_prepared_data_ident { let get_prepared_data = quote! { self.into() }; (quote! {#prepared}, get_prepared_data) } else { let prepared_data = quote! { () }; (prepared_data.clone(), prepared_data) }; Ok(TokenStream::from(quote! { #(#field_struct_impls)* impl #impl_generics #render_path::render_resource::AsBindGroup for #struct_name #ty_generics #where_clause { type Data = #prepared_data; fn as_bind_group( &self, layout: &#render_path::render_resource::BindGroupLayout, render_device: &#render_path::renderer::RenderDevice, images: &#render_path::render_asset::RenderAssets<#render_path::texture::Image>, fallback_image: &#render_path::texture::FallbackImage, ) -> Result<#render_path::render_resource::PreparedBindGroup, #render_path::render_resource::AsBindGroupError> { let bindings = vec![#(#binding_impls,)*]; let bind_group = { let descriptor = #render_path::render_resource::BindGroupDescriptor { entries: &[#(#bind_group_entries,)*], label: None, layout: &layout, }; render_device.create_bind_group(&descriptor) }; Ok(#render_path::render_resource::PreparedBindGroup { bindings, bind_group, data: #get_prepared_data, }) } fn bind_group_layout(render_device: &#render_path::renderer::RenderDevice) -> #render_path::render_resource::BindGroupLayout { render_device.create_bind_group_layout(&#render_path::render_resource::BindGroupLayoutDescriptor { entries: &[#(#binding_layouts,)*], label: None, }) } } })) } /// Represents the arguments for the `uniform` binding attribute. /// /// If parsed, represents an attribute /// like `#[uniform(LitInt, Ident)]` struct UniformBindingMeta { lit_int: LitInt, _comma: Token![,], ident: Ident, } /// Represents the arguments for any general binding attribute. /// /// If parsed, represents an attribute /// like `#[foo(LitInt, ...)]` where the rest is optional `NestedMeta`. enum BindingMeta { IndexOnly(LitInt), IndexWithOptions(BindingIndexOptions), } /// Represents the arguments for an attribute with a list of arguments. /// /// This represents, for example, `#[texture(0, dimension = "2d_array")]`. struct BindingIndexOptions { lit_int: LitInt, _comma: Token![,], meta_list: Punctuated, } impl Parse for BindingMeta { fn parse(input: ParseStream) -> Result { if input.peek2(Token![,]) { input.parse().map(Self::IndexWithOptions) } else { input.parse().map(Self::IndexOnly) } } } impl Parse for BindingIndexOptions { fn parse(input: ParseStream) -> Result { Ok(Self { lit_int: input.parse()?, _comma: input.parse()?, meta_list: input.parse_terminated(NestedMeta::parse)?, }) } } impl Parse for UniformBindingMeta { fn parse(input: ParseStream) -> Result { Ok(Self { lit_int: input.parse()?, _comma: input.parse()?, ident: input.parse()?, }) } } fn get_uniform_binding_attr(attr: &syn::Attribute) -> Result<(u32, Ident)> { let uniform_binding_meta = attr.parse_args_with(UniformBindingMeta::parse)?; let binding_index = uniform_binding_meta.lit_int.base10_parse()?; let ident = uniform_binding_meta.ident; Ok((binding_index, ident)) } fn get_binding_nested_attr(attr: &syn::Attribute) -> Result<(u32, Vec)> { let binding_meta = attr.parse_args_with(BindingMeta::parse)?; match binding_meta { BindingMeta::IndexOnly(lit_int) => Ok((lit_int.base10_parse()?, Vec::new())), BindingMeta::IndexWithOptions(BindingIndexOptions { lit_int, _comma: _, meta_list, }) => Ok((lit_int.base10_parse()?, meta_list.into_iter().collect())), } } #[derive(Default)] enum ShaderStageVisibility { #[default] All, None, Flags(VisibilityFlags), } #[derive(Default)] struct VisibilityFlags { vertex: bool, fragment: bool, compute: bool, } impl ShaderStageVisibility { fn vertex_fragment() -> Self { Self::Flags(VisibilityFlags::vertex_fragment()) } } impl VisibilityFlags { fn vertex_fragment() -> Self { Self { vertex: true, fragment: true, ..Default::default() } } } impl ShaderStageVisibility { fn hygenic_quote(&self, path: &proc_macro2::TokenStream) -> proc_macro2::TokenStream { match self { ShaderStageVisibility::All => quote! { #path::ShaderStages::all() }, ShaderStageVisibility::None => quote! { #path::ShaderStages::NONE }, ShaderStageVisibility::Flags(flags) => { let mut quoted = Vec::new(); if flags.vertex { quoted.push(quote! { #path::ShaderStages::VERTEX }); } if flags.fragment { quoted.push(quote! { #path::ShaderStages::FRAGMENT }); } if flags.compute { quoted.push(quote! { #path::ShaderStages::COMPUTE }); } quote! { #(#quoted)|* } } } } } const VISIBILITY: Symbol = Symbol("visibility"); const VISIBILITY_VERTEX: Symbol = Symbol("vertex"); const VISIBILITY_FRAGMENT: Symbol = Symbol("fragment"); const VISIBILITY_COMPUTE: Symbol = Symbol("compute"); const VISIBILITY_ALL: Symbol = Symbol("all"); const VISIBILITY_NONE: Symbol = Symbol("none"); fn get_visibility_flag_value( nested_metas: &Punctuated, ) -> Result { let mut visibility = VisibilityFlags::vertex_fragment(); for meta in nested_metas { use syn::{Meta::Path, NestedMeta::Meta}; match meta { // Parse `visibility(all)]`. Meta(Path(path)) if path == VISIBILITY_ALL => { return Ok(ShaderStageVisibility::All) } // Parse `visibility(none)]`. Meta(Path(path)) if path == VISIBILITY_NONE => { return Ok(ShaderStageVisibility::None) } // Parse `visibility(vertex, ...)]`. Meta(Path(path)) if path == VISIBILITY_VERTEX => { visibility.vertex = true; } // Parse `visibility(fragment, ...)]`. Meta(Path(path)) if path == VISIBILITY_FRAGMENT => { visibility.fragment = true; } // Parse `visibility(compute, ...)]`. Meta(Path(path)) if path == VISIBILITY_COMPUTE => { visibility.compute = true; } Meta(Path(path)) => return Err(Error::new_spanned( path, "Not a valid visibility flag. Must be `all`, `none`, or a list-combination of `vertex`, `fragment` and/or `compute`." )), _ => return Err(Error::new_spanned( meta, "Invalid visibility format: `visibility(...)`.", )), } } Ok(ShaderStageVisibility::Flags(visibility)) } #[derive(Default)] enum BindingTextureDimension { D1, #[default] D2, D2Array, Cube, CubeArray, D3, } enum BindingTextureSampleType { Float { filterable: bool }, Depth, Sint, Uint, } impl ToTokens for BindingTextureDimension { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { tokens.extend(match self { BindingTextureDimension::D1 => quote! { TextureViewDimension::D1 }, BindingTextureDimension::D2 => quote! { TextureViewDimension::D2 }, BindingTextureDimension::D2Array => quote! { TextureViewDimension::D2Array }, BindingTextureDimension::Cube => quote! { TextureViewDimension::Cube }, BindingTextureDimension::CubeArray => quote! { TextureViewDimension::CubeArray }, BindingTextureDimension::D3 => quote! { TextureViewDimension::D3 }, }); } } impl ToTokens for BindingTextureSampleType { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { tokens.extend(match self { BindingTextureSampleType::Float { filterable } => { quote! { TextureSampleType::Float { filterable: #filterable } } } BindingTextureSampleType::Depth => quote! { TextureSampleType::Depth }, BindingTextureSampleType::Sint => quote! { TextureSampleType::Sint }, BindingTextureSampleType::Uint => quote! { TextureSampleType::Uint }, }); } } struct TextureAttrs { dimension: BindingTextureDimension, sample_type: BindingTextureSampleType, multisampled: bool, visibility: ShaderStageVisibility, } impl Default for BindingTextureSampleType { fn default() -> Self { BindingTextureSampleType::Float { filterable: true } } } impl Default for TextureAttrs { fn default() -> Self { Self { dimension: Default::default(), sample_type: Default::default(), multisampled: true, visibility: Default::default(), } } } const DIMENSION: Symbol = Symbol("dimension"); const SAMPLE_TYPE: Symbol = Symbol("sample_type"); const FILTERABLE: Symbol = Symbol("filterable"); const MULTISAMPLED: Symbol = Symbol("multisampled"); // Values for `dimension` attribute. const DIM_1D: &str = "1d"; const DIM_2D: &str = "2d"; const DIM_3D: &str = "3d"; const DIM_2D_ARRAY: &str = "2d_array"; const DIM_CUBE: &str = "cube"; const DIM_CUBE_ARRAY: &str = "cube_array"; // Values for sample `type` attribute. const FLOAT: &str = "float"; const DEPTH: &str = "depth"; const S_INT: &str = "s_int"; const U_INT: &str = "u_int"; fn get_texture_attrs(metas: Vec) -> Result { let mut dimension = Default::default(); let mut sample_type = Default::default(); let mut multisampled = Default::default(); let mut filterable = None; let mut filterable_ident = None; let mut visibility = ShaderStageVisibility::vertex_fragment(); for meta in metas { use syn::{ Meta::{List, NameValue}, NestedMeta::Meta, }; match meta { // Parse #[texture(0, dimension = "...")]. Meta(NameValue(m)) if m.path == DIMENSION => { let value = get_lit_str(DIMENSION, &m.lit)?; dimension = get_texture_dimension_value(value)?; } // Parse #[texture(0, sample_type = "...")]. Meta(NameValue(m)) if m.path == SAMPLE_TYPE => { let value = get_lit_str(SAMPLE_TYPE, &m.lit)?; sample_type = get_texture_sample_type_value(value)?; } // Parse #[texture(0, multisampled = "...")]. Meta(NameValue(m)) if m.path == MULTISAMPLED => { multisampled = get_lit_bool(MULTISAMPLED, &m.lit)?; } // Parse #[texture(0, filterable = "...")]. Meta(NameValue(m)) if m.path == FILTERABLE => { filterable = get_lit_bool(FILTERABLE, &m.lit)?.into(); filterable_ident = m.path.into(); } // Parse #[texture(0, visibility(...))]. Meta(List(m)) if m.path == VISIBILITY => { visibility = get_visibility_flag_value(&m.nested)?; } Meta(NameValue(m)) => { return Err(Error::new_spanned( m.path, "Not a valid name. Available attributes: `dimension`, `sample_type`, `multisampled`, or `filterable`." )); } _ => { return Err(Error::new_spanned( meta, "Not a name value pair: `foo = \"...\"`", )); } } } // Resolve `filterable` since the float // sample type is the one that contains the value. if let Some(filterable) = filterable { let path = filterable_ident.unwrap(); match sample_type { BindingTextureSampleType::Float { filterable: _ } => { sample_type = BindingTextureSampleType::Float { filterable } } _ => { return Err(Error::new_spanned( path, "Type must be `float` to use the `filterable` attribute.", )); } }; } Ok(TextureAttrs { dimension, sample_type, multisampled, visibility, }) } fn get_texture_dimension_value(lit_str: &LitStr) -> Result { match lit_str.value().as_str() { DIM_1D => Ok(BindingTextureDimension::D1), DIM_2D => Ok(BindingTextureDimension::D2), DIM_2D_ARRAY => Ok(BindingTextureDimension::D2Array), DIM_3D => Ok(BindingTextureDimension::D3), DIM_CUBE => Ok(BindingTextureDimension::Cube), DIM_CUBE_ARRAY => Ok(BindingTextureDimension::CubeArray), _ => Err(Error::new_spanned( lit_str, "Not a valid dimension. Must be `1d`, `2d`, `2d_array`, `3d`, `cube` or `cube_array`.", )), } } fn get_texture_sample_type_value(lit_str: &LitStr) -> Result { match lit_str.value().as_str() { FLOAT => Ok(BindingTextureSampleType::Float { filterable: true }), DEPTH => Ok(BindingTextureSampleType::Depth), S_INT => Ok(BindingTextureSampleType::Sint), U_INT => Ok(BindingTextureSampleType::Uint), _ => Err(Error::new_spanned( lit_str, "Not a valid sample type. Must be `float`, `depth`, `s_int` or `u_int`.", )), } } #[derive(Default)] struct SamplerAttrs { sampler_binding_type: SamplerBindingType, visibility: ShaderStageVisibility, } #[derive(Default)] enum SamplerBindingType { #[default] Filtering, NonFiltering, Comparison, } impl ToTokens for SamplerBindingType { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { tokens.extend(match self { SamplerBindingType::Filtering => quote! { SamplerBindingType::Filtering }, SamplerBindingType::NonFiltering => quote! { SamplerBindingType::NonFiltering }, SamplerBindingType::Comparison => quote! { SamplerBindingType::Comparison }, }); } } const SAMPLER_TYPE: Symbol = Symbol("sampler_type"); const FILTERING: &str = "filtering"; const NON_FILTERING: &str = "non_filtering"; const COMPARISON: &str = "comparison"; fn get_sampler_attrs(metas: Vec) -> Result { let mut sampler_binding_type = Default::default(); let mut visibility = ShaderStageVisibility::vertex_fragment(); for meta in metas { use syn::{ Meta::{List, NameValue}, NestedMeta::Meta, }; match meta { // Parse #[sampler(0, sampler_type = "..."))]. Meta(NameValue(m)) if m.path == SAMPLER_TYPE => { let value = get_lit_str(DIMENSION, &m.lit)?; sampler_binding_type = get_sampler_binding_type_value(value)?; } // Parse #[sampler(0, visibility(...))]. Meta(List(m)) if m.path == VISIBILITY => { visibility = get_visibility_flag_value(&m.nested)?; } Meta(NameValue(m)) => { return Err(Error::new_spanned( m.path, "Not a valid name. Available attributes: `sampler_type`.", )); } _ => { return Err(Error::new_spanned( meta, "Not a name value pair: `foo = \"...\"`", )); } } } Ok(SamplerAttrs { sampler_binding_type, visibility, }) } fn get_sampler_binding_type_value(lit_str: &LitStr) -> Result { match lit_str.value().as_str() { FILTERING => Ok(SamplerBindingType::Filtering), NON_FILTERING => Ok(SamplerBindingType::NonFiltering), COMPARISON => Ok(SamplerBindingType::Comparison), _ => Err(Error::new_spanned( lit_str, "Not a valid dimension. Must be `filtering`, `non_filtering`, or `comparison`.", )), } }