From 174819be83611bc1b238b5bf2b131db2da16d8cf Mon Sep 17 00:00:00 2001 From: Torstein Grindvik Date: Mon, 21 Nov 2022 13:19:44 +0000 Subject: [PATCH] ExtractComponent output optional associated type (#6699) # Objective Allow more use cases where the user may benefit from both `ExtractComponentPlugin` _and_ `UniformComponentPlugin`. ## Solution Add an associated type to `ExtractComponent` in order to allow specifying the output component (or bundle). Make `extract_component` return an `Option<_>` such that components can be extracted only when needed. What problem does this solve? `ExtractComponentPlugin` allows extracting components, but currently the output type is the same as the input. This means that use cases such as having a settings struct which turns into a uniform is awkward. For example we might have: ```rust struct MyStruct { enabled: bool, val: f32 } struct MyStructUniform { val: f32 } ``` With the new approach, we can extract `MyStruct` only when it is enabled, and turn it into its related uniform. This chains well with `UniformComponentPlugin`. The user may then: ```rust app.add_plugin(ExtractComponentPlugin::::default()); app.add_plugin(UniformComponentPlugin::::default()); ``` This then saves the user a fair amount of boilerplate. ## Changelog ### Changed - `ExtractComponent` can specify output type, and outputting is optional. Co-authored-by: Torstein Grindvik <52322338+torsteingrindvik@users.noreply.github.com> --- crates/bevy_core_pipeline/src/bloom/mod.rs | 124 ++++++++---------- .../src/core_2d/camera_2d.rs | 5 +- .../src/core_3d/camera_3d.rs | 5 +- crates/bevy_core_pipeline/src/fxaa/mod.rs | 5 +- .../bevy_core_pipeline/src/tonemapping/mod.rs | 5 +- crates/bevy_render/src/extract_component.rs | 33 ++++- crates/bevy_ui/src/camera_config.rs | 5 +- examples/shader/shader_instancing.rs | 5 +- 8 files changed, 98 insertions(+), 89 deletions(-) diff --git a/crates/bevy_core_pipeline/src/bloom/mod.rs b/crates/bevy_core_pipeline/src/bloom/mod.rs index 519a50a69f..f7550dba63 100644 --- a/crates/bevy_core_pipeline/src/bloom/mod.rs +++ b/crates/bevy_core_pipeline/src/bloom/mod.rs @@ -3,7 +3,7 @@ use bevy_app::{App, Plugin}; use bevy_asset::{load_internal_asset, HandleUntyped}; use bevy_ecs::{ prelude::{Component, Entity}, - query::{QueryState, With}, + query::{QueryItem, QueryState, With}, system::{Commands, Query, Res, ResMut, Resource}, world::{FromWorld, World}, }; @@ -11,14 +11,18 @@ use bevy_math::UVec2; use bevy_reflect::{Reflect, TypeUuid}; use bevy_render::{ camera::ExtractedCamera, + extract_component::{ + ComponentUniforms, DynamicUniformIndex, ExtractComponent, ExtractComponentPlugin, + UniformComponentPlugin, + }, prelude::Camera, render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext, SlotInfo, SlotType}, render_phase::TrackedRenderPass, render_resource::*, - renderer::{RenderContext, RenderDevice, RenderQueue}, + renderer::{RenderContext, RenderDevice}, texture::{CachedTexture, TextureCache}, view::ViewTarget, - Extract, RenderApp, RenderStage, + RenderApp, RenderStage, }; #[cfg(feature = "trace")] use bevy_utils::tracing::info_span; @@ -35,6 +39,8 @@ impl Plugin for BloomPlugin { load_internal_asset!(app, BLOOM_SHADER_HANDLE, "bloom.wgsl", Shader::from_wgsl); app.register_type::(); + app.add_plugin(ExtractComponentPlugin::::default()); + app.add_plugin(UniformComponentPlugin::::default()); let render_app = match app.get_sub_app_mut(RenderApp) { Ok(render_app) => render_app, @@ -43,10 +49,7 @@ impl Plugin for BloomPlugin { render_app .init_resource::() - .init_resource::() - .add_system_to_stage(RenderStage::Extract, extract_bloom_settings) .add_system_to_stage(RenderStage::Prepare, prepare_bloom_textures) - .add_system_to_stage(RenderStage::Prepare, prepare_bloom_uniforms) .add_system_to_stage(RenderStage::Queue, queue_bloom_bind_groups); { @@ -151,13 +154,39 @@ impl Default for BloomSettings { } } +impl ExtractComponent for BloomSettings { + type Query = (&'static Self, &'static Camera); + + type Filter = (); + type Out = BloomUniform; + + fn extract_component((settings, camera): QueryItem<'_, Self::Query>) -> Option { + if !(camera.is_active && camera.hdr) { + return None; + } + + camera.physical_viewport_size().map(|size| { + let min_view = size.x.min(size.y) / 2; + let mip_count = calculate_mip_count(min_view); + let scale = (min_view / 2u32.pow(mip_count)) as f32 / 8.0; + + BloomUniform { + threshold: settings.threshold, + knee: settings.knee, + scale: settings.scale * scale, + intensity: settings.intensity, + } + }) + } +} + pub struct BloomNode { view_query: QueryState<( &'static ExtractedCamera, &'static ViewTarget, &'static BloomTextures, &'static BloomBindGroups, - &'static BloomUniformIndex, + &'static DynamicUniformIndex, )>, } @@ -227,7 +256,11 @@ impl Node for BloomNode { }, )); prefilter_pass.set_render_pipeline(downsampling_prefilter_pipeline); - prefilter_pass.set_bind_group(0, &bind_groups.prefilter_bind_group, &[uniform_index.0]); + prefilter_pass.set_bind_group( + 0, + &bind_groups.prefilter_bind_group, + &[uniform_index.index()], + ); if let Some(viewport) = camera.viewport.as_ref() { prefilter_pass.set_camera_viewport(viewport); } @@ -252,7 +285,7 @@ impl Node for BloomNode { downsampling_pass.set_bind_group( 0, &bind_groups.downsampling_bind_groups[mip as usize - 1], - &[uniform_index.0], + &[uniform_index.index()], ); if let Some(viewport) = camera.viewport.as_ref() { downsampling_pass.set_camera_viewport(viewport); @@ -278,7 +311,7 @@ impl Node for BloomNode { upsampling_pass.set_bind_group( 0, &bind_groups.upsampling_bind_groups[mip as usize - 1], - &[uniform_index.0], + &[uniform_index.index()], ); if let Some(viewport) = camera.viewport.as_ref() { upsampling_pass.set_camera_viewport(viewport); @@ -304,7 +337,7 @@ impl Node for BloomNode { upsampling_final_pass.set_bind_group( 0, &bind_groups.upsampling_final_bind_group, - &[uniform_index.0], + &[uniform_index.index()], ); if let Some(viewport) = camera.viewport.as_ref() { upsampling_final_pass.set_camera_viewport(viewport); @@ -522,17 +555,6 @@ impl FromWorld for BloomPipelines { } } -fn extract_bloom_settings( - mut commands: Commands, - cameras: Extract>>, -) { - for (entity, camera, bloom_settings) in &cameras { - if camera.is_active && camera.hdr { - commands.get_or_spawn(entity).insert(bloom_settings.clone()); - } - } -} - #[derive(Component)] struct BloomTextures { texture_a: CachedTexture, @@ -554,7 +576,7 @@ fn prepare_bloom_textures( mut commands: Commands, mut texture_cache: ResMut, render_device: Res, - views: Query<(Entity, &ExtractedCamera), With>, + views: Query<(Entity, &ExtractedCamera), With>, ) { let mut texture_as = HashMap::default(); let mut texture_bs = HashMap::default(); @@ -602,59 +624,17 @@ fn prepare_bloom_textures( } } -#[derive(ShaderType)] -struct BloomUniform { +/// The uniform struct extracted from [`BloomSettings`] attached to a [`Camera`]. +/// Will be available for use in the Bloom shader. +#[doc(hidden)] +#[derive(Component, ShaderType, Clone)] +pub struct BloomUniform { threshold: f32, knee: f32, scale: f32, intensity: f32, } -#[derive(Resource, Default)] -struct BloomUniforms { - uniforms: DynamicUniformBuffer, -} - -#[derive(Component)] -struct BloomUniformIndex(u32); - -fn prepare_bloom_uniforms( - mut commands: Commands, - render_device: Res, - render_queue: Res, - mut bloom_uniforms: ResMut, - bloom_query: Query<(Entity, &ExtractedCamera, &BloomSettings)>, -) { - bloom_uniforms.uniforms.clear(); - - let entities = bloom_query - .iter() - .filter_map(|(entity, camera, settings)| { - let size = match camera.physical_viewport_size { - Some(size) => size, - None => return None, - }; - let min_view = size.x.min(size.y) / 2; - let mip_count = calculate_mip_count(min_view); - let scale = (min_view / 2u32.pow(mip_count)) as f32 / 8.0; - - let uniform = BloomUniform { - threshold: settings.threshold, - knee: settings.knee, - scale: settings.scale * scale, - intensity: settings.intensity, - }; - let index = bloom_uniforms.uniforms.push(uniform); - Some((entity, (BloomUniformIndex(index)))) - }) - .collect::>(); - commands.insert_or_spawn_batch(entities); - - bloom_uniforms - .uniforms - .write_buffer(&render_device, &render_queue); -} - #[derive(Component)] struct BloomBindGroups { prefilter_bind_group: BindGroup, @@ -667,10 +647,10 @@ fn queue_bloom_bind_groups( mut commands: Commands, render_device: Res, pipelines: Res, - uniforms: Res, + uniforms: Res>, views: Query<(Entity, &ViewTarget, &BloomTextures)>, ) { - if let Some(uniforms) = uniforms.uniforms.binding() { + if let Some(uniforms) = uniforms.binding() { for (entity, view_target, textures) in &views { let prefilter_bind_group = render_device.create_bind_group(&BindGroupDescriptor { label: Some("bloom_prefilter_bind_group"), diff --git a/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs b/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs index 86906240b4..1f52beeec8 100644 --- a/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs +++ b/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs @@ -18,9 +18,10 @@ pub struct Camera2d { impl ExtractComponent for Camera2d { type Query = &'static Self; type Filter = With; + type Out = Self; - fn extract_component(item: QueryItem<'_, Self::Query>) -> Self { - item.clone() + fn extract_component(item: QueryItem<'_, Self::Query>) -> Option { + Some(item.clone()) } } diff --git a/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs b/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs index 057f45c53a..d722f411a2 100644 --- a/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs +++ b/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs @@ -50,9 +50,10 @@ impl From for LoadOp { impl ExtractComponent for Camera3d { type Query = &'static Self; type Filter = With; + type Out = Self; - fn extract_component(item: QueryItem<'_, Self::Query>) -> Self { - item.clone() + fn extract_component(item: QueryItem<'_, Self::Query>) -> Option { + Some(item.clone()) } } diff --git a/crates/bevy_core_pipeline/src/fxaa/mod.rs b/crates/bevy_core_pipeline/src/fxaa/mod.rs index 5e7173ddbb..74b937575e 100644 --- a/crates/bevy_core_pipeline/src/fxaa/mod.rs +++ b/crates/bevy_core_pipeline/src/fxaa/mod.rs @@ -69,9 +69,10 @@ impl Default for Fxaa { impl ExtractComponent for Fxaa { type Query = &'static Self; type Filter = With; + type Out = Self; - fn extract_component(item: QueryItem) -> Self { - item.clone() + fn extract_component(item: QueryItem) -> Option { + Some(item.clone()) } } diff --git a/crates/bevy_core_pipeline/src/tonemapping/mod.rs b/crates/bevy_core_pipeline/src/tonemapping/mod.rs index 8c08d59605..9e3957a41f 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/mod.rs +++ b/crates/bevy_core_pipeline/src/tonemapping/mod.rs @@ -164,8 +164,9 @@ impl Tonemapping { impl ExtractComponent for Tonemapping { type Query = &'static Self; type Filter = With; + type Out = Self; - fn extract_component(item: QueryItem) -> Self { - item.clone() + fn extract_component(item: QueryItem) -> Option { + Some(item.clone()) } } diff --git a/crates/bevy_render/src/extract_component.rs b/crates/bevy_render/src/extract_component.rs index 573c3e0cff..7cae7d8065 100644 --- a/crates/bevy_render/src/extract_component.rs +++ b/crates/bevy_render/src/extract_component.rs @@ -37,8 +37,26 @@ pub trait ExtractComponent: Component { type Query: WorldQuery + ReadOnlyWorldQuery; /// Filters the entities with additional constraints. type Filter: WorldQuery + ReadOnlyWorldQuery; + + /// The output from extraction. + /// + /// Returning `None` based on the queried item can allow early optimization, + /// for example if there is an `enabled: bool` field on `Self`, or by only accepting + /// values within certain thresholds. + /// + /// The output may be different from the queried component. + /// This can be useful for example if only a subset of the fields are useful + /// in the render world. + /// + /// `Out` has a [`Bundle`] trait bound instead of a [`Component`] trait bound in order to allow use cases + /// such as tuples of components as output. + type Out: Bundle; + + // TODO: https://github.com/rust-lang/rust/issues/29661 + // type Out: Component = Self; + /// Defines how the component is transferred into the "render world". - fn extract_component(item: QueryItem<'_, Self::Query>) -> Self; + fn extract_component(item: QueryItem<'_, Self::Query>) -> Option; } /// This plugin prepares the components of the corresponding type for the GPU @@ -172,10 +190,11 @@ impl Plugin for ExtractComponentPlugin { impl ExtractComponent for Handle { type Query = Read>; type Filter = (); + type Out = Handle; #[inline] - fn extract_component(handle: QueryItem<'_, Self::Query>) -> Self { - handle.clone_weak() + fn extract_component(handle: QueryItem<'_, Self::Query>) -> Option { + Some(handle.clone_weak()) } } @@ -187,7 +206,9 @@ fn extract_components( ) { let mut values = Vec::with_capacity(*previous_len); for (entity, query_item) in &query { - values.push((entity, C::extract_component(query_item))); + if let Some(component) = C::extract_component(query_item) { + values.push((entity, component)); + } } *previous_len = values.len(); commands.insert_or_spawn_batch(values); @@ -202,7 +223,9 @@ fn extract_visible_components( let mut values = Vec::with_capacity(*previous_len); for (entity, computed_visibility, query_item) in &query { if computed_visibility.is_visible() { - values.push((entity, C::extract_component(query_item))); + if let Some(component) = C::extract_component(query_item) { + values.push((entity, component)); + } } } *previous_len = values.len(); diff --git a/crates/bevy_ui/src/camera_config.rs b/crates/bevy_ui/src/camera_config.rs index da903e5137..db01b29fd9 100644 --- a/crates/bevy_ui/src/camera_config.rs +++ b/crates/bevy_ui/src/camera_config.rs @@ -30,8 +30,9 @@ impl Default for UiCameraConfig { impl ExtractComponent for UiCameraConfig { type Query = &'static Self; type Filter = With; + type Out = Self; - fn extract_component(item: QueryItem<'_, Self::Query>) -> Self { - item.clone() + fn extract_component(item: QueryItem<'_, Self::Query>) -> Option { + Some(item.clone()) } } diff --git a/examples/shader/shader_instancing.rs b/examples/shader/shader_instancing.rs index 5f49027eed..dd57825d30 100644 --- a/examples/shader/shader_instancing.rs +++ b/examples/shader/shader_instancing.rs @@ -69,9 +69,10 @@ struct InstanceMaterialData(Vec); impl ExtractComponent for InstanceMaterialData { type Query = &'static InstanceMaterialData; type Filter = (); + type Out = Self; - fn extract_component(item: QueryItem<'_, Self::Query>) -> Self { - InstanceMaterialData(item.0.clone()) + fn extract_component(item: QueryItem<'_, Self::Query>) -> Option { + Some(InstanceMaterialData(item.0.clone())) } }