Skip to content

Commit 3b603cb

Browse files
committed
Add RenderLayer support to UI cameras
This enables having different UI per camera. With #5225, this enables having different interactive UIs per window. Although, to properly complete this, the focus system will need to account for RenderLayer of UI nodes.
1 parent 3d718ee commit 3b603cb

File tree

4 files changed

+100
-36
lines changed

4 files changed

+100
-36
lines changed

crates/bevy_ui/src/entity.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use bevy_ecs::{bundle::Bundle, prelude::Component};
88
use bevy_math::Vec2;
99
use bevy_render::{
1010
camera::{DepthCalculation, OrthographicProjection, WindowOrigin},
11-
view::Visibility,
11+
view::{RenderLayers, Visibility},
1212
};
1313
use bevy_text::Text;
1414
use bevy_transform::prelude::{GlobalTransform, Transform};
@@ -32,6 +32,8 @@ pub struct NodeBundle {
3232
pub global_transform: GlobalTransform,
3333
/// Describes the visibility properties of the node
3434
pub visibility: Visibility,
35+
/// The ui camera layers this node is visible in.
36+
pub render_layers: RenderLayers,
3537
}
3638

3739
/// A UI node that is an image
@@ -57,6 +59,8 @@ pub struct ImageBundle {
5759
pub global_transform: GlobalTransform,
5860
/// Describes the visibility properties of the node
5961
pub visibility: Visibility,
62+
/// The ui camera layers this image is visible in.
63+
pub render_layers: RenderLayers,
6064
}
6165

6266
/// A UI node that is text
@@ -78,6 +82,8 @@ pub struct TextBundle {
7882
pub global_transform: GlobalTransform,
7983
/// Describes the visibility properties of the node
8084
pub visibility: Visibility,
85+
/// The ui camera layers this text is visible in.
86+
pub render_layers: RenderLayers,
8187
}
8288

8389
impl Default for TextBundle {
@@ -91,6 +97,7 @@ impl Default for TextBundle {
9197
transform: Default::default(),
9298
global_transform: Default::default(),
9399
visibility: Default::default(),
100+
render_layers: Default::default(),
94101
}
95102
}
96103
}
@@ -118,6 +125,8 @@ pub struct ButtonBundle {
118125
pub global_transform: GlobalTransform,
119126
/// Describes the visibility properties of the node
120127
pub visibility: Visibility,
128+
/// The ui camera layers this button is visible in.
129+
pub render_layers: RenderLayers,
121130
}
122131

123132
/// Configuration for cameras related to UI.
@@ -135,6 +144,8 @@ pub struct UiCameraConfig {
135144
pub show_ui: bool,
136145
/// The position of the UI camera in UI space.
137146
pub position: Vec2,
147+
/// The ui camera layers this camera can see.
148+
pub ui_render_layers: RenderLayers,
138149
/// The projection data for the UI camera.
139150
///
140151
/// The code relies on this not being set,
@@ -162,6 +173,7 @@ impl Default for UiCameraConfig {
162173
Self {
163174
show_ui: true,
164175
position: Vec2::ZERO,
176+
ui_render_layers: Default::default(),
165177
projection: OrthographicProjection {
166178
far: UI_CAMERA_FAR,
167179
window_origin: WindowOrigin::BottomLeft,

crates/bevy_ui/src/render/mod.rs

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use bevy_render::{
2020
render_resource::*,
2121
renderer::{RenderDevice, RenderQueue},
2222
texture::Image,
23-
view::{ExtractedView, ViewUniforms, Visibility},
23+
view::{ExtractedView, RenderLayers, ViewUniforms, Visibility},
2424
RenderApp, RenderStage, RenderWorld,
2525
};
2626
use bevy_sprite::{Rect, SpriteAssetEvents, TextureAtlas};
@@ -166,6 +166,7 @@ pub struct ExtractedUiNode {
166166
pub image: Handle<Image>,
167167
pub atlas_size: Option<Vec2>,
168168
pub clip: Option<Rect>,
169+
pub render_layers: RenderLayers,
169170
}
170171

171172
#[derive(Default)]
@@ -182,12 +183,13 @@ pub fn extract_uinodes(
182183
&UiColor,
183184
&UiImage,
184185
&Visibility,
186+
&RenderLayers,
185187
Option<&CalculatedClip>,
186188
)>,
187189
) {
188190
let mut extracted_uinodes = render_world.resource_mut::<ExtractedUiNodes>();
189191
extracted_uinodes.uinodes.clear();
190-
for (uinode, transform, color, image, visibility, clip) in uinode_query.iter() {
192+
for (uinode, transform, color, image, visibility, render_layers, clip) in uinode_query.iter() {
191193
if !visibility.is_visible {
192194
continue;
193195
}
@@ -206,6 +208,7 @@ pub fn extract_uinodes(
206208
image,
207209
atlas_size: None,
208210
clip: clip.map(|clip| clip.clip),
211+
render_layers: *render_layers,
209212
});
210213
}
211214
}
@@ -224,6 +227,7 @@ const UI_CAMERA_TRANSFORM_OFFSET: f32 = -0.1;
224227
#[derive(Component, Debug)]
225228
pub struct UiCamera {
226229
pub entity: Entity,
230+
layers: RenderLayers,
227231
}
228232

229233
pub fn extract_default_ui_camera_view<T: Component>(
@@ -260,7 +264,10 @@ pub fn extract_default_ui_camera_view<T: Component>(
260264
height: physical_size.y,
261265
});
262266
commands.get_or_spawn(camera_entity).insert_bundle((
263-
UiCamera { entity: ui_camera },
267+
UiCamera {
268+
entity: ui_camera,
269+
layers: ui_config.ui_render_layers,
270+
},
264271
RenderPhase::<TransparentUi>::default(),
265272
));
266273
}
@@ -278,14 +285,15 @@ pub fn extract_text_uinodes(
278285
&GlobalTransform,
279286
&Text,
280287
&Visibility,
288+
&RenderLayers,
281289
Option<&CalculatedClip>,
282290
)>,
283291
) {
284292
let mut extracted_uinodes = render_world.resource_mut::<ExtractedUiNodes>();
285293

286294
let scale_factor = windows.scale_factor(WindowId::primary()) as f32;
287295

288-
for (entity, uinode, transform, text, visibility, clip) in uinode_query.iter() {
296+
for (entity, uinode, transform, text, visibility, render_layers, clip) in uinode_query.iter() {
289297
if !visibility.is_visible {
290298
continue;
291299
}
@@ -321,6 +329,7 @@ pub fn extract_text_uinodes(
321329
image: texture,
322330
atlas_size,
323331
clip: clip.map(|clip| clip.clip),
332+
render_layers: *render_layers,
324333
});
325334
}
326335
}
@@ -358,8 +367,10 @@ const QUAD_VERTEX_POSITIONS: [Vec3; 4] = [
358367

359368
const QUAD_INDICES: [usize; 6] = [0, 2, 3, 0, 1, 2];
360369

370+
// Ui nodes are batches per image and per layer
361371
#[derive(Component)]
362372
pub struct UiBatch {
373+
pub layers: RenderLayers,
363374
pub range: Range<u32>,
364375
pub image: Handle<Image>,
365376
pub z: f32,
@@ -382,20 +393,26 @@ pub fn prepare_uinodes(
382393
let mut start = 0;
383394
let mut end = 0;
384395
let mut current_batch_handle = Default::default();
396+
let mut current_batch_layers = Default::default();
385397
let mut last_z = 0.0;
386398
for extracted_uinode in &extracted_uinodes.uinodes {
387-
if current_batch_handle != extracted_uinode.image {
399+
let same_layers = current_batch_layers == extracted_uinode.render_layers;
400+
let same_handle = current_batch_handle == extracted_uinode.image;
401+
if !same_handle || !same_layers {
388402
if start != end {
389403
commands.spawn_bundle((UiBatch {
404+
layers: current_batch_layers,
390405
range: start..end,
391406
image: current_batch_handle,
392407
z: last_z,
393408
},));
394409
start = end;
395410
}
411+
current_batch_layers = extracted_uinode.render_layers;
396412
current_batch_handle = extracted_uinode.image.clone_weak();
397413
}
398414

415+
// TODO: the following code is hard to grasp, a refactor would be welcome :)
399416
let uinode_rect = extracted_uinode.rect;
400417
let rect_size = uinode_rect.size().extend(1.0);
401418

@@ -473,14 +490,14 @@ pub fn prepare_uinodes(
473490
color: extracted_uinode.color.as_linear_rgba_f32(),
474491
});
475492
}
476-
477493
last_z = extracted_uinode.transform.w_axis[2];
478494
end += QUAD_INDICES.len() as u32;
479495
}
480496

481497
// if start != end, there is one last batch to process
482498
if start != end {
483499
commands.spawn_bundle((UiBatch {
500+
layers: current_batch_layers,
484501
range: start..end,
485502
image: current_batch_handle,
486503
z: last_z,
@@ -507,7 +524,7 @@ pub fn queue_uinodes(
507524
mut image_bind_groups: ResMut<UiImageBindGroups>,
508525
gpu_images: Res<RenderAssets<Image>>,
509526
ui_batches: Query<(Entity, &UiBatch)>,
510-
mut views: Query<&mut RenderPhase<TransparentUi>>,
527+
mut views: Query<(&mut RenderPhase<TransparentUi>, &UiCamera)>,
511528
events: Res<SpriteAssetEvents>,
512529
) {
513530
// If an image has changed, the GpuImage has (probably) changed
@@ -531,8 +548,11 @@ pub fn queue_uinodes(
531548
}));
532549
let draw_ui_function = draw_functions.read().get_id::<DrawUi>().unwrap();
533550
let pipeline = pipelines.specialize(&mut pipeline_cache, &ui_pipeline, UiPipelineKey {});
534-
for mut transparent_phase in views.iter_mut() {
551+
for (mut transparent_phase, cam_data) in views.iter_mut() {
535552
for (entity, batch) in ui_batches.iter() {
553+
if !batch.layers.intersects(&cam_data.layers) {
554+
continue;
555+
}
536556
image_bind_groups
537557
.values
538558
.entry(batch.image.clone_weak())

crates/bevy_ui/src/render/render_pass.rs

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,18 @@ use bevy_render::{
1717
use bevy_utils::FloatOrd;
1818

1919
pub struct UiPassNode {
20-
ui_view_query:
20+
view_query:
2121
QueryState<(&'static RenderPhase<TransparentUi>, &'static ViewTarget), With<ExtractedView>>,
22-
default_camera_view_query: QueryState<&'static UiCamera>,
22+
ui_camera_query: QueryState<&'static UiCamera>,
2323
}
2424

2525
impl UiPassNode {
2626
pub const IN_VIEW: &'static str = "view";
2727

2828
pub fn new(world: &mut World) -> Self {
2929
Self {
30-
ui_view_query: world.query_filtered(),
31-
default_camera_view_query: world.query(),
30+
view_query: world.query_filtered(),
31+
ui_camera_query: world.query(),
3232
}
3333
}
3434
}
@@ -39,8 +39,8 @@ impl Node for UiPassNode {
3939
}
4040

4141
fn update(&mut self, world: &mut World) {
42-
self.ui_view_query.update_archetypes(world);
43-
self.default_camera_view_query.update_archetypes(world);
42+
self.view_query.update_archetypes(world);
43+
self.ui_camera_query.update_archetypes(world);
4444
}
4545

4646
fn run(
@@ -49,10 +49,10 @@ impl Node for UiPassNode {
4949
render_context: &mut RenderContext,
5050
world: &World,
5151
) -> Result<(), NodeRunError> {
52-
let input_view_entity = graph.get_input_entity(Self::IN_VIEW)?;
52+
let camera_view = graph.get_input_entity(Self::IN_VIEW)?;
5353

5454
let (transparent_phase, target) =
55-
if let Ok(result) = self.ui_view_query.get_manual(world, input_view_entity) {
55+
if let Ok(result) = self.view_query.get_manual(world, camera_view) {
5656
result
5757
} else {
5858
return Ok(());
@@ -61,14 +61,12 @@ impl Node for UiPassNode {
6161
if transparent_phase.items.is_empty() {
6262
return Ok(());
6363
}
64-
let view_entity = if let Ok(default_view) = self
65-
.default_camera_view_query
66-
.get_manual(world, input_view_entity)
67-
{
68-
default_view.entity
69-
} else {
70-
input_view_entity
71-
};
64+
let ui_view_entity =
65+
if let Ok(ui_view) = self.ui_camera_query.get_manual(world, camera_view) {
66+
ui_view.entity
67+
} else {
68+
return Ok(());
69+
};
7270

7371
let pass_descriptor = RenderPassDescriptor {
7472
label: Some("ui_pass"),
@@ -93,7 +91,7 @@ impl Node for UiPassNode {
9391
let mut tracked_pass = TrackedRenderPass::new(render_pass);
9492
for item in &transparent_phase.items {
9593
let draw_function = draw_functions.get_mut(item.draw_function).unwrap();
96-
draw_function.draw(world, &mut tracked_pass, view_entity, item);
94+
draw_function.draw(world, &mut tracked_pass, ui_view_entity, item);
9795
}
9896
Ok(())
9997
}

examples/window/multiple_windows.rs

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use bevy::{
44
prelude::*,
5-
render::camera::RenderTarget,
5+
render::{camera::RenderTarget, view::RenderLayers},
66
window::{CreateWindow, PresentMode, WindowId},
77
};
88

@@ -30,10 +30,15 @@ fn setup(
3030
..default()
3131
});
3232
// main camera
33-
commands.spawn_bundle(Camera3dBundle {
34-
transform: Transform::from_xyz(0.0, 0.0, 6.0).looking_at(Vec3::ZERO, Vec3::Y),
35-
..default()
36-
});
33+
commands
34+
.spawn_bundle(Camera3dBundle {
35+
transform: Transform::from_xyz(0.0, 0.0, 6.0).looking_at(Vec3::ZERO, Vec3::Y),
36+
..default()
37+
})
38+
.insert(UiCameraConfig {
39+
ui_render_layers: RenderLayers::layer(1),
40+
..default()
41+
});
3742

3843
let window_id = WindowId::new();
3944

@@ -50,12 +55,41 @@ fn setup(
5055
});
5156

5257
// second window camera
53-
commands.spawn_bundle(Camera3dBundle {
54-
transform: Transform::from_xyz(6.0, 0.0, 0.0).looking_at(Vec3::ZERO, Vec3::Y),
55-
camera: Camera {
56-
target: RenderTarget::Window(window_id),
58+
commands
59+
.spawn_bundle(Camera3dBundle {
60+
transform: Transform::from_xyz(6.0, 0.0, 0.0).looking_at(Vec3::ZERO, Vec3::Y),
61+
camera: Camera {
62+
target: RenderTarget::Window(window_id),
63+
..default()
64+
},
5765
..default()
58-
},
66+
})
67+
.insert(UiCameraConfig {
68+
ui_render_layers: RenderLayers::layer(2),
69+
..default()
70+
});
71+
let text_style = TextStyle {
72+
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
73+
font_size: 100.0,
74+
color: Color::WHITE,
75+
};
76+
let align = TextAlignment {
77+
horizontal: HorizontalAlign::Center,
78+
..default()
79+
};
80+
commands.spawn_bundle(TextBundle {
81+
text: Text::with_section("Face", text_style.clone(), align),
82+
render_layers: RenderLayers::layer(1),
83+
..default()
84+
});
85+
commands.spawn_bundle(TextBundle {
86+
text: Text::with_section("Profile", text_style.clone(), align),
87+
render_layers: RenderLayers::layer(2),
88+
..default()
89+
});
90+
commands.spawn_bundle(TextBundle {
91+
text: Text::with_section("view", text_style, align),
92+
render_layers: RenderLayers::all(),
5993
..default()
6094
});
6195
}

0 commit comments

Comments
 (0)