diff --git a/crates/bevy_ui/src/entity.rs b/crates/bevy_ui/src/entity.rs index 8f456ed79e8fab..fb01c1c67e3cc7 100644 --- a/crates/bevy_ui/src/entity.rs +++ b/crates/bevy_ui/src/entity.rs @@ -8,7 +8,7 @@ use bevy_ecs::{bundle::Bundle, prelude::Component}; use bevy_math::Vec2; use bevy_render::{ camera::{DepthCalculation, OrthographicProjection, WindowOrigin}, - view::Visibility, + view::{RenderLayers, Visibility}, }; use bevy_text::Text; use bevy_transform::prelude::{GlobalTransform, Transform}; @@ -32,6 +32,8 @@ pub struct NodeBundle { pub global_transform: GlobalTransform, /// Describes the visibility properties of the node pub visibility: Visibility, + /// The ui camera layers this node is visible in. + pub render_layers: RenderLayers, } /// A UI node that is an image @@ -57,6 +59,8 @@ pub struct ImageBundle { pub global_transform: GlobalTransform, /// Describes the visibility properties of the node pub visibility: Visibility, + /// The ui camera layers this image is visible in. + pub render_layers: RenderLayers, } /// A UI node that is text @@ -78,6 +82,8 @@ pub struct TextBundle { pub global_transform: GlobalTransform, /// Describes the visibility properties of the node pub visibility: Visibility, + /// The ui camera layers this text is visible in. + pub render_layers: RenderLayers, } impl Default for TextBundle { @@ -91,6 +97,7 @@ impl Default for TextBundle { transform: Default::default(), global_transform: Default::default(), visibility: Default::default(), + render_layers: Default::default(), } } } @@ -118,6 +125,8 @@ pub struct ButtonBundle { pub global_transform: GlobalTransform, /// Describes the visibility properties of the node pub visibility: Visibility, + /// The ui camera layers this button is visible in. + pub render_layers: RenderLayers, } /// Configuration for cameras related to UI. @@ -135,6 +144,8 @@ pub struct UiCameraConfig { pub show_ui: bool, /// The position of the UI camera in UI space. pub position: Vec2, + /// The ui camera layers this camera can see. + pub ui_render_layers: RenderLayers, /// The projection data for the UI camera. /// /// The code relies on this not being set, @@ -162,6 +173,7 @@ impl Default for UiCameraConfig { Self { show_ui: true, position: Vec2::ZERO, + ui_render_layers: Default::default(), projection: OrthographicProjection { far: UI_CAMERA_FAR, window_origin: WindowOrigin::BottomLeft, diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index f2aa6e6b161f50..e66c9ba899f4ed 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -20,7 +20,7 @@ use bevy_render::{ render_resource::*, renderer::{RenderDevice, RenderQueue}, texture::Image, - view::{ExtractedView, ViewUniforms, Visibility}, + view::{ExtractedView, RenderLayers, ViewUniforms, Visibility}, RenderApp, RenderStage, RenderWorld, }; use bevy_sprite::{Rect, SpriteAssetEvents, TextureAtlas}; @@ -166,6 +166,7 @@ pub struct ExtractedUiNode { pub image: Handle, pub atlas_size: Option, pub clip: Option, + pub render_layers: RenderLayers, } #[derive(Default)] @@ -182,12 +183,13 @@ pub fn extract_uinodes( &UiColor, &UiImage, &Visibility, + &RenderLayers, Option<&CalculatedClip>, )>, ) { let mut extracted_uinodes = render_world.resource_mut::(); extracted_uinodes.uinodes.clear(); - for (uinode, transform, color, image, visibility, clip) in uinode_query.iter() { + for (uinode, transform, color, image, visibility, render_layers, clip) in uinode_query.iter() { if !visibility.is_visible { continue; } @@ -206,6 +208,7 @@ pub fn extract_uinodes( image, atlas_size: None, clip: clip.map(|clip| clip.clip), + render_layers: *render_layers, }); } } @@ -224,6 +227,7 @@ const UI_CAMERA_TRANSFORM_OFFSET: f32 = -0.1; #[derive(Component, Debug)] pub struct UiCamera { pub entity: Entity, + layers: RenderLayers, } pub fn extract_default_ui_camera_view( @@ -260,7 +264,10 @@ pub fn extract_default_ui_camera_view( height: physical_size.y, }); commands.get_or_spawn(camera_entity).insert_bundle(( - UiCamera { entity: ui_camera }, + UiCamera { + entity: ui_camera, + layers: ui_config.ui_render_layers, + }, RenderPhase::::default(), )); } @@ -278,6 +285,7 @@ pub fn extract_text_uinodes( &GlobalTransform, &Text, &Visibility, + &RenderLayers, Option<&CalculatedClip>, )>, ) { @@ -285,7 +293,7 @@ pub fn extract_text_uinodes( let scale_factor = windows.scale_factor(WindowId::primary()) as f32; - for (entity, uinode, transform, text, visibility, clip) in uinode_query.iter() { + for (entity, uinode, transform, text, visibility, render_layers, clip) in uinode_query.iter() { if !visibility.is_visible { continue; } @@ -321,6 +329,7 @@ pub fn extract_text_uinodes( image: texture, atlas_size, clip: clip.map(|clip| clip.clip), + render_layers: *render_layers, }); } } @@ -358,8 +367,10 @@ const QUAD_VERTEX_POSITIONS: [Vec3; 4] = [ const QUAD_INDICES: [usize; 6] = [0, 2, 3, 0, 1, 2]; +// Ui nodes are batches per image and per layer #[derive(Component)] pub struct UiBatch { + pub layers: RenderLayers, pub range: Range, pub image: Handle, pub z: f32, @@ -382,20 +393,26 @@ pub fn prepare_uinodes( let mut start = 0; let mut end = 0; let mut current_batch_handle = Default::default(); + let mut current_batch_layers = Default::default(); let mut last_z = 0.0; for extracted_uinode in &extracted_uinodes.uinodes { - if current_batch_handle != extracted_uinode.image { + let same_layers = current_batch_layers == extracted_uinode.render_layers; + let same_handle = current_batch_handle == extracted_uinode.image; + if !same_handle || !same_layers { if start != end { commands.spawn_bundle((UiBatch { + layers: current_batch_layers, range: start..end, image: current_batch_handle, z: last_z, },)); start = end; } + current_batch_layers = extracted_uinode.render_layers; current_batch_handle = extracted_uinode.image.clone_weak(); } + // TODO: the following code is hard to grasp, a refactor would be welcome :) let uinode_rect = extracted_uinode.rect; let rect_size = uinode_rect.size().extend(1.0); @@ -473,7 +490,6 @@ pub fn prepare_uinodes( color: extracted_uinode.color.as_linear_rgba_f32(), }); } - last_z = extracted_uinode.transform.w_axis[2]; end += QUAD_INDICES.len() as u32; } @@ -481,6 +497,7 @@ pub fn prepare_uinodes( // if start != end, there is one last batch to process if start != end { commands.spawn_bundle((UiBatch { + layers: current_batch_layers, range: start..end, image: current_batch_handle, z: last_z, @@ -507,7 +524,7 @@ pub fn queue_uinodes( mut image_bind_groups: ResMut, gpu_images: Res>, ui_batches: Query<(Entity, &UiBatch)>, - mut views: Query<&mut RenderPhase>, + mut views: Query<(&mut RenderPhase, &UiCamera)>, events: Res, ) { // If an image has changed, the GpuImage has (probably) changed @@ -531,8 +548,11 @@ pub fn queue_uinodes( })); let draw_ui_function = draw_functions.read().get_id::().unwrap(); let pipeline = pipelines.specialize(&mut pipeline_cache, &ui_pipeline, UiPipelineKey {}); - for mut transparent_phase in views.iter_mut() { + for (mut transparent_phase, cam_data) in views.iter_mut() { for (entity, batch) in ui_batches.iter() { + if !batch.layers.intersects(&cam_data.layers) { + continue; + } image_bind_groups .values .entry(batch.image.clone_weak()) diff --git a/crates/bevy_ui/src/render/render_pass.rs b/crates/bevy_ui/src/render/render_pass.rs index a1930ed5411fd5..43109c8bc3e389 100644 --- a/crates/bevy_ui/src/render/render_pass.rs +++ b/crates/bevy_ui/src/render/render_pass.rs @@ -17,9 +17,9 @@ use bevy_render::{ use bevy_utils::FloatOrd; pub struct UiPassNode { - ui_view_query: + view_query: QueryState<(&'static RenderPhase, &'static ViewTarget), With>, - default_camera_view_query: QueryState<&'static UiCamera>, + ui_camera_query: QueryState<&'static UiCamera>, } impl UiPassNode { @@ -27,8 +27,8 @@ impl UiPassNode { pub fn new(world: &mut World) -> Self { Self { - ui_view_query: world.query_filtered(), - default_camera_view_query: world.query(), + view_query: world.query_filtered(), + ui_camera_query: world.query(), } } } @@ -39,8 +39,8 @@ impl Node for UiPassNode { } fn update(&mut self, world: &mut World) { - self.ui_view_query.update_archetypes(world); - self.default_camera_view_query.update_archetypes(world); + self.view_query.update_archetypes(world); + self.ui_camera_query.update_archetypes(world); } fn run( @@ -49,10 +49,10 @@ impl Node for UiPassNode { render_context: &mut RenderContext, world: &World, ) -> Result<(), NodeRunError> { - let input_view_entity = graph.get_input_entity(Self::IN_VIEW)?; + let camera_view = graph.get_input_entity(Self::IN_VIEW)?; let (transparent_phase, target) = - if let Ok(result) = self.ui_view_query.get_manual(world, input_view_entity) { + if let Ok(result) = self.view_query.get_manual(world, camera_view) { result } else { return Ok(()); @@ -61,14 +61,12 @@ impl Node for UiPassNode { if transparent_phase.items.is_empty() { return Ok(()); } - let view_entity = if let Ok(default_view) = self - .default_camera_view_query - .get_manual(world, input_view_entity) - { - default_view.entity - } else { - input_view_entity - }; + let ui_view_entity = + if let Ok(ui_view) = self.ui_camera_query.get_manual(world, camera_view) { + ui_view.entity + } else { + return Ok(()); + }; let pass_descriptor = RenderPassDescriptor { label: Some("ui_pass"), @@ -93,7 +91,7 @@ impl Node for UiPassNode { let mut tracked_pass = TrackedRenderPass::new(render_pass); for item in &transparent_phase.items { let draw_function = draw_functions.get_mut(item.draw_function).unwrap(); - draw_function.draw(world, &mut tracked_pass, view_entity, item); + draw_function.draw(world, &mut tracked_pass, ui_view_entity, item); } Ok(()) } diff --git a/examples/window/multiple_windows.rs b/examples/window/multiple_windows.rs index 7d9e4a643de697..46ef6cb56264b6 100644 --- a/examples/window/multiple_windows.rs +++ b/examples/window/multiple_windows.rs @@ -2,7 +2,7 @@ use bevy::{ prelude::*, - render::camera::RenderTarget, + render::{camera::RenderTarget, view::RenderLayers}, window::{CreateWindow, PresentMode, WindowId}, }; @@ -30,10 +30,15 @@ fn setup( ..default() }); // main camera - commands.spawn_bundle(Camera3dBundle { - transform: Transform::from_xyz(0.0, 0.0, 6.0).looking_at(Vec3::ZERO, Vec3::Y), - ..default() - }); + commands + .spawn_bundle(Camera3dBundle { + transform: Transform::from_xyz(0.0, 0.0, 6.0).looking_at(Vec3::ZERO, Vec3::Y), + ..default() + }) + .insert(UiCameraConfig { + ui_render_layers: RenderLayers::layer(1), + ..default() + }); let window_id = WindowId::new(); @@ -50,12 +55,41 @@ fn setup( }); // second window camera - commands.spawn_bundle(Camera3dBundle { - transform: Transform::from_xyz(6.0, 0.0, 0.0).looking_at(Vec3::ZERO, Vec3::Y), - camera: Camera { - target: RenderTarget::Window(window_id), + commands + .spawn_bundle(Camera3dBundle { + transform: Transform::from_xyz(6.0, 0.0, 0.0).looking_at(Vec3::ZERO, Vec3::Y), + camera: Camera { + target: RenderTarget::Window(window_id), + ..default() + }, ..default() - }, + }) + .insert(UiCameraConfig { + ui_render_layers: RenderLayers::layer(2), + ..default() + }); + let text_style = TextStyle { + font: asset_server.load("fonts/FiraSans-Bold.ttf"), + font_size: 100.0, + color: Color::WHITE, + }; + let align = TextAlignment { + horizontal: HorizontalAlign::Center, + ..default() + }; + commands.spawn_bundle(TextBundle { + text: Text::with_section("Face", text_style.clone(), align), + render_layers: RenderLayers::layer(1), + ..default() + }); + commands.spawn_bundle(TextBundle { + text: Text::with_section("Profile", text_style.clone(), align), + render_layers: RenderLayers::layer(2), + ..default() + }); + commands.spawn_bundle(TextBundle { + text: Text::with_section("view", text_style, align), + render_layers: RenderLayers::all(), ..default() }); }