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 21, 2022
1 parent 44e9cd4 commit 31155fe
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 109 deletions.
54 changes: 19 additions & 35 deletions crates/bevy_ui/src/entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,10 @@ use crate::{
widget::{Button, ImageMode},
CalculatedSize, FocusPolicy, Interaction, Node, Style, UiColor, UiImage,
};
use bevy_ecs::{
bundle::Bundle,
prelude::{Component, With},
query::QueryItem,
};
use bevy_ecs::{bundle::Bundle, prelude::Component};
use bevy_render::{
camera::Camera, extract_component::ExtractComponent, prelude::ComputedVisibility,
view::Visibility,
prelude::ComputedVisibility,
view::{RenderLayers, Visibility},
};
use bevy_text::Text;
use bevy_transform::prelude::{GlobalTransform, Transform};
Expand All @@ -37,6 +33,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 @@ -64,6 +62,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 @@ -87,6 +87,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 @@ -101,12 +103,13 @@ impl Default for TextBundle {
global_transform: Default::default(),
visibility: Default::default(),
computed_visibility: Default::default(),
render_layers: Default::default(),
}
}
}

/// A UI node that is a button
#[derive(Bundle, Clone, Debug)]
#[derive(Bundle, Clone, Debug, Default)]
pub struct ButtonBundle {
/// Describes the size of the node
pub node: Node,
Expand All @@ -130,25 +133,10 @@ 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,
}

impl Default for ButtonBundle {
fn default() -> Self {
ButtonBundle {
button: Button,
interaction: Default::default(),
focus_policy: Default::default(),
node: Default::default(),
style: Default::default(),
color: Default::default(),
image: Default::default(),
transform: Default::default(),
global_transform: Default::default(),
visibility: Default::default(),
computed_visibility: Default::default(),
}
}
}
/// Configuration for cameras related to UI.
///
/// When a [`Camera`] doesn't have the [`UiCameraConfig`] component,
Expand All @@ -162,19 +150,15 @@ pub struct UiCameraConfig {
/// When a `Camera` doesn't have the [`UiCameraConfig`] component,
/// it will display the UI by default.
pub show_ui: bool,
/// The ui camera layers this camera can see.
pub ui_render_layers: RenderLayers,
}

impl Default for UiCameraConfig {
fn default() -> Self {
Self { show_ui: true }
}
}

impl ExtractComponent for UiCameraConfig {
type Query = &'static Self;
type Filter = With<Camera>;

fn extract_component(item: QueryItem<Self::Query>) -> Self {
item.clone()
Self {
show_ui: true,
ui_render_layers: Default::default(),
}
}
}
6 changes: 1 addition & 5 deletions crates/bevy_ui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ pub mod entity;
pub mod update;
pub mod widget;

use bevy_render::extract_component::ExtractComponentPlugin;
pub use flex::*;
pub use focus::*;
pub use geometry::*;
Expand All @@ -32,8 +31,6 @@ use bevy_transform::TransformSystem;
use bevy_window::ModifiesWindows;
use update::{ui_z_system, update_clipping_system};

use crate::prelude::UiCameraConfig;

/// The basic plugin for Bevy UI
#[derive(Default)]
pub struct UiPlugin;
Expand All @@ -49,8 +46,7 @@ pub enum UiSystem {

impl Plugin for UiPlugin {
fn build(&self, app: &mut App) {
app.add_plugin(ExtractComponentPlugin::<UiCameraConfig>::default())
.init_resource::<FlexSurface>()
app.init_resource::<FlexSurface>()
.register_type::<AlignContent>()
.register_type::<AlignItems>()
.register_type::<AlignSelf>()
Expand Down
81 changes: 53 additions & 28 deletions crates/bevy_ui/src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,13 @@ 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};
use bevy_text::{DefaultTextPipeline, Text};
use bevy_transform::components::GlobalTransform;
use bevy_utils::FloatOrd;
use bevy_utils::HashMap;
use bevy_utils::{FloatOrd, HashMap};
use bevy_window::{WindowId, Windows};
use bytemuck::{Pod, Zeroable};
use std::ops::Range;
Expand Down Expand Up @@ -166,6 +165,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 +183,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 +208,7 @@ pub fn extract_uinodes(
image,
atlas_size: None,
clip: clip.map(|clip| clip.clip),
render_layers: *render_layers,
});
}
}
Expand All @@ -222,30 +224,36 @@ const UI_CAMERA_FAR: f32 = 1000.0;
// TODO: Evaluate if we still need this.
const UI_CAMERA_TRANSFORM_OFFSET: f32 = -0.1;

#[derive(Component)]
pub struct DefaultCameraView(pub Entity);
#[derive(Component, Debug)]
pub struct UiCamera {
pub entity: Entity,
layers: RenderLayers,
}

pub fn extract_default_ui_camera_view<T: Component>(
mut commands: Commands,
query: Extract<Query<(Entity, &Camera, Option<&UiCameraConfig>), With<T>>>,
) {
for (entity, camera, camera_ui) in query.iter() {
for (camera_entity, camera, opt_ui_config) in query.iter() {
let ui_config = opt_ui_config.cloned().unwrap_or_default();
// ignore cameras with disabled ui
if matches!(camera_ui, Some(&UiCameraConfig { show_ui: false, .. })) {
if !ui_config.show_ui {
continue;
}
if let (Some(logical_size), Some(physical_size)) = (
camera.logical_viewport_size(),
camera.physical_viewport_size(),
) {
let mut projection = OrthographicProjection {
far: UI_CAMERA_FAR,
window_origin: WindowOrigin::BottomLeft,
depth_calculation: DepthCalculation::ZDifference,
..Default::default()
};
projection.update(logical_size.x, logical_size.y);
let default_camera_view = commands
let logical_size = if let Some(logical_size) = camera.logical_viewport_size() {
logical_size
} else {
continue;
};
let mut projection = OrthographicProjection {
far: UI_CAMERA_FAR,
window_origin: WindowOrigin::BottomLeft,
depth_calculation: DepthCalculation::ZDifference,
..Default::default()
};
projection.update(logical_size.x, logical_size.y);
if let Some(physical_size) = camera.physical_viewport_size() {
let ui_camera = commands
.spawn()
.insert(ExtractedView {
projection: projection.get_projection_matrix(),
Expand All @@ -258,8 +266,11 @@ pub fn extract_default_ui_camera_view<T: Component>(
height: physical_size.y,
})
.id();
commands.get_or_spawn(entity).insert_bundle((
DefaultCameraView(default_camera_view),
commands.get_or_spawn(camera_entity).insert_bundle((
UiCamera {
entity: ui_camera,
layers: ui_config.ui_render_layers,
},
RenderPhase::<TransparentUi>::default(),
));
}
Expand All @@ -278,12 +289,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 @@ -306,7 +319,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 @@ -319,6 +332,7 @@ pub fn extract_text_uinodes(
image: texture,
atlas_size,
clip: clip.map(|clip| clip.clip),
render_layers: *render_layers,
});
}
}
Expand Down Expand Up @@ -356,8 +370,10 @@ const QUAD_VERTEX_POSITIONS: [Vec3; 4] = [

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

/// UI nodes are batched 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 @@ -380,20 +396,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 @@ -471,14 +493,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 @@ -505,7 +527,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 @@ -529,8 +551,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
Loading

0 comments on commit 31155fe

Please sign in to comment.