From 08969a24b8d97e99b068ba739ecb25f007ca221a Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Thu, 23 Sep 2021 06:16:11 +0000 Subject: [PATCH] Modular Rendering (#2831) This changes how render logic is composed to make it much more modular. Previously, all extraction logic was centralized for a given "type" of rendered thing. For example, we extracted meshes into a vector of ExtractedMesh, which contained the mesh and material asset handles, the transform, etc. We looked up bindings for "drawn things" using their index in the `Vec`. This worked fine for built in rendering, but made it hard to reuse logic for "custom" rendering. It also prevented us from reusing things like "extracted transforms" across contexts. To make rendering more modular, I made a number of changes: * Entities now drive rendering: * We extract "render components" from "app components" and store them _on_ entities. No more centralized uber lists! We now have true "ECS-driven rendering" * To make this perform well, I implemented #2673 in upstream Bevy for fast batch insertions into specific entities. This was merged into the `pipelined-rendering` branch here: #2815 * Reworked the `Draw` abstraction: * Generic `PhaseItems`: each draw phase can define its own type of "rendered thing", which can define its own "sort key" * Ported the 2d, 3d, and shadow phases to the new PhaseItem impl (currently Transparent2d, Transparent3d, and Shadow PhaseItems) * `Draw` trait and and `DrawFunctions` are now generic on PhaseItem * Modular / Ergonomic `DrawFunctions` via `RenderCommands` * RenderCommand is a trait that runs an ECS query and produces one or more RenderPass calls. Types implementing this trait can be composed to create a final DrawFunction. For example the DrawPbr DrawFunction is created from the following DrawCommand tuple. Const generics are used to set specific bind group locations: ```rust pub type DrawPbr = ( SetPbrPipeline, SetMeshViewBindGroup<0>, SetStandardMaterialBindGroup<1>, SetTransformBindGroup<2>, DrawMesh, ); ``` * The new `custom_shader_pipelined` example illustrates how the commands above can be reused to create a custom draw function: ```rust type DrawCustom = ( SetCustomMaterialPipeline, SetMeshViewBindGroup<0>, SetTransformBindGroup<2>, DrawMesh, ); ``` * ExtractComponentPlugin and UniformComponentPlugin: * Simple, standardized ways to easily extract individual components and write them to GPU buffers * Ported PBR and Sprite rendering to the new primitives above. * Removed staging buffer from UniformVec in favor of direct Queue usage * Makes UniformVec much easier to use and more ergonomic. Completely removes the need for custom render graph nodes in these contexts (see the PbrNode and view Node removals and the much simpler call patterns in the relevant Prepare systems). * Added a many_cubes_pipelined example to benchmark baseline 3d rendering performance and ensure there were no major regressions during this port. Avoiding regressions was challenging given that the old approach of extracting into centralized vectors is basically the "optimal" approach. However thanks to a various ECS optimizations and render logic rephrasing, we pretty much break even on this benchmark! * Lifetimeless SystemParams: this will be a bit divisive, but as we continue to embrace "trait driven systems" (ex: ExtractComponentPlugin, UniformComponentPlugin, DrawCommand), the ergonomics of `(Query<'static, 'static, (&'static A, &'static B, &'static)>, Res<'static, C>)` were getting very hard to bear. As a compromise, I added "static type aliases" for the relevant SystemParams. The previous example can now be expressed like this: `(SQuery<(Read, Read)>, SRes)`. If anyone has better ideas / conflicting opinions, please let me know! * RunSystem trait: a way to define Systems via a trait with a SystemParam associated type. This is used to implement the various plugins mentioned above. I also added SystemParamItem and QueryItem type aliases to make "trait stye" ecs interactions nicer on the eyes (and fingers). * RenderAsset retrying: ensures that render assets are only created when they are "ready" and allows us to create bind groups directly inside render assets (which significantly simplified the StandardMaterial code). I think ultimately we should swap this out on "asset dependency" events to wait for dependencies to load, but this will require significant asset system changes. * Updated some built in shaders to account for missing MeshUniform fields --- Cargo.toml | 9 + assets/shaders/custom.wgsl | 46 ++ crates/bevy_asset/src/handle.rs | 2 + crates/bevy_ecs/src/lib.rs | 2 + crates/bevy_ecs/src/query/fetch.rs | 2 + crates/bevy_ecs/src/system/function_system.rs | 74 +- crates/bevy_ecs/src/system/system_param.rs | 11 + examples/3d/many_cubes_pipelined.rs | 50 ++ examples/README.md | 2 + examples/shader/custom_shader_pipelined.rs | 297 ++++++++ examples/tools/bevymark_pipelined.rs | 20 +- pipelined/bevy_core_pipeline/Cargo.toml | 2 + pipelined/bevy_core_pipeline/src/lib.rs | 75 +- .../bevy_core_pipeline/src/main_pass_2d.rs | 20 +- .../bevy_core_pipeline/src/main_pass_3d.rs | 20 +- pipelined/bevy_pbr2/src/lib.rs | 32 +- pipelined/bevy_pbr2/src/material.rs | 126 ++- pipelined/bevy_pbr2/src/render/depth.wgsl | 4 + pipelined/bevy_pbr2/src/render/light.rs | 154 ++-- pipelined/bevy_pbr2/src/render/mod.rs | 715 +++++++----------- pipelined/bevy_pbr2/src/render/pbr.wgsl | 1 - pipelined/bevy_render2/src/lib.rs | 13 +- pipelined/bevy_render2/src/mesh/mesh/mod.rs | 15 +- pipelined/bevy_render2/src/render_asset.rs | 94 ++- .../bevy_render2/src/render_component.rs | 162 ++++ .../bevy_render2/src/render_phase/draw.rs | 148 +++- .../bevy_render2/src/render_phase/mod.rs | 30 +- .../bevy_render2/src/render_resource/mod.rs | 11 +- .../src/render_resource/uniform_vec.rs | 82 +- pipelined/bevy_render2/src/texture/image.rs | 13 +- pipelined/bevy_render2/src/view/mod.rs | 51 +- pipelined/bevy_sprite2/src/lib.rs | 14 +- pipelined/bevy_sprite2/src/render/mod.rs | 254 +++---- pipelined/bevy_sprite2/src/render/sprite.frag | 11 - pipelined/bevy_sprite2/src/render/sprite.vert | 17 - pipelined/bevy_sprite2/src/render/sprite.wgsl | 1 - 36 files changed, 1653 insertions(+), 927 deletions(-) create mode 100644 assets/shaders/custom.wgsl create mode 100644 examples/3d/many_cubes_pipelined.rs create mode 100644 examples/shader/custom_shader_pipelined.rs create mode 100644 pipelined/bevy_render2/src/render_component.rs delete mode 100644 pipelined/bevy_sprite2/src/render/sprite.frag delete mode 100644 pipelined/bevy_sprite2/src/render/sprite.vert diff --git a/Cargo.toml b/Cargo.toml index 6ecad24599612..6bb9d5c9407e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -108,6 +108,7 @@ ron = "0.6.2" serde = { version = "1", features = ["derive"] } # Needed to poll Task examples futures-lite = "1.11.3" +crevice = {path = "crates/crevice"} [[example]] name = "hello_world" @@ -159,6 +160,10 @@ path = "examples/3d/3d_scene.rs" name = "3d_scene_pipelined" path = "examples/3d/3d_scene_pipelined.rs" +[[example]] +name = "many_cubes_pipelined" +path = "examples/3d/many_cubes_pipelined.rs" + [[example]] name = "cornell_box_pipelined" path = "examples/3d/cornell_box_pipelined.rs" @@ -462,6 +467,10 @@ path = "examples/shader/shader_custom_material.rs" name = "shader_defs" path = "examples/shader/shader_defs.rs" +[[example]] +name = "custom_shader_pipelined" +path = "examples/shader/custom_shader_pipelined.rs" + # Tools [[example]] name = "bevymark" diff --git a/assets/shaders/custom.wgsl b/assets/shaders/custom.wgsl new file mode 100644 index 0000000000000..100c4b62b0896 --- /dev/null +++ b/assets/shaders/custom.wgsl @@ -0,0 +1,46 @@ +[[block]] +struct View { + view_proj: mat4x4; + projection: mat4x4; + world_position: vec3; +}; +[[group(0), binding(0)]] +var view: View; + +[[block]] +struct Mesh { + transform: mat4x4; +}; +[[group(2), binding(0)]] +var mesh: Mesh; + +struct Vertex { + [[location(0)]] position: vec3; + [[location(1)]] normal: vec3; + [[location(2)]] uv: vec2; +}; + +struct VertexOutput { + [[builtin(position)]] clip_position: vec4; +}; + +[[stage(vertex)]] +fn vertex(vertex: Vertex) -> VertexOutput { + let world_position = mesh.transform * vec4(vertex.position, 1.0); + + var out: VertexOutput; + out.clip_position = view.view_proj * world_position; + return out; +} + +[[block]] +struct CustomMaterial { + color: vec4; +}; +[[group(1), binding(0)]] +var material: CustomMaterial; + +[[stage(fragment)]] +fn fragment() -> [[location(0)]] vec4 { + return material.color; +} diff --git a/crates/bevy_asset/src/handle.rs b/crates/bevy_asset/src/handle.rs index ae2bb45c0837c..d49121c487e16 100644 --- a/crates/bevy_asset/src/handle.rs +++ b/crates/bevy_asset/src/handle.rs @@ -96,6 +96,7 @@ impl Handle { } } + #[inline] pub fn weak(id: HandleId) -> Self { Self { id, @@ -129,6 +130,7 @@ impl Handle { self.handle_type = HandleType::Strong(sender); } + #[inline] pub fn clone_weak(&self) -> Self { Handle::weak(self.id) } diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index c51925b710569..6e07e8bea3a29 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -36,6 +36,8 @@ pub mod prelude { }; } +pub use bevy_ecs_macros::all_tuples; + #[cfg(test)] mod tests { use crate as bevy_ecs; diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 9e20c208761e7..e0725568d02a8 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -45,6 +45,8 @@ pub trait WorldQuery { type State: FetchState; } +pub type QueryItem<'w, 's, Q> = <::Fetch as Fetch<'w, 's>>::Item; + pub trait Fetch<'world, 'state>: Sized { type Item; type State: FetchState; diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index c37653c078567..85109090a224a 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -4,7 +4,7 @@ use crate::{ query::{Access, FilteredAccessSet}, system::{ check_system_change_tick, ReadOnlySystemParamFetch, System, SystemId, SystemParam, - SystemParamFetch, SystemParamState, + SystemParamFetch, SystemParamItem, SystemParamState, }, world::{World, WorldId}, }; @@ -48,6 +48,11 @@ impl SystemMeta { pub fn set_non_send(&mut self) { self.is_send = false; } + + #[inline] + pub(crate) fn check_change_tick(&mut self, change_tick: u32) { + check_system_change_tick(&mut self.last_change_tick, change_tick, self.name.as_ref()); + } } // TODO: Actually use this in FunctionSystem. We should probably only do this once Systems are constructed using a World reference @@ -123,6 +128,10 @@ impl SystemState { self.world_id == world.id() } + pub(crate) fn new_archetype(&mut self, archetype: &Archetype) { + self.param_state.new_archetype(archetype, &mut self.meta); + } + fn validate_world_and_update_archetypes(&mut self, world: &World) { assert!(self.matches_world(world), "Encountered a mismatched World. A SystemState cannot be used with Worlds other than the one it was created with."); let archetypes = world.archetypes(); @@ -161,6 +170,69 @@ impl SystemState { } } +pub trait RunSystem: Send + Sync + 'static { + type Param: SystemParam; + fn run(param: SystemParamItem); + fn system(world: &mut World) -> ParamSystem { + ParamSystem { + run: Self::run, + state: SystemState::new(world), + } + } +} + +pub struct ParamSystem { + state: SystemState

, + run: fn(SystemParamItem

), +} + +impl System for ParamSystem

{ + type In = (); + + type Out = (); + + fn name(&self) -> Cow<'static, str> { + self.state.meta().name.clone() + } + + fn id(&self) -> SystemId { + self.state.meta().id + } + + fn new_archetype(&mut self, archetype: &Archetype) { + self.state.new_archetype(archetype); + } + + fn component_access(&self) -> &Access { + self.state.meta().component_access_set.combined_access() + } + + fn archetype_component_access(&self) -> &Access { + &self.state.meta().archetype_component_access + } + + fn is_send(&self) -> bool { + self.state.meta().is_send() + } + + unsafe fn run_unsafe(&mut self, _input: Self::In, world: &World) -> Self::Out { + let param = self.state.get_unchecked_manual(world); + (self.run)(param); + } + + fn apply_buffers(&mut self, world: &mut World) { + self.state.apply(world); + } + + fn initialize(&mut self, _world: &mut World) { + // already initialized by nature of the SystemState being constructed + } + + fn check_change_tick(&mut self, change_tick: u32) { + self.state.meta.check_change_tick(change_tick); + } +} + /// Conversion trait to turn something into a [`System`]. /// /// Use this to get a system from a function. Also note that every system implements this trait as diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 6f3e731ad3f22..0274793b63223 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -47,6 +47,8 @@ pub trait SystemParam: Sized { type Fetch: for<'w, 's> SystemParamFetch<'w, 's>; } +pub type SystemParamItem<'w, 's, P> = <

{ + pub fn add>(&mut self, draw_function: T) -> DrawFunctionId { + self.add_with::(draw_function) + } + + pub fn add_with>(&mut self, draw_function: D) -> DrawFunctionId { self.draw_functions.push(Box::new(draw_function)); let id = DrawFunctionId(self.draw_functions.len() - 1); - self.indices.insert(TypeId::of::(), id); + self.indices.insert(TypeId::of::(), id); id } - pub fn get_mut(&mut self, id: DrawFunctionId) -> Option<&mut dyn Draw> { + pub fn get_mut(&mut self, id: DrawFunctionId) -> Option<&mut dyn Draw

> { self.draw_functions.get_mut(id.0).map(|f| &mut **f) } - pub fn get_id(&self) -> Option { - self.indices.get(&TypeId::of::()).copied() + pub fn get_id(&self) -> Option { + self.indices.get(&TypeId::of::()).copied() } } -#[derive(Default)] -pub struct DrawFunctions { - internal: RwLock, +pub struct DrawFunctions { + internal: RwLock>, } -impl DrawFunctions { - pub fn read(&self) -> RwLockReadGuard<'_, DrawFunctionsInternal> { +impl Default for DrawFunctions

{ + fn default() -> Self { + Self { + internal: RwLock::new(DrawFunctionsInternal { + draw_functions: Vec::new(), + indices: HashMap::default(), + }), + } + } +} + +impl DrawFunctions

{ + pub fn read(&self) -> RwLockReadGuard<'_, DrawFunctionsInternal

> { self.internal.read() } - pub fn write(&self) -> RwLockWriteGuard<'_, DrawFunctionsInternal> { + pub fn write(&self) -> RwLockWriteGuard<'_, DrawFunctionsInternal

> { self.internal.write() } } +pub trait RenderCommand { + type Param: SystemParam; + fn render<'w>( + view: Entity, + item: &P, + param: SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, + ); +} + +macro_rules! render_command_tuple_impl { + ($($name: ident),*) => { + impl),*> RenderCommand

for ($($name,)*) { + type Param = ($($name::Param,)*); + + #[allow(non_snake_case)] + fn render<'w>( + _view: Entity, + _item: &P, + ($($name,)*): SystemParamItem<'w, '_, Self::Param>, + _pass: &mut TrackedRenderPass<'w>, + ) { + $($name::render(_view, _item, $name, _pass);)* + } + } + }; +} + +all_tuples!(render_command_tuple_impl, 0, 15, C); + +pub struct RenderCommandState> { + state: SystemState, +} + +impl> RenderCommandState { + pub fn new(world: &mut World) -> Self { + Self { + state: SystemState::new(world), + } + } +} + +impl + Send + Sync + 'static> Draw

for RenderCommandState +where + ::Fetch: ReadOnlySystemParamFetch, +{ + fn draw<'w>( + &mut self, + world: &'w World, + pass: &mut TrackedRenderPass<'w>, + view: Entity, + item: &P, + ) { + let param = self.state.get(world); + C::render(view, item, param, pass); + } +} + +pub trait AddRenderCommand { + fn add_render_command + Send + Sync + 'static>( + &mut self, + ) -> &mut Self + where + ::Fetch: ReadOnlySystemParamFetch; +} + +impl AddRenderCommand for App { + fn add_render_command + Send + Sync + 'static>( + &mut self, + ) -> &mut Self + where + ::Fetch: ReadOnlySystemParamFetch, + { + let draw_function = RenderCommandState::::new(&mut self.world); + let draw_functions = self.world.get_resource::>().unwrap(); + draw_functions.write().add_with::(draw_function); + self + } +} diff --git a/pipelined/bevy_render2/src/render_phase/mod.rs b/pipelined/bevy_render2/src/render_phase/mod.rs index 082d98d108554..ba2a1fb4080fb 100644 --- a/pipelined/bevy_render2/src/render_phase/mod.rs +++ b/pipelined/bevy_render2/src/render_phase/mod.rs @@ -5,41 +5,29 @@ pub use draw::*; pub use draw_state::*; use bevy_ecs::prelude::Query; -use std::marker::PhantomData; -// TODO: make this configurable per phase? -pub struct Drawable { - pub draw_function: DrawFunctionId, - pub draw_key: usize, - pub sort_key: usize, +pub struct RenderPhase { + pub items: Vec, } -pub struct RenderPhase { - pub drawn_things: Vec, - marker: PhantomData T>, -} - -impl Default for RenderPhase { +impl Default for RenderPhase { fn default() -> Self { - Self { - drawn_things: Vec::new(), - marker: PhantomData, - } + Self { items: Vec::new() } } } -impl RenderPhase { +impl RenderPhase { #[inline] - pub fn add(&mut self, drawable: Drawable) { - self.drawn_things.push(drawable); + pub fn add(&mut self, item: I) { + self.items.push(item); } pub fn sort(&mut self) { - self.drawn_things.sort_by_key(|d| d.sort_key); + self.items.sort_by_key(|d| d.sort_key()); } } -pub fn sort_phase_system(mut render_phases: Query<&mut RenderPhase>) { +pub fn sort_phase_system(mut render_phases: Query<&mut RenderPhase>) { for mut phase in render_phases.iter_mut() { phase.sort(); } diff --git a/pipelined/bevy_render2/src/render_resource/mod.rs b/pipelined/bevy_render2/src/render_resource/mod.rs index 07dc4db88b43e..9de9715f7035d 100644 --- a/pipelined/bevy_render2/src/render_resource/mod.rs +++ b/pipelined/bevy_render2/src/render_resource/mod.rs @@ -15,15 +15,14 @@ pub use texture::*; pub use uniform_vec::*; // TODO: decide where re-exports should go -pub use wgpu::util::BufferInitDescriptor; pub use wgpu::{ - AddressMode, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, - BindGroupLayoutEntry, BindingResource, BindingType, BlendComponent, BlendFactor, - BlendOperation, BlendState, BufferAddress, BufferBindingType, BufferSize, BufferUsage, - ColorTargetState, ColorWrite, CompareFunction, ComputePassDescriptor, + util::BufferInitDescriptor, AddressMode, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, + BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BindingType, BlendComponent, + BlendFactor, BlendOperation, BlendState, BufferAddress, BufferBindingType, BufferSize, + BufferUsage, ColorTargetState, ColorWrite, CompareFunction, ComputePassDescriptor, ComputePipelineDescriptor, DepthBiasState, DepthStencilState, Extent3d, Face, FilterMode, FragmentState, FrontFace, IndexFormat, InputStepMode, LoadOp, MultisampleState, Operations, - PipelineLayoutDescriptor, PolygonMode, PrimitiveState, PrimitiveTopology, + PipelineLayout, PipelineLayoutDescriptor, PolygonMode, PrimitiveState, PrimitiveTopology, RenderPassColorAttachment, RenderPassDepthStencilAttachment, RenderPassDescriptor, RenderPipelineDescriptor, SamplerDescriptor, ShaderFlags, ShaderModule, ShaderModuleDescriptor, ShaderSource, ShaderStage, StencilFaceState, StencilOperation, StencilState, diff --git a/pipelined/bevy_render2/src/render_resource/uniform_vec.rs b/pipelined/bevy_render2/src/render_resource/uniform_vec.rs index 6a380a14ad3cd..1d6658514673f 100644 --- a/pipelined/bevy_render2/src/render_resource/uniform_vec.rs +++ b/pipelined/bevy_render2/src/render_resource/uniform_vec.rs @@ -1,11 +1,14 @@ -use crate::{render_resource::Buffer, renderer::RenderDevice}; +use crate::{ + render_resource::Buffer, + renderer::{RenderDevice, RenderQueue}, +}; use crevice::std140::{self, AsStd140, DynamicUniform, Std140}; -use std::{num::NonZeroU64, ops::DerefMut}; -use wgpu::{BindingResource, BufferBinding, BufferDescriptor, BufferUsage, CommandEncoder}; +use std::num::NonZeroU64; +use wgpu::{BindingResource, BufferBinding, BufferDescriptor, BufferUsage}; pub struct UniformVec { values: Vec, - staging_buffer: Option, + scratch: Vec, uniform_buffer: Option, capacity: usize, item_size: usize, @@ -15,7 +18,7 @@ impl Default for UniformVec { fn default() -> Self { Self { values: Vec::new(), - staging_buffer: None, + scratch: Vec::new(), uniform_buffer: None, capacity: 0, item_size: (T::std140_size_static() + ::Std140Type::ALIGNMENT - 1) @@ -25,23 +28,18 @@ impl Default for UniformVec { } impl UniformVec { - #[inline] - pub fn staging_buffer(&self) -> Option<&Buffer> { - self.staging_buffer.as_ref() - } - #[inline] pub fn uniform_buffer(&self) -> Option<&Buffer> { self.uniform_buffer.as_ref() } #[inline] - pub fn binding(&self) -> BindingResource { - BindingResource::Buffer(BufferBinding { - buffer: self.uniform_buffer().expect("uniform buffer should exist"), + pub fn binding(&self) -> Option { + Some(BindingResource::Buffer(BufferBinding { + buffer: self.uniform_buffer()?, offset: 0, size: Some(NonZeroU64::new(self.item_size as u64).unwrap()), - }) + })) } #[inline] @@ -75,16 +73,11 @@ impl UniformVec { pub fn reserve(&mut self, capacity: usize, device: &RenderDevice) { if capacity > self.capacity { self.capacity = capacity; - let size = (self.item_size * capacity) as wgpu::BufferAddress; - self.staging_buffer = Some(device.create_buffer(&BufferDescriptor { - label: None, - size, - usage: BufferUsage::COPY_SRC | BufferUsage::MAP_WRITE, - mapped_at_creation: false, - })); + let size = self.item_size * capacity; + self.scratch.resize(size, 0); self.uniform_buffer = Some(device.create_buffer(&BufferDescriptor { label: None, - size, + size: size as wgpu::BufferAddress, usage: BufferUsage::COPY_DST | BufferUsage::UNIFORM, mapped_at_creation: false, })); @@ -96,29 +89,12 @@ impl UniformVec { self.reserve(capacity, device); } - pub fn write_to_staging_buffer(&self, device: &RenderDevice) { - if let Some(staging_buffer) = &self.staging_buffer { - let slice = staging_buffer.slice(..); - device.map_buffer(&slice, wgpu::MapMode::Write); - { - let mut data = slice.get_mapped_range_mut(); - let mut writer = std140::Writer::new(data.deref_mut()); - writer.write(self.values.as_slice()).unwrap(); - } - staging_buffer.unmap() - } - } - pub fn write_to_uniform_buffer(&self, command_encoder: &mut CommandEncoder) { - if let (Some(staging_buffer), Some(uniform_buffer)) = - (&self.staging_buffer, &self.uniform_buffer) - { - command_encoder.copy_buffer_to_buffer( - staging_buffer, - 0, - uniform_buffer, - 0, - (self.values.len() * self.item_size) as u64, - ); + pub fn write_buffer(&mut self, queue: &RenderQueue) { + if let Some(uniform_buffer) = &self.uniform_buffer { + let range = 0..self.item_size * self.values.len(); + let mut writer = std140::Writer::new(&mut self.scratch[range.clone()]); + writer.write(self.values.as_slice()).unwrap(); + queue.write_buffer(uniform_buffer, 0, &self.scratch[range]); } } @@ -140,18 +116,13 @@ impl Default for DynamicUniformVec { } impl DynamicUniformVec { - #[inline] - pub fn staging_buffer(&self) -> Option<&Buffer> { - self.uniform_vec.staging_buffer() - } - #[inline] pub fn uniform_buffer(&self) -> Option<&Buffer> { self.uniform_vec.uniform_buffer() } #[inline] - pub fn binding(&self) -> BindingResource { + pub fn binding(&self) -> Option { self.uniform_vec.binding() } @@ -186,13 +157,8 @@ impl DynamicUniformVec { } #[inline] - pub fn write_to_staging_buffer(&self, device: &RenderDevice) { - self.uniform_vec.write_to_staging_buffer(device); - } - - #[inline] - pub fn write_to_uniform_buffer(&self, command_encoder: &mut CommandEncoder) { - self.uniform_vec.write_to_uniform_buffer(command_encoder); + pub fn write_buffer(&mut self, queue: &RenderQueue) { + self.uniform_vec.write_buffer(queue); } #[inline] diff --git a/pipelined/bevy_render2/src/texture/image.rs b/pipelined/bevy_render2/src/texture/image.rs index 11eb06fcff0f0..1be9671c92806 100644 --- a/pipelined/bevy_render2/src/texture/image.rs +++ b/pipelined/bevy_render2/src/texture/image.rs @@ -1,10 +1,11 @@ use super::image_texture_conversion::image_to_texture; use crate::{ - render_asset::RenderAsset, + render_asset::{PrepareAssetError, RenderAsset}, render_resource::{Sampler, Texture, TextureView}, renderer::{RenderDevice, RenderQueue}, texture::BevyDefault, }; +use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem}; use bevy_reflect::TypeUuid; use thiserror::Error; use wgpu::{ @@ -349,6 +350,7 @@ pub struct GpuImage { impl RenderAsset for Image { type ExtractedAsset = Image; type PreparedAsset = GpuImage; + type Param = (SRes, SRes); fn extract_asset(&self) -> Self::ExtractedAsset { self.clone() @@ -356,9 +358,8 @@ impl RenderAsset for Image { fn prepare_asset( image: Self::ExtractedAsset, - render_device: &RenderDevice, - render_queue: &RenderQueue, - ) -> Self::PreparedAsset { + (render_device, render_queue): &mut SystemParamItem, + ) -> Result> { let texture = render_device.create_texture(&image.texture_descriptor); let sampler = render_device.create_sampler(&image.sampler_descriptor); @@ -388,10 +389,10 @@ impl RenderAsset for Image { ); let texture_view = texture.create_view(&TextureViewDescriptor::default()); - GpuImage { + Ok(GpuImage { texture, texture_view, sampler, - } + }) } } diff --git a/pipelined/bevy_render2/src/view/mod.rs b/pipelined/bevy_render2/src/view/mod.rs index d4d02cf35ef06..94d98bba85019 100644 --- a/pipelined/bevy_render2/src/view/mod.rs +++ b/pipelined/bevy_render2/src/view/mod.rs @@ -1,34 +1,25 @@ pub mod window; -use bevy_transform::components::GlobalTransform; pub use window::*; use crate::{ - render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext}, render_resource::DynamicUniformVec, - renderer::{RenderContext, RenderDevice}, + renderer::{RenderDevice, RenderQueue}, RenderApp, RenderStage, }; use bevy_app::{App, Plugin}; use bevy_ecs::prelude::*; use bevy_math::{Mat4, Vec3}; +use bevy_transform::components::GlobalTransform; use crevice::std140::AsStd140; pub struct ViewPlugin; -impl ViewPlugin { - pub const VIEW_NODE: &'static str = "view"; -} - impl Plugin for ViewPlugin { fn build(&self, app: &mut App) { - let render_app = app.sub_app(RenderApp); - render_app - .init_resource::() + app.sub_app(RenderApp) + .init_resource::() .add_system_to_stage(RenderStage::Prepare, prepare_views); - - let mut graph = render_app.world.get_resource_mut::().unwrap(); - graph.add_node(ViewPlugin::VIEW_NODE, ViewNode); } } @@ -47,7 +38,7 @@ pub struct ViewUniform { } #[derive(Default)] -pub struct ViewMeta { +pub struct ViewUniforms { pub uniforms: DynamicUniformVec, } @@ -57,17 +48,18 @@ pub struct ViewUniformOffset { fn prepare_views( mut commands: Commands, - render_resources: Res, - mut view_meta: ResMut, + render_device: Res, + render_queue: Res, + mut view_uniforms: ResMut, mut extracted_views: Query<(Entity, &ExtractedView)>, ) { - view_meta + view_uniforms .uniforms - .reserve_and_clear(extracted_views.iter_mut().len(), &render_resources); + .reserve_and_clear(extracted_views.iter_mut().len(), &render_device); for (entity, camera) in extracted_views.iter() { let projection = camera.projection; let view_uniforms = ViewUniformOffset { - offset: view_meta.uniforms.push(ViewUniform { + offset: view_uniforms.uniforms.push(ViewUniform { view_proj: projection * camera.transform.compute_matrix().inverse(), projection, world_position: camera.transform.translation, @@ -77,24 +69,5 @@ fn prepare_views( commands.entity(entity).insert(view_uniforms); } - view_meta - .uniforms - .write_to_staging_buffer(&render_resources); -} - -pub struct ViewNode; - -impl Node for ViewNode { - fn run( - &self, - _graph: &mut RenderGraphContext, - render_context: &mut RenderContext, - world: &World, - ) -> Result<(), NodeRunError> { - let view_meta = world.get_resource::().unwrap(); - view_meta - .uniforms - .write_to_uniform_buffer(&mut render_context.command_encoder); - Ok(()) - } + view_uniforms.uniforms.write_buffer(&render_queue); } diff --git a/pipelined/bevy_sprite2/src/lib.rs b/pipelined/bevy_sprite2/src/lib.rs index 6a7e2925f0e3f..4d443d2b69912 100644 --- a/pipelined/bevy_sprite2/src/lib.rs +++ b/pipelined/bevy_sprite2/src/lib.rs @@ -6,7 +6,6 @@ mod sprite; mod texture_atlas; mod texture_atlas_builder; -use bevy_asset::AddAsset; pub use bundle::*; pub use dynamic_texture_atlas_builder::*; pub use rect::*; @@ -16,6 +15,8 @@ pub use texture_atlas::*; pub use texture_atlas_builder::*; use bevy_app::prelude::*; +use bevy_asset::AddAsset; +use bevy_core_pipeline::Transparent2d; use bevy_render2::{ render_graph::RenderGraph, render_phase::DrawFunctions, RenderApp, RenderStage, }; @@ -28,17 +29,18 @@ impl Plugin for SpritePlugin { app.add_asset::().register_type::(); let render_app = app.sub_app(RenderApp); render_app - .init_resource::() + .init_resource::() + .init_resource::() + .init_resource::() .add_system_to_stage(RenderStage::Extract, render::extract_atlases) .add_system_to_stage(RenderStage::Extract, render::extract_sprites) .add_system_to_stage(RenderStage::Prepare, render::prepare_sprites) - .add_system_to_stage(RenderStage::Queue, queue_sprites) - .init_resource::() - .init_resource::(); + .add_system_to_stage(RenderStage::Queue, queue_sprites); + let draw_sprite = DrawSprite::new(&mut render_app.world); render_app .world - .get_resource::() + .get_resource::>() .unwrap() .write() .add(draw_sprite); diff --git a/pipelined/bevy_sprite2/src/render/mod.rs b/pipelined/bevy_sprite2/src/render/mod.rs index 7310b968c4ec7..01d08bc1f5dff 100644 --- a/pipelined/bevy_sprite2/src/render/mod.rs +++ b/pipelined/bevy_sprite2/src/render/mod.rs @@ -3,23 +3,25 @@ use crate::{ Rect, Sprite, }; use bevy_asset::{Assets, Handle}; -use bevy_core_pipeline::Transparent2dPhase; -use bevy_ecs::{prelude::*, system::SystemState}; +use bevy_core_pipeline::Transparent2d; +use bevy_ecs::{ + prelude::*, + system::{lifetimeless::*, SystemState}, +}; use bevy_math::{Mat4, Vec2, Vec3, Vec4Swizzles}; use bevy_render2::{ mesh::{shape::Quad, Indices, Mesh, VertexAttributeValues}, render_asset::RenderAssets, render_graph::{Node, NodeRunError, RenderGraphContext}, - render_phase::{Draw, DrawFunctions, Drawable, RenderPhase, TrackedRenderPass}, + render_phase::{Draw, DrawFunctions, RenderPhase, TrackedRenderPass}, render_resource::*, renderer::{RenderContext, RenderDevice}, shader::Shader, texture::{BevyDefault, Image}, - view::{ViewMeta, ViewUniformOffset}, - RenderWorld, + view::{ViewUniformOffset, ViewUniforms}, }; use bevy_transform::components::GlobalTransform; -use bevy_utils::slab::{FrameSlabMap, FrameSlabMapKey}; +use bevy_utils::HashMap; use bytemuck::{Pod, Zeroable}; pub struct SpriteShaders { @@ -146,75 +148,71 @@ impl FromWorld for SpriteShaders { } } -struct ExtractedSprite { +pub struct ExtractedSprite { transform: Mat4, rect: Rect, handle: Handle, atlas_size: Option, -} - -#[derive(Default)] -pub struct ExtractedSprites { - sprites: Vec, + vertex_index: usize, } pub fn extract_atlases( + mut commands: Commands, texture_atlases: Res>, - atlas_query: Query<(&TextureAtlasSprite, &GlobalTransform, &Handle)>, - mut render_world: ResMut, + atlas_query: Query<( + Entity, + &TextureAtlasSprite, + &GlobalTransform, + &Handle, + )>, ) { - let mut extracted_sprites = Vec::new(); - for (atlas_sprite, transform, texture_atlas_handle) in atlas_query.iter() { - if !texture_atlases.contains(texture_atlas_handle) { - continue; - } - + let mut sprites = Vec::new(); + for (entity, atlas_sprite, transform, texture_atlas_handle) in atlas_query.iter() { if let Some(texture_atlas) = texture_atlases.get(texture_atlas_handle) { let rect = texture_atlas.textures[atlas_sprite.index as usize]; - extracted_sprites.push(ExtractedSprite { - atlas_size: Some(texture_atlas.size), - transform: transform.compute_matrix(), - rect, - handle: texture_atlas.texture.clone_weak(), - }); + sprites.push(( + entity, + (ExtractedSprite { + atlas_size: Some(texture_atlas.size), + transform: transform.compute_matrix(), + rect, + handle: texture_atlas.texture.clone_weak(), + vertex_index: 0, + },), + )); } } - - if let Some(mut extracted_sprites_res) = render_world.get_resource_mut::() { - extracted_sprites_res.sprites.extend(extracted_sprites); - } + commands.insert_or_spawn_batch(sprites); } pub fn extract_sprites( + mut commands: Commands, images: Res>, - sprite_query: Query<(&Sprite, &GlobalTransform, &Handle)>, - mut render_world: ResMut, + sprite_query: Query<(Entity, &Sprite, &GlobalTransform, &Handle)>, ) { - let mut extracted_sprites = Vec::new(); - for (sprite, transform, handle) in sprite_query.iter() { - let image = if let Some(image) = images.get(handle) { - image - } else { - continue; + let mut sprites = Vec::new(); + for (entity, sprite, transform, handle) in sprite_query.iter() { + if let Some(image) = images.get(handle) { + let size = image.texture_descriptor.size; + + sprites.push(( + entity, + (ExtractedSprite { + atlas_size: None, + transform: transform.compute_matrix(), + rect: Rect { + min: Vec2::ZERO, + max: sprite + .custom_size + .unwrap_or_else(|| Vec2::new(size.width as f32, size.height as f32)), + }, + handle: handle.clone_weak(), + vertex_index: 0, + },), + )); }; - let size = image.texture_descriptor.size; - - extracted_sprites.push(ExtractedSprite { - atlas_size: None, - transform: transform.compute_matrix(), - rect: Rect { - min: Vec2::ZERO, - max: sprite - .custom_size - .unwrap_or_else(|| Vec2::new(size.width as f32, size.height as f32)), - }, - handle: handle.clone_weak(), - }); - } - - if let Some(mut extracted_sprites_res) = render_world.get_resource_mut::() { - extracted_sprites_res.sprites.extend(extracted_sprites); } + commands.insert_or_spawn_batch(sprites); } #[repr(C)] @@ -229,8 +227,6 @@ pub struct SpriteMeta { indices: BufferVec, quad: Mesh, view_bind_group: Option, - texture_bind_group_keys: Vec, BindGroup>>, - texture_bind_groups: FrameSlabMap, BindGroup>, } impl Default for SpriteMeta { @@ -238,8 +234,6 @@ impl Default for SpriteMeta { Self { vertices: BufferVec::new(BufferUsage::VERTEX), indices: BufferVec::new(BufferUsage::INDEX), - texture_bind_groups: Default::default(), - texture_bind_group_keys: Default::default(), view_bind_group: None, quad: Quad { size: Vec2::new(1.0, 1.0), @@ -253,10 +247,11 @@ impl Default for SpriteMeta { pub fn prepare_sprites( render_device: Res, mut sprite_meta: ResMut, - extracted_sprites: Res, + mut extracted_sprites: Query<&mut ExtractedSprite>, ) { + let extracted_sprite_len = extracted_sprites.iter_mut().len(); // dont create buffers when there are no sprites - if extracted_sprites.sprites.is_empty() { + if extracted_sprite_len == 0 { return; } @@ -279,15 +274,14 @@ pub fn prepare_sprites( }; sprite_meta.vertices.reserve_and_clear( - extracted_sprites.sprites.len() * quad_vertex_positions.len(), - &render_device, - ); - sprite_meta.indices.reserve_and_clear( - extracted_sprites.sprites.len() * quad_indices.len(), + extracted_sprite_len * quad_vertex_positions.len(), &render_device, ); + sprite_meta + .indices + .reserve_and_clear(extracted_sprite_len * quad_indices.len(), &render_device); - for (i, extracted_sprite) in extracted_sprites.sprites.iter().enumerate() { + for (i, mut extracted_sprite) in extracted_sprites.iter_mut().enumerate() { let sprite_rect = extracted_sprite.rect; // Specify the corners of the sprite @@ -298,6 +292,7 @@ pub fn prepare_sprites( let atlas_positions: [Vec2; 4] = [bottom_left, top_left, top_right, bottom_right]; + extracted_sprite.vertex_index = i; for (index, vertex_position) in quad_vertex_positions.iter().enumerate() { let mut final_position = Vec3::from(*vertex_position) * extracted_sprite.rect.size().extend(1.0); @@ -321,71 +316,63 @@ pub fn prepare_sprites( sprite_meta.indices.write_to_staging_buffer(&render_device); } +#[derive(Default)] +pub struct ImageBindGroups { + values: HashMap, BindGroup>, +} + #[allow(clippy::too_many_arguments)] pub fn queue_sprites( - draw_functions: Res, + draw_functions: Res>, render_device: Res, mut sprite_meta: ResMut, - view_meta: Res, + view_uniforms: Res, sprite_shaders: Res, - mut extracted_sprites: ResMut, + mut image_bind_groups: ResMut, gpu_images: Res>, - mut views: Query<&mut RenderPhase>, + mut extracted_sprites: Query<(Entity, &ExtractedSprite)>, + mut views: Query<&mut RenderPhase>, ) { - if view_meta.uniforms.is_empty() { - return; - } - - // TODO: define this without needing to check every frame - sprite_meta.view_bind_group.get_or_insert_with(|| { - render_device.create_bind_group(&BindGroupDescriptor { + if let Some(view_binding) = view_uniforms.uniforms.binding() { + sprite_meta.view_bind_group = Some(render_device.create_bind_group(&BindGroupDescriptor { entries: &[BindGroupEntry { binding: 0, - resource: view_meta.uniforms.binding(), + resource: view_binding, }], label: None, layout: &sprite_shaders.view_layout, - }) - }); - let sprite_meta = &mut *sprite_meta; - let draw_sprite_function = draw_functions.read().get_id::().unwrap(); - sprite_meta.texture_bind_groups.next_frame(); - sprite_meta.texture_bind_group_keys.clear(); - for mut transparent_phase in views.iter_mut() { - for (i, sprite) in extracted_sprites.sprites.iter().enumerate() { - let texture_bind_group_key = sprite_meta.texture_bind_groups.get_or_insert_with( - sprite.handle.clone_weak(), - || { - let gpu_image = gpu_images.get(&sprite.handle).unwrap(); - render_device.create_bind_group(&BindGroupDescriptor { - entries: &[ - BindGroupEntry { - binding: 0, - resource: BindingResource::TextureView(&gpu_image.texture_view), - }, - BindGroupEntry { - binding: 1, - resource: BindingResource::Sampler(&gpu_image.sampler), - }, - ], - label: None, - layout: &sprite_shaders.material_layout, - }) - }, - ); - sprite_meta - .texture_bind_group_keys - .push(texture_bind_group_key); - - transparent_phase.add(Drawable { - draw_function: draw_sprite_function, - draw_key: i, - sort_key: texture_bind_group_key.index(), - }); + })); + let draw_sprite_function = draw_functions.read().get_id::().unwrap(); + for mut transparent_phase in views.iter_mut() { + for (entity, sprite) in extracted_sprites.iter_mut() { + image_bind_groups + .values + .entry(sprite.handle.clone_weak()) + .or_insert_with(|| { + let gpu_image = gpu_images.get(&sprite.handle).unwrap(); + render_device.create_bind_group(&BindGroupDescriptor { + entries: &[ + BindGroupEntry { + binding: 0, + resource: BindingResource::TextureView(&gpu_image.texture_view), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::Sampler(&gpu_image.sampler), + }, + ], + label: None, + layout: &sprite_shaders.material_layout, + }) + }); + transparent_phase.add(Transparent2d { + draw_function: draw_sprite_function, + entity, + sort_key: sprite.handle.clone_weak(), + }); + } } } - - extracted_sprites.sprites.clear(); } // TODO: this logic can be moved to prepare_sprites once wgpu::Queue is exposed directly @@ -409,13 +396,14 @@ impl Node for SpriteNode { } } -type DrawSpriteQuery<'s, 'w> = ( - Res<'w, SpriteShaders>, - Res<'w, SpriteMeta>, - Query<'w, 's, &'w ViewUniformOffset>, -); pub struct DrawSprite { - params: SystemState>, + params: SystemState<( + SRes, + SRes, + SRes, + SQuery>, + SQuery>, + )>, } impl DrawSprite { @@ -426,19 +414,21 @@ impl DrawSprite { } } -impl Draw for DrawSprite { +impl Draw for DrawSprite { fn draw<'w>( &mut self, world: &'w World, pass: &mut TrackedRenderPass<'w>, view: Entity, - draw_key: usize, - _sort_key: usize, + item: &Transparent2d, ) { const INDICES: usize = 6; - let (sprite_shaders, sprite_meta, views) = self.params.get(world); + let (sprite_shaders, sprite_meta, image_bind_groups, views, sprites) = + self.params.get(world); let view_uniform = views.get(view).unwrap(); let sprite_meta = sprite_meta.into_inner(); + let image_bind_groups = image_bind_groups.into_inner(); + let extracted_sprite = sprites.get(item.entity).unwrap(); pass.set_render_pipeline(&sprite_shaders.into_inner().pipeline); pass.set_vertex_buffer(0, sprite_meta.vertices.buffer().unwrap().slice(..)); pass.set_index_buffer( @@ -453,12 +443,16 @@ impl Draw for DrawSprite { ); pass.set_bind_group( 1, - &sprite_meta.texture_bind_groups[sprite_meta.texture_bind_group_keys[draw_key]], + image_bind_groups + .values + .get(&extracted_sprite.handle) + .unwrap(), &[], ); pass.draw_indexed( - (draw_key * INDICES) as u32..(draw_key * INDICES + INDICES) as u32, + (extracted_sprite.vertex_index * INDICES) as u32 + ..(extracted_sprite.vertex_index * INDICES + INDICES) as u32, 0, 0..1, ); diff --git a/pipelined/bevy_sprite2/src/render/sprite.frag b/pipelined/bevy_sprite2/src/render/sprite.frag deleted file mode 100644 index e673c0ab1dcf4..0000000000000 --- a/pipelined/bevy_sprite2/src/render/sprite.frag +++ /dev/null @@ -1,11 +0,0 @@ -#version 450 - -layout(location = 0) in vec2 v_Uv; -layout(location = 0) out vec4 o_Target; - -layout(set = 1, binding = 0) uniform texture2D sprite_texture; -layout(set = 1, binding = 1) uniform sampler sprite_sampler; - -void fragment() { - o_Target = texture(sampler2D(sprite_texture, sprite_sampler), v_Uv); -} diff --git a/pipelined/bevy_sprite2/src/render/sprite.vert b/pipelined/bevy_sprite2/src/render/sprite.vert deleted file mode 100644 index 8cbf89d4878fd..0000000000000 --- a/pipelined/bevy_sprite2/src/render/sprite.vert +++ /dev/null @@ -1,17 +0,0 @@ -#version 450 - -layout(location = 0) in vec3 Vertex_Position; -layout(location = 1) in vec2 Vertex_Uv; - -layout(location = 0) out vec2 v_Uv; - -layout(set = 0, binding = 0) uniform View { - mat4 ViewProj; - vec4 ViewWorldPosition; - // vec3 ViewWorldPosition; -}; - -void vertex() { - v_Uv = Vertex_Uv; - gl_Position = ViewProj * vec4(Vertex_Position, 1.0); -} diff --git a/pipelined/bevy_sprite2/src/render/sprite.wgsl b/pipelined/bevy_sprite2/src/render/sprite.wgsl index 0f662d5cc0e92..f3a31f92b3dc7 100644 --- a/pipelined/bevy_sprite2/src/render/sprite.wgsl +++ b/pipelined/bevy_sprite2/src/render/sprite.wgsl @@ -1,4 +1,3 @@ -// TODO: try merging this block with the binding? [[block]] struct View { view_proj: mat4x4;

::Fetch as SystemParamFetch<'w, 's>>::Item; + /// The state of a [`SystemParam`]. /// /// # Safety @@ -1220,3 +1222,12 @@ macro_rules! impl_system_param_tuple { } all_tuples!(impl_system_param_tuple, 0, 16, P); + +pub mod lifetimeless { + pub type SQuery = super::Query<'static, 'static, Q, F>; + pub type Read = &'static T; + pub type Write = &'static mut T; + pub type SRes = super::Res<'static, T>; + pub type SResMut = super::ResMut<'static, T>; + pub type SCommands = crate::system::Commands<'static, 'static>; +} diff --git a/examples/3d/many_cubes_pipelined.rs b/examples/3d/many_cubes_pipelined.rs new file mode 100644 index 0000000000000..7ed8f248a91a4 --- /dev/null +++ b/examples/3d/many_cubes_pipelined.rs @@ -0,0 +1,50 @@ +use bevy::{ + diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, + ecs::prelude::*, + pbr2::{PbrBundle, StandardMaterial}, + prelude::{App, Assets, Transform}, + render2::{ + camera::PerspectiveCameraBundle, + color::Color, + mesh::{shape, Mesh}, + }, + PipelinedDefaultPlugins, +}; + +fn main() { + App::new() + .add_plugins(PipelinedDefaultPlugins) + .add_plugin(FrameTimeDiagnosticsPlugin::default()) + .add_plugin(LogDiagnosticsPlugin::default()) + .add_startup_system(setup.system()) + .run(); +} + +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + const WIDTH: usize = 100; + const HEIGHT: usize = 100; + for x in 0..WIDTH { + for y in 0..HEIGHT { + // cube + commands.spawn_bundle(PbrBundle { + mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + material: materials.add(StandardMaterial { + base_color: Color::PINK, + ..Default::default() + }), + transform: Transform::from_xyz((x as f32) * 2.0, (y as f32) * 2.0, 0.0), + ..Default::default() + }); + } + } + + // camera + commands.spawn_bundle(PerspectiveCameraBundle { + transform: Transform::from_xyz(80.0, 80.0, 300.0), + ..Default::default() + }); +} diff --git a/examples/README.md b/examples/README.md index 3ea3824673145..fdaf892a3cdf5 100644 --- a/examples/README.md +++ b/examples/README.md @@ -101,6 +101,7 @@ Example | File | Description `cornell_box_pipelined` | [`3d/cornell_box_pipelined.rs`](./3d/cornell_box_pipelined.rs) | Re-production of the cornell box `load_gltf` | [`3d/load_gltf.rs`](./3d/load_gltf.rs) | Loads and renders a gltf file as a scene `load_gltf_pipelined` | [`3d/load_gltf_pipelined.rs`](./3d/load_gltf_pipelined.rs) | Loads and renders a gltf file as a scene +`many_cubes_pipelined` | [`3d/many_cubes_pipelined.rs`](./3d/many_cubes_pipelined.rs) | Simple benchmark to test per-entity draw overhead `msaa` | [`3d/msaa.rs`](./3d/msaa.rs) | Configures MSAA (Multi-Sample Anti-Aliasing) for smoother edges `orthographic` | [`3d/orthographic.rs`](./3d/orthographic.rs) | Shows how to create a 3D orthographic view (for isometric-look games or CAD applications) `orthographic_pipelined` | [`3d/orthographic_pipelined.rs`](./3d/orthographic_pipelined.rs) | Shows how to create a 3D orthographic view (for isometric-look games or CAD applications) @@ -222,6 +223,7 @@ Example | File | Description --- | --- | --- `animate_shader` | [`shader/animate_shader.rs`](./shader/animate_shader.rs) | Shows how to animate a shader by accessing a time uniform variable `array_texture` | [`shader/array_texture.rs`](./shader/array_texture.rs) | Illustrates how to create a texture for use with a texture2DArray shader uniform variable +`custom_shader_pipelined` | [`shader/custom_shader_pipelined.rs`](./shader/custom_shader_pipelined.rs) | Illustrates how to create custom shaders `hot_shader_reloading` | [`shader/hot_shader_reloading.rs`](./shader/hot_shader_reloading.rs) | Illustrates how to load shaders such that they can be edited while the example is still running `mesh_custom_attribute` | [`shader/mesh_custom_attribute.rs`](./shader/mesh_custom_attribute.rs) | Illustrates how to add a custom attribute to a mesh and use it in a custom shader `shader_custom_material` | [`shader/shader_custom_material.rs`](./shader/shader_custom_material.rs) | Illustrates creating a custom material and a shader that uses it diff --git a/examples/shader/custom_shader_pipelined.rs b/examples/shader/custom_shader_pipelined.rs new file mode 100644 index 0000000000000..9fb9d118e4034 --- /dev/null +++ b/examples/shader/custom_shader_pipelined.rs @@ -0,0 +1,297 @@ +use bevy::{ + core_pipeline::Transparent3d, + diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, + ecs::{ + prelude::*, + system::{lifetimeless::*, SystemParamItem}, + }, + math::{Vec3, Vec4}, + pbr2::{DrawMesh, MeshUniform, PbrShaders, SetMeshViewBindGroup, SetTransformBindGroup}, + prelude::{AddAsset, App, Assets, GlobalTransform, Handle, Plugin, Transform}, + reflect::TypeUuid, + render2::{ + camera::PerspectiveCameraBundle, + color::Color, + mesh::{shape, Mesh}, + render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets}, + render_component::ExtractComponentPlugin, + render_phase::{ + AddRenderCommand, DrawFunctions, RenderCommand, RenderPhase, TrackedRenderPass, + }, + render_resource::*, + renderer::RenderDevice, + shader::Shader, + texture::BevyDefault, + view::ExtractedView, + RenderApp, RenderStage, + }, + PipelinedDefaultPlugins, +}; +use crevice::std140::{AsStd140, Std140}; + +#[derive(Debug, Clone, TypeUuid)] +#[uuid = "4ee9c363-1124-4113-890e-199d81b00281"] +pub struct CustomMaterial { + color: Color, +} + +#[derive(Clone)] +pub struct GpuCustomMaterial { + _buffer: Buffer, + bind_group: BindGroup, +} + +impl RenderAsset for CustomMaterial { + type ExtractedAsset = CustomMaterial; + type PreparedAsset = GpuCustomMaterial; + type Param = (SRes, SRes); + fn extract_asset(&self) -> Self::ExtractedAsset { + self.clone() + } + + fn prepare_asset( + extracted_asset: Self::ExtractedAsset, + (render_device, custom_pipeline): &mut SystemParamItem, + ) -> Result> { + let color: Vec4 = extracted_asset.color.as_rgba_linear().into(); + let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor { + contents: color.as_std140().as_bytes(), + label: None, + usage: BufferUsage::UNIFORM | BufferUsage::COPY_DST, + }); + let bind_group = render_device.create_bind_group(&BindGroupDescriptor { + entries: &[BindGroupEntry { + binding: 0, + resource: buffer.as_entire_binding(), + }], + label: None, + layout: &custom_pipeline.material_layout, + }); + + Ok(GpuCustomMaterial { + _buffer: buffer, + bind_group, + }) + } +} +pub struct CustomMaterialPlugin; + +impl Plugin for CustomMaterialPlugin { + fn build(&self, app: &mut App) { + app.add_asset::() + .add_plugin(ExtractComponentPlugin::>::default()) + .add_plugin(RenderAssetPlugin::::default()); + app.sub_app(RenderApp) + .add_render_command::() + .init_resource::() + .add_system_to_stage(RenderStage::Queue, queue_custom); + } +} + +fn main() { + App::new() + .add_plugins(PipelinedDefaultPlugins) + .add_plugin(FrameTimeDiagnosticsPlugin::default()) + .add_plugin(LogDiagnosticsPlugin::default()) + .add_plugin(CustomMaterialPlugin) + .add_startup_system(setup) + .run(); +} + +/// set up a simple 3D scene +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + // cube + commands.spawn().insert_bundle(( + meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + Transform::from_xyz(0.0, 0.5, 0.0), + GlobalTransform::default(), + materials.add(CustomMaterial { + color: Color::GREEN, + }), + )); + + // camera + commands.spawn_bundle(PerspectiveCameraBundle { + transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), + ..Default::default() + }); +} + +pub struct CustomPipeline { + material_layout: BindGroupLayout, + pipeline: RenderPipeline, +} + +// TODO: this pattern for initializing the shaders / pipeline isn't ideal. this should be handled by the asset system +impl FromWorld for CustomPipeline { + fn from_world(world: &mut World) -> Self { + let render_device = world.get_resource::().unwrap(); + let shader = Shader::from_wgsl(include_str!("../../assets/shaders/custom.wgsl")); + let shader_module = render_device.create_shader_module(&shader); + + let material_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { + entries: &[BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStage::FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: BufferSize::new(Vec4::std140_size_static() as u64), + }, + count: None, + }], + label: None, + }); + let pbr_pipeline = world.get_resource::().unwrap(); + + let pipeline_layout = render_device.create_pipeline_layout(&PipelineLayoutDescriptor { + label: None, + push_constant_ranges: &[], + bind_group_layouts: &[ + &pbr_pipeline.view_layout, + &material_layout, + &pbr_pipeline.mesh_layout, + ], + }); + + let pipeline = render_device.create_render_pipeline(&RenderPipelineDescriptor { + label: None, + vertex: VertexState { + buffers: &[VertexBufferLayout { + array_stride: 32, + step_mode: InputStepMode::Vertex, + attributes: &[ + // Position (GOTCHA! Vertex_Position isn't first in the buffer due to how Mesh sorts attributes (alphabetically)) + VertexAttribute { + format: VertexFormat::Float32x3, + offset: 12, + shader_location: 0, + }, + // Normal + VertexAttribute { + format: VertexFormat::Float32x3, + offset: 0, + shader_location: 1, + }, + // Uv + VertexAttribute { + format: VertexFormat::Float32x2, + offset: 24, + shader_location: 2, + }, + ], + }], + module: &shader_module, + entry_point: "vertex", + }, + fragment: Some(FragmentState { + module: &shader_module, + entry_point: "fragment", + targets: &[ColorTargetState { + format: TextureFormat::bevy_default(), + blend: Some(BlendState { + color: BlendComponent { + src_factor: BlendFactor::SrcAlpha, + dst_factor: BlendFactor::OneMinusSrcAlpha, + operation: BlendOperation::Add, + }, + alpha: BlendComponent { + src_factor: BlendFactor::One, + dst_factor: BlendFactor::One, + operation: BlendOperation::Add, + }, + }), + write_mask: ColorWrite::ALL, + }], + }), + depth_stencil: Some(DepthStencilState { + format: TextureFormat::Depth32Float, + depth_write_enabled: true, + depth_compare: CompareFunction::Greater, + stencil: StencilState { + front: StencilFaceState::IGNORE, + back: StencilFaceState::IGNORE, + read_mask: 0, + write_mask: 0, + }, + bias: DepthBiasState { + constant: 0, + slope_scale: 0.0, + clamp: 0.0, + }, + }), + layout: Some(&pipeline_layout), + multisample: MultisampleState::default(), + primitive: PrimitiveState { + topology: PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: FrontFace::Ccw, + cull_mode: Some(Face::Back), + polygon_mode: PolygonMode::Fill, + clamp_depth: false, + conservative: false, + }, + }); + + CustomPipeline { + pipeline, + material_layout, + } + } +} + +pub fn queue_custom( + transparent_3d_draw_functions: Res>, + materials: Res>, + material_meshes: Query<(Entity, &Handle, &MeshUniform), With>>, + mut views: Query<(&ExtractedView, &mut RenderPhase)>, +) { + let draw_custom = transparent_3d_draw_functions + .read() + .get_id::() + .unwrap(); + for (view, mut transparent_phase) in views.iter_mut() { + let view_matrix = view.transform.compute_matrix(); + let view_row_2 = view_matrix.row(2); + for (entity, material_handle, mesh_uniform) in material_meshes.iter() { + if materials.contains_key(material_handle) { + transparent_phase.add(Transparent3d { + entity, + draw_function: draw_custom, + distance: view_row_2.dot(mesh_uniform.transform.col(3)), + }); + } + } + } +} + +type DrawCustom = ( + SetCustomMaterialPipeline, + SetMeshViewBindGroup<0>, + SetTransformBindGroup<2>, + DrawMesh, +); + +struct SetCustomMaterialPipeline; +impl RenderCommand for SetCustomMaterialPipeline { + type Param = ( + SRes>, + SRes, + SQuery>>, + ); + fn render<'w>( + _view: Entity, + item: &Transparent3d, + (materials, custom_pipeline, query): SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, + ) { + let material_handle = query.get(item.entity).unwrap(); + let material = materials.into_inner().get(material_handle).unwrap(); + pass.set_render_pipeline(&custom_pipeline.into_inner().pipeline); + pass.set_bind_group(1, &material.bind_group, &[]); + } +} diff --git a/examples/tools/bevymark_pipelined.rs b/examples/tools/bevymark_pipelined.rs index 648d91a88deb1..f42969bc174d5 100644 --- a/examples/tools/bevymark_pipelined.rs +++ b/examples/tools/bevymark_pipelined.rs @@ -66,11 +66,23 @@ struct BirdTexture(Handle); fn setup( mut commands: Commands, - _window: Res, - _counter: ResMut, + window: Res, + mut counter: ResMut, asset_server: Res, ) { - // spawn_birds(&mut commands, &window, &mut counter, 10); + let texture = asset_server.load("branding/icon.png"); + if let Some(initial_count) = std::env::args() + .nth(1) + .and_then(|arg| arg.parse::().ok()) + { + spawn_birds( + &mut commands, + &window, + &mut counter, + initial_count, + texture.clone_weak(), + ); + } commands.spawn_bundle(OrthographicCameraBundle::new_2d()); // commands.spawn_bundle(UiCameraBundle::default()); // commands.spawn_bundle(TextBundle { @@ -123,7 +135,7 @@ fn setup( // ..Default::default() // }); - commands.insert_resource(BirdTexture(asset_server.load("branding/icon.png"))); + commands.insert_resource(BirdTexture(texture)); } #[allow(clippy::too_many_arguments)] diff --git a/pipelined/bevy_core_pipeline/Cargo.toml b/pipelined/bevy_core_pipeline/Cargo.toml index 539816e466b03..4f4ac0e2fed5f 100644 --- a/pipelined/bevy_core_pipeline/Cargo.toml +++ b/pipelined/bevy_core_pipeline/Cargo.toml @@ -15,6 +15,8 @@ keywords = ["bevy"] [dependencies] # bevy bevy_app = { path = "../../crates/bevy_app", version = "0.5.0" } +bevy_asset = { path = "../../crates/bevy_asset", version = "0.5.0" } +bevy_core = { path = "../../crates/bevy_core", version = "0.5.0" } bevy_ecs = { path = "../../crates/bevy_ecs", version = "0.5.0" } bevy_render2 = { path = "../bevy_render2", version = "0.5.0" } diff --git a/pipelined/bevy_core_pipeline/src/lib.rs b/pipelined/bevy_core_pipeline/src/lib.rs index c9187adf39daa..5276df1fc057a 100644 --- a/pipelined/bevy_core_pipeline/src/lib.rs +++ b/pipelined/bevy_core_pipeline/src/lib.rs @@ -7,19 +7,18 @@ pub use main_pass_3d::*; pub use main_pass_driver::*; use bevy_app::{App, Plugin}; +use bevy_asset::Handle; +use bevy_core::FloatOrd; use bevy_ecs::prelude::*; use bevy_render2::{ camera::{ActiveCameras, CameraPlugin}, color::Color, render_graph::{EmptyNode, RenderGraph, SlotInfo, SlotType}, - render_phase::{sort_phase_system, RenderPhase}, - render_resource::{ - Extent3d, Texture, TextureDescriptor, TextureDimension, TextureFormat, TextureUsage, - TextureView, - }, + render_phase::{sort_phase_system, DrawFunctionId, DrawFunctions, PhaseItem, RenderPhase}, + render_resource::*, renderer::RenderDevice, - texture::TextureCache, - view::{ExtractedView, ViewPlugin}, + texture::{Image, TextureCache}, + view::ExtractedView, RenderApp, RenderStage, RenderWorld, }; @@ -76,17 +75,13 @@ impl Plugin for CorePipelinePlugin { let render_app = app.sub_app(RenderApp); render_app + .init_resource::>() + .init_resource::>() .add_system_to_stage(RenderStage::Extract, extract_clear_color) .add_system_to_stage(RenderStage::Extract, extract_core_pipeline_camera_phases) .add_system_to_stage(RenderStage::Prepare, prepare_core_views_system) - .add_system_to_stage( - RenderStage::PhaseSort, - sort_phase_system::, - ) - .add_system_to_stage( - RenderStage::PhaseSort, - sort_phase_system::, - ); + .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::) + .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::); let pass_node_2d = MainPass2dNode::new(&mut render_app.world); let pass_node_3d = MainPass3dNode::new(&mut render_app.world); @@ -151,17 +146,51 @@ impl Plugin for CorePipelinePlugin { graph.add_node(node::MAIN_PASS_DEPENDENCIES, EmptyNode); graph.add_node(node::MAIN_PASS_DRIVER, MainPassDriverNode); - graph - .add_node_edge(ViewPlugin::VIEW_NODE, node::MAIN_PASS_DEPENDENCIES) - .unwrap(); graph .add_node_edge(node::MAIN_PASS_DEPENDENCIES, node::MAIN_PASS_DRIVER) .unwrap(); } } -pub struct Transparent3dPhase; -pub struct Transparent2dPhase; +pub struct Transparent2d { + pub sort_key: Handle, + pub entity: Entity, + pub draw_function: DrawFunctionId, +} + +impl PhaseItem for Transparent2d { + type SortKey = Handle; + + #[inline] + fn sort_key(&self) -> Self::SortKey { + self.sort_key.clone_weak() + } + + #[inline] + fn draw_function(&self) -> DrawFunctionId { + self.draw_function + } +} + +pub struct Transparent3d { + pub distance: f32, + pub entity: Entity, + pub draw_function: DrawFunctionId, +} + +impl PhaseItem for Transparent3d { + type SortKey = FloatOrd; + + #[inline] + fn sort_key(&self) -> Self::SortKey { + FloatOrd(self.distance) + } + + #[inline] + fn draw_function(&self) -> DrawFunctionId { + self.draw_function + } +} pub struct ViewDepthTexture { pub texture: Texture, @@ -184,14 +213,14 @@ pub fn extract_core_pipeline_camera_phases( if let Some(entity) = camera_2d.entity { commands .get_or_spawn(entity) - .insert(RenderPhase::::default()); + .insert(RenderPhase::::default()); } } if let Some(camera_3d) = active_cameras.get(CameraPlugin::CAMERA_3D) { if let Some(entity) = camera_3d.entity { commands .get_or_spawn(entity) - .insert(RenderPhase::::default()); + .insert(RenderPhase::::default()); } } } @@ -200,7 +229,7 @@ pub fn prepare_core_views_system( mut commands: Commands, mut texture_cache: ResMut, render_device: Res, - views: Query<(Entity, &ExtractedView), With>>, + views: Query<(Entity, &ExtractedView), With>>, ) { for (entity, view) in views.iter() { let cached_texture = texture_cache.get( diff --git a/pipelined/bevy_core_pipeline/src/main_pass_2d.rs b/pipelined/bevy_core_pipeline/src/main_pass_2d.rs index e21d2dac5fc09..32996fc728807 100644 --- a/pipelined/bevy_core_pipeline/src/main_pass_2d.rs +++ b/pipelined/bevy_core_pipeline/src/main_pass_2d.rs @@ -1,4 +1,4 @@ -use crate::{ClearColor, Transparent2dPhase}; +use crate::{ClearColor, Transparent2d}; use bevy_ecs::prelude::*; use bevy_render2::{ render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, @@ -9,7 +9,7 @@ use bevy_render2::{ }; pub struct MainPass2dNode { - query: QueryState<&'static RenderPhase, With>, + query: QueryState<&'static RenderPhase, With>, } impl MainPass2dNode { @@ -57,7 +57,9 @@ impl Node for MainPass2dNode { }; let view_entity = graph.get_input_entity(Self::IN_VIEW)?; - let draw_functions = world.get_resource::().unwrap(); + let draw_functions = world + .get_resource::>() + .unwrap(); let transparent_phase = self .query @@ -70,15 +72,9 @@ impl Node for MainPass2dNode { let mut draw_functions = draw_functions.write(); let mut tracked_pass = TrackedRenderPass::new(render_pass); - for drawable in transparent_phase.drawn_things.iter() { - let draw_function = draw_functions.get_mut(drawable.draw_function).unwrap(); - draw_function.draw( - world, - &mut tracked_pass, - view_entity, - drawable.draw_key, - drawable.sort_key, - ); + for item in transparent_phase.items.iter() { + let draw_function = draw_functions.get_mut(item.draw_function).unwrap(); + draw_function.draw(world, &mut tracked_pass, view_entity, item); } Ok(()) } diff --git a/pipelined/bevy_core_pipeline/src/main_pass_3d.rs b/pipelined/bevy_core_pipeline/src/main_pass_3d.rs index b2a1ab644bd8d..c2c0fc08a6fa0 100644 --- a/pipelined/bevy_core_pipeline/src/main_pass_3d.rs +++ b/pipelined/bevy_core_pipeline/src/main_pass_3d.rs @@ -1,4 +1,4 @@ -use crate::{ClearColor, Transparent3dPhase}; +use crate::{ClearColor, Transparent3d}; use bevy_ecs::prelude::*; use bevy_render2::{ render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, @@ -12,7 +12,7 @@ use bevy_render2::{ }; pub struct MainPass3dNode { - query: QueryState<&'static RenderPhase, With>, + query: QueryState<&'static RenderPhase, With>, } impl MainPass3dNode { @@ -70,7 +70,9 @@ impl Node for MainPass3dNode { }; let view_entity = graph.get_input_entity(Self::IN_VIEW)?; - let draw_functions = world.get_resource::().unwrap(); + let draw_functions = world + .get_resource::>() + .unwrap(); let transparent_phase = self .query @@ -82,15 +84,9 @@ impl Node for MainPass3dNode { .begin_render_pass(&pass_descriptor); let mut draw_functions = draw_functions.write(); let mut tracked_pass = TrackedRenderPass::new(render_pass); - for drawable in transparent_phase.drawn_things.iter() { - let draw_function = draw_functions.get_mut(drawable.draw_function).unwrap(); - draw_function.draw( - world, - &mut tracked_pass, - view_entity, - drawable.draw_key, - drawable.sort_key, - ); + for item in transparent_phase.items.iter() { + let draw_function = draw_functions.get_mut(item.draw_function).unwrap(); + draw_function.draw(world, &mut tracked_pass, view_entity, item); } Ok(()) } diff --git a/pipelined/bevy_pbr2/src/lib.rs b/pipelined/bevy_pbr2/src/lib.rs index 9b0b535245a30..cd4e48bb52851 100644 --- a/pipelined/bevy_pbr2/src/lib.rs +++ b/pipelined/bevy_pbr2/src/lib.rs @@ -9,10 +9,13 @@ pub use material::*; pub use render::*; use bevy_app::prelude::*; +use bevy_asset::Handle; +use bevy_core_pipeline::Transparent3d; use bevy_ecs::prelude::*; use bevy_render2::{ + render_component::{ExtractComponentPlugin, UniformComponentPlugin}, render_graph::RenderGraph, - render_phase::{sort_phase_system, DrawFunctions}, + render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions}, RenderApp, RenderStage, }; @@ -28,15 +31,17 @@ pub struct PbrPlugin; impl Plugin for PbrPlugin { fn build(&self, app: &mut App) { app.add_plugin(StandardMaterialPlugin) + .add_plugin(ExtractComponentPlugin::>::default()) + .add_plugin(UniformComponentPlugin::::default()) .init_resource::() .init_resource::() - .init_resource::(); + .init_resource::() + .init_resource::(); let render_app = app.sub_app(RenderApp); render_app .add_system_to_stage(RenderStage::Extract, render::extract_meshes) .add_system_to_stage(RenderStage::Extract, render::extract_lights) - .add_system_to_stage(RenderStage::Prepare, render::prepare_meshes) .add_system_to_stage( RenderStage::Prepare, // this is added as an exclusive system because it contributes new views. it must run (and have Commands applied) @@ -44,27 +49,24 @@ impl Plugin for PbrPlugin { render::prepare_lights.exclusive_system(), ) .add_system_to_stage(RenderStage::Queue, render::queue_meshes) - .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::) - // FIXME: Hack to ensure RenderCommandQueue is initialized when PbrShaders is being initialized - // .init_resource::() + .add_system_to_stage(RenderStage::Queue, render::queue_shadows) + .add_system_to_stage(RenderStage::Queue, render::queue_shadow_view_bind_group) + .add_system_to_stage(RenderStage::Queue, render::queue_transform_bind_group) + .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::) .init_resource::() .init_resource::() - .init_resource::() + .init_resource::>() .init_resource::(); - let draw_pbr = DrawPbr::new(&mut render_app.world); let draw_shadow_mesh = DrawShadowMesh::new(&mut render_app.world); let shadow_pass_node = ShadowPassNode::new(&mut render_app.world); + render_app.add_render_command::(); let render_world = render_app.world.cell(); - let draw_functions = render_world.get_resource::().unwrap(); - draw_functions.write().add(draw_pbr); + let draw_functions = render_world + .get_resource::>() + .unwrap(); draw_functions.write().add(draw_shadow_mesh); let mut graph = render_world.get_resource_mut::().unwrap(); - graph.add_node("pbr", PbrNode); - graph - .add_node_edge("pbr", bevy_core_pipeline::node::MAIN_PASS_DEPENDENCIES) - .unwrap(); - let draw_3d_graph = graph .get_sub_graph_mut(bevy_core_pipeline::draw_3d_graph::NAME) .unwrap(); diff --git a/pipelined/bevy_pbr2/src/material.rs b/pipelined/bevy_pbr2/src/material.rs index 0434f657179e6..ec40936b2aea9 100644 --- a/pipelined/bevy_pbr2/src/material.rs +++ b/pipelined/bevy_pbr2/src/material.rs @@ -1,15 +1,19 @@ use bevy_app::{App, Plugin}; use bevy_asset::{AddAsset, Handle}; +use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem}; use bevy_math::Vec4; use bevy_reflect::TypeUuid; use bevy_render2::{ color::Color, - render_asset::{RenderAsset, RenderAssetPlugin}, - render_resource::{Buffer, BufferInitDescriptor, BufferUsage}, - renderer::{RenderDevice, RenderQueue}, + render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets}, + render_resource::{BindGroup, Buffer, BufferInitDescriptor, BufferUsage, Sampler, TextureView}, + renderer::RenderDevice, texture::Image, }; use crevice::std140::{AsStd140, Std140}; +use wgpu::{BindGroupDescriptor, BindGroupEntry, BindingResource}; + +use crate::PbrShaders; // NOTE: These must match the bit flags in bevy_pbr2/src/render/pbr.frag! bitflags::bitflags! { @@ -134,16 +138,17 @@ impl Plugin for StandardMaterialPlugin { #[derive(Debug, Clone)] pub struct GpuStandardMaterial { pub buffer: Buffer, - // FIXME: image handles feel unnecessary here but the extracted asset is discarded - pub base_color_texture: Option>, - pub emissive_texture: Option>, - pub metallic_roughness_texture: Option>, - pub occlusion_texture: Option>, + pub bind_group: BindGroup, } impl RenderAsset for StandardMaterial { type ExtractedAsset = StandardMaterial; type PreparedAsset = GpuStandardMaterial; + type Param = ( + SRes, + SRes, + SRes>, + ); fn extract_asset(&self) -> Self::ExtractedAsset { self.clone() @@ -151,9 +156,41 @@ impl RenderAsset for StandardMaterial { fn prepare_asset( material: Self::ExtractedAsset, - render_device: &RenderDevice, - _render_queue: &RenderQueue, - ) -> Self::PreparedAsset { + (render_device, pbr_shaders, gpu_images): &mut SystemParamItem, + ) -> Result> { + let (base_color_texture_view, base_color_sampler) = if let Some(result) = + image_handle_to_view_sampler(pbr_shaders, gpu_images, &material.base_color_texture) + { + result + } else { + return Err(PrepareAssetError::RetryNextUpdate(material)); + }; + + let (emissive_texture_view, emissive_sampler) = if let Some(result) = + image_handle_to_view_sampler(pbr_shaders, gpu_images, &material.emissive_texture) + { + result + } else { + return Err(PrepareAssetError::RetryNextUpdate(material)); + }; + + let (metallic_roughness_texture_view, metallic_roughness_sampler) = if let Some(result) = + image_handle_to_view_sampler( + pbr_shaders, + gpu_images, + &material.metallic_roughness_texture, + ) { + result + } else { + return Err(PrepareAssetError::RetryNextUpdate(material)); + }; + let (occlusion_texture_view, occlusion_sampler) = if let Some(result) = + image_handle_to_view_sampler(pbr_shaders, gpu_images, &material.occlusion_texture) + { + result + } else { + return Err(PrepareAssetError::RetryNextUpdate(material)); + }; let mut flags = StandardMaterialFlags::NONE; if material.base_color_texture.is_some() { flags |= StandardMaterialFlags::BASE_COLOR_TEXTURE; @@ -188,12 +225,65 @@ impl RenderAsset for StandardMaterial { usage: BufferUsage::UNIFORM | BufferUsage::COPY_DST, contents: value_std140.as_bytes(), }); - GpuStandardMaterial { - buffer, - base_color_texture: material.base_color_texture, - emissive_texture: material.emissive_texture, - metallic_roughness_texture: material.metallic_roughness_texture, - occlusion_texture: material.occlusion_texture, - } + let bind_group = render_device.create_bind_group(&BindGroupDescriptor { + entries: &[ + BindGroupEntry { + binding: 0, + resource: buffer.as_entire_binding(), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::TextureView(base_color_texture_view), + }, + BindGroupEntry { + binding: 2, + resource: BindingResource::Sampler(base_color_sampler), + }, + BindGroupEntry { + binding: 3, + resource: BindingResource::TextureView(emissive_texture_view), + }, + BindGroupEntry { + binding: 4, + resource: BindingResource::Sampler(emissive_sampler), + }, + BindGroupEntry { + binding: 5, + resource: BindingResource::TextureView(metallic_roughness_texture_view), + }, + BindGroupEntry { + binding: 6, + resource: BindingResource::Sampler(metallic_roughness_sampler), + }, + BindGroupEntry { + binding: 7, + resource: BindingResource::TextureView(occlusion_texture_view), + }, + BindGroupEntry { + binding: 8, + resource: BindingResource::Sampler(occlusion_sampler), + }, + ], + label: None, + layout: &pbr_shaders.material_layout, + }); + + Ok(GpuStandardMaterial { buffer, bind_group }) + } +} + +fn image_handle_to_view_sampler<'a>( + pbr_pipeline: &'a PbrShaders, + gpu_images: &'a RenderAssets, + handle_option: &Option>, +) -> Option<(&'a TextureView, &'a Sampler)> { + if let Some(handle) = handle_option { + let gpu_image = gpu_images.get(handle)?; + Some((&gpu_image.texture_view, &gpu_image.sampler)) + } else { + Some(( + &pbr_pipeline.dummy_white_gpu_image.texture_view, + &pbr_pipeline.dummy_white_gpu_image.sampler, + )) } } diff --git a/pipelined/bevy_pbr2/src/render/depth.wgsl b/pipelined/bevy_pbr2/src/render/depth.wgsl index 36367c1b9c23d..35e66e26bf71f 100644 --- a/pipelined/bevy_pbr2/src/render/depth.wgsl +++ b/pipelined/bevy_pbr2/src/render/depth.wgsl @@ -12,7 +12,11 @@ var view: View; [[block]] struct Mesh { model: mat4x4; + inverse_transpose_model: mat4x4; + // 'flags' is a bit field indicating various options. u32 is 32 bits so we have up to 32 options. + flags: u32; }; + [[group(1), binding(0)]] var mesh: Mesh; diff --git a/pipelined/bevy_pbr2/src/render/light.rs b/pipelined/bevy_pbr2/src/render/light.rs index fb45a1f2b132a..5321093ae3cc4 100644 --- a/pipelined/bevy_pbr2/src/render/light.rs +++ b/pipelined/bevy_pbr2/src/render/light.rs @@ -1,22 +1,30 @@ use crate::{ - AmbientLight, DirectionalLight, DirectionalLightShadowMap, ExtractedMeshes, MeshMeta, - PbrShaders, PointLight, PointLightShadowMap, + AmbientLight, DirectionalLight, DirectionalLightShadowMap, MeshUniform, NotShadowCaster, + PbrShaders, PointLight, PointLightShadowMap, TransformBindGroup, +}; +use bevy_asset::Handle; +use bevy_core::FloatOrd; +use bevy_core_pipeline::Transparent3d; +use bevy_ecs::{ + prelude::*, + system::{lifetimeless::*, SystemState}, }; -use bevy_core_pipeline::Transparent3dPhase; -use bevy_ecs::{prelude::*, system::SystemState}; use bevy_math::{const_vec3, Mat4, Vec3, Vec4}; use bevy_render2::{ camera::CameraProjection, color::Color, mesh::Mesh, render_asset::RenderAssets, + render_component::DynamicUniformIndex, render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, - render_phase::{Draw, DrawFunctions, RenderPhase, TrackedRenderPass}, + render_phase::{ + Draw, DrawFunctionId, DrawFunctions, PhaseItem, RenderPhase, TrackedRenderPass, + }, render_resource::*, - renderer::{RenderContext, RenderDevice}, + renderer::{RenderContext, RenderDevice, RenderQueue}, shader::Shader, texture::*, - view::{ExtractedView, ViewUniformOffset}, + view::{ExtractedView, ViewUniformOffset, ViewUniforms}, }; use bevy_transform::components::GlobalTransform; use crevice::std140::AsStd140; @@ -374,8 +382,9 @@ pub fn prepare_lights( mut commands: Commands, mut texture_cache: ResMut, render_device: Res, + render_queue: Res, mut light_meta: ResMut, - views: Query>>, + views: Query>>, ambient_light: Res, point_light_shadow_map: Res, directional_light_shadow_map: Res, @@ -477,7 +486,7 @@ pub fn prepare_lights( transform: view_translation * view_rotation, projection, }, - RenderPhase::::default(), + RenderPhase::::default(), )) .id(); view_lights.push(view_light_entity); @@ -563,7 +572,7 @@ pub fn prepare_lights( transform: GlobalTransform::from_matrix(view.inverse()), projection, }, - RenderPhase::::default(), + RenderPhase::::default(), )) .id(); view_lights.push(view_light_entity); @@ -604,16 +613,77 @@ pub fn prepare_lights( }); } - light_meta - .view_gpu_lights - .write_to_staging_buffer(&render_device); + light_meta.view_gpu_lights.write_buffer(&render_queue); +} + +pub fn queue_shadow_view_bind_group( + render_device: Res, + shadow_shaders: Res, + mut light_meta: ResMut, + view_uniforms: Res, +) { + if let Some(view_binding) = view_uniforms.uniforms.binding() { + light_meta.shadow_view_bind_group = + Some(render_device.create_bind_group(&BindGroupDescriptor { + entries: &[BindGroupEntry { + binding: 0, + resource: view_binding, + }], + label: None, + layout: &shadow_shaders.view_layout, + })); + } +} + +pub fn queue_shadows( + shadow_draw_functions: Res>, + casting_meshes: Query>, Without)>, + mut view_lights: Query<&ViewLights>, + mut view_light_shadow_phases: Query<&mut RenderPhase>, +) { + for view_lights in view_lights.iter_mut() { + // ultimately lights should check meshes for relevancy (ex: light views can "see" different meshes than the main view can) + let draw_shadow_mesh = shadow_draw_functions + .read() + .get_id::() + .unwrap(); + for view_light_entity in view_lights.lights.iter().copied() { + let mut shadow_phase = view_light_shadow_phases.get_mut(view_light_entity).unwrap(); + // TODO: this should only queue up meshes that are actually visible by each "light view" + for entity in casting_meshes.iter() { + shadow_phase.add(Shadow { + draw_function: draw_shadow_mesh, + entity, + distance: 0.0, // TODO: sort back-to-front + }) + } + } + } +} + +pub struct Shadow { + pub distance: f32, + pub entity: Entity, + pub draw_function: DrawFunctionId, } -pub struct ShadowPhase; +impl PhaseItem for Shadow { + type SortKey = FloatOrd; + + #[inline] + fn sort_key(&self) -> Self::SortKey { + FloatOrd(self.distance) + } + + #[inline] + fn draw_function(&self) -> DrawFunctionId { + self.draw_function + } +} pub struct ShadowPassNode { main_view_query: QueryState<&'static ViewLights>, - view_light_query: QueryState<(&'static ViewLight, &'static RenderPhase)>, + view_light_query: QueryState<(&'static ViewLight, &'static RenderPhase)>, } impl ShadowPassNode { @@ -663,22 +733,16 @@ impl Node for ShadowPassNode { }), }; - let draw_functions = world.get_resource::().unwrap(); + let draw_functions = world.get_resource::>().unwrap(); let render_pass = render_context .command_encoder .begin_render_pass(&pass_descriptor); let mut draw_functions = draw_functions.write(); let mut tracked_pass = TrackedRenderPass::new(render_pass); - for drawable in shadow_phase.drawn_things.iter() { - let draw_function = draw_functions.get_mut(drawable.draw_function).unwrap(); - draw_function.draw( - world, - &mut tracked_pass, - view_light_entity, - drawable.draw_key, - drawable.sort_key, - ); + for item in shadow_phase.items.iter() { + let draw_function = draw_functions.get_mut(item.draw_function).unwrap(); + draw_function.draw(world, &mut tracked_pass, view_light_entity, item); } } } @@ -687,16 +751,15 @@ impl Node for ShadowPassNode { } } -type DrawShadowMeshParams<'s, 'w> = ( - Res<'w, ShadowShaders>, - Res<'w, ExtractedMeshes>, - Res<'w, LightMeta>, - Res<'w, MeshMeta>, - Res<'w, RenderAssets>, - Query<'w, 's, &'w ViewUniformOffset>, -); pub struct DrawShadowMesh { - params: SystemState>, + params: SystemState<( + SRes, + SRes, + SRes, + SRes>, + SQuery<(Read>, Read>)>, + SQuery>, + )>, } impl DrawShadowMesh { @@ -707,21 +770,19 @@ impl DrawShadowMesh { } } -impl Draw for DrawShadowMesh { +impl Draw for DrawShadowMesh { fn draw<'w>( &mut self, world: &'w World, pass: &mut TrackedRenderPass<'w>, view: Entity, - draw_key: usize, - _sort_key: usize, + item: &Shadow, ) { - let (shadow_shaders, extracted_meshes, light_meta, mesh_meta, meshes, views) = + let (shadow_shaders, light_meta, transform_bind_group, meshes, items, views) = self.params.get(world); + let (transform_index, mesh_handle) = items.get(item.entity).unwrap(); let view_uniform_offset = views.get(view).unwrap(); - let extracted_mesh = &extracted_meshes.into_inner().meshes[draw_key]; - let shadow_shaders = shadow_shaders.into_inner(); - pass.set_render_pipeline(&shadow_shaders.pipeline); + pass.set_render_pipeline(&shadow_shaders.into_inner().pipeline); pass.set_bind_group( 0, light_meta @@ -732,18 +793,13 @@ impl Draw for DrawShadowMesh { &[view_uniform_offset.offset], ); - let transform_bindgroup_key = mesh_meta.mesh_transform_bind_group_key.unwrap(); pass.set_bind_group( 1, - mesh_meta - .into_inner() - .mesh_transform_bind_group - .get_value(transform_bindgroup_key) - .unwrap(), - &[extracted_mesh.transform_binding_offset], + &transform_bind_group.into_inner().value, + &[transform_index.index()], ); - let gpu_mesh = meshes.into_inner().get(&extracted_mesh.mesh).unwrap(); + let gpu_mesh = meshes.into_inner().get(mesh_handle).unwrap(); pass.set_vertex_buffer(0, gpu_mesh.vertex_buffer.slice(..)); if let Some(index_info) = &gpu_mesh.index_info { pass.set_index_buffer(index_info.buffer.slice(..), 0, IndexFormat::Uint32); diff --git a/pipelined/bevy_pbr2/src/render/mod.rs b/pipelined/bevy_pbr2/src/render/mod.rs index 47d75c5ad6e15..19354e0cd3fc4 100644 --- a/pipelined/bevy_pbr2/src/render/mod.rs +++ b/pipelined/bevy_pbr2/src/render/mod.rs @@ -1,37 +1,127 @@ mod light; + pub use light::*; use crate::{NotShadowCaster, NotShadowReceiver, StandardMaterial, StandardMaterialUniformData}; -use bevy_asset::{Assets, Handle}; -use bevy_core_pipeline::Transparent3dPhase; -use bevy_ecs::{prelude::*, system::SystemState}; +use bevy_asset::Handle; +use bevy_core_pipeline::Transparent3d; +use bevy_ecs::{ + prelude::*, + system::{lifetimeless::*, SystemParamItem}, +}; use bevy_math::Mat4; use bevy_render2::{ mesh::Mesh, render_asset::RenderAssets, - render_graph::{Node, NodeRunError, RenderGraphContext}, - render_phase::{Draw, DrawFunctions, Drawable, RenderPhase, TrackedRenderPass}, + render_component::{ComponentUniforms, DynamicUniformIndex}, + render_phase::{DrawFunctions, RenderCommand, RenderPhase, TrackedRenderPass}, render_resource::*, - renderer::{RenderContext, RenderDevice, RenderQueue}, + renderer::{RenderDevice, RenderQueue}, shader::Shader, texture::{BevyDefault, GpuImage, Image, TextureFormatPixelInfo}, - view::{ExtractedView, ViewMeta, ViewUniformOffset}, + view::{ExtractedView, ViewUniformOffset, ViewUniforms}, }; use bevy_transform::components::GlobalTransform; -use bevy_utils::slab::{FrameSlabMap, FrameSlabMapKey}; use crevice::std140::AsStd140; use wgpu::{ Extent3d, ImageCopyTexture, ImageDataLayout, Origin3d, TextureDimension, TextureFormat, TextureViewDescriptor, }; +#[derive(AsStd140, Clone)] +pub struct MeshUniform { + pub transform: Mat4, + pub inverse_transpose_model: Mat4, + pub flags: u32, +} + +// NOTE: These must match the bit flags in bevy_pbr2/src/render/pbr.wgsl! +bitflags::bitflags! { + #[repr(transparent)] + struct MeshFlags: u32 { + const SHADOW_RECEIVER = (1 << 0); + const NONE = 0; + const UNINITIALIZED = 0xFFFF; + } +} + +pub fn extract_meshes( + mut commands: Commands, + mut previous_caster_len: Local, + mut previous_not_caster_len: Local, + caster_query: Query< + ( + Entity, + &GlobalTransform, + &Handle, + Option<&NotShadowReceiver>, + ), + Without, + >, + not_caster_query: Query< + ( + Entity, + &GlobalTransform, + &Handle, + Option<&NotShadowReceiver>, + ), + With, + >, +) { + let mut caster_values = Vec::with_capacity(*previous_caster_len); + for (entity, transform, handle, not_receiver) in caster_query.iter() { + let transform = transform.compute_matrix(); + caster_values.push(( + entity, + ( + handle.clone_weak(), + MeshUniform { + flags: if not_receiver.is_some() { + MeshFlags::empty().bits + } else { + MeshFlags::SHADOW_RECEIVER.bits + }, + transform, + inverse_transpose_model: transform.inverse().transpose(), + }, + ), + )); + } + *previous_caster_len = caster_values.len(); + commands.insert_or_spawn_batch(caster_values); + + let mut not_caster_values = Vec::with_capacity(*previous_not_caster_len); + for (entity, transform, handle, not_receiver) in not_caster_query.iter() { + let transform = transform.compute_matrix(); + not_caster_values.push(( + entity, + ( + handle.clone_weak(), + MeshUniform { + flags: if not_receiver.is_some() { + MeshFlags::empty().bits + } else { + MeshFlags::SHADOW_RECEIVER.bits + }, + transform, + inverse_transpose_model: transform.inverse().transpose(), + }, + NotShadowCaster, + ), + )); + } + *previous_not_caster_len = not_caster_values.len(); + commands.insert_or_spawn_batch(not_caster_values); +} + pub struct PbrShaders { - pipeline: RenderPipeline, - view_layout: BindGroupLayout, - material_layout: BindGroupLayout, - mesh_layout: BindGroupLayout, + pub pipeline: RenderPipeline, + pub shader_module: ShaderModule, + pub view_layout: BindGroupLayout, + pub material_layout: BindGroupLayout, + pub mesh_layout: BindGroupLayout, // This dummy white texture is to be used in place of optional StandardMaterial textures - dummy_white_gpu_image: GpuImage, + pub dummy_white_gpu_image: GpuImage, } // TODO: this pattern for initializing the shaders / pipeline isn't ideal. this should be handled by the asset system @@ -41,7 +131,6 @@ impl FromWorld for PbrShaders { let shader = Shader::from_wgsl(include_str!("pbr.wgsl")); let shader_module = render_device.create_shader_module(&shader); - // TODO: move this into ViewMeta? let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { entries: &[ // View @@ -361,6 +450,7 @@ impl FromWorld for PbrShaders { }; PbrShaders { pipeline, + shader_module, view_layout, material_layout, mesh_layout, @@ -369,476 +459,227 @@ impl FromWorld for PbrShaders { } } -struct ExtractedMesh { - transform: Mat4, - mesh: Handle, - transform_binding_offset: u32, - material_handle: Handle, - casts_shadows: bool, - receives_shadows: bool, -} - -pub struct ExtractedMeshes { - meshes: Vec, +pub struct TransformBindGroup { + pub value: BindGroup, } -pub fn extract_meshes( +pub fn queue_transform_bind_group( mut commands: Commands, - meshes: Res>, - materials: Res>, - images: Res>, - query: Query<( - &GlobalTransform, - &Handle, - &Handle, - Option<&NotShadowCaster>, - Option<&NotShadowReceiver>, - )>, -) { - let mut extracted_meshes = Vec::new(); - for ( - transform, - mesh_handle, - material_handle, - maybe_not_shadow_caster, - maybe_not_shadow_receiver, - ) in query.iter() - { - if !meshes.contains(mesh_handle) { - continue; - } - - if let Some(material) = materials.get(material_handle) { - if let Some(ref image) = material.base_color_texture { - if !images.contains(image) { - continue; - } - } - if let Some(ref image) = material.emissive_texture { - if !images.contains(image) { - continue; - } - } - if let Some(ref image) = material.metallic_roughness_texture { - if !images.contains(image) { - continue; - } - } - if let Some(ref image) = material.occlusion_texture { - if !images.contains(image) { - continue; - } - } - extracted_meshes.push(ExtractedMesh { - transform: transform.compute_matrix(), - mesh: mesh_handle.clone_weak(), - transform_binding_offset: 0, - material_handle: material_handle.clone_weak(), - // NOTE: Double-negative is so that meshes cast and receive shadows by default - // Not not shadow caster means that this mesh is a shadow caster - casts_shadows: maybe_not_shadow_caster.is_none(), - receives_shadows: maybe_not_shadow_receiver.is_none(), - }); - } else { - continue; - } - } - - commands.insert_resource(ExtractedMeshes { - meshes: extracted_meshes, - }); -} - -struct MeshDrawInfo { - // TODO: compare cost of doing this vs cloning the BindGroup? - material_bind_group_key: FrameSlabMapKey, -} - -#[derive(Debug, AsStd140)] -pub struct MeshUniform { - model: Mat4, - inverse_transpose_model: Mat4, - flags: u32, -} - -// NOTE: These must match the bit flags in bevy_pbr2/src/render/pbr.wgsl! -bitflags::bitflags! { - #[repr(transparent)] - struct MeshFlags: u32 { - const SHADOW_RECEIVER = (1 << 0); - const NONE = 0; - const UNINITIALIZED = 0xFFFF; - } -} - -#[derive(Default)] -pub struct MeshMeta { - transform_uniforms: DynamicUniformVec, - material_bind_groups: FrameSlabMap, - mesh_transform_bind_group: FrameSlabMap, - mesh_transform_bind_group_key: Option>, - mesh_draw_info: Vec, -} - -pub fn prepare_meshes( + pbr_shaders: Res, render_device: Res, - mut mesh_meta: ResMut, - mut extracted_meshes: ResMut, + transform_uniforms: Res>, ) { - mesh_meta - .transform_uniforms - .reserve_and_clear(extracted_meshes.meshes.len(), &render_device); - for extracted_mesh in extracted_meshes.meshes.iter_mut() { - let model = extracted_mesh.transform; - let inverse_transpose_model = model.inverse().transpose(); - let flags = if extracted_mesh.receives_shadows { - MeshFlags::SHADOW_RECEIVER - } else { - MeshFlags::NONE - }; - extracted_mesh.transform_binding_offset = mesh_meta.transform_uniforms.push(MeshUniform { - model, - inverse_transpose_model, - flags: flags.bits, + if let Some(binding) = transform_uniforms.uniforms().binding() { + commands.insert_resource(TransformBindGroup { + value: render_device.create_bind_group(&BindGroupDescriptor { + entries: &[BindGroupEntry { + binding: 0, + resource: binding, + }], + label: None, + layout: &pbr_shaders.mesh_layout, + }), }); } - - mesh_meta - .transform_uniforms - .write_to_staging_buffer(&render_device); } -pub struct MeshViewBindGroups { - view: BindGroup, -} - -fn image_handle_to_view_sampler<'a>( - pbr_shaders: &'a PbrShaders, - gpu_images: &'a RenderAssets, - image_option: &Option>, -) -> (&'a TextureView, &'a Sampler) { - image_option.as_ref().map_or( - ( - &pbr_shaders.dummy_white_gpu_image.texture_view, - &pbr_shaders.dummy_white_gpu_image.sampler, - ), - |image_handle| { - let gpu_image = gpu_images - .get(image_handle) - .expect("only materials with valid textures should be drawn"); - (&gpu_image.texture_view, &gpu_image.sampler) - }, - ) +pub struct PbrViewBindGroup { + pub value: BindGroup, } #[allow(clippy::too_many_arguments)] pub fn queue_meshes( mut commands: Commands, - draw_functions: Res, + transparent_3d_draw_functions: Res>, render_device: Res, pbr_shaders: Res, shadow_shaders: Res, - mesh_meta: ResMut, - mut light_meta: ResMut, - view_meta: Res, - mut extracted_meshes: ResMut, - gpu_images: Res>, + light_meta: Res, + view_uniforms: Res, render_materials: Res>, + standard_material_meshes: Query< + (Entity, &Handle, &MeshUniform), + With>, + >, mut views: Query<( Entity, &ExtractedView, &ViewLights, - &mut RenderPhase, + &mut RenderPhase, )>, - mut view_light_shadow_phases: Query<&mut RenderPhase>, ) { - let mesh_meta = mesh_meta.into_inner(); - - if view_meta.uniforms.is_empty() { - return; - } - - light_meta.shadow_view_bind_group.get_or_insert_with(|| { - render_device.create_bind_group(&BindGroupDescriptor { - entries: &[BindGroupEntry { - binding: 0, - resource: view_meta.uniforms.binding(), - }], - label: None, - layout: &shadow_shaders.view_layout, - }) - }); - if extracted_meshes.meshes.is_empty() { - return; - } - - let transform_uniforms = &mesh_meta.transform_uniforms; - mesh_meta.mesh_transform_bind_group.next_frame(); - mesh_meta.mesh_transform_bind_group_key = - Some(mesh_meta.mesh_transform_bind_group.get_or_insert_with( - transform_uniforms.uniform_buffer().unwrap().id(), - || { - render_device.create_bind_group(&BindGroupDescriptor { - entries: &[BindGroupEntry { + if let (Some(view_binding), Some(light_binding)) = ( + view_uniforms.uniforms.binding(), + light_meta.view_gpu_lights.binding(), + ) { + for (entity, view, view_lights, mut transparent_phase) in views.iter_mut() { + let view_bind_group = render_device.create_bind_group(&BindGroupDescriptor { + entries: &[ + BindGroupEntry { binding: 0, - resource: transform_uniforms.binding(), - }], - label: None, - layout: &pbr_shaders.mesh_layout, - }) - }, - )); - for (entity, view, view_lights, mut transparent_phase) in views.iter_mut() { - // TODO: cache this? - let view_bind_group = render_device.create_bind_group(&BindGroupDescriptor { - entries: &[ - BindGroupEntry { - binding: 0, - resource: view_meta.uniforms.binding(), - }, - BindGroupEntry { - binding: 1, - resource: light_meta.view_gpu_lights.binding(), - }, - BindGroupEntry { - binding: 2, - resource: BindingResource::TextureView( - &view_lights.point_light_depth_texture_view, - ), - }, - BindGroupEntry { - binding: 3, - resource: BindingResource::Sampler(&shadow_shaders.point_light_sampler), - }, - BindGroupEntry { - binding: 4, - resource: BindingResource::TextureView( - &view_lights.directional_light_depth_texture_view, - ), - }, - BindGroupEntry { - binding: 5, - resource: BindingResource::Sampler(&shadow_shaders.directional_light_sampler), - }, - ], - label: None, - layout: &pbr_shaders.view_layout, - }); - - commands.entity(entity).insert(MeshViewBindGroups { - view: view_bind_group, - }); - - let draw_pbr = draw_functions.read().get_id::().unwrap(); - mesh_meta.mesh_draw_info.clear(); - mesh_meta.material_bind_groups.next_frame(); - - let view_matrix = view.transform.compute_matrix(); - let view_row_2 = view_matrix.row(2); - for (i, mesh) in extracted_meshes.meshes.iter_mut().enumerate() { - let gpu_material = &render_materials - .get(&mesh.material_handle) - .expect("Failed to get StandardMaterial PreparedAsset"); - let material_bind_group_key = - mesh_meta - .material_bind_groups - .get_or_insert_with(gpu_material.buffer.id(), || { - let (base_color_texture_view, base_color_sampler) = - image_handle_to_view_sampler( - &pbr_shaders, - &gpu_images, - &gpu_material.base_color_texture, - ); - - let (emissive_texture_view, emissive_sampler) = - image_handle_to_view_sampler( - &pbr_shaders, - &gpu_images, - &gpu_material.emissive_texture, - ); - - let (metallic_roughness_texture_view, metallic_roughness_sampler) = - image_handle_to_view_sampler( - &pbr_shaders, - &gpu_images, - &gpu_material.metallic_roughness_texture, - ); - let (occlusion_texture_view, occlusion_sampler) = - image_handle_to_view_sampler( - &pbr_shaders, - &gpu_images, - &gpu_material.occlusion_texture, - ); - render_device.create_bind_group(&BindGroupDescriptor { - entries: &[ - BindGroupEntry { - binding: 0, - resource: gpu_material.buffer.as_entire_binding(), - }, - BindGroupEntry { - binding: 1, - resource: BindingResource::TextureView(base_color_texture_view), - }, - BindGroupEntry { - binding: 2, - resource: BindingResource::Sampler(base_color_sampler), - }, - BindGroupEntry { - binding: 3, - resource: BindingResource::TextureView(emissive_texture_view), - }, - BindGroupEntry { - binding: 4, - resource: BindingResource::Sampler(emissive_sampler), - }, - BindGroupEntry { - binding: 5, - resource: BindingResource::TextureView( - metallic_roughness_texture_view, - ), - }, - BindGroupEntry { - binding: 6, - resource: BindingResource::Sampler(metallic_roughness_sampler), - }, - BindGroupEntry { - binding: 7, - resource: BindingResource::TextureView(occlusion_texture_view), - }, - BindGroupEntry { - binding: 8, - resource: BindingResource::Sampler(occlusion_sampler), - }, - ], - label: None, - layout: &pbr_shaders.material_layout, - }) - }); - - mesh_meta.mesh_draw_info.push(MeshDrawInfo { - material_bind_group_key, + resource: view_binding.clone(), + }, + BindGroupEntry { + binding: 1, + resource: light_binding.clone(), + }, + BindGroupEntry { + binding: 2, + resource: BindingResource::TextureView( + &view_lights.point_light_depth_texture_view, + ), + }, + BindGroupEntry { + binding: 3, + resource: BindingResource::Sampler(&shadow_shaders.point_light_sampler), + }, + BindGroupEntry { + binding: 4, + resource: BindingResource::TextureView( + &view_lights.directional_light_depth_texture_view, + ), + }, + BindGroupEntry { + binding: 5, + resource: BindingResource::Sampler( + &shadow_shaders.directional_light_sampler, + ), + }, + ], + label: None, + layout: &pbr_shaders.view_layout, }); - // NOTE: row 2 of the view matrix dotted with column 3 of the model matrix - // gives the z component of translation of the mesh in view space - let mesh_z = view_row_2.dot(mesh.transform.col(3)); - // FIXME: Switch from usize to u64 for portability and use sort key encoding - // similar to https://realtimecollisiondetection.net/blog/?p=86 as appropriate - // FIXME: What is the best way to map from view space z to a number of bits of unsigned integer? - let sort_key = (((mesh_z * 1000.0) as usize) << 10) - | (material_bind_group_key.index() & ((1 << 10) - 1)); - // TODO: currently there is only "transparent phase". this should pick transparent vs opaque according to the mesh material - transparent_phase.add(Drawable { - draw_function: draw_pbr, - draw_key: i, - sort_key, + commands.entity(entity).insert(PbrViewBindGroup { + value: view_bind_group, }); - } - // ultimately lights should check meshes for relevancy (ex: light views can "see" different meshes than the main view can) - let draw_shadow_mesh = draw_functions.read().get_id::().unwrap(); - for view_light_entity in view_lights.lights.iter().copied() { - let mut shadow_phase = view_light_shadow_phases.get_mut(view_light_entity).unwrap(); - // TODO: this should only queue up meshes that are actually visible by each "light view" - for (i, mesh) in extracted_meshes.meshes.iter().enumerate() { - if mesh.casts_shadows { - shadow_phase.add(Drawable { - draw_function: draw_shadow_mesh, - draw_key: i, - sort_key: 0, // TODO: sort back-to-front - }); + let draw_pbr = transparent_3d_draw_functions + .read() + .get_id::() + .unwrap(); + + let view_matrix = view.transform.compute_matrix(); + let view_row_2 = view_matrix.row(2); + + for (entity, material_handle, mesh_uniform) in standard_material_meshes.iter() { + if !render_materials.contains_key(material_handle) { + continue; } + // NOTE: row 2 of the view matrix dotted with column 3 of the model matrix + // gives the z component of translation of the mesh in view space + let mesh_z = view_row_2.dot(mesh_uniform.transform.col(3)); + // TODO: currently there is only "transparent phase". this should pick transparent vs opaque according to the mesh material + transparent_phase.add(Transparent3d { + entity, + draw_function: draw_pbr, + distance: mesh_z, + }); } } } } -// TODO: this logic can be moved to prepare_meshes once wgpu::Queue is exposed directly -pub struct PbrNode; - -impl Node for PbrNode { - fn run( - &self, - _graph: &mut RenderGraphContext, - render_context: &mut RenderContext, - world: &World, - ) -> Result<(), NodeRunError> { - let mesh_meta = world.get_resource::().unwrap(); - let light_meta = world.get_resource::().unwrap(); - mesh_meta - .transform_uniforms - .write_to_uniform_buffer(&mut render_context.command_encoder); - light_meta - .view_gpu_lights - .write_to_uniform_buffer(&mut render_context.command_encoder); - Ok(()) - } -} - -type DrawPbrParams<'s, 'w> = ( - Res<'w, PbrShaders>, - Res<'w, MeshMeta>, - Res<'w, ExtractedMeshes>, - Res<'w, RenderAssets>, - Query< - 'w, - 's, - ( - &'w ViewUniformOffset, - &'w ViewLights, - &'w MeshViewBindGroups, - ), - >, +pub type DrawPbr = ( + SetPbrPipeline, + SetMeshViewBindGroup<0>, + SetStandardMaterialBindGroup<1>, + SetTransformBindGroup<2>, + DrawMesh, ); -pub struct DrawPbr { - params: SystemState>, -} - -impl DrawPbr { - pub fn new(world: &mut World) -> Self { - Self { - params: SystemState::new(world), - } +pub struct SetPbrPipeline; +impl RenderCommand for SetPbrPipeline { + type Param = SRes; + #[inline] + fn render<'w>( + _view: Entity, + _item: &Transparent3d, + pbr_shaders: SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, + ) { + pass.set_render_pipeline(&pbr_shaders.into_inner().pipeline); } } -impl Draw for DrawPbr { - fn draw<'w, 's>( - &'s mut self, - world: &'w World, - pass: &mut TrackedRenderPass<'w>, +pub struct SetMeshViewBindGroup; +impl RenderCommand for SetMeshViewBindGroup { + type Param = SQuery<( + Read, + Read, + Read, + )>; + #[inline] + fn render<'w>( view: Entity, - draw_key: usize, - _sort_key: usize, + _item: &Transparent3d, + view_query: SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, ) { - let (pbr_shaders, mesh_meta, extracted_meshes, meshes, views) = self.params.get(world); - let (view_uniforms, view_lights, mesh_view_bind_groups) = views.get(view).unwrap(); - let extracted_mesh = &extracted_meshes.into_inner().meshes[draw_key]; - let mesh_meta = mesh_meta.into_inner(); - pass.set_render_pipeline(&pbr_shaders.into_inner().pipeline); - pass.set_bind_group( - 0, - &mesh_view_bind_groups.view, - &[view_uniforms.offset, view_lights.gpu_light_binding_index], - ); - let mesh_draw_info = &mesh_meta.mesh_draw_info[draw_key]; + let (view_uniform, view_lights, pbr_view_bind_group) = view_query.get(view).unwrap(); pass.set_bind_group( - 1, - // &mesh_meta.material_bind_groups[sort_key & ((1 << 10) - 1)], - &mesh_meta.material_bind_groups[mesh_draw_info.material_bind_group_key], - &[], + I, + &pbr_view_bind_group.value, + &[view_uniform.offset, view_lights.gpu_light_binding_index], ); + } +} + +pub struct SetTransformBindGroup; +impl RenderCommand for SetTransformBindGroup { + type Param = ( + SRes, + SQuery>>, + ); + #[inline] + fn render<'w>( + _view: Entity, + item: &Transparent3d, + (transform_bind_group, mesh_query): SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, + ) { + let transform_index = mesh_query.get(item.entity).unwrap(); pass.set_bind_group( - 2, - mesh_meta - .mesh_transform_bind_group - .get_value(mesh_meta.mesh_transform_bind_group_key.unwrap()) - .unwrap(), - &[extracted_mesh.transform_binding_offset], + I, + &transform_bind_group.into_inner().value, + &[transform_index.index()], ); + } +} + +pub struct SetStandardMaterialBindGroup; +impl RenderCommand for SetStandardMaterialBindGroup { + type Param = ( + SRes>, + SQuery>>, + ); + #[inline] + fn render<'w>( + _view: Entity, + item: &Transparent3d, + (materials, handle_query): SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, + ) { + let handle = handle_query.get(item.entity).unwrap(); + let materials = materials.into_inner(); + let material = materials.get(handle).unwrap(); - let gpu_mesh = meshes.into_inner().get(&extracted_mesh.mesh).unwrap(); + pass.set_bind_group(I, &material.bind_group, &[]); + } +} + +pub struct DrawMesh; +impl RenderCommand for DrawMesh { + type Param = (SRes>, SQuery>>); + #[inline] + fn render<'w>( + _view: Entity, + item: &Transparent3d, + (meshes, mesh_query): SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, + ) { + let mesh_handle = mesh_query.get(item.entity).unwrap(); + let gpu_mesh = meshes.into_inner().get(mesh_handle).unwrap(); pass.set_vertex_buffer(0, gpu_mesh.vertex_buffer.slice(..)); if let Some(index_info) = &gpu_mesh.index_info { pass.set_index_buffer(index_info.buffer.slice(..), 0, IndexFormat::Uint32); diff --git a/pipelined/bevy_pbr2/src/render/pbr.wgsl b/pipelined/bevy_pbr2/src/render/pbr.wgsl index 5532a5bb1fde3..1528cca4d2a96 100644 --- a/pipelined/bevy_pbr2/src/render/pbr.wgsl +++ b/pipelined/bevy_pbr2/src/render/pbr.wgsl @@ -1,4 +1,3 @@ -// TODO: try merging this block with the binding? // NOTE: Keep in sync with depth.wgsl [[block]] struct View { diff --git a/pipelined/bevy_render2/src/lib.rs b/pipelined/bevy_render2/src/lib.rs index b4f8467fce75c..6749303f323da 100644 --- a/pipelined/bevy_render2/src/lib.rs +++ b/pipelined/bevy_render2/src/lib.rs @@ -2,6 +2,7 @@ pub mod camera; pub mod color; pub mod mesh; pub mod render_asset; +pub mod render_component; pub mod render_graph; pub mod render_phase; pub mod render_resource; @@ -10,22 +11,21 @@ pub mod shader; pub mod texture; pub mod view; -use std::ops::{Deref, DerefMut}; - pub use once_cell; -use wgpu::BackendBit; use crate::{ camera::CameraPlugin, mesh::MeshPlugin, render_graph::RenderGraph, - render_phase::DrawFunctions, renderer::render_system, texture::ImagePlugin, view::{ViewPlugin, WindowRenderPlugin}, }; use bevy_app::{App, AppLabel, Plugin}; +use bevy_asset::AssetServer; use bevy_ecs::prelude::*; +use std::ops::{Deref, DerefMut}; +use wgpu::BackendBit; #[derive(Default)] pub struct RenderPlugin; @@ -96,6 +96,7 @@ impl Plugin for RenderPlugin { app.insert_resource(device.clone()) .insert_resource(queue.clone()) .init_resource::(); + let asset_server = app.world.get_resource::().unwrap().clone(); let mut render_app = App::empty(); let mut extract_stage = SystemStage::parallel(); @@ -115,8 +116,8 @@ impl Plugin for RenderPlugin { .insert_resource(instance) .insert_resource(device) .insert_resource(queue) - .init_resource::() - .init_resource::(); + .insert_resource(asset_server) + .init_resource::(); app.add_sub_app(RenderApp, render_app, move |app_world, render_app| { // reserve all existing app entities for use in render_app diff --git a/pipelined/bevy_render2/src/mesh/mesh/mod.rs b/pipelined/bevy_render2/src/mesh/mesh/mod.rs index 18e22ab08d166..4c2caa9c4d3ac 100644 --- a/pipelined/bevy_render2/src/mesh/mesh/mod.rs +++ b/pipelined/bevy_render2/src/mesh/mesh/mod.rs @@ -1,11 +1,12 @@ mod conversions; use crate::{ - render_asset::RenderAsset, + render_asset::{PrepareAssetError, RenderAsset}, render_resource::Buffer, - renderer::{RenderDevice, RenderQueue}, + renderer::RenderDevice, }; use bevy_core::cast_slice; +use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem}; use bevy_math::*; use bevy_reflect::TypeUuid; use bevy_utils::EnumVariantMeta; @@ -540,6 +541,7 @@ pub struct GpuIndexInfo { impl RenderAsset for Mesh { type ExtractedAsset = Mesh; type PreparedAsset = GpuMesh; + type Param = SRes; fn extract_asset(&self) -> Self::ExtractedAsset { self.clone() @@ -547,9 +549,8 @@ impl RenderAsset for Mesh { fn prepare_asset( mesh: Self::ExtractedAsset, - render_device: &RenderDevice, - _render_queue: &RenderQueue, - ) -> Self::PreparedAsset { + render_device: &mut SystemParamItem, + ) -> Result> { let vertex_buffer_data = mesh.get_vertex_buffer_data(); let vertex_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor { usage: BufferUsage::VERTEX, @@ -566,9 +567,9 @@ impl RenderAsset for Mesh { count: mesh.indices().unwrap().len() as u32, }); - GpuMesh { + Ok(GpuMesh { vertex_buffer, index_info, - } + }) } } diff --git a/pipelined/bevy_render2/src/render_asset.rs b/pipelined/bevy_render2/src/render_asset.rs index 4185c93cc09c2..93e4297215e1d 100644 --- a/pipelined/bevy_render2/src/render_asset.rs +++ b/pipelined/bevy_render2/src/render_asset.rs @@ -1,23 +1,26 @@ -use std::marker::PhantomData; - -use crate::{ - renderer::{RenderDevice, RenderQueue}, - RenderApp, RenderStage, -}; +use crate::{RenderApp, RenderStage}; use bevy_app::{App, Plugin}; use bevy_asset::{Asset, AssetEvent, Assets, Handle}; -use bevy_ecs::prelude::*; +use bevy_ecs::{ + prelude::*, + system::{lifetimeless::*, RunSystem, SystemParam, SystemParamItem}, +}; use bevy_utils::{HashMap, HashSet}; +use std::marker::PhantomData; + +pub enum PrepareAssetError { + RetryNextUpdate(E), +} pub trait RenderAsset: Asset { type ExtractedAsset: Send + Sync + 'static; type PreparedAsset: Send + Sync + 'static; + type Param: SystemParam; fn extract_asset(&self) -> Self::ExtractedAsset; fn prepare_asset( extracted_asset: Self::ExtractedAsset, - render_device: &RenderDevice, - render_queue: &RenderQueue, - ) -> Self::PreparedAsset; + param: &mut SystemParamItem, + ) -> Result>; } /// Extracts assets into gpu-usable data @@ -31,15 +34,18 @@ impl Default for RenderAssetPlugin { impl Plugin for RenderAssetPlugin { fn build(&self, app: &mut App) { - app.sub_app(RenderApp) + let render_app = app.sub_app(RenderApp); + let prepare_asset_system = PrepareAssetSystem::::system(&mut render_app.world); + render_app .init_resource::>() .init_resource::>() + .init_resource::>() .add_system_to_stage(RenderStage::Extract, extract_render_asset::) - .add_system_to_stage(RenderStage::Prepare, prepare_render_asset::); + .add_system_to_stage(RenderStage::Prepare, prepare_asset_system); } } -struct ExtractedAssets { +pub struct ExtractedAssets { extracted: Vec<(Handle, A::ExtractedAsset)>, removed: Vec>, } @@ -91,18 +97,58 @@ fn extract_render_asset( }) } -fn prepare_render_asset( - mut extracted_assets: ResMut>, - mut render_assets: ResMut>, - render_device: Res, - render_queue: Res, -) { - for removed in extracted_assets.removed.iter() { - render_assets.remove(removed); +pub type RenderAssetParams = ( + SResMut>, + SResMut>, + SResMut>, + ::Param, +); + +// TODO: consider storing inside system? +pub struct PrepareNextFrameAssets { + assets: Vec<(Handle, A::ExtractedAsset)>, +} + +impl Default for PrepareNextFrameAssets { + fn default() -> Self { + Self { + assets: Default::default(), + } } +} + +pub struct PrepareAssetSystem(PhantomData); + +impl RunSystem for PrepareAssetSystem { + type Param = RenderAssetParams; + fn run( + (mut extracted_assets, mut render_assets, mut prepare_next_frame, mut param): SystemParamItem, + ) { + let mut queued_assets = std::mem::take(&mut prepare_next_frame.assets); + for (handle, extracted_asset) in queued_assets.drain(..) { + match R::prepare_asset(extracted_asset, &mut param) { + Ok(prepared_asset) => { + render_assets.insert(handle, prepared_asset); + } + Err(PrepareAssetError::RetryNextUpdate(extracted_asset)) => { + prepare_next_frame.assets.push((handle, extracted_asset)); + } + } + } - for (handle, extracted_asset) in extracted_assets.extracted.drain(..) { - let prepared_asset = R::prepare_asset(extracted_asset, &render_device, &render_queue); - render_assets.insert(handle, prepared_asset); + for removed in std::mem::take(&mut extracted_assets.removed) { + render_assets.remove(&removed); + } + + for (handle, extracted_asset) in std::mem::take(&mut extracted_assets.extracted) { + match R::prepare_asset(extracted_asset, &mut param) { + Ok(prepared_asset) => { + render_assets.insert(handle, prepared_asset); + } + Err(PrepareAssetError::RetryNextUpdate(extracted_asset)) => { + prepare_next_frame.assets.push((handle, extracted_asset)); + } + } + } } } diff --git a/pipelined/bevy_render2/src/render_component.rs b/pipelined/bevy_render2/src/render_component.rs new file mode 100644 index 0000000000000..1a81cb2fa1c9d --- /dev/null +++ b/pipelined/bevy_render2/src/render_component.rs @@ -0,0 +1,162 @@ +use crate::{ + render_resource::DynamicUniformVec, + renderer::{RenderDevice, RenderQueue}, + RenderApp, RenderStage, +}; +use bevy_app::{App, Plugin}; +use bevy_asset::{Asset, Handle}; +use bevy_ecs::{ + component::Component, + prelude::*, + query::{FilterFetch, QueryItem, ReadOnlyFetch, WorldQuery}, + system::{ + lifetimeless::{Read, SCommands, SQuery}, + RunSystem, SystemParamItem, + }, +}; +use crevice::std140::AsStd140; +use std::{marker::PhantomData, ops::Deref}; + +pub struct DynamicUniformIndex { + index: u32, + marker: PhantomData, +} + +impl DynamicUniformIndex { + #[inline] + pub fn index(&self) -> u32 { + self.index + } +} + +pub trait ExtractComponent: Component { + type Query: WorldQuery; + type Filter: WorldQuery; + fn extract_component(item: QueryItem) -> Self; +} + +/// Extracts assets into gpu-usable data +pub struct UniformComponentPlugin(PhantomData C>); + +impl Default for UniformComponentPlugin { + fn default() -> Self { + Self(PhantomData) + } +} + +impl Plugin for UniformComponentPlugin { + fn build(&self, app: &mut App) { + app.sub_app(RenderApp) + .insert_resource(ComponentUniforms::::default()) + .add_system_to_stage( + RenderStage::Prepare, + prepare_uniform_components::.system(), + ); + } +} + +pub struct ComponentUniforms { + uniforms: DynamicUniformVec, +} + +impl Deref for ComponentUniforms { + type Target = DynamicUniformVec; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.uniforms + } +} + +impl ComponentUniforms { + #[inline] + pub fn uniforms(&self) -> &DynamicUniformVec { + &self.uniforms + } +} + +impl Default for ComponentUniforms { + fn default() -> Self { + Self { + uniforms: Default::default(), + } + } +} + +fn prepare_uniform_components( + mut commands: Commands, + render_device: Res, + render_queue: Res, + mut component_uniforms: ResMut>, + components: Query<(Entity, &C)>, +) where + C: AsStd140 + Clone, +{ + let len = components.iter().len(); + component_uniforms + .uniforms + .reserve_and_clear(len, &render_device); + for (entity, component) in components.iter() { + commands + .get_or_spawn(entity) + .insert(DynamicUniformIndex:: { + index: component_uniforms.uniforms.push(component.clone()), + marker: PhantomData, + }); + } + + component_uniforms.uniforms.write_buffer(&render_queue); +} + +pub struct ExtractComponentPlugin(PhantomData (C, F)>); + +impl Default for ExtractComponentPlugin { + fn default() -> Self { + Self(PhantomData) + } +} + +impl Plugin for ExtractComponentPlugin +where + ::Fetch: ReadOnlyFetch, + ::Fetch: FilterFetch, +{ + fn build(&self, app: &mut App) { + let system = ExtractComponentSystem::::system(&mut app.world); + let render_app = app.sub_app(RenderApp); + render_app.add_system_to_stage(RenderStage::Extract, system); + } +} + +impl ExtractComponent for Handle { + type Query = Read>; + type Filter = (); + + #[inline] + fn extract_component(handle: QueryItem) -> Self { + handle.clone_weak() + } +} + +pub struct ExtractComponentSystem(PhantomData); + +impl RunSystem for ExtractComponentSystem +where + ::Fetch: FilterFetch, + ::Fetch: ReadOnlyFetch, +{ + type Param = ( + SCommands, + Local<'static, usize>, + SQuery<(Entity, C::Query), C::Filter>, + ); + + fn run((mut commands, mut previous_len, query): SystemParamItem) { + let mut values = Vec::with_capacity(*previous_len); + for (entity, query_item) in query.iter() { + values.push((entity, (C::extract_component(query_item),))); + } + *previous_len = values.len(); + commands.insert_or_spawn_batch(values); + } +} diff --git a/pipelined/bevy_render2/src/render_phase/draw.rs b/pipelined/bevy_render2/src/render_phase/draw.rs index 98df9ea12e727..02272eb89685e 100644 --- a/pipelined/bevy_render2/src/render_phase/draw.rs +++ b/pipelined/bevy_render2/src/render_phase/draw.rs @@ -1,59 +1,161 @@ use crate::render_phase::TrackedRenderPass; -use bevy_ecs::{entity::Entity, world::World}; +use bevy_app::App; +use bevy_ecs::{ + all_tuples, + entity::Entity, + system::{ReadOnlySystemParamFetch, SystemParam, SystemParamItem, SystemState}, + world::World, +}; use bevy_utils::HashMap; use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use std::{any::TypeId, fmt::Debug, hash::Hash}; -// TODO: should this be generic on "drawn thing"? would provide more flexibility and explicitness -// instead of hard coded draw key and sort key -pub trait Draw: Send + Sync + 'static { - fn draw<'w, 's>( - &'s mut self, +pub trait Draw: Send + Sync + 'static { + fn draw<'w>( + &mut self, world: &'w World, pass: &mut TrackedRenderPass<'w>, view: Entity, - draw_key: usize, - sort_key: usize, + item: &P, ); } +pub trait PhaseItem: Send + Sync + 'static { + type SortKey: Ord; + fn sort_key(&self) -> Self::SortKey; + fn draw_function(&self) -> DrawFunctionId; +} + +// TODO: make this generic? #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub struct DrawFunctionId(usize); -#[derive(Default)] -pub struct DrawFunctionsInternal { - pub draw_functions: Vec>, +pub struct DrawFunctionsInternal { + pub draw_functions: Vec>>, pub indices: HashMap, } -impl DrawFunctionsInternal { - pub fn add(&mut self, draw_function: D) -> DrawFunctionId { +impl DrawFunctionsInternal