From 538283c7d06c26bc084fb1e8bf6de22f6de50f43 Mon Sep 17 00:00:00 2001 From: Gabriel Bourgeois Date: Sat, 19 Feb 2022 12:19:19 -0500 Subject: [PATCH 01/24] Add Border and BorderRadius components to bevy_ui --- crates/bevy_ui/src/entity.rs | 16 ++++++++++++- crates/bevy_ui/src/ui_node.rs | 45 +++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ui/src/entity.rs b/crates/bevy_ui/src/entity.rs index 02aac0c6bc8c2..4aac744b877ad 100644 --- a/crates/bevy_ui/src/entity.rs +++ b/crates/bevy_ui/src/entity.rs @@ -2,7 +2,7 @@ use crate::{ widget::{Button, ImageMode}, - CalculatedSize, FocusPolicy, Interaction, Node, Style, UiColor, UiImage, CAMERA_UI, + CalculatedSize, FocusPolicy, Interaction, Node, Style, UiColor, UiImage, CAMERA_UI, BorderRadius, Border, }; use bevy_ecs::bundle::Bundle; use bevy_render::{ @@ -31,6 +31,10 @@ pub struct NodeBundle { pub global_transform: GlobalTransform, /// Describes the visibility properties of the node pub visibility: Visibility, + /// Describes the border radius of the node + pub border_radius: BorderRadius, + /// Describes the visual properties of the node's border + pub border: Border, } /// A UI node that is an image @@ -56,6 +60,10 @@ pub struct ImageBundle { pub global_transform: GlobalTransform, /// Describes the visibility properties of the node pub visibility: Visibility, + /// Describes the border radius of the node + pub border_radius: BorderRadius, + /// Describes the visual properties of the node's border + pub border: Border, } /// A UI node that is text @@ -117,6 +125,10 @@ pub struct ButtonBundle { pub global_transform: GlobalTransform, /// Describes the visibility properties of the node pub visibility: Visibility, + /// Describes the border radius of the node + pub border_radius: BorderRadius, + /// Describes the visual properties of the node's border + pub border: Border, } impl Default for ButtonBundle { @@ -132,6 +144,8 @@ impl Default for ButtonBundle { transform: Default::default(), global_transform: Default::default(), visibility: Default::default(), + border_radius: Default::default(), + border: Default::default(), } } } diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index f29e087c825bc..6004a5a548461 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -391,3 +391,48 @@ pub struct CalculatedClip { /// The rect of the clip pub clip: bevy_sprite::Rect, } + +/// The border radius of the node +/// +/// This doesn't require a [`Border`] component +#[derive(Component, Default, Copy, Clone, Debug, Reflect)] +#[reflect(Component)] +pub struct BorderRadius { + pub top_left: f32, + pub bottom_left: f32, + pub top_right: f32, + pub bottom_right: f32, +} + +impl BorderRadius { + pub fn all(border_radius: f32) -> Self { + Self { + top_left: border_radius, + bottom_left: border_radius, + top_right: border_radius, + bottom_right: border_radius, + } + } + + pub fn to_array(&self) -> [f32; 4] { + [ + self.top_left, + self.bottom_left, + self.top_right, + self.bottom_right, + ] + } +} + +/// The visual properties of the node's border +#[derive(Component, Default, Copy, Clone, Debug, Reflect)] +#[reflect(Component)] +pub struct Border { + /// The width of the border + /// + /// This is different from [`Style`] border and it will not cause any displacement inside the node. + pub width: f32, + + /// The color of the border + pub color: Color, +} From 6d3a6c0af93fe580a1a2df5501269ba7f1717476 Mon Sep 17 00:00:00 2001 From: Gabriel Bourgeois Date: Sat, 19 Feb 2022 12:21:23 -0500 Subject: [PATCH 02/24] Change UI example to showcase new Border component --- examples/ui/ui.rs | 52 ++++++++++++++++++++--------------------------- 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/examples/ui/ui.rs b/examples/ui/ui.rs index 112e7ce4730bb..1ffdc2a0c92a2 100644 --- a/examples/ui/ui.rs +++ b/examples/ui/ui.rs @@ -34,42 +34,34 @@ fn setup(mut commands: Commands, asset_server: Res) { style: Style { size: Size::new(Val::Px(200.0), Val::Percent(100.0)), border: Rect::all(Val::Px(2.0)), + align_items: AlignItems::FlexEnd, ..Default::default() }, - color: Color::rgb(0.65, 0.65, 0.65).into(), + color: Color::rgb(0.15, 0.15, 0.15).into(), + border: Border { + color: Color::rgb(0.65, 0.65, 0.65), + width: 2.0, + }, ..Default::default() }) .with_children(|parent| { - // left vertical fill (content) - parent - .spawn_bundle(NodeBundle { - style: Style { - size: Size::new(Val::Percent(100.0), Val::Percent(100.0)), - align_items: AlignItems::FlexEnd, - ..Default::default() - }, - color: Color::rgb(0.15, 0.15, 0.15).into(), + // text + parent.spawn_bundle(TextBundle { + style: Style { + margin: Rect::all(Val::Px(5.0)), ..Default::default() - }) - .with_children(|parent| { - // text - parent.spawn_bundle(TextBundle { - style: Style { - margin: Rect::all(Val::Px(5.0)), - ..Default::default() - }, - text: Text::with_section( - "Text Example", - TextStyle { - font: asset_server.load("fonts/FiraSans-Bold.ttf"), - font_size: 30.0, - color: Color::WHITE, - }, - Default::default(), - ), - ..Default::default() - }); - }); + }, + text: Text::with_section( + "Text Example", + TextStyle { + font: asset_server.load("fonts/FiraSans-Bold.ttf"), + font_size: 30.0, + color: Color::WHITE, + }, + Default::default(), + ), + ..Default::default() + }); }); // right vertical fill parent From ccfb5262f8803e37ced9f4c989cab0d3054ffb4c Mon Sep 17 00:00:00 2001 From: Gabriel Bourgeois Date: Sat, 19 Feb 2022 12:21:51 -0500 Subject: [PATCH 03/24] Change Button example to showcase new BorderRadius component --- examples/ui/button.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/ui/button.rs b/examples/ui/button.rs index 48e67b555ebf9..fbd11bd1f8660 100644 --- a/examples/ui/button.rs +++ b/examples/ui/button.rs @@ -56,6 +56,11 @@ fn setup(mut commands: Commands, asset_server: Res) { ..Default::default() }, color: NORMAL_BUTTON.into(), + border_radius: BorderRadius::all(5.0), + border: Border { + color: Color::rgb(0.05, 0.05, 0.05), + width: 5.0, + }, ..Default::default() }) .with_children(|parent| { From de272c939ee19543441a97cdb464c9a369063877 Mon Sep 17 00:00:00 2001 From: Gabriel Bourgeois Date: Sat, 19 Feb 2022 12:38:36 -0500 Subject: [PATCH 04/24] Add support for border and border radius to UI renderer --- crates/bevy_ui/src/render/mod.rs | 46 ++++++++++++++++++++----- crates/bevy_ui/src/render/pipeline.rs | 39 +++++++++++++++++++++- crates/bevy_ui/src/render/ui.wgsl | 48 +++++++++++++++++++++++++-- 3 files changed, 120 insertions(+), 13 deletions(-) diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 3644d1037f197..ada7b1a6b1236 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -34,7 +34,7 @@ use bevy_window::Windows; use bytemuck::{Pod, Zeroable}; -use crate::{CalculatedClip, Node, UiColor, UiImage}; +use crate::{Border, BorderRadius, CalculatedClip, Node, UiColor, UiImage}; pub mod node { pub const UI_PASS_DRIVER: &str = "ui_pass_driver"; @@ -126,6 +126,9 @@ pub struct ExtractedUiNode { pub image: Handle, pub atlas_size: Option, pub clip: Option, + pub border_color: Option, + pub border_width: Option, + pub border_radius: Option<[f32; 4]>, } #[derive(Default)] @@ -143,11 +146,15 @@ pub fn extract_uinodes( &UiImage, &Visibility, Option<&CalculatedClip>, + Option<&BorderRadius>, + Option<&Border>, )>, ) { let mut extracted_uinodes = render_world.get_resource_mut::().unwrap(); extracted_uinodes.uinodes.clear(); - for (uinode, transform, color, image, visibility, clip) in uinode_query.iter() { + for (uinode, transform, color, image, visibility, clip, border_radius, border) in + uinode_query.iter() + { if !visibility.is_visible { continue; } @@ -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), + border_radius: border_radius.map(|border_radius| border_radius.to_array()), }); } } @@ -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, + border_radius: None, }); } } @@ -240,6 +253,13 @@ struct UiVertex { pub position: [f32; 3], pub uv: [f32; 2], pub color: u32, + pub uv_min: Vec2, + pub uv_max: Vec2, + pub size: Vec2, + pub border_color: u32, + pub border_width: f32, + /// Radius for each corner in this order: top-left, bottom-left, top-right, bottom-right + pub border_radius: [f32; 4], } pub struct UiMeta { @@ -371,18 +391,26 @@ 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 + } for i in QUAD_INDICES { ui_meta.vertices.push(UiVertex { position: positions_clipped[i].into(), uv: uvs[i].into(), - color, + color: encode_color_as_u32(extracted_uinode.color), + size: Vec2::new(rect_size.x, rect_size.y), + uv_min: uinode_rect.min / extracted_uinode.atlas_size.unwrap_or(uinode_rect.max), + uv_max: uinode_rect.max / extracted_uinode.atlas_size.unwrap_or(uinode_rect.max), + border_color: extracted_uinode.border_color.map_or(0, encode_color_as_u32), + border_width: extracted_uinode.border_width.unwrap_or(0.0), + border_radius: extracted_uinode.border_radius.unwrap_or([0.0; 4]), }); } diff --git a/crates/bevy_ui/src/render/pipeline.rs b/crates/bevy_ui/src/render/pipeline.rs index 4538dc922b131..5000808fb8195 100644 --- a/crates/bevy_ui/src/render/pipeline.rs +++ b/crates/bevy_ui/src/render/pipeline.rs @@ -67,7 +67,7 @@ impl SpecializedPipeline for UiPipeline { /// FIXME: there are no specialization for now, should this be removed? fn specialize(&self, _key: Self::Key) -> RenderPipelineDescriptor { let vertex_buffer_layout = VertexBufferLayout { - array_stride: 24, + array_stride: 72, step_mode: VertexStepMode::Vertex, attributes: vec![ // Position @@ -82,11 +82,48 @@ impl SpecializedPipeline for UiPipeline { offset: 12, shader_location: 1, }, + // Color VertexAttribute { format: VertexFormat::Uint32, offset: 20, shader_location: 2, }, + // UV Min + VertexAttribute { + format: VertexFormat::Float32x2, + offset: 24, + shader_location: 3, + }, + // UV Max + VertexAttribute { + format: VertexFormat::Float32x2, + offset: 32, + shader_location: 4, + }, + // Size + VertexAttribute { + format: VertexFormat::Float32x2, + offset: 40, + shader_location: 5, + }, + // Border Color + VertexAttribute { + format: VertexFormat::Uint32, + offset: 48, + shader_location: 6, + }, + // Border Width + VertexAttribute { + format: VertexFormat::Float32, + offset: 52, + shader_location: 7, + }, + // Border Radius + VertexAttribute { + format: VertexFormat::Float32x4, + offset: 56, + shader_location: 8, + }, ], }; let shader_defs = Vec::new(); diff --git a/crates/bevy_ui/src/render/ui.wgsl b/crates/bevy_ui/src/render/ui.wgsl index 5c6c02664cec1..298a4baa50836 100644 --- a/crates/bevy_ui/src/render/ui.wgsl +++ b/crates/bevy_ui/src/render/ui.wgsl @@ -8,6 +8,13 @@ var view: View; struct VertexOutput { [[location(0)]] uv: vec2; [[location(1)]] color: vec4; + [[location(2)]] center: vec2; + [[location(3)]] uv_min: vec2; + [[location(4)]] uv_max: vec2; + [[location(5)]] size: vec2; + [[location(6)]] border_color: vec4; + [[location(7)]] border_width: f32; + [[location(8)]] border_radius: vec4; [[builtin(position)]] position: vec4; }; @@ -16,22 +23,57 @@ fn vertex( [[location(0)]] vertex_position: vec3, [[location(1)]] vertex_uv: vec2, [[location(2)]] vertex_color: u32, + [[location(3)]] uv_min: vec2, + [[location(4)]] uv_max: vec2, + [[location(5)]] size: vec2, + [[location(6)]] border_color: u32, + [[location(7)]] border_width: f32, + [[location(8)]] border_radius: vec4, ) -> VertexOutput { var out: VertexOutput; out.uv = vertex_uv; out.position = view.view_proj * vec4(vertex_position, 1.0); out.color = vec4((vec4(vertex_color) >> vec4(0u, 8u, 16u, 24u)) & vec4(255u)) / 255.0; + out.size = size; + out.uv_min = uv_min; + out.uv_max = uv_max; + out.border_width = border_width; + out.border_color = vec4((vec4(border_color) >> vec4(0u, 8u, 16u, 24u)) & vec4(255u)) / 255.0; + out.border_radius = border_radius; return out; -} +} [[group(1), binding(0)]] var sprite_texture: texture_2d; [[group(1), binding(1)]] var sprite_sampler: sampler; +fn distance_round_border(point: vec2, size: vec2, radius_by_corner: vec4) -> f32 { + var corner_index = select(0, 1, point.y > 0.0) + select(0, 2, point.x > 0.0); + var radius = radius_by_corner[corner_index]; + var q = abs(point) - size + radius; + return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0))) - radius; +} + [[stage(fragment)]] fn fragment(in: VertexOutput) -> [[location(0)]] vec4 { - var color = textureSample(sprite_texture, sprite_sampler, in.uv); + var color = textureSample(sprite_texture, sprite_sampler, in.uv); color = in.color * color; + + // this makes rounded borders look softer, but it's affecting colors so I'm excluding it for now + var edge_softness = 0.0; //clamp(in.border_width - 1.0, 0.0, 2.0); + var border_softness = 0.0; //clamp(in.border_width - 1.0, 0.0, 1.0); + + // clamp radius between (0.0) and (shortest side / 2.0) + var radius = clamp(in.border_radius, vec4(0.0), vec4(min(in.size.x, in.size.y) / 2.0)); + + // get a normalized point based on uv, uv_max and uv_min + var point = ((in.uv - in.uv_min) / (in.uv_max - in.uv_min) - vec2(0.5)) * in.size; + var distance = distance_round_border(point, in.size * 0.5, radius); + + var inner_alpha = 1.0 - smoothStep(0.0, edge_softness, distance + edge_softness); + var border_alpha = 1.0 - smoothStep(in.border_width - border_softness, in.border_width, abs(distance)); + color = mix(vec4(0.0), mix(color, in.border_color, border_alpha), inner_alpha); + return color; -} \ No newline at end of file +} From 0b962489b2c6df93cfa0241f423feabcebd0fb74 Mon Sep 17 00:00:00 2001 From: Gabriel Bourgeois Date: Sat, 19 Feb 2022 20:04:51 -0500 Subject: [PATCH 05/24] Rename border radius to corner radius --- crates/bevy_ui/src/entity.rs | 16 ++++++++-------- crates/bevy_ui/src/render/mod.rs | 16 ++++++++-------- crates/bevy_ui/src/render/ui.wgsl | 8 ++++---- crates/bevy_ui/src/ui_node.rs | 21 ++++++++++++--------- examples/ui/button.rs | 2 +- 5 files changed, 33 insertions(+), 30 deletions(-) diff --git a/crates/bevy_ui/src/entity.rs b/crates/bevy_ui/src/entity.rs index 4aac744b877ad..0846ea1d0a1c6 100644 --- a/crates/bevy_ui/src/entity.rs +++ b/crates/bevy_ui/src/entity.rs @@ -2,7 +2,7 @@ use crate::{ widget::{Button, ImageMode}, - CalculatedSize, FocusPolicy, Interaction, Node, Style, UiColor, UiImage, CAMERA_UI, BorderRadius, Border, + CalculatedSize, FocusPolicy, Interaction, Node, Style, UiColor, UiImage, CAMERA_UI, CornerRadius, Border, }; use bevy_ecs::bundle::Bundle; use bevy_render::{ @@ -31,8 +31,8 @@ pub struct NodeBundle { pub global_transform: GlobalTransform, /// Describes the visibility properties of the node pub visibility: Visibility, - /// Describes the border radius of the node - pub border_radius: BorderRadius, + /// Describes the radius of corners for the node + pub corner_radius: CornerRadius, /// Describes the visual properties of the node's border pub border: Border, } @@ -60,8 +60,8 @@ pub struct ImageBundle { pub global_transform: GlobalTransform, /// Describes the visibility properties of the node pub visibility: Visibility, - /// Describes the border radius of the node - pub border_radius: BorderRadius, + /// Describes the radius of corners for the node + pub corner_radius: CornerRadius, /// Describes the visual properties of the node's border pub border: Border, } @@ -125,8 +125,8 @@ pub struct ButtonBundle { pub global_transform: GlobalTransform, /// Describes the visibility properties of the node pub visibility: Visibility, - /// Describes the border radius of the node - pub border_radius: BorderRadius, + /// Describes the radius of corners for the node + pub corner_radius: CornerRadius, /// Describes the visual properties of the node's border pub border: Border, } @@ -144,7 +144,7 @@ impl Default for ButtonBundle { transform: Default::default(), global_transform: Default::default(), visibility: Default::default(), - border_radius: Default::default(), + corner_radius: Default::default(), border: Default::default(), } } diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index ada7b1a6b1236..0f209ec5e0396 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -34,7 +34,7 @@ use bevy_window::Windows; use bytemuck::{Pod, Zeroable}; -use crate::{Border, BorderRadius, CalculatedClip, Node, UiColor, UiImage}; +use crate::{Border, CornerRadius, CalculatedClip, Node, UiColor, UiImage}; pub mod node { pub const UI_PASS_DRIVER: &str = "ui_pass_driver"; @@ -128,7 +128,7 @@ pub struct ExtractedUiNode { pub clip: Option, pub border_color: Option, pub border_width: Option, - pub border_radius: Option<[f32; 4]>, + pub corner_radius: Option<[f32; 4]>, } #[derive(Default)] @@ -146,13 +146,13 @@ pub fn extract_uinodes( &UiImage, &Visibility, Option<&CalculatedClip>, - Option<&BorderRadius>, + Option<&CornerRadius>, Option<&Border>, )>, ) { let mut extracted_uinodes = render_world.get_resource_mut::().unwrap(); extracted_uinodes.uinodes.clear(); - for (uinode, transform, color, image, visibility, clip, border_radius, border) in + for (uinode, transform, color, image, visibility, clip, corner_radius, border) in uinode_query.iter() { if !visibility.is_visible { @@ -175,7 +175,7 @@ pub fn extract_uinodes( clip: clip.map(|clip| clip.clip), border_color: border.map(|border| border.color), border_width: border.map(|border| border.width), - border_radius: border_radius.map(|border_radius| border_radius.to_array()), + corner_radius: corner_radius.map(|corner_radius| corner_radius.to_array()), }); } } @@ -240,7 +240,7 @@ pub fn extract_text_uinodes( clip: clip.map(|clip| clip.clip), border_color: None, border_width: None, - border_radius: None, + corner_radius: None, }); } } @@ -259,7 +259,7 @@ struct UiVertex { pub border_color: u32, pub border_width: f32, /// Radius for each corner in this order: top-left, bottom-left, top-right, bottom-right - pub border_radius: [f32; 4], + pub corner_radius: [f32; 4], } pub struct UiMeta { @@ -410,7 +410,7 @@ pub fn prepare_uinodes( uv_max: uinode_rect.max / extracted_uinode.atlas_size.unwrap_or(uinode_rect.max), border_color: extracted_uinode.border_color.map_or(0, encode_color_as_u32), border_width: extracted_uinode.border_width.unwrap_or(0.0), - border_radius: extracted_uinode.border_radius.unwrap_or([0.0; 4]), + corner_radius: extracted_uinode.corner_radius.unwrap_or([0.0; 4]), }); } diff --git a/crates/bevy_ui/src/render/ui.wgsl b/crates/bevy_ui/src/render/ui.wgsl index 298a4baa50836..4edac283143b8 100644 --- a/crates/bevy_ui/src/render/ui.wgsl +++ b/crates/bevy_ui/src/render/ui.wgsl @@ -14,7 +14,7 @@ struct VertexOutput { [[location(5)]] size: vec2; [[location(6)]] border_color: vec4; [[location(7)]] border_width: f32; - [[location(8)]] border_radius: vec4; + [[location(8)]] corner_radius: vec4; [[builtin(position)]] position: vec4; }; @@ -28,7 +28,7 @@ fn vertex( [[location(5)]] size: vec2, [[location(6)]] border_color: u32, [[location(7)]] border_width: f32, - [[location(8)]] border_radius: vec4, + [[location(8)]] corner_radius: vec4, ) -> VertexOutput { var out: VertexOutput; out.uv = vertex_uv; @@ -39,7 +39,7 @@ fn vertex( out.uv_max = uv_max; out.border_width = border_width; out.border_color = vec4((vec4(border_color) >> vec4(0u, 8u, 16u, 24u)) & vec4(255u)) / 255.0; - out.border_radius = border_radius; + out.corner_radius = corner_radius; return out; } @@ -65,7 +65,7 @@ fn fragment(in: VertexOutput) -> [[location(0)]] vec4 { var border_softness = 0.0; //clamp(in.border_width - 1.0, 0.0, 1.0); // clamp radius between (0.0) and (shortest side / 2.0) - var radius = clamp(in.border_radius, vec4(0.0), vec4(min(in.size.x, in.size.y) / 2.0)); + var radius = clamp(in.corner_radius, vec4(0.0), vec4(min(in.size.x, in.size.y) / 2.0)); // get a normalized point based on uv, uv_max and uv_min var point = ((in.uv - in.uv_min) / (in.uv_max - in.uv_min) - vec2(0.5)) * in.size; diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index 6004a5a548461..476eed2c027cf 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -392,28 +392,31 @@ pub struct CalculatedClip { pub clip: bevy_sprite::Rect, } -/// The border radius of the node +/// The corner radius of the node /// -/// This doesn't require a [`Border`] component +/// This describes a radius value for each corner of a node, even if they have no [`Border`]. #[derive(Component, Default, Copy, Clone, Debug, Reflect)] #[reflect(Component)] -pub struct BorderRadius { +pub struct CornerRadius { pub top_left: f32, pub bottom_left: f32, pub top_right: f32, pub bottom_right: f32, } -impl BorderRadius { - pub fn all(border_radius: f32) -> Self { +impl CornerRadius { + /// Creates a CornerRadius instance with all corners set to the specified radius. + pub fn all(corner_radius: f32) -> Self { Self { - top_left: border_radius, - bottom_left: border_radius, - top_right: border_radius, - bottom_right: border_radius, + top_left: corner_radius, + bottom_left: corner_radius, + top_right: corner_radius, + bottom_right: corner_radius, } } + /// Creates an array with the values for all corners in this order: + /// top-left, bottom-left, top-right, bottom-right pub fn to_array(&self) -> [f32; 4] { [ self.top_left, diff --git a/examples/ui/button.rs b/examples/ui/button.rs index fbd11bd1f8660..818ffcffe24a5 100644 --- a/examples/ui/button.rs +++ b/examples/ui/button.rs @@ -56,7 +56,7 @@ fn setup(mut commands: Commands, asset_server: Res) { ..Default::default() }, color: NORMAL_BUTTON.into(), - border_radius: BorderRadius::all(5.0), + corner_radius: CornerRadius::all(5.0), border: Border { color: Color::rgb(0.05, 0.05, 0.05), width: 5.0, From 3d2370b967b634911e84ebb8bb29123c1d0060e5 Mon Sep 17 00:00:00 2001 From: Gabriel Bourgeois Date: Sat, 19 Feb 2022 23:09:13 -0500 Subject: [PATCH 06/24] Refactor ui renderer to be smaller and simpler --- crates/bevy_ui/src/render/mod.rs | 10 +++--- crates/bevy_ui/src/render/pipeline.rs | 26 ++++++-------- crates/bevy_ui/src/render/ui.wgsl | 49 +++++++++++++-------------- 3 files changed, 38 insertions(+), 47 deletions(-) diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 0f209ec5e0396..d3c8c6c87f80d 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -12,7 +12,7 @@ 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, Vec4Swizzles, Vec3Swizzles}; use bevy_reflect::TypeUuid; use bevy_render::{ camera::ActiveCameras, @@ -34,7 +34,7 @@ use bevy_window::Windows; use bytemuck::{Pod, Zeroable}; -use crate::{Border, CornerRadius, CalculatedClip, Node, UiColor, UiImage}; +use crate::{Border, CalculatedClip, CornerRadius, Node, UiColor, UiImage}; pub mod node { pub const UI_PASS_DRIVER: &str = "ui_pass_driver"; @@ -253,9 +253,8 @@ struct UiVertex { pub position: [f32; 3], pub uv: [f32; 2], pub color: u32, - pub uv_min: Vec2, - pub uv_max: Vec2, pub size: Vec2, + pub center: Vec2, pub border_color: u32, pub border_width: f32, /// Radius for each corner in this order: top-left, bottom-left, top-right, bottom-right @@ -406,8 +405,7 @@ pub fn prepare_uinodes( uv: uvs[i].into(), color: encode_color_as_u32(extracted_uinode.color), size: Vec2::new(rect_size.x, rect_size.y), - uv_min: uinode_rect.min / extracted_uinode.atlas_size.unwrap_or(uinode_rect.max), - uv_max: uinode_rect.max / extracted_uinode.atlas_size.unwrap_or(uinode_rect.max), + 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.unwrap_or([0.0; 4]), diff --git a/crates/bevy_ui/src/render/pipeline.rs b/crates/bevy_ui/src/render/pipeline.rs index 5000808fb8195..12548b7a88d9a 100644 --- a/crates/bevy_ui/src/render/pipeline.rs +++ b/crates/bevy_ui/src/render/pipeline.rs @@ -67,7 +67,7 @@ impl SpecializedPipeline for UiPipeline { /// FIXME: there are no specialization for now, should this be removed? fn specialize(&self, _key: Self::Key) -> RenderPipelineDescriptor { let vertex_buffer_layout = VertexBufferLayout { - array_stride: 72, + array_stride: 64, step_mode: VertexStepMode::Vertex, attributes: vec![ // Position @@ -88,41 +88,35 @@ impl SpecializedPipeline for UiPipeline { offset: 20, shader_location: 2, }, - // UV Min + // Size VertexAttribute { format: VertexFormat::Float32x2, offset: 24, shader_location: 3, }, - // UV Max + // Center VertexAttribute { format: VertexFormat::Float32x2, offset: 32, shader_location: 4, }, - // Size - VertexAttribute { - format: VertexFormat::Float32x2, - offset: 40, - shader_location: 5, - }, // Border Color VertexAttribute { format: VertexFormat::Uint32, - offset: 48, - shader_location: 6, + offset: 40, + shader_location: 5, }, // Border Width VertexAttribute { format: VertexFormat::Float32, - offset: 52, - shader_location: 7, + offset: 44, + shader_location: 6, }, - // Border Radius + // Corner Radius VertexAttribute { format: VertexFormat::Float32x4, - offset: 56, - shader_location: 8, + offset: 48, + shader_location: 7, }, ], }; diff --git a/crates/bevy_ui/src/render/ui.wgsl b/crates/bevy_ui/src/render/ui.wgsl index 4edac283143b8..0f3e1b1a82619 100644 --- a/crates/bevy_ui/src/render/ui.wgsl +++ b/crates/bevy_ui/src/render/ui.wgsl @@ -8,38 +8,41 @@ var view: View; struct VertexOutput { [[location(0)]] uv: vec2; [[location(1)]] color: vec4; - [[location(2)]] center: vec2; - [[location(3)]] uv_min: vec2; - [[location(4)]] uv_max: vec2; - [[location(5)]] size: vec2; - [[location(6)]] border_color: vec4; - [[location(7)]] border_width: f32; - [[location(8)]] corner_radius: vec4; + [[location(2)]] size: vec2; + [[location(3)]] point: vec2; + [[location(4)]] border_color: vec4; + [[location(5)]] border_width: f32; + [[location(6)]] radius: f32; [[builtin(position)]] position: vec4; }; +fn unpack_color_from_u32(color: u32) -> vec4 { + return vec4((vec4(color) >> vec4(0u, 8u, 16u, 24u)) & vec4(255u)) / 255.0; +} + [[stage(vertex)]] fn vertex( [[location(0)]] vertex_position: vec3, [[location(1)]] vertex_uv: vec2, [[location(2)]] vertex_color: u32, - [[location(3)]] uv_min: vec2, - [[location(4)]] uv_max: vec2, - [[location(5)]] size: vec2, - [[location(6)]] border_color: u32, - [[location(7)]] border_width: f32, - [[location(8)]] corner_radius: vec4, + [[location(3)]] size: vec2, + [[location(4)]] center: vec2, + [[location(5)]] border_color: u32, + [[location(6)]] border_width: f32, + [[location(7)]] corner_radius: vec4, ) -> VertexOutput { var out: VertexOutput; out.uv = vertex_uv; out.position = view.view_proj * vec4(vertex_position, 1.0); - out.color = vec4((vec4(vertex_color) >> vec4(0u, 8u, 16u, 24u)) & vec4(255u)) / 255.0; + out.color = unpack_color_from_u32(vertex_color); out.size = size; - out.uv_min = uv_min; - out.uv_max = uv_max; + out.point = vertex_position.xy - center; out.border_width = border_width; - out.border_color = vec4((vec4(border_color) >> vec4(0u, 8u, 16u, 24u)) & vec4(255u)) / 255.0; - out.corner_radius = corner_radius; + out.border_color = unpack_color_from_u32(border_color); + + var corner_index = select(0, 1, out.point.y > 0.0) + select(0, 2, out.point.x > 0.0); + out.radius = corner_radius[corner_index]; + return out; } @@ -48,9 +51,7 @@ var sprite_texture: texture_2d; [[group(1), binding(1)]] var sprite_sampler: sampler; -fn distance_round_border(point: vec2, size: vec2, radius_by_corner: vec4) -> f32 { - var corner_index = select(0, 1, point.y > 0.0) + select(0, 2, point.x > 0.0); - var radius = radius_by_corner[corner_index]; +fn distance_round_border(point: vec2, size: vec2, radius: f32) -> f32 { var q = abs(point) - size + radius; return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0))) - radius; } @@ -65,11 +66,9 @@ fn fragment(in: VertexOutput) -> [[location(0)]] vec4 { var border_softness = 0.0; //clamp(in.border_width - 1.0, 0.0, 1.0); // clamp radius between (0.0) and (shortest side / 2.0) - var radius = clamp(in.corner_radius, vec4(0.0), vec4(min(in.size.x, in.size.y) / 2.0)); + var radius = clamp(in.radius, 0.0, min(in.size.x, in.size.y) / 2.0); - // get a normalized point based on uv, uv_max and uv_min - var point = ((in.uv - in.uv_min) / (in.uv_max - in.uv_min) - vec2(0.5)) * in.size; - var distance = distance_round_border(point, in.size * 0.5, radius); + var distance = distance_round_border(in.point, in.size * 0.5, radius); var inner_alpha = 1.0 - smoothStep(0.0, edge_softness, distance + edge_softness); var border_alpha = 1.0 - smoothStep(in.border_width - border_softness, in.border_width, abs(distance)); From a94f9e83e72f04c94d5e6afae54db6b7ae28f1ce Mon Sep 17 00:00:00 2001 From: Gabriel Bourgeois Date: Tue, 22 Feb 2022 23:14:04 -0500 Subject: [PATCH 07/24] Refactor UI renderer to pass node information via a uniform buffer --- crates/bevy_ui/src/render/mod.rs | 81 ++++++++++++++++++++---- crates/bevy_ui/src/render/pipeline.rs | 61 ++++++++---------- crates/bevy_ui/src/render/render_pass.rs | 21 ++++++ crates/bevy_ui/src/render/ui.wgsl | 39 ++++++++---- 4 files changed, 143 insertions(+), 59 deletions(-) diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index d3c8c6c87f80d..2fe7de24f75b1 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -12,7 +12,7 @@ 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, Vec3Swizzles}; +use bevy_math::{const_vec3, Mat4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles}; use bevy_reflect::TypeUuid; use bevy_render::{ camera::ActiveCameras, @@ -20,7 +20,7 @@ use bevy_render::{ render_asset::RenderAssets, render_graph::{RenderGraph, SlotInfo, SlotType}, render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions, RenderPhase}, - render_resource::*, + render_resource::{std140::AsStd140, *}, renderer::{RenderDevice, RenderQueue}, texture::Image, view::{ViewUniforms, Visibility}, @@ -252,18 +252,34 @@ 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, - /// Radius for each corner in this order: top-left, bottom-left, top-right, bottom-right - pub corner_radius: [f32; 4], + /// 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, view_bind_group: Option, + ui_uniforms: DynamicUniformVec, + ui_uniform_bind_group: Option, } impl Default for UiMeta { @@ -271,6 +287,8 @@ impl Default for UiMeta { Self { vertices: BufferVec::new(BufferUsages::VERTEX), view_bind_group: None, + ui_uniforms: Default::default(), + ui_uniform_bind_group: None, } } } @@ -284,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, pub image: Handle, + pub ui_uniform_offset: u32, pub z: f32, } @@ -299,6 +318,7 @@ pub fn prepare_uinodes( mut extracted_uinodes: ResMut, ) { ui_meta.vertices.clear(); + ui_meta.ui_uniforms.clear(); // sort by increasing z for correct transparency extracted_uinodes @@ -309,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(); @@ -399,33 +432,45 @@ pub fn prepare_uinodes( | ((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: 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.unwrap_or([0.0; 4]), + 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)] @@ -500,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, + })); + } } diff --git a/crates/bevy_ui/src/render/pipeline.rs b/crates/bevy_ui/src/render/pipeline.rs index 12548b7a88d9a..cf97a2597b0c8 100644 --- a/crates/bevy_ui/src/render/pipeline.rs +++ b/crates/bevy_ui/src/render/pipeline.rs @@ -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 { @@ -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, } } } @@ -64,10 +84,9 @@ 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_buffer_layout = VertexBufferLayout { - array_stride: 64, + array_stride: 24, step_mode: VertexStepMode::Vertex, attributes: vec![ // Position @@ -82,42 +101,12 @@ impl SpecializedPipeline for UiPipeline { offset: 12, shader_location: 1, }, - // Color + // UiUniform Node Index VertexAttribute { format: VertexFormat::Uint32, offset: 20, shader_location: 2, }, - // Size - VertexAttribute { - format: VertexFormat::Float32x2, - offset: 24, - shader_location: 3, - }, - // Center - VertexAttribute { - format: VertexFormat::Float32x2, - offset: 32, - shader_location: 4, - }, - // Border Color - VertexAttribute { - format: VertexFormat::Uint32, - offset: 40, - shader_location: 5, - }, - // Border Width - VertexAttribute { - format: VertexFormat::Float32, - offset: 44, - shader_location: 6, - }, - // Corner Radius - VertexAttribute { - format: VertexFormat::Float32x4, - offset: 48, - shader_location: 7, - }, ], }; let shader_defs = Vec::new(); @@ -139,7 +128,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, diff --git a/crates/bevy_ui/src/render/render_pass.rs b/crates/bevy_ui/src/render/render_pass.rs index 831b6dee9f9ae..2bea0371cd0bf 100644 --- a/crates/bevy_ui/src/render/render_pass.rs +++ b/crates/bevy_ui/src/render/render_pass.rs @@ -139,6 +139,7 @@ pub type DrawUi = ( SetItemPipeline, SetUiViewBindGroup<0>, SetUiTextureBindGroup<1>, + SetUiUniformBindGroup<2>, DrawUiNode, ); @@ -178,6 +179,26 @@ impl EntityRenderCommand for SetUiTextureBindGroup { RenderCommandResult::Success } } +pub struct SetUiUniformBindGroup; +impl EntityRenderCommand for SetUiUniformBindGroup { + type Param = (SRes, SQuery>); + + fn render<'w>( + _view: Entity, + item: Entity, + (ui_meta, query_batch): SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, + ) -> RenderCommandResult { + let batch = query_batch.get(item).unwrap(); + + pass.set_bind_group( + I, + ui_meta.into_inner().ui_uniform_bind_group.as_ref().unwrap(), + &[batch.ui_uniform_offset], + ); + RenderCommandResult::Success + } +} pub struct DrawUiNode; impl EntityRenderCommand for DrawUiNode { type Param = (SRes, SQuery>); diff --git a/crates/bevy_ui/src/render/ui.wgsl b/crates/bevy_ui/src/render/ui.wgsl index 0f3e1b1a82619..20d929c93a4a6 100644 --- a/crates/bevy_ui/src/render/ui.wgsl +++ b/crates/bevy_ui/src/render/ui.wgsl @@ -5,6 +5,23 @@ struct View { [[group(0), binding(0)]] var view: View; +struct UiUniformEntry { + color: u32; + size: vec2; + center: vec2; + border_color: u32; + border_width: f32; + corner_radius: vec4; +}; + +struct UiUniform { + // NOTE: this array size must be kept in sync with the constants defined bevy_ui/src/render/mod.rs + entries: array; +}; + +[[group(2), binding(0)]] +var ui_uniform: UiUniform; + struct VertexOutput { [[location(0)]] uv: vec2; [[location(1)]] color: vec4; @@ -24,24 +41,20 @@ fn unpack_color_from_u32(color: u32) -> vec4 { fn vertex( [[location(0)]] vertex_position: vec3, [[location(1)]] vertex_uv: vec2, - [[location(2)]] vertex_color: u32, - [[location(3)]] size: vec2, - [[location(4)]] center: vec2, - [[location(5)]] border_color: u32, - [[location(6)]] border_width: f32, - [[location(7)]] corner_radius: vec4, + [[location(2)]] ui_uniform_index: u32, ) -> VertexOutput { var out: VertexOutput; + var node = ui_uniform.entries[ui_uniform_index]; out.uv = vertex_uv; out.position = view.view_proj * vec4(vertex_position, 1.0); - out.color = unpack_color_from_u32(vertex_color); - out.size = size; - out.point = vertex_position.xy - center; - out.border_width = border_width; - out.border_color = unpack_color_from_u32(border_color); + out.color = unpack_color_from_u32(node.color); + out.size = node.size; + out.point = vertex_position.xy - node.center; + out.border_width = node.border_width; + out.border_color = unpack_color_from_u32(node.border_color); - var corner_index = select(0, 1, out.point.y > 0.0) + select(0, 2, out.point.x > 0.0); - out.radius = corner_radius[corner_index]; + var corner_index = select(0, 1, out.position.y > 0.0) + select(0, 2, out.position.x > 0.0); + out.radius = node.corner_radius[corner_index]; return out; } From 2489b1e76d186920b0597dff039ecd53c08a966b Mon Sep 17 00:00:00 2001 From: Gabriel Bourgeois Date: Tue, 22 Feb 2022 23:41:30 -0500 Subject: [PATCH 08/24] Run cargo formatter on all files --- crates/bevy_ui/src/entity.rs | 7 ++++--- crates/bevy_ui/src/ui_node.rs | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ui/src/entity.rs b/crates/bevy_ui/src/entity.rs index 0846ea1d0a1c6..bd5baefa0d0bc 100644 --- a/crates/bevy_ui/src/entity.rs +++ b/crates/bevy_ui/src/entity.rs @@ -2,7 +2,8 @@ use crate::{ widget::{Button, ImageMode}, - CalculatedSize, FocusPolicy, Interaction, Node, Style, UiColor, UiImage, CAMERA_UI, CornerRadius, Border, + Border, CalculatedSize, CornerRadius, FocusPolicy, Interaction, Node, Style, UiColor, UiImage, + CAMERA_UI, }; use bevy_ecs::bundle::Bundle; use bevy_render::{ @@ -33,7 +34,7 @@ pub struct NodeBundle { pub visibility: Visibility, /// Describes the radius of corners for the node pub corner_radius: CornerRadius, - /// Describes the visual properties of the node's border + /// Describes the visual properties of the node's border pub border: Border, } @@ -62,7 +63,7 @@ pub struct ImageBundle { pub visibility: Visibility, /// Describes the radius of corners for the node pub corner_radius: CornerRadius, - /// Describes the visual properties of the node's border + /// Describes the visual properties of the node's border pub border: Border, } diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index 476eed2c027cf..159dfbbbf23c7 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -393,7 +393,7 @@ pub struct CalculatedClip { } /// The corner radius of the node -/// +/// /// This describes a radius value for each corner of a node, even if they have no [`Border`]. #[derive(Component, Default, Copy, Clone, Debug, Reflect)] #[reflect(Component)] From 7412b563a0074985810b55d0ff0a414c04bf591e Mon Sep 17 00:00:00 2001 From: Gabriel Bourgeois Date: Wed, 23 Feb 2022 21:00:06 -0500 Subject: [PATCH 09/24] Fix issue raised by clippy --- crates/bevy_ui/src/ui_node.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index 159dfbbbf23c7..7f1415f4c9c3b 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -405,7 +405,7 @@ pub struct CornerRadius { } impl CornerRadius { - /// Creates a CornerRadius instance with all corners set to the specified radius. + /// Creates a [`CornerRadius`] instance with all corners set to the specified radius. pub fn all(corner_radius: f32) -> Self { Self { top_left: corner_radius, From dd132028c2e3d6928adf4ba7122f902f111be361 Mon Sep 17 00:00:00 2001 From: Gabriel Bourgeois Date: Fri, 4 Mar 2022 21:17:14 -0500 Subject: [PATCH 10/24] Tweak UI shader code for optimization and clarity Move some work from fragment shader to vertex shader Add branching to skip complexity when there's no rounding or border Remove softness since we're not using it --- crates/bevy_ui/src/render/ui.wgsl | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/crates/bevy_ui/src/render/ui.wgsl b/crates/bevy_ui/src/render/ui.wgsl index 20d929c93a4a6..955f347a1a9c9 100644 --- a/crates/bevy_ui/src/render/ui.wgsl +++ b/crates/bevy_ui/src/render/ui.wgsl @@ -53,9 +53,13 @@ fn vertex( out.border_width = node.border_width; out.border_color = unpack_color_from_u32(node.border_color); + // get radius for this specific corner var corner_index = select(0, 1, out.position.y > 0.0) + select(0, 2, out.position.x > 0.0); out.radius = node.corner_radius[corner_index]; + // clamp radius between (0.0) and (shortest side / 2.0) + out.radius = clamp(out.radius, 0.0, min(out.size.x, out.size.y) / 2.0); + return out; } @@ -74,18 +78,13 @@ fn fragment(in: VertexOutput) -> [[location(0)]] vec4 { var color = textureSample(sprite_texture, sprite_sampler, in.uv); color = in.color * color; - // this makes rounded borders look softer, but it's affecting colors so I'm excluding it for now - var edge_softness = 0.0; //clamp(in.border_width - 1.0, 0.0, 2.0); - var border_softness = 0.0; //clamp(in.border_width - 1.0, 0.0, 1.0); - - // clamp radius between (0.0) and (shortest side / 2.0) - var radius = clamp(in.radius, 0.0, min(in.size.x, in.size.y) / 2.0); - - var distance = distance_round_border(in.point, in.size * 0.5, radius); + if (in.radius > 0.0 || in.border_width > 0.0) { + var distance = distance_round_border(in.point, in.size * 0.5, in.radius); - var inner_alpha = 1.0 - smoothStep(0.0, edge_softness, distance + edge_softness); - var border_alpha = 1.0 - smoothStep(in.border_width - border_softness, in.border_width, abs(distance)); - color = mix(vec4(0.0), mix(color, in.border_color, border_alpha), inner_alpha); + var inner_alpha = 1.0 - smoothStep(0.0, 0.0, distance); + var border_alpha = 1.0 - smoothStep(in.border_width, in.border_width, abs(distance)); + color = mix(vec4(0.0), mix(color, in.border_color, border_alpha), inner_alpha); + } return color; } From 97abed1317d879a8d55f42b12b5a5a5084b02cf4 Mon Sep 17 00:00:00 2001 From: Natalia Asteria Date: Mon, 27 Mar 2023 12:32:04 +0700 Subject: [PATCH 11/24] Port PR code to 0.10.0's Signed-off-by: Natalia Asteria --- crates/bevy_ui/src/node_bundles.rs | 14 ++++++++++++-- crates/bevy_ui/src/render/pipeline.rs | 10 +++++++--- crates/bevy_ui/src/render/render_pass.rs | 16 ++++++++++++---- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/crates/bevy_ui/src/node_bundles.rs b/crates/bevy_ui/src/node_bundles.rs index e0eee6a61062a..3f49e08481e24 100644 --- a/crates/bevy_ui/src/node_bundles.rs +++ b/crates/bevy_ui/src/node_bundles.rs @@ -1,8 +1,8 @@ //! This module contains basic node bundles used to build UIs use crate::{ - widget::Button, BackgroundColor, CalculatedSize, FocusPolicy, Interaction, Node, Style, - UiImage, ZIndex, + widget::Button, BackgroundColor, Border, CalculatedSize, CornerRadius, FocusPolicy, + Interaction, Node, Style, UiImage, ZIndex, }; use bevy_ecs::bundle::Bundle; use bevy_render::{ @@ -42,6 +42,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 { @@ -57,6 +61,8 @@ impl Default for NodeBundle { visibility: Default::default(), computed_visibility: Default::default(), z_index: Default::default(), + corner_radius: Default::default(), + border: Default::default(), } } } @@ -94,6 +100,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")] diff --git a/crates/bevy_ui/src/render/pipeline.rs b/crates/bevy_ui/src/render/pipeline.rs index 682ccfb8dd09c..40c437c6d8a8c 100644 --- a/crates/bevy_ui/src/render/pipeline.rs +++ b/crates/bevy_ui/src/render/pipeline.rs @@ -1,3 +1,5 @@ +use std::mem::size_of; + use bevy_ecs::prelude::*; use bevy_render::{ render_resource::*, @@ -63,7 +65,9 @@ impl FromWorld for UiPipeline { ty: BindingType::Buffer { ty: BufferBindingType::Uniform, has_dynamic_offset: true, - min_binding_size: BufferSize::new(UiUniform::std140_size_static() as u64), + min_binding_size: BufferSize::new( + size_of::() as u64 + ), }, count: None, @@ -125,11 +129,11 @@ impl SpecializedRenderPipeline for UiPipeline { push_constant_ranges: Vec::new(), - layout: Some(vec![ + layout: vec![ self.view_layout.clone(), self.image_layout.clone(), self.ui_uniform_layout.clone(), - ]), + ], primitive: PrimitiveState { front_face: FrontFace::Ccw, cull_mode: None, diff --git a/crates/bevy_ui/src/render/render_pass.rs b/crates/bevy_ui/src/render/render_pass.rs index 44ea813d0d7f7..b1b50e9c04d7b 100644 --- a/crates/bevy_ui/src/render/render_pass.rs +++ b/crates/bevy_ui/src/render/render_pass.rs @@ -167,16 +167,20 @@ impl RenderCommand

for SetUiTextureBindGroup } } pub struct SetUiUniformBindGroup; -impl EntityRenderCommand for SetUiUniformBindGroup { +impl RenderCommand

for SetUiUniformBindGroup +where + P: PhaseItem, +{ type Param = (SRes, SQuery>); fn render<'w>( - _view: Entity, - item: Entity, + item: &P, + view: (), + entity: (), (ui_meta, query_batch): SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { - let batch = query_batch.get(item).unwrap(); + let batch = query_batch.get(item.entity()).unwrap(); pass.set_bind_group( I, @@ -185,6 +189,10 @@ impl EntityRenderCommand for SetUiUniformBindGroup { ); RenderCommandResult::Success } + + type ViewWorldQuery = (); + + type ItemWorldQuery = (); } pub struct DrawUiNode; impl RenderCommand

for DrawUiNode { From fd67eac1f1adf7a12fd8a708501d3fb6ce1461a2 Mon Sep 17 00:00:00 2001 From: Natalia Asteria Date: Tue, 28 Mar 2023 17:04:24 +0700 Subject: [PATCH 12/24] don't clear the ui uniforms twice Signed-off-by: Natalia Asteria --- crates/bevy_ui/src/render/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index f96dc416173bf..7877db3a62ff4 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -442,7 +442,6 @@ pub fn prepare_uinodes( ) { ui_meta.vertices.clear(); ui_meta.ui_uniforms.clear(); - ui_meta.ui_uniforms.clear(); // sort by ui stack index, starting from the deepest node extracted_uinodes From cee29ff2aff2a1a9d2f54a0d35d7290d77ef9972 Mon Sep 17 00:00:00 2001 From: Natalia Asteria Date: Tue, 28 Mar 2023 17:04:45 +0700 Subject: [PATCH 13/24] fix ui Signed-off-by: Natalia Asteria --- crates/bevy_ui/src/render/ui.wgsl | 54 ++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ui/src/render/ui.wgsl b/crates/bevy_ui/src/render/ui.wgsl index 11ce13aa5468d..3871ba96d8f27 100644 --- a/crates/bevy_ui/src/render/ui.wgsl +++ b/crates/bevy_ui/src/render/ui.wgsl @@ -6,19 +6,59 @@ var view: View; struct VertexOutput { @location(0) uv: vec2, @location(1) color: vec4, + @location(2) border_color: vec4, + @location(3) border_width: f32, + @location(4) radius: f32, @builtin(position) position: vec4, + }; +struct UiUniformEntry { + color: u32, + size: vec2, + center: vec2, + border_color: u32, + border_width: f32, + corner_radius: vec4, +}; + +struct UiUniform { + // NOTE: this array size must be kept in sync with the constants defined bevy_ui/src/render/mod.rs + entries: array; +}; + +[[group(2), binding(0)]] +var ui_uniform: UiUniform; + + +fn unpack_color_from_u32(color: u32) -> vec4 { + return vec4((vec4(color) >> vec4(0u, 8u, 16u, 24u)) & vec4(255u)) / 255.0; +} + + @vertex fn vertex( @location(0) vertex_position: vec3, @location(1) vertex_uv: vec2, @location(2) vertex_color: vec4, + @location(3) ui_uniform_index: u32, ) -> VertexOutput { var out: VertexOutput; out.uv = vertex_uv; + var node = ui_uniform.entries[ui_uniform_index]; out.position = view.view_proj * vec4(vertex_position, 1.0); - out.color = vertex_color; + out.color = unpack_color_from_u32(node.color); + out.size = node.size; + out.point = vertex_position.xy - node.center; + out.border_width = node.border_width; + out.border_color = unpack_color_from_u32(node.border_color); + + // get radius for this specific corner + var corner_index = select(0, 1, out.position.y > 0.0) + select(0, 2, out.position.x > 0.0); + out.radius = node.corner_radius[corner_index]; + + // clamp radius between (0.0) and (shortest side / 2.0) + out.radius = clamp(out.radius, 0.0, min(out.size.x, out.size.y) / 2.0); return out; } @@ -27,9 +67,21 @@ var sprite_texture: texture_2d; @group(1) @binding(1) var sprite_sampler: sampler; +fn distance_round_border(point: vec2, size: vec2, radius: f32) -> f32 { + var q = abs(point) - size + radius; + return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0))) - radius; +} @fragment fn fragment(in: VertexOutput) -> @location(0) vec4 { var color = textureSample(sprite_texture, sprite_sampler, in.uv); color = in.color * color; + + if (in.radius > 0.0 || in.border_width > 0.0) { + var distance = distance_round_border(in.point, in.size * 0.5, in.radius); + + var inner_alpha = 1.0 - smoothStep(0.0, 0.0, distance); + var border_alpha = 1.0 - smoothStep(in.border_width, in.border_width, abs(distance)); + color = mix(vec4(0.0), mix(color, in.border_color, border_alpha), inner_alpha); + } return color; } From a25cda22ba1d59ec3d9ec3f26a901d0fd7580a40 Mon Sep 17 00:00:00 2001 From: Natalia Asteria Date: Tue, 28 Mar 2023 17:04:51 +0700 Subject: [PATCH 14/24] remove entity.rs Signed-off-by: Natalia Asteria --- crates/bevy_ui/src/entity.rs | 191 ----------------------------------- 1 file changed, 191 deletions(-) delete mode 100644 crates/bevy_ui/src/entity.rs diff --git a/crates/bevy_ui/src/entity.rs b/crates/bevy_ui/src/entity.rs deleted file mode 100644 index bd5baefa0d0bc..0000000000000 --- a/crates/bevy_ui/src/entity.rs +++ /dev/null @@ -1,191 +0,0 @@ -//! This module contains the bundles used in Bevy's UI - -use crate::{ - widget::{Button, ImageMode}, - Border, CalculatedSize, CornerRadius, FocusPolicy, Interaction, Node, Style, UiColor, UiImage, - CAMERA_UI, -}; -use bevy_ecs::bundle::Bundle; -use bevy_render::{ - camera::{Camera, DepthCalculation, OrthographicProjection, WindowOrigin}, - view::{Visibility, VisibleEntities}, -}; -use bevy_text::Text; -use bevy_transform::prelude::{GlobalTransform, Transform}; - -/// The basic UI node -#[derive(Bundle, Clone, Debug, Default)] -pub struct NodeBundle { - /// Describes the size of the node - pub node: Node, - /// Describes the style including flexbox settings - pub style: Style, - /// Describes the color of the node - pub color: UiColor, - /// Describes the image of the node - pub image: UiImage, - /// Whether this node should block interaction with lower nodes - pub focus_policy: FocusPolicy, - /// The transform of the node - pub transform: Transform, - /// The global transform of the node - 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 -#[derive(Bundle, Clone, Debug, Default)] -pub struct ImageBundle { - /// Describes the size of the node - pub node: Node, - /// Describes the style including flexbox settings - pub style: Style, - /// Configures how the image should scale - pub image_mode: ImageMode, - /// The calculated size based on the given image - pub calculated_size: CalculatedSize, - /// The color of the node - pub color: UiColor, - /// The image of the node - pub image: UiImage, - /// Whether this node should block interaction with lower nodes - pub focus_policy: FocusPolicy, - /// The transform of the node - pub transform: Transform, - /// The global transform of the node - 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 -#[derive(Bundle, Clone, Debug)] -pub struct TextBundle { - /// Describes the size of the node - pub node: Node, - /// Describes the style including flexbox settings - pub style: Style, - /// Contains the text of the node - pub text: Text, - /// The calculated size based on the given image - pub calculated_size: CalculatedSize, - /// Whether this node should block interaction with lower nodes - pub focus_policy: FocusPolicy, - /// The transform of the node - pub transform: Transform, - /// The global transform of the node - pub global_transform: GlobalTransform, - /// Describes the visibility properties of the node - pub visibility: Visibility, -} - -impl Default for TextBundle { - fn default() -> Self { - TextBundle { - focus_policy: FocusPolicy::Pass, - text: Default::default(), - node: Default::default(), - calculated_size: Default::default(), - style: Default::default(), - transform: Default::default(), - global_transform: Default::default(), - visibility: Default::default(), - } - } -} - -/// A UI node that is a button -#[derive(Bundle, Clone, Debug)] -pub struct ButtonBundle { - /// Describes the size of the node - pub node: Node, - /// Marker component that signals this node is a button - pub button: Button, - /// Describes the style including flexbox settings - pub style: Style, - /// Describes whether and how the button has been interacted with by the input - pub interaction: Interaction, - /// Whether this node should block interaction with lower nodes - pub focus_policy: FocusPolicy, - /// The color of the node - pub color: UiColor, - /// The image of the node - pub image: UiImage, - /// The transform of the node - pub transform: Transform, - /// The global transform of the node - 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 { - 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(), - corner_radius: Default::default(), - border: Default::default(), - } - } -} - -/// The camera that is needed to see UI elements -#[derive(Bundle, Debug)] -pub struct UiCameraBundle { - /// The camera component - pub camera: Camera, - /// The orthographic projection settings - pub orthographic_projection: OrthographicProjection, - /// The transform of the camera - pub transform: Transform, - /// The global transform of the camera - pub global_transform: GlobalTransform, - /// Contains visible entities - // FIXME there is no frustrum culling for UI - pub visible_entities: VisibleEntities, -} - -impl Default for UiCameraBundle { - fn default() -> Self { - // we want 0 to be "closest" and +far to be "farthest" in 2d, so we offset - // the camera's translation by far and use a right handed coordinate system - let far = 1000.0; - UiCameraBundle { - camera: Camera { - name: Some(CAMERA_UI.to_string()), - ..Default::default() - }, - orthographic_projection: OrthographicProjection { - far, - window_origin: WindowOrigin::BottomLeft, - depth_calculation: DepthCalculation::ZDifference, - ..Default::default() - }, - transform: Transform::from_xyz(0.0, 0.0, far - 0.1), - global_transform: Default::default(), - visible_entities: Default::default(), - } - } -} From ff1cad22932ebd80dd0151541ee153dba688301e Mon Sep 17 00:00:00 2001 From: Natalia Asteria Date: Tue, 28 Mar 2023 17:05:06 +0700 Subject: [PATCH 15/24] aa Signed-off-by: Natalia Asteria --- crates/bevy_ui/src/render/render_pass.rs | 4 ++-- examples/ui/ui.rs | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/crates/bevy_ui/src/render/render_pass.rs b/crates/bevy_ui/src/render/render_pass.rs index b1b50e9c04d7b..d5f51b4dcec55 100644 --- a/crates/bevy_ui/src/render/render_pass.rs +++ b/crates/bevy_ui/src/render/render_pass.rs @@ -175,8 +175,8 @@ where fn render<'w>( item: &P, - view: (), - entity: (), + _view: (), + _entity: (), (ui_meta, query_batch): SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { diff --git a/examples/ui/ui.rs b/examples/ui/ui.rs index 71bf4718c213f..a3b990fffa705 100644 --- a/examples/ui/ui.rs +++ b/examples/ui/ui.rs @@ -40,7 +40,7 @@ fn setup(mut commands: Commands, asset_server: Res) { .spawn(NodeBundle { style: Style { size: Size::new(Val::Px(200.0), Val::Percent(100.0)), - border: Rect::all(Val::Px(2.0)), + border: UiRect::all(Val::Px(2.0)), align_items: AlignItems::FlexEnd, ..default() }, @@ -53,19 +53,18 @@ fn setup(mut commands: Commands, asset_server: Res) { }) .with_children(|parent| { // text - parent.spawn_bundle(TextBundle { + parent.spawn(TextBundle { style: Style { - margin: Rect::all(Val::Px(5.0)), + margin: UiRect::all(Val::Px(5.0)), ..default() }, - text: Text::with_section( + text: Text::from_section( "Text Example", TextStyle { font: asset_server.load("fonts/FiraSans-Bold.ttf"), font_size: 30.0, color: Color::WHITE, }, - Default::default(), ), ..default() }); From 053632592221eee23928370d8bc76ba76454d421 Mon Sep 17 00:00:00 2001 From: Natalia Asteria Date: Wed, 29 Mar 2023 17:44:28 +0700 Subject: [PATCH 16/24] Fix syntax errors in ui.wgsl Signed-off-by: Natalia Asteria --- crates/bevy_ui/src/render/ui.wgsl | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/crates/bevy_ui/src/render/ui.wgsl b/crates/bevy_ui/src/render/ui.wgsl index 3871ba96d8f27..79988e79e21a2 100644 --- a/crates/bevy_ui/src/render/ui.wgsl +++ b/crates/bevy_ui/src/render/ui.wgsl @@ -24,10 +24,10 @@ struct UiUniformEntry { struct UiUniform { // NOTE: this array size must be kept in sync with the constants defined bevy_ui/src/render/mod.rs - entries: array; + entries: array, }; -[[group(2), binding(0)]] +@group(2) @binding(0) var ui_uniform: UiUniform; @@ -45,20 +45,8 @@ fn vertex( ) -> VertexOutput { var out: VertexOutput; out.uv = vertex_uv; - var node = ui_uniform.entries[ui_uniform_index]; out.position = view.view_proj * vec4(vertex_position, 1.0); - out.color = unpack_color_from_u32(node.color); - out.size = node.size; - out.point = vertex_position.xy - node.center; - out.border_width = node.border_width; - out.border_color = unpack_color_from_u32(node.border_color); - - // get radius for this specific corner - var corner_index = select(0, 1, out.position.y > 0.0) + select(0, 2, out.position.x > 0.0); - out.radius = node.corner_radius[corner_index]; - - // clamp radius between (0.0) and (shortest side / 2.0) - out.radius = clamp(out.radius, 0.0, min(out.size.x, out.size.y) / 2.0); + out.color = vertex_color; return out; } @@ -75,13 +63,5 @@ fn distance_round_border(point: vec2, size: vec2, radius: f32) -> f32 fn fragment(in: VertexOutput) -> @location(0) vec4 { var color = textureSample(sprite_texture, sprite_sampler, in.uv); color = in.color * color; - - if (in.radius > 0.0 || in.border_width > 0.0) { - var distance = distance_round_border(in.point, in.size * 0.5, in.radius); - - var inner_alpha = 1.0 - smoothStep(0.0, 0.0, distance); - var border_alpha = 1.0 - smoothStep(in.border_width, in.border_width, abs(distance)); - color = mix(vec4(0.0), mix(color, in.border_color, border_alpha), inner_alpha); - } return color; } From 3902bef42f9a772ee8f5ece6fc59af769554e857 Mon Sep 17 00:00:00 2001 From: Natalia Asteria Date: Wed, 29 Mar 2023 18:24:17 +0700 Subject: [PATCH 17/24] Fix things Signed-off-by: Natalia Asteria --- crates/bevy_ui/src/render/ui.wgsl | 33 ++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ui/src/render/ui.wgsl b/crates/bevy_ui/src/render/ui.wgsl index 79988e79e21a2..f9c9169f1bbcd 100644 --- a/crates/bevy_ui/src/render/ui.wgsl +++ b/crates/bevy_ui/src/render/ui.wgsl @@ -6,9 +6,11 @@ var view: View; struct VertexOutput { @location(0) uv: vec2, @location(1) color: vec4, - @location(2) border_color: vec4, - @location(3) border_width: f32, - @location(4) radius: f32, + @location(2) size: vec2, + @location(3) point: vec2, + @location(4) border_color: vec4, + @location(5) border_width: f32, + @location(6) radius: f32, @builtin(position) position: vec4, }; @@ -44,6 +46,21 @@ fn vertex( @location(3) ui_uniform_index: u32, ) -> VertexOutput { var out: VertexOutput; + var node = ui_uniform.entries[ui_uniform_index]; + + out.size = node.size; + out.point = vertex_position.xy - node.center; + out.border_width = node.border_width; + out.border_color = unpack_color_from_u32(node.border_color); + + // get radius for this specific corner + var corner_index = select(0, 1, out.position.y > 0.0) + select(0, 2, out.position.x > 0.0); + out.radius = node.corner_radius[corner_index]; + + // clamp radius between (0.0) and (shortest side / 2.0) + out.radius = clamp(out.radius, 0.0, min(out.size.x, out.size.y) / 2.0); + + out.uv = vertex_uv; out.position = view.view_proj * vec4(vertex_position, 1.0); out.color = vertex_color; @@ -63,5 +80,15 @@ fn distance_round_border(point: vec2, size: vec2, radius: f32) -> f32 fn fragment(in: VertexOutput) -> @location(0) vec4 { var color = textureSample(sprite_texture, sprite_sampler, in.uv); color = in.color * color; + + if (in.radius > 0.0 || in.border_width > 0.0) { + var distance = distance_round_border(in.point, in.size * 0.5, in.radius); + + var inner_alpha = 1.0 - smoothstep(0.0, 0.0, distance); + var border_alpha = 1.0 - smoothstep(in.border_width, in.border_width, abs(distance)); + color = mix(vec4(0.0), mix(color, in.border_color, border_alpha), inner_alpha); + } + + return color; } From 2b9ccd2384d2d9ddacb6d4cb9ce8fcd3be65df1c Mon Sep 17 00:00:00 2001 From: Natalia Asteria Date: Wed, 29 Mar 2023 18:47:13 +0700 Subject: [PATCH 18/24] aa Signed-off-by: Natalia Asteria --- crates/bevy_ui/src/render/ui.wgsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ui/src/render/ui.wgsl b/crates/bevy_ui/src/render/ui.wgsl index f9c9169f1bbcd..255d496f38708 100644 --- a/crates/bevy_ui/src/render/ui.wgsl +++ b/crates/bevy_ui/src/render/ui.wgsl @@ -63,7 +63,7 @@ fn vertex( out.uv = vertex_uv; out.position = view.view_proj * vec4(vertex_position, 1.0); - out.color = vertex_color; + out.color = unpack_color_from_u32(node.color); return out; } From ba125bf4030026ef00c102d873777b12e1e11a3d Mon Sep 17 00:00:00 2001 From: Natalia Asteria Date: Fri, 14 Apr 2023 09:21:03 +0700 Subject: [PATCH 19/24] Switch to Iced's border radius code Signed-off-by: Natalia Asteria --- crates/bevy_ui/src/render/ui.wgsl | 78 ++++++++++++++++++++++++++++--- 1 file changed, 71 insertions(+), 7 deletions(-) diff --git a/crates/bevy_ui/src/render/ui.wgsl b/crates/bevy_ui/src/render/ui.wgsl index 255d496f38708..a6c6375dd4763 100644 --- a/crates/bevy_ui/src/render/ui.wgsl +++ b/crates/bevy_ui/src/render/ui.wgsl @@ -11,6 +11,7 @@ struct VertexOutput { @location(4) border_color: vec4, @location(5) border_width: f32, @location(6) radius: f32, + @location(7) pos: vec2, @builtin(position) position: vec4, }; @@ -60,13 +61,48 @@ fn vertex( // clamp radius between (0.0) and (shortest side / 2.0) out.radius = clamp(out.radius, 0.0, min(out.size.x, out.size.y) / 2.0); - + out.pos = vertex_position.xy; out.uv = vertex_uv; out.position = view.view_proj * vec4(vertex_position, 1.0); out.color = unpack_color_from_u32(node.color); return out; } + +fn distance_alg( + frag_coord: vec2, + position: vec2, + size: vec2, + radius: f32 +) -> f32 { + var inner_size: vec2 = size - vec2(radius, radius) * 2.0; + var top_left: vec2 = position + vec2(radius, radius); + var bottom_right: vec2 = top_left + inner_size; + + var top_left_distance: vec2 = top_left - frag_coord; + var bottom_right_distance: vec2 = frag_coord - bottom_right; + + var dist: vec2 = vec2( + max(max(top_left_distance.x, bottom_right_distance.x), 0.0), + max(max(top_left_distance.y, bottom_right_distance.y), 0.0) + ); + + return sqrt(dist.x * dist.x + dist.y * dist.y); +} + +// Based on the fragement position and the center of the quad, select one of the 4 radi. +// Order matches CSS border radius attribute: +// radi.x = top-left, radi.y = top-right, radi.z = bottom-right, radi.w = bottom-left +fn select_border_radius(radi: vec4, position: vec2, center: vec2) -> f32 { + var rx = radi.x; + var ry = radi.y; + rx = select(radi.x, radi.y, position.x > center.x); + ry = select(radi.w, radi.z, position.x > center.x); + rx = select(rx, ry, position.y > center.y); + return rx; +} + + @group(1) @binding(0) var sprite_texture: texture_2d; @group(1) @binding(1) @@ -80,15 +116,43 @@ fn distance_round_border(point: vec2, size: vec2, radius: f32) -> f32 fn fragment(in: VertexOutput) -> @location(0) vec4 { var color = textureSample(sprite_texture, sprite_sampler, in.uv); color = in.color * color; + + - if (in.radius > 0.0 || in.border_width > 0.0) { - var distance = distance_round_border(in.point, in.size * 0.5, in.radius); + var border_radius = in.radius; + var scale = vec2(1.0, 1.0); - var inner_alpha = 1.0 - smoothstep(0.0, 0.0, distance); - var border_alpha = 1.0 - smoothstep(in.border_width, in.border_width, abs(distance)); - color = mix(vec4(0.0), mix(color, in.border_color, border_alpha), inner_alpha); + if (in.border_width > 0.0) { + var internal_border: f32 = max(border_radius - in.border_width, 0.0); + + var internal_distance: f32 = distance_alg( + in.position.xy, + in.pos + vec2(in.border_width, in.border_width), + scale - vec2(in.border_width * 2.0, in.border_width * 2.0), + internal_border + ); + + var border_mix: f32 = smoothstep( + max(internal_border - 0.5, 0.0), + internal_border + 0.5, + internal_distance + ); + + color = mix(in.color, in.border_color, vec4(border_mix, border_mix, border_mix, border_mix)); } + var dist: f32 = distance_alg( + vec2(in.position.x, in.position.y), + in.pos, + scale, + border_radius + ); + + var radius_alpha: f32 = 1.0 - smoothstep( + max(border_radius - 0.5, 0.0), + border_radius + 0.5, + dist + ); - return color; + return vec4(color.x, color.y, color.z, color.w * radius_alpha); } From cbeaf92c80c39f7761efcddaef7515c9568193ff Mon Sep 17 00:00:00 2001 From: Natalia Asteria Date: Fri, 14 Apr 2023 09:37:45 +0700 Subject: [PATCH 20/24] Revert "Switch to Iced's border radius code" This reverts commit ba125bf4030026ef00c102d873777b12e1e11a3d. --- crates/bevy_ui/src/render/ui.wgsl | 78 +++---------------------------- 1 file changed, 7 insertions(+), 71 deletions(-) diff --git a/crates/bevy_ui/src/render/ui.wgsl b/crates/bevy_ui/src/render/ui.wgsl index a6c6375dd4763..255d496f38708 100644 --- a/crates/bevy_ui/src/render/ui.wgsl +++ b/crates/bevy_ui/src/render/ui.wgsl @@ -11,7 +11,6 @@ struct VertexOutput { @location(4) border_color: vec4, @location(5) border_width: f32, @location(6) radius: f32, - @location(7) pos: vec2, @builtin(position) position: vec4, }; @@ -61,48 +60,13 @@ fn vertex( // clamp radius between (0.0) and (shortest side / 2.0) out.radius = clamp(out.radius, 0.0, min(out.size.x, out.size.y) / 2.0); - out.pos = vertex_position.xy; + out.uv = vertex_uv; out.position = view.view_proj * vec4(vertex_position, 1.0); out.color = unpack_color_from_u32(node.color); return out; } - -fn distance_alg( - frag_coord: vec2, - position: vec2, - size: vec2, - radius: f32 -) -> f32 { - var inner_size: vec2 = size - vec2(radius, radius) * 2.0; - var top_left: vec2 = position + vec2(radius, radius); - var bottom_right: vec2 = top_left + inner_size; - - var top_left_distance: vec2 = top_left - frag_coord; - var bottom_right_distance: vec2 = frag_coord - bottom_right; - - var dist: vec2 = vec2( - max(max(top_left_distance.x, bottom_right_distance.x), 0.0), - max(max(top_left_distance.y, bottom_right_distance.y), 0.0) - ); - - return sqrt(dist.x * dist.x + dist.y * dist.y); -} - -// Based on the fragement position and the center of the quad, select one of the 4 radi. -// Order matches CSS border radius attribute: -// radi.x = top-left, radi.y = top-right, radi.z = bottom-right, radi.w = bottom-left -fn select_border_radius(radi: vec4, position: vec2, center: vec2) -> f32 { - var rx = radi.x; - var ry = radi.y; - rx = select(radi.x, radi.y, position.x > center.x); - ry = select(radi.w, radi.z, position.x > center.x); - rx = select(rx, ry, position.y > center.y); - return rx; -} - - @group(1) @binding(0) var sprite_texture: texture_2d; @group(1) @binding(1) @@ -116,43 +80,15 @@ fn distance_round_border(point: vec2, size: vec2, radius: f32) -> f32 fn fragment(in: VertexOutput) -> @location(0) vec4 { var color = textureSample(sprite_texture, sprite_sampler, in.uv); color = in.color * color; - - - var border_radius = in.radius; - var scale = vec2(1.0, 1.0); + if (in.radius > 0.0 || in.border_width > 0.0) { + var distance = distance_round_border(in.point, in.size * 0.5, in.radius); - if (in.border_width > 0.0) { - var internal_border: f32 = max(border_radius - in.border_width, 0.0); - - var internal_distance: f32 = distance_alg( - in.position.xy, - in.pos + vec2(in.border_width, in.border_width), - scale - vec2(in.border_width * 2.0, in.border_width * 2.0), - internal_border - ); - - var border_mix: f32 = smoothstep( - max(internal_border - 0.5, 0.0), - internal_border + 0.5, - internal_distance - ); - - color = mix(in.color, in.border_color, vec4(border_mix, border_mix, border_mix, border_mix)); + var inner_alpha = 1.0 - smoothstep(0.0, 0.0, distance); + var border_alpha = 1.0 - smoothstep(in.border_width, in.border_width, abs(distance)); + color = mix(vec4(0.0), mix(color, in.border_color, border_alpha), inner_alpha); } - var dist: f32 = distance_alg( - vec2(in.position.x, in.position.y), - in.pos, - scale, - border_radius - ); - - var radius_alpha: f32 = 1.0 - smoothstep( - max(border_radius - 0.5, 0.0), - border_radius + 0.5, - dist - ); - return vec4(color.x, color.y, color.z, color.w * radius_alpha); + return color; } From e500f337ae583aac7f70ea27fb66e820c6e3f8d7 Mon Sep 17 00:00:00 2001 From: Natalia Asteria Date: Fri, 14 Apr 2023 11:15:19 +0700 Subject: [PATCH 21/24] Add corner radius and border to ButtonBundle Signed-off-by: Natalia Asteria --- crates/bevy_ui/src/node_bundles.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/bevy_ui/src/node_bundles.rs b/crates/bevy_ui/src/node_bundles.rs index 3f49e08481e24..254fcf6dc241b 100644 --- a/crates/bevy_ui/src/node_bundles.rs +++ b/crates/bevy_ui/src/node_bundles.rs @@ -236,6 +236,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 { @@ -253,6 +257,8 @@ impl Default for ButtonBundle { visibility: Default::default(), computed_visibility: Default::default(), z_index: Default::default(), + corner_radius: Default::default(), + border: Default::default(), } } } From 97d15e0847cbdba53fb9af39e53c758333a583ac Mon Sep 17 00:00:00 2001 From: Natalia Asteria Date: Fri, 14 Apr 2023 11:18:36 +0700 Subject: [PATCH 22/24] move the corner_radius to the one with the actual background color Signed-off-by: Natalia Asteria --- crates/bevy_ui/src/ui_node.rs | 4 ++++ examples/ui/button.rs | 3 +-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index 298bfe8c495c3..380de652028b5 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -971,6 +971,10 @@ mod tests { /// The corner radius of the node /// /// This describes a radius value for each corner of a node, even if they have no [`Border`]. +/// +/// ## Limitations +/// Currently, every UI nodes that have CornerRadius must have its BackgroundColor's opacity to something bigger than zero.
+/// TODO: Remove this limitation #[derive(Component, Default, Copy, Clone, Debug, Reflect)] #[reflect(Component)] pub struct CornerRadius { diff --git a/examples/ui/button.rs b/examples/ui/button.rs index f599db05cc94f..59c678262cc4a 100644 --- a/examples/ui/button.rs +++ b/examples/ui/button.rs @@ -54,8 +54,6 @@ fn setup(mut commands: Commands, asset_server: Res) { justify_content: JustifyContent::Center, ..default() }, - - corner_radius: CornerRadius::all(25.0), border: Border { color: Color::rgb(0.05, 0.05, 0.05), width: 1.0, @@ -74,6 +72,7 @@ fn setup(mut commands: Commands, asset_server: Res) { ..default() }, background_color: NORMAL_BUTTON.into(), + corner_radius: CornerRadius::all(25.0), ..default() }) .with_children(|parent| { From 34eaeb0c71c2ddfae584807b1f1fb33648ec9698 Mon Sep 17 00:00:00 2001 From: Natalia Asteria Date: Sun, 11 Jun 2023 06:06:17 +0700 Subject: [PATCH 23/24] fix the import Signed-off-by: Natalia Asteria --- crates/bevy_ui/src/node_bundles.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ui/src/node_bundles.rs b/crates/bevy_ui/src/node_bundles.rs index a4500de9eb368..90d29c77d8a95 100644 --- a/crates/bevy_ui/src/node_bundles.rs +++ b/crates/bevy_ui/src/node_bundles.rs @@ -1,9 +1,9 @@ //! This module contains basic node bundles used to build UIs use crate::{ - - widget::{Button, TextFlags, UiImageSize, Border, CalculatedSize, CornerRadius,}, - BackgroundColor, ContentSize, FocusPolicy, Interaction, Node, Style, UiImage, ZIndex, + widget::{Button, TextFlags, UiImageSize}, + BackgroundColor, Border, ContentSize, CornerRadius, FocusPolicy, Interaction, + Node, Style, UiImage, ZIndex, }; use bevy_ecs::bundle::Bundle; use bevy_render::{ From ee52a03cb0b9b7e7729f6686b839175111f75226 Mon Sep 17 00:00:00 2001 From: Natalia Asteria Date: Sun, 11 Jun 2023 06:08:46 +0700 Subject: [PATCH 24/24] fix aliasing Signed-off-by: Natalia Asteria --- crates/bevy_ui/src/render/ui.wgsl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/bevy_ui/src/render/ui.wgsl b/crates/bevy_ui/src/render/ui.wgsl index 255d496f38708..306c1e4e0086d 100644 --- a/crates/bevy_ui/src/render/ui.wgsl +++ b/crates/bevy_ui/src/render/ui.wgsl @@ -83,8 +83,7 @@ fn fragment(in: VertexOutput) -> @location(0) vec4 { if (in.radius > 0.0 || in.border_width > 0.0) { var distance = distance_round_border(in.point, in.size * 0.5, in.radius); - - var inner_alpha = 1.0 - smoothstep(0.0, 0.0, distance); + var inner_alpha = 1.0 - smoothstep(0.0, 0.1, distance); var border_alpha = 1.0 - smoothstep(in.border_width, in.border_width, abs(distance)); color = mix(vec4(0.0), mix(color, in.border_color, border_alpha), inner_alpha); }