diff --git a/assets/shaders/custom_draw.wgsl b/assets/shaders/custom_draw.wgsl index 982e032ff9733..e8fa9b8507fd3 100644 --- a/assets/shaders/custom_draw.wgsl +++ b/assets/shaders/custom_draw.wgsl @@ -1,22 +1,18 @@ #import bevy_pbr::mesh_functions as mesh_functions -#import bevy_render::view View -#import bevy_pbr::mesh_types Mesh +#import bevy_pbr::{ + mesh_types::Mesh, + view_transformations, + forward_io::Vertex, +} +#import bevy_render::view::View -@group(0) @binding(0) -var view: View; +@group(0) @binding(0) var view: View; +@group(1) @binding(0) var mesh: Mesh; struct CustomMaterial { color: vec4, }; -@group(1) @binding(0) -var material: CustomMaterial; - -@group(2) @binding(0) -var mesh: Mesh; - -struct Vertex { - @location(0) position: vec3, -}; +@group(2) @binding(0) var material: CustomMaterial; struct VertexOutput { @builtin(position) position: vec4, @@ -25,8 +21,9 @@ struct VertexOutput { @vertex fn vertex(vertex: Vertex) -> VertexOutput { var out: VertexOutput; - let world_position = mesh_functions::mesh_position_local_to_world(mesh.model, vec4(vertex.position, 1.0)); - out.position = mesh_functions::mesh_position_world_to_clip(world_position); + let model = mesh_functions::get_model_matrix(vertex.instance_index); + let world_position = mesh_functions::mesh_position_local_to_world(model, vec4(vertex.position, 1.0)); + out.position = view_transformations::position_world_to_clip(world_position.xyz); return out; } diff --git a/examples/shader/custom_render_phase.rs b/examples/shader/custom_render_phase.rs index e7a4850802b90..2636ec0236001 100644 --- a/examples/shader/custom_render_phase.rs +++ b/examples/shader/custom_render_phase.rs @@ -4,6 +4,8 @@ //! //! This is a fairly low level example and assumes some familiarity with rendering concepts and wgpu. +use std::ops::Range; + use bevy::{ core_pipeline::core_3d::{self, CORE_3D}, ecs::{ @@ -14,12 +16,17 @@ use bevy::{ }, }, pbr::{ - DrawMesh, MeshPipeline, MeshPipelineKey, MeshUniform, SetMeshBindGroup, - SetMeshViewBindGroup, + DrawMesh, MeshPipeline, MeshPipelineKey, MeshPipelineViewLayoutKey, RenderMeshInstances, + SetMeshBindGroup, SetMeshViewBindGroup, }, prelude::*, render::{ - extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin}, + batching::batch_and_prepare_render_phase, + extract_component::{ + ComponentUniforms, DynamicUniformIndex, ExtractComponent, ExtractComponentPlugin, + UniformComponentPlugin, + }, + mesh::MeshVertexBufferLayout, render_asset::RenderAssets, render_graph::{ NodeRunError, RenderGraphApp, RenderGraphContext, ViewNode, ViewNodeRunner, @@ -30,18 +37,16 @@ use bevy::{ SetItemPipeline, TrackedRenderPass, }, render_resource::{ - BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, - BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, BufferBindingType, - CachedRenderPipelineId, LoadOp, Operations, PipelineCache, - RenderPassDepthStencilAttachment, RenderPassDescriptor, RenderPipelineDescriptor, - ShaderStages, ShaderType, SpecializedMeshPipeline, SpecializedMeshPipelineError, - SpecializedMeshPipelines, + binding_types::uniform_buffer, BindGroup, BindGroupEntries, BindGroupLayout, + BindGroupLayoutEntries, CachedRenderPipelineId, PipelineCache, RenderPassDescriptor, + RenderPipelineDescriptor, ShaderStages, ShaderType, SpecializedMeshPipeline, + SpecializedMeshPipelineError, SpecializedMeshPipelines, StoreOp, }, renderer::{RenderContext, RenderDevice}, view::{ExtractedView, ViewDepthTexture, ViewTarget, VisibleEntities}, Extract, Render, RenderApp, RenderSet, }, - utils::FloatOrd, + utils::{nonmax::NonMaxU32, FloatOrd}, }; fn main() { @@ -60,7 +65,7 @@ fn setup(mut commands: Commands, mut meshes: ResMut>) { }); // Spawn 3 cubes that use the custom draw command - // Each cube is at a different depth to show that the sorting works correctly + // Each cube is at a different depth to show the sorting commands.spawn(CustomMaterialMeshBundle { mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), @@ -94,7 +99,10 @@ impl Plugin for CustomRenderPhasePlugin { fn build(&self, app: &mut App) { // The UniformComponentPlugin will set up the necessary system to // automatically extract and prepare the given uniform component - app.add_plugins(UniformComponentPlugin::::default()); + app.add_plugins(( + ExtractComponentPlugin::::default(), + UniformComponentPlugin::::default(), + )); // We need to get the render app from the main app let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { @@ -116,17 +124,19 @@ impl Plugin for CustomRenderPhasePlugin { // that data and you can only do it in the ExtractSchedule. // Some common extract scenarios have plugins that will do this automatically. // For the purpose of the example we will do it manually. - .add_systems( - ExtractSchedule, - (extract_render_phase, extract_custom_material_uniform), - ) + .add_systems(ExtractSchedule, extract_render_phase) .add_systems( Render, ( + // TODO + queue_mesh_custom_phase.in_set(RenderSet::Queue), // This will automatically sort all items in the phase based on the [`PhaseItem::sort_key()`] sort_phase_system::.in_set(RenderSet::PhaseSort), - queue_mesh_custom_phase.in_set(RenderSet::Queue), - queue_custom_bind_group.in_set(RenderSet::Queue), + // TODO + batch_and_prepare_render_phase:: + .in_set(RenderSet::PrepareResources), + // TODO + prepare_custom_bind_group.in_set(RenderSet::PrepareBindGroups), ), ); @@ -144,7 +154,7 @@ impl Plugin for CustomRenderPhasePlugin { // This will schedule the custom node to run after the main opaque pass .add_render_graph_edge( CORE_3D, - core_3d::graph::node::MAIN_OPAQUE_PASS, + core_3d::graph::node::START_MAIN_PASS, CustomNode::NAME, ); } @@ -164,11 +174,10 @@ impl Plugin for CustomRenderPhasePlugin { #[derive(Default)] pub struct CustomNode; impl CustomNode { - const NAME: &str = "custom_node"; + const NAME: &'static str = "custom_node"; } - impl ViewNode for CustomNode { - type ViewQuery = ( + type ViewData = ( &'static RenderPhase, &'static ViewTarget, &'static ViewDepthTexture, @@ -177,34 +186,22 @@ impl ViewNode for CustomNode { &self, graph: &mut RenderGraphContext, render_context: &mut RenderContext, - (custom_phase, target, depth): QueryItem, + (custom_phase, target, depth): QueryItem, world: &World, ) -> Result<(), NodeRunError> { - let view_entity = graph.view_entity(); - - if custom_phase.items.is_empty() { - return Ok(()); - } - - // The render pass that will be used for by the draw command + // The render pass that will be used by the draw commands of the render phase. + // A render phase can only use a single render pass. + // If you need multiple passes you'll need a separate phase. let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { - label: Some("custom_pass"), - color_attachments: &[Some(target.get_color_attachment(Operations { - load: LoadOp::Load, - store: true, - }))], - depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { - view: &depth.view, - depth_ops: Some(Operations { - load: LoadOp::Load, - store: true, - }), - stencil_ops: None, - }), + label: Some("main_opaque_pass_3d"), + color_attachments: &[Some(target.get_color_attachment())], + depth_stencil_attachment: Some(depth.get_attachment(StoreOp::Store)), + timestamp_writes: None, + occlusion_query_set: None, }); // This will automatically call the draw command on each items in the render phase - custom_phase.render(&mut render_pass, world, view_entity); + custom_phase.render(&mut render_pass, world, graph.view_entity()); Ok(()) } @@ -215,17 +212,17 @@ impl ViewNode for CustomNode { pub struct CustomMaterialBindGroup(BindGroup); // TODO document render command -pub struct SetMaterialBindGroup; -impl RenderCommand

for SetMaterialBindGroup { +pub struct SetCustomMaterialBindGroup; +impl RenderCommand

for SetCustomMaterialBindGroup { type Param = SRes; - type ViewWorldQuery = (); - type ItemWorldQuery = Read>; + type ViewData = (); + type ItemData = Read>; #[inline] fn render<'w>( _item: &P, _view: (), - mesh_index: ROQueryItem<'w, Self::ItemWorldQuery>, + mesh_index: ROQueryItem<'w, Self::ItemData>, bind_group: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { @@ -241,49 +238,70 @@ type DrawCustom = ( SetItemPipeline, // Sets the mesh view bind group at index 0 SetMeshViewBindGroup<0>, - // Sets the custom material bind group at index 1 - SetMaterialBindGroup<1>, - // Sets the mesh bind group at index 2 - SetMeshBindGroup<2>, + // Sets the mesh bind group at index 1 + SetMeshBindGroup<1>, + // Sets the custom material bind group at index 2 + SetCustomMaterialBindGroup<2>, // Draws the mesh with the specified pipeline and bind groups DrawMesh, ); // TODO explain what a PhaseItem is +// TODO explain all the batch stuff pub struct CustomPhaseItem { pub distance: f32, - pub pipeline: CachedRenderPipelineId, + pub pipeline_id: CachedRenderPipelineId, pub entity: Entity, pub draw_function: DrawFunctionId, + pub batch_range: Range, + pub dynamic_offset: Option, } impl PhaseItem for CustomPhaseItem { type SortKey = FloatOrd; + #[inline] fn entity(&self) -> Entity { self.entity } + #[inline] fn sort_key(&self) -> Self::SortKey { FloatOrd(self.distance) } + #[inline] fn draw_function(&self) -> DrawFunctionId { self.draw_function } + + #[inline] + fn batch_range(&self) -> &Range { + &self.batch_range + } + + #[inline] + fn batch_range_mut(&mut self) -> &mut Range { + &mut self.batch_range + } + + #[inline] + fn dynamic_offset(&self) -> Option { + self.dynamic_offset + } + + #[inline] + fn dynamic_offset_mut(&mut self) -> &mut Option { + &mut self.dynamic_offset + } } impl CachedRenderPipelinePhaseItem for CustomPhaseItem { fn cached_pipeline(&self) -> CachedRenderPipelineId { - self.pipeline + self.pipeline_id } } -#[derive(Component, ShaderType, Clone, Copy)] -pub struct CustomMaterialUniform { - base_color: Color, -} - #[derive(Resource)] pub struct CustomPipeline { mesh_pipeline: MeshPipeline, @@ -295,20 +313,13 @@ impl FromWorld for CustomPipeline { fn from_world(world: &mut World) -> Self { let render_device = world.resource::(); - let bind_group_layout = - render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { - label: Some("custom_bind_group_layout"), - entries: &[BindGroupLayoutEntry { - binding: 0, - ty: BindingType::Buffer { - ty: BufferBindingType::Uniform, - has_dynamic_offset: true, - min_binding_size: Some(CustomMaterialUniform::min_size()), - }, - visibility: ShaderStages::FRAGMENT, - count: None, - }], - }); + let bind_group_layout = render_device.create_bind_group_layout( + "custom_bind_group_layout", + &BindGroupLayoutEntries::single( + ShaderStages::FRAGMENT, + uniform_buffer::(true), + ), + ); let shader = world .resource::() @@ -329,18 +340,20 @@ impl SpecializedMeshPipeline for CustomPipeline { fn specialize( &self, key: Self::Key, - layout: &bevy_internal::render::mesh::MeshVertexBufferLayout, + layout: &MeshVertexBufferLayout, ) -> Result { let mut desc = self.mesh_pipeline.specialize(key, layout)?; - desc.label = Some("mesh_custom_pipeline".into()); + desc.label = Some("custom_mesh_pipeline".into()); // The layout of the pipeline // It's important that it matches the order specified in the `DrawCustom` desc.layout = vec![ - self.mesh_pipeline.view_layout.clone(), - self.bind_group_layout.clone(), + self.mesh_pipeline + .get_view_layout(MeshPipelineViewLayoutKey::from(key)) + .clone(), self.mesh_pipeline.mesh_layouts.model_only.clone(), + self.bind_group_layout.clone(), ]; desc.vertex.shader = self.shader.clone(); desc.fragment.as_mut().unwrap().shader = self.shader.clone(); @@ -355,6 +368,7 @@ fn extract_render_phase( cameras_3d: Extract>>, ) { for (entity, camera) in &cameras_3d { + // This isn't required for this example but it's good practice to only extract the phase on active camera if camera.is_active { commands .get_or_spawn(entity) @@ -363,54 +377,45 @@ fn extract_render_phase( } } -/// Create the [`CustomMaterialUniform`] for each mesh with an Outline component -fn extract_custom_material_uniform( - mut commands: Commands, - custom_materials: Extract>, -) { - for (entity, custom_material) in &custom_materials { - commands.get_or_spawn(entity).insert(CustomMaterialUniform { - base_color: custom_material.base_color, - }); - } -} - /// Queues the creation of the bind group -fn queue_custom_bind_group( +fn prepare_custom_bind_group( mut commands: Commands, custom_pipeline: Res, render_device: Res, - custom_material_uniforms: Res>, + custom_material_uniforms: Res>, ) { - let Some(uniform) = custom_material_uniforms.binding() else { - return; - }; - let bind_group = render_device.create_bind_group(&BindGroupDescriptor { - label: Some("custom_material_bind_group"), - layout: &custom_pipeline.bind_group_layout, - entries: &[BindGroupEntry { - binding: 0, - resource: uniform.clone(), - }], - }); - commands.insert_resource(CustomMaterialBindGroup(bind_group)); + if let Some(uniform) = custom_material_uniforms.binding() { + let bind_group = render_device.create_bind_group( + "custom_material_bind_group", + &custom_pipeline.bind_group_layout, + &BindGroupEntries::single(uniform), + ); + commands.insert_resource(CustomMaterialBindGroup(bind_group)); + } } -#[derive(Component, Clone, Copy, Default)] +// To keep this example simple this struct is also a valid ShaderType. +// If you want to make the main world struct more flexible you can use a struct that isn't a valid ShaderType +// and convert it to a valid ShaderType in an extract system. +#[derive(Component, Clone, Copy, Default, ShaderType, ExtractComponent)] pub struct CustomMaterial { pub base_color: Color, } // Bundle used to spanw a mesh rendered with the `CustomMaterial` -// It's essentially the `MaterialMeshBundle` but with the `CustomMaterial` instead of and `Handle` +// It's essentially the `MaterialMeshBundle` but with the `CustomMaterial` instead of `Handle` #[derive(Bundle, Clone, Default)] struct CustomMaterialMeshBundle { - mesh: Handle, + pub mesh: Handle, material: CustomMaterial, - transform: Transform, - global_transform: GlobalTransform, - visibility: Visibility, - computed_visibility: ComputedVisibility, + pub transform: Transform, + pub global_transform: GlobalTransform, + /// User indication of whether an entity is visible + pub visibility: Visibility, + /// Inherited visibility of an entity. + pub inherited_visibility: InheritedVisibility, + /// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering + pub view_visibility: ViewVisibility, } #[allow(clippy::too_many_arguments)] @@ -420,7 +425,8 @@ fn queue_mesh_custom_phase( mut pipelines: ResMut>, pipeline_cache: Res, render_meshes: Res>, - meshes: Query<(Entity, &Handle, &MeshUniform), With>, + mut render_mesh_instances: ResMut, + filter_meshes: Query<(), With>, mut views: Query<( &ExtractedView, &mut VisibleEntities, @@ -431,33 +437,46 @@ fn queue_mesh_custom_phase( let draw_function = draw_functions.read().id::(); for (view, visible_entities, mut custom_phase) in views.iter_mut() { - let view_matrix = view.transform.compute_matrix(); - let inv_view_row_2 = view_matrix.inverse().row(2); - let view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()); - for visible_entity in visible_entities.entities.iter().copied() { - let Ok((entity, mesh_handle, mesh_uniform)) = meshes.get(visible_entity) else { + let rangefinder = view.rangefinder3d(); + for visible_entity in &visible_entities.entities { + // We only want meshes with our custom material to be rendered so we need to filter the other visible meshes + if filter_meshes.get(*visible_entity).is_err() { + continue; + } + + let Some(mesh_instance) = render_mesh_instances.get_mut(visible_entity) else { continue; }; - let Some(mesh) = render_meshes.get(mesh_handle) else { + let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else { continue; }; let key = MeshPipelineKey::from_primitive_topology(mesh.primitive_topology) | view_key; - let Ok(pipeline) = pipelines.specialize(&pipeline_cache, &pipeline, key, &mesh.layout) else { - continue; - }; + let pipeline_id = + match pipelines.specialize(&pipeline_cache, &pipeline, key, &mesh.layout) { + Ok(id) => id, + Err(err) => { + error!("{}", err); + continue; + } + }; + + // Computes the distance to the view + // This will be used to sort the draw command + let distance = + rangefinder.distance_translation(&mesh_instance.transforms.transform.translation); // Add the draw command for the mesh to the custom phase custom_phase.add(CustomPhaseItem { - entity, - pipeline, + entity: *visible_entity, + pipeline_id, draw_function, - // Computes the distance to the view - // This will be used to sort the draw command - distance: inv_view_row_2.dot(mesh_uniform.transform.col(3)), + distance, + batch_range: 0..1, + dynamic_offset: None, }); } }