Skip to content

Commit

Permalink
Add RenderLayer support to UI cameras
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
nicopap committed Jul 9, 2022
1 parent e371b46 commit 109a06d
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 36 deletions.
14 changes: 13 additions & 1 deletion crates/bevy_ui/src/entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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 {
Expand All @@ -91,6 +97,7 @@ impl Default for TextBundle {
transform: Default::default(),
global_transform: Default::default(),
visibility: Default::default(),
render_layers: Default::default(),
}
}
}
Expand Down Expand Up @@ -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.
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
37 changes: 29 additions & 8 deletions crates/bevy_ui/src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use bevy_render::{
render_resource::*,
renderer::{RenderDevice, RenderQueue},
texture::Image,
view::{ExtractedView, ViewUniforms, Visibility},
view::{ExtractedView, RenderLayers, ViewUniforms, Visibility},
Extract, RenderApp, RenderStage,
};
use bevy_sprite::{Rect, SpriteAssetEvents, TextureAtlas};
Expand Down Expand Up @@ -166,6 +166,7 @@ pub struct ExtractedUiNode {
pub image: Handle<Image>,
pub atlas_size: Option<Vec2>,
pub clip: Option<Rect>,
pub render_layers: RenderLayers,
}

#[derive(Default)]
Expand All @@ -183,12 +184,13 @@ pub fn extract_uinodes(
&UiColor,
&UiImage,
&Visibility,
&RenderLayers,
Option<&CalculatedClip>,
)>,
>,
) {
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;
}
Expand All @@ -207,6 +209,7 @@ pub fn extract_uinodes(
image,
atlas_size: None,
clip: clip.map(|clip| clip.clip),
render_layers: *render_layers,
});
}
}
Expand All @@ -225,6 +228,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<T: Component>(
Expand Down Expand Up @@ -261,7 +265,10 @@ pub fn extract_default_ui_camera_view<T: Component>(
})
.id();
commands.get_or_spawn(camera_entity).insert_bundle((
UiCamera { entity: ui_camera },
UiCamera {
entity: ui_camera,
layers: ui_config.ui_render_layers,
},
RenderPhase::<TransparentUi>::default(),
));
}
Expand All @@ -280,12 +287,14 @@ pub fn extract_text_uinodes(
&GlobalTransform,
&Text,
&Visibility,
&RenderLayers,
Option<&CalculatedClip>,
)>,
>,
) {
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;
}
Expand Down Expand Up @@ -321,6 +330,7 @@ pub fn extract_text_uinodes(
image: texture,
atlas_size,
clip: clip.map(|clip| clip.clip),
render_layers: *render_layers,
});
}
}
Expand Down Expand Up @@ -358,8 +368,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<u32>,
pub image: Handle<Image>,
pub z: f32,
Expand All @@ -382,20 +394,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);

Expand Down Expand Up @@ -473,14 +491,14 @@ 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;
}

// 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,
Expand All @@ -507,7 +525,7 @@ pub fn queue_uinodes(
mut image_bind_groups: ResMut<UiImageBindGroups>,
gpu_images: Res<RenderAssets<Image>>,
ui_batches: Query<(Entity, &UiBatch)>,
mut views: Query<&mut RenderPhase<TransparentUi>>,
mut views: Query<(&mut RenderPhase<TransparentUi>, &UiCamera)>,
events: Res<SpriteAssetEvents>,
) {
// If an image has changed, the GpuImage has (probably) changed
Expand All @@ -531,8 +549,11 @@ pub fn queue_uinodes(
}));
let draw_ui_function = draw_functions.read().get_id::<DrawUi>().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())
Expand Down
32 changes: 15 additions & 17 deletions crates/bevy_ui/src/render/render_pass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,18 @@ use bevy_render::{
use bevy_utils::FloatOrd;

pub struct UiPassNode {
ui_view_query:
view_query:
QueryState<(&'static RenderPhase<TransparentUi>, &'static ViewTarget), With<ExtractedView>>,
default_camera_view_query: QueryState<&'static UiCamera>,
ui_camera_query: QueryState<&'static UiCamera>,
}

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

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(),
}
}
}
Expand All @@ -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(
Expand All @@ -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(());
Expand All @@ -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"),
Expand All @@ -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(())
}
Expand Down
54 changes: 44 additions & 10 deletions examples/window/multiple_windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use bevy::{
prelude::*,
render::camera::RenderTarget,
render::{camera::RenderTarget, view::RenderLayers},
window::{CreateWindow, PresentMode, WindowId},
};

Expand Down Expand Up @@ -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();

Expand All @@ -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()
});
}

0 comments on commit 109a06d

Please sign in to comment.