Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type safe retained render world #15756

Merged
merged 18 commits into from
Oct 10, 2024

Conversation

tychedelia
Copy link
Contributor

@tychedelia tychedelia commented Oct 9, 2024

Objective

In the Render World, there are a number of collections that are derived from Main World entities and are used to drive rendering. The most notable are:

  • VisibleEntities, which is generated in the check_visibility system and contains visible entities for a view.
  • ExtractedInstances, which maps entity ids to asset ids.

In the old model, these collections were trivially kept in sync -- any extracted phase item could look itself up because the render entity id was guaranteed to always match the corresponding main world id.

After #15320, this became much more complicated, and was leading to a number of subtle bugs in the Render World. The main rendering systems, i.e. queue_material_meshes and queue_material2d_meshes, follow a similar pattern:

for visible_entity in visible_entities.iter::<With<Mesh2d>>() {
    let Some(mesh_instance) = render_mesh_instances.get_mut(visible_entity) else {
        continue;
    };
            
    // Look some more stuff up and specialize the pipeline...
            
    let bin_key = Opaque2dBinKey {
        pipeline: pipeline_id,
        draw_function: draw_opaque_2d,
        asset_id: mesh_instance.mesh_asset_id.into(),
        material_bind_group_id: material_2d.get_bind_group_id().0,
    };
    opaque_phase.add(
        bin_key,
        *visible_entity,
        BinnedRenderPhaseType::mesh(mesh_instance.automatic_batching),
    );
}

In this case, visible_entities and render_mesh_instances are both collections that are created and keyed by Main World entity ids, and so this lookup happens to work by coincidence. However, there is a major unintentional bug here: namely, because visible_entities is a collection of Main World ids, the phase item being queued is created with a Main World id rather than its correct Render World id.

This happens to not break mesh rendering because the render commands used for drawing meshes do not access the ItemQuery parameter, but demonstrates the confusion that is now possible: our UI phase items are correctly being queued with Render World ids while our meshes aren't.

Additionally, this makes it very easy and error prone to use the wrong entity id to look up things like assets. For example, if instead we ignored visibility checks and queued our meshes via a query, we'd have to be extra careful to use &MainEntity instead of the natural Entity.

Solution

Make all collections that are derived from Main World data use MainEntity as their key, to ensure type safety and avoid accidentally looking up data with the wrong entity id:

pub type MainEntityHashMap<V> = hashbrown::HashMap<MainEntity, V, EntityHash>;

Additionally, we make all PhaseItem be able to provide both their Main and Render World ids, to allow render phase implementors maximum flexibility as to what id should be used to look up data.

You can think of this like tracking at the type level whether something in the Render World should use it's "primary key", i.e. entity id, or needs to use a foreign key, i.e. MainEntity.

Testing

TODO:

This will require extensive testing to make sure things didn't break! Additionally, some extraction logic has become more complicated and needs to be checked for regressions.

Migration Guide

With the advent of the retained render world, collections that contain references to Entity that are extracted into the render world have been changed to contain MainEntity in order to prevent errors where a render world entity id is used to look up an item by accident. Custom rendering code may need to be changed to query for &MainEntity in order to look up the correct item from such a collection. Additionally, users who implement their own extraction logic for collections of main world entity should strongly consider extracting into a different collection that uses MainEntity as a key.

Additionally, render phases now require specifying both the Entity and MainEntity for a given PhaseItem. Custom render phases should ensure MainEntity is available when queuing a phase item.

@tychedelia tychedelia added C-Bug An unexpected or incorrect behavior A-Rendering Drawing game state to the screen S-Needs-Testing Testing must be done before this is safe to merge labels Oct 9, 2024
@tychedelia tychedelia added this to the 0.15 milestone Oct 9, 2024
Copy link
Contributor

@IceSentry IceSentry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The approach used in the PR make sense to me. It's a bit unfortuante that we need to do all that but I think it's the easiest fix for now.

LGTM once CI is green

@tychedelia tychedelia marked this pull request as ready for review October 9, 2024 05:32
@tychedelia
Copy link
Contributor Author

Blocked on #15755

@alice-i-cecile alice-i-cecile added the C-Code-Quality A section of code that is hard to understand or change label Oct 9, 2024
@BenjaminBrienen
Copy link
Contributor

BenjaminBrienen commented Oct 10, 2024

The blocker is fixed so after a merge and a migration guide this should be ready

@tychedelia tychedelia added S-Needs-Review Needs reviewer attention (from anyone!) to move forward and removed S-Needs-Testing Testing must be done before this is safe to merge labels Oct 10, 2024
Copy link
Contributor

@NiseVoid NiseVoid left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would maybe be a bit nicer if it was harder to turn entities into MainEntity/RenderEntity incorrectly (trough .into()s) and getting typed a typed RenderEntity back in more places, but that's probably not worth blocking on since we probably want a nicer fix in the long term anyway.

@alice-i-cecile alice-i-cecile added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Oct 10, 2024
Copy link
Member

@alice-i-cecile alice-i-cecile left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that this is substantially more error resistant. Thank you.

@alice-i-cecile alice-i-cecile added this pull request to the merge queue Oct 10, 2024
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Oct 10, 2024
@alice-i-cecile alice-i-cecile added this pull request to the merge queue Oct 10, 2024
Merged via the queue into bevyengine:main with commit dd812b3 Oct 10, 2024
26 checks passed
@tim-blackbird tim-blackbird mentioned this pull request Oct 10, 2024
github-merge-queue bot pushed a commit that referenced this pull request Oct 10, 2024
# Objective

- Immediate mode gizmos don't have a main world entity but the phase
items require `MainEntity` since #15756

## Solution

- Add a dummy `MainEntity` component.

## Testing

Both the `3d_gizmos` and `2d_gizmos` examples show gizmos again
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Rendering Drawing game state to the screen C-Bug An unexpected or incorrect behavior C-Code-Quality A section of code that is hard to understand or change S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

5 participants