GithubHelp home page GithubHelp logo

mosure / bevy_interleave Goto Github PK

View Code? Open in Web Editor NEW
2.0 1.0 0.0 42 KB

derive macros for interleaved and planar formats, compatible with bevy ShaderType

License: MIT License

Dockerfile 1.34% Rust 97.27% HTML 1.39%
bevy interleaved macros

bevy_interleave's Introduction

bevy_interleave 🧩

test GitHub License GitHub Releases GitHub Issues crates.io

bevy support for e2e packed to planar bind groups

minimal example

use bevy_interleave::prelude::*;

#[derive(
    Debug,
    Planar,
    ReflectInterleaved,
    StorageBindings,
    TextureBindings,
)]
pub struct MyStruct {
    #[texture_format(TextureFormat::R32Sint)]
    pub field: i32,

    #[texture_format(TextureFormat::R32Uint)]
    pub field2: u32,

    #[texture_format(TextureFormat::R8Unorm)]
    pub bool_field: bool,

    #[texture_format(TextureFormat::Rgba32Uint)]
    pub array: [u32; 4],
}

fn interleaved() -> Vec<MyStruct> {
    vec![
        MyStruct { field: 0, field2: 1_u32, bool_field: true, array: [0, 1, 2, 3] },
        MyStruct { field: 2, field2: 3_u32, bool_field: false, array: [4, 5, 6, 7] },
        MyStruct { field: 4, field2: 5_u32, bool_field: true, array: [8, 9, 10, 11] },
    ];
}

fn main() {
    let planar = PlanarMyStruct::from_interleaved(interleaved());

    println!("{:?}", planar.field);
    println!("{:?}", planar.field2);
    println!("{:?}", planar.bool_field);
    println!("{:?}", planar.array);

    // Prints:
    // [0, 2, 4]
    // [1, 3, 5]
    // [true, false, true]
    // [[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]]

    println!("\n\n{:?}", MyStruct::min_binding_sizes());
    println!("{:?}", MyStruct::ordered_field_names());

    // Prints:
    // [4, 4, 1, 16]
    // ["field", "field2", "bool_field", "array"]


    let mut app = App::new()
        .add_plugins((
            DefaultPlugins,
            PlanarPlugin::<PlanarMyStruct>::default(),
            PlanarTexturePlugin::<PlanarTextureMyStruct>::default(),
        ));

    app.sub_app_mut(bevy::render::RenderApp)
        .add_systems(
            bevy::render::Render,
            check_bind_group.in_set(bevy::render::RenderSet::QueueMeshes),
        );

    app.run();
}

fn setup_planar_asset(
    mut commands: Commands,
    mut planar_assets: ResMut<Assets<PlanarMyStruct>>,
) {
    let planar = PlanarMyStruct::from_interleaved(interleaved());

    commands.spawn(planar_assets.add(planar));
}

fn check_bind_group(
    bind_group: Query<&PlanarTextureBindGroup::<PlanarTextureMyStruct>>,
) {
    // attach bind group to render pipeline
    // size:
    //     2 x 2 x bpp
    // format:
    //     binding: 0 - texture - R32Sint - depth 1
    //     binding: 1 - texture - R32Uint - depth 1
    //     binding: 2 - texture - R8Unorm - depth 1
    //     binding: 3 - texture - Rgba32Uint - depth 1
}

why bevy?

bevy_interleave simplifies bind group creation within bevy. Planar derives can be used in conjunction with ShaderType's to support both packed and planar data render pipelines.

compatible bevy versions

bevy_interleave bevy
0.2 0.13
0.1 0.12

bevy_interleave's People

Contributors

mosure avatar

Stargazers

Marko Lazić avatar  avatar

Watchers

 avatar

bevy_interleave's Issues

support 3D texture sampling

bevy::render::render_resource::TextureViewDimension::D2 // TODO: support 3D texture sampling

use convert_case::{
    Case,
    Casing,
};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{
    Attribute,
    Data,
    DeriveInput,
    Error,
    Fields,
    FieldsNamed,
    Ident,
    parse::{
        Parse,
        ParseStream,
    },
    Path,
    Result,
};


pub fn texture_bindings(input: &DeriveInput) -> Result<quote::__private::TokenStream> {
    let name = &input.ident;

    let planar_name = Ident::new(&format!("Planar{}", name), name.span());
    let gpu_planar_name = Ident::new(&format!("PlanarTexture{}", name), name.span());

    let fields_struct = if let Data::Struct(ref data_struct) = input.data {
        match data_struct.fields {
            Fields::Named(ref fields) => fields,
            _ => return Err(Error::new_spanned(input, "Unsupported struct type")),
        }
    } else {
        return Err(Error::new_spanned(input, "Planar macro only supports structs"));
    };

    let field_names = fields_struct.named.iter().map(|f| f.ident.as_ref().unwrap());
    let field_types = fields_struct.named.iter().map(|_| {
        quote! { bevy::asset::Handle<bevy::render::texture::Image> }
    });

    let bind_group = generate_bind_group_method(name, fields_struct);
    let bind_group_layout = generate_bind_group_layout_method(name, fields_struct);
    let prepare = generate_prepare_method(fields_struct);
    let get_asset_handles = generate_get_asset_handles_method(fields_struct);

    let expanded = quote! {
        #[derive(bevy::prelude::Component, Default, Clone, Debug, bevy::reflect::Reflect)]
        pub struct #gpu_planar_name {
            #(pub #field_names: #field_types,)*
        }

        impl bevy::render::extract_component::ExtractComponent for #gpu_planar_name {
            type QueryData = &'static Self;

            type QueryFilter = ();
            type Out = Self;

            fn extract_component(texture_buffers: bevy::ecs::query::QueryItem<'_, Self::QueryData>) -> Option<Self::Out> {
                texture_buffers.clone().into()
            }
        }

        impl PlanarTexture for #gpu_planar_name {
            type PackedType = #name;
            type PlanarType = #planar_name;

            #bind_group
            #bind_group_layout
            #prepare
            #get_asset_handles
        }
    };

    Ok(expanded)
}


pub fn generate_bind_group_method(struct_name: &Ident, fields_named: &FieldsNamed) -> quote::__private::TokenStream {
    let struct_name_snake = struct_name.to_string().to_case(Case::Snake);
    let bind_group_name = format!("texture_{}_bind_group", struct_name_snake);

    let bind_group_entries = fields_named.named
        .iter()
        .enumerate()
        .map(|(idx, field)| {
            let name = field.ident.as_ref().unwrap();
            quote! {
                bevy::render::render_resource::BindGroupEntry {
                    binding: #idx as u32,
                    resource: bevy::render::render_resource::BindingResource::TextureView(
                        &gpu_images.get(&self.#name).unwrap().texture_view
                    ),
                },
            }
        });

    quote! {
        fn bind_group(
            &self,
            render_device: &bevy::render::renderer::RenderDevice,
            gpu_images: &bevy::render::render_asset::RenderAssets<bevy::render::texture::Image>,
            layout: &bevy::render::render_resource::BindGroupLayout,
        ) -> bevy::render::render_resource::BindGroup {
            render_device.create_bind_group(
                #bind_group_name,
                &layout,
                &[
                    #(#bind_group_entries)*
                ]
            )
        }
    }
}


pub fn generate_bind_group_layout_method(struct_name: &Ident, fields_named: &FieldsNamed) -> quote::__private::TokenStream {
    let struct_name_snake = struct_name.to_string().to_case(Case::Snake);
    let bind_group_layout_name = format!("texture_{}_bind_group_layout", struct_name_snake);

    let bind_group_layout_entries = fields_named.named
        .iter()
        .enumerate()
        .map(|(idx, field)| {
            let name = field.ident.as_ref().unwrap();
            let format = extract_texture_format(&field.attrs);

            let field_type = &field.ty;

            quote! {
                let sample_type = #format.sample_type(None, None).unwrap();

                let size = std::mem::size_of::<#field_type>();
                let format_bpp = #format.pixel_size();
                let depth = (size as f32 / format_bpp as f32).ceil() as u32;

                let view_dimension = if depth == 1 {
                    bevy::render::render_resource::TextureViewDimension::D2  // TODO: support 3D texture sampling
                } else {
                    bevy::render::render_resource::TextureViewDimension::D2Array
                };

                let #name = bevy::render::render_resource::BindGroupLayoutEntry {
                    binding: #idx as u32,
                    visibility: bevy::render::render_resource::ShaderStages::all(),
                    ty: bevy::render::render_resource::BindingType::Texture {
                        view_dimension,
                        sample_type,
                        multisampled: false,
                    },
                    count: None,
                };
            }
        });

    let layout_names = fields_named.named
        .iter()
        .map(|field| {
            let name = field.ident.as_ref().unwrap();
            quote! { #name }
        });

    quote! {
        fn bind_group_layout(
            render_device: &bevy::render::renderer::RenderDevice,
        ) -> bevy::render::render_resource::BindGroupLayout {
            #(#bind_group_layout_entries)*

            render_device.create_bind_group_layout(
                Some(#bind_group_layout_name),
                &[
                    #(#layout_names),*
                ],
            )
        }
    }
}


pub fn generate_prepare_method(fields_named: &FieldsNamed) -> quote::__private::TokenStream {
    let buffers = fields_named.named
        .iter()
        .map(|field| {
            let name = field.ident.as_ref().unwrap();
            let format = extract_texture_format(&field.attrs);

            let field_type = &field.ty;

            quote! {
                let square = (planar.#name.len() as f32).sqrt().ceil() as u32;

                let size = std::mem::size_of::<#field_type>();
                let format_bpp = #format.pixel_size();
                let depth = (size as f32 / format_bpp as f32).ceil() as u32;

                let mut data = bytemuck::cast_slice(planar.#name.as_slice()).to_vec();

                let padded_size = (square * square * depth * format_bpp as u32) as usize;
                data.resize(padded_size, 0);

                let mut #name = bevy::render::texture::Image::new(
                    bevy::render::render_resource::Extent3d {
                        width: square,
                        height: square,
                        depth_or_array_layers: depth,
                    },
                    bevy::render::render_resource::TextureDimension::D2,
                    data,
                    #format,
                    bevy::render::render_asset::RenderAssetUsages::default(),  // TODO: if there are no CPU image derived features, set to render only
                );
                let #name = images.add(#name);
            }
        });

    let buffer_names = fields_named.named
        .iter()
        .map(|field| {
            let name = field.ident.as_ref().unwrap();
            quote! { #name }
        });

    quote! {
        fn prepare(
            images: &mut bevy::asset::Assets<bevy::render::texture::Image>,
            planar: &Self::PlanarType,
        ) -> Self {
            #(#buffers)*

            Self {
                #(#buffer_names),*
            }
        }
    }
}


pub fn generate_get_asset_handles_method(fields_named: &FieldsNamed) -> quote::__private::TokenStream {
    let buffer_names = fields_named.named
        .iter()
        .map(|field| {
            let name = field.ident.as_ref().unwrap();
            quote! { self.#name.clone() }
        });

    quote! {
        fn get_asset_handles(&self) -> Vec<bevy::asset::Handle<bevy::render::texture::Image>> {
            vec![
                #(#buffer_names),*
            ]
        }
    }
}


struct TextureFormatAttr(Path);

impl Parse for TextureFormatAttr {
    fn parse(input: ParseStream) -> Result<Self> {
        let format: Path = input.parse()?;
        Ok(TextureFormatAttr(format))
    }
}

fn extract_texture_format(attributes: &[Attribute]) -> TokenStream {
    for attr in attributes {
        if attr.path().is_ident("texture_format") {
            if let Ok(parsed) = attr.parse_args::<TextureFormatAttr>() {
                let TextureFormatAttr(format) = parsed;
                return quote! { #format };
            } else {
                panic!("error parsing texture_format attribute");
            }
        }
    }

    panic!("no texture_format attribute found, add `#[texture_format(Ident)]` to the field declarations");
}

PlanarStoragePlugin::<PlanarStorageMyStruct>::default(),

// TODO: PlanarStoragePlugin::<PlanarStorageMyStruct>::default(),

use bevy::prelude::*;

use bevy_interleave::prelude::*;


#[derive(
    Debug,
    Planar,
    ReflectInterleaved,
    StorageBindings,
    TextureBindings,
)]
pub struct MyStruct {
    #[texture_format(TextureFormat::R32Sint)]
    pub field: i32,

    #[texture_format(TextureFormat::R32Uint)]
    pub field2: u32,

    #[texture_format(TextureFormat::R8Unorm)]
    pub bool_field: bool,

    #[texture_format(TextureFormat::Rgba32Uint)]
    pub array: [u32; 4],
}


fn main() {
    let mut app = App::new();

    app.add_plugins((
        DefaultPlugins,
        PlanarPlugin::<PlanarMyStruct>::default(),
        PlanarTexturePlugin::<PlanarTextureMyStruct>::default(),
        // TODO: PlanarStoragePlugin::<PlanarStorageMyStruct>::default(),
    ));

    app.run();
}

if there are no CPU image derived features, set to render only

bevy::render::render_asset::RenderAssetUsages::default(), // TODO: if there are no CPU image derived features, set to render only

use convert_case::{
    Case,
    Casing,
};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{
    Attribute,
    Data,
    DeriveInput,
    Error,
    Fields,
    FieldsNamed,
    Ident,
    parse::{
        Parse,
        ParseStream,
    },
    Path,
    Result,
};


pub fn texture_bindings(input: &DeriveInput) -> Result<quote::__private::TokenStream> {
    let name = &input.ident;

    let planar_name = Ident::new(&format!("Planar{}", name), name.span());
    let gpu_planar_name = Ident::new(&format!("PlanarTexture{}", name), name.span());

    let fields_struct = if let Data::Struct(ref data_struct) = input.data {
        match data_struct.fields {
            Fields::Named(ref fields) => fields,
            _ => return Err(Error::new_spanned(input, "Unsupported struct type")),
        }
    } else {
        return Err(Error::new_spanned(input, "Planar macro only supports structs"));
    };

    let field_names = fields_struct.named.iter().map(|f| f.ident.as_ref().unwrap());
    let field_types = fields_struct.named.iter().map(|_| {
        quote! { bevy::asset::Handle<bevy::render::texture::Image> }
    });

    let bind_group = generate_bind_group_method(name, fields_struct);
    let bind_group_layout = generate_bind_group_layout_method(name, fields_struct);
    let prepare = generate_prepare_method(fields_struct);
    let get_asset_handles = generate_get_asset_handles_method(fields_struct);

    let expanded = quote! {
        #[derive(bevy::prelude::Component, Default, Clone, Debug, bevy::reflect::Reflect)]
        pub struct #gpu_planar_name {
            #(pub #field_names: #field_types,)*
        }

        impl bevy::render::extract_component::ExtractComponent for #gpu_planar_name {
            type QueryData = &'static Self;

            type QueryFilter = ();
            type Out = Self;

            fn extract_component(texture_buffers: bevy::ecs::query::QueryItem<'_, Self::QueryData>) -> Option<Self::Out> {
                texture_buffers.clone().into()
            }
        }

        impl PlanarTexture for #gpu_planar_name {
            type PackedType = #name;
            type PlanarType = #planar_name;

            #bind_group
            #bind_group_layout
            #prepare
            #get_asset_handles
        }
    };

    Ok(expanded)
}


pub fn generate_bind_group_method(struct_name: &Ident, fields_named: &FieldsNamed) -> quote::__private::TokenStream {
    let struct_name_snake = struct_name.to_string().to_case(Case::Snake);
    let bind_group_name = format!("texture_{}_bind_group", struct_name_snake);

    let bind_group_entries = fields_named.named
        .iter()
        .enumerate()
        .map(|(idx, field)| {
            let name = field.ident.as_ref().unwrap();
            quote! {
                bevy::render::render_resource::BindGroupEntry {
                    binding: #idx as u32,
                    resource: bevy::render::render_resource::BindingResource::TextureView(
                        &gpu_images.get(&self.#name).unwrap().texture_view
                    ),
                },
            }
        });

    quote! {
        fn bind_group(
            &self,
            render_device: &bevy::render::renderer::RenderDevice,
            gpu_images: &bevy::render::render_asset::RenderAssets<bevy::render::texture::Image>,
            layout: &bevy::render::render_resource::BindGroupLayout,
        ) -> bevy::render::render_resource::BindGroup {
            render_device.create_bind_group(
                #bind_group_name,
                &layout,
                &[
                    #(#bind_group_entries)*
                ]
            )
        }
    }
}


pub fn generate_bind_group_layout_method(struct_name: &Ident, fields_named: &FieldsNamed) -> quote::__private::TokenStream {
    let struct_name_snake = struct_name.to_string().to_case(Case::Snake);
    let bind_group_layout_name = format!("texture_{}_bind_group_layout", struct_name_snake);

    let bind_group_layout_entries = fields_named.named
        .iter()
        .enumerate()
        .map(|(idx, field)| {
            let name = field.ident.as_ref().unwrap();
            let format = extract_texture_format(&field.attrs);

            let field_type = &field.ty;

            quote! {
                let sample_type = #format.sample_type(None, None).unwrap();

                let size = std::mem::size_of::<#field_type>();
                let format_bpp = #format.pixel_size();
                let depth = (size as f32 / format_bpp as f32).ceil() as u32;

                let view_dimension = if depth == 1 {
                    bevy::render::render_resource::TextureViewDimension::D2  // TODO: support 3D texture sampling
                } else {
                    bevy::render::render_resource::TextureViewDimension::D2Array
                };

                let #name = bevy::render::render_resource::BindGroupLayoutEntry {
                    binding: #idx as u32,
                    visibility: bevy::render::render_resource::ShaderStages::all(),
                    ty: bevy::render::render_resource::BindingType::Texture {
                        view_dimension,
                        sample_type,
                        multisampled: false,
                    },
                    count: None,
                };
            }
        });

    let layout_names = fields_named.named
        .iter()
        .map(|field| {
            let name = field.ident.as_ref().unwrap();
            quote! { #name }
        });

    quote! {
        fn bind_group_layout(
            render_device: &bevy::render::renderer::RenderDevice,
        ) -> bevy::render::render_resource::BindGroupLayout {
            #(#bind_group_layout_entries)*

            render_device.create_bind_group_layout(
                Some(#bind_group_layout_name),
                &[
                    #(#layout_names),*
                ],
            )
        }
    }
}


pub fn generate_prepare_method(fields_named: &FieldsNamed) -> quote::__private::TokenStream {
    let buffers = fields_named.named
        .iter()
        .map(|field| {
            let name = field.ident.as_ref().unwrap();
            let format = extract_texture_format(&field.attrs);

            let field_type = &field.ty;

            quote! {
                let square = (planar.#name.len() as f32).sqrt().ceil() as u32;

                let size = std::mem::size_of::<#field_type>();
                let format_bpp = #format.pixel_size();
                let depth = (size as f32 / format_bpp as f32).ceil() as u32;

                let mut data = bytemuck::cast_slice(planar.#name.as_slice()).to_vec();

                let padded_size = (square * square * depth * format_bpp as u32) as usize;
                data.resize(padded_size, 0);

                let mut #name = bevy::render::texture::Image::new(
                    bevy::render::render_resource::Extent3d {
                        width: square,
                        height: square,
                        depth_or_array_layers: depth,
                    },
                    bevy::render::render_resource::TextureDimension::D2,
                    data,
                    #format,
                    bevy::render::render_asset::RenderAssetUsages::default(),  // TODO: if there are no CPU image derived features, set to render only
                );
                let #name = images.add(#name);
            }
        });

    let buffer_names = fields_named.named
        .iter()
        .map(|field| {
            let name = field.ident.as_ref().unwrap();
            quote! { #name }
        });

    quote! {
        fn prepare(
            images: &mut bevy::asset::Assets<bevy::render::texture::Image>,
            planar: &Self::PlanarType,
        ) -> Self {
            #(#buffers)*

            Self {
                #(#buffer_names),*
            }
        }
    }
}


pub fn generate_get_asset_handles_method(fields_named: &FieldsNamed) -> quote::__private::TokenStream {
    let buffer_names = fields_named.named
        .iter()
        .map(|field| {
            let name = field.ident.as_ref().unwrap();
            quote! { self.#name.clone() }
        });

    quote! {
        fn get_asset_handles(&self) -> Vec<bevy::asset::Handle<bevy::render::texture::Image>> {
            vec![
                #(#buffer_names),*
            ]
        }
    }
}


struct TextureFormatAttr(Path);

impl Parse for TextureFormatAttr {
    fn parse(input: ParseStream) -> Result<Self> {
        let format: Path = input.parse()?;
        Ok(TextureFormatAttr(format))
    }
}

fn extract_texture_format(attributes: &[Attribute]) -> TokenStream {
    for attr in attributes {
        if attr.path().is_ident("texture_format") {
            if let Ok(parsed) = attr.parse_args::<TextureFormatAttr>() {
                let TextureFormatAttr(format) = parsed;
                return quote! { #format };
            } else {
                panic!("error parsing texture_format attribute");
            }
        }
    }

    panic!("no texture_format attribute found, add `#[texture_format(Ident)]` to the field declarations");
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.