diff --git a/Cargo.lock b/Cargo.lock index 7d724904f..5f727d417 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1397,6 +1397,17 @@ dependencies = [ "bevy_nannou_video", ] +[[package]] +name = "bevy_nannou_derive" +version = "0.1.0" +dependencies = [ + "bevy 0.15.0-dev", + "bevy_nannou_draw", + "proc-macro2 1.0.86", + "quote 1.0.36", + "syn 1.0.109", +] + [[package]] name = "bevy_nannou_draw" version = "0.1.0" @@ -5359,6 +5370,7 @@ dependencies = [ "bevy_common_assets", "bevy_egui 0.29.0 (git+https://github.com/tychedelia/bevy_egui?branch=main)", "bevy_nannou", + "bevy_nannou_derive", "find_folder", "futures 0.3.30", "getrandom 0.2.15", diff --git a/Cargo.toml b/Cargo.toml index 0f526d9ff..280303fd8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "bevy_nannou", + "bevy_nannou_derive", "bevy_nannou_draw", "bevy_nannou_isf", "bevy_nannou_video", diff --git a/bevy_nannou_derive/Cargo.toml b/bevy_nannou_derive/Cargo.toml new file mode 100644 index 000000000..ade9c3685 --- /dev/null +++ b/bevy_nannou_derive/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "bevy_nannou_derive" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "1", features = ["full"] } +quote = "1.0" +proc-macro2 = "1.0" + +[dev-dependencies] +bevy = { workspace = true } +bevy_nannou_draw = { path = "../bevy_nannou_draw" } \ No newline at end of file diff --git a/bevy_nannou_derive/src/lib.rs b/bevy_nannou_derive/src/lib.rs new file mode 100644 index 000000000..c45305c95 --- /dev/null +++ b/bevy_nannou_derive/src/lib.rs @@ -0,0 +1,75 @@ +use proc_macro::TokenStream; +use quote::{quote, ToTokens}; +use syn::{parse_macro_input, parse_quote, Attribute, ItemStruct, Lit, Meta, NestedMeta}; + +#[proc_macro_attribute] +pub fn shader_model(attr: TokenStream, item: TokenStream) -> TokenStream { + let mut input = parse_macro_input!(item as ItemStruct); + let attrs = parse_macro_input!(attr as syn::AttributeArgs); + + let (vertex_shader, fragment_shader) = parse_shader_attributes(&attrs); + + let name = &input.ident; + let generics = &input.generics; + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let vertex_shader_impl = vertex_shader + .map(|path| quote! { #path.into() }) + .unwrap_or_else(|| quote! { ::nannou::prelude::ShaderRef::Default }); + + let fragment_shader_impl = fragment_shader + .map(|path| quote! { #path.into() }) + .unwrap_or_else(|| quote! { ::nannou::prelude::ShaderRef::Default }); + + // Add derive attributes + input + .attrs + .push(parse_quote!(#[derive(Asset, TypePath, AsBindGroup, Debug, Clone, Default)])); + + let expanded = quote! { + #input + + impl #impl_generics ::nannou::prelude::render::ShaderModel for #name #ty_generics #where_clause { + fn vertex_shader() -> ::nannou::prelude::ShaderRef { + #vertex_shader_impl + } + + fn fragment_shader() -> ::nannou::prelude::ShaderRef { + #fragment_shader_impl + } + } + + impl #impl_generics ::nannou::prelude::Material for #name #ty_generics #where_clause { + fn vertex_shader() -> ::nannou::prelude::ShaderRef { + ::vertex_shader() + } + + fn fragment_shader() -> ::nannou::prelude::ShaderRef { + ::fragment_shader() + } + } + }; + + TokenStream::from(expanded) +} + +fn parse_shader_attributes(attrs: &[syn::NestedMeta]) -> (Option, Option) { + let mut vertex_shader = None; + let mut fragment_shader = None; + + for attr in attrs { + if let NestedMeta::Meta(Meta::NameValue(nv)) = attr { + if nv.path.is_ident("vertex") { + if let Lit::Str(lit) = &nv.lit { + vertex_shader = Some(lit.value()); + } + } else if nv.path.is_ident("fragment") { + if let Lit::Str(lit) = &nv.lit { + fragment_shader = Some(lit.value()); + } + } + } + } + + (vertex_shader, fragment_shader) +} diff --git a/bevy_nannou_derive/tests/shader_model_tests.rs b/bevy_nannou_derive/tests/shader_model_tests.rs new file mode 100644 index 000000000..3897e626f --- /dev/null +++ b/bevy_nannou_derive/tests/shader_model_tests.rs @@ -0,0 +1,60 @@ +use bevy::render::render_resource::ShaderRef; +use bevy_nannou_derive::shader_model; +use bevy_nannou_draw::render::ShaderModel; + +#[shader_model] +struct TestMaterial {} + +#[test] +fn test_default_shaders() { + assert!(matches!(TestMaterial::vertex_shader(), ShaderRef::Default)); + assert!(matches!( + TestMaterial::fragment_shader(), + ShaderRef::Default + )); +} + +#[shader_model(vertex = "custom_vertex.wgsl")] +struct TestVertexMaterial {} + +#[test] +fn test_custom_vertex_shader() { + assert!(matches!( + TestVertexMaterial::vertex_shader(), + ShaderRef::Path(_) + )); + assert!(matches!( + TestVertexMaterial::fragment_shader(), + ShaderRef::Default + )); +} + +#[shader_model(fragment = "custom_fragment.wgsl")] +struct TestFragmentMaterial {} + +#[test] +fn test_custom_fragment_shader() { + assert!(matches!( + TestFragmentMaterial::vertex_shader(), + ShaderRef::Default + )); + assert!(matches!( + TestFragmentMaterial::fragment_shader(), + ShaderRef::Path(_) + )); +} + +#[shader_model(vertex = "custom_vertex.wgsl", fragment = "custom_fragment.wgsl")] +struct TestBothMaterial {} + +#[test] +fn test_both_custom_shaders() { + assert!(matches!( + TestBothMaterial::vertex_shader(), + ShaderRef::Path(_) + )); + assert!(matches!( + TestBothMaterial::fragment_shader(), + ShaderRef::Path(_) + )); +} diff --git a/bevy_nannou_draw/src/draw/drawing.rs b/bevy_nannou_draw/src/draw/drawing.rs index 1dc77e877..8f781d8e0 100644 --- a/bevy_nannou_draw/src/draw/drawing.rs +++ b/bevy_nannou_draw/src/draw/drawing.rs @@ -140,51 +140,6 @@ where self.finish_inner() } - /// Set the material's fragment shader for the drawing. Note: this shader must have - /// been initialized during application setup. - #[cfg(feature = "nightly")] - pub fn fragment_shader( - mut self, - ) -> Drawing<'a, T, ExtendedNannouMaterial<"", FS>> { - self.finish_on_drop = false; - - let Drawing { - ref draw, - index, - material_index, - .. - } = self; - - let state = draw.state.clone(); - let new_id = UntypedAssetId::Uuid { - type_id: TypeId::of::>(), - uuid: Uuid::new_v4(), - }; - - let material: ExtendedNannouMaterial<"", FS> = Default::default(); - let mut state = state.write().unwrap(); - state.materials.insert(new_id.clone(), Box::new(material)); - // Mark the last material as the new material so that further drawings use the same material - // as the parent draw ref. - state.last_material = Some(new_id.clone()); - - let draw = Draw { - state: draw.state.clone(), - context: draw.context.clone(), - material: new_id.clone(), - window: draw.window, - _material: Default::default(), - }; - - Drawing::<'a, T, ExtendedMaterial>> { - draw: DrawRef::Owned(draw), - index, - material_index, - finish_on_drop: true, - _ty: PhantomData, - } - } - // Map the the parent's material to a new material type, taking ownership over the // draw instance clone. pub fn map_material(mut self, map: F) -> Drawing<'a, T, M> diff --git a/bevy_nannou_draw/src/draw/indirect.rs b/bevy_nannou_draw/src/draw/indirect.rs new file mode 100644 index 000000000..682eb9316 --- /dev/null +++ b/bevy_nannou_draw/src/draw/indirect.rs @@ -0,0 +1,329 @@ +//! A shader that renders a mesh multiple times in one draw call. + +use crate::draw::drawing::Drawing; +use crate::draw::primitive::Primitive; +use crate::draw::{Draw, DrawCommand}; +use bevy::core_pipeline::core_3d::Opaque3dBinKey; +use bevy::pbr::{MaterialPipeline, MaterialPipelineKey, PreparedMaterial, SetMaterialBindGroup}; +use bevy::render::mesh::allocator::MeshAllocator; +use bevy::render::mesh::RenderMeshBufferInfo; +use bevy::render::render_asset::prepare_assets; +use bevy::render::render_phase::{BinnedRenderPhaseType, ViewBinnedRenderPhases}; +use bevy::{ + core_pipeline::core_3d::Opaque3d, + ecs::system::{lifetimeless::*, SystemParamItem}, + pbr::{ + MeshPipeline, MeshPipelineKey, RenderMeshInstances, SetMeshBindGroup, SetMeshViewBindGroup, + }, + prelude::*, + render::{ + extract_component::ExtractComponent, + mesh::{MeshVertexBufferLayoutRef, RenderMesh}, + render_asset::RenderAssets, + render_phase::{ + AddRenderCommand, DrawFunctions, PhaseItem, PhaseItemExtraIndex, RenderCommand, + RenderCommandResult, SetItemPipeline, TrackedRenderPass, + }, + render_resource::*, + renderer::RenderDevice, + view::ExtractedView, + Render, RenderApp, RenderSet, + }, +}; +use rayon::prelude::*; +use std::hash::Hash; +use std::marker::PhantomData; +use std::ops::Range; +use bevy::render::storage::ShaderStorageBuffer; + +pub struct Indirect<'a, M> +where + M: Material + Default, +{ + draw: &'a Draw, + data: Option<(usize, Handle)>, +} + +impl<'a, M> Drop for Indirect<'a, M> +where + M: Material + Default, +{ + fn drop(&mut self) { + if let Some((index, data)) = self.data.take() { + self.insert_indirect_draw_command(index, data); + } + } +} + +pub fn new(draw: &Draw) -> Indirect +where + M: Material + Default, +{ + Indirect { draw, data: None } +} + +impl<'a, M> Indirect<'a, M> +where + M: Material + Default, +{ + pub fn with(mut self, drawing: Drawing, indirect_buffer: Handle) -> Indirect<'a, M> + where + T: Into, + { + self.draw + .state + .write() + .unwrap() + .ignored_drawings + .insert(drawing.index); + self.data = Some((drawing.index, indirect_buffer)); + self + } + + fn insert_indirect_draw_command(&self, index: usize, indirect_buffer: Handle) { + let mut state = self.draw.state.write().unwrap(); + let primitive = state.drawing.remove(&index).unwrap(); + state + .draw_commands + .push(Some(DrawCommand::Indirect(primitive, indirect_buffer))); + } +} + +#[derive(Component)] +pub struct IndirectEntity; + +pub struct IndirectMaterialPlugin(PhantomData); + +impl Default for IndirectMaterialPlugin +where + M: Default, +{ + fn default() -> Self { + IndirectMaterialPlugin(PhantomData) + } +} + +impl Plugin for IndirectMaterialPlugin +where + M: Material + Default, + M::Data: PartialEq + Eq + Hash + Clone, +{ + fn build(&self, app: &mut App) { + app.sub_app_mut(RenderApp) + .add_render_command::>() + .init_resource::>>() + .add_systems( + Render, + (queue_indirect:: + .after(prepare_assets::>) + .in_set(RenderSet::QueueMeshes)), + ); + } + + fn finish(&self, app: &mut App) { + app.sub_app_mut(RenderApp) + .init_resource::>(); + } +} + +#[allow(clippy::too_many_arguments)] +fn queue_indirect( + draw_functions: Res>, + custom_pipeline: Res>, + mut pipelines: ResMut>>, + pipeline_cache: Res, + meshes: Res>, + (render_mesh_instances, material_meshes, mut phases, mut views, materials): ( + Res, + Query<(Entity, &Handle), With>, + ResMut>, + Query<(Entity, &ExtractedView, &Msaa)>, + Res>>, + ), +) where + M: Material, + M::Data: PartialEq + Eq + Hash + Clone, +{ + let drawn_function = draw_functions.read().id::>(); + + for (view_entity, view, msaa) in &mut views { + let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples()); + let Some(phase) = phases.get_mut(&view_entity) else { + continue; + }; + + let view_key = msaa_key | MeshPipelineKey::from_hdr(view.hdr); + for (entity, material) in &material_meshes { + let material = materials.get(material).unwrap(); + let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(entity) else { + continue; + }; + let Some(mesh) = meshes.get(mesh_instance.mesh_asset_id) else { + continue; + }; + let mesh_key = + view_key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology()); + let key = MaterialPipelineKey { + mesh_key, + bind_group_data: material.key.clone(), + }; + let pipeline = pipelines + .specialize(&pipeline_cache, &custom_pipeline, key, &mesh.layout) + .unwrap(); + info!("Queueing indirect mesh {:?}", entity); + phase.add( + Opaque3dBinKey { + draw_function: drawn_function, + pipeline, + asset_id: AssetId::::invalid().untyped(), + material_bind_group_id: None, + lightmap_image: None, + }, + entity, + BinnedRenderPhaseType::NonMesh, + ); + } + } +} + +#[derive(Component)] +pub(crate) struct InstanceRange(pub Range); + +#[derive(Resource)] +struct IndirectDataPipeline { + mesh_pipeline: MeshPipeline, + material_layout: BindGroupLayout, + vertex_shader: Option>, + fragment_shader: Option>, + marker: PhantomData, +} + +impl FromWorld for IndirectDataPipeline { + fn from_world(world: &mut World) -> Self { + let asset_server = world.resource::(); + let render_device = world.resource::(); + + IndirectDataPipeline { + mesh_pipeline: world.resource::().clone(), + material_layout: M::bind_group_layout(render_device), + vertex_shader: match M::vertex_shader() { + ShaderRef::Default => None, + ShaderRef::Handle(handle) => Some(handle), + ShaderRef::Path(path) => Some(asset_server.load(path)), + }, + fragment_shader: match M::fragment_shader() { + ShaderRef::Default => None, + ShaderRef::Handle(handle) => Some(handle), + ShaderRef::Path(path) => Some(asset_server.load(path)), + }, + marker: PhantomData, + } + } +} + +impl SpecializedMeshPipeline for IndirectDataPipeline +where + M::Data: PartialEq + Eq + Hash + Clone, +{ + type Key = MaterialPipelineKey; + + fn specialize( + &self, + key: Self::Key, + layout: &MeshVertexBufferLayoutRef, + ) -> Result { + let mut descriptor = self.mesh_pipeline.specialize(key.mesh_key, layout)?; + if let Some(vertex_shader) = &self.vertex_shader { + descriptor.vertex.shader = vertex_shader.clone(); + } + + if let Some(fragment_shader) = &self.fragment_shader { + descriptor.fragment.as_mut().unwrap().shader = fragment_shader.clone(); + } + + descriptor.layout.insert(2, self.material_layout.clone()); + + let pipeline = MaterialPipeline { + mesh_pipeline: self.mesh_pipeline.clone(), + material_layout: self.material_layout.clone(), + vertex_shader: self.vertex_shader.clone(), + fragment_shader: self.fragment_shader.clone(), + marker: Default::default(), + }; + M::specialize(&pipeline, &mut descriptor, layout, key)?; + Ok(descriptor) + } +} + +type DrawIndirectMaterial = ( + SetItemPipeline, + SetMeshViewBindGroup<0>, + SetMeshBindGroup<1>, + SetMaterialBindGroup, + DrawMeshIndirect, +); + +struct DrawMeshIndirect; + +impl RenderCommand

for DrawMeshIndirect { + type Param = ( + SRes>, + SRes, + SRes, + ); + type ViewQuery = (); + type ItemQuery = Read; + + #[inline] + fn render<'w>( + item: &P, + _view: (), + instance_range: Option<&'w InstanceRange>, + (meshes, render_mesh_instances, mesh_allocator): SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, + ) -> RenderCommandResult { + let mesh_allocator = mesh_allocator.into_inner(); + + let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(item.entity()) + else { + return RenderCommandResult::Skip; + }; + let Some(gpu_mesh) = meshes.into_inner().get(mesh_instance.mesh_asset_id) else { + return RenderCommandResult::Skip; + }; + let Some(instance_range) = instance_range else { + return RenderCommandResult::Skip; + }; + let Some(vertex_buffer_slice) = + mesh_allocator.mesh_vertex_slice(&mesh_instance.mesh_asset_id) + else { + return RenderCommandResult::Skip; + }; + + pass.set_vertex_buffer(0, vertex_buffer_slice.buffer.slice(..)); + + match &gpu_mesh.buffer_info { + RenderMeshBufferInfo::Indexed { + index_format, + count, + } => { + let Some(index_buffer_slice) = + mesh_allocator.mesh_index_slice(&mesh_instance.mesh_asset_id) + else { + return RenderCommandResult::Skip; + }; + + pass.set_index_buffer(index_buffer_slice.buffer.slice(..), 0, *index_format); + pass.draw_indexed( + index_buffer_slice.range.start..(index_buffer_slice.range.start + count), + vertex_buffer_slice.range.start as i32, + instance_range.0.clone(), + ); + } + RenderMeshBufferInfo::NonIndexed => { + pass.draw(0..gpu_mesh.vertex_count, instance_range.0.clone()); + } + } + RenderCommandResult::Success + } +} diff --git a/bevy_nannou_draw/src/lib.rs b/bevy_nannou_draw/src/lib.rs index a6dabaa52..577ffeeee 100644 --- a/bevy_nannou_draw/src/lib.rs +++ b/bevy_nannou_draw/src/lib.rs @@ -1,5 +1,3 @@ -#![cfg_attr(feature = "nightly", feature(adt_const_params))] - use bevy::prelude::*; use crate::render::NannouRenderPlugin; diff --git a/bevy_nannou_draw/src/render.rs b/bevy_nannou_draw/src/render.rs index ebc5c40bf..5c43f26c7 100644 --- a/bevy_nannou_draw/src/render.rs +++ b/bevy_nannou_draw/src/render.rs @@ -2,22 +2,27 @@ use std::any::TypeId; use std::hash::Hash; use std::ops::{Deref, DerefMut}; +use bevy::asset::Asset; use bevy::asset::UntypedAssetId; -use bevy::pbr::MaterialExtension; +use bevy::pbr::{ + ExtendedMaterial, MaterialExtension, MaterialExtensionKey, MaterialExtensionPipeline, + StandardMaterial, +}; +use bevy::prelude::TypePath; use bevy::prelude::*; use bevy::render::camera::RenderTarget; use bevy::render::extract_component::{ExtractComponent, ExtractComponentPlugin}; use bevy::render::extract_resource::{ExtractResource, ExtractResourcePlugin}; +use bevy::render::mesh::MeshVertexBufferLayoutRef; use bevy::render::render_resource as wgpu; -use bevy::render::render_resource::{AsBindGroup, BlendState, PolygonMode}; +use bevy::render::render_resource::{ + AsBindGroup, BlendState, PolygonMode, RenderPipelineDescriptor, ShaderRef, + SpecializedMeshPipelineError, +}; use bevy::render::view::{NoFrustumCulling, RenderLayers}; use bevy::window::WindowRef; use lyon::lyon_tessellation::{FillTessellator, StrokeTessellator}; -#[cfg(feature = "nightly")] -pub use nightly::*; -#[cfg(not(feature = "nightly"))] -pub use stable::*; use crate::draw::instanced::InstancingPlugin; use crate::draw::mesh::MeshExt; @@ -25,6 +30,23 @@ use crate::draw::render::{RenderContext, RenderPrimitive}; use crate::draw::{DrawCommand, DrawContext}; use crate::DrawHolder; +pub trait ShaderModel: + Material + AsBindGroup + Clone + Default + Sized + Send + Sync + 'static +{ + /// Returns this material's vertex shader. If [`ShaderRef::Default`] is returned, the default mesh vertex shader + /// will be used. + fn vertex_shader() -> ShaderRef { + ShaderRef::Default + } + + /// Returns this material's fragment shader. If [`ShaderRef::Default`] is returned, the default mesh fragment shader + /// will be used. + #[allow(unused_variables)] + fn fragment_shader() -> ShaderRef { + ShaderRef::Default + } +} + pub struct NannouRenderPlugin; impl Plugin for NannouRenderPlugin { @@ -58,164 +80,53 @@ where // Components and Resources // ---------------------------------------------------------------------------- -#[cfg(feature = "nightly")] -mod nightly { - use bevy::asset::Asset; - use bevy::pbr::{ - ExtendedMaterial, MaterialExtension, MaterialExtensionKey, MaterialExtensionPipeline, - StandardMaterial, - }; - use bevy::prelude::TypePath; - use bevy::render::mesh::MeshVertexBufferLayoutRef; - use bevy::render::render_resource::{ - AsBindGroup, BlendState, PolygonMode, RenderPipelineDescriptor, ShaderRef, - SpecializedMeshPipelineError, - }; - - pub type DefaultNannouMaterial = ExtendedMaterial>; +pub type DefaultNannouMaterial = ExtendedMaterial; - pub type ExtendedNannouMaterial = - ExtendedMaterial>; - - #[derive(Asset, AsBindGroup, TypePath, Debug, Clone, Default)] - #[bind_group_data(NannouMaterialKey)] - pub struct NannouMaterial { - pub polygon_mode: PolygonMode, - pub blend: Option, - } +pub type ExtendedNannouMaterial = ExtendedMaterial; - #[derive(Eq, PartialEq, Hash, Clone)] - pub struct NannouMaterialKey { - polygon_mode: PolygonMode, - blend: Option, - } - - impl From<&NannouMaterial> - for NannouMaterialKey - { - fn from(material: &NannouMaterial) -> Self { - Self { - polygon_mode: material.polygon_mode, - blend: material.blend, - } - } - } - - impl MaterialExtension for NannouMaterial { - fn vertex_shader() -> ShaderRef { - if !VS.is_empty() { - VS.into() - } else { - ShaderRef::Default - } - } - - fn fragment_shader() -> ShaderRef { - if !FS.is_empty() { - FS.into() - } else { - ShaderRef::Default - } - } - - fn specialize( - _pipeline: &MaterialExtensionPipeline, - descriptor: &mut RenderPipelineDescriptor, - _layout: &MeshVertexBufferLayoutRef, - key: MaterialExtensionKey, - ) -> Result<(), SpecializedMeshPipelineError> { - if let Some(blend) = key.bind_group_data.blend { - let fragment = descriptor.fragment.as_mut().unwrap(); - fragment.targets.iter_mut().for_each(|target| { - if let Some(target) = target { - target.blend = Some(blend); - } - }); - } - - descriptor.primitive.polygon_mode = key.bind_group_data.polygon_mode; - Ok(()) - } - } +#[derive(Asset, AsBindGroup, TypePath, Debug, Clone, Default)] +#[bind_group_data(NannouMaterialKey)] +pub struct NannouMaterial { + pub polygon_mode: PolygonMode, + pub blend: Option, } -#[cfg(not(feature = "nightly"))] -mod stable { - use bevy::asset::Asset; - use bevy::pbr::{ - ExtendedMaterial, MaterialExtension, MaterialExtensionKey, MaterialExtensionPipeline, - StandardMaterial, - }; - use bevy::prelude::TypePath; - use bevy::render::mesh::MeshVertexBufferLayoutRef; - use bevy::render::render_resource::{ - AsBindGroup, BlendState, PolygonMode, RenderPipelineDescriptor, ShaderRef, - SpecializedMeshPipelineError, - }; - - pub type DefaultNannouMaterial = ExtendedMaterial; - - pub type ExtendedNannouMaterial = ExtendedMaterial; - - #[derive(Asset, AsBindGroup, TypePath, Debug, Clone, Default)] - #[bind_group_data(NannouMaterialKey)] - pub struct NannouMaterial { - pub polygon_mode: PolygonMode, - pub blend: Option, - } - - #[derive(Eq, PartialEq, Hash, Clone)] - pub struct NannouMaterialKey { - polygon_mode: PolygonMode, - blend: Option, - } +#[derive(Eq, PartialEq, Hash, Clone)] +pub struct NannouMaterialKey { + polygon_mode: PolygonMode, + blend: Option, +} - impl From<&NannouMaterial> for NannouMaterialKey { - fn from(material: &NannouMaterial) -> Self { - Self { - polygon_mode: material.polygon_mode, - blend: material.blend, - } +impl From<&NannouMaterial> for NannouMaterialKey { + fn from(material: &NannouMaterial) -> Self { + Self { + polygon_mode: material.polygon_mode, + blend: material.blend, } } +} - impl MaterialExtension for NannouMaterial { - fn specialize( - _pipeline: &MaterialExtensionPipeline, - descriptor: &mut RenderPipelineDescriptor, - _layout: &MeshVertexBufferLayoutRef, - key: MaterialExtensionKey, - ) -> Result<(), SpecializedMeshPipelineError> { - if let Some(blend) = key.bind_group_data.blend { - let fragment = descriptor.fragment.as_mut().unwrap(); - fragment.targets.iter_mut().for_each(|target| { - if let Some(target) = target { - target.blend = Some(blend); - } - }); - } - - descriptor.primitive.polygon_mode = key.bind_group_data.polygon_mode; - Ok(()) +impl MaterialExtension for NannouMaterial { + fn specialize( + _pipeline: &MaterialExtensionPipeline, + descriptor: &mut RenderPipelineDescriptor, + _layout: &MeshVertexBufferLayoutRef, + key: MaterialExtensionKey, + ) -> Result<(), SpecializedMeshPipelineError> { + if let Some(blend) = key.bind_group_data.blend { + let fragment = descriptor.fragment.as_mut().unwrap(); + fragment.targets.iter_mut().for_each(|target| { + if let Some(target) = target { + target.blend = Some(blend); + } + }); } - } - impl From<&NannouMaterial> for crate::render::NannouMaterialKey { - fn from(material: &NannouMaterial) -> Self { - Self { - polygon_mode: material.polygon_mode, - blend: material.blend, - } - } + descriptor.primitive.polygon_mode = key.bind_group_data.polygon_mode; + Ok(()) } } -#[derive(Eq, PartialEq, Hash, Clone)] -pub struct NannouMaterialKey { - polygon_mode: PolygonMode, - blend: Option, -} - #[derive(Resource, Deref, DerefMut, ExtractResource, Clone)] pub struct DefaultTextureHandle(Handle); diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 0a8ba9c44..bd5af915c 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -81,10 +81,6 @@ name = "draw_custom_material" path = "draw/draw_custom_material.rs" required-features = ["nannou/hot_reload"] [[example]] -name = "draw_fragment_shader" -path = "draw/draw_fragment_shader.rs" -required-features = ["nannou/nightly", "nannou/hot_reload"] -[[example]] name = "draw_loop" path = "draw/draw_loop.rs" [[example]] diff --git a/examples/draw/draw_custom_material.rs b/examples/draw/draw_custom_material.rs index 350b98111..de9ccb76c 100644 --- a/examples/draw/draw_custom_material.rs +++ b/examples/draw/draw_custom_material.rs @@ -4,25 +4,19 @@ fn main() { nannou::app(model) .simple_window(view) // Register our custom material to make it available for use in our drawing - .init_custom_material::() + .shader_model::() .run() } struct Model {} // This struct defines the data that will be passed to your shader -#[derive(Asset, TypePath, AsBindGroup, Debug, Clone, Default)] -struct CustomMaterial { +#[shader_model(fragment = "draw_custom_material.wgsl")] +struct ShaderModel { #[uniform(0)] color: LinearRgba, } -impl Material for CustomMaterial { - fn fragment_shader() -> ShaderRef { - "draw_custom_material.wgsl".into() - } -} - fn model(app: &App) -> Model { Model {} } @@ -32,7 +26,7 @@ fn view(app: &App, model: &Model, window: Entity) { let draw = app .draw() // Initialize our draw instance with our custom material - .material(CustomMaterial { color: RED.into() }); + .material(ShaderModel { color: RED.into() }); draw.ellipse().x(-200.0); diff --git a/examples/draw/draw_fragment_shader.rs b/examples/draw/draw_fragment_shader.rs deleted file mode 100644 index 09568b870..000000000 --- a/examples/draw/draw_fragment_shader.rs +++ /dev/null @@ -1,29 +0,0 @@ -use nannou::prelude::*; - -fn main() { - nannou::app(model) - .simple_window(view) - // We need to initialize the fragment shader with the path to the shader file - // in our assets directory so that it will be available to use as a material. - .init_fragment_shader::<"draw_fragment_shader.wgsl">() - .run() -} - -struct Model {} - -fn model(app: &App) -> Model { - Model {} -} - -fn view(app: &App, model: &Model, window: Entity) { - // Begin drawing - let draw = app.draw(); - // Draw a full-screen quad - let window = app.main_window(); - let window_rect = window.rect(); - draw.rect() - // Specify the shader to use for this draw call - .fragment_shader::<"draw_fragment_shader.wgsl">() - .w_h(window_rect.w(), window_rect.h()) - .color(WHITE); -} diff --git a/examples/video/video_material.rs b/examples/video/video_material.rs index f5df17231..d4f1d72a4 100644 --- a/examples/video/video_material.rs +++ b/examples/video/video_material.rs @@ -4,7 +4,7 @@ use nannou::prelude::*; fn main() { nannou::app(model) // Register our custom material to make it available for use in our drawing - .init_custom_material::() + .shader_model::() .run(); } @@ -16,19 +16,13 @@ struct Model { } // This struct defines the data that will be passed to your shader -#[derive(Asset, TypePath, AsBindGroup, Debug, Clone, Default)] -struct VideoMaterial { +#[shader_model(fragment = "draw_video_material.wgsl")] +struct VideoShaderModel { #[texture(0)] #[sampler(1)] texture: Handle, } -impl Material for VideoMaterial { - fn fragment_shader() -> ShaderRef { - "draw_video_material.wgsl".into() - } -} - fn model(app: &App) -> Model { let camera = app.new_camera().build(); let window = app @@ -50,14 +44,15 @@ fn model(app: &App) -> Model { } fn view(app: &App, model: &Model) { - let Some(video) = app.assets().get(&model.video) else { + let assets = app.assets(); + let Some(video) = assets.get(&model.video) else { return; }; let draw = app .draw() // Initialize our draw instance with our custom material - .material(VideoMaterial { + .material(VideoShaderModel { texture: video.texture.clone(), }); diff --git a/nannou/Cargo.toml b/nannou/Cargo.toml index b1ba20e93..8628976e6 100644 --- a/nannou/Cargo.toml +++ b/nannou/Cargo.toml @@ -16,6 +16,7 @@ bevy-inspector-egui = { workspace = true, optional = true } bevy_egui = { workspace = true, optional = true } bevy_common_assets = { workspace = true, optional = true } bevy_nannou = { version = "0.1.0", path = "../bevy_nannou" } +bevy_nannou_derive = { version = "0.1.0", path = "../bevy_nannou_derive" } futures = "0.3" find_folder = "0.3" getrandom = "0.2.3" @@ -45,7 +46,6 @@ image = { workspace = true, features = [], default-features = false } [features] default = ["notosans"] egui = ["bevy_egui", "bevy-inspector-egui"] -nightly = ["bevy_nannou/nightly"] hot_reload = ["bevy/file_watcher"] isf = ["bevy_nannou/isf"] video = ["bevy_nannou/video"] diff --git a/nannou/src/app.rs b/nannou/src/app.rs index 3f16d4632..b2a11cdba 100644 --- a/nannou/src/app.rs +++ b/nannou/src/app.rs @@ -54,8 +54,7 @@ use bevy_nannou::NannouPlugin; use crate::frame::{Frame, FramePlugin}; use crate::prelude::bevy_ecs::system::SystemState; -use crate::prelude::bevy_render::extract_component::ExtractComponentPlugin; -use crate::prelude::render::NannouMesh; +use crate::prelude::render::{NannouMesh, ShaderModel}; use crate::prelude::NannouMaterialPlugin; use crate::render::{NannouRenderNode, RenderApp, RenderPlugin}; use crate::window::WindowUserFunctions; @@ -340,23 +339,15 @@ where self } - pub fn init_custom_material(mut self) -> Self + pub fn shader_model(mut self) -> Self where - T: Material + Default, + T: ShaderModel, T::Data: PartialEq + Eq + Hash + Clone, { self.app.add_plugins(NannouMaterialPlugin::::default()); self } - /// Load a fragment shader asset from the given path for use with the nannou `Draw` API. - #[cfg(feature = "nightly")] - pub fn init_fragment_shader(mut self) -> Self { - self.app - .add_plugins(NannouMaterialPlugin::>::default()); - self - } - #[cfg(any(feature = "config_json", feature = "config_toml"))] pub fn init_config(mut self) -> Self where diff --git a/nannou/src/lib.rs b/nannou/src/lib.rs index c2fdccacd..6e3351572 100644 --- a/nannou/src/lib.rs +++ b/nannou/src/lib.rs @@ -12,8 +12,6 @@ //! If you're new to nannou, we recommend checking out [the //! examples](https://github.com/nannou-org/nannou/tree/master/examples) to get an idea of how //! nannou applications are structured and how the API works. -#![cfg_attr(feature = "nightly", feature(adt_const_params))] - pub use find_folder; pub use lyon; diff --git a/nannou/src/prelude.rs b/nannou/src/prelude.rs index 347c93687..1ee22d708 100644 --- a/nannou/src/prelude.rs +++ b/nannou/src/prelude.rs @@ -14,6 +14,7 @@ pub use crate::wgpu; pub use crate::wgpu::util::{BufferInitDescriptor, DeviceExt}; pub use bevy_nannou::prelude::*; pub use nannou_core::prelude::*; +pub use bevy_nannou_derive::shader_model; pub use crate::app::{self, App, RunMode, UpdateModeExt}; pub use crate::camera::SetCamera;