Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add UI Materials #9506

Merged
merged 63 commits into from
Nov 3, 2023
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
2d37454
Adds UIMaterial Struct
MarkusTheOrt Aug 17, 2023
fb90baa
Adds `MaterialNodeBundle`
MarkusTheOrt Aug 18, 2023
7a98e22
export material::* from bevy_ui
MarkusTheOrt Aug 18, 2023
ccba0a2
reverts UI shader
MarkusTheOrt Aug 18, 2023
4298848
UiMaterial now working as intended
MarkusTheOrt Aug 20, 2023
cbbbd88
fixes compilation warnings
MarkusTheOrt Aug 20, 2023
7ed8914
fix cargo clippy warnings
MarkusTheOrt Aug 20, 2023
e65163e
adds example `ui_material`
MarkusTheOrt Aug 21, 2023
d5e12d4
ran cargo fmt
MarkusTheOrt Aug 21, 2023
bb19f42
Split ui_material pipeline into multiple files
MarkusTheOrt Aug 21, 2023
3b8022a
removes usage of UiPipeline inside UiMaterialPipeline
MarkusTheOrt Aug 21, 2023
ced9292
Use `UiMaterialVertex` without `mode` field.
MarkusTheOrt Aug 22, 2023
62709a0
Apply suggestions from code review
MarkusTheOrt Aug 22, 2023
62a51e0
removes export for UiVertex
MarkusTheOrt Aug 22, 2023
d4327cc
changed example `ui_material` back to single MaterialNodeBundle
MarkusTheOrt Aug 22, 2023
7706744
add `UiVertexOutput` shader export
MarkusTheOrt Aug 23, 2023
493d3cc
move `UiVertexOutput` into its own file (`ui_vertex_output.wgsl`)
MarkusTheOrt Aug 24, 2023
f637d7a
adds UiMaterialNode marker component
MarkusTheOrt Aug 27, 2023
033dee0
Merge branch 'bevyengine:main' into ui_material
MarkusTheOrt Aug 27, 2023
86ae1b8
ran cargo fmt
MarkusTheOrt Aug 27, 2023
66a4cd9
Fix crash where a empty UiBatch spawns
MarkusTheOrt Aug 27, 2023
ac911ba
Merge branch 'main' of github.com:MarkusTheOrt/bevy into ui_material
MarkusTheOrt Sep 10, 2023
d800744
Fix compiler errors, disables lots of functionality
MarkusTheOrt Sep 10, 2023
4c65b29
change `extract_uinodes` query to `Has<UiMaterialNode>`
MarkusTheOrt Sep 10, 2023
794b722
adds back material rendering capabilities
MarkusTheOrt Sep 10, 2023
4a150cd
ran cargo fmt
MarkusTheOrt Sep 10, 2023
c9fcabe
fix compiler warnings
MarkusTheOrt Sep 10, 2023
193197a
ran cargo fmt
MarkusTheOrt Sep 10, 2023
d2fbd1d
improve docs surrounding `UiMaterial`
MarkusTheOrt Sep 10, 2023
930ec95
fix typo in doc comment
MarkusTheOrt Sep 10, 2023
fef7fac
remove `is_material` from `ExtracedUiNode`
MarkusTheOrt Sep 11, 2023
7eeb644
update example `ui_material`
MarkusTheOrt Sep 11, 2023
6294a17
add `InheritedVisibility` to `MaterialNodeBundle`
MarkusTheOrt Sep 11, 2023
1226bf1
remove `UiMaterialNode` marker component
MarkusTheOrt Sep 11, 2023
301b193
adds description to `ui_material` example
MarkusTheOrt Sep 11, 2023
e77172b
Fix typo in `material_pipeline.rs`
MarkusTheOrt Sep 12, 2023
239faf9
renamed some files to be more descriptive
MarkusTheOrt Sep 14, 2023
1239117
removes vertexcolor from `ui_material_pipeline`
MarkusTheOrt Sep 14, 2023
bc2c029
updated example ui_material
MarkusTheOrt Sep 14, 2023
84ef0ac
removes unused imports
MarkusTheOrt Sep 14, 2023
62b99d6
ci fixes
MarkusTheOrt Sep 14, 2023
1e0727a
fixed wrong comment formatting
MarkusTheOrt Sep 14, 2023
781fb57
Merge branch 'main' of github.com:MarkusTheOrt/bevy into ui_material
MarkusTheOrt Sep 19, 2023
7988610
Apply suggestions from code review
MarkusTheOrt Oct 6, 2023
b51c143
Merge branch 'main' of github.com:MarkusTheOrt/bevy into ui_material
MarkusTheOrt Oct 16, 2023
57419c0
Update from main
MarkusTheOrt Oct 16, 2023
4a9bf5f
add missing example in README
MarkusTheOrt Oct 16, 2023
08cf2f3
code review changes
MarkusTheOrt Oct 16, 2023
a978a1e
fix line ending in ui.wgsl
MarkusTheOrt Oct 16, 2023
a3c613e
add border sizes to vertex data
MarkusTheOrt Oct 21, 2023
fb49734
Merge branch 'main' of github.com:MarkusTheOrt/bevy into ui_material
MarkusTheOrt Oct 21, 2023
83d703a
fix compile error
MarkusTheOrt Oct 21, 2023
b0c6ec9
add docs for UiVertexOutput
MarkusTheOrt Oct 21, 2023
12b6a33
fix error in docs
MarkusTheOrt Oct 21, 2023
3ea88ca
remove doc comment from UiMatreialBatch as suggested
MarkusTheOrt Oct 21, 2023
65ae48a
apply suggestions from code review
MarkusTheOrt Oct 21, 2023
b412839
Merge branch 'main' of github.com:MarkusTheOrt/bevy into ui_material
MarkusTheOrt Oct 21, 2023
349af3c
fix compile errors, fix wgsl imports
MarkusTheOrt Oct 21, 2023
d5909ba
adopt new bind group API
MarkusTheOrt Oct 21, 2023
c61a2aa
remove old import from example shader
MarkusTheOrt Oct 23, 2023
afe56e5
Merge branch 'main' of github.com:MarkusTheOrt/bevy into ui_material
MarkusTheOrt Oct 28, 2023
f8267d1
apply suggestions from code review
MarkusTheOrt Oct 28, 2023
2425a10
Merge branch 'main' of github.com:MarkusTheOrt/bevy into ui_material
MarkusTheOrt Nov 3, 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
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2298,6 +2298,17 @@ description = "Demonstrates resizing and responding to resizing a window"
category = "Window"
wasm = true

[[example]]
name = "ui_material"
path = "examples/ui/ui_material.rs"
doc-scrape-examples = true

[package.metadata.example.ui_material]
name = "UI Material"
description = "Demonstrates Creating and using custom Ui materials"
MarkusTheOrt marked this conversation as resolved.
Show resolved Hide resolved
category = "UI (User Interface)"
wasm = true

[profile.wasm-release]
inherits = "release"
opt-level = "z"
Expand Down
43 changes: 43 additions & 0 deletions assets/shaders/circle_shader.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// This shader draws a circular progress bar
#import bevy_render::view View
#import bevy_ui::ui_vertex_output UiVertexOutput

struct CustomUiMaterial {
@location(0) fill_amount: f32,
@location(1) color: vec4<f32>
}

@group(0) @binding(0)
var<uniform> view: View;
@group(1) @binding(0)
var<uniform> input: CustomUiMaterial;

// How smooth the border of the gradient should be
const gradient_ease: f32 = 25.0;
// the width of the gradient
const width = 0.25;
const PI = 3.141592656;
const TAU = 6.283185312;

@fragment
fn fragment(in: UiVertexOutput) -> @location(0) vec4<f32> {
MarkusTheOrt marked this conversation as resolved.
Show resolved Hide resolved
let fill_amount = input.fill_amount;
let fill_angle = fill_amount * TAU;
let uv = in.uv * 2.0 - 1.0;
var color = vec4<f32>(0.0);
if (atan2(uv.y, uv.x) + PI < fill_angle) {
var inner_width = 1.0 - width;
inner_width *= inner_width;
let d = uv.x * uv.x + uv.y * uv.y;
if (d <= 1.0 && d >= inner_width) {
var w: f32 = abs((1.0 + inner_width) / 2.0 - d) / (1.0 - inner_width);
w = 1.0 - pow(w + 0.5, gradient_ease);
color = vec4<f32>(input.color.rgb, min(1.0, w));
} else {
color.a = 0.0;
}
} else {
color.a = 0.0;
}
return color;
}
6 changes: 4 additions & 2 deletions crates/bevy_ui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//! This UI is laid out with the Flexbox and CSS Grid layout models (see <https://cssreference.io/flexbox/>)

pub mod camera_config;
pub mod material;
pub mod measurement;
pub mod node_bundles;
pub mod update;
Expand All @@ -25,6 +26,7 @@ mod ui_node;
pub use focus::*;
pub use geometry::*;
pub use layout::*;
pub use material::*;
pub use measurement::*;
pub use render::*;
pub use ui_node::*;
Expand All @@ -34,8 +36,8 @@ use widget::UiImageSize;
pub mod prelude {
#[doc(hidden)]
pub use crate::{
camera_config::*, geometry::*, node_bundles::*, ui_node::*, widget::Button, widget::Label,
Interaction, UiScale,
camera_config::*, geometry::*, material::*, node_bundles::*, ui_node::*, widget::Button,
widget::Label, Interaction, UiMaterialPlugin, UiScale,
};
}

Expand Down
129 changes: 129 additions & 0 deletions crates/bevy_ui/src/material.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
use std::hash::Hash;

use bevy_asset::Asset;
use bevy_render::render_resource::{AsBindGroup, RenderPipelineDescriptor, ShaderRef};
/// Materials are used alongside [`UiMaterialPlugin`](crate::UiMaterialPipeline) and [`MaterialNodeBundle`](crate::prelude::MaterialNodeBundle)
MarkusTheOrt marked this conversation as resolved.
Show resolved Hide resolved
/// to spawn entities that are rendered with a specific [`UiMaterial`] type. They serve as an easy to use high level
/// way to render `Node` entities with custom shader logic.
///
/// `UiMaterials` must implement [`AsBindGroup`] to define how data will be transferred to the GPU and bound in shaders.
/// [`AsBindGroup`] can be derived, which makes generating bindings straightforward. See the [`AsBindGroup`] docs for details.
///
/// Materials must also implement [`Asset`] so they can be treated as such.
///
/// # Example
///
/// Here is a simple [`UiMaterial`] implementation. The [`AsBindGroup`] derive has many features. To see what else is available,
/// check out the [`AsBindGroup`] documentation.
/// ```
/// # use bevy_ui::prelude::*;
/// # use bevy_ecs::prelude::*;
/// # use bevy_reflect::TypePath;
/// # use bevy_render::{render_resource::{AsBindGroup, ShaderRef}, texture::Image, color::Color};
/// # use bevy_asset::{Handle, AssetServer, Assets, Asset};
///
/// #[derive(AsBindGroup, Asset, TypePath, Debug, Clone)]
/// pub struct CustomMaterial {
/// // Uniform bindings must implement `ShaderType`, which will be used to convert the value to
/// // its shader-compatible equivalent. Most core math types already implement `ShaderType`.
/// #[uniform(0)]
/// color: Color,
/// // Images can be bound as textures in shaders. If the Image's sampler is also needed, just
/// // add the sampler attribute with a different binding index.
/// #[texture(1)]
/// #[sampler(2)]
/// color_texture: Handle<Image>,
/// }
///
/// // All functions on `UiMaterial` have default impls. You only need to implement the
/// // functions that are relevant for your material.
/// impl UiMaterial for CustomMaterial {
/// fn fragment_shader() -> ShaderRef {
/// "shaders/custom_material.wgsl".into()
/// }
/// }
///
/// // Spawn an entity using `CustomMaterial`.
/// fn setup(mut commands: Commands, mut materials: ResMut<Assets<CustomMaterial>>, asset_server: Res<AssetServer>) {
/// commands.spawn(MaterialNodeBundle {
/// style: Style {
/// width: Val::Percent(100.0),
/// ..Default::default()
/// },
/// material: materials.add(CustomMaterial {
/// color: Color::RED,
/// color_texture: asset_server.load("some_image.png"),
/// }),
/// ..Default::default()
/// });
/// }
/// ```
/// In WGSL shaders, the material's binding would look like this:
///
/// ```wgsl
/// struct CustomMaterial {
/// color: vec4<f32>,
/// }
///
/// @group(1) @binding(0)
/// var<uniform> material: CustomMaterial;
/// @group(1) @binding(1)
/// var color_texture: texture_2d<f32>;
/// @group(1) @binding(2)
/// var color_sampler: sampler;
/// ```
pub trait UiMaterial: AsBindGroup + Asset + Clone + Sized {
/// Returns this materials vertex shader. If [`ShaderRef::Default`] is returned, the default UI
/// vertex shader will be used.
fn vertex_shader() -> ShaderRef {
ShaderRef::Default
}

/// Returns this materials fragment shader. If [`ShaderRef::Default`] is returned, the default
/// UI fragment shader will be used.
fn fragment_shader() -> ShaderRef {
ShaderRef::Default
}

#[allow(unused_variables)]
#[inline]
fn specialize(descriptor: &mut RenderPipelineDescriptor, key: UiMaterialKey<Self>) {}
}

pub struct UiMaterialKey<M: UiMaterial> {
pub hdr: bool,
pub bind_group_data: M::Data,
}

impl<M: UiMaterial> Eq for UiMaterialKey<M> where M::Data: PartialEq {}

impl<M: UiMaterial> PartialEq for UiMaterialKey<M>
where
M::Data: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
self.hdr == other.hdr && self.bind_group_data == other.bind_group_data
}
}

impl<M: UiMaterial> Clone for UiMaterialKey<M>
where
M::Data: Clone,
{
fn clone(&self) -> Self {
Self {
hdr: self.hdr,
bind_group_data: self.bind_group_data.clone(),
}
}
}

impl<M: UiMaterial> Hash for UiMaterialKey<M>
where
M::Data: Hash,
{
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.hdr.hash(state);
self.bind_group_data.hash(state);
}
}
51 changes: 50 additions & 1 deletion crates/bevy_ui/src/node_bundles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::widget::TextFlags;
use crate::{
widget::{Button, UiImageSize},
BackgroundColor, BorderColor, ContentSize, FocusPolicy, Interaction, Node, Style, UiImage,
UiTextureAtlasImage, ZIndex,
UiMaterial, UiTextureAtlasImage, ZIndex,
};
use bevy_asset::Handle;
use bevy_ecs::bundle::Bundle;
Expand Down Expand Up @@ -329,3 +329,52 @@ impl Default for ButtonBundle {
}
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah noticed a snag, ideally we want some sort of MaterialImageBundle as well:

/// A Ui Node that renders a shader
#[derive(Bundle, Debug)]
pub struct MaterialImageBundle<M: UiMaterial> {
    /// Describes the logical size of the node
    pub node: Node,
    /// Styles which control the layout (size and position) of the node and it's children
    /// In some cases these styles also affect how the node drawn/painted.
    pub style: Style,
    /// The Material of the Node.
    pub material: Handle<M>,
    /// Whether this node should block interaction with lower nodes
    pub focus_policy: FocusPolicy,
    /// The transform of the node
    ///
    /// This field is automatically managed by the UI layout system.
    /// To alter the position of the `NodeBundle`, use the properties of the [`Style`] component.
    pub transform: Transform,
    /// The global transform of the node
    ///
    /// This field is automatically managed by the UI layout system.
    /// To alter the position of the `NodeBundle`, use the properties of the [`Style`] component.
    pub global_transform: GlobalTransform,
    /// Describes the visibility properties of the node
    pub visibility: Visibility,
    /// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
    pub computed_visibility: ComputedVisibility,
    /// Indicates the depth at which the node should appear in the UI
    pub z_index: ZIndex,
    /// The size of the image in pixels
    ///
    /// This field is set automatically
    pub image_size: UiImageSize,
    /// The calculated size based on the given image
    pub content_size: ContentSize,

}

impl<M: UiMaterial> Default for MaterialImageBundle<M> {
    fn default() -> Self {
        Self {
            node: Default::default(),
            style: Default::default(),
            material: Default::default(),
            focus_policy: Default::default(),
            transform: Default::default(),
            global_transform: Default::default(),
            visibility: Default::default(),
            computed_visibility: Default::default(),
            z_index: Default::default(),
            content_size: Default::default(),
        }
    }
}

The ContentSize component contains a measure func that is used to negotiate with the layout algorithm to find an optimal size for a node's content given that content's properties (like its aspect ratio and pixel density) and the space constraints of the UI layout.

ImageBundle has a matching system update_image_content_size_system that retrieves the size of the image from its texture asset and sets a measure func that preserves its aspect ratio. But we can't do that here because UiMaterial doesn't expose its texture asset publically (if it has one).

Copy link
Contributor

@ickshonpe ickshonpe Aug 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be fine not to worry about measurefuncs for this PR though, the implementation looks great, really nice and clean.

It's not essential as users can create their own system to add a measurefunc, which isn't very hard. We do need a custom measurefunc example, it would make sense to base one around a UiMaterial.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great suggestion, I agree with you there, I wasn't aware of ContentSize

I'd like to get this PR done and would be happy to take on the MaterialImageBundle as a followup once it is ready

Copy link
Contributor

@superdump superdump Oct 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn’t the image just be a member of the material type? @ickshonpe as well as other data needed - the image size.

/// A UI node that is rendered using a [`UiMaterial`]
#[derive(Bundle, Clone, Debug)]
pub struct MaterialNodeBundle<M: UiMaterial> {
/// Describes the logical size of the node
pub node: Node,
/// Styles which control the layout (size and position) of the node and it's children
/// In some cases these styles also affect how the node drawn/painted.
pub style: Style,
/// The [`UiMaterial`] used to render the node.
pub material: Handle<M>,
/// Whether this node should block interaction with lower nodes
pub focus_policy: FocusPolicy,
/// The transform of the node
///
/// This field is automatically managed by the UI layout system.
/// To alter the position of the `NodeBundle`, use the properties of the [`Style`] component.
pub transform: Transform,
/// The global transform of the node
///
/// This field is automatically managed by the UI layout system.
/// To alter the position of the `NodeBundle`, use the properties of the [`Style`] component.
pub global_transform: GlobalTransform,
/// Describes the visibility properties of the node
pub visibility: Visibility,
/// Inherited visibility of an entity.
pub inherited_visibility: InheritedVisibility,
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
pub view_visibility: ViewVisibility,
/// Indicates the depth at which the node should appear in the UI
pub z_index: ZIndex,
}

impl<M: UiMaterial> Default for MaterialNodeBundle<M> {
fn default() -> Self {
Self {
node: Default::default(),
style: Default::default(),
material: Default::default(),
focus_policy: Default::default(),
transform: Default::default(),
global_transform: Default::default(),
visibility: Default::default(),
inherited_visibility: Default::default(),
view_visibility: Default::default(),
z_index: Default::default(),
}
}
}
23 changes: 23 additions & 0 deletions crates/bevy_ui/src/render/material.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#import bevy_render::view View
#import bevy_ui::ui_vertex_output UiVertexOutput

@group(0) @binding(0)
var<uniform> view: View;

@vertex
fn vertex(
@location(0) vertex_position: vec3<f32>,
@location(1) vertex_uv: vec2<f32>,
@location(2) vertex_color: vec4<f32>,
) -> UiVertexOutput {
var out: UiVertexOutput;
out.uv = vertex_uv;
out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
out.color = vertex_color;
MarkusTheOrt marked this conversation as resolved.
Show resolved Hide resolved
return out;
}

@fragment
fn fragment(in: UiVertexOutput) -> @location(0) vec4<f32> {
return vec4<f32>(1.0);
}
Loading
Loading