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 bevyengine#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 19, 2022
1 parent 6e0ff4c commit f8bafc7
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 37 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 @@ -9,7 +9,7 @@ use bevy_math::Vec2;
use bevy_render::{
camera::{DepthCalculation, OrthographicProjection, WindowOrigin},
prelude::ComputedVisibility,
view::Visibility,
view::{RenderLayers, Visibility},
};
use bevy_text::Text;
use bevy_transform::prelude::{GlobalTransform, Transform};
Expand All @@ -35,6 +35,8 @@ pub struct NodeBundle {
pub visibility: Visibility,
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
pub computed_visibility: ComputedVisibility,
/// The ui camera layers this node is visible in.
pub render_layers: RenderLayers,
}

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

/// A UI node that is text
Expand All @@ -85,6 +89,8 @@ pub struct TextBundle {
pub visibility: Visibility,
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
pub computed_visibility: ComputedVisibility,
/// The ui camera layers this text is visible in.
pub render_layers: RenderLayers,
}

impl Default for TextBundle {
Expand All @@ -99,6 +105,7 @@ impl Default for TextBundle {
global_transform: Default::default(),
visibility: Default::default(),
computed_visibility: Default::default(),
render_layers: Default::default(),
}
}
}
Expand Down Expand Up @@ -128,6 +135,8 @@ pub struct ButtonBundle {
pub visibility: Visibility,
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
pub computed_visibility: ComputedVisibility,
/// The ui camera layers this button is visible in.
pub render_layers: RenderLayers,
}

/// Configuration for cameras related to UI.
Expand All @@ -145,6 +154,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 @@ -172,6 +183,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
39 changes: 30 additions & 9 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::{ComputedVisibility, ExtractedView, ViewUniforms},
view::{ComputedVisibility, ExtractedView, RenderLayers, ViewUniforms},
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,
&ComputedVisibility,
&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,
&ComputedVisibility,
&RenderLayers,
Option<&CalculatedClip>,
)>,
>,
) {
let scale_factor = windows.scale_factor(WindowId::primary()) as f32;
for (entity, uinode, global_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 All @@ -308,7 +317,7 @@ pub fn extract_text_uinodes(
let atlas_size = Some(atlas.size);

// NOTE: Should match `bevy_text::text2d::extract_text2d_sprite`
let extracted_transform = global_transform.compute_matrix()
let extracted_transform = transform.compute_matrix()
* Mat4::from_scale(Vec3::splat(scale_factor.recip()))
* Mat4::from_translation(
alignment_offset * scale_factor + text_glyph.position.extend(0.),
Expand All @@ -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 &mut views {
for (mut transparent_phase, cam_data) in &mut views {
for (entity, batch) in &ui_batches {
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 f8bafc7

Please sign in to comment.