ExtractResourcePlugin (#3745)

# Objective

- Add an `ExtractResourcePlugin` for convenience and consistency

## Solution

- Add an `ExtractResourcePlugin` similar to `ExtractComponentPlugin` but for ECS `Resource`s. The system that is executed simply clones the main world resource into a render world resource, if and only if the main world resource was either added or changed since the last execution of the system.
- Add an `ExtractResource` trait with a `fn extract_resource(res: &Self) -> Self` function. This is used by the `ExtractResourcePlugin` to extract the resource
- Add a derive macro for `ExtractResource` on a `Resource` with the `Clone` trait, that simply returns `res.clone()`
- Use `ExtractResourcePlugin` wherever both possible and appropriate
This commit is contained in:
Robert Swain 2022-05-30 18:36:03 +00:00
parent ba53a44956
commit a0a3d8798b
21 changed files with 154 additions and 75 deletions

View file

@ -24,6 +24,7 @@ use bevy_ecs::prelude::*;
use bevy_render::{
camera::{ActiveCamera, Camera2d, Camera3d, ExtractedCamera, RenderTarget},
color::Color,
extract_resource::{ExtractResource, ExtractResourcePlugin},
render_graph::{EmptyNode, RenderGraph, SlotInfo, SlotType},
render_phase::{
batch_phase_system, sort_phase_system, BatchedPhaseItem, CachedRenderPipelinePhaseItem,
@ -33,7 +34,7 @@ use bevy_render::{
renderer::RenderDevice,
texture::TextureCache,
view::{ExtractedView, Msaa, ViewDepthTexture},
RenderApp, RenderStage, RenderWorld,
RenderApp, RenderStage,
};
use bevy_utils::FloatOrd;
@ -41,7 +42,7 @@ use bevy_utils::FloatOrd;
///
/// This color appears as the "background" color for simple apps, when
/// there are portions of the screen with nothing rendered.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, ExtractResource)]
pub struct ClearColor(pub Color);
impl Default for ClearColor {
@ -50,7 +51,7 @@ impl Default for ClearColor {
}
}
#[derive(Clone, Debug, Default)]
#[derive(Clone, Debug, Default, ExtractResource)]
pub struct RenderTargetClearColors {
colors: HashMap<RenderTarget, Color>,
}
@ -113,7 +114,9 @@ pub enum CorePipelineRenderSystems {
impl Plugin for CorePipelinePlugin {
fn build(&self, app: &mut App) {
app.init_resource::<ClearColor>()
.init_resource::<RenderTargetClearColors>();
.init_resource::<RenderTargetClearColors>()
.add_plugin(ExtractResourcePlugin::<ClearColor>::default())
.add_plugin(ExtractResourcePlugin::<RenderTargetClearColors>::default());
let render_app = match app.get_sub_app_mut(RenderApp) {
Ok(render_app) => render_app,
@ -125,7 +128,6 @@ impl Plugin for CorePipelinePlugin {
.init_resource::<DrawFunctions<Opaque3d>>()
.init_resource::<DrawFunctions<AlphaMask3d>>()
.init_resource::<DrawFunctions<Transparent3d>>()
.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(
@ -347,24 +349,6 @@ impl CachedRenderPipelinePhaseItem for Transparent3d {
}
}
pub fn extract_clear_color(
clear_color: Res<ClearColor>,
clear_colors: Res<RenderTargetClearColors>,
mut render_world: ResMut<RenderWorld>,
) {
// If the clear color has changed
if clear_color.is_changed() {
// Update the clear color resource in the render world
render_world.insert_resource(clear_color.clone());
}
// If the clear color has changed
if clear_colors.is_changed() {
// Update the clear color resource in the render world
render_world.insert_resource(clear_colors.clone());
}
}
pub fn extract_core_pipeline_camera_phases(
mut commands: Commands,
active_2d: Res<ActiveCamera<Camera2d>>,

View file

@ -39,6 +39,7 @@ use bevy_asset::{load_internal_asset, Assets, Handle, HandleUntyped};
use bevy_ecs::prelude::*;
use bevy_reflect::TypeUuid;
use bevy_render::{
extract_resource::ExtractResourcePlugin,
prelude::Color,
render_graph::RenderGraph,
render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions},
@ -76,6 +77,7 @@ impl Plugin for PbrPlugin {
.init_resource::<GlobalVisiblePointLights>()
.init_resource::<DirectionalLightShadowMap>()
.init_resource::<PointLightShadowMap>()
.add_plugin(ExtractResourcePlugin::<AmbientLight>::default())
.add_system_to_stage(
CoreStage::PostUpdate,
// NOTE: Clusters need to have been added before update_clusters is run so

View file

@ -9,6 +9,7 @@ use bevy_reflect::prelude::*;
use bevy_render::{
camera::{Camera, CameraProjection, OrthographicProjection},
color::Color,
extract_resource::ExtractResource,
prelude::Image,
primitives::{Aabb, CubemapFrusta, Frustum, Plane, Sphere},
render_resource::BufferBindingType,
@ -170,7 +171,7 @@ impl Default for DirectionalLightShadowMap {
}
/// An ambient light, which lights the entire scene equally.
#[derive(Debug)]
#[derive(Clone, Debug, ExtractResource)]
pub struct AmbientLight {
pub color: Color,
/// A direct scale factor multiplied with `color` before being passed to the shader.

View file

@ -15,9 +15,9 @@ use bevy_ecs::{
world::FromWorld,
};
use bevy_render::{
extract_component::ExtractComponentPlugin,
mesh::{Mesh, MeshVertexBufferLayout},
render_asset::{RenderAsset, RenderAssetPlugin, RenderAssets},
render_component::ExtractComponentPlugin,
render_phase::{
AddRenderCommand, DrawFunctions, EntityRenderCommand, RenderCommandResult, RenderPhase,
SetItemPipeline, TrackedRenderPass,

View file

@ -45,11 +45,6 @@ pub enum RenderLightSystems {
QueueShadows,
}
pub struct ExtractedAmbientLight {
color: Color,
brightness: f32,
}
#[derive(Component)]
pub struct ExtractedPointLight {
color: Color,
@ -63,8 +58,6 @@ pub struct ExtractedPointLight {
shadow_normal_bias: f32,
}
pub type ExtractedPointLightShadowMap = PointLightShadowMap;
#[derive(Component)]
pub struct ExtractedDirectionalLight {
color: Color,
@ -76,8 +69,6 @@ pub struct ExtractedDirectionalLight {
shadow_normal_bias: f32,
}
pub type ExtractedDirectionalLightShadowMap = DirectionalLightShadowMap;
#[derive(Copy, Clone, ShaderType, Default, Debug)]
pub struct GpuPointLight {
// The lower-right 2x2 values of the projection matrix 22 23 32 33
@ -408,7 +399,6 @@ pub fn extract_clusters(mut commands: Commands, views: Query<(Entity, &Clusters)
#[allow(clippy::too_many_arguments)]
pub fn extract_lights(
mut commands: Commands,
ambient_light: Res<AmbientLight>,
point_light_shadow_map: Res<PointLightShadowMap>,
directional_light_shadow_map: Res<DirectionalLightShadowMap>,
global_point_lights: Res<GlobalVisiblePointLights>,
@ -422,14 +412,14 @@ pub fn extract_lights(
)>,
mut previous_point_lights_len: Local<usize>,
) {
commands.insert_resource(ExtractedAmbientLight {
color: ambient_light.color,
brightness: ambient_light.brightness,
});
commands.insert_resource::<ExtractedPointLightShadowMap>(point_light_shadow_map.clone());
commands.insert_resource::<ExtractedDirectionalLightShadowMap>(
directional_light_shadow_map.clone(),
);
// NOTE: These shadow map resources are extracted here as they are used here too so this avoids
// races between scheduling of ExtractResourceSystems and this system.
if point_light_shadow_map.is_changed() {
commands.insert_resource(point_light_shadow_map.clone());
}
if directional_light_shadow_map.is_changed() {
commands.insert_resource(directional_light_shadow_map.clone());
}
// This is the point light shadow map texel size for one face of the cube as a distance of 1.0
// world unit from the light.
// point_light_texel_size = 2.0 * 1.0 * tan(PI / 4.0) / cube face width in texels
@ -665,9 +655,9 @@ pub fn prepare_lights(
(Entity, &ExtractedView, &ExtractedClusterConfig),
With<RenderPhase<Transparent3d>>,
>,
ambient_light: Res<ExtractedAmbientLight>,
point_light_shadow_map: Res<ExtractedPointLightShadowMap>,
directional_light_shadow_map: Res<ExtractedDirectionalLightShadowMap>,
ambient_light: Res<AmbientLight>,
point_light_shadow_map: Res<PointLightShadowMap>,
directional_light_shadow_map: Res<DirectionalLightShadowMap>,
point_lights: Query<(Entity, &ExtractedPointLight)>,
directional_lights: Query<(Entity, &ExtractedDirectionalLight)>,
) {

View file

@ -12,12 +12,12 @@ use bevy_ecs::{
use bevy_math::{Mat4, Vec2};
use bevy_reflect::TypeUuid;
use bevy_render::{
extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin},
mesh::{
skinning::{SkinnedMesh, SkinnedMeshInverseBindposes},
GpuBufferInfo, Mesh, MeshVertexBufferLayout,
},
render_asset::RenderAssets,
render_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin},
render_phase::{EntityRenderCommand, RenderCommandResult, TrackedRenderPass},
render_resource::*,
renderer::{RenderDevice, RenderQueue},

View file

@ -7,6 +7,7 @@ use bevy_ecs::{prelude::*, reflect::ReflectComponent};
use bevy_reflect::std_traits::ReflectDefault;
use bevy_reflect::{Reflect, TypeUuid};
use bevy_render::{
extract_resource::{ExtractResource, ExtractResourcePlugin},
mesh::{Mesh, MeshVertexBufferLayout},
render_asset::RenderAssets,
render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline},
@ -34,7 +35,8 @@ impl Plugin for WireframePlugin {
Shader::from_wgsl
);
app.init_resource::<WireframeConfig>();
app.init_resource::<WireframeConfig>()
.add_plugin(ExtractResourcePlugin::<WireframeConfig>::default());
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
@ -42,18 +44,11 @@ impl Plugin for WireframePlugin {
.init_resource::<WireframePipeline>()
.init_resource::<SpecializedMeshPipelines<WireframePipeline>>()
.add_system_to_stage(RenderStage::Extract, extract_wireframes)
.add_system_to_stage(RenderStage::Extract, extract_wireframe_config)
.add_system_to_stage(RenderStage::Queue, queue_wireframes);
}
}
}
fn extract_wireframe_config(mut commands: Commands, wireframe_config: Res<WireframeConfig>) {
if wireframe_config.is_added() || wireframe_config.is_changed() {
commands.insert_resource(wireframe_config.into_inner().clone());
}
}
fn extract_wireframes(mut commands: Commands, query: Query<Entity, With<Wireframe>>) {
for entity in query.iter() {
commands.get_or_spawn(entity).insert(Wireframe);
@ -65,7 +60,7 @@ fn extract_wireframes(mut commands: Commands, query: Query<Entity, With<Wirefram
#[reflect(Component, Default)]
pub struct Wireframe;
#[derive(Debug, Clone, Default)]
#[derive(Debug, Clone, Default, ExtractResource)]
pub struct WireframeConfig {
/// Whether to show wireframes for all meshes. If `false`, only meshes with a [Wireframe] component will be rendered.
pub global: bool,

View file

@ -36,6 +36,7 @@ bevy_ecs = { path = "../bevy_ecs", version = "0.8.0-dev" }
bevy_encase_derive = { path = "../bevy_encase_derive", version = "0.8.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.8.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.8.0-dev", features = ["bevy"] }
bevy_render_macros = { path = "macros", version = "0.8.0-dev" }
bevy_transform = { path = "../bevy_transform", version = "0.8.0-dev" }
bevy_window = { path = "../bevy_window", version = "0.8.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.8.0-dev" }

View file

@ -0,0 +1,19 @@
[package]
name = "bevy_render_macros"
version = "0.8.0-dev"
edition = "2021"
description = "Derive implementations for bevy_render"
homepage = "https://bevyengine.org"
repository = "https://github.com/bevyengine/bevy"
license = "MIT OR Apache-2.0"
keywords = ["bevy"]
[lib]
proc-macro = true
[dependencies]
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.8.0-dev" }
syn = "1.0"
proc-macro2 = "1.0"
quote = "1.0"

View file

@ -0,0 +1,26 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, parse_quote, DeriveInput, Path};
pub fn derive_extract_resource(input: TokenStream) -> TokenStream {
let mut ast = parse_macro_input!(input as DeriveInput);
let bevy_render_path: Path = crate::bevy_render_path();
ast.generics
.make_where_clause()
.predicates
.push(parse_quote! { Self: Clone });
let struct_name = &ast.ident;
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
TokenStream::from(quote! {
impl #impl_generics #bevy_render_path::extract_resource::ExtractResource for #struct_name #type_generics #where_clause {
type Source = Self;
fn extract_resource(source: &Self::Source) -> Self {
source.clone()
}
}
})
}

View file

@ -0,0 +1,16 @@
mod extract_resource;
use bevy_macro_utils::BevyManifest;
use proc_macro::TokenStream;
pub(crate) fn bevy_render_path() -> syn::Path {
BevyManifest::default()
.maybe_get_path("bevy_render")
// NOTE: If the derivation is within bevy_render, then we need to return 'crate'
.unwrap_or_else(|| BevyManifest::parse_str("crate"))
}
#[proc_macro_derive(ExtractResource)]
pub fn derive_extract_resource(input: TokenStream) -> TokenStream {
extract_resource::derive_extract_resource(input)
}

View file

@ -0,0 +1,46 @@
use std::marker::PhantomData;
use bevy_app::{App, Plugin};
use bevy_ecs::system::{Commands, Res, Resource};
pub use bevy_render_macros::ExtractResource;
use crate::{RenderApp, RenderStage};
/// Describes how a resource gets extracted for rendering.
///
/// Therefore the resource is transferred from the "main world" into the "render world"
/// in the [`RenderStage::Extract`](crate::RenderStage::Extract) step.
pub trait ExtractResource: Resource {
type Source: Resource;
/// Defines how the resource is transferred into the "render world".
fn extract_resource(source: &Self::Source) -> Self;
}
/// This plugin extracts the resources into the "render world".
///
/// Therefore it sets up the [`RenderStage::Extract`](crate::RenderStage::Extract) step
/// for the specified [`Resource`].
pub struct ExtractResourcePlugin<R: ExtractResource>(PhantomData<R>);
impl<R: ExtractResource> Default for ExtractResourcePlugin<R> {
fn default() -> Self {
Self(PhantomData)
}
}
impl<R: ExtractResource> Plugin for ExtractResourcePlugin<R> {
fn build(&self, app: &mut App) {
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app.add_system_to_stage(RenderStage::Extract, extract_resource::<R>);
}
}
}
/// This system extracts the resource of the corresponding [`Resource`] type
/// by cloning it.
pub fn extract_resource<R: ExtractResource>(mut commands: Commands, resource: Res<R::Source>) {
if resource.is_changed() {
commands.insert_resource(R::extract_resource(resource.into_inner()));
}
}

View file

@ -2,10 +2,11 @@ extern crate core;
pub mod camera;
pub mod color;
pub mod extract_component;
pub mod extract_resource;
pub mod mesh;
pub mod primitives;
pub mod render_asset;
pub mod render_component;
pub mod render_graph;
pub mod render_phase;
pub mod render_resource;

View file

@ -10,6 +10,7 @@ pub use window::*;
use crate::{
camera::ExtractedCamera,
extract_resource::{ExtractResource, ExtractResourcePlugin},
prelude::Image,
render_asset::RenderAssets,
render_resource::{DynamicUniformBuffer, ShaderType, Texture, TextureView},
@ -27,12 +28,14 @@ pub struct ViewPlugin;
impl Plugin for ViewPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<Msaa>().add_plugin(VisibilityPlugin);
app.init_resource::<Msaa>()
// NOTE: windows.is_changed() handles cases where a window was resized
.add_plugin(ExtractResourcePlugin::<Msaa>::default())
.add_plugin(VisibilityPlugin);
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.init_resource::<ViewUniforms>()
.add_system_to_stage(RenderStage::Extract, extract_msaa)
.add_system_to_stage(RenderStage::Prepare, prepare_view_uniforms)
.add_system_to_stage(
RenderStage::Prepare,
@ -42,7 +45,7 @@ impl Plugin for ViewPlugin {
}
}
#[derive(Clone)]
#[derive(Clone, ExtractResource)]
/// Configuration resource for [Multi-Sample Anti-Aliasing](https://en.wikipedia.org/wiki/Multisample_anti-aliasing).
///
/// # Example
@ -70,11 +73,6 @@ impl Default for Msaa {
}
}
pub fn extract_msaa(mut commands: Commands, msaa: Res<Msaa>) {
// NOTE: windows.is_changed() handles cases where a window was resized
commands.insert_resource(msaa.clone());
}
#[derive(Component)]
pub struct ExtractedView {
pub projection: Mat4,

View file

@ -12,9 +12,9 @@ use bevy_ecs::{
};
use bevy_log::error;
use bevy_render::{
extract_component::ExtractComponentPlugin,
mesh::{Mesh, MeshVertexBufferLayout},
render_asset::{RenderAsset, RenderAssetPlugin, RenderAssets},
render_component::ExtractComponentPlugin,
render_phase::{
AddRenderCommand, DrawFunctions, EntityRenderCommand, RenderCommandResult, RenderPhase,
SetItemPipeline, TrackedRenderPass,

View file

@ -7,9 +7,9 @@ use bevy_ecs::{
use bevy_math::{Mat4, Vec2};
use bevy_reflect::{Reflect, TypeUuid};
use bevy_render::{
extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin},
mesh::{GpuBufferInfo, Mesh, MeshVertexBufferLayout},
render_asset::RenderAssets,
render_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin},
render_phase::{EntityRenderCommand, RenderCommandResult, TrackedRenderPass},
render_resource::*,
renderer::{RenderDevice, RenderQueue},

View file

@ -7,6 +7,7 @@ use bevy::{
core_pipeline::node::MAIN_PASS_DEPENDENCIES,
prelude::*,
render::{
extract_resource::{ExtractResource, ExtractResourcePlugin},
render_asset::RenderAssets,
render_graph::{self, RenderGraph},
render_resource::*,
@ -66,10 +67,12 @@ pub struct GameOfLifeComputePlugin;
impl Plugin for GameOfLifeComputePlugin {
fn build(&self, app: &mut App) {
// Extract the game of life image resource from the main world into the render world
// for operation on by the compute shader and display on the sprite.
app.add_plugin(ExtractResourcePlugin::<GameOfLifeImage>::default());
let render_app = app.sub_app_mut(RenderApp);
render_app
.init_resource::<GameOfLifePipeline>()
.add_system_to_stage(RenderStage::Extract, extract_game_of_life_image)
.add_system_to_stage(RenderStage::Queue, queue_bind_group);
let mut render_graph = render_app.world.resource_mut::<RenderGraph>();
@ -80,15 +83,11 @@ impl Plugin for GameOfLifeComputePlugin {
}
}
#[derive(Deref)]
#[derive(Clone, Deref, ExtractResource)]
struct GameOfLifeImage(Handle<Image>);
struct GameOfLifeImageBindGroup(BindGroup);
fn extract_game_of_life_image(mut commands: Commands, image: Res<GameOfLifeImage>) {
commands.insert_resource(GameOfLifeImage(image.clone()));
}
fn queue_bind_group(
mut commands: Commands,
pipeline: Res<GameOfLifePipeline>,

View file

@ -8,9 +8,9 @@ use bevy::{
},
prelude::*,
render::{
extract_component::{ExtractComponent, ExtractComponentPlugin},
mesh::MeshVertexBufferLayout,
render_asset::RenderAssets,
render_component::{ExtractComponent, ExtractComponentPlugin},
render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline},
render_resource::{
PipelineCache, RenderPipelineDescriptor, SpecializedMeshPipeline,

View file

@ -7,9 +7,9 @@ use bevy::{
pbr::{MeshPipeline, MeshPipelineKey, MeshUniform, SetMeshBindGroup, SetMeshViewBindGroup},
prelude::*,
render::{
extract_component::{ExtractComponent, ExtractComponentPlugin},
mesh::{GpuBufferInfo, MeshVertexBufferLayout},
render_asset::RenderAssets,
render_component::{ExtractComponent, ExtractComponentPlugin},
render_phase::{
AddRenderCommand, DrawFunctions, EntityRenderCommand, RenderCommandResult, RenderPhase,
SetItemPipeline, TrackedRenderPass,

View file

@ -22,6 +22,7 @@ crates=(
bevy_transform
bevy_window
bevy_encase_derive
bevy_render/macros
bevy_render
bevy_core_pipeline
bevy_input