Skip to content

Commit

Permalink
The Cooler 'Retain Rendering World' (#15320)
Browse files Browse the repository at this point in the history
- Adopted from #14449
- Still fixes #12144.

## Migration Guide

The retained render world is a complex change: migrating might take one
of a few different forms depending on the patterns you're using.

For every example, we specify in which world the code is run. Most of
the changes affect render world code, so for the average Bevy user who's
using Bevy's high-level rendering APIs, these changes are unlikely to
affect your code.

### Spawning entities in the render world

Previously, if you spawned an entity with `world.spawn(...)`,
`commands.spawn(...)` or some other method in the rendering world, it
would be despawned at the end of each frame. In 0.15, this is no longer
the case and so your old code could leak entities. This can be mitigated
by either re-architecting your code to no longer continuously spawn
entities (like you're used to in the main world), or by adding the
`bevy_render::world_sync::TemporaryRenderEntity` component to the entity
you're spawning. Entities tagged with `TemporaryRenderEntity` will be
removed at the end of each frame (like before).

### Extract components with `ExtractComponentPlugin`

```
// main world
app.add_plugins(ExtractComponentPlugin::<ComponentToExtract>::default());
```

`ExtractComponentPlugin` has been changed to only work with synced
entities. Entities are automatically synced if `ComponentToExtract` is
added to them. However, entities are not "unsynced" if any given
`ComponentToExtract` is removed, because an entity may have multiple
components to extract. This would cause the other components to no
longer get extracted because the entity is not synced.

So be careful when only removing extracted components from entities in
the render world, because it might leave an entity behind in the render
world. The solution here is to avoid only removing extracted components
and instead despawn the entire entity.

### Manual extraction using `Extract<Query<(Entity, ...)>>`

```rust
// in render world, inspired by bevy_pbr/src/cluster/mod.rs
pub fn extract_clusters(
    mut commands: Commands,
    views: Extract<Query<(Entity, &Clusters, &Camera)>>,
) {
    for (entity, clusters, camera) in &views {
        // some code
        commands.get_or_spawn(entity).insert(...);
    }
}
```
One of the primary consequences of the retained rendering world is that
there's no longer a one-to-one mapping from entity IDs in the main world
to entity IDs in the render world. Unlike in Bevy 0.14, Entity 42 in the
main world doesn't necessarily map to entity 42 in the render world.

Previous code which called `get_or_spawn(main_world_entity)` in the
render world (`Extract<Query<(Entity, ...)>>` returns main world
entities). Instead, you should use `&RenderEntity` and
`render_entity.id()` to get the correct entity in the render world. Note
that this entity does need to be synced first in order to have a
`RenderEntity`.

When performing manual abstraction, this won't happen automatically
(like with `ExtractComponentPlugin`) so add a `SyncToRenderWorld` marker
component to the entities you want to extract.

This results in the following code:
```rust
// in render world, inspired by bevy_pbr/src/cluster/mod.rs
pub fn extract_clusters(
    mut commands: Commands,
    views: Extract<Query<(&RenderEntity, &Clusters, &Camera)>>,
) {
    for (render_entity, clusters, camera) in &views {
        // some code
        commands.get_or_spawn(render_entity.id()).insert(...);
    }
}

// in main world, when spawning
world.spawn(Clusters::default(), Camera::default(), SyncToRenderWorld)
```

### Looking up `Entity` ids in the render world

As previously stated, there's now no correspondence between main world
and render world `Entity` identifiers.

Querying for `Entity` in the render world will return the `Entity` id in
the render world: query for `MainEntity` (and use its `id()` method) to
get the corresponding entity in the main world.

This is also a good way to tell the difference between synced and
unsynced entities in the render world, because unsynced entities won't
have a `MainEntity` component.

---------

Co-authored-by: re0312 <re0312@outlook.com>
Co-authored-by: re0312 <45868716+re0312@users.noreply.github.com>
Co-authored-by: Periwink <charlesbour@gmail.com>
Co-authored-by: Anselmo Sampietro <ans.samp@gmail.com>
Co-authored-by: Emerson Coskey <56370779+ecoskey@users.noreply.github.com>
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Christian Hughes <9044780+ItsDoot@users.noreply.github.com>
  • Loading branch information
8 people authored Sep 30, 2024
1 parent 01dce47 commit 56f8e52
Show file tree
Hide file tree
Showing 28 changed files with 690 additions and 241 deletions.
5 changes: 3 additions & 2 deletions crates/bevy_core_pipeline/src/auto_exposure/buffers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use bevy_ecs::prelude::*;
use bevy_render::{
render_resource::{StorageBuffer, UniformBuffer},
renderer::{RenderDevice, RenderQueue},
world_sync::RenderEntity,
Extract,
};
use bevy_utils::{Entry, HashMap};
Expand All @@ -26,13 +27,13 @@ pub(super) struct ExtractedStateBuffers {

pub(super) fn extract_buffers(
mut commands: Commands,
changed: Extract<Query<(Entity, &AutoExposure), Changed<AutoExposure>>>,
changed: Extract<Query<(&RenderEntity, &AutoExposure), Changed<AutoExposure>>>,
mut removed: Extract<RemovedComponents<AutoExposure>>,
) {
commands.insert_resource(ExtractedStateBuffers {
changed: changed
.iter()
.map(|(entity, settings)| (entity, settings.clone()))
.map(|(entity, settings)| (entity.id(), settings.clone()))
.collect(),
removed: removed.read().collect(),
});
Expand Down
5 changes: 5 additions & 0 deletions crates/bevy_core_pipeline/src/core_2d/camera_2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::{
};
use bevy_ecs::prelude::*;
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::world_sync::SyncToRenderWorld;
use bevy_render::{
camera::{
Camera, CameraMainTextureUsages, CameraProjection, CameraRenderGraph,
Expand Down Expand Up @@ -35,6 +36,8 @@ pub struct Camera2dBundle {
pub deband_dither: DebandDither,
pub main_texture_usages: CameraMainTextureUsages,
pub msaa: Msaa,
/// Marker component that indicates that its entity needs to be synchronized to the render world
pub sync: SyncToRenderWorld,
}

impl Default for Camera2dBundle {
Expand All @@ -55,6 +58,7 @@ impl Default for Camera2dBundle {
deband_dither: DebandDither::Disabled,
main_texture_usages: Default::default(),
msaa: Default::default(),
sync: Default::default(),
}
}
}
Expand Down Expand Up @@ -88,6 +92,7 @@ impl Camera2dBundle {
deband_dither: DebandDither::Disabled,
main_texture_usages: Default::default(),
msaa: Default::default(),
sync: Default::default(),
}
}
}
7 changes: 3 additions & 4 deletions crates/bevy_core_pipeline/src/core_2d/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ use bevy_render::{
renderer::RenderDevice,
texture::TextureCache,
view::{Msaa, ViewDepthTexture},
world_sync::RenderEntity,
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
};

Expand Down Expand Up @@ -357,11 +358,10 @@ impl CachedRenderPipelinePhaseItem for Transparent2d {
}

pub fn extract_core_2d_camera_phases(
mut commands: Commands,
mut transparent_2d_phases: ResMut<ViewSortedRenderPhases<Transparent2d>>,
mut opaque_2d_phases: ResMut<ViewBinnedRenderPhases<Opaque2d>>,
mut alpha_mask_2d_phases: ResMut<ViewBinnedRenderPhases<AlphaMask2d>>,
cameras_2d: Extract<Query<(Entity, &Camera), With<Camera2d>>>,
cameras_2d: Extract<Query<(&RenderEntity, &Camera), With<Camera2d>>>,
mut live_entities: Local<EntityHashSet>,
) {
live_entities.clear();
Expand All @@ -370,8 +370,7 @@ pub fn extract_core_2d_camera_phases(
if !camera.is_active {
continue;
}

commands.get_or_spawn(entity);
let entity = entity.id();
transparent_2d_phases.insert_or_clear(entity);
opaque_2d_phases.insert_or_clear(entity);
alpha_mask_2d_phases.insert_or_clear(entity);
Expand Down
4 changes: 4 additions & 0 deletions crates/bevy_core_pipeline/src/core_3d/camera_3d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use bevy_render::{
primitives::Frustum,
render_resource::{LoadOp, TextureUsages},
view::{ColorGrading, Msaa, VisibleEntities},
world_sync::SyncToRenderWorld,
};
use bevy_transform::prelude::{GlobalTransform, Transform};
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -153,6 +154,8 @@ pub struct Camera3dBundle {
pub exposure: Exposure,
pub main_texture_usages: CameraMainTextureUsages,
pub msaa: Msaa,
/// Marker component that indicates that its entity needs to be synchronized to the render world
pub sync: SyncToRenderWorld,
}

// NOTE: ideally Perspective and Orthographic defaults can share the same impl, but sadly it breaks rust's type inference
Expand All @@ -173,6 +176,7 @@ impl Default for Camera3dBundle {
main_texture_usages: Default::default(),
deband_dither: DebandDither::Enabled,
msaa: Default::default(),
sync: Default::default(),
}
}
}
23 changes: 14 additions & 9 deletions crates/bevy_core_pipeline/src/core_3d/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ use bevy_render::{
renderer::RenderDevice,
texture::{BevyDefault, ColorAttachment, Image, TextureCache},
view::{ExtractedView, ViewDepthTexture, ViewTarget},
world_sync::RenderEntity,
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
};
use bevy_utils::{tracing::warn, HashMap};
Expand Down Expand Up @@ -504,23 +505,21 @@ impl CachedRenderPipelinePhaseItem for Transparent3d {
}

pub fn extract_core_3d_camera_phases(
mut commands: Commands,
mut opaque_3d_phases: ResMut<ViewBinnedRenderPhases<Opaque3d>>,
mut alpha_mask_3d_phases: ResMut<ViewBinnedRenderPhases<AlphaMask3d>>,
mut transmissive_3d_phases: ResMut<ViewSortedRenderPhases<Transmissive3d>>,
mut transparent_3d_phases: ResMut<ViewSortedRenderPhases<Transparent3d>>,
cameras_3d: Extract<Query<(Entity, &Camera), With<Camera3d>>>,
cameras_3d: Extract<Query<(&RenderEntity, &Camera), With<Camera3d>>>,
mut live_entities: Local<EntityHashSet>,
) {
live_entities.clear();

for (entity, camera) in &cameras_3d {
for (render_entity, camera) in &cameras_3d {
if !camera.is_active {
continue;
}

commands.get_or_spawn(entity);

let entity = render_entity.id();
opaque_3d_phases.insert_or_clear(entity);
alpha_mask_3d_phases.insert_or_clear(entity);
transmissive_3d_phases.insert_or_clear(entity);
Expand All @@ -545,7 +544,7 @@ pub fn extract_camera_prepass_phase(
cameras_3d: Extract<
Query<
(
Entity,
&RenderEntity,
&Camera,
Has<DepthPrepass>,
Has<NormalPrepass>,
Expand All @@ -559,13 +558,20 @@ pub fn extract_camera_prepass_phase(
) {
live_entities.clear();

for (entity, camera, depth_prepass, normal_prepass, motion_vector_prepass, deferred_prepass) in
cameras_3d.iter()
for (
render_entity,
camera,
depth_prepass,
normal_prepass,
motion_vector_prepass,
deferred_prepass,
) in cameras_3d.iter()
{
if !camera.is_active {
continue;
}

let entity = render_entity.id();
if depth_prepass || normal_prepass || motion_vector_prepass {
opaque_3d_prepass_phases.insert_or_clear(entity);
alpha_mask_3d_prepass_phases.insert_or_clear(entity);
Expand All @@ -581,7 +587,6 @@ pub fn extract_camera_prepass_phase(
opaque_3d_deferred_phases.remove(&entity);
alpha_mask_3d_deferred_phases.remove(&entity);
}

live_entities.insert(entity);

commands
Expand Down
4 changes: 3 additions & 1 deletion crates/bevy_core_pipeline/src/dof/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ use bevy_render::{
prepare_view_targets, ExtractedView, Msaa, ViewDepthTexture, ViewTarget, ViewUniform,
ViewUniformOffset, ViewUniforms,
},
world_sync::RenderEntity,
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
};
use bevy_utils::{info_once, prelude::default, warn_once};
Expand Down Expand Up @@ -809,7 +810,7 @@ impl SpecializedRenderPipeline for DepthOfFieldPipeline {
/// Extracts all [`DepthOfField`] components into the render world.
fn extract_depth_of_field_settings(
mut commands: Commands,
mut query: Extract<Query<(Entity, &DepthOfField, &Projection)>>,
mut query: Extract<Query<(&RenderEntity, &DepthOfField, &Projection)>>,
) {
if !DEPTH_TEXTURE_SAMPLING_SUPPORTED {
info_once!(
Expand All @@ -819,6 +820,7 @@ fn extract_depth_of_field_settings(
}

for (entity, depth_of_field, projection) in query.iter_mut() {
let entity = entity.id();
// Depth of field is nonsensical without a perspective projection.
let Projection::Perspective(ref perspective_projection) = *projection else {
continue;
Expand Down
23 changes: 15 additions & 8 deletions crates/bevy_core_pipeline/src/taa/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ use bevy_render::{
renderer::{RenderContext, RenderDevice},
texture::{BevyDefault, CachedTexture, TextureCache},
view::{ExtractedView, Msaa, ViewTarget},
world_sync::RenderEntity,
ExtractSchedule, MainWorld, Render, RenderApp, RenderSet,
};
use bevy_utils::tracing::warn;
Expand Down Expand Up @@ -351,20 +352,26 @@ impl SpecializedRenderPipeline for TaaPipeline {
}

fn extract_taa_settings(mut commands: Commands, mut main_world: ResMut<MainWorld>) {
let mut cameras_3d = main_world
.query_filtered::<(Entity, &Camera, &Projection, &mut TemporalAntiAliasing), (
With<Camera3d>,
With<TemporalJitter>,
With<DepthPrepass>,
With<MotionVectorPrepass>,
)>();
let mut cameras_3d = main_world.query_filtered::<(
&RenderEntity,
&Camera,
&Projection,
&mut TemporalAntiAliasing,
), (
With<Camera3d>,
With<TemporalJitter>,
With<DepthPrepass>,
With<MotionVectorPrepass>,
)>();

for (entity, camera, camera_projection, mut taa_settings) in
cameras_3d.iter_mut(&mut main_world)
{
let has_perspective_projection = matches!(camera_projection, Projection::Perspective(_));
if camera.is_active && has_perspective_projection {
commands.get_or_spawn(entity).insert(taa_settings.clone());
commands
.get_or_spawn(entity.id())
.insert(taa_settings.clone());
taa_settings.reset = false;
}
}
Expand Down
3 changes: 2 additions & 1 deletion crates/bevy_gizmos/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ use {
ShaderStages, ShaderType, VertexFormat,
},
renderer::RenderDevice,
world_sync::TemporaryRenderEntity,
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
},
bytemuck::cast_slice,
Expand All @@ -113,7 +114,6 @@ use {
any(feature = "bevy_pbr", feature = "bevy_sprite"),
))]
use bevy_render::render_resource::{VertexAttribute, VertexBufferLayout, VertexStepMode};

use bevy_time::Fixed;
use bevy_utils::TypeIdMap;
use config::{
Expand Down Expand Up @@ -459,6 +459,7 @@ fn extract_gizmo_data(
(*handle).clone_weak(),
#[cfg(any(feature = "bevy_pbr", feature = "bevy_sprite"))]
config::GizmoMeshConfig::from(config),
TemporaryRenderEntity,
));
}
}
Expand Down
7 changes: 7 additions & 0 deletions crates/bevy_pbr/src/bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use bevy_render::{
mesh::Mesh,
primitives::{CascadesFrusta, CubemapFrusta, Frustum},
view::{InheritedVisibility, ViewVisibility, Visibility},
world_sync::SyncToRenderWorld,
};
use bevy_transform::components::{GlobalTransform, Transform};

Expand Down Expand Up @@ -108,6 +109,8 @@ pub struct PointLightBundle {
pub inherited_visibility: InheritedVisibility,
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
pub view_visibility: ViewVisibility,
/// Marker component that indicates that its entity needs to be synchronized to the render world
pub sync: SyncToRenderWorld,
}

/// A component bundle for spot light entities
Expand All @@ -124,6 +127,8 @@ pub struct SpotLightBundle {
pub inherited_visibility: InheritedVisibility,
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
pub view_visibility: ViewVisibility,
/// Marker component that indicates that its entity needs to be synchronized to the render world
pub sync: SyncToRenderWorld,
}

/// A component bundle for [`DirectionalLight`] entities.
Expand All @@ -142,4 +147,6 @@ pub struct DirectionalLightBundle {
pub inherited_visibility: InheritedVisibility,
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
pub view_visibility: ViewVisibility,
/// Marker component that indicates that its entity needs to be synchronized to the render world
pub sync: SyncToRenderWorld,
}
14 changes: 9 additions & 5 deletions crates/bevy_pbr/src/cluster/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use bevy_render::{
UniformBuffer,
},
renderer::{RenderDevice, RenderQueue},
world_sync::RenderEntity,
Extract,
};
use bevy_utils::{hashbrown::HashSet, tracing::warn};
Expand Down Expand Up @@ -525,7 +526,8 @@ pub(crate) fn clusterable_object_order(
/// Extracts clusters from the main world from the render world.
pub fn extract_clusters(
mut commands: Commands,
views: Extract<Query<(Entity, &Clusters, &Camera)>>,
views: Extract<Query<(&RenderEntity, &Clusters, &Camera)>>,
mapper: Extract<Query<&RenderEntity>>,
) {
for (entity, clusters, camera) in &views {
if !camera.is_active {
Expand All @@ -544,13 +546,15 @@ pub fn extract_clusters(
cluster_objects.spot_light_count as u32,
));
for clusterable_entity in &cluster_objects.entities {
data.push(ExtractedClusterableObjectElement::ClusterableObjectEntity(
*clusterable_entity,
));
if let Ok(entity) = mapper.get(*clusterable_entity) {
data.push(ExtractedClusterableObjectElement::ClusterableObjectEntity(
entity.id(),
));
}
}
}

commands.get_or_spawn(entity).insert((
commands.get_or_spawn(entity.id()).insert((
ExtractedClusterableObjects { data },
ExtractedClusterConfig {
near: clusters.near,
Expand Down
3 changes: 3 additions & 0 deletions crates/bevy_pbr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,9 @@ impl Plugin for PbrPlugin {
)
.init_resource::<LightMeta>();

render_app.world_mut().observe(add_light_view_entities);
render_app.world_mut().observe(remove_light_view_entities);

let shadow_pass_node = ShadowPassNode::new(render_app.world_mut());
let mut graph = render_app.world_mut().resource_mut::<RenderGraph>();
let draw_3d_graph = graph.get_sub_graph_mut(Core3d).unwrap();
Expand Down
Loading

0 comments on commit 56f8e52

Please sign in to comment.