Path: blob/main/crates/bevy_render/macros/src/as_bind_group.rs
9374 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 (render_path, image_path, asset_path, ecs_path) = BevyManifest::shared(|manifest| {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");6566(render_path, image_path, asset_path, ecs_path)67});6869let mut binding_states: Vec<BindingState> = Vec::new();70let mut binding_impls = Vec::new();71let mut bindless_binding_layouts = Vec::new();72let mut non_bindless_binding_layouts = Vec::new();73let mut bindless_resource_types = Vec::new();74let mut bindless_buffer_descriptors = Vec::new();75let mut attr_prepared_data_ident = None;76// After the first attribute pass, this will be `None` if the object isn't77// bindless and `Some` if it is.78let mut attr_bindless_count = None;79let mut attr_bindless_index_table_range = None;80let mut attr_bindless_index_table_binding = None;8182// `actual_bindless_slot_count` holds the actual number of bindless slots83// per bind group, taking into account whether the current platform supports84// bindless resources.85let actual_bindless_slot_count = Ident::new("actual_bindless_slot_count", Span::call_site());86let bind_group_layout_entries = Ident::new("bind_group_layout_entries", Span::call_site());8788// The `BufferBindingType` and corresponding `BufferUsages` used for89// uniforms. We need this because bindless uniforms don't exist, so in90// bindless mode we must promote uniforms to storage buffers.91let uniform_binding_type = Ident::new("uniform_binding_type", Span::call_site());92let uniform_buffer_usages = Ident::new("uniform_buffer_usages", Span::call_site());9394// Read struct-level attributes, first pass.95for attr in &ast.attrs {96if let Some(attr_ident) = attr.path().get_ident() {97if attr_ident == BIND_GROUP_DATA_ATTRIBUTE_NAME {98if let Ok(prepared_data_ident) =99attr.parse_args_with(|input: ParseStream| input.parse::<Ident>())100{101attr_prepared_data_ident = Some(prepared_data_ident);102}103} else if attr_ident == BINDLESS_ATTRIBUTE_NAME {104attr_bindless_count = Some(BindlessSlabResourceLimitAttr::Auto);105if let Meta::List(_) = attr.meta {106// Parse bindless features.107attr.parse_nested_meta(|submeta| {108if submeta.path.is_ident(&LIMIT_MODIFIER_NAME) {109let content;110parenthesized!(content in submeta.input);111let lit: LitInt = content.parse()?;112113attr_bindless_count = Some(BindlessSlabResourceLimitAttr::Limit(lit));114return Ok(());115}116117if submeta.path.is_ident(&INDEX_TABLE_MODIFIER_NAME) {118submeta.parse_nested_meta(|subsubmeta| {119if subsubmeta.path.is_ident(&RANGE_MODIFIER_NAME) {120let content;121parenthesized!(content in subsubmeta.input);122let start: LitInt = content.parse()?;123content.parse::<DotDot>()?;124let end: LitInt = content.parse()?;125attr_bindless_index_table_range =126Some(BindlessIndexTableRangeAttr { start, end });127return Ok(());128}129130if subsubmeta.path.is_ident(&BINDING_MODIFIER_NAME) {131let content;132parenthesized!(content in subsubmeta.input);133let lit: LitInt = content.parse()?;134135attr_bindless_index_table_binding = Some(lit);136return Ok(());137}138139Err(Error::new_spanned(140attr,141"Expected `range(M..N)` or `binding(N)`",142))143})?;144return Ok(());145}146147Err(Error::new_spanned(148attr,149"Expected `limit` or `index_table`",150))151})?;152}153}154}155}156157// Read struct-level attributes, second pass.158for attr in &ast.attrs {159if let Some(attr_ident) = attr.path().get_ident()160&& (attr_ident == UNIFORM_ATTRIBUTE_NAME || attr_ident == DATA_ATTRIBUTE_NAME)161{162let UniformBindingAttr {163binding_type,164binding_index,165converted_shader_type,166binding_array: binding_array_binding,167} = get_uniform_binding_attr(attr)?;168match binding_type {169UniformBindingAttrType::Uniform => {170binding_impls.push(quote! {{171use #render_path::render_resource::AsBindGroupShaderType;172let mut buffer = #render_path::render_resource::encase::UniformBuffer::new(Vec::new());173let converted: #converted_shader_type = self.as_bind_group_shader_type(&images);174buffer.write(&converted).unwrap();175(176#binding_index,177#render_path::render_resource::OwnedBindingResource::Buffer(render_device.create_buffer_with_data(178&#render_path::render_resource::BufferInitDescriptor {179label: None,180usage: #uniform_buffer_usages,181contents: buffer.as_ref(),182},183))184)185}});186187match (&binding_array_binding, &attr_bindless_count) {188(&None, &Some(_)) => {189return Err(Error::new_spanned(190attr,191"Must specify `binding_array(...)` with `#[uniform]` if the \192object is bindless",193));194}195(&Some(_), &None) => {196return Err(Error::new_spanned(197attr,198"`binding_array(...)` with `#[uniform]` requires the object to \199be bindless",200));201}202_ => {}203}204205let binding_array_binding = binding_array_binding.unwrap_or(0);206bindless_binding_layouts.push(quote! {207#bind_group_layout_entries.push(208#render_path::render_resource::BindGroupLayoutEntry {209binding: #binding_array_binding,210visibility: #render_path::render_resource::ShaderStages::FRAGMENT | #render_path::render_resource::ShaderStages::VERTEX | #render_path::render_resource::ShaderStages::COMPUTE,211ty: #render_path::render_resource::BindingType::Buffer {212ty: #uniform_binding_type,213has_dynamic_offset: false,214min_binding_size: Some(<#converted_shader_type as #render_path::render_resource::ShaderType>::min_size()),215},216count: #actual_bindless_slot_count,217}218);219});220221add_bindless_resource_type(222&render_path,223&mut bindless_resource_types,224binding_index,225quote! { #render_path::render_resource::BindlessResourceType::Buffer },226);227}228229UniformBindingAttrType::Data => {230binding_impls.push(quote! {{231use #render_path::render_resource::AsBindGroupShaderType;232use #render_path::render_resource::encase::{ShaderType, internal::WriteInto};233let mut buffer: Vec<u8> = Vec::new();234let converted: #converted_shader_type = self.as_bind_group_shader_type(&images);235converted.write_into(236&mut #render_path::render_resource::encase::internal::Writer::new(237&converted,238&mut buffer,2390,240).unwrap(),241);242let min_size = <#converted_shader_type as #render_path::render_resource::ShaderType>::min_size().get() as usize;243while buffer.len() < min_size {244buffer.push(0);245}246(247#binding_index,248#render_path::render_resource::OwnedBindingResource::Data(249#render_path::render_resource::OwnedData(buffer)250)251)252}});253254let binding_array_binding = binding_array_binding.unwrap_or(0);255bindless_binding_layouts.push(quote! {256#bind_group_layout_entries.push(257#render_path::render_resource::BindGroupLayoutEntry {258binding: #binding_array_binding,259visibility: #render_path::render_resource::ShaderStages::FRAGMENT | #render_path::render_resource::ShaderStages::VERTEX | #render_path::render_resource::ShaderStages::COMPUTE,260ty: #render_path::render_resource::BindingType::Buffer {261ty: #uniform_binding_type,262has_dynamic_offset: false,263min_binding_size: Some(<#converted_shader_type as #render_path::render_resource::ShaderType>::min_size()),264},265count: None,266}267);268});269270add_bindless_resource_type(271&render_path,272&mut bindless_resource_types,273binding_index,274quote! { #render_path::render_resource::BindlessResourceType::DataBuffer },275);276}277}278279// Push the non-bindless binding layout.280281non_bindless_binding_layouts.push(quote!{282#bind_group_layout_entries.push(283#render_path::render_resource::BindGroupLayoutEntry {284binding: #binding_index,285visibility: #render_path::render_resource::ShaderStages::FRAGMENT | #render_path::render_resource::ShaderStages::VERTEX | #render_path::render_resource::ShaderStages::COMPUTE,286ty: #render_path::render_resource::BindingType::Buffer {287ty: #uniform_binding_type,288has_dynamic_offset: false,289min_binding_size: Some(<#converted_shader_type as #render_path::render_resource::ShaderType>::min_size()),290},291count: None,292}293);294});295296bindless_buffer_descriptors.push(quote! {297#render_path::render_resource::BindlessBufferDescriptor {298// Note that, because this is bindless, *binding299// index* here refers to the index in the300// bindless index table (`bindless_index`), and301// the actual binding number is the *binding302// array binding*.303binding_number: #render_path::render_resource::BindingNumber(304#binding_array_binding305),306bindless_index:307#render_path::render_resource::BindlessIndex(#binding_index),308size: Some(309<310#converted_shader_type as311#render_path::render_resource::ShaderType312>::min_size().get() as usize313),314}315});316317let required_len = binding_index as usize + 1;318if required_len > binding_states.len() {319binding_states.resize(required_len, BindingState::Free);320}321binding_states[binding_index as usize] = BindingState::OccupiedConvertedUniform;322}323}324325let fields = match &ast.data {326Data::Struct(DataStruct {327fields: Fields::Named(fields),328..329}) => &fields.named,330_ => {331return Err(Error::new_spanned(332ast,333"Expected a struct with named fields",334));335}336};337338// Count the number of sampler fields needed. We might have to disable339// bindless if bindless arrays take the GPU over the maximum number of340// samplers.341let mut sampler_binding_count: u32 = 0;342343// Read field-level attributes344for field in fields {345// Search ahead for texture attributes so we can use them with any346// corresponding sampler attribute.347let mut tex_attrs = None;348for attr in &field.attrs {349let Some(attr_ident) = attr.path().get_ident() else {350continue;351};352if attr_ident == TEXTURE_ATTRIBUTE_NAME {353let (_binding_index, nested_meta_items) = get_binding_nested_attr(attr)?;354tex_attrs = Some(get_texture_attrs(nested_meta_items)?);355}356}357358for attr in &field.attrs {359let Some(attr_ident) = attr.path().get_ident() else {360continue;361};362363let binding_type = if attr_ident == UNIFORM_ATTRIBUTE_NAME {364BindingType::Uniform365} else if attr_ident == TEXTURE_ATTRIBUTE_NAME {366BindingType::Texture367} else if attr_ident == STORAGE_TEXTURE_ATTRIBUTE_NAME {368BindingType::StorageTexture369} else if attr_ident == SAMPLER_ATTRIBUTE_NAME {370BindingType::Sampler371} else if attr_ident == STORAGE_ATTRIBUTE_NAME {372BindingType::Storage373} else {374continue;375};376377let (binding_index, nested_meta_items) = get_binding_nested_attr(attr)?;378379let field_name = field.ident.as_ref().unwrap();380let required_len = binding_index as usize + 1;381if required_len > binding_states.len() {382binding_states.resize(required_len, BindingState::Free);383}384385match &mut binding_states[binding_index as usize] {386value @ BindingState::Free => {387*value = match binding_type {388BindingType::Uniform => BindingState::OccupiedMergeableUniform {389uniform_fields: vec![field],390},391_ => {392// only populate bind group entries for non-uniforms393// uniform entries are deferred until the end394BindingState::Occupied {395binding_type,396ident: field_name,397}398}399}400}401BindingState::Occupied {402binding_type,403ident: occupied_ident,404} => {405return Err(Error::new_spanned(406attr,407format!("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:?}.")408));409}410BindingState::OccupiedConvertedUniform => {411return Err(Error::new_spanned(412attr,413format!("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.")414));415}416BindingState::OccupiedMergeableUniform { uniform_fields } => match binding_type {417BindingType::Uniform => {418uniform_fields.push(field);419}420_ => {421return Err(Error::new_spanned(422attr,423format!("The '{field_name}' field cannot be assigned to binding {binding_index} because it is already occupied by a {:?}.", BindingType::Uniform)424));425}426},427}428429match binding_type {430BindingType::Uniform => {431if attr_bindless_count.is_some() {432return Err(Error::new_spanned(433attr,434"Only structure-level `#[uniform]` attributes are supported in \435bindless mode",436));437}438439// uniform codegen is deferred to account for combined uniform bindings440}441442BindingType::Storage => {443let StorageAttrs {444visibility,445binding_array: binding_array_binding,446read_only,447buffer,448} = get_storage_binding_attr(nested_meta_items)?;449let visibility =450visibility.hygienic_quote("e! { #render_path::render_resource });451452let field_name = field.ident.as_ref().unwrap();453454if buffer {455binding_impls.push(quote! {456(457#binding_index,458#render_path::render_resource::OwnedBindingResource::Buffer({459self.#field_name.clone()460})461)462});463} else {464binding_impls.push(quote! {465(466#binding_index,467#render_path::render_resource::OwnedBindingResource::Buffer({468let handle: &#asset_path::Handle<#render_path::storage::ShaderBuffer> = (&self.#field_name);469storage_buffers.get(handle).ok_or_else(|| #render_path::render_resource::AsBindGroupError::RetryNextUpdate)?.buffer.clone()470})471)472});473}474475non_bindless_binding_layouts.push(quote! {476#bind_group_layout_entries.push(477#render_path::render_resource::BindGroupLayoutEntry {478binding: #binding_index,479visibility: #visibility,480ty: #render_path::render_resource::BindingType::Buffer {481ty: #render_path::render_resource::BufferBindingType::Storage { read_only: #read_only },482has_dynamic_offset: false,483min_binding_size: None,484},485count: #actual_bindless_slot_count,486}487);488});489490if let Some(binding_array_binding) = binding_array_binding {491// Add the storage buffer to the `BindlessResourceType` list492// in the bindless descriptor.493let bindless_resource_type = quote! {494#render_path::render_resource::BindlessResourceType::Buffer495};496add_bindless_resource_type(497&render_path,498&mut bindless_resource_types,499binding_index,500bindless_resource_type,501);502503// Push the buffer descriptor.504bindless_buffer_descriptors.push(quote! {505#render_path::render_resource::BindlessBufferDescriptor {506// Note that, because this is bindless, *binding507// index* here refers to the index in the bindless508// index table (`bindless_index`), and the actual509// binding number is the *binding array binding*.510binding_number: #render_path::render_resource::BindingNumber(511#binding_array_binding512),513bindless_index:514#render_path::render_resource::BindlessIndex(#binding_index),515size: None,516}517});518519// Declare the binding array.520bindless_binding_layouts.push(quote!{521#bind_group_layout_entries.push(522#render_path::render_resource::BindGroupLayoutEntry {523binding: #binding_array_binding,524visibility: #render_path::render_resource::ShaderStages::FRAGMENT | #render_path::render_resource::ShaderStages::VERTEX | #render_path::render_resource::ShaderStages::COMPUTE,525ty: #render_path::render_resource::BindingType::Buffer {526ty: #render_path::render_resource::BufferBindingType::Storage {527read_only: #read_only528},529has_dynamic_offset: false,530min_binding_size: None,531},532count: #actual_bindless_slot_count,533}534);535});536}537}538539BindingType::StorageTexture => {540if attr_bindless_count.is_some() {541return Err(Error::new_spanned(542attr,543"Storage textures are unsupported in bindless mode",544));545}546547let StorageTextureAttrs {548dimension,549image_format,550access,551visibility,552} = get_storage_texture_binding_attr(nested_meta_items)?;553554let visibility =555visibility.hygienic_quote("e! { #render_path::render_resource });556557let fallback_image = get_fallback_image(&render_path, dimension);558559// insert fallible texture-based entries at 0 so that if we fail here, we exit before allocating any buffers560binding_impls.insert(0, quote! {561( #binding_index,562#render_path::render_resource::OwnedBindingResource::TextureView(563#render_path::render_resource::#dimension,564{565let handle: Option<&#asset_path::Handle<#image_path::Image>> = (&self.#field_name).into();566if let Some(handle) = handle {567images.get(handle).ok_or_else(|| #render_path::render_resource::AsBindGroupError::RetryNextUpdate)?.texture_view.clone()568} else {569#fallback_image.texture_view.clone()570}571}572)573)574});575576non_bindless_binding_layouts.push(quote! {577#bind_group_layout_entries.push(578#render_path::render_resource::BindGroupLayoutEntry {579binding: #binding_index,580visibility: #visibility,581ty: #render_path::render_resource::BindingType::StorageTexture {582access: #render_path::render_resource::StorageTextureAccess::#access,583format: #render_path::render_resource::TextureFormat::#image_format,584view_dimension: #render_path::render_resource::#dimension,585},586count: #actual_bindless_slot_count,587}588);589});590}591592BindingType::Texture => {593let TextureAttrs {594dimension,595sample_type,596multisampled,597visibility,598} = tex_attrs.as_ref().unwrap();599600let visibility =601visibility.hygienic_quote("e! { #render_path::render_resource });602603let fallback_image = get_fallback_image(&render_path, *dimension);604605// insert fallible texture-based entries at 0 so that if we fail here, we exit before allocating any buffers606binding_impls.insert(0, quote! {607(608#binding_index,609#render_path::render_resource::OwnedBindingResource::TextureView(610#render_path::render_resource::#dimension,611{612let handle: Option<&#asset_path::Handle<#image_path::Image>> = (&self.#field_name).into();613if let Some(handle) = handle {614images.get(handle).ok_or_else(|| #render_path::render_resource::AsBindGroupError::RetryNextUpdate)?.texture_view.clone()615} else {616#fallback_image.texture_view.clone()617}618}619)620)621});622623sampler_binding_count += 1;624625non_bindless_binding_layouts.push(quote! {626#bind_group_layout_entries.push(627#render_path::render_resource::BindGroupLayoutEntry {628binding: #binding_index,629visibility: #visibility,630ty: #render_path::render_resource::BindingType::Texture {631multisampled: #multisampled,632sample_type: #render_path::render_resource::#sample_type,633view_dimension: #render_path::render_resource::#dimension,634},635count: #actual_bindless_slot_count,636}637);638});639640let bindless_resource_type = match *dimension {641BindingTextureDimension::D1 => {642quote! {643#render_path::render_resource::BindlessResourceType::Texture1d644}645}646BindingTextureDimension::D2 => {647quote! {648#render_path::render_resource::BindlessResourceType::Texture2d649}650}651BindingTextureDimension::D2Array => {652quote! {653#render_path::render_resource::BindlessResourceType::Texture2dArray654}655}656BindingTextureDimension::Cube => {657quote! {658#render_path::render_resource::BindlessResourceType::TextureCube659}660}661BindingTextureDimension::CubeArray => {662quote! {663#render_path::render_resource::BindlessResourceType::TextureCubeArray664}665}666BindingTextureDimension::D3 => {667quote! {668#render_path::render_resource::BindlessResourceType::Texture3d669}670}671};672673// Add the texture to the `BindlessResourceType` list in the674// bindless descriptor.675add_bindless_resource_type(676&render_path,677&mut bindless_resource_types,678binding_index,679bindless_resource_type,680);681}682683BindingType::Sampler => {684let SamplerAttrs {685sampler_binding_type,686visibility,687..688} = get_sampler_attrs(nested_meta_items)?;689let TextureAttrs { dimension, .. } = tex_attrs690.as_ref()691.expect("sampler attribute must have matching texture attribute");692693let visibility =694visibility.hygienic_quote("e! { #render_path::render_resource });695696let fallback_image = get_fallback_image(&render_path, *dimension);697698let expected_samplers = match sampler_binding_type {699SamplerBindingType::Filtering => {700quote!( [#render_path::render_resource::TextureSampleType::Float { filterable: true }] )701}702SamplerBindingType::NonFiltering => quote!([703#render_path::render_resource::TextureSampleType::Float { filterable: false },704#render_path::render_resource::TextureSampleType::Sint,705#render_path::render_resource::TextureSampleType::Uint,706]),707SamplerBindingType::Comparison => {708quote!( [#render_path::render_resource::TextureSampleType::Depth] )709}710};711712// insert fallible texture-based entries at 0 so that if we fail here, we exit before allocating any buffers713binding_impls.insert(0, quote! {714(715#binding_index,716#render_path::render_resource::OwnedBindingResource::Sampler(717// TODO: Support other types.718#render_path::render_resource::SamplerBindingType::Filtering,719{720let handle: Option<&#asset_path::Handle<#image_path::Image>> = (&self.#field_name).into();721if let Some(handle) = handle {722let image = images.get(handle).ok_or_else(|| #render_path::render_resource::AsBindGroupError::RetryNextUpdate)?;723724let Some(sample_type) = image.texture_descriptor.format.sample_type(None, Some(render_device.features())) else {725return Err(#render_path::render_resource::AsBindGroupError::InvalidSamplerType(726#binding_index,727"None".to_string(),728format!("{:?}", #expected_samplers),729));730};731732let valid = #expected_samplers.contains(&sample_type);733734if !valid {735return Err(#render_path::render_resource::AsBindGroupError::InvalidSamplerType(736#binding_index,737format!("{:?}", sample_type),738format!("{:?}", #expected_samplers),739));740}741image.sampler.clone()742} else {743#fallback_image.sampler.clone()744}745})746)747});748749sampler_binding_count += 1;750751non_bindless_binding_layouts.push(quote!{752#bind_group_layout_entries.push(753#render_path::render_resource::BindGroupLayoutEntry {754binding: #binding_index,755visibility: #visibility,756ty: #render_path::render_resource::BindingType::Sampler(#render_path::render_resource::#sampler_binding_type),757count: #actual_bindless_slot_count,758}759);760});761762// Add the sampler to the `BindlessResourceType` list in the763// bindless descriptor.764//765// TODO: Support other types of samplers.766add_bindless_resource_type(767&render_path,768&mut bindless_resource_types,769binding_index,770quote! {771#render_path::render_resource::BindlessResourceType::SamplerFiltering772},773);774}775}776}777}778779// Produce impls for fields with uniform bindings780let struct_name = &ast.ident;781let struct_name_literal = struct_name.to_string();782let struct_name_literal = struct_name_literal.as_str();783let mut field_struct_impls = Vec::new();784785let uniform_binding_type_declarations = match attr_bindless_count {786Some(_) => {787quote! {788let (#uniform_binding_type, #uniform_buffer_usages) =789if Self::bindless_supported(render_device) && !force_no_bindless {790(791#render_path::render_resource::BufferBindingType::Storage { read_only: true },792#render_path::render_resource::BufferUsages::STORAGE,793)794} else {795(796#render_path::render_resource::BufferBindingType::Uniform,797#render_path::render_resource::BufferUsages::UNIFORM,798)799};800}801}802None => {803quote! {804let (#uniform_binding_type, #uniform_buffer_usages) = (805#render_path::render_resource::BufferBindingType::Uniform,806#render_path::render_resource::BufferUsages::UNIFORM,807);808}809}810};811812for (binding_index, binding_state) in binding_states.iter().enumerate() {813let binding_index = binding_index as u32;814if let BindingState::OccupiedMergeableUniform { uniform_fields } = binding_state {815// single field uniform bindings for a given index can use a straightforward binding816if uniform_fields.len() == 1 {817let field = &uniform_fields[0];818let field_name = field.ident.as_ref().unwrap();819let field_ty = &field.ty;820binding_impls.push(quote! {{821let mut buffer = #render_path::render_resource::encase::UniformBuffer::new(Vec::new());822buffer.write(&self.#field_name).unwrap();823(824#binding_index,825#render_path::render_resource::OwnedBindingResource::Buffer(render_device.create_buffer_with_data(826&#render_path::render_resource::BufferInitDescriptor {827label: None,828usage: #uniform_buffer_usages,829contents: buffer.as_ref(),830},831))832)833}});834835non_bindless_binding_layouts.push(quote!{836#bind_group_layout_entries.push(837#render_path::render_resource::BindGroupLayoutEntry {838binding: #binding_index,839visibility: #render_path::render_resource::ShaderStages::FRAGMENT | #render_path::render_resource::ShaderStages::VERTEX | #render_path::render_resource::ShaderStages::COMPUTE,840ty: #render_path::render_resource::BindingType::Buffer {841ty: #uniform_binding_type,842has_dynamic_offset: false,843min_binding_size: Some(<#field_ty as #render_path::render_resource::ShaderType>::min_size()),844},845count: #actual_bindless_slot_count,846}847);848});849// multi-field uniform bindings for a given index require an intermediate struct to derive ShaderType850} else {851let uniform_struct_name = Ident::new(852&format!("_{struct_name}AsBindGroupUniformStructBindGroup{binding_index}"),853Span::call_site(),854);855856let field_name = uniform_fields.iter().map(|f| f.ident.as_ref().unwrap());857let field_type = uniform_fields.iter().map(|f| &f.ty);858field_struct_impls.push(quote! {859#[derive(#render_path::render_resource::ShaderType)]860struct #uniform_struct_name<'a> {861#(#field_name: &'a #field_type,)*862}863});864865let field_name = uniform_fields.iter().map(|f| f.ident.as_ref().unwrap());866binding_impls.push(quote! {{867let mut buffer = #render_path::render_resource::encase::UniformBuffer::new(Vec::new());868buffer.write(&#uniform_struct_name {869#(#field_name: &self.#field_name,)*870}).unwrap();871(872#binding_index,873#render_path::render_resource::OwnedBindingResource::Buffer(render_device.create_buffer_with_data(874&#render_path::render_resource::BufferInitDescriptor {875label: None,876usage: #uniform_buffer_usages,877contents: buffer.as_ref(),878},879))880)881}});882883non_bindless_binding_layouts.push(quote!{884#bind_group_layout_entries.push(#render_path::render_resource::BindGroupLayoutEntry {885binding: #binding_index,886visibility: #render_path::render_resource::ShaderStages::FRAGMENT | #render_path::render_resource::ShaderStages::VERTEX | #render_path::render_resource::ShaderStages::COMPUTE,887ty: #render_path::render_resource::BindingType::Buffer {888ty: #uniform_binding_type,889has_dynamic_offset: false,890min_binding_size: Some(<#uniform_struct_name as #render_path::render_resource::ShaderType>::min_size()),891},892count: #actual_bindless_slot_count,893});894});895}896}897}898899let generics = ast.generics;900let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();901902let (prepared_data, get_prepared_data) = if let Some(prepared) = attr_prepared_data_ident {903let get_prepared_data = quote! { self.into() };904(quote! {#prepared}, get_prepared_data)905} else {906let prepared_data = quote! { () };907(prepared_data.clone(), prepared_data)908};909910// Calculate the number of samplers that we need, so that we don't go over911// the limit on certain platforms. See912// https://github.com/bevyengine/bevy/issues/16988.913let bindless_count_syntax = match attr_bindless_count {914Some(BindlessSlabResourceLimitAttr::Auto) => {915quote! { #render_path::render_resource::AUTO_BINDLESS_SLAB_RESOURCE_LIMIT }916}917Some(BindlessSlabResourceLimitAttr::Limit(ref count)) => {918quote! { #count }919}920None => quote! { 0 },921};922923// Calculate the actual bindless index table range, taking the924// `#[bindless(index_table(range(M..N)))]` attribute into account.925let bindless_index_table_range = match attr_bindless_index_table_range {926None => {927let resource_count = bindless_resource_types.len() as u32;928quote! {929#render_path::render_resource::BindlessIndex(0)..930#render_path::render_resource::BindlessIndex(#resource_count)931}932}933Some(BindlessIndexTableRangeAttr { start, end }) => {934quote! {935#render_path::render_resource::BindlessIndex(#start)..936#render_path::render_resource::BindlessIndex(#end)937}938}939};940941// Calculate the actual binding number of the bindless index table, taking942// the `#[bindless(index_table(binding(B)))]` into account.943let bindless_index_table_binding_number = match attr_bindless_index_table_binding {944None => quote! { #render_path::render_resource::BindingNumber(0) },945Some(binding_number) => {946quote! { #render_path::render_resource::BindingNumber(#binding_number) }947}948};949950// Calculate the actual number of bindless slots, taking hardware951// limitations into account.952let (bindless_slot_count, actual_bindless_slot_count_declaration, bindless_descriptor_syntax) =953match attr_bindless_count {954Some(ref bindless_count) => {955let bindless_supported_syntax = quote! {956fn bindless_supported(957render_device: &#render_path::renderer::RenderDevice958) -> bool {959render_device.features().contains(960#render_path::settings::WgpuFeatures::BUFFER_BINDING_ARRAY |961#render_path::settings::WgpuFeatures::TEXTURE_BINDING_ARRAY962) &&963render_device.limits().max_storage_buffers_per_shader_stage > 0 &&964render_device.limits().max_samplers_per_shader_stage >=965(#sampler_binding_count * #bindless_count_syntax)966}967};968let actual_bindless_slot_count_declaration = quote! {969let #actual_bindless_slot_count = if Self::bindless_supported(render_device) &&970!force_no_bindless {971::core::num::NonZeroU32::new(#bindless_count_syntax)972} else {973None974};975};976let bindless_slot_count_declaration = match bindless_count {977BindlessSlabResourceLimitAttr::Auto => {978quote! {979fn bindless_slot_count() -> Option<980#render_path::render_resource::BindlessSlabResourceLimit981> {982Some(#render_path::render_resource::BindlessSlabResourceLimit::Auto)983}984}985}986BindlessSlabResourceLimitAttr::Limit(lit) => {987quote! {988fn bindless_slot_count() -> Option<989#render_path::render_resource::BindlessSlabResourceLimit990> {991Some(#render_path::render_resource::BindlessSlabResourceLimit::Custom(#lit))992}993}994}995};996997let bindless_buffer_descriptor_count = bindless_buffer_descriptors.len();998999// We use `LazyLock` so that we can call `min_size`, which isn't1000// a `const fn`.1001let bindless_descriptor_syntax = quote! {1002static RESOURCES: &[#render_path::render_resource::BindlessResourceType] = &[1003#(#bindless_resource_types),*1004];1005static BUFFERS: ::std::sync::LazyLock<[1006#render_path::render_resource::BindlessBufferDescriptor;1007#bindless_buffer_descriptor_count1008]> = ::std::sync::LazyLock::new(|| {1009[#(#bindless_buffer_descriptors),*]1010});1011static INDEX_TABLES: &[1012#render_path::render_resource::BindlessIndexTableDescriptor1013] = &[1014#render_path::render_resource::BindlessIndexTableDescriptor {1015indices: #bindless_index_table_range,1016binding_number: #bindless_index_table_binding_number,1017}1018];1019Some(#render_path::render_resource::BindlessDescriptor {1020resources: ::std::borrow::Cow::Borrowed(RESOURCES),1021buffers: ::std::borrow::Cow::Borrowed(&*BUFFERS),1022index_tables: ::std::borrow::Cow::Borrowed(&*INDEX_TABLES),1023})1024};10251026(1027quote! {1028#bindless_slot_count_declaration1029#bindless_supported_syntax1030},1031actual_bindless_slot_count_declaration,1032bindless_descriptor_syntax,1033)1034}1035None => (1036TokenStream::new().into(),1037quote! { let #actual_bindless_slot_count: Option<::core::num::NonZeroU32> = None; },1038quote! { None },1039),1040};10411042Ok(TokenStream::from(quote! {1043#(#field_struct_impls)*10441045impl #impl_generics #render_path::render_resource::AsBindGroup for #struct_name #ty_generics #where_clause {1046type Data = #prepared_data;10471048type Param = (1049#ecs_path::system::lifetimeless::SRes<#render_path::render_asset::RenderAssets<#render_path::texture::GpuImage>>,1050#ecs_path::system::lifetimeless::SRes<#render_path::texture::FallbackImage>,1051#ecs_path::system::lifetimeless::SRes<#render_path::render_asset::RenderAssets<#render_path::storage::GpuShaderBuffer>>,1052);10531054#bindless_slot_count10551056fn label() -> &'static str {1057#struct_name_literal1058}10591060fn unprepared_bind_group(1061&self,1062layout: &#render_path::render_resource::BindGroupLayout,1063render_device: &#render_path::renderer::RenderDevice,1064(images, fallback_image, storage_buffers): &mut #ecs_path::system::SystemParamItem<'_, '_, Self::Param>,1065force_no_bindless: bool,1066) -> Result<#render_path::render_resource::UnpreparedBindGroup, #render_path::render_resource::AsBindGroupError> {1067#uniform_binding_type_declarations10681069let bindings = #render_path::render_resource::BindingResources(vec![#(#binding_impls,)*]);10701071Ok(#render_path::render_resource::UnpreparedBindGroup {1072bindings,1073})1074}10751076#[allow(clippy::unused_unit)]1077fn bind_group_data(&self) -> Self::Data {1078#get_prepared_data1079}10801081fn bind_group_layout_entries(1082render_device: &#render_path::renderer::RenderDevice,1083force_no_bindless: bool1084) -> Vec<#render_path::render_resource::BindGroupLayoutEntry> {1085#actual_bindless_slot_count_declaration1086#uniform_binding_type_declarations10871088let mut #bind_group_layout_entries = Vec::new();1089match #actual_bindless_slot_count {1090Some(bindless_slot_count) => {1091let bindless_index_table_range = #bindless_index_table_range;1092#bind_group_layout_entries.extend(1093#render_path::render_resource::create_bindless_bind_group_layout_entries(1094bindless_index_table_range.end.0 -1095bindless_index_table_range.start.0,1096bindless_slot_count.into(),1097#bindless_index_table_binding_number,1098).into_iter()1099);1100#(#bindless_binding_layouts)*;1101}1102None => {1103#(#non_bindless_binding_layouts)*;1104}1105};1106#bind_group_layout_entries1107}11081109fn bindless_descriptor() -> Option<#render_path::render_resource::BindlessDescriptor> {1110#bindless_descriptor_syntax1111}1112}1113}))1114}11151116/// Adds a bindless resource type to the `BindlessResourceType` array in the1117/// bindless descriptor we're building up.1118///1119/// See the `bevy_render::render_resource::bindless::BindlessResourceType`1120/// documentation for more information.1121fn add_bindless_resource_type(1122render_path: &syn::Path,1123bindless_resource_types: &mut Vec<proc_macro2::TokenStream>,1124binding_index: u32,1125bindless_resource_type: proc_macro2::TokenStream,1126) {1127// If we need to grow the array, pad the unused fields with1128// `BindlessResourceType::None`.1129if bindless_resource_types.len() < (binding_index as usize + 1) {1130bindless_resource_types.resize_with(binding_index as usize + 1, || {1131quote! { #render_path::render_resource::BindlessResourceType::None }1132});1133}11341135// Assign the `BindlessResourceType`.1136bindless_resource_types[binding_index as usize] = bindless_resource_type;1137}11381139fn get_fallback_image(1140render_path: &syn::Path,1141dimension: BindingTextureDimension,1142) -> proc_macro2::TokenStream {1143quote! {1144match #render_path::render_resource::#dimension {1145#render_path::render_resource::TextureViewDimension::D1 => &fallback_image.d1,1146#render_path::render_resource::TextureViewDimension::D2 => &fallback_image.d2,1147#render_path::render_resource::TextureViewDimension::D2Array => &fallback_image.d2_array,1148#render_path::render_resource::TextureViewDimension::Cube => &fallback_image.cube,1149#render_path::render_resource::TextureViewDimension::CubeArray => &fallback_image.cube_array,1150#render_path::render_resource::TextureViewDimension::D3 => &fallback_image.d3,1151}1152}1153}11541155/// Represents the arguments for the `uniform` binding attribute.1156///1157/// If parsed, represents an attribute1158/// like `#[uniform(LitInt, Ident)]`1159struct UniformBindingMeta {1160lit_int: LitInt,1161ident: Ident,1162binding_array: Option<LitInt>,1163}11641165/// The parsed structure-level `#[uniform]` or `#[data]` attribute.1166///1167/// The corresponding syntax is `#[uniform(BINDING_INDEX, CONVERTED_SHADER_TYPE,1168/// binding_array(BINDING_ARRAY)]`, optionally replacing `uniform` with `data`.1169struct UniformBindingAttr {1170/// Whether the declaration is `#[uniform]` or `#[data]`.1171binding_type: UniformBindingAttrType,1172/// The binding index.1173binding_index: u32,1174/// The uniform data type.1175converted_shader_type: Ident,1176/// The binding number of the binding array, if this is a bindless material.1177binding_array: Option<u32>,1178}11791180/// Whether a structure-level shader type declaration is `#[uniform]` or1181/// `#[data]`.1182enum UniformBindingAttrType {1183/// `#[uniform]`: i.e. in bindless mode, we need a separate buffer per data1184/// instance.1185Uniform,1186/// `#[data]`: i.e. in bindless mode, we concatenate all instance data into1187/// a single buffer.1188Data,1189}11901191/// Represents the arguments for any general binding attribute.1192///1193/// If parsed, represents an attribute1194/// like `#[foo(LitInt, ...)]` where the rest is optional [`Meta`].1195enum BindingMeta {1196IndexOnly(LitInt),1197IndexWithOptions(BindingIndexOptions),1198}11991200/// Represents the arguments for an attribute with a list of arguments.1201///1202/// This represents, for example, `#[texture(0, dimension = "2d_array")]`.1203struct BindingIndexOptions {1204lit_int: LitInt,1205_comma: Comma,1206meta_list: Punctuated<Meta, Comma>,1207}12081209impl Parse for BindingMeta {1210fn parse(input: ParseStream) -> Result<Self> {1211if input.peek2(Comma) {1212input.parse().map(Self::IndexWithOptions)1213} else {1214input.parse().map(Self::IndexOnly)1215}1216}1217}12181219impl Parse for BindingIndexOptions {1220fn parse(input: ParseStream) -> Result<Self> {1221Ok(Self {1222lit_int: input.parse()?,1223_comma: input.parse()?,1224meta_list: input.parse_terminated(Meta::parse, Comma)?,1225})1226}1227}12281229impl Parse for UniformBindingMeta {1230// Parse syntax like `#[uniform(0, StandardMaterial, binding_array(10))]`.1231fn parse(input: ParseStream) -> Result<Self> {1232let lit_int = input.parse()?;1233input.parse::<Comma>()?;1234let ident = input.parse()?;12351236// Look for a `binding_array(BINDING_NUMBER)` declaration.1237let mut binding_array: Option<LitInt> = None;1238if input.parse::<Comma>().is_ok() {1239if input1240.parse::<syn::Path>()?1241.get_ident()1242.is_none_or(|ident| *ident != BINDING_ARRAY_MODIFIER_NAME)1243{1244return Err(Error::new_spanned(ident, "Expected `binding_array`"));1245}1246let parser;1247parenthesized!(parser in input);1248binding_array = Some(parser.parse()?);1249}12501251Ok(Self {1252lit_int,1253ident,1254binding_array,1255})1256}1257}12581259/// Parses a structure-level `#[uniform]` attribute (not a field-level1260/// `#[uniform]` attribute).1261fn get_uniform_binding_attr(attr: &syn::Attribute) -> Result<UniformBindingAttr> {1262let attr_ident = attr1263.path()1264.get_ident()1265.expect("Shouldn't be here if we didn't have an attribute");12661267let uniform_binding_meta = attr.parse_args_with(UniformBindingMeta::parse)?;12681269let binding_index = uniform_binding_meta.lit_int.base10_parse()?;1270let ident = uniform_binding_meta.ident;1271let binding_array = match uniform_binding_meta.binding_array {1272None => None,1273Some(binding_array) => Some(binding_array.base10_parse()?),1274};12751276Ok(UniformBindingAttr {1277binding_type: if attr_ident == UNIFORM_ATTRIBUTE_NAME {1278UniformBindingAttrType::Uniform1279} else {1280UniformBindingAttrType::Data1281},1282binding_index,1283converted_shader_type: ident,1284binding_array,1285})1286}12871288fn get_binding_nested_attr(attr: &syn::Attribute) -> Result<(u32, Vec<Meta>)> {1289let binding_meta = attr.parse_args_with(BindingMeta::parse)?;12901291match binding_meta {1292BindingMeta::IndexOnly(lit_int) => Ok((lit_int.base10_parse()?, Vec::new())),1293BindingMeta::IndexWithOptions(BindingIndexOptions {1294lit_int,1295_comma: _,1296meta_list,1297}) => Ok((lit_int.base10_parse()?, meta_list.into_iter().collect())),1298}1299}13001301#[derive(Default)]1302enum ShaderStageVisibility {1303#[default]1304All,1305None,1306Flags(VisibilityFlags),1307}13081309#[derive(Default)]1310struct VisibilityFlags {1311vertex: bool,1312fragment: bool,1313compute: bool,1314}13151316impl ShaderStageVisibility {1317fn vertex_fragment() -> Self {1318Self::Flags(VisibilityFlags::vertex_fragment())1319}13201321fn compute() -> Self {1322Self::Flags(VisibilityFlags::compute())1323}1324}13251326impl VisibilityFlags {1327fn vertex_fragment() -> Self {1328Self {1329vertex: true,1330fragment: true,1331..Default::default()1332}1333}13341335fn compute() -> Self {1336Self {1337compute: true,1338..Default::default()1339}1340}1341}13421343impl ShaderStageVisibility {1344fn hygienic_quote(&self, path: &proc_macro2::TokenStream) -> proc_macro2::TokenStream {1345match self {1346ShaderStageVisibility::All => quote! {1347if cfg!(feature = "webgpu") {1348todo!("Please use a more specific shader stage: https://github.com/gfx-rs/wgpu/issues/7708")1349} else {1350#path::ShaderStages::all()1351}1352},1353ShaderStageVisibility::None => quote! { #path::ShaderStages::NONE },1354ShaderStageVisibility::Flags(flags) => {1355let mut quoted = Vec::new();13561357if flags.vertex {1358quoted.push(quote! { #path::ShaderStages::VERTEX });1359}1360if flags.fragment {1361quoted.push(quote! { #path::ShaderStages::FRAGMENT });1362}1363if flags.compute {1364quoted.push(quote! { #path::ShaderStages::COMPUTE });1365}13661367quote! { #(#quoted)|* }1368}1369}1370}1371}13721373const VISIBILITY: Symbol = Symbol("visibility");1374const VISIBILITY_VERTEX: Symbol = Symbol("vertex");1375const VISIBILITY_FRAGMENT: Symbol = Symbol("fragment");1376const VISIBILITY_COMPUTE: Symbol = Symbol("compute");1377const VISIBILITY_ALL: Symbol = Symbol("all");1378const VISIBILITY_NONE: Symbol = Symbol("none");13791380fn get_visibility_flag_value(meta_list: &MetaList) -> Result<ShaderStageVisibility> {1381let mut flags = Vec::new();13821383meta_list.parse_nested_meta(|meta| {1384flags.push(meta.path);1385Ok(())1386})?;13871388if flags.is_empty() {1389return Err(Error::new_spanned(1390meta_list,1391"Invalid visibility format. Must be `visibility(flags)`, flags can be `all`, `none`, or a list-combination of `vertex`, `fragment` and/or `compute`."1392));1393}13941395if flags.len() == 11396&& let Some(flag) = flags.first()1397{1398if flag == VISIBILITY_ALL {1399return Ok(ShaderStageVisibility::All);1400} else if flag == VISIBILITY_NONE {1401return Ok(ShaderStageVisibility::None);1402}1403}14041405let mut visibility = VisibilityFlags::default();14061407for flag in flags {1408if flag == VISIBILITY_VERTEX {1409visibility.vertex = true;1410} else if flag == VISIBILITY_FRAGMENT {1411visibility.fragment = true;1412} else if flag == VISIBILITY_COMPUTE {1413visibility.compute = true;1414} else {1415return Err(Error::new_spanned(1416flag,1417"Not a valid visibility flag. Must be `all`, `none`, or a list-combination of `vertex`, `fragment` and/or `compute`."1418));1419}1420}14211422Ok(ShaderStageVisibility::Flags(visibility))1423}14241425// Returns the `binding_array(10)` part of a field-level declaration like1426// `#[storage(binding_array(10))]`.1427fn get_binding_array_flag_value(meta_list: &MetaList) -> Result<u32> {1428meta_list1429.parse_args_with(|input: ParseStream| input.parse::<LitInt>())?1430.base10_parse()1431}14321433#[derive(Clone, Copy, Default)]1434enum BindingTextureDimension {1435D1,1436#[default]1437D2,1438D2Array,1439Cube,1440CubeArray,1441D3,1442}14431444enum BindingTextureSampleType {1445Float { filterable: bool },1446Depth,1447Sint,1448Uint,1449}14501451impl ToTokens for BindingTextureDimension {1452fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {1453tokens.extend(match self {1454BindingTextureDimension::D1 => quote! { TextureViewDimension::D1 },1455BindingTextureDimension::D2 => quote! { TextureViewDimension::D2 },1456BindingTextureDimension::D2Array => quote! { TextureViewDimension::D2Array },1457BindingTextureDimension::Cube => quote! { TextureViewDimension::Cube },1458BindingTextureDimension::CubeArray => quote! { TextureViewDimension::CubeArray },1459BindingTextureDimension::D3 => quote! { TextureViewDimension::D3 },1460});1461}1462}14631464impl ToTokens for BindingTextureSampleType {1465fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {1466tokens.extend(match self {1467BindingTextureSampleType::Float { filterable } => {1468quote! { TextureSampleType::Float { filterable: #filterable } }1469}1470BindingTextureSampleType::Depth => quote! { TextureSampleType::Depth },1471BindingTextureSampleType::Sint => quote! { TextureSampleType::Sint },1472BindingTextureSampleType::Uint => quote! { TextureSampleType::Uint },1473});1474}1475}14761477struct TextureAttrs {1478dimension: BindingTextureDimension,1479sample_type: BindingTextureSampleType,1480multisampled: bool,1481visibility: ShaderStageVisibility,1482}14831484impl Default for BindingTextureSampleType {1485fn default() -> Self {1486BindingTextureSampleType::Float { filterable: true }1487}1488}14891490impl Default for TextureAttrs {1491fn default() -> Self {1492Self {1493dimension: Default::default(),1494sample_type: Default::default(),1495multisampled: true,1496visibility: Default::default(),1497}1498}1499}15001501struct StorageTextureAttrs {1502dimension: BindingTextureDimension,1503// Parsing of the image_format parameter is deferred to the type checker,1504// which will error if the format is not member of the TextureFormat enum.1505image_format: proc_macro2::TokenStream,1506// Parsing of the access parameter is deferred to the type checker,1507// which will error if the access is not member of the StorageTextureAccess enum.1508access: proc_macro2::TokenStream,1509visibility: ShaderStageVisibility,1510}15111512impl Default for StorageTextureAttrs {1513fn default() -> Self {1514Self {1515dimension: Default::default(),1516image_format: quote! { Rgba8Unorm },1517access: quote! { ReadWrite },1518visibility: ShaderStageVisibility::compute(),1519}1520}1521}15221523fn get_storage_texture_binding_attr(metas: Vec<Meta>) -> Result<StorageTextureAttrs> {1524let mut storage_texture_attrs = StorageTextureAttrs::default();15251526for meta in metas {1527use syn::Meta::{List, NameValue};1528match meta {1529// Parse #[storage_texture(0, dimension = "...")].1530NameValue(m) if m.path == DIMENSION => {1531let value = get_lit_str(DIMENSION, &m.value)?;1532storage_texture_attrs.dimension = get_texture_dimension_value(value)?;1533}1534// Parse #[storage_texture(0, format = ...))].1535NameValue(m) if m.path == IMAGE_FORMAT => {1536storage_texture_attrs.image_format = m.value.into_token_stream();1537}1538// Parse #[storage_texture(0, access = ...))].1539NameValue(m) if m.path == ACCESS => {1540storage_texture_attrs.access = m.value.into_token_stream();1541}1542// Parse #[storage_texture(0, visibility(...))].1543List(m) if m.path == VISIBILITY => {1544storage_texture_attrs.visibility = get_visibility_flag_value(&m)?;1545}1546NameValue(m) => {1547return Err(Error::new_spanned(1548m.path,1549"Not a valid name. Available attributes: `dimension`, `image_format`, `access`.",1550));1551}1552_ => {1553return Err(Error::new_spanned(1554meta,1555"Not a name value pair: `foo = \"...\"`",1556));1557}1558}1559}15601561Ok(storage_texture_attrs)1562}15631564const DIMENSION: Symbol = Symbol("dimension");1565const IMAGE_FORMAT: Symbol = Symbol("image_format");1566const ACCESS: Symbol = Symbol("access");1567const SAMPLE_TYPE: Symbol = Symbol("sample_type");1568const FILTERABLE: Symbol = Symbol("filterable");1569const MULTISAMPLED: Symbol = Symbol("multisampled");15701571// Values for `dimension` attribute.1572const DIM_1D: &str = "1d";1573const DIM_2D: &str = "2d";1574const DIM_3D: &str = "3d";1575const DIM_2D_ARRAY: &str = "2d_array";1576const DIM_CUBE: &str = "cube";1577const DIM_CUBE_ARRAY: &str = "cube_array";15781579// Values for sample `type` attribute.1580const FLOAT: &str = "float";1581const DEPTH: &str = "depth";1582const S_INT: &str = "s_int";1583const U_INT: &str = "u_int";15841585fn get_texture_attrs(metas: Vec<Meta>) -> Result<TextureAttrs> {1586let mut dimension = Default::default();1587let mut sample_type = Default::default();1588let mut multisampled = Default::default();1589let mut filterable = None;1590let mut filterable_ident = None;15911592let mut visibility = ShaderStageVisibility::vertex_fragment();15931594for meta in metas {1595use syn::Meta::{List, NameValue};1596match meta {1597// Parse #[texture(0, dimension = "...")].1598NameValue(m) if m.path == DIMENSION => {1599let value = get_lit_str(DIMENSION, &m.value)?;1600dimension = get_texture_dimension_value(value)?;1601}1602// Parse #[texture(0, sample_type = "...")].1603NameValue(m) if m.path == SAMPLE_TYPE => {1604let value = get_lit_str(SAMPLE_TYPE, &m.value)?;1605sample_type = get_texture_sample_type_value(value)?;1606}1607// Parse #[texture(0, multisampled = "...")].1608NameValue(m) if m.path == MULTISAMPLED => {1609multisampled = get_lit_bool(MULTISAMPLED, &m.value)?;1610}1611// Parse #[texture(0, filterable = "...")].1612NameValue(m) if m.path == FILTERABLE => {1613filterable = get_lit_bool(FILTERABLE, &m.value)?.into();1614filterable_ident = m.path.into();1615}1616// Parse #[texture(0, visibility(...))].1617List(m) if m.path == VISIBILITY => {1618visibility = get_visibility_flag_value(&m)?;1619}1620NameValue(m) => {1621return Err(Error::new_spanned(1622m.path,1623"Not a valid name. Available attributes: `dimension`, `sample_type`, `multisampled`, or `filterable`."1624));1625}1626_ => {1627return Err(Error::new_spanned(1628meta,1629"Not a name value pair: `foo = \"...\"`",1630));1631}1632}1633}16341635// Resolve `filterable` since the float1636// sample type is the one that contains the value.1637if let Some(filterable) = filterable {1638let path = filterable_ident.unwrap();1639match sample_type {1640BindingTextureSampleType::Float { filterable: _ } => {1641sample_type = BindingTextureSampleType::Float { filterable }1642}1643_ => {1644return Err(Error::new_spanned(1645path,1646"Type must be `float` to use the `filterable` attribute.",1647));1648}1649};1650}16511652Ok(TextureAttrs {1653dimension,1654sample_type,1655multisampled,1656visibility,1657})1658}16591660fn get_texture_dimension_value(lit_str: &LitStr) -> Result<BindingTextureDimension> {1661match lit_str.value().as_str() {1662DIM_1D => Ok(BindingTextureDimension::D1),1663DIM_2D => Ok(BindingTextureDimension::D2),1664DIM_2D_ARRAY => Ok(BindingTextureDimension::D2Array),1665DIM_3D => Ok(BindingTextureDimension::D3),1666DIM_CUBE => Ok(BindingTextureDimension::Cube),1667DIM_CUBE_ARRAY => Ok(BindingTextureDimension::CubeArray),16681669_ => Err(Error::new_spanned(1670lit_str,1671"Not a valid dimension. Must be `1d`, `2d`, `2d_array`, `3d`, `cube` or `cube_array`.",1672)),1673}1674}16751676fn get_texture_sample_type_value(lit_str: &LitStr) -> Result<BindingTextureSampleType> {1677match lit_str.value().as_str() {1678FLOAT => Ok(BindingTextureSampleType::Float { filterable: true }),1679DEPTH => Ok(BindingTextureSampleType::Depth),1680S_INT => Ok(BindingTextureSampleType::Sint),1681U_INT => Ok(BindingTextureSampleType::Uint),16821683_ => Err(Error::new_spanned(1684lit_str,1685"Not a valid sample type. Must be `float`, `depth`, `s_int` or `u_int`.",1686)),1687}1688}16891690#[derive(Default)]1691struct SamplerAttrs {1692sampler_binding_type: SamplerBindingType,1693visibility: ShaderStageVisibility,1694}16951696#[derive(Default)]1697enum SamplerBindingType {1698#[default]1699Filtering,1700NonFiltering,1701Comparison,1702}17031704impl ToTokens for SamplerBindingType {1705fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {1706tokens.extend(match self {1707SamplerBindingType::Filtering => quote! { SamplerBindingType::Filtering },1708SamplerBindingType::NonFiltering => quote! { SamplerBindingType::NonFiltering },1709SamplerBindingType::Comparison => quote! { SamplerBindingType::Comparison },1710});1711}1712}17131714const SAMPLER_TYPE: Symbol = Symbol("sampler_type");17151716const FILTERING: &str = "filtering";1717const NON_FILTERING: &str = "non_filtering";1718const COMPARISON: &str = "comparison";17191720fn get_sampler_attrs(metas: Vec<Meta>) -> Result<SamplerAttrs> {1721let mut sampler_binding_type = Default::default();1722let mut visibility = ShaderStageVisibility::vertex_fragment();17231724for meta in metas {1725use syn::Meta::{List, NameValue};1726match meta {1727// Parse #[sampler(0, sampler_type = "..."))].1728NameValue(m) if m.path == SAMPLER_TYPE => {1729let value = get_lit_str(DIMENSION, &m.value)?;1730sampler_binding_type = get_sampler_binding_type_value(value)?;1731}1732// Parse #[sampler(0, visibility(...))].1733List(m) if m.path == VISIBILITY => {1734visibility = get_visibility_flag_value(&m)?;1735}1736NameValue(m) => {1737return Err(Error::new_spanned(1738m.path,1739"Not a valid name. Available attributes: `sampler_type`.",1740));1741}1742_ => {1743return Err(Error::new_spanned(1744meta,1745"Not a name value pair: `foo = \"...\"`",1746));1747}1748}1749}17501751Ok(SamplerAttrs {1752sampler_binding_type,1753visibility,1754})1755}17561757fn get_sampler_binding_type_value(lit_str: &LitStr) -> Result<SamplerBindingType> {1758match lit_str.value().as_str() {1759FILTERING => Ok(SamplerBindingType::Filtering),1760NON_FILTERING => Ok(SamplerBindingType::NonFiltering),1761COMPARISON => Ok(SamplerBindingType::Comparison),17621763_ => Err(Error::new_spanned(1764lit_str,1765"Not a valid dimension. Must be `filtering`, `non_filtering`, or `comparison`.",1766)),1767}1768}17691770#[derive(Default)]1771struct StorageAttrs {1772visibility: ShaderStageVisibility,1773binding_array: Option<u32>,1774read_only: bool,1775buffer: bool,1776}17771778const READ_ONLY: Symbol = Symbol("read_only");1779const BUFFER: Symbol = Symbol("buffer");17801781fn get_storage_binding_attr(metas: Vec<Meta>) -> Result<StorageAttrs> {1782let mut visibility = ShaderStageVisibility::vertex_fragment();1783let mut binding_array = None;1784let mut read_only = false;1785let mut buffer = false;17861787for meta in metas {1788use syn::Meta::{List, Path};1789match meta {1790// Parse #[storage(0, visibility(...))].1791List(m) if m.path == VISIBILITY => {1792visibility = get_visibility_flag_value(&m)?;1793}1794// Parse #[storage(0, binding_array(...))] for bindless mode.1795List(m) if m.path == BINDING_ARRAY_MODIFIER_NAME => {1796binding_array = Some(get_binding_array_flag_value(&m)?);1797}1798Path(path) if path == READ_ONLY => {1799read_only = true;1800}1801Path(path) if path == BUFFER => {1802buffer = true;1803}1804_ => {1805return Err(Error::new_spanned(1806meta,1807"Not a valid attribute. Available attributes: `read_only`, `visibility`",1808));1809}1810}1811}18121813Ok(StorageAttrs {1814visibility,1815binding_array,1816read_only,1817buffer,1818})1819}182018211822