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

[Merged by Bors] - use marker components for cameras instead of name strings #3635

Conversation

jakobhellermann
Copy link
Contributor

Problem

  • whenever you want more than one of the builtin cameras (for example multiple windows, split screen, portals), you need to add a render graph node that executes the correct sub graph, extract the camera into the render world and add the correct RenderPhase<T> components
  • querying for the 3d camera is annoying because you need to compare the camera's name to e.g. CameraPlugin::CAMERA_3d

Solution

  • Introduce the marker types Camera3d, Camera2d and CameraUi
    -> Query<&mut Transform, With<Camera3d>> works
  • PerspectiveCameraBundle::new_3d() and PerspectiveCameraBundle::<Camera3d>::default() contain the Camera3d marker
  • OrthographicCameraBundle::new_3d() has Camera3d, OrthographicCameraBundle::new_2d() has Camera2d
  • remove ActiveCameras, ExtractedCameraNames
  • run 2d, 3d and ui passes for every camera of their respective marker
    -> no custom setup for multiple windows example needed

Open questions

  • do we need a replacement for ActiveCameras? What about a component ActiveCamera { is_active: bool } similar to Visibility?

@jakobhellermann jakobhellermann added the A-Rendering Drawing game state to the screen label Jan 10, 2022
@github-actions github-actions bot added the S-Needs-Triage This issue needs to be labelled label Jan 10, 2022
@jakobhellermann jakobhellermann added C-Enhancement A new feature C-Usability A simple quality-of-life change that makes Bevy easier to use labels Jan 10, 2022
@james7132 james7132 removed the S-Needs-Triage This issue needs to be labelled label Jan 10, 2022
@alice-i-cecile
Copy link
Member

Closes #1854. Previously attempted in #1888.

@alice-i-cecile alice-i-cecile added C-Code-Quality A section of code that is hard to understand or change and removed C-Enhancement A new feature labels Jan 10, 2022
@alice-i-cecile
Copy link
Member

We should think about how to coordinate this with #3412. @HackerFoo, do you have a preference?


/// Component bundle for camera entities with perspective projection
///
/// Use this for 3D rendering.
#[derive(Bundle)]
pub struct PerspectiveCameraBundle {
pub struct PerspectiveCameraBundle<M: Component> {
Copy link
Member

Choose a reason for hiding this comment

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

I love this style of API; I've used it before in my games and it's great.

pub fn new_3d() -> Self {
Default::default()
PerspectiveCameraBundle::new()
Copy link
Member

Choose a reason for hiding this comment

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

This feels a bit weird to me? Why not just use PerspectiveCameraBundle<Camera3d>::default() instead? And then specialize the default impls based on which generic is passed in.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wanted to have only one Default impl so that PerspectiveCameraBundle { ..Default::default() } works without complaining about an uninferred type parameter.

@HackerFoo
Copy link
Contributor

We should think about how to coordinate this with #3412. @HackerFoo, do you have a preference?

I'd like #3412 and #3568 to be considered, because they make it useful to run the draw_3d_graph subgraph on multiple cameras. It would be useful to attach some identifier to the types, to somehow control the layering order, and make other minor modifications to passes. I might have more feedback after reading through this PR.

I also have an idea for specifying the pass order in Camera, to allow e.g. skyboxes (behind everything else) and UI layers (in front.)

@jakobhellermann
Copy link
Contributor Author

We should think about how to coordinate this with #3412. HackerFoo, do you have a preference?

Rebasing one on top of the other once it is merged shouldnt be too hard, the changes are in similar code paths but are mostly unrelated.

@alice-i-cecile
Copy link
Member

This is looking much cleaner. I'm really happy to see this finally get cleaned up. Just a few nits :)

@superdump
Copy link
Contributor

do we need a replacement for ActiveCameras? What about a component ActiveCamera { is_active: bool } similar to Visibility?

And here’s another instance of the same question. I’ve seen this 4 times in the past two days. :) @mockersf suggested adding enable/disable for lights while I was doing some other things but it’s not trivial imo because of the question of whether to have something like a ZST for the less likely case (i.e. an empty InactiveCamera component) which introduces archetype fragmentation but is directly queryable, or always adding an ActiveCamera { is_active: bool } component and forcing visiting all entities just to find one or the other subset, and always using the storage for the component. @cart suggested adding Visibility to the light bundles but Visibility feels like the wrong property for lights. I felt like something more generically usable everywhere like Enabled { is_enabled: bool } or Active { is_active: bool } or some better name, for all cases. Visibility, lights being enabled, cameras being active, whatever. And then when we make an editor there is one component that handles them all in a sensible and consistent way. But, I didn’t proceed because there’s still the question of the component with a bool member or the marker component for the less likely case. It feels like marker components should be used only where iteration performance is a top priority because I fear the combinatoric explosion and subsequent fragmentation if we use too many marker components… :/ I don’t know

@alice-i-cecile
Copy link
Member

alice-i-cecile commented Jan 10, 2022

I felt like something more generically usable everywhere like Enabled { is_enabled: bool } or Active { is_active: bool } or some better name, for all cases

I'm in favor of this: being able to standardize the ecosystem around this pattern is super useful.

But, I didn’t proceed because there’s still the question of the component with a bool member or the marker component for the less likely case. It feels like marker components should be used only where iteration performance is a top priority because I fear the combinatoric explosion and subsequent fragmentation if we use too many marker components… :/

So, IMO archetype fragmentation is less important performance-wise than the requirement to constantly check values. I'd prefer a marker component: if we use sparse-set storage it'll be faster to add and remove too. I would be fine with either Enabled/Active or Disabled/Inactive as the marker, depending on which pattern you think is clearer / more useful for your rendering use cases.

Overall I think a Inactive marker component is going to be the clearest for debugging purposes, and the most natural to reuse in other contexts. I'd probably toss it in bevy_ecs; it'll be reused all over the place.

@HackerFoo
Copy link
Contributor

HackerFoo commented Jan 10, 2022

How about RenderLayers::none() to disable a camera? That should already work, but maybe isn't optimized. I like RenderLayers because you can define complex relations between cameras and renderable entities.

Similar mechanisms are used for physics (e.g. in Rapier.)

@HackerFoo
Copy link
Contributor

HackerFoo commented Jan 10, 2022

It would be useful to attach some identifier to the types

I suppose this could be done with some other component if needed.

@superdump
Copy link
Contributor

I felt like something more generically usable everywhere like Enabled { is_enabled: bool } or Active { is_active: bool } or some better name, for all cases

I'm in favor of this: being able to standardize the ecosystem around this pattern is super useful.

👍

But, I didn’t proceed because there’s still the question of the component with a bool member or the marker component for the less likely case. It feels like marker components should be used only where iteration performance is a top priority because I fear the combinatoric explosion and subsequent fragmentation if we use too many marker components… :/

So, IMO archetype fragmentation is less important performance-wise than the requirement to constantly check values. I'd prefer a marker component: if we use sparse-set storage it'll be faster to add and remove too. I would be fine with either Enabled/Active or Disabled/Inactive as the marker, depending on which pattern you think is clearer / more useful for your rendering use cases.

I didn’t know this so I hoped those who know more about the ECS performance impacts would speak up so thanks! :)

Overall I think a Inactive marker component is going to be the clearest for debugging purposes, and the most natural to reuse in other contexts. I'd probably toss it in bevy_ecs; it'll be reused all over the place.

Sounds good to me. I generally prefer to have positive logic in the sense of avoiding thinking “if !thing {} else {}” but in these marker component cases it feels like, from an ECS perspective, it makes more sense to mark the less frequent case. That’s how I ended up with NotShadowCaster and NotShadowReceiver. Following that logic and assuming that relatively few entities would be disabled, marking as Inactive makes sense to me. :)

@alice-i-cecile
Copy link
Member

Following that logic and assuming that relatively few entities would be disabled, marking as Inactive makes sense to me. :)

I think the strategy here is then:

  1. Create the Inactive marker in bevy_ecs in this PR.
  2. Swap all of the other rendering places where this could be used to Inactive as a seperate commit in this PR.
  3. Discuss automatically excluding Inactive entities from queries by default in another issue / PR.

@HackerFoo
Copy link
Contributor

HackerFoo commented Jan 11, 2022

It would be useful to be able wrap any component in Deactivatable<T: Component> (I know, terrible name) with a command to set if the component is active. Or maybe it could be a trait? That way it's similar to change detection, which works on all components, but it would be opt-in, unless it's cheap enough to implement for all components.

I suggest not tying it to this PR, though. The existing component RenderLayers should work fine for now, in my opinion.

@alice-i-cecile
Copy link
Member

Yeah okay @HackerFoo is right: we should use the simplest possible solution for this PR, and then a better solution later.

@alice-i-cecile alice-i-cecile added the S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it label Jan 18, 2022
@HackerFoo
Copy link
Contributor

An exclusive marker component would be generally useful. Maybe it could be both a resource containing just an optional entity, and a component, where inserting it updates the resource and removes the component from the previous entity.

I've used both marker components and resources to identify specific entities. Marker components are more flexible and can't have invalid entities, but resources are probably faster and are guaranteed unique.

@superdump
Copy link
Contributor

Another problem with marker components is that they require command updates and if you have system dependencies with a stage that’s a problem.

Why can there only be one camera of one type in a scene? What about split screen local for example?

@cart
Copy link
Member

cart commented Mar 10, 2022

Why can there only be one camera of one type in a scene? What about split screen local for example?

There can only be one active camera of one type per render target. Split screen would involve at least two render targets: either two targets with custom viewports or three targets .... two for each split and the third for compositing. Cameras will correlate to single render targets. "Exclusive marker components" wouldn't cover this case. "Non-exclusive marker components" would work, because you could iterate the active list and filter down to the target currently being rendered. "Exclusive resource" also wouldn't work for multiple render targets. My plan there was either to make the ActiveCamera<T> a resource for the "default target" and a component for "custom targets", or to make all targets entities (for consistency) and insert the default target entity by default.

@cart
Copy link
Member

cart commented Mar 11, 2022

I'm pretty partial to this design: "every RenderTarget is an entity, every RenderTarget has ActiveCamera<T>(Option<Entity>), which defaults to the first camera in the query, if none is set". It provides solid zero-user-effort default behavior and also provides a single, easy-to-query, canonical place to find and set each target's active camera. This would even still work with scenes, provided the RenderTarget added to a scene file can reference the active camera (which is currently possible for scenes + will
ultimately be possible for nested scenes as well). This seems like a solid tradeoff, given the distributed / nestable nature of scenes.

Given that right now RenderTarget entities aren't a thing (im working on those now), I think building a hard-coded ActiveCamera<T> resource first in this PR, then migrating to components + entities later is the way to go. I'll get started on that now, but feel free to continue discussing designs.

@HackerFoo
Copy link
Contributor

There can only be one active camera of one type per render target.

The example in #3552 uses two cameras with the same render target (RenderTarget::Window(WindowId::primary()).) Furthermore, my app (Noumenal) uses two cameras in order to layer the 3D components of the UI on top.

As further motivation, I've considered updating the cameras at different rates (the first is mostly static without user interaction), so I've been thinking about how to store the colors from the first pass and only rendering the second. I could use compositing, but passes are expensive on mobile devices - I saved ~30% GPU time by just eliminating the clear pass, and a bunch more by eliminating other passes.

Nonetheless, rendering multiple cameras directly to a single texture has been useful for me in practice.

@cart
Copy link
Member

cart commented Mar 11, 2022

Nonetheless, rendering multiple cameras directly to a single texture has been useful for me in practice.

This will still be possible with the outlined plan, as RenderTargets won't be "exclusive". You can create a second RenderTarget that also targets WindowId::primary(), then assign your second camera (of the same type) to it. They can both be "active", they will just be active in different targets.

I like this design because it embraces the fact that the core pipeline is designed around "one active camera of a given type at a time", and lets us treat cameras as "simple logical views" instead of trying to treat them as "owners of render pass configuration".

@cart
Copy link
Member

cart commented Mar 11, 2022

Just added an ActiveCamera<T> resource and CameraTypePlugin<T> (for easily adding new camera types). I think this is probably the right general direction, but I'm down to continue discussing the cameras/passes/render_targets relationships more. I do think we should merge the ActiveCamera approach because it is a "lateral move" ... it doesn't change the current paradigm, it just adapts it to use marker components. That way we can merge this PR asap without worrying too much about breaking people, and we can start building new features on top of marker components.

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 would prefer basic docs throughout this PR, but I can live without them and add them in a follow-up.

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 think this a good approach, and the code quality is good. LGTM.

crates/bevy_render/src/camera/camera.rs Outdated Show resolved Hide resolved
examples/3d/render_to_texture.rs Show resolved Hide resolved
@cart
Copy link
Member

cart commented Mar 12, 2022

bors r+

bors bot pushed a commit that referenced this pull request Mar 12, 2022
**Problem**
- whenever you want more than one of the builtin cameras (for example multiple windows, split screen, portals), you need to add a render graph node that executes the correct sub graph, extract the camera into the render world and add the correct `RenderPhase<T>` components
- querying for the 3d camera is annoying because you need to compare the camera's name to e.g. `CameraPlugin::CAMERA_3d`

**Solution**
- Introduce the marker types `Camera3d`, `Camera2d` and `CameraUi`
-> `Query<&mut Transform, With<Camera3d>>` works
- `PerspectiveCameraBundle::new_3d()` and `PerspectiveCameraBundle::<Camera3d>::default()` contain the `Camera3d` marker
- `OrthographicCameraBundle::new_3d()` has `Camera3d`, `OrthographicCameraBundle::new_2d()` has `Camera2d`
- remove `ActiveCameras`, `ExtractedCameraNames`
- run 2d, 3d and ui passes for every camera of their respective marker
-> no custom setup for multiple windows example needed

**Open questions**
- do we need a replacement for `ActiveCameras`? What about a component `ActiveCamera { is_active: bool }` similar to `Visibility`?

Co-authored-by: Carter Anderson <mcanders1@gmail.com>
@bors bors bot changed the title use marker components for cameras instead of name strings [Merged by Bors] - use marker components for cameras instead of name strings Mar 12, 2022
@bors bors bot closed this Mar 12, 2022
@jakobhellermann jakobhellermann deleted the camera-marker-components branch March 31, 2022 17:51
bors bot pushed a commit that referenced this pull request Apr 1, 2022
The example was broken in #3635 when the `ActiveCamera` logic was introduced, after which there could only be one active `Camera3d` globally.
Ideally there could be one `Camera3d` per render target, not globally, but that isn't the case yet.

To fix the example, we need to
- don't use `Camera3d` twice, add a new `SecondWindowCamera3d` marker
- add the `CameraTypePlugin::<SecondWindowCamera3d>`
- extract the correct `RenderPhase`s
- add a 3d pass driver node for the secondary camera

Fixes #4378

Co-authored-by: Jakob Hellermann <hellermann@sipgate.de>
aevyrie pushed a commit to aevyrie/bevy that referenced this pull request Jun 7, 2022
…#3635)

**Problem**
- whenever you want more than one of the builtin cameras (for example multiple windows, split screen, portals), you need to add a render graph node that executes the correct sub graph, extract the camera into the render world and add the correct `RenderPhase<T>` components
- querying for the 3d camera is annoying because you need to compare the camera's name to e.g. `CameraPlugin::CAMERA_3d`

**Solution**
- Introduce the marker types `Camera3d`, `Camera2d` and `CameraUi`
-> `Query<&mut Transform, With<Camera3d>>` works
- `PerspectiveCameraBundle::new_3d()` and `PerspectiveCameraBundle::<Camera3d>::default()` contain the `Camera3d` marker
- `OrthographicCameraBundle::new_3d()` has `Camera3d`, `OrthographicCameraBundle::new_2d()` has `Camera2d`
- remove `ActiveCameras`, `ExtractedCameraNames`
- run 2d, 3d and ui passes for every camera of their respective marker
-> no custom setup for multiple windows example needed

**Open questions**
- do we need a replacement for `ActiveCameras`? What about a component `ActiveCamera { is_active: bool }` similar to `Visibility`?

Co-authored-by: Carter Anderson <mcanders1@gmail.com>
aevyrie pushed a commit to aevyrie/bevy that referenced this pull request Jun 7, 2022
The example was broken in bevyengine#3635 when the `ActiveCamera` logic was introduced, after which there could only be one active `Camera3d` globally.
Ideally there could be one `Camera3d` per render target, not globally, but that isn't the case yet.

To fix the example, we need to
- don't use `Camera3d` twice, add a new `SecondWindowCamera3d` marker
- add the `CameraTypePlugin::<SecondWindowCamera3d>`
- extract the correct `RenderPhase`s
- add a 3d pass driver node for the secondary camera

Fixes bevyengine#4378

Co-authored-by: Jakob Hellermann <hellermann@sipgate.de>
ItsDoot pushed a commit to ItsDoot/bevy that referenced this pull request Feb 1, 2023
…#3635)

**Problem**
- whenever you want more than one of the builtin cameras (for example multiple windows, split screen, portals), you need to add a render graph node that executes the correct sub graph, extract the camera into the render world and add the correct `RenderPhase<T>` components
- querying for the 3d camera is annoying because you need to compare the camera's name to e.g. `CameraPlugin::CAMERA_3d`

**Solution**
- Introduce the marker types `Camera3d`, `Camera2d` and `CameraUi`
-> `Query<&mut Transform, With<Camera3d>>` works
- `PerspectiveCameraBundle::new_3d()` and `PerspectiveCameraBundle::<Camera3d>::default()` contain the `Camera3d` marker
- `OrthographicCameraBundle::new_3d()` has `Camera3d`, `OrthographicCameraBundle::new_2d()` has `Camera2d`
- remove `ActiveCameras`, `ExtractedCameraNames`
- run 2d, 3d and ui passes for every camera of their respective marker
-> no custom setup for multiple windows example needed

**Open questions**
- do we need a replacement for `ActiveCameras`? What about a component `ActiveCamera { is_active: bool }` similar to `Visibility`?

Co-authored-by: Carter Anderson <mcanders1@gmail.com>
ItsDoot pushed a commit to ItsDoot/bevy that referenced this pull request Feb 1, 2023
The example was broken in bevyengine#3635 when the `ActiveCamera` logic was introduced, after which there could only be one active `Camera3d` globally.
Ideally there could be one `Camera3d` per render target, not globally, but that isn't the case yet.

To fix the example, we need to
- don't use `Camera3d` twice, add a new `SecondWindowCamera3d` marker
- add the `CameraTypePlugin::<SecondWindowCamera3d>`
- extract the correct `RenderPhase`s
- add a 3d pass driver node for the secondary camera

Fixes bevyengine#4378

Co-authored-by: Jakob Hellermann <hellermann@sipgate.de>
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-Breaking-Change A breaking change to Bevy's public API that needs to be noted in a migration guide C-Code-Quality A section of code that is hard to understand or change C-Usability A simple quality-of-life change that makes Bevy easier to use 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.

6 participants