Skip to content

Commit 31155fe

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 44e9cd4 commit 31155fe

File tree

5 files changed

+137
-109
lines changed

5 files changed

+137
-109
lines changed

crates/bevy_ui/src/entity.rs

Lines changed: 19 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,10 @@ use crate::{
44
widget::{Button, ImageMode},
55
CalculatedSize, FocusPolicy, Interaction, Node, Style, UiColor, UiImage,
66
};
7-
use bevy_ecs::{
8-
bundle::Bundle,
9-
prelude::{Component, With},
10-
query::QueryItem,
11-
};
7+
use bevy_ecs::{bundle::Bundle, prelude::Component};
128
use bevy_render::{
13-
camera::Camera, extract_component::ExtractComponent, prelude::ComputedVisibility,
14-
view::Visibility,
9+
prelude::ComputedVisibility,
10+
view::{RenderLayers, Visibility},
1511
};
1612
use bevy_text::Text;
1713
use bevy_transform::prelude::{GlobalTransform, Transform};
@@ -37,6 +33,8 @@ pub struct NodeBundle {
3733
pub visibility: Visibility,
3834
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
3935
pub computed_visibility: ComputedVisibility,
36+
/// The UI camera layers this node is visible in.
37+
pub render_layers: RenderLayers,
4038
}
4139

4240
/// A UI node that is an image
@@ -64,6 +62,8 @@ pub struct ImageBundle {
6462
pub visibility: Visibility,
6563
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
6664
pub computed_visibility: ComputedVisibility,
65+
/// The ui camera layers this image is visible in.
66+
pub render_layers: RenderLayers,
6767
}
6868

6969
/// A UI node that is text
@@ -87,6 +87,8 @@ pub struct TextBundle {
8787
pub visibility: Visibility,
8888
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
8989
pub computed_visibility: ComputedVisibility,
90+
/// The ui camera layers this text is visible in.
91+
pub render_layers: RenderLayers,
9092
}
9193

9294
impl Default for TextBundle {
@@ -101,12 +103,13 @@ impl Default for TextBundle {
101103
global_transform: Default::default(),
102104
visibility: Default::default(),
103105
computed_visibility: Default::default(),
106+
render_layers: Default::default(),
104107
}
105108
}
106109
}
107110

108111
/// A UI node that is a button
109-
#[derive(Bundle, Clone, Debug)]
112+
#[derive(Bundle, Clone, Debug, Default)]
110113
pub struct ButtonBundle {
111114
/// Describes the size of the node
112115
pub node: Node,
@@ -130,25 +133,10 @@ pub struct ButtonBundle {
130133
pub visibility: Visibility,
131134
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
132135
pub computed_visibility: ComputedVisibility,
136+
/// The ui camera layers this button is visible in.
137+
pub render_layers: RenderLayers,
133138
}
134139

135-
impl Default for ButtonBundle {
136-
fn default() -> Self {
137-
ButtonBundle {
138-
button: Button,
139-
interaction: Default::default(),
140-
focus_policy: Default::default(),
141-
node: Default::default(),
142-
style: Default::default(),
143-
color: Default::default(),
144-
image: Default::default(),
145-
transform: Default::default(),
146-
global_transform: Default::default(),
147-
visibility: Default::default(),
148-
computed_visibility: Default::default(),
149-
}
150-
}
151-
}
152140
/// Configuration for cameras related to UI.
153141
///
154142
/// When a [`Camera`] doesn't have the [`UiCameraConfig`] component,
@@ -162,19 +150,15 @@ pub struct UiCameraConfig {
162150
/// When a `Camera` doesn't have the [`UiCameraConfig`] component,
163151
/// it will display the UI by default.
164152
pub show_ui: bool,
153+
/// The ui camera layers this camera can see.
154+
pub ui_render_layers: RenderLayers,
165155
}
166156

167157
impl Default for UiCameraConfig {
168158
fn default() -> Self {
169-
Self { show_ui: true }
170-
}
171-
}
172-
173-
impl ExtractComponent for UiCameraConfig {
174-
type Query = &'static Self;
175-
type Filter = With<Camera>;
176-
177-
fn extract_component(item: QueryItem<Self::Query>) -> Self {
178-
item.clone()
159+
Self {
160+
show_ui: true,
161+
ui_render_layers: Default::default(),
162+
}
179163
}
180164
}

crates/bevy_ui/src/lib.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ pub mod entity;
1212
pub mod update;
1313
pub mod widget;
1414

15-
use bevy_render::extract_component::ExtractComponentPlugin;
1615
pub use flex::*;
1716
pub use focus::*;
1817
pub use geometry::*;
@@ -32,8 +31,6 @@ use bevy_transform::TransformSystem;
3231
use bevy_window::ModifiesWindows;
3332
use update::{ui_z_system, update_clipping_system};
3433

35-
use crate::prelude::UiCameraConfig;
36-
3734
/// The basic plugin for Bevy UI
3835
#[derive(Default)]
3936
pub struct UiPlugin;
@@ -49,8 +46,7 @@ pub enum UiSystem {
4946

5047
impl Plugin for UiPlugin {
5148
fn build(&self, app: &mut App) {
52-
app.add_plugin(ExtractComponentPlugin::<UiCameraConfig>::default())
53-
.init_resource::<FlexSurface>()
49+
app.init_resource::<FlexSurface>()
5450
.register_type::<AlignContent>()
5551
.register_type::<AlignItems>()
5652
.register_type::<AlignSelf>()

crates/bevy_ui/src/render/mod.rs

Lines changed: 53 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,13 @@ use bevy_render::{
2020
render_resource::*,
2121
renderer::{RenderDevice, RenderQueue},
2222
texture::Image,
23-
view::{ComputedVisibility, ExtractedView, ViewUniforms},
23+
view::{ComputedVisibility, ExtractedView, RenderLayers, ViewUniforms},
2424
Extract, RenderApp, RenderStage,
2525
};
2626
use bevy_sprite::{Rect, SpriteAssetEvents, TextureAtlas};
2727
use bevy_text::{DefaultTextPipeline, Text};
2828
use bevy_transform::components::GlobalTransform;
29-
use bevy_utils::FloatOrd;
30-
use bevy_utils::HashMap;
29+
use bevy_utils::{FloatOrd, HashMap};
3130
use bevy_window::{WindowId, Windows};
3231
use bytemuck::{Pod, Zeroable};
3332
use std::ops::Range;
@@ -166,6 +165,7 @@ pub struct ExtractedUiNode {
166165
pub image: Handle<Image>,
167166
pub atlas_size: Option<Vec2>,
168167
pub clip: Option<Rect>,
168+
pub render_layers: RenderLayers,
169169
}
170170

171171
#[derive(Default)]
@@ -183,12 +183,13 @@ pub fn extract_uinodes(
183183
&UiColor,
184184
&UiImage,
185185
&ComputedVisibility,
186+
&RenderLayers,
186187
Option<&CalculatedClip>,
187188
)>,
188189
>,
189190
) {
190191
extracted_uinodes.uinodes.clear();
191-
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() {
192193
if !visibility.is_visible() {
193194
continue;
194195
}
@@ -207,6 +208,7 @@ pub fn extract_uinodes(
207208
image,
208209
atlas_size: None,
209210
clip: clip.map(|clip| clip.clip),
211+
render_layers: *render_layers,
210212
});
211213
}
212214
}
@@ -222,30 +224,36 @@ const UI_CAMERA_FAR: f32 = 1000.0;
222224
// TODO: Evaluate if we still need this.
223225
const UI_CAMERA_TRANSFORM_OFFSET: f32 = -0.1;
224226

225-
#[derive(Component)]
226-
pub struct DefaultCameraView(pub Entity);
227+
#[derive(Component, Debug)]
228+
pub struct UiCamera {
229+
pub entity: Entity,
230+
layers: RenderLayers,
231+
}
227232

228233
pub fn extract_default_ui_camera_view<T: Component>(
229234
mut commands: Commands,
230235
query: Extract<Query<(Entity, &Camera, Option<&UiCameraConfig>), With<T>>>,
231236
) {
232-
for (entity, camera, camera_ui) in query.iter() {
237+
for (camera_entity, camera, opt_ui_config) in query.iter() {
238+
let ui_config = opt_ui_config.cloned().unwrap_or_default();
233239
// ignore cameras with disabled ui
234-
if matches!(camera_ui, Some(&UiCameraConfig { show_ui: false, .. })) {
240+
if !ui_config.show_ui {
235241
continue;
236242
}
237-
if let (Some(logical_size), Some(physical_size)) = (
238-
camera.logical_viewport_size(),
239-
camera.physical_viewport_size(),
240-
) {
241-
let mut projection = OrthographicProjection {
242-
far: UI_CAMERA_FAR,
243-
window_origin: WindowOrigin::BottomLeft,
244-
depth_calculation: DepthCalculation::ZDifference,
245-
..Default::default()
246-
};
247-
projection.update(logical_size.x, logical_size.y);
248-
let default_camera_view = commands
243+
let logical_size = if let Some(logical_size) = camera.logical_viewport_size() {
244+
logical_size
245+
} else {
246+
continue;
247+
};
248+
let mut projection = OrthographicProjection {
249+
far: UI_CAMERA_FAR,
250+
window_origin: WindowOrigin::BottomLeft,
251+
depth_calculation: DepthCalculation::ZDifference,
252+
..Default::default()
253+
};
254+
projection.update(logical_size.x, logical_size.y);
255+
if let Some(physical_size) = camera.physical_viewport_size() {
256+
let ui_camera = commands
249257
.spawn()
250258
.insert(ExtractedView {
251259
projection: projection.get_projection_matrix(),
@@ -258,8 +266,11 @@ pub fn extract_default_ui_camera_view<T: Component>(
258266
height: physical_size.y,
259267
})
260268
.id();
261-
commands.get_or_spawn(entity).insert_bundle((
262-
DefaultCameraView(default_camera_view),
269+
commands.get_or_spawn(camera_entity).insert_bundle((
270+
UiCamera {
271+
entity: ui_camera,
272+
layers: ui_config.ui_render_layers,
273+
},
263274
RenderPhase::<TransparentUi>::default(),
264275
));
265276
}
@@ -278,12 +289,14 @@ pub fn extract_text_uinodes(
278289
&GlobalTransform,
279290
&Text,
280291
&ComputedVisibility,
292+
&RenderLayers,
281293
Option<&CalculatedClip>,
282294
)>,
283295
>,
284296
) {
285297
let scale_factor = windows.scale_factor(WindowId::primary()) as f32;
286-
for (entity, uinode, global_transform, text, visibility, clip) in uinode_query.iter() {
298+
299+
for (entity, uinode, transform, text, visibility, render_layers, clip) in uinode_query.iter() {
287300
if !visibility.is_visible() {
288301
continue;
289302
}
@@ -306,7 +319,7 @@ pub fn extract_text_uinodes(
306319
let atlas_size = Some(atlas.size);
307320

308321
// NOTE: Should match `bevy_text::text2d::extract_text2d_sprite`
309-
let extracted_transform = global_transform.compute_matrix()
322+
let extracted_transform = transform.compute_matrix()
310323
* Mat4::from_scale(Vec3::splat(scale_factor.recip()))
311324
* Mat4::from_translation(
312325
alignment_offset * scale_factor + text_glyph.position.extend(0.),
@@ -319,6 +332,7 @@ pub fn extract_text_uinodes(
319332
image: texture,
320333
atlas_size,
321334
clip: clip.map(|clip| clip.clip),
335+
render_layers: *render_layers,
322336
});
323337
}
324338
}
@@ -356,8 +370,10 @@ const QUAD_VERTEX_POSITIONS: [Vec3; 4] = [
356370

357371
const QUAD_INDICES: [usize; 6] = [0, 2, 3, 0, 1, 2];
358372

373+
/// UI nodes are batched per image and per layer
359374
#[derive(Component)]
360375
pub struct UiBatch {
376+
pub layers: RenderLayers,
361377
pub range: Range<u32>,
362378
pub image: Handle<Image>,
363379
pub z: f32,
@@ -380,20 +396,26 @@ pub fn prepare_uinodes(
380396
let mut start = 0;
381397
let mut end = 0;
382398
let mut current_batch_handle = Default::default();
399+
let mut current_batch_layers = Default::default();
383400
let mut last_z = 0.0;
384401
for extracted_uinode in &extracted_uinodes.uinodes {
385-
if current_batch_handle != extracted_uinode.image {
402+
let same_layers = current_batch_layers == extracted_uinode.render_layers;
403+
let same_handle = current_batch_handle == extracted_uinode.image;
404+
if !same_handle || !same_layers {
386405
if start != end {
387406
commands.spawn_bundle((UiBatch {
407+
layers: current_batch_layers,
388408
range: start..end,
389409
image: current_batch_handle,
390410
z: last_z,
391411
},));
392412
start = end;
393413
}
414+
current_batch_layers = extracted_uinode.render_layers;
394415
current_batch_handle = extracted_uinode.image.clone_weak();
395416
}
396417

418+
// TODO: the following code is hard to grasp, a refactor would be welcome :)
397419
let uinode_rect = extracted_uinode.rect;
398420
let rect_size = uinode_rect.size().extend(1.0);
399421

@@ -471,14 +493,14 @@ pub fn prepare_uinodes(
471493
color: extracted_uinode.color.as_linear_rgba_f32(),
472494
});
473495
}
474-
475496
last_z = extracted_uinode.transform.w_axis[2];
476497
end += QUAD_INDICES.len() as u32;
477498
}
478499

479500
// if start != end, there is one last batch to process
480501
if start != end {
481502
commands.spawn_bundle((UiBatch {
503+
layers: current_batch_layers,
482504
range: start..end,
483505
image: current_batch_handle,
484506
z: last_z,
@@ -505,7 +527,7 @@ pub fn queue_uinodes(
505527
mut image_bind_groups: ResMut<UiImageBindGroups>,
506528
gpu_images: Res<RenderAssets<Image>>,
507529
ui_batches: Query<(Entity, &UiBatch)>,
508-
mut views: Query<&mut RenderPhase<TransparentUi>>,
530+
mut views: Query<(&mut RenderPhase<TransparentUi>, &UiCamera)>,
509531
events: Res<SpriteAssetEvents>,
510532
) {
511533
// If an image has changed, the GpuImage has (probably) changed
@@ -529,8 +551,11 @@ pub fn queue_uinodes(
529551
}));
530552
let draw_ui_function = draw_functions.read().get_id::<DrawUi>().unwrap();
531553
let pipeline = pipelines.specialize(&mut pipeline_cache, &ui_pipeline, UiPipelineKey {});
532-
for mut transparent_phase in &mut views {
554+
for (mut transparent_phase, cam_data) in &mut views {
533555
for (entity, batch) in &ui_batches {
556+
if !batch.layers.intersects(&cam_data.layers) {
557+
continue;
558+
}
534559
image_bind_groups
535560
.values
536561
.entry(batch.image.clone_weak())

0 commit comments

Comments
 (0)