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> = <

::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

{ + 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;