Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Border and CornerRadius components to bevy_ui #3991

Closed
Closed
17 changes: 16 additions & 1 deletion crates/bevy_ui/src/entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

use crate::{
widget::{Button, ImageMode},
CalculatedSize, FocusPolicy, Interaction, Node, Style, UiColor, UiImage, CAMERA_UI,
Border, CalculatedSize, CornerRadius, FocusPolicy, Interaction, Node, Style, UiColor, UiImage,
CAMERA_UI,
};
use bevy_ecs::bundle::Bundle;
use bevy_render::{
Expand Down Expand Up @@ -31,6 +32,10 @@ pub struct NodeBundle {
pub global_transform: GlobalTransform,
/// Describes the visibility properties of the node
pub visibility: Visibility,
/// Describes the radius of corners for the node
pub corner_radius: CornerRadius,
/// Describes the visual properties of the node's border
pub border: Border,
}

/// A UI node that is an image
Expand All @@ -56,6 +61,10 @@ pub struct ImageBundle {
pub global_transform: GlobalTransform,
/// Describes the visibility properties of the node
pub visibility: Visibility,
/// Describes the radius of corners for the node
pub corner_radius: CornerRadius,
/// Describes the visual properties of the node's border
pub border: Border,
}

/// A UI node that is text
Expand Down Expand Up @@ -117,6 +126,10 @@ pub struct ButtonBundle {
pub global_transform: GlobalTransform,
/// Describes the visibility properties of the node
pub visibility: Visibility,
/// Describes the radius of corners for the node
pub corner_radius: CornerRadius,
/// Describes the visual properties of the node's border
pub border: Border,
}

impl Default for ButtonBundle {
Expand All @@ -132,6 +145,8 @@ impl Default for ButtonBundle {
transform: Default::default(),
global_transform: Default::default(),
visibility: Default::default(),
corner_radius: Default::default(),
border: Default::default(),
}
}
}
Expand Down
109 changes: 96 additions & 13 deletions crates/bevy_ui/src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ use bevy_app::prelude::*;
use bevy_asset::{load_internal_asset, AssetEvent, Assets, Handle, HandleUntyped};
use bevy_core::FloatOrd;
use bevy_ecs::prelude::*;
use bevy_math::{const_vec3, Mat4, Vec2, Vec3, Vec4Swizzles};
use bevy_math::{const_vec3, Mat4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles};
use bevy_reflect::TypeUuid;
use bevy_render::{
camera::ActiveCameras,
color::Color,
render_asset::RenderAssets,
render_graph::{RenderGraph, SlotInfo, SlotType},
render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions, RenderPhase},
render_resource::*,
render_resource::{std140::AsStd140, *},
oceantume marked this conversation as resolved.
Show resolved Hide resolved
renderer::{RenderDevice, RenderQueue},
texture::Image,
view::{ViewUniforms, Visibility},
Expand All @@ -34,7 +34,7 @@ use bevy_window::Windows;

use bytemuck::{Pod, Zeroable};

use crate::{CalculatedClip, Node, UiColor, UiImage};
use crate::{Border, CalculatedClip, CornerRadius, Node, UiColor, UiImage};

pub mod node {
pub const UI_PASS_DRIVER: &str = "ui_pass_driver";
Expand Down Expand Up @@ -126,6 +126,9 @@ pub struct ExtractedUiNode {
pub image: Handle<Image>,
pub atlas_size: Option<Vec2>,
pub clip: Option<Rect>,
pub border_color: Option<Color>,
pub border_width: Option<f32>,
pub corner_radius: Option<[f32; 4]>,
}

#[derive(Default)]
Expand All @@ -143,11 +146,15 @@ pub fn extract_uinodes(
&UiImage,
&Visibility,
Option<&CalculatedClip>,
Option<&CornerRadius>,
Option<&Border>,
)>,
) {
let mut extracted_uinodes = render_world.resource_mut::<ExtractedUiNodes>();
extracted_uinodes.uinodes.clear();
for (uinode, transform, color, image, visibility, clip) in uinode_query.iter() {
for (uinode, transform, color, image, visibility, clip, corner_radius, border) in
uinode_query.iter()
{
if !visibility.is_visible {
continue;
}
Expand All @@ -166,6 +173,9 @@ pub fn extract_uinodes(
image,
atlas_size: None,
clip: clip.map(|clip| clip.clip),
border_color: border.map(|border| border.color),
border_width: border.map(|border| border.width),
corner_radius: corner_radius.map(|corner_radius| corner_radius.to_array()),
});
}
}
Expand Down Expand Up @@ -228,6 +238,9 @@ pub fn extract_text_uinodes(
image: texture,
atlas_size,
clip: clip.map(|clip| clip.clip),
border_color: None,
border_width: None,
corner_radius: None,
});
}
}
Expand All @@ -239,19 +252,43 @@ pub fn extract_text_uinodes(
struct UiVertex {
pub position: [f32; 3],
pub uv: [f32; 2],
pub uniform_index: u32,
}

const MAX_UI_UNIFORM_ENTRIES: usize = 256;

#[repr(C)]
#[derive(Copy, Clone, AsStd140, Debug)]
pub struct UiUniform {
entries: [UiUniformEntry; MAX_UI_UNIFORM_ENTRIES],
}

#[repr(C)]
#[derive(Copy, Clone, AsStd140, Debug, Default)]
pub struct UiUniformEntry {
pub color: u32,
pub size: Vec2,
pub center: Vec2,
pub border_color: u32,
pub border_width: f32,
/// NOTE: This is a Vec4 because using [f32; 4] with AsStd140 results in a 16-bytes alignment.
pub corner_radius: Vec4,
}

pub struct UiMeta {
vertices: BufferVec<UiVertex>,
view_bind_group: Option<BindGroup>,
ui_uniforms: DynamicUniformVec<UiUniform>,
ui_uniform_bind_group: Option<BindGroup>,
}

impl Default for UiMeta {
fn default() -> Self {
Self {
vertices: BufferVec::new(BufferUsages::VERTEX),
view_bind_group: None,
ui_uniforms: Default::default(),
ui_uniform_bind_group: None,
}
}
}
Expand All @@ -265,10 +302,11 @@ const QUAD_VERTEX_POSITIONS: [Vec3; 4] = [

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

#[derive(Component)]
#[derive(Component, Debug)]
pub struct UiBatch {
pub range: Range<u32>,
pub image: Handle<Image>,
pub ui_uniform_offset: u32,
pub z: f32,
}

Expand All @@ -280,6 +318,7 @@ pub fn prepare_uinodes(
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
) {
ui_meta.vertices.clear();
ui_meta.ui_uniforms.clear();

// sort by increasing z for correct transparency
extracted_uinodes
Expand All @@ -290,14 +329,27 @@ pub fn prepare_uinodes(
let mut end = 0;
let mut current_batch_handle = Default::default();
let mut last_z = 0.0;
let mut current_batch_uniform: UiUniform = UiUniform {
entries: [UiUniformEntry::default(); MAX_UI_UNIFORM_ENTRIES],
};
let mut current_uniform_index: u32 = 0;
for extracted_uinode in &extracted_uinodes.uinodes {
if current_batch_handle != extracted_uinode.image {
if current_batch_handle != extracted_uinode.image
|| current_uniform_index >= MAX_UI_UNIFORM_ENTRIES as u32
{
if start != end {
commands.spawn_bundle((UiBatch {
range: start..end,
image: current_batch_handle,
ui_uniform_offset: ui_meta.ui_uniforms.push(current_batch_uniform),
z: last_z,
},));

current_uniform_index = 0;
current_batch_uniform = UiUniform {
entries: [UiUniformEntry::default(); MAX_UI_UNIFORM_ENTRIES],
};

start = end;
}
current_batch_handle = extracted_uinode.image.clone_weak();
Expand Down Expand Up @@ -371,35 +423,54 @@ pub fn prepare_uinodes(
]
.map(|pos| pos / atlas_extent);

let color = extracted_uinode.color.as_linear_rgba_f32();
// encode color as a single u32 to save space
let color = (color[0] * 255.0) as u32
| ((color[1] * 255.0) as u32) << 8
| ((color[2] * 255.0) as u32) << 16
| ((color[3] * 255.0) as u32) << 24;
fn encode_color_as_u32(color: Color) -> u32 {
let color = color.as_linear_rgba_f32();
// encode color as a single u32 to save space
(color[0] * 255.0) as u32
| ((color[1] * 255.0) as u32) << 8
| ((color[2] * 255.0) as u32) << 16
| ((color[3] * 255.0) as u32) << 24
}

current_batch_uniform.entries[current_uniform_index as usize] = UiUniformEntry {
color: encode_color_as_u32(extracted_uinode.color),
size: Vec2::new(rect_size.x, rect_size.y),
center: ((positions[0] + positions[2]) / 2.0).xy(),
border_color: extracted_uinode.border_color.map_or(0, encode_color_as_u32),
border_width: extracted_uinode.border_width.unwrap_or(0.0),
corner_radius: extracted_uinode
.corner_radius
.map_or(Vec4::default(), |c| c.into()),
};

for i in QUAD_INDICES {
ui_meta.vertices.push(UiVertex {
position: positions_clipped[i].into(),
uv: uvs[i].into(),
color,
uniform_index: current_uniform_index,
});
}

current_uniform_index += 1;
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 {
let offset = ui_meta.ui_uniforms.push(current_batch_uniform);
commands.spawn_bundle((UiBatch {
range: start..end,
image: current_batch_handle,
ui_uniform_offset: offset,
z: last_z,
},));
}

ui_meta.vertices.write_buffer(&render_device, &render_queue);
ui_meta
.ui_uniforms
.write_buffer(&render_device, &render_queue);
}

#[derive(Default)]
Expand Down Expand Up @@ -474,4 +545,16 @@ pub fn queue_uinodes(
}
}
}

if let Some(uniforms_binding) = ui_meta.ui_uniforms.binding() {
ui_meta.ui_uniform_bind_group =
Some(render_device.create_bind_group(&BindGroupDescriptor {
entries: &[BindGroupEntry {
binding: 0,
resource: uniforms_binding,
}],
label: Some("ui_uniforms_bind_group"),
layout: &ui_pipeline.ui_uniform_layout,
}));
}
}
29 changes: 26 additions & 3 deletions crates/bevy_ui/src/render/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ use bevy_render::{
view::ViewUniform,
};

use crate::UiUniform;

pub struct UiPipeline {
pub view_layout: BindGroupLayout,
pub image_layout: BindGroupLayout,
pub ui_uniform_layout: BindGroupLayout,
}

impl FromWorld for UiPipeline {
Expand Down Expand Up @@ -52,9 +55,26 @@ impl FromWorld for UiPipeline {
label: Some("ui_image_layout"),
});

let ui_uniform_layout =
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &[BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::VERTEX,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: BufferSize::new(UiUniform::std140_size_static() as u64),
},

count: None,
}],
label: Some("ui_uniform_layout"),
});

UiPipeline {
view_layout,
image_layout,
ui_uniform_layout,
}
}
}
Expand All @@ -64,7 +84,6 @@ pub struct UiPipelineKey {}

impl SpecializedPipeline for UiPipeline {
type Key = UiPipelineKey;
/// FIXME: there are no specialization for now, should this be removed?
fn specialize(&self, _key: Self::Key) -> RenderPipelineDescriptor {
let vertex_layout = VertexBufferLayout::from_vertex_formats(
VertexStepMode::Vertex,
Expand All @@ -73,7 +92,7 @@ impl SpecializedPipeline for UiPipeline {
VertexFormat::Float32x3,
// uv
VertexFormat::Float32x2,
// color
// ui_uniform entry index
VertexFormat::Uint32,
],
);
Expand All @@ -96,7 +115,11 @@ impl SpecializedPipeline for UiPipeline {
write_mask: ColorWrites::ALL,
}],
}),
layout: Some(vec![self.view_layout.clone(), self.image_layout.clone()]),
layout: Some(vec![
self.view_layout.clone(),
self.image_layout.clone(),
self.ui_uniform_layout.clone(),
]),
primitive: PrimitiveState {
front_face: FrontFace::Ccw,
cull_mode: None,
Expand Down
Loading