Skip to content

Border & CornerRadius #8381

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

Closed
wants to merge 31 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
538283c
Add Border and BorderRadius components to bevy_ui
oceantume Feb 19, 2022
6d3a6c0
Change UI example to showcase new Border component
oceantume Feb 19, 2022
ccfb526
Change Button example to showcase new BorderRadius component
oceantume Feb 19, 2022
de272c9
Add support for border and border radius to UI renderer
oceantume Feb 19, 2022
0b96248
Rename border radius to corner radius
oceantume Feb 20, 2022
3d2370b
Refactor ui renderer to be smaller and simpler
oceantume Feb 20, 2022
a94f9e8
Refactor UI renderer to pass node information via a uniform buffer
oceantume Feb 23, 2022
2489b1e
Run cargo formatter on all files
oceantume Feb 23, 2022
7412b56
Fix issue raised by clippy
oceantume Feb 24, 2022
893b17d
Merge branch 'main' of github.com:oceantume/bevy into ui-nodes-new-vi…
oceantume Feb 24, 2022
dd13202
Tweak UI shader code for optimization and clarity
oceantume Mar 5, 2022
26b7eb6
Merge branch 'main' into ui-nodes-new-visual-features
oceantume Mar 5, 2022
e3064f5
Merge branch 'ui-nodes-new-visual-features' of https://github.com/oce…
Mar 27, 2023
97abed1
Port PR code to 0.10.0's
Mar 27, 2023
8e52dcf
Merge branch 'main' of https://github.com/bevyengine/bevy
Mar 27, 2023
fd67eac
don't clear the ui uniforms twice
Mar 28, 2023
cee29ff
fix ui
Mar 28, 2023
a25cda2
remove entity.rs
Mar 28, 2023
ff1cad2
aa
Mar 28, 2023
0536325
Fix syntax errors in ui.wgsl
Mar 29, 2023
3902bef
Fix things
Mar 29, 2023
2b9ccd2
aa
Mar 29, 2023
ff2293c
Merge branch 'bevyengine:main' into ui-renderer-0.11.0
Mar 29, 2023
dd86c32
Merge branch 'bevyengine:main' into ui-renderer-0.11.0
Apr 13, 2023
ba125bf
Switch to Iced's border radius code
Apr 14, 2023
cbeaf92
Revert "Switch to Iced's border radius code"
Apr 14, 2023
e500f33
Add corner radius and border to ButtonBundle
Apr 14, 2023
97d15e0
move the corner_radius to the one with the actual background color
Apr 14, 2023
9d8eca7
Merge floral-main to ui-renderer-0.11.0
Jun 10, 2023
34eaeb0
fix the import
Jun 10, 2023
ee52a03
fix aliasing
Jun 10, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion crates/bevy_ui/src/node_bundles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

use crate::{
widget::{Button, TextFlags, UiImageSize},
BackgroundColor, ContentSize, FocusPolicy, Interaction, Node, Style, UiImage, ZIndex,
BackgroundColor, Border, ContentSize, CornerRadius, FocusPolicy, Interaction,
Node, Style, UiImage, ZIndex,
};
use bevy_ecs::bundle::Bundle;
use bevy_render::{
Expand Down Expand Up @@ -43,6 +44,10 @@ pub struct NodeBundle {
pub computed_visibility: ComputedVisibility,
/// Indicates the depth at which the node should appear in the UI
pub z_index: ZIndex,
/// 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 NodeBundle {
Expand All @@ -58,6 +63,8 @@ impl Default for NodeBundle {
visibility: Default::default(),
computed_visibility: Default::default(),
z_index: Default::default(),
corner_radius: Default::default(),
border: Default::default(),
}
}
}
Expand Down Expand Up @@ -103,6 +110,10 @@ pub struct ImageBundle {
pub computed_visibility: ComputedVisibility,
/// Indicates the depth at which the node should appear in the UI
pub z_index: ZIndex,
/// Describes the radius of corners for the node
pub corner_radius: CornerRadius,
/// Describes the visual properties of the node's border
pub border: Border,
}

#[cfg(feature = "bevy_text")]
Expand Down Expand Up @@ -243,6 +254,10 @@ pub struct ButtonBundle {
pub computed_visibility: ComputedVisibility,
/// Indicates the depth at which the node should appear in the UI
pub z_index: ZIndex,
/// 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 @@ -260,6 +275,8 @@ impl Default for ButtonBundle {
visibility: Default::default(),
computed_visibility: Default::default(),
z_index: Default::default(),
corner_radius: Default::default(),
border: Default::default(),
}
}
}
113 changes: 109 additions & 4 deletions crates/bevy_ui/src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ use crate::{prelude::UiCameraConfig, BackgroundColor, CalculatedClip, Node, UiIm
use bevy_app::prelude::*;
use bevy_asset::{load_internal_asset, AssetEvent, Assets, Handle, HandleUntyped};
use bevy_ecs::prelude::*;

use bevy_math::{Mat4, Rect, UVec4, Vec2, Vec3, Vec4Swizzles};

use bevy_math::{Vec3Swizzles, Vec4};
use bevy_reflect::TypeUuid;
use bevy_render::texture::DEFAULT_IMAGE_HANDLE;
use bevy_render::{
Expand All @@ -38,6 +41,8 @@ use bevy_utils::HashMap;
use bytemuck::{Pod, Zeroable};
use std::ops::Range;

use crate::{Border, CornerRadius};

pub mod node {
pub const UI_PASS_DRIVER: &str = "ui_pass_driver";
}
Expand Down Expand Up @@ -154,6 +159,9 @@ pub struct ExtractedUiNode {
pub clip: Option<Rect>,
pub flip_x: bool,
pub flip_y: bool,
pub border_color: Option<Color>,
pub border_width: Option<f32>,
pub corner_radius: Option<[f32; 4]>,
}

#[derive(Resource, Default)]
Expand All @@ -173,13 +181,24 @@ pub fn extract_uinodes(
Option<&UiImage>,
&ComputedVisibility,
Option<&CalculatedClip>,
Option<&CornerRadius>,
Option<&Border>,
)>,
>,
) {
extracted_uinodes.uinodes.clear();

for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() {
if let Ok((uinode, transform, color, maybe_image, visibility, clip)) =
uinode_query.get(*entity)
if let Ok((
uinode,
transform,
color,
maybe_image,
visibility,
clip,
corner_radius,
border,
)) = uinode_query.get(*entity)
{
// Skip invisible and completely transparent nodes
if !visibility.is_visible() || color.0.a() == 0.0 {
Expand Down Expand Up @@ -209,6 +228,9 @@ pub fn extract_uinodes(
clip: clip.map(|clip| clip.clip),
flip_x,
flip_y,
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 @@ -337,6 +359,10 @@ pub fn extract_text_uinodes(
clip: clip.map(|clip| clip.clip),
flip_x: false,
flip_y: false,

border_color: None,
border_width: None,
corner_radius: None,
});
}
}
Expand All @@ -349,19 +375,44 @@ struct UiVertex {
pub position: [f32; 3],
pub uv: [f32; 2],
pub color: [f32; 4],
pub uniform_index: u32,
}

const MAX_UI_UNIFORM_ENTRIES: usize = 256;

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

#[repr(C)]
#[derive(Copy, Clone, ShaderType, 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 AsShaderType results in a 16-bytes alignment.
pub corner_radius: Vec4,
}

#[derive(Resource)]
pub struct UiMeta {
vertices: BufferVec<UiVertex>,
view_bind_group: Option<BindGroup>,
ui_uniforms: DynamicUniformBuffer<UiUniform>,
ui_uniform_bind_group: Option<BindGroup>,
}

impl Default for UiMeta {
fn default() -> Self {
Self {
vertices: BufferVec::new(BufferUsages::VERTEX),
ui_uniforms: Default::default(),
view_bind_group: None,
ui_uniform_bind_group: None,
}
}
}
Expand All @@ -375,10 +426,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 @@ -390,6 +442,7 @@ pub fn prepare_uinodes(
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
) {
ui_meta.vertices.clear();
ui_meta.ui_uniforms.clear();

// sort by ui stack index, starting from the deepest node
extracted_uinodes
Expand All @@ -400,14 +453,26 @@ 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(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 @@ -509,28 +574,56 @@ pub fn prepare_uinodes(
};

let color = extracted_uinode.color.as_linear_rgba_f32();
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(),
uniform_index: current_uniform_index,
color,
});
}

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(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(Resource, Default)]
Expand Down Expand Up @@ -609,4 +702,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,
}));
}
}
36 changes: 33 additions & 3 deletions crates/bevy_ui/src/render/pipeline.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::mem::size_of;

use bevy_ecs::prelude::*;
use bevy_render::{
render_resource::*,
Expand All @@ -6,10 +8,13 @@ use bevy_render::{
view::{ViewTarget, ViewUniform},
};

use crate::UiUniform;

#[derive(Resource)]
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 +57,28 @@ 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(
size_of::<UiUniform>() as u64
),
},

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

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

impl SpecializedRenderPipeline for UiPipeline {
type Key = UiPipelineKey;

fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
let vertex_layout = VertexBufferLayout::from_vertex_formats(
VertexStepMode::Vertex,
Expand All @@ -77,10 +100,11 @@ impl SpecializedRenderPipeline for UiPipeline {
VertexFormat::Float32x2,
// color
VertexFormat::Float32x4,
// ui_uniform entry index
VertexFormat::Uint32,
],
);
let shader_defs = Vec::new();

RenderPipelineDescriptor {
vertex: VertexState {
shader: super::UI_SHADER_HANDLE.typed::<Shader>(),
Expand All @@ -102,8 +126,14 @@ impl SpecializedRenderPipeline for UiPipeline {
write_mask: ColorWrites::ALL,
})],
}),
layout: vec![self.view_layout.clone(), self.image_layout.clone()],

push_constant_ranges: Vec::new(),

layout: 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