From 2d374543d0e9779148304fa7e5b66ad8ae64d2d1 Mon Sep 17 00:00:00 2001 From: Markus Ort Date: Thu, 17 Aug 2023 11:30:15 +0200 Subject: [PATCH 01/55] Adds UIMaterial Struct --- crates/bevy_ui/src/lib.rs | 1 + crates/bevy_ui/src/material.rs | 132 +++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 crates/bevy_ui/src/material.rs diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index 1752180b77d86..afa7d2bda0e0c 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -10,6 +10,7 @@ mod layout; mod render; mod stack; mod ui_node; +mod material; #[cfg(feature = "bevy_text")] mod accessibility; diff --git a/crates/bevy_ui/src/material.rs b/crates/bevy_ui/src/material.rs new file mode 100644 index 0000000000000..66343beda09d0 --- /dev/null +++ b/crates/bevy_ui/src/material.rs @@ -0,0 +1,132 @@ +use std::{hash::Hash, marker::PhantomData}; + +use bevy_app::{App, Plugin}; +use bevy_asset::{AddAsset, Handle}; +use bevy_ecs::system::Resource; +use bevy_reflect::{TypePath, TypeUuid}; +use bevy_render::{ + extract_component::ExtractComponentPlugin, + render_resource::{ + AsBindGroup, BindGroupLayout, RenderPipelineDescriptor, Shader, + ShaderRef, SpecializedRenderPipeline, + }, + RenderApp, +}; + +use crate::{UiPipeline, UiPipelineKey}; + +pub struct UiMaterialKey { + pub is_hdr: bool, + pub bind_group_data: M::Data, +} + +impl Eq for UiMaterialKey where M::Data: PartialEq {} + +impl PartialEq for UiMaterialKey +where + M::Data: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + self.is_hdr == other.is_hdr && self.bind_group_data == other.bind_group_data + } +} + +impl Clone for UiMaterialKey +where + M::Data: Clone, +{ + fn clone(&self) -> Self { + Self { + is_hdr: self.is_hdr, + bind_group_data: self.bind_group_data.clone(), + } + } +} + +impl Hash for UiMaterialKey +where + M::Data: Hash, +{ + fn hash(&self, state: &mut H) { + self.is_hdr.hash(state); + self.bind_group_data.hash(state); + } +} + +pub trait UiMaterial: AsBindGroup + Send + Sync + Clone + TypeUuid + TypePath + Sized { + fn vertex_shader() -> ShaderRef { + ShaderRef::Default + } + + fn fragment_shader() -> ShaderRef { + ShaderRef::Default + } + + #[allow(unused_variables)] + #[inline] + fn specialize(descriptor: &mut RenderPipelineDescriptor, key: UiMaterialKey) {} +} + +pub struct UiMaterialPlugin { + pub _marker: PhantomData, +} + +impl Default for UiMaterialPlugin { + fn default() -> Self { + Self { + _marker: Default::default(), + } + } +} + +impl Plugin for UiMaterialPlugin +where + M::Data: PartialEq + Eq + Hash + Clone, +{ + fn build(&self, app: &mut App) { + app.add_asset::() + .add_plugins(ExtractComponentPlugin::>::extract_visible()); + + + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + todo!("Implement this further") + } + } +} + +#[derive(Resource)] +pub struct UiMaterialPipeline { + pub ui_pipeline: UiPipeline, + pub ui_material_layout: BindGroupLayout, + pub vertex_shader: Option>, + pub fragment_shader: Option>, + markus: PhantomData, +} + +impl SpecializedRenderPipeline for UiMaterialPipeline +where + M::Data: PartialEq + Eq + Hash + Clone, +{ + type Key = UiMaterialKey; + fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { + let mut descriptor = self + .ui_pipeline + .specialize(UiPipelineKey { hdr: key.is_hdr }); + if let Some(vertex_shader) = &self.vertex_shader { + descriptor.vertex.shader = vertex_shader.clone(); + } + + if let Some(fragment_shader) = &self.fragment_shader { + descriptor.fragment.as_mut().unwrap().shader = fragment_shader.clone(); + } + + descriptor.layout = vec![ + self.ui_pipeline.view_layout.clone(), + self.ui_pipeline.image_layout.clone(), + self.ui_material_layout.clone(), + ]; + + M::specialize(&mut descriptor, key); + descriptor + } +} From fb90baa73a0ea2bfd9b718eb7f382a766be86c1c Mon Sep 17 00:00:00 2001 From: Markus Ort Date: Sat, 19 Aug 2023 00:09:36 +0200 Subject: [PATCH 02/55] Adds `MaterialNodeBundle` Adds a new `MaterialNodeBundle` that uses the new `UiMaterial` to render Nodes with a certain Shader and Parameters --- crates/bevy_ui/src/material.rs | 405 ++++++++++++++++++++++---- crates/bevy_ui/src/render/pipeline.rs | 2 +- crates/bevy_ui/src/render/ui.wgsl | 21 +- 3 files changed, 363 insertions(+), 65 deletions(-) diff --git a/crates/bevy_ui/src/material.rs b/crates/bevy_ui/src/material.rs index 66343beda09d0..2e6fbd11ebf14 100644 --- a/crates/bevy_ui/src/material.rs +++ b/crates/bevy_ui/src/material.rs @@ -1,22 +1,108 @@ use std::{hash::Hash, marker::PhantomData}; use bevy_app::{App, Plugin}; -use bevy_asset::{AddAsset, Handle}; -use bevy_ecs::system::Resource; +use bevy_asset::{AddAsset, AssetEvent, AssetServer, Assets, Handle}; +use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::{ + prelude::{Bundle, Entity, EventReader}, + query::ROQueryItem, + schedule::IntoSystemConfigs, + system::{ + lifetimeless::{Read, SRes}, + Commands, Local, Query, Res, ResMut, Resource, SystemParamItem, + }, + world::{FromWorld, World}, +}; use bevy_reflect::{TypePath, TypeUuid}; use bevy_render::{ extract_component::ExtractComponentPlugin, + render_asset::{PrepareAssetSet, RenderAssets}, + render_phase::{ + AddRenderCommand, DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult, + RenderPhase, SetItemPipeline, TrackedRenderPass, + }, render_resource::{ - AsBindGroup, BindGroupLayout, RenderPipelineDescriptor, Shader, - ShaderRef, SpecializedRenderPipeline, + AsBindGroup, AsBindGroupError, AsBindGroupShaderType, BindGroup, BindGroupLayout, + OwnedBindingResource, PipelineCache, RenderPipelineDescriptor, Shader, ShaderRef, + SpecializedRenderPipeline, SpecializedRenderPipelines, }, - RenderApp, + renderer::RenderDevice, + texture::{FallbackImage, Image}, + view::{ComputedVisibility, ExtractedView, ViewUniforms, Visibility}, + Extract, ExtractSchedule, Render, RenderApp, RenderSet, +}; +use bevy_transform::prelude::{GlobalTransform, Transform}; +use bevy_utils::{FloatOrd, HashMap, HashSet}; + +use crate::{ + DrawUi, DrawUiNode, FocusPolicy, Node, SetUiTextureBindGroup, SetUiViewBindGroup, + Style, TransparentUi, UiBatch, UiImageBindGroups, UiMeta, UiPipeline, UiPipelineKey, ZIndex, }; -use crate::{UiPipeline, UiPipelineKey}; +pub trait UiMaterial: AsBindGroup + Send + Sync + Clone + TypeUuid + TypePath + 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) {} +} + +/// Adds the necessary ECS resources and render logic to enable rendering entities using the given +/// [`UiMaterial`] asset type (which includes [`UiMaterial`] types). +pub struct UiMaterialPlugin(PhantomData); + +impl Default for UiMaterialPlugin { + fn default() -> Self { + Self(Default::default()) + } +} + +impl Plugin for UiMaterialPlugin +where + M::Data: PartialEq + Eq + Hash + Clone, +{ + fn build(&self, app: &mut bevy_app::App) { + app.add_asset::() + .add_plugins(ExtractComponentPlugin::>::extract_visible()); + + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + render_app + .add_render_command::>() + .init_resource::>() + .init_resource::>() + .init_resource::>>() + .add_systems(ExtractSchedule, extract_ui_materials::) + .add_systems( + Render, + ( + prepare_ui_materials:: + .in_set(RenderSet::Prepare) + .after(PrepareAssetSet::PreAssetPrepare), + queue_ui_material_nodes::.in_set(RenderSet::Queue), + ), + ); + } + } + + fn finish(&self, app: &mut App) { + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + render_app.init_resource::>(); + } + } +} pub struct UiMaterialKey { - pub is_hdr: bool, + pub hdr: bool, pub bind_group_data: M::Data, } @@ -27,7 +113,7 @@ where M::Data: PartialEq, { fn eq(&self, other: &Self) -> bool { - self.is_hdr == other.is_hdr && self.bind_group_data == other.bind_group_data + self.hdr == other.hdr && self.bind_group_data == other.bind_group_data } } @@ -37,7 +123,7 @@ where { fn clone(&self) -> Self { Self { - is_hdr: self.is_hdr, + hdr: self.hdr, bind_group_data: self.bind_group_data.clone(), } } @@ -48,59 +134,18 @@ where M::Data: Hash, { fn hash(&self, state: &mut H) { - self.is_hdr.hash(state); + self.hdr.hash(state); self.bind_group_data.hash(state); } } -pub trait UiMaterial: AsBindGroup + Send + Sync + Clone + TypeUuid + TypePath + Sized { - fn vertex_shader() -> ShaderRef { - ShaderRef::Default - } - - fn fragment_shader() -> ShaderRef { - ShaderRef::Default - } - - #[allow(unused_variables)] - #[inline] - fn specialize(descriptor: &mut RenderPipelineDescriptor, key: UiMaterialKey) {} -} - -pub struct UiMaterialPlugin { - pub _marker: PhantomData, -} - -impl Default for UiMaterialPlugin { - fn default() -> Self { - Self { - _marker: Default::default(), - } - } -} - -impl Plugin for UiMaterialPlugin -where - M::Data: PartialEq + Eq + Hash + Clone, -{ - fn build(&self, app: &mut App) { - app.add_asset::() - .add_plugins(ExtractComponentPlugin::>::extract_visible()); - - - if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - todo!("Implement this further") - } - } -} - #[derive(Resource)] pub struct UiMaterialPipeline { pub ui_pipeline: UiPipeline, - pub ui_material_layout: BindGroupLayout, + pub ui_layout: BindGroupLayout, pub vertex_shader: Option>, pub fragment_shader: Option>, - markus: PhantomData, + marker: PhantomData, } impl SpecializedRenderPipeline for UiMaterialPipeline @@ -108,10 +153,9 @@ where M::Data: PartialEq + Eq + Hash + Clone, { type Key = UiMaterialKey; + fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { - let mut descriptor = self - .ui_pipeline - .specialize(UiPipelineKey { hdr: key.is_hdr }); + let mut descriptor = self.ui_pipeline.specialize(UiPipelineKey { hdr: key.hdr }); if let Some(vertex_shader) = &self.vertex_shader { descriptor.vertex.shader = vertex_shader.clone(); } @@ -122,11 +166,256 @@ where descriptor.layout = vec![ self.ui_pipeline.view_layout.clone(), + self.ui_layout.clone(), self.ui_pipeline.image_layout.clone(), - self.ui_material_layout.clone(), ]; M::specialize(&mut descriptor, key); - descriptor + + return descriptor; + } +} + +impl FromWorld for UiMaterialPipeline { + fn from_world(world: &mut World) -> Self { + let asset_server = world.resource::(); + let render_device = world.resource::(); + let ui_layout = M::bind_group_layout(render_device); + + UiMaterialPipeline { + ui_pipeline: world.resource::().clone(), + ui_layout, + vertex_shader: match M::vertex_shader() { + ShaderRef::Default => None, + ShaderRef::Handle(handle) => Some(handle), + ShaderRef::Path(path) => Some(asset_server.load(path)), + }, + fragment_shader: match M::fragment_shader() { + ShaderRef::Default => None, + ShaderRef::Handle(handle) => Some(handle), + ShaderRef::Path(path) => Some(asset_server.load(path)), + }, + marker: PhantomData, + } + } +} + +type DrawUiMaterial = ( + SetItemPipeline, + SetUiViewBindGroup<0>, + SetUiMaterialBindGroup, + SetUiTextureBindGroup<2>, + DrawUiNode, +); + +pub struct SetUiMaterialBindGroup(PhantomData); +impl RenderCommand

+ for SetUiMaterialBindGroup +{ + type Param = SRes>; + type ViewWorldQuery = (); + type ItemWorldQuery = Read>; + + fn render<'w>( + item: &P, + view: (), + ui_material_handle: ROQueryItem<'_, Self::ItemWorldQuery>, + materials: SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, + ) -> RenderCommandResult { + let ui_material = materials.into_inner().get(ui_material_handle).unwrap(); + pass.set_bind_group(I, &ui_material.bind_group, &[]); + RenderCommandResult::Success + } +} + +#[allow(clippy::too_many_arguments)] +pub fn queue_ui_material_nodes( + draw_functions: Res>, + ui_material_pipeline: Res>, + mut ui_meta: ResMut, + view_uniforms: Res, + mut pipelines: ResMut>>, + pipeline_cache: Res, + mut image_bind_groups: ResMut, + render_materials: Res>, + ui_batches: Query<(&Handle, Entity, &UiBatch)>, + mut views: Query<(&ExtractedView, &mut RenderPhase)>, +) where + M::Data: PartialEq + Eq + Hash + Clone, +{ + if ui_batches.is_empty() { + return; + } + + if let Some(view_binding) = view_uniforms.uniforms.binding() { + let draw_ui_functrion = draw_functions.read().id::>(); + for (view, mut transparent_phase) in &mut views { + for (handle, entity, batch) in &ui_batches { + if let Some(ui_material) = render_materials.get(handle) { + let pipeline_id = pipelines.specialize( + &pipeline_cache, + &ui_material_pipeline, + UiMaterialKey { + hdr: view.hdr, + bind_group_data: ui_material.key.clone(), + }, + ); + transparent_phase.add(TransparentUi { + sort_key: FloatOrd(batch.z), + entity, + pipeline: pipeline_id, + draw_function: draw_ui_functrion, + }); + } + } + } + } +} + +#[derive(Resource, Deref, DerefMut)] +pub struct RenderUiMaterials(HashMap, PreparedUiMaterial>); + +impl Default for RenderUiMaterials { + fn default() -> Self { + Self(Default::default()) + } +} + +pub struct PreparedUiMaterial { + pub bindings: Vec, + pub bind_group: BindGroup, + pub key: T::Data, +} + +#[derive(Resource)] +pub struct ExtractedUiMaterials { + extracted: Vec<(Handle, M)>, + removed: Vec>, +} + +impl Default for ExtractedUiMaterials { + fn default() -> Self { + Self { + extracted: Default::default(), + removed: Default::default(), + } + } +} + +pub fn extract_ui_materials( + mut commands: Commands, + mut events: Extract>>, + assets: Extract>>, +) { + let mut changed_assets = HashSet::default(); + let mut removed = Vec::new(); + for event in events.iter() { + match event { + AssetEvent::Created { handle } | AssetEvent::Modified { handle } => { + changed_assets.insert(handle.clone_weak()); + } + AssetEvent::Removed { handle } => { + changed_assets.remove(handle); + removed.push(handle.clone_weak()); + } + } + } + + let mut extracted_assets = Vec::new(); + for handle in changed_assets.drain() { + if let Some(asset) = assets.get(&handle) { + extracted_assets.push((handle, asset.clone())); + } + } + + commands.insert_resource(ExtractedUiMaterials { + extracted: extracted_assets, + removed, + }); +} + +pub struct PrepareNextFrameMaterials { + assets: Vec<(Handle, M)>, +} + +impl Default for PrepareNextFrameMaterials { + fn default() -> Self { + Self { + assets: Default::default(), + } + } +} + +pub fn prepare_ui_materials( + mut prepare_next_frame: Local>, + mut extracted_assets: ResMut>, + mut render_materials: ResMut>, + render_device: Res, + images: Res>, + fallback_image: Res, + pipeline: Res>, +) { + let queued_assets = std::mem::take(&mut prepare_next_frame.assets); + for (handle, material) in queued_assets { + match prepare_ui_material( + &material, + &render_device, + &images, + &fallback_image, + &pipeline, + ) { + Ok(prepared_asset) => { + render_materials.insert(handle, prepared_asset); + } + Err(AsBindGroupError::RetryNextUpdate) => { + prepare_next_frame.assets.push((handle, material)); + } + } + } +} + +fn prepare_ui_material( + material: &M, + render_device: &RenderDevice, + images: &RenderAssets, + fallback_image: &Res, + pipeline: &UiMaterialPipeline, +) -> Result, AsBindGroupError> { + let prepared = + material.as_bind_group(&pipeline.ui_layout, render_device, images, fallback_image)?; + Ok(PreparedUiMaterial { + bindings: prepared.bindings, + bind_group: prepared.bind_group, + key: prepared.data, + }) +} + +#[derive(Bundle, Clone, Debug)] +pub struct MaterialNodeBundle { + pub node: Node, + pub style: Style, + pub material: Handle, + pub focus_policy: FocusPolicy, + pub transform: Transform, + pub global_transform: GlobalTransform, + pub visibility: Visibility, + pub computed_visibility: ComputedVisibility, + pub z_index: ZIndex, +} + +impl Default for MaterialNodeBundle { + 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(), + } } } diff --git a/crates/bevy_ui/src/render/pipeline.rs b/crates/bevy_ui/src/render/pipeline.rs index f6b4b0cc3c1ea..b5a70923f7936 100644 --- a/crates/bevy_ui/src/render/pipeline.rs +++ b/crates/bevy_ui/src/render/pipeline.rs @@ -6,7 +6,7 @@ use bevy_render::{ view::{ViewTarget, ViewUniform}, }; -#[derive(Resource)] +#[derive(Resource, Clone)] pub struct UiPipeline { pub view_layout: BindGroupLayout, pub image_layout: BindGroupLayout, diff --git a/crates/bevy_ui/src/render/ui.wgsl b/crates/bevy_ui/src/render/ui.wgsl index 36e18f201604a..63e799a1a8ab8 100644 --- a/crates/bevy_ui/src/render/ui.wgsl +++ b/crates/bevy_ui/src/render/ui.wgsl @@ -1,6 +1,20 @@ #import bevy_render::view View -const TEXTURED_QUAD: u32 = 0u; +const TEXTURED_QUAD: u32 = 1u; + +struct UiMaterialDefault { + color: vec4, + // mode is a field indicating wheter this is a textured quad or not. + // (0 = untextured, 1 = textured) + mode: u32 +} + +@group(1) @binding(0) +var color: vec4; +@group(1) @binding(1) +var texture: texture_2d; +@group(1) @binding(2) +var texture_sampler: sampler; @group(0) @binding(0) var view: View; @@ -27,11 +41,6 @@ fn vertex( return out; } -@group(1) @binding(0) -var sprite_texture: texture_2d; -@group(1) @binding(1) -var sprite_sampler: sampler; - @fragment fn fragment(in: VertexOutput) -> @location(0) vec4 { // textureSample can only be called in unform control flow, not inside an if branch. From 7a98e22173bbd330c5fd416155b8085cd0791b18 Mon Sep 17 00:00:00 2001 From: Markus Ort Date: Sat, 19 Aug 2023 00:17:46 +0200 Subject: [PATCH 03/55] export material::* from bevy_ui --- crates/bevy_ui/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index afa7d2bda0e0c..4f06d27a6823c 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -30,6 +30,7 @@ pub use layout::*; pub use measurement::*; pub use render::*; pub use ui_node::*; +pub use material::*; use widget::UiImageSize; #[doc(hidden)] From ccba0a2af41f6456b0347c80095ce2eee84e807c Mon Sep 17 00:00:00 2001 From: Markus Ort Date: Sat, 19 Aug 2023 00:29:29 +0200 Subject: [PATCH 04/55] reverts UI shader --- crates/bevy_ui/src/render/ui.wgsl | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/crates/bevy_ui/src/render/ui.wgsl b/crates/bevy_ui/src/render/ui.wgsl index 63e799a1a8ab8..3af52fdd77c7e 100644 --- a/crates/bevy_ui/src/render/ui.wgsl +++ b/crates/bevy_ui/src/render/ui.wgsl @@ -1,20 +1,6 @@ #import bevy_render::view View -const TEXTURED_QUAD: u32 = 1u; - -struct UiMaterialDefault { - color: vec4, - // mode is a field indicating wheter this is a textured quad or not. - // (0 = untextured, 1 = textured) - mode: u32 -} - -@group(1) @binding(0) -var color: vec4; -@group(1) @binding(1) -var texture: texture_2d; -@group(1) @binding(2) -var texture_sampler: sampler; +const TEXTURED_QUAD: u32 = 0u; @group(0) @binding(0) var view: View; @@ -41,6 +27,11 @@ fn vertex( return out; } +@group(1) @binding(0) +var sprite_texture: texture_2d; +@group(1) @binding(1) +var sprite_sampler: sampler; + @fragment fn fragment(in: VertexOutput) -> @location(0) vec4 { // textureSample can only be called in unform control flow, not inside an if branch. @@ -51,4 +42,4 @@ fn fragment(in: VertexOutput) -> @location(0) vec4 { color = in.color; } return color; -} +} \ No newline at end of file From 4298848fe2c3f9e8340414571b3dcf6fcb27f270 Mon Sep 17 00:00:00 2001 From: Markus Ort Date: Sun, 20 Aug 2023 12:56:08 +0200 Subject: [PATCH 05/55] UiMaterial now working as intended --- crates/bevy_ui/src/material.rs | 368 ++++++++++++++++++++++++++++--- crates/bevy_ui/src/render/mod.rs | 10 +- 2 files changed, 337 insertions(+), 41 deletions(-) diff --git a/crates/bevy_ui/src/material.rs b/crates/bevy_ui/src/material.rs index 2e6fbd11ebf14..52013816f934c 100644 --- a/crates/bevy_ui/src/material.rs +++ b/crates/bevy_ui/src/material.rs @@ -1,10 +1,10 @@ -use std::{hash::Hash, marker::PhantomData}; +use std::{hash::Hash, marker::PhantomData, ops::Range}; use bevy_app::{App, Plugin}; use bevy_asset::{AddAsset, AssetEvent, AssetServer, Assets, Handle}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ - prelude::{Bundle, Entity, EventReader}, + prelude::{Bundle, Component, Entity, EventReader}, query::ROQueryItem, schedule::IntoSystemConfigs, system::{ @@ -13,30 +13,33 @@ use bevy_ecs::{ }, world::{FromWorld, World}, }; +use bevy_math::{Mat4, Rect, Vec2, Vec4Swizzles}; use bevy_reflect::{TypePath, TypeUuid}; use bevy_render::{ extract_component::ExtractComponentPlugin, + prelude::Color, render_asset::{PrepareAssetSet, RenderAssets}, render_phase::{ AddRenderCommand, DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult, RenderPhase, SetItemPipeline, TrackedRenderPass, }, render_resource::{ - AsBindGroup, AsBindGroupError, AsBindGroupShaderType, BindGroup, BindGroupLayout, - OwnedBindingResource, PipelineCache, RenderPipelineDescriptor, Shader, ShaderRef, - SpecializedRenderPipeline, SpecializedRenderPipelines, + AsBindGroup, AsBindGroupError, BindGroup, BindGroupDescriptor, BindGroupEntry, + BindGroupLayout, OwnedBindingResource, PipelineCache, RenderPipelineDescriptor, Shader, + ShaderRef, SpecializedRenderPipeline, SpecializedRenderPipelines, BufferVec, BufferUsages, BindingResource, }, - renderer::RenderDevice, - texture::{FallbackImage, Image}, - view::{ComputedVisibility, ExtractedView, ViewUniforms, Visibility}, + renderer::{RenderDevice, RenderQueue}, + texture::{FallbackImage, Image, DEFAULT_IMAGE_HANDLE}, + view::{ComputedVisibility, ExtractedView, ViewUniforms, Visibility, ViewUniformOffset}, Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; use bevy_transform::prelude::{GlobalTransform, Transform}; use bevy_utils::{FloatOrd, HashMap, HashSet}; use crate::{ - DrawUi, DrawUiNode, FocusPolicy, Node, SetUiTextureBindGroup, SetUiViewBindGroup, - Style, TransparentUi, UiBatch, UiImageBindGroups, UiMeta, UiPipeline, UiPipelineKey, ZIndex, + CalculatedClip, FocusPolicy, Node, RenderUiSystem, SetUiViewBindGroup, + Style, TransparentUi, UiMeta, UiPipeline, UiPipelineKey, UiStack, + ZIndex, QUAD_INDICES, QUAD_VERTEX_POSITIONS, UiVertex, SetUiTextureBindGroup, UiImageBindGroups, }; pub trait UiMaterial: AsBindGroup + Send + Sync + Clone + TypeUuid + TypePath + Sized { @@ -67,6 +70,23 @@ impl Default for UiMaterialPlugin { } } +#[derive(Resource)] +pub struct UiMatMeta { + pub vertices: BufferVec, + pub view_bind_group: Option, + marker: PhantomData +} + +impl Default for UiMatMeta { + fn default() -> Self { + Self { + vertices: BufferVec::new(BufferUsages::VERTEX), + view_bind_group: Default::default(), + marker: PhantomData + } + } +} + impl Plugin for UiMaterialPlugin where M::Data: PartialEq + Eq + Hash + Clone, @@ -79,15 +99,24 @@ where render_app .add_render_command::>() .init_resource::>() + .init_resource::>() .init_resource::>() + .init_resource::>() .init_resource::>>() - .add_systems(ExtractSchedule, extract_ui_materials::) + .add_systems( + ExtractSchedule, + ( + extract_ui_materials::, + extract_uinodes::.in_set(RenderUiSystem::ExtractNode), + ), + ) .add_systems( Render, ( prepare_ui_materials:: .in_set(RenderSet::Prepare) .after(PrepareAssetSet::PreAssetPrepare), + prepare_uimaterial_nodes::.in_set(RenderSet::Prepare), queue_ui_material_nodes::.in_set(RenderSet::Queue), ), ); @@ -117,6 +146,13 @@ where } } +#[derive(Component)] +pub struct MatUiBatch { + pub range: Range, + pub material: Handle, + pub z: f32, +} + impl Clone for UiMaterialKey where M::Data: Clone, @@ -166,8 +202,8 @@ where descriptor.layout = vec![ self.ui_pipeline.view_layout.clone(), - self.ui_layout.clone(), self.ui_pipeline.image_layout.clone(), + self.ui_layout.clone(), ]; M::specialize(&mut descriptor, key); @@ -178,6 +214,7 @@ where impl FromWorld for UiMaterialPipeline { fn from_world(world: &mut World) -> Self { + println!("fromworld my friend!"); let asset_server = world.resource::(); let render_device = world.resource::(); let ui_layout = M::bind_group_layout(render_device); @@ -202,70 +239,308 @@ impl FromWorld for UiMaterialPipeline { type DrawUiMaterial = ( SetItemPipeline, - SetUiViewBindGroup<0>, - SetUiMaterialBindGroup, - SetUiTextureBindGroup<2>, - DrawUiNode, + SetMatUiViewBindGroup, + SetMatUiTextureBindGroup, + SetUiMaterialBindGroup, + DrawUiMatNode, ); +pub struct SetMatUiTextureBindGroup(PhantomData); +impl RenderCommand

for SetMatUiTextureBindGroup { + type Param = SRes; + type ViewWorldQuery = (); + type ItemWorldQuery = Read>; + + #[inline] + fn render<'w>( + _item: &P, + _view: (), + batch: &'w MatUiBatch, + image_bind_groups: SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, + ) -> RenderCommandResult { + let image_bind_groups = image_bind_groups.into_inner(); + pass.set_bind_group(I, image_bind_groups.values.get(&DEFAULT_IMAGE_HANDLE.typed()).unwrap(), &[]); + RenderCommandResult::Success + } +} + +pub struct SetMatUiViewBindGroup(PhantomData); +impl RenderCommand

for SetMatUiViewBindGroup { + type Param = SRes>; + type ViewWorldQuery = Read; + type ItemWorldQuery = (); + + fn render<'w>( + _item: &P, + view_uniform: &'w ViewUniformOffset, + _entity: (), + ui_meta: SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, + ) -> RenderCommandResult { + pass.set_bind_group( + I, + ui_meta.into_inner().view_bind_group.as_ref().unwrap(), + &[view_uniform.offset], + ); + RenderCommandResult::Success + } +} + +pub struct DrawUiMatNode(PhantomData); +impl RenderCommand

for DrawUiMatNode { + type Param = SRes>; + type ViewWorldQuery = (); + type ItemWorldQuery = Read>; + + #[inline] + fn render<'w>( + _item: &P, + _view: (), + batch: &'w MatUiBatch, + ui_meta: SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, + ) -> RenderCommandResult { + pass.set_vertex_buffer(0, ui_meta.into_inner().vertices.buffer().unwrap().slice(..)); + pass.draw(batch.range.clone(), 0..1); + RenderCommandResult::Success + } +} + pub struct SetUiMaterialBindGroup(PhantomData); impl RenderCommand

for SetUiMaterialBindGroup { type Param = SRes>; type ViewWorldQuery = (); - type ItemWorldQuery = Read>; + type ItemWorldQuery = Read>; fn render<'w>( - item: &P, - view: (), - ui_material_handle: ROQueryItem<'_, Self::ItemWorldQuery>, + _item: &P, + _view: (), + material_handle: ROQueryItem<'_, Self::ItemWorldQuery>, materials: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { - let ui_material = materials.into_inner().get(ui_material_handle).unwrap(); - pass.set_bind_group(I, &ui_material.bind_group, &[]); + let material = materials.into_inner().get(&material_handle.material).unwrap(); + pass.set_bind_group(I, &material.bind_group, &[]); RenderCommandResult::Success } } +pub struct ExtractedUiMaterialNode { + pub stack_index: usize, + pub transform: Mat4, + pub rect: Rect, + pub material: Handle, + pub clip: Option, +} + +#[derive(Resource)] +pub struct ExtractedUiMaterialNodes { + pub uinodes: Vec>, +} + +impl Default for ExtractedUiMaterialNodes { + fn default() -> Self { + Self { + uinodes: Default::default(), + } + } +} + +pub fn extract_uinodes( + mut extracted_uinodes: ResMut>, + ui_stack: Extract>, + uinode_query: Extract< + Query<( + &Node, + &GlobalTransform, + &Handle, + &ComputedVisibility, + Option<&CalculatedClip>, + )>, + >, +) { + for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() { + if let Ok((uinode, transform, handle, visibility, clip)) = uinode_query.get(*entity) { + // Skip invisible and completely transparent nodes + extracted_uinodes.uinodes.push(ExtractedUiMaterialNode { + stack_index, + transform: transform.compute_matrix(), + material: handle.clone_weak(), + rect: Rect { + min: Vec2::ZERO, + max: uinode.calculated_size, + }, + clip: clip.map(|clip| clip.clip), + }); + }; + } +} + +pub fn prepare_uimaterial_nodes( + mut commands: Commands, + render_device: Res, + render_queue: Res, + mut ui_meta: ResMut>, + mut extracted_uinodes: ResMut>, +) { + ui_meta.vertices.clear(); + + extracted_uinodes + .uinodes + .sort_by_key(|node| node.stack_index); + for extracted_uinode in extracted_uinodes.uinodes.drain(..) { + let mut uinode_rect = extracted_uinode.rect; + + let rect_size = uinode_rect.size().extend(1.0); + + let positions = QUAD_VERTEX_POSITIONS + .map(|pos| (extracted_uinode.transform * (pos * rect_size).extend(1.)).xyz()); + + let mut positions_diff = if let Some(clip) = extracted_uinode.clip { + [ + Vec2::new( + f32::max(clip.min.x - positions[0].x, 0.), + f32::max(clip.min.y - positions[0].y, 0.), + ), + Vec2::new( + f32::min(clip.max.x - positions[1].x, 0.), + f32::max(clip.min.y - positions[1].y, 0.), + ), + Vec2::new( + f32::min(clip.max.x - positions[2].x, 0.), + f32::min(clip.max.y - positions[2].y, 0.), + ), + Vec2::new( + f32::max(clip.min.x - positions[3].x, 0.), + f32::min(clip.max.y - positions[3].y, 0.), + ), + ] + } else { + [Vec2::ZERO; 4] + }; + + let positions_clipped = [ + positions[0] + positions_diff[0].extend(0.), + positions[1] + positions_diff[1].extend(0.), + positions[2] + positions_diff[2].extend(0.), + positions[3] + positions_diff[3].extend(0.), + ]; + + let transformed_rect_size = extracted_uinode.transform.transform_vector3(rect_size); + + if extracted_uinode.transform.x_axis[1] == 0.0 { + if positions_diff[0].x - positions_diff[1].x >= transformed_rect_size.x + || positions_diff[1].y - positions_diff[2].y >= transformed_rect_size.y + { + continue; + } + } + let extent = uinode_rect.max; + let uvs = [ + Vec2::new( + uinode_rect.min.x + positions_diff[0].x, + uinode_rect.min.y + positions_diff[0].y, + ), + Vec2::new( + uinode_rect.max.x + positions_diff[1].x, + uinode_rect.min.y + positions_diff[1].y, + ), + Vec2::new( + uinode_rect.max.x + positions_diff[2].x, + uinode_rect.max.y + positions_diff[2].y, + ), + Vec2::new( + uinode_rect.min.x + positions_diff[3].x, + uinode_rect.max.y + positions_diff[3].y, + ), + ] + .map(|pos| pos / extent); + + for i in QUAD_INDICES { + ui_meta.vertices.push(crate::UiVertex { + position: positions_clipped[i].into(), + uv: uvs[i].into(), + color: Color::WHITE.into(), + mode: 1, + }); + } + commands.spawn(MatUiBatch { + range: 0..QUAD_INDICES.len() as u32, + material: extracted_uinode.material, + z: extracted_uinode.transform.w_axis[2], + }); + } + ui_meta.vertices.write_buffer(&render_device, &render_queue); +} + #[allow(clippy::too_many_arguments)] pub fn queue_ui_material_nodes( draw_functions: Res>, + render_device: Res, ui_material_pipeline: Res>, - mut ui_meta: ResMut, + mut ui_meta: ResMut>, + mut image_bind_groups: ResMut, view_uniforms: Res, mut pipelines: ResMut>>, pipeline_cache: Res, - mut image_bind_groups: ResMut, + gpu_images: Res>, render_materials: Res>, - ui_batches: Query<(&Handle, Entity, &UiBatch)>, + ui_batches: Query<(Entity, &MatUiBatch)>, mut views: Query<(&ExtractedView, &mut RenderPhase)>, ) where M::Data: PartialEq + Eq + Hash + Clone, { - if ui_batches.is_empty() { - return; - } - if let Some(view_binding) = view_uniforms.uniforms.binding() { - let draw_ui_functrion = draw_functions.read().id::>(); + ui_meta.view_bind_group = Some(render_device.create_bind_group(&BindGroupDescriptor { + entries: &[BindGroupEntry { + binding: 0, + resource: view_binding, + }], + label: Some("ui_view_bind_group"), + layout: &ui_material_pipeline.ui_pipeline.view_layout, + })); + let draw_ui_function = draw_functions.read().id::>(); for (view, mut transparent_phase) in &mut views { - for (handle, entity, batch) in &ui_batches { - if let Some(ui_material) = render_materials.get(handle) { - let pipeline_id = pipelines.specialize( + for (entity, batch) in &ui_batches { + + if let Some(material) = render_materials.get(&batch.material) { + let pipeline = pipelines.specialize( &pipeline_cache, &ui_material_pipeline, UiMaterialKey { hdr: view.hdr, - bind_group_data: ui_material.key.clone(), + bind_group_data: material.key.clone(), }, ); + image_bind_groups + .values + .entry(DEFAULT_IMAGE_HANDLE.typed().clone_weak()) + .or_insert_with(|| { + let gpu_image = gpu_images.get(&DEFAULT_IMAGE_HANDLE.typed()).unwrap(); + render_device.create_bind_group(&BindGroupDescriptor { + entries: &[ + BindGroupEntry { + binding: 0, + resource: BindingResource::TextureView(&gpu_image.texture_view), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::Sampler(&gpu_image.sampler), + }, + ], + label: Some("ui_material_bind_group"), + layout: &ui_material_pipeline.ui_pipeline.image_layout, + }) + }); + transparent_phase.add(TransparentUi { sort_key: FloatOrd(batch.z), entity, - pipeline: pipeline_id, - draw_function: draw_ui_functrion, + pipeline, + draw_function: draw_ui_function, }); } } @@ -373,6 +648,27 @@ pub fn prepare_ui_materials( } } } + + for removed in std::mem::take(&mut extracted_assets.removed) { + render_materials.remove(&removed); + } + + for (handle, material) in std::mem::take(&mut extracted_assets.extracted) { + match prepare_ui_material( + &material, + &render_device, + &images, + &fallback_image, + &pipeline, + ) { + Ok(prepared_asset) => { + render_materials.insert(handle, prepared_asset); + } + Err(AsBindGroupError::RetryNextUpdate) => { + prepare_next_frame.assets.push((handle, material)); + } + } + } } fn prepare_ui_material( diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index c2e82a7440671..6d3e67d891ca6 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -579,7 +579,7 @@ pub fn extract_text_uinodes( #[repr(C)] #[derive(Copy, Clone, Pod, Zeroable)] -struct UiVertex { +pub struct UiVertex { pub position: [f32; 3], pub uv: [f32; 2], pub color: [f32; 4], @@ -588,8 +588,8 @@ struct UiVertex { #[derive(Resource)] pub struct UiMeta { - vertices: BufferVec, - view_bind_group: Option, + pub vertices: BufferVec, + pub view_bind_group: Option, } impl Default for UiMeta { @@ -601,14 +601,14 @@ impl Default for UiMeta { } } -const QUAD_VERTEX_POSITIONS: [Vec3; 4] = [ +pub const QUAD_VERTEX_POSITIONS: [Vec3; 4] = [ Vec3::new(-0.5, -0.5, 0.0), Vec3::new(0.5, -0.5, 0.0), Vec3::new(0.5, 0.5, 0.0), Vec3::new(-0.5, 0.5, 0.0), ]; -const QUAD_INDICES: [usize; 6] = [0, 2, 3, 0, 1, 2]; +pub const QUAD_INDICES: [usize; 6] = [0, 2, 3, 0, 1, 2]; #[derive(Component)] pub struct UiBatch { From cbbbd883fd73f5a63578dd09bc9d9466863154d7 Mon Sep 17 00:00:00 2001 From: Markus Ort Date: Sun, 20 Aug 2023 15:55:12 +0200 Subject: [PATCH 06/55] fixes compilation warnings --- crates/bevy_ui/src/lib.rs | 4 +- crates/bevy_ui/src/material.rs | 96 +++++++++++++++++++------------- crates/bevy_ui/src/render/mod.rs | 4 +- 3 files changed, 60 insertions(+), 44 deletions(-) diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index 4f06d27a6823c..a8b83bfa2f312 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -7,10 +7,10 @@ mod focus; mod geometry; mod layout; +mod material; mod render; mod stack; mod ui_node; -mod material; #[cfg(feature = "bevy_text")] mod accessibility; @@ -27,10 +27,10 @@ use bevy_render::{extract_component::ExtractComponentPlugin, RenderApp}; pub use focus::*; pub use geometry::*; pub use layout::*; +pub use material::*; pub use measurement::*; pub use render::*; pub use ui_node::*; -pub use material::*; use widget::UiImageSize; #[doc(hidden)] diff --git a/crates/bevy_ui/src/material.rs b/crates/bevy_ui/src/material.rs index 52013816f934c..3b697f63f5418 100644 --- a/crates/bevy_ui/src/material.rs +++ b/crates/bevy_ui/src/material.rs @@ -25,21 +25,21 @@ use bevy_render::{ }, render_resource::{ AsBindGroup, AsBindGroupError, BindGroup, BindGroupDescriptor, BindGroupEntry, - BindGroupLayout, OwnedBindingResource, PipelineCache, RenderPipelineDescriptor, Shader, - ShaderRef, SpecializedRenderPipeline, SpecializedRenderPipelines, BufferVec, BufferUsages, BindingResource, + BindGroupLayout, BindingResource, BufferUsages, BufferVec, OwnedBindingResource, + PipelineCache, RenderPipelineDescriptor, Shader, ShaderRef, SpecializedRenderPipeline, + SpecializedRenderPipelines, }, renderer::{RenderDevice, RenderQueue}, texture::{FallbackImage, Image, DEFAULT_IMAGE_HANDLE}, - view::{ComputedVisibility, ExtractedView, ViewUniforms, Visibility, ViewUniformOffset}, + view::{ComputedVisibility, ExtractedView, ViewUniformOffset, ViewUniforms, Visibility}, Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; use bevy_transform::prelude::{GlobalTransform, Transform}; use bevy_utils::{FloatOrd, HashMap, HashSet}; use crate::{ - CalculatedClip, FocusPolicy, Node, RenderUiSystem, SetUiViewBindGroup, - Style, TransparentUi, UiMeta, UiPipeline, UiPipelineKey, UiStack, - ZIndex, QUAD_INDICES, QUAD_VERTEX_POSITIONS, UiVertex, SetUiTextureBindGroup, UiImageBindGroups, + CalculatedClip, FocusPolicy, Node, RenderUiSystem, Style, TransparentUi, UiImageBindGroups, + UiPipeline, UiPipelineKey, UiStack, UiVertex, ZIndex, QUAD_INDICES, QUAD_VERTEX_POSITIONS, }; pub trait UiMaterial: AsBindGroup + Send + Sync + Clone + TypeUuid + TypePath + Sized { @@ -72,9 +72,9 @@ impl Default for UiMaterialPlugin { #[derive(Resource)] pub struct UiMatMeta { - pub vertices: BufferVec, - pub view_bind_group: Option, - marker: PhantomData + vertices: BufferVec, + view_bind_group: Option, + marker: PhantomData, } impl Default for UiMatMeta { @@ -82,7 +82,7 @@ impl Default for UiMatMeta { Self { vertices: BufferVec::new(BufferUsages::VERTEX), view_bind_group: Default::default(), - marker: PhantomData + marker: PhantomData, } } } @@ -107,7 +107,7 @@ where ExtractSchedule, ( extract_ui_materials::, - extract_uinodes::.in_set(RenderUiSystem::ExtractNode), + extract_material_uinodes::.in_set(RenderUiSystem::ExtractNode), ), ) .add_systems( @@ -246,7 +246,9 @@ type DrawUiMaterial = ( ); pub struct SetMatUiTextureBindGroup(PhantomData); -impl RenderCommand

for SetMatUiTextureBindGroup { +impl RenderCommand

+ for SetMatUiTextureBindGroup +{ type Param = SRes; type ViewWorldQuery = (); type ItemWorldQuery = Read>; @@ -255,17 +257,24 @@ impl RenderCommand

for SetMatUiT fn render<'w>( _item: &P, _view: (), - batch: &'w MatUiBatch, + _batch: &'w MatUiBatch, image_bind_groups: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { let image_bind_groups = image_bind_groups.into_inner(); - pass.set_bind_group(I, image_bind_groups.values.get(&DEFAULT_IMAGE_HANDLE.typed()).unwrap(), &[]); + pass.set_bind_group( + I, + image_bind_groups + .values + .get(&DEFAULT_IMAGE_HANDLE.typed()) + .unwrap(), + &[], + ); RenderCommandResult::Success } } -pub struct SetMatUiViewBindGroup(PhantomData); +pub struct SetMatUiViewBindGroup(PhantomData); impl RenderCommand

for SetMatUiViewBindGroup { type Param = SRes>; type ViewWorldQuery = Read; @@ -322,7 +331,10 @@ impl RenderCommand

materials: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { - let material = materials.into_inner().get(&material_handle.material).unwrap(); + let material = materials + .into_inner() + .get(&material_handle.material) + .unwrap(); pass.set_bind_group(I, &material.bind_group, &[]); RenderCommandResult::Success } @@ -349,7 +361,7 @@ impl Default for ExtractedUiMaterialNodes { } } -pub fn extract_uinodes( +pub fn extract_material_uinodes( mut extracted_uinodes: ResMut>, ui_stack: Extract>, uinode_query: Extract< @@ -364,6 +376,9 @@ pub fn extract_uinodes( ) { for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() { if let Ok((uinode, transform, handle, visibility, clip)) = uinode_query.get(*entity) { + if !visibility.is_visible() { + continue; + } // Skip invisible and completely transparent nodes extracted_uinodes.uinodes.push(ExtractedUiMaterialNode { stack_index, @@ -392,14 +407,14 @@ pub fn prepare_uimaterial_nodes( .uinodes .sort_by_key(|node| node.stack_index); for extracted_uinode in extracted_uinodes.uinodes.drain(..) { - let mut uinode_rect = extracted_uinode.rect; + let uinode_rect = extracted_uinode.rect; let rect_size = uinode_rect.size().extend(1.0); let positions = QUAD_VERTEX_POSITIONS .map(|pos| (extracted_uinode.transform * (pos * rect_size).extend(1.)).xyz()); - let mut positions_diff = if let Some(clip) = extracted_uinode.clip { + let positions_diff = if let Some(clip) = extracted_uinode.clip { [ Vec2::new( f32::max(clip.min.x - positions[0].x, 0.), @@ -505,7 +520,6 @@ pub fn queue_ui_material_nodes( let draw_ui_function = draw_functions.read().id::>(); for (view, mut transparent_phase) in &mut views { for (entity, batch) in &ui_batches { - if let Some(material) = render_materials.get(&batch.material) { let pipeline = pipelines.specialize( &pipeline_cache, @@ -515,26 +529,28 @@ pub fn queue_ui_material_nodes( bind_group_data: material.key.clone(), }, ); - image_bind_groups - .values - .entry(DEFAULT_IMAGE_HANDLE.typed().clone_weak()) - .or_insert_with(|| { - let gpu_image = gpu_images.get(&DEFAULT_IMAGE_HANDLE.typed()).unwrap(); - render_device.create_bind_group(&BindGroupDescriptor { - entries: &[ - BindGroupEntry { - binding: 0, - resource: BindingResource::TextureView(&gpu_image.texture_view), - }, - BindGroupEntry { - binding: 1, - resource: BindingResource::Sampler(&gpu_image.sampler), - }, - ], - label: Some("ui_material_bind_group"), - layout: &ui_material_pipeline.ui_pipeline.image_layout, - }) - }); + image_bind_groups + .values + .entry(DEFAULT_IMAGE_HANDLE.typed().clone_weak()) + .or_insert_with(|| { + let gpu_image = gpu_images.get(&DEFAULT_IMAGE_HANDLE.typed()).unwrap(); + render_device.create_bind_group(&BindGroupDescriptor { + entries: &[ + BindGroupEntry { + binding: 0, + resource: BindingResource::TextureView( + &gpu_image.texture_view, + ), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::Sampler(&gpu_image.sampler), + }, + ], + label: Some("ui_material_bind_group"), + layout: &ui_material_pipeline.ui_pipeline.image_layout, + }) + }); transparent_phase.add(TransparentUi { sort_key: FloatOrd(batch.z), diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 6d3e67d891ca6..81344e81f138a 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -588,8 +588,8 @@ pub struct UiVertex { #[derive(Resource)] pub struct UiMeta { - pub vertices: BufferVec, - pub view_bind_group: Option, + vertices: BufferVec, + view_bind_group: Option, } impl Default for UiMeta { From 7ed89149d1e861d84ecf446d93f4e1ecac2b99cb Mon Sep 17 00:00:00 2001 From: Markus Ort Date: Sun, 20 Aug 2023 16:02:37 +0200 Subject: [PATCH 07/55] fix cargo clippy warnings --- crates/bevy_ui/src/material.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/crates/bevy_ui/src/material.rs b/crates/bevy_ui/src/material.rs index 3b697f63f5418..6b4fa0b06f2c2 100644 --- a/crates/bevy_ui/src/material.rs +++ b/crates/bevy_ui/src/material.rs @@ -208,7 +208,7 @@ where M::specialize(&mut descriptor, key); - return descriptor; + descriptor } } @@ -446,12 +446,11 @@ pub fn prepare_uimaterial_nodes( let transformed_rect_size = extracted_uinode.transform.transform_vector3(rect_size); - if extracted_uinode.transform.x_axis[1] == 0.0 { - if positions_diff[0].x - positions_diff[1].x >= transformed_rect_size.x - || positions_diff[1].y - positions_diff[2].y >= transformed_rect_size.y - { - continue; - } + if extracted_uinode.transform.x_axis[1] == 0.0 + && positions_diff[0].x - positions_diff[1].x >= transformed_rect_size.x + || positions_diff[1].y - positions_diff[2].y >= transformed_rect_size.y + { + continue; } let extent = uinode_rect.max; let uvs = [ From e65163e02bd24f3e2ee2c4ac6fb7f0317a4c2a5e Mon Sep 17 00:00:00 2001 From: Markus Ort Date: Mon, 21 Aug 2023 15:35:49 +0200 Subject: [PATCH 08/55] adds example `ui_material` --- Cargo.toml | 11 ++++++ assets/shaders/circle_shader.wgsl | 48 +++++++++++++++++++++++ crates/bevy_ui/src/lib.rs | 4 +- examples/README.md | 1 + examples/ui/ui_material.rs | 64 +++++++++++++++++++++++++++++++ 5 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 assets/shaders/circle_shader.wgsl create mode 100644 examples/ui/ui_material.rs diff --git a/Cargo.toml b/Cargo.toml index 0c367ae2fcd54..30da641178a1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2289,6 +2289,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" +category = "UI (User Interface)" +wasm = true + [profile.wasm-release] inherits = "release" opt-level = "z" diff --git a/assets/shaders/circle_shader.wgsl b/assets/shaders/circle_shader.wgsl new file mode 100644 index 0000000000000..c38dbe4f3187b --- /dev/null +++ b/assets/shaders/circle_shader.wgsl @@ -0,0 +1,48 @@ +// This shader draws a circle, that can be driven with certain properties + +#import bevy_render::view View + +struct VertexOutput { + @location(0) uv: vec2, + @location(1) color: vec4, + @location(3) @interpolate(flat) mode: u32, + @builtin(position) position: vec4, +}; + +struct CustomUiMaterial { + @location(0) fill_amount: f32, + @location(1) color: vec4 +} + +@group(2) @binding(0) +var 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: VertexOutput) -> @location(0) vec4 { + let fill_amount = input.fill_amount; + let fill_angle = fill_amount * TAU; + let uv = in.uv * 2.0 - 1.0; + var color = vec4(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(input.color.rgb, min(1.0, w)); + } else { + color.a = 0.0; + } + } else { + color.a = 0.0; + } + return color; +} diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index a8b83bfa2f312..15fa9cc47cec0 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -37,8 +37,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, UiScale, }; } diff --git a/examples/README.md b/examples/README.md index 6f53c34f7048c..4e0178f9715b7 100644 --- a/examples/README.md +++ b/examples/README.md @@ -354,6 +354,7 @@ Example | Description [Text Wrap Debug](../examples/ui/text_wrap_debug.rs) | Demonstrates text wrapping [Transparency UI](../examples/ui/transparency_ui.rs) | Demonstrates transparency for UI [UI](../examples/ui/ui.rs) | Illustrates various features of Bevy UI +[UI Material](../examples/ui/ui_material.rs) | Demonstrates Creating and using custom Ui materials [UI Scaling](../examples/ui/ui_scaling.rs) | Illustrates how to scale the UI [UI Texture Atlas](../examples/ui/ui_texture_atlas.rs) | Illustrates how to use TextureAtlases in UI [UI Z-Index](../examples/ui/z_index.rs) | Demonstrates how to control the relative depth (z-position) of UI elements diff --git a/examples/ui/ui_material.rs b/examples/ui/ui_material.rs new file mode 100644 index 0000000000000..ee4793580ad83 --- /dev/null +++ b/examples/ui/ui_material.rs @@ -0,0 +1,64 @@ +//! This example illustrates how to create a Node that shows a Material and driver parameters from +//! it + +use bevy::prelude::*; +use bevy_internal::{render::render_resource::*, reflect::{TypePath, TypeUuid}}; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_plugins(UiMaterialPlugin::::default()) + .add_systems(Startup, setup) + .add_systems(Update, update) + .run(); +} + +fn update(time: Res

- for SetMatUiTextureBindGroup -{ - type Param = SRes; - type ViewWorldQuery = (); - type ItemWorldQuery = Read>; - - #[inline] - fn render<'w>( - _item: &P, - _view: (), - _batch: &'w MatUiBatch, - image_bind_groups: SystemParamItem<'w, '_, Self::Param>, - pass: &mut TrackedRenderPass<'w>, - ) -> RenderCommandResult { - let image_bind_groups = image_bind_groups.into_inner(); - pass.set_bind_group( - I, - image_bind_groups - .values - .get(&DEFAULT_IMAGE_HANDLE.typed()) - .unwrap(), - &[], - ); - RenderCommandResult::Success - } -} - -pub struct SetMatUiViewBindGroup(PhantomData); -impl RenderCommand

for SetMatUiViewBindGroup { - type Param = SRes>; - type ViewWorldQuery = Read; - type ItemWorldQuery = (); - - fn render<'w>( - _item: &P, - view_uniform: &'w ViewUniformOffset, - _entity: (), - ui_meta: SystemParamItem<'w, '_, Self::Param>, - pass: &mut TrackedRenderPass<'w>, - ) -> RenderCommandResult { - pass.set_bind_group( - I, - ui_meta.into_inner().view_bind_group.as_ref().unwrap(), - &[view_uniform.offset], - ); - RenderCommandResult::Success - } -} - -pub struct DrawUiMatNode(PhantomData); -impl RenderCommand

for DrawUiMatNode { - type Param = SRes>; - type ViewWorldQuery = (); - type ItemWorldQuery = Read>; - - #[inline] - fn render<'w>( - _item: &P, - _view: (), - batch: &'w MatUiBatch, - ui_meta: SystemParamItem<'w, '_, Self::Param>, - pass: &mut TrackedRenderPass<'w>, - ) -> RenderCommandResult { - pass.set_vertex_buffer(0, ui_meta.into_inner().vertices.buffer().unwrap().slice(..)); - pass.draw(batch.range.clone(), 0..1); - RenderCommandResult::Success - } -} - -pub struct SetUiMaterialBindGroup(PhantomData); -impl RenderCommand

- for SetUiMaterialBindGroup -{ - type Param = SRes>; - type ViewWorldQuery = (); - type ItemWorldQuery = Read>; - - fn render<'w>( - _item: &P, - _view: (), - material_handle: ROQueryItem<'_, Self::ItemWorldQuery>, - materials: SystemParamItem<'w, '_, Self::Param>, - pass: &mut TrackedRenderPass<'w>, - ) -> RenderCommandResult { - let material = materials - .into_inner() - .get(&material_handle.material) - .unwrap(); - pass.set_bind_group(I, &material.bind_group, &[]); - RenderCommandResult::Success - } -} - -pub struct ExtractedUiMaterialNode { - pub stack_index: usize, - pub transform: Mat4, - pub rect: Rect, - pub material: Handle, - pub clip: Option, -} - -#[derive(Resource)] -pub struct ExtractedUiMaterialNodes { - pub uinodes: Vec>, -} - -impl Default for ExtractedUiMaterialNodes { - fn default() -> Self { - Self { - uinodes: Default::default(), - } - } -} - -pub fn extract_material_uinodes( - mut extracted_uinodes: ResMut>, - ui_stack: Extract>, - uinode_query: Extract< - Query<( - &Node, - &GlobalTransform, - &Handle, - &ComputedVisibility, - Option<&CalculatedClip>, - )>, - >, -) { - for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() { - if let Ok((uinode, transform, handle, visibility, clip)) = uinode_query.get(*entity) { - if !visibility.is_visible() { - continue; - } - // Skip invisible and completely transparent nodes - extracted_uinodes.uinodes.push(ExtractedUiMaterialNode { - stack_index, - transform: transform.compute_matrix(), - material: handle.clone_weak(), - rect: Rect { - min: Vec2::ZERO, - max: uinode.calculated_size, - }, - clip: clip.map(|clip| clip.clip), - }); - }; - } -} - -pub fn prepare_uimaterial_nodes( - mut commands: Commands, - render_device: Res, - render_queue: Res, - mut ui_meta: ResMut>, - mut extracted_uinodes: ResMut>, -) { - ui_meta.vertices.clear(); - - extracted_uinodes - .uinodes - .sort_by_key(|node| node.stack_index); - for extracted_uinode in extracted_uinodes.uinodes.drain(..) { - let uinode_rect = extracted_uinode.rect; - - let rect_size = uinode_rect.size().extend(1.0); - - let positions = QUAD_VERTEX_POSITIONS - .map(|pos| (extracted_uinode.transform * (pos * rect_size).extend(1.)).xyz()); - - let positions_diff = if let Some(clip) = extracted_uinode.clip { - [ - Vec2::new( - f32::max(clip.min.x - positions[0].x, 0.), - f32::max(clip.min.y - positions[0].y, 0.), - ), - Vec2::new( - f32::min(clip.max.x - positions[1].x, 0.), - f32::max(clip.min.y - positions[1].y, 0.), - ), - Vec2::new( - f32::min(clip.max.x - positions[2].x, 0.), - f32::min(clip.max.y - positions[2].y, 0.), - ), - Vec2::new( - f32::max(clip.min.x - positions[3].x, 0.), - f32::min(clip.max.y - positions[3].y, 0.), - ), - ] - } else { - [Vec2::ZERO; 4] - }; - - let positions_clipped = [ - positions[0] + positions_diff[0].extend(0.), - positions[1] + positions_diff[1].extend(0.), - positions[2] + positions_diff[2].extend(0.), - positions[3] + positions_diff[3].extend(0.), - ]; - - let transformed_rect_size = extracted_uinode.transform.transform_vector3(rect_size); - - if extracted_uinode.transform.x_axis[1] == 0.0 - && positions_diff[0].x - positions_diff[1].x >= transformed_rect_size.x - || positions_diff[1].y - positions_diff[2].y >= transformed_rect_size.y - { - continue; - } - let extent = uinode_rect.max; - let uvs = [ - Vec2::new( - uinode_rect.min.x + positions_diff[0].x, - uinode_rect.min.y + positions_diff[0].y, - ), - Vec2::new( - uinode_rect.max.x + positions_diff[1].x, - uinode_rect.min.y + positions_diff[1].y, - ), - Vec2::new( - uinode_rect.max.x + positions_diff[2].x, - uinode_rect.max.y + positions_diff[2].y, - ), - Vec2::new( - uinode_rect.min.x + positions_diff[3].x, - uinode_rect.max.y + positions_diff[3].y, - ), - ] - .map(|pos| pos / extent); - - for i in QUAD_INDICES { - ui_meta.vertices.push(crate::UiVertex { - position: positions_clipped[i].into(), - uv: uvs[i].into(), - color: Color::WHITE.into(), - mode: 1, - }); - } - commands.spawn(MatUiBatch { - range: 0..QUAD_INDICES.len() as u32, - material: extracted_uinode.material, - z: extracted_uinode.transform.w_axis[2], - }); - } - ui_meta.vertices.write_buffer(&render_device, &render_queue); -} - -#[allow(clippy::too_many_arguments)] -pub fn queue_ui_material_nodes( - draw_functions: Res>, - render_device: Res, - ui_material_pipeline: Res>, - mut ui_meta: ResMut>, - mut image_bind_groups: ResMut, - view_uniforms: Res, - mut pipelines: ResMut>>, - pipeline_cache: Res, - gpu_images: Res>, - render_materials: Res>, - ui_batches: Query<(Entity, &MatUiBatch)>, - mut views: Query<(&ExtractedView, &mut RenderPhase)>, -) where - M::Data: PartialEq + Eq + Hash + Clone, -{ - if let Some(view_binding) = view_uniforms.uniforms.binding() { - ui_meta.view_bind_group = Some(render_device.create_bind_group(&BindGroupDescriptor { - entries: &[BindGroupEntry { - binding: 0, - resource: view_binding, - }], - label: Some("ui_view_bind_group"), - layout: &ui_material_pipeline.ui_pipeline.view_layout, - })); - let draw_ui_function = draw_functions.read().id::>(); - for (view, mut transparent_phase) in &mut views { - for (entity, batch) in &ui_batches { - if let Some(material) = render_materials.get(&batch.material) { - let pipeline = pipelines.specialize( - &pipeline_cache, - &ui_material_pipeline, - UiMaterialKey { - hdr: view.hdr, - bind_group_data: material.key.clone(), - }, - ); - image_bind_groups - .values - .entry(DEFAULT_IMAGE_HANDLE.typed().clone_weak()) - .or_insert_with(|| { - let gpu_image = gpu_images.get(&DEFAULT_IMAGE_HANDLE.typed()).unwrap(); - render_device.create_bind_group(&BindGroupDescriptor { - entries: &[ - BindGroupEntry { - binding: 0, - resource: BindingResource::TextureView( - &gpu_image.texture_view, - ), - }, - BindGroupEntry { - binding: 1, - resource: BindingResource::Sampler(&gpu_image.sampler), - }, - ], - label: Some("ui_material_bind_group"), - layout: &ui_material_pipeline.ui_pipeline.image_layout, - }) - }); - - transparent_phase.add(TransparentUi { - sort_key: FloatOrd(batch.z), - entity, - pipeline, - draw_function: draw_ui_function, - }); - } - } - } - } -} - -#[derive(Resource, Deref, DerefMut)] -pub struct RenderUiMaterials(HashMap, PreparedUiMaterial>); - -impl Default for RenderUiMaterials { - fn default() -> Self { - Self(Default::default()) - } -} - -pub struct PreparedUiMaterial { - pub bindings: Vec, - pub bind_group: BindGroup, - pub key: T::Data, -} - -#[derive(Resource)] -pub struct ExtractedUiMaterials { - extracted: Vec<(Handle, M)>, - removed: Vec>, -} - -impl Default for ExtractedUiMaterials { - fn default() -> Self { - Self { - extracted: Default::default(), - removed: Default::default(), - } - } -} - -pub fn extract_ui_materials( - mut commands: Commands, - mut events: Extract>>, - assets: Extract>>, -) { - let mut changed_assets = HashSet::default(); - let mut removed = Vec::new(); - for event in events.iter() { - match event { - AssetEvent::Created { handle } | AssetEvent::Modified { handle } => { - changed_assets.insert(handle.clone_weak()); - } - AssetEvent::Removed { handle } => { - changed_assets.remove(handle); - removed.push(handle.clone_weak()); - } - } - } - - let mut extracted_assets = Vec::new(); - for handle in changed_assets.drain() { - if let Some(asset) = assets.get(&handle) { - extracted_assets.push((handle, asset.clone())); - } - } - - commands.insert_resource(ExtractedUiMaterials { - extracted: extracted_assets, - removed, - }); -} - -pub struct PrepareNextFrameMaterials { - assets: Vec<(Handle, M)>, -} - -impl Default for PrepareNextFrameMaterials { - fn default() -> Self { - Self { - assets: Default::default(), - } - } -} - -pub fn prepare_ui_materials( - mut prepare_next_frame: Local>, - mut extracted_assets: ResMut>, - mut render_materials: ResMut>, - render_device: Res, - images: Res>, - fallback_image: Res, - pipeline: Res>, -) { - let queued_assets = std::mem::take(&mut prepare_next_frame.assets); - for (handle, material) in queued_assets { - match prepare_ui_material( - &material, - &render_device, - &images, - &fallback_image, - &pipeline, - ) { - Ok(prepared_asset) => { - render_materials.insert(handle, prepared_asset); - } - Err(AsBindGroupError::RetryNextUpdate) => { - prepare_next_frame.assets.push((handle, material)); - } - } - } - - for removed in std::mem::take(&mut extracted_assets.removed) { - render_materials.remove(&removed); - } - - for (handle, material) in std::mem::take(&mut extracted_assets.extracted) { - match prepare_ui_material( - &material, - &render_device, - &images, - &fallback_image, - &pipeline, - ) { - Ok(prepared_asset) => { - render_materials.insert(handle, prepared_asset); - } - Err(AsBindGroupError::RetryNextUpdate) => { - prepare_next_frame.assets.push((handle, material)); - } - } - } -} - -fn prepare_ui_material( - material: &M, - render_device: &RenderDevice, - images: &RenderAssets, - fallback_image: &Res, - pipeline: &UiMaterialPipeline, -) -> Result, AsBindGroupError> { - let prepared = - material.as_bind_group(&pipeline.ui_layout, render_device, images, fallback_image)?; - Ok(PreparedUiMaterial { - bindings: prepared.bindings, - bind_group: prepared.bind_group, - key: prepared.data, - }) -} - -#[derive(Bundle, Clone, Debug)] -pub struct MaterialNodeBundle { - pub node: Node, - pub style: Style, - pub material: Handle, - pub focus_policy: FocusPolicy, - pub transform: Transform, - pub global_transform: GlobalTransform, - pub visibility: Visibility, - pub computed_visibility: ComputedVisibility, - pub z_index: ZIndex, -} - -impl Default for MaterialNodeBundle { - 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(), - } - } -} diff --git a/crates/bevy_ui/src/node_bundles.rs b/crates/bevy_ui/src/node_bundles.rs index a8c82a4828893..061e1d7f0a9a5 100644 --- a/crates/bevy_ui/src/node_bundles.rs +++ b/crates/bevy_ui/src/node_bundles.rs @@ -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; @@ -326,3 +326,49 @@ impl Default for ButtonBundle { } } } + +/// A Ui Node that renders a shader +#[derive(Bundle, Clone, Debug)] +pub struct MaterialNodeBundle { + /// 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, + /// 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, +} + +impl Default for MaterialNodeBundle { + 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(), + } + } +} diff --git a/crates/bevy_ui/src/render/material_pipeline.rs b/crates/bevy_ui/src/render/material_pipeline.rs new file mode 100644 index 0000000000000..f437aa6f00311 --- /dev/null +++ b/crates/bevy_ui/src/render/material_pipeline.rs @@ -0,0 +1,646 @@ +use std::{hash::Hash, marker::PhantomData, ops::Range}; + +use bevy_app::{App, Plugin}; +use bevy_asset::{AddAsset, AssetEvent, AssetServer, Assets, Handle}; +use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::{ + prelude::{Component, Entity, EventReader}, + query::ROQueryItem, + schedule::IntoSystemConfigs, + system::{ + lifetimeless::{Read, SRes}, + Commands, Local, Query, Res, ResMut, Resource, SystemParamItem, + }, + world::{FromWorld, World}, +}; +use bevy_math::{Mat4, Rect, Vec2, Vec4Swizzles}; +use bevy_render::{ + extract_component::ExtractComponentPlugin, + prelude::Color, + render_asset::{PrepareAssetSet, RenderAssets}, + render_phase::{ + AddRenderCommand, DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult, + RenderPhase, SetItemPipeline, TrackedRenderPass, + }, + render_resource::{ + AsBindGroupError, BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, + BindingResource, BufferUsages, BufferVec, OwnedBindingResource, PipelineCache, + RenderPipelineDescriptor, Shader, ShaderRef, SpecializedRenderPipeline, + SpecializedRenderPipelines, + }, + renderer::{RenderDevice, RenderQueue}, + texture::{FallbackImage, Image, DEFAULT_IMAGE_HANDLE}, + view::{ComputedVisibility, ExtractedView, ViewUniformOffset, ViewUniforms}, + Extract, ExtractSchedule, Render, RenderApp, RenderSet, +}; +use bevy_transform::prelude::GlobalTransform; +use bevy_utils::{FloatOrd, HashMap, HashSet}; + +use crate::{ + CalculatedClip, Node, RenderUiSystem, TransparentUi, UiImageBindGroups, UiMaterial, + UiMaterialKey, UiPipeline, UiPipelineKey, UiStack, UiVertex, QUAD_INDICES, + QUAD_VERTEX_POSITIONS, +}; + +/// Adds the necessary ECS resources and render logic to enable rendering entities using the given +/// [`UiMaterial`] asset type (which includes [`UiMaterial`] types). +pub struct UiMaterialPlugin(PhantomData); + +impl Default for UiMaterialPlugin { + fn default() -> Self { + Self(Default::default()) + } +} + +impl Plugin for UiMaterialPlugin +where + M::Data: PartialEq + Eq + Hash + Clone, +{ + fn build(&self, app: &mut bevy_app::App) { + app.add_asset::() + .add_plugins(ExtractComponentPlugin::>::extract_visible()); + + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + render_app + .add_render_command::>() + .init_resource::>() + .init_resource::>() + .init_resource::>() + .init_resource::>() + .init_resource::>>() + .add_systems( + ExtractSchedule, + ( + extract_ui_materials::, + extract_material_uinodes::.in_set(RenderUiSystem::ExtractNode), + ), + ) + .add_systems( + Render, + ( + prepare_ui_materials:: + .in_set(RenderSet::Prepare) + .after(PrepareAssetSet::PreAssetPrepare), + prepare_uimaterial_nodes::.in_set(RenderSet::Prepare), + queue_ui_material_nodes::.in_set(RenderSet::Queue), + ), + ); + } + } + + fn finish(&self, app: &mut App) { + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + render_app.init_resource::>(); + } + } +} + +#[derive(Resource)] +pub struct UiMatMeta { + vertices: BufferVec, + view_bind_group: Option, + marker: PhantomData, +} + +impl Default for UiMatMeta { + fn default() -> Self { + Self { + vertices: BufferVec::new(BufferUsages::VERTEX), + view_bind_group: Default::default(), + marker: PhantomData, + } + } +} + +#[derive(Component)] +pub struct MatUiBatch { + pub range: Range, + pub material: Handle, + pub z: f32, +} + +#[derive(Resource)] +pub struct UiMaterialPipeline { + pub ui_pipeline: UiPipeline, + pub ui_layout: BindGroupLayout, + pub vertex_shader: Option>, + pub fragment_shader: Option>, + marker: PhantomData, +} + +impl SpecializedRenderPipeline for UiMaterialPipeline +where + M::Data: PartialEq + Eq + Hash + Clone, +{ + type Key = UiMaterialKey; + + fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { + let mut descriptor = self.ui_pipeline.specialize(UiPipelineKey { hdr: key.hdr }); + if let Some(vertex_shader) = &self.vertex_shader { + descriptor.vertex.shader = vertex_shader.clone(); + } + + if let Some(fragment_shader) = &self.fragment_shader { + descriptor.fragment.as_mut().unwrap().shader = fragment_shader.clone(); + } + + descriptor.layout = vec![ + self.ui_pipeline.view_layout.clone(), + self.ui_pipeline.image_layout.clone(), + self.ui_layout.clone(), + ]; + + M::specialize(&mut descriptor, key); + + descriptor + } +} + +impl FromWorld for UiMaterialPipeline { + fn from_world(world: &mut World) -> Self { + let asset_server = world.resource::(); + let render_device = world.resource::(); + let ui_layout = M::bind_group_layout(render_device); + + UiMaterialPipeline { + ui_pipeline: world.resource::().clone(), + ui_layout, + vertex_shader: match M::vertex_shader() { + ShaderRef::Default => None, + ShaderRef::Handle(handle) => Some(handle), + ShaderRef::Path(path) => Some(asset_server.load(path)), + }, + fragment_shader: match M::fragment_shader() { + ShaderRef::Default => None, + ShaderRef::Handle(handle) => Some(handle), + ShaderRef::Path(path) => Some(asset_server.load(path)), + }, + marker: PhantomData, + } + } +} + +pub type DrawUiMaterial = ( + SetItemPipeline, + SetMatUiViewBindGroup, + SetMatUiTextureBindGroup, + SetUiMaterialBindGroup, + DrawUiMatNode, +); + +pub struct SetMatUiTextureBindGroup(PhantomData); +impl RenderCommand

+ for SetMatUiTextureBindGroup +{ + type Param = SRes; + type ViewWorldQuery = (); + type ItemWorldQuery = Read>; + + #[inline] + fn render<'w>( + _item: &P, + _view: (), + _batch: &'w MatUiBatch, + image_bind_groups: SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, + ) -> RenderCommandResult { + let image_bind_groups = image_bind_groups.into_inner(); + pass.set_bind_group( + I, + image_bind_groups + .values + .get(&DEFAULT_IMAGE_HANDLE.typed()) + .unwrap(), + &[], + ); + RenderCommandResult::Success + } +} + +pub struct SetMatUiViewBindGroup(PhantomData); +impl RenderCommand

for SetMatUiViewBindGroup { + type Param = SRes>; + type ViewWorldQuery = Read; + type ItemWorldQuery = (); + + fn render<'w>( + _item: &P, + view_uniform: &'w ViewUniformOffset, + _entity: (), + ui_meta: SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, + ) -> RenderCommandResult { + pass.set_bind_group( + I, + ui_meta.into_inner().view_bind_group.as_ref().unwrap(), + &[view_uniform.offset], + ); + RenderCommandResult::Success + } +} + +pub struct DrawUiMatNode(PhantomData); +impl RenderCommand

for DrawUiMatNode { + type Param = SRes>; + type ViewWorldQuery = (); + type ItemWorldQuery = Read>; + + #[inline] + fn render<'w>( + _item: &P, + _view: (), + batch: &'w MatUiBatch, + ui_meta: SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, + ) -> RenderCommandResult { + pass.set_vertex_buffer(0, ui_meta.into_inner().vertices.buffer().unwrap().slice(..)); + pass.draw(batch.range.clone(), 0..1); + RenderCommandResult::Success + } +} + +pub struct SetUiMaterialBindGroup(PhantomData); +impl RenderCommand

+ for SetUiMaterialBindGroup +{ + type Param = SRes>; + type ViewWorldQuery = (); + type ItemWorldQuery = Read>; + + fn render<'w>( + _item: &P, + _view: (), + material_handle: ROQueryItem<'_, Self::ItemWorldQuery>, + materials: SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, + ) -> RenderCommandResult { + let material = materials + .into_inner() + .get(&material_handle.material) + .unwrap(); + pass.set_bind_group(I, &material.bind_group, &[]); + RenderCommandResult::Success + } +} + +pub struct ExtractedUiMaterialNode { + pub stack_index: usize, + pub transform: Mat4, + pub rect: Rect, + pub material: Handle, + pub clip: Option, +} + +#[derive(Resource)] +pub struct ExtractedUiMaterialNodes { + pub uinodes: Vec>, +} + +impl Default for ExtractedUiMaterialNodes { + fn default() -> Self { + Self { + uinodes: Default::default(), + } + } +} + +pub fn extract_material_uinodes( + mut extracted_uinodes: ResMut>, + ui_stack: Extract>, + uinode_query: Extract< + Query<( + &Node, + &GlobalTransform, + &Handle, + &ComputedVisibility, + Option<&CalculatedClip>, + )>, + >, +) { + for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() { + if let Ok((uinode, transform, handle, visibility, clip)) = uinode_query.get(*entity) { + if !visibility.is_visible() { + continue; + } + // Skip invisible and completely transparent nodes + extracted_uinodes.uinodes.push(ExtractedUiMaterialNode { + stack_index, + transform: transform.compute_matrix(), + material: handle.clone_weak(), + rect: Rect { + min: Vec2::ZERO, + max: uinode.calculated_size, + }, + clip: clip.map(|clip| clip.clip), + }); + }; + } +} + +pub fn prepare_uimaterial_nodes( + mut commands: Commands, + render_device: Res, + render_queue: Res, + mut ui_meta: ResMut>, + mut extracted_uinodes: ResMut>, +) { + ui_meta.vertices.clear(); + + extracted_uinodes + .uinodes + .sort_by_key(|node| node.stack_index); + for extracted_uinode in extracted_uinodes.uinodes.drain(..) { + let uinode_rect = extracted_uinode.rect; + + let rect_size = uinode_rect.size().extend(1.0); + + let positions = QUAD_VERTEX_POSITIONS + .map(|pos| (extracted_uinode.transform * (pos * rect_size).extend(1.)).xyz()); + + let positions_diff = if let Some(clip) = extracted_uinode.clip { + [ + Vec2::new( + f32::max(clip.min.x - positions[0].x, 0.), + f32::max(clip.min.y - positions[0].y, 0.), + ), + Vec2::new( + f32::min(clip.max.x - positions[1].x, 0.), + f32::max(clip.min.y - positions[1].y, 0.), + ), + Vec2::new( + f32::min(clip.max.x - positions[2].x, 0.), + f32::min(clip.max.y - positions[2].y, 0.), + ), + Vec2::new( + f32::max(clip.min.x - positions[3].x, 0.), + f32::min(clip.max.y - positions[3].y, 0.), + ), + ] + } else { + [Vec2::ZERO; 4] + }; + + let positions_clipped = [ + positions[0] + positions_diff[0].extend(0.), + positions[1] + positions_diff[1].extend(0.), + positions[2] + positions_diff[2].extend(0.), + positions[3] + positions_diff[3].extend(0.), + ]; + + let transformed_rect_size = extracted_uinode.transform.transform_vector3(rect_size); + + if extracted_uinode.transform.x_axis[1] == 0.0 + && positions_diff[0].x - positions_diff[1].x >= transformed_rect_size.x + || positions_diff[1].y - positions_diff[2].y >= transformed_rect_size.y + { + continue; + } + let extent = uinode_rect.max; + let uvs = [ + Vec2::new( + uinode_rect.min.x + positions_diff[0].x, + uinode_rect.min.y + positions_diff[0].y, + ), + Vec2::new( + uinode_rect.max.x + positions_diff[1].x, + uinode_rect.min.y + positions_diff[1].y, + ), + Vec2::new( + uinode_rect.max.x + positions_diff[2].x, + uinode_rect.max.y + positions_diff[2].y, + ), + Vec2::new( + uinode_rect.min.x + positions_diff[3].x, + uinode_rect.max.y + positions_diff[3].y, + ), + ] + .map(|pos| pos / extent); + + for i in QUAD_INDICES { + ui_meta.vertices.push(crate::UiVertex { + position: positions_clipped[i].into(), + uv: uvs[i].into(), + color: Color::WHITE.into(), + mode: 1, + }); + } + commands.spawn(MatUiBatch { + range: 0..QUAD_INDICES.len() as u32, + material: extracted_uinode.material, + z: extracted_uinode.transform.w_axis[2], + }); + } + ui_meta.vertices.write_buffer(&render_device, &render_queue); +} + +#[allow(clippy::too_many_arguments)] +pub fn queue_ui_material_nodes( + draw_functions: Res>, + render_device: Res, + ui_material_pipeline: Res>, + mut ui_meta: ResMut>, + mut image_bind_groups: ResMut, + view_uniforms: Res, + mut pipelines: ResMut>>, + pipeline_cache: Res, + gpu_images: Res>, + render_materials: Res>, + ui_batches: Query<(Entity, &MatUiBatch)>, + mut views: Query<(&ExtractedView, &mut RenderPhase)>, +) where + M::Data: PartialEq + Eq + Hash + Clone, +{ + if let Some(view_binding) = view_uniforms.uniforms.binding() { + ui_meta.view_bind_group = Some(render_device.create_bind_group(&BindGroupDescriptor { + entries: &[BindGroupEntry { + binding: 0, + resource: view_binding, + }], + label: Some("ui_view_bind_group"), + layout: &ui_material_pipeline.ui_pipeline.view_layout, + })); + let draw_ui_function = draw_functions.read().id::>(); + for (view, mut transparent_phase) in &mut views { + for (entity, batch) in &ui_batches { + if let Some(material) = render_materials.get(&batch.material) { + let pipeline = pipelines.specialize( + &pipeline_cache, + &ui_material_pipeline, + UiMaterialKey { + hdr: view.hdr, + bind_group_data: material.key.clone(), + }, + ); + image_bind_groups + .values + .entry(DEFAULT_IMAGE_HANDLE.typed().clone_weak()) + .or_insert_with(|| { + let gpu_image = gpu_images.get(&DEFAULT_IMAGE_HANDLE.typed()).unwrap(); + render_device.create_bind_group(&BindGroupDescriptor { + entries: &[ + BindGroupEntry { + binding: 0, + resource: BindingResource::TextureView( + &gpu_image.texture_view, + ), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::Sampler(&gpu_image.sampler), + }, + ], + label: Some("ui_material_bind_group"), + layout: &ui_material_pipeline.ui_pipeline.image_layout, + }) + }); + + transparent_phase.add(TransparentUi { + sort_key: FloatOrd(batch.z), + entity, + pipeline, + draw_function: draw_ui_function, + }); + } + } + } + } +} + +#[derive(Resource, Deref, DerefMut)] +pub struct RenderUiMaterials(HashMap, PreparedUiMaterial>); + +impl Default for RenderUiMaterials { + fn default() -> Self { + Self(Default::default()) + } +} + +pub struct PreparedUiMaterial { + pub bindings: Vec, + pub bind_group: BindGroup, + pub key: T::Data, +} + +#[derive(Resource)] +pub struct ExtractedUiMaterials { + extracted: Vec<(Handle, M)>, + removed: Vec>, +} + +impl Default for ExtractedUiMaterials { + fn default() -> Self { + Self { + extracted: Default::default(), + removed: Default::default(), + } + } +} + +pub fn extract_ui_materials( + mut commands: Commands, + mut events: Extract>>, + assets: Extract>>, +) { + let mut changed_assets = HashSet::default(); + let mut removed = Vec::new(); + for event in events.iter() { + match event { + AssetEvent::Created { handle } | AssetEvent::Modified { handle } => { + changed_assets.insert(handle.clone_weak()); + } + AssetEvent::Removed { handle } => { + changed_assets.remove(handle); + removed.push(handle.clone_weak()); + } + } + } + + let mut extracted_assets = Vec::new(); + for handle in changed_assets.drain() { + if let Some(asset) = assets.get(&handle) { + extracted_assets.push((handle, asset.clone())); + } + } + + commands.insert_resource(ExtractedUiMaterials { + extracted: extracted_assets, + removed, + }); +} + +pub struct PrepareNextFrameMaterials { + assets: Vec<(Handle, M)>, +} + +impl Default for PrepareNextFrameMaterials { + fn default() -> Self { + Self { + assets: Default::default(), + } + } +} + +pub fn prepare_ui_materials( + mut prepare_next_frame: Local>, + mut extracted_assets: ResMut>, + mut render_materials: ResMut>, + render_device: Res, + images: Res>, + fallback_image: Res, + pipeline: Res>, +) { + let queued_assets = std::mem::take(&mut prepare_next_frame.assets); + for (handle, material) in queued_assets { + match prepare_ui_material( + &material, + &render_device, + &images, + &fallback_image, + &pipeline, + ) { + Ok(prepared_asset) => { + render_materials.insert(handle, prepared_asset); + } + Err(AsBindGroupError::RetryNextUpdate) => { + prepare_next_frame.assets.push((handle, material)); + } + } + } + + for removed in std::mem::take(&mut extracted_assets.removed) { + render_materials.remove(&removed); + } + + for (handle, material) in std::mem::take(&mut extracted_assets.extracted) { + match prepare_ui_material( + &material, + &render_device, + &images, + &fallback_image, + &pipeline, + ) { + Ok(prepared_asset) => { + render_materials.insert(handle, prepared_asset); + } + Err(AsBindGroupError::RetryNextUpdate) => { + prepare_next_frame.assets.push((handle, material)); + } + } + } +} + +fn prepare_ui_material( + material: &M, + render_device: &RenderDevice, + images: &RenderAssets, + fallback_image: &Res, + pipeline: &UiMaterialPipeline, +) -> Result, AsBindGroupError> { + let prepared = + material.as_bind_group(&pipeline.ui_layout, render_device, images, fallback_image)?; + Ok(PreparedUiMaterial { + bindings: prepared.bindings, + bind_group: prepared.bind_group, + key: prepared.data, + }) +} diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 81344e81f138a..d41fa90c8419d 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -1,3 +1,4 @@ +mod material_pipeline; mod pipeline; mod render_pass; @@ -5,6 +6,7 @@ use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d}; use bevy_hierarchy::Parent; use bevy_render::{ExtractSchedule, Render}; use bevy_window::{PrimaryWindow, Window}; +pub use material_pipeline::*; pub use pipeline::*; pub use render_pass::*; From 3b8022a27b2e5a4ab593bb1d08eb4614d59a16a4 Mon Sep 17 00:00:00 2001 From: Markus Ort Date: Mon, 21 Aug 2023 19:05:07 +0200 Subject: [PATCH 11/55] removes usage of UiPipeline inside UiMaterialPipeline --- assets/shaders/circle_shader.wgsl | 2 +- crates/bevy_ui/src/material.rs | 2 +- .../bevy_ui/src/render/material_pipeline.rs | 159 ++++++++++-------- crates/bevy_ui/src/render/pipeline.rs | 2 +- 4 files changed, 89 insertions(+), 76 deletions(-) diff --git a/assets/shaders/circle_shader.wgsl b/assets/shaders/circle_shader.wgsl index c38dbe4f3187b..e8d2a84cde03e 100644 --- a/assets/shaders/circle_shader.wgsl +++ b/assets/shaders/circle_shader.wgsl @@ -14,7 +14,7 @@ struct CustomUiMaterial { @location(1) color: vec4 } -@group(2) @binding(0) +@group(1) @binding(0) var input: CustomUiMaterial; // How smooth the border of the gradient should be diff --git a/crates/bevy_ui/src/material.rs b/crates/bevy_ui/src/material.rs index 21a13c5427faf..cdcb8a7a577e9 100644 --- a/crates/bevy_ui/src/material.rs +++ b/crates/bevy_ui/src/material.rs @@ -7,7 +7,7 @@ use bevy_render::render_resource::{AsBindGroup, RenderPipelineDescriptor, Shader /// 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. +/// `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 [`TypeUuid`] so they can be treated as an [`Asset`](bevy_asset::Asset). diff --git a/crates/bevy_ui/src/render/material_pipeline.rs b/crates/bevy_ui/src/render/material_pipeline.rs index f437aa6f00311..97ad0dc5856bf 100644 --- a/crates/bevy_ui/src/render/material_pipeline.rs +++ b/crates/bevy_ui/src/render/material_pipeline.rs @@ -24,22 +24,26 @@ use bevy_render::{ }, render_resource::{ AsBindGroupError, BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, - BindingResource, BufferUsages, BufferVec, OwnedBindingResource, PipelineCache, - RenderPipelineDescriptor, Shader, ShaderRef, SpecializedRenderPipeline, - SpecializedRenderPipelines, + BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, BlendState, + BufferBindingType, BufferUsages, BufferVec, ColorTargetState, ColorWrites, FragmentState, + FrontFace, MultisampleState, OwnedBindingResource, PipelineCache, PolygonMode, + PrimitiveState, PrimitiveTopology, RenderPipelineDescriptor, Shader, ShaderRef, + ShaderStages, ShaderType, SpecializedRenderPipeline, SpecializedRenderPipelines, + TextureFormat, VertexBufferLayout, VertexFormat, VertexState, VertexStepMode, }, renderer::{RenderDevice, RenderQueue}, - texture::{FallbackImage, Image, DEFAULT_IMAGE_HANDLE}, - view::{ComputedVisibility, ExtractedView, ViewUniformOffset, ViewUniforms}, + texture::{BevyDefault, FallbackImage, Image}, + view::{ + ComputedVisibility, ExtractedView, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms, + }, Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; use bevy_transform::prelude::GlobalTransform; use bevy_utils::{FloatOrd, HashMap, HashSet}; use crate::{ - CalculatedClip, Node, RenderUiSystem, TransparentUi, UiImageBindGroups, UiMaterial, - UiMaterialKey, UiPipeline, UiPipelineKey, UiStack, UiVertex, QUAD_INDICES, - QUAD_VERTEX_POSITIONS, + CalculatedClip, Node, RenderUiSystem, TransparentUi, UiMaterial, UiMaterialKey, UiStack, + UiVertex, QUAD_INDICES, QUAD_VERTEX_POSITIONS, }; /// Adds the necessary ECS resources and render logic to enable rendering entities using the given @@ -121,8 +125,8 @@ pub struct MatUiBatch { #[derive(Resource)] pub struct UiMaterialPipeline { - pub ui_pipeline: UiPipeline, pub ui_layout: BindGroupLayout, + pub view_layout: BindGroupLayout, pub vertex_shader: Option>, pub fragment_shader: Option>, marker: PhantomData, @@ -135,7 +139,61 @@ where type Key = UiMaterialKey; fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { - let mut descriptor = self.ui_pipeline.specialize(UiPipelineKey { hdr: key.hdr }); + let vertex_layout = VertexBufferLayout::from_vertex_formats( + VertexStepMode::Vertex, + vec![ + // position + VertexFormat::Float32x3, + // uv + VertexFormat::Float32x2, + // color + VertexFormat::Float32x4, + // mode + VertexFormat::Uint32, + ], + ); + let shader_defs = Vec::new(); + + let mut descriptor = RenderPipelineDescriptor { + vertex: VertexState { + shader: super::UI_SHADER_HANDLE.typed::(), + entry_point: "vertex".into(), + shader_defs: shader_defs.clone(), + buffers: vec![vertex_layout], + }, + fragment: Some(FragmentState { + shader: super::UI_SHADER_HANDLE.typed::(), + shader_defs, + entry_point: "fragment".into(), + targets: vec![Some(ColorTargetState { + format: if key.hdr { + ViewTarget::TEXTURE_FORMAT_HDR + } else { + TextureFormat::bevy_default() + }, + blend: Some(BlendState::ALPHA_BLENDING), + write_mask: ColorWrites::ALL, + })], + }), + layout: vec![], + push_constant_ranges: Vec::new(), + primitive: PrimitiveState { + front_face: FrontFace::Ccw, + cull_mode: None, + unclipped_depth: false, + polygon_mode: PolygonMode::Fill, + conservative: false, + topology: PrimitiveTopology::TriangleList, + strip_index_format: None, + }, + depth_stencil: None, + multisample: MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + label: Some("material_ui_pipeline".into()), + }; if let Some(vertex_shader) = &self.vertex_shader { descriptor.vertex.shader = vertex_shader.clone(); } @@ -144,11 +202,7 @@ where descriptor.fragment.as_mut().unwrap().shader = fragment_shader.clone(); } - descriptor.layout = vec![ - self.ui_pipeline.view_layout.clone(), - self.ui_pipeline.image_layout.clone(), - self.ui_layout.clone(), - ]; + descriptor.layout = vec![self.view_layout.clone(), self.ui_layout.clone()]; M::specialize(&mut descriptor, key); @@ -162,9 +216,23 @@ impl FromWorld for UiMaterialPipeline { let render_device = world.resource::(); let ui_layout = M::bind_group_layout(render_device); + let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { + entries: &[BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: true, + min_binding_size: Some(ViewUniform::min_size()), + }, + count: None, + }], + label: Some("ui_view_layout"), + }); + UiMaterialPipeline { - ui_pipeline: world.resource::().clone(), ui_layout, + view_layout, vertex_shader: match M::vertex_shader() { ShaderRef::Default => None, ShaderRef::Handle(handle) => Some(handle), @@ -183,40 +251,10 @@ impl FromWorld for UiMaterialPipeline { pub type DrawUiMaterial = ( SetItemPipeline, SetMatUiViewBindGroup, - SetMatUiTextureBindGroup, - SetUiMaterialBindGroup, + SetUiMaterialBindGroup, DrawUiMatNode, ); -pub struct SetMatUiTextureBindGroup(PhantomData); -impl RenderCommand

- for SetMatUiTextureBindGroup -{ - type Param = SRes; - type ViewWorldQuery = (); - type ItemWorldQuery = Read>; - - #[inline] - fn render<'w>( - _item: &P, - _view: (), - _batch: &'w MatUiBatch, - image_bind_groups: SystemParamItem<'w, '_, Self::Param>, - pass: &mut TrackedRenderPass<'w>, - ) -> RenderCommandResult { - let image_bind_groups = image_bind_groups.into_inner(); - pass.set_bind_group( - I, - image_bind_groups - .values - .get(&DEFAULT_IMAGE_HANDLE.typed()) - .unwrap(), - &[], - ); - RenderCommandResult::Success - } -} - pub struct SetMatUiViewBindGroup(PhantomData); impl RenderCommand

for SetMatUiViewBindGroup { type Param = SRes>; @@ -439,11 +477,9 @@ pub fn queue_ui_material_nodes( render_device: Res, ui_material_pipeline: Res>, mut ui_meta: ResMut>, - mut image_bind_groups: ResMut, view_uniforms: Res, mut pipelines: ResMut>>, pipeline_cache: Res, - gpu_images: Res>, render_materials: Res>, ui_batches: Query<(Entity, &MatUiBatch)>, mut views: Query<(&ExtractedView, &mut RenderPhase)>, @@ -457,7 +493,7 @@ pub fn queue_ui_material_nodes( resource: view_binding, }], label: Some("ui_view_bind_group"), - layout: &ui_material_pipeline.ui_pipeline.view_layout, + layout: &ui_material_pipeline.view_layout, })); let draw_ui_function = draw_functions.read().id::>(); for (view, mut transparent_phase) in &mut views { @@ -471,29 +507,6 @@ pub fn queue_ui_material_nodes( bind_group_data: material.key.clone(), }, ); - image_bind_groups - .values - .entry(DEFAULT_IMAGE_HANDLE.typed().clone_weak()) - .or_insert_with(|| { - let gpu_image = gpu_images.get(&DEFAULT_IMAGE_HANDLE.typed()).unwrap(); - render_device.create_bind_group(&BindGroupDescriptor { - entries: &[ - BindGroupEntry { - binding: 0, - resource: BindingResource::TextureView( - &gpu_image.texture_view, - ), - }, - BindGroupEntry { - binding: 1, - resource: BindingResource::Sampler(&gpu_image.sampler), - }, - ], - label: Some("ui_material_bind_group"), - layout: &ui_material_pipeline.ui_pipeline.image_layout, - }) - }); - transparent_phase.add(TransparentUi { sort_key: FloatOrd(batch.z), entity, diff --git a/crates/bevy_ui/src/render/pipeline.rs b/crates/bevy_ui/src/render/pipeline.rs index b5a70923f7936..f6b4b0cc3c1ea 100644 --- a/crates/bevy_ui/src/render/pipeline.rs +++ b/crates/bevy_ui/src/render/pipeline.rs @@ -6,7 +6,7 @@ use bevy_render::{ view::{ViewTarget, ViewUniform}, }; -#[derive(Resource, Clone)] +#[derive(Resource)] pub struct UiPipeline { pub view_layout: BindGroupLayout, pub image_layout: BindGroupLayout, From ced929232ad0c80213f03fb51e87cc2da4d04975 Mon Sep 17 00:00:00 2001 From: Markus Ort Date: Tue, 22 Aug 2023 14:46:29 +0200 Subject: [PATCH 12/55] Use `UiMaterialVertex` without `mode` field. due to this change the `UiMaterialPipeline` also has to use a custom shader. --- assets/shaders/circle_shader.wgsl | 6 +- crates/bevy_ui/src/material.rs | 9 +- crates/bevy_ui/src/render/material.wgsl | 28 +++ .../bevy_ui/src/render/material_pipeline.rs | 203 ++++++++++-------- examples/ui/ui_material.rs | 12 ++ 5 files changed, 163 insertions(+), 95 deletions(-) create mode 100644 crates/bevy_ui/src/render/material.wgsl diff --git a/assets/shaders/circle_shader.wgsl b/assets/shaders/circle_shader.wgsl index e8d2a84cde03e..470b469cab49b 100644 --- a/assets/shaders/circle_shader.wgsl +++ b/assets/shaders/circle_shader.wgsl @@ -1,11 +1,9 @@ -// This shader draws a circle, that can be driven with certain properties - +// This shader draws a circular progress bar #import bevy_render::view View struct VertexOutput { @location(0) uv: vec2, @location(1) color: vec4, - @location(3) @interpolate(flat) mode: u32, @builtin(position) position: vec4, }; @@ -14,6 +12,8 @@ struct CustomUiMaterial { @location(1) color: vec4 } +@group(0) @binding(0) +var view: View; @group(1) @binding(0) var input: CustomUiMaterial; diff --git a/crates/bevy_ui/src/material.rs b/crates/bevy_ui/src/material.rs index cdcb8a7a577e9..1d193bdf73d34 100644 --- a/crates/bevy_ui/src/material.rs +++ b/crates/bevy_ui/src/material.rs @@ -2,10 +2,9 @@ use std::hash::Hash; use bevy_reflect::{TypePath, TypeUuid}; use bevy_render::render_resource::{AsBindGroup, RenderPipelineDescriptor, ShaderRef}; - -/// Materials are used alongside [`UiMaterialPlugin`] and [`MaterialNodeBundle`] +/// Materials are used alongside `UiMaterialPlugin` and `MaterialNodeBundle` /// 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. +/// 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. @@ -17,7 +16,7 @@ use bevy_render::render_resource::{AsBindGroup, RenderPipelineDescriptor, Shader /// 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::{UiMaterial, MaterialNodeBundle}; +/// # use bevy_ui::prelude::*; /// # use bevy_ecs::prelude::*; /// # use bevy_reflect::{TypeUuid, TypePath}; /// # use bevy_render::{render_resource::{AsBindGroup, ShaderRef}, texture::Image, color::Color}; @@ -48,7 +47,7 @@ use bevy_render::render_resource::{AsBindGroup, RenderPipelineDescriptor, Shader /// // Spawn an entity using `CustomMaterial`. /// fn setup(mut commands: Commands, mut materials: ResMut>, asset_server: Res) { /// commands.spawn(MaterialNodeBundle { -/// style: { +/// style: Style { /// width: Val::Percent(100.0), /// ..Default::default() /// }, diff --git a/crates/bevy_ui/src/render/material.wgsl b/crates/bevy_ui/src/render/material.wgsl new file mode 100644 index 0000000000000..09a4dd8cd53d3 --- /dev/null +++ b/crates/bevy_ui/src/render/material.wgsl @@ -0,0 +1,28 @@ +#import bevy_render::view View + +@group(0) @binding(0) +var view: View; + +struct VertexOutput { + @location(0) uv: vec2, + @location(1) color: vec4, + @builtin(position) position: vec4, +}; + +@vertex +fn vertex( + @location(0) vertex_position: vec3, + @location(1) vertex_uv: vec2, + @location(2) vertex_color: vec4, +) -> VertexOutput { + var out: VertexOutput; + out.uv = vertex_uv; + out.position = view.view_proj * vec4(vertex_position, 1.0); + out.color = vertex_color; + return out; +} + +@fragment +fn fragment(in: VertexOutput) -> @location(0) vec4 { + return vec4(1.0); +} diff --git a/crates/bevy_ui/src/render/material_pipeline.rs b/crates/bevy_ui/src/render/material_pipeline.rs index 97ad0dc5856bf..9268a2e88b8e7 100644 --- a/crates/bevy_ui/src/render/material_pipeline.rs +++ b/crates/bevy_ui/src/render/material_pipeline.rs @@ -1,7 +1,9 @@ use std::{hash::Hash, marker::PhantomData, ops::Range}; use bevy_app::{App, Plugin}; -use bevy_asset::{AddAsset, AssetEvent, AssetServer, Assets, Handle}; +use bevy_asset::{ + load_internal_asset, AddAsset, AssetEvent, AssetServer, Assets, Handle, HandleUntyped, +}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ prelude::{Component, Entity, EventReader}, @@ -14,6 +16,7 @@ use bevy_ecs::{ world::{FromWorld, World}, }; use bevy_math::{Mat4, Rect, Vec2, Vec4Swizzles}; +use bevy_reflect::TypeUuid; use bevy_render::{ extract_component::ExtractComponentPlugin, prelude::Color, @@ -40,12 +43,15 @@ use bevy_render::{ }; use bevy_transform::prelude::GlobalTransform; use bevy_utils::{FloatOrd, HashMap, HashSet}; +use bytemuck::{Pod, Zeroable}; use crate::{ CalculatedClip, Node, RenderUiSystem, TransparentUi, UiMaterial, UiMaterialKey, UiStack, - UiVertex, QUAD_INDICES, QUAD_VERTEX_POSITIONS, + QUAD_INDICES, QUAD_VERTEX_POSITIONS, }; +pub const MATERIAL_UI_SHADER_HANDLE: HandleUntyped = + HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 10074188772096983955); /// Adds the necessary ECS resources and render logic to enable rendering entities using the given /// [`UiMaterial`] asset type (which includes [`UiMaterial`] types). pub struct UiMaterialPlugin(PhantomData); @@ -61,6 +67,13 @@ where M::Data: PartialEq + Eq + Hash + Clone, { fn build(&self, app: &mut bevy_app::App) { + load_internal_asset!( + app, + MATERIAL_UI_SHADER_HANDLE, + "material.wgsl", + Shader::from_wgsl + ); + app.add_asset::() .add_plugins(ExtractComponentPlugin::>::extract_visible()); @@ -70,7 +83,7 @@ where .init_resource::>() .init_resource::>() .init_resource::>() - .init_resource::>() + .init_resource::>() .init_resource::>>() .add_systems( ExtractSchedule, @@ -100,13 +113,13 @@ where } #[derive(Resource)] -pub struct UiMatMeta { - vertices: BufferVec, +pub struct UiMaterialMeta { + vertices: BufferVec, view_bind_group: Option, marker: PhantomData, } -impl Default for UiMatMeta { +impl Default for UiMaterialMeta { fn default() -> Self { Self { vertices: BufferVec::new(BufferUsages::VERTEX), @@ -116,8 +129,16 @@ impl Default for UiMatMeta { } } +#[repr(C)] +#[derive(Copy, Clone, Pod, Zeroable)] +pub struct UiMaterialVertex { + pub position: [f32; 3], + pub uv: [f32; 2], + pub color: [f32; 4], +} + #[derive(Component)] -pub struct MatUiBatch { +pub struct UiMaterialBatch { pub range: Range, pub material: Handle, pub z: f32, @@ -148,21 +169,19 @@ where VertexFormat::Float32x2, // color VertexFormat::Float32x4, - // mode - VertexFormat::Uint32, ], ); let shader_defs = Vec::new(); let mut descriptor = RenderPipelineDescriptor { vertex: VertexState { - shader: super::UI_SHADER_HANDLE.typed::(), + shader: MATERIAL_UI_SHADER_HANDLE.typed::(), entry_point: "vertex".into(), shader_defs: shader_defs.clone(), buffers: vec![vertex_layout], }, fragment: Some(FragmentState { - shader: super::UI_SHADER_HANDLE.typed::(), + shader: MATERIAL_UI_SHADER_HANDLE.typed::(), shader_defs, entry_point: "fragment".into(), targets: vec![Some(ColorTargetState { @@ -252,12 +271,12 @@ pub type DrawUiMaterial = ( SetItemPipeline, SetMatUiViewBindGroup, SetUiMaterialBindGroup, - DrawUiMatNode, + DrawUiMaterialNode, ); pub struct SetMatUiViewBindGroup(PhantomData); impl RenderCommand

for SetMatUiViewBindGroup { - type Param = SRes>; + type Param = SRes>; type ViewWorldQuery = Read; type ItemWorldQuery = (); @@ -277,33 +296,13 @@ impl RenderCommand

for SetMatUiV } } -pub struct DrawUiMatNode(PhantomData); -impl RenderCommand

for DrawUiMatNode { - type Param = SRes>; - type ViewWorldQuery = (); - type ItemWorldQuery = Read>; - - #[inline] - fn render<'w>( - _item: &P, - _view: (), - batch: &'w MatUiBatch, - ui_meta: SystemParamItem<'w, '_, Self::Param>, - pass: &mut TrackedRenderPass<'w>, - ) -> RenderCommandResult { - pass.set_vertex_buffer(0, ui_meta.into_inner().vertices.buffer().unwrap().slice(..)); - pass.draw(batch.range.clone(), 0..1); - RenderCommandResult::Success - } -} - pub struct SetUiMaterialBindGroup(PhantomData); impl RenderCommand

for SetUiMaterialBindGroup { type Param = SRes>; type ViewWorldQuery = (); - type ItemWorldQuery = Read>; + type ItemWorldQuery = Read>; fn render<'w>( _item: &P, @@ -321,6 +320,26 @@ impl RenderCommand

} } +pub struct DrawUiMaterialNode(PhantomData); +impl RenderCommand

for DrawUiMaterialNode { + type Param = SRes>; + type ViewWorldQuery = (); + type ItemWorldQuery = Read>; + + #[inline] + fn render<'w>( + _item: &P, + _view: (), + batch: &'w UiMaterialBatch, + ui_meta: SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, + ) -> RenderCommandResult { + pass.set_vertex_buffer(0, ui_meta.into_inner().vertices.buffer().unwrap().slice(..)); + pass.draw(batch.range.clone(), 0..1); + RenderCommandResult::Success + } +} + pub struct ExtractedUiMaterialNode { pub stack_index: usize, pub transform: Mat4, @@ -344,6 +363,7 @@ impl Default for ExtractedUiMaterialNodes { pub fn extract_material_uinodes( mut extracted_uinodes: ResMut>, + materials: Extract>>, ui_stack: Extract>, uinode_query: Extract< Query<( @@ -360,6 +380,11 @@ pub fn extract_material_uinodes( if !visibility.is_visible() { continue; } + + // Skip loading materials + if !materials.contains(handle) { + continue; + } // Skip invisible and completely transparent nodes extracted_uinodes.uinodes.push(ExtractedUiMaterialNode { stack_index, @@ -379,7 +404,7 @@ pub fn prepare_uimaterial_nodes( mut commands: Commands, render_device: Res, render_queue: Res, - mut ui_meta: ResMut>, + mut ui_meta: ResMut>, mut extracted_uinodes: ResMut>, ) { ui_meta.vertices.clear(); @@ -387,6 +412,9 @@ pub fn prepare_uimaterial_nodes( extracted_uinodes .uinodes .sort_by_key(|node| node.stack_index); + + let mut start = 0; + let mut end = 0; for extracted_uinode in extracted_uinodes.uinodes.drain(..) { let uinode_rect = extracted_uinode.rect; @@ -455,70 +483,23 @@ pub fn prepare_uimaterial_nodes( .map(|pos| pos / extent); for i in QUAD_INDICES { - ui_meta.vertices.push(crate::UiVertex { + ui_meta.vertices.push(UiMaterialVertex { position: positions_clipped[i].into(), uv: uvs[i].into(), color: Color::WHITE.into(), - mode: 1, }); } - commands.spawn(MatUiBatch { - range: 0..QUAD_INDICES.len() as u32, + end += QUAD_INDICES.len() as u32; + commands.spawn(UiMaterialBatch { + range: start..end, material: extracted_uinode.material, z: extracted_uinode.transform.w_axis[2], }); + start = end; } ui_meta.vertices.write_buffer(&render_device, &render_queue); } -#[allow(clippy::too_many_arguments)] -pub fn queue_ui_material_nodes( - draw_functions: Res>, - render_device: Res, - ui_material_pipeline: Res>, - mut ui_meta: ResMut>, - view_uniforms: Res, - mut pipelines: ResMut>>, - pipeline_cache: Res, - render_materials: Res>, - ui_batches: Query<(Entity, &MatUiBatch)>, - mut views: Query<(&ExtractedView, &mut RenderPhase)>, -) where - M::Data: PartialEq + Eq + Hash + Clone, -{ - if let Some(view_binding) = view_uniforms.uniforms.binding() { - ui_meta.view_bind_group = Some(render_device.create_bind_group(&BindGroupDescriptor { - entries: &[BindGroupEntry { - binding: 0, - resource: view_binding, - }], - label: Some("ui_view_bind_group"), - layout: &ui_material_pipeline.view_layout, - })); - let draw_ui_function = draw_functions.read().id::>(); - for (view, mut transparent_phase) in &mut views { - for (entity, batch) in &ui_batches { - if let Some(material) = render_materials.get(&batch.material) { - let pipeline = pipelines.specialize( - &pipeline_cache, - &ui_material_pipeline, - UiMaterialKey { - hdr: view.hdr, - bind_group_data: material.key.clone(), - }, - ); - transparent_phase.add(TransparentUi { - sort_key: FloatOrd(batch.z), - entity, - pipeline, - draw_function: draw_ui_function, - }); - } - } - } - } -} - #[derive(Resource, Deref, DerefMut)] pub struct RenderUiMaterials(HashMap, PreparedUiMaterial>); @@ -657,3 +638,51 @@ fn prepare_ui_material( key: prepared.data, }) } + +#[allow(clippy::too_many_arguments)] +pub fn queue_ui_material_nodes( + draw_functions: Res>, + render_device: Res, + ui_material_pipeline: Res>, + mut ui_meta: ResMut>, + view_uniforms: Res, + mut pipelines: ResMut>>, + pipeline_cache: Res, + render_materials: Res>, + ui_batches: Query<(Entity, &UiMaterialBatch)>, + mut views: Query<(&ExtractedView, &mut RenderPhase)>, +) where + M::Data: PartialEq + Eq + Hash + Clone, +{ + if let Some(view_binding) = view_uniforms.uniforms.binding() { + ui_meta.view_bind_group = Some(render_device.create_bind_group(&BindGroupDescriptor { + entries: &[BindGroupEntry { + binding: 0, + resource: view_binding, + }], + label: Some("ui_view_bind_group"), + layout: &ui_material_pipeline.view_layout, + })); + let draw_ui_function = draw_functions.read().id::>(); + for (view, mut transparent_phase) in &mut views { + for (entity, batch) in &ui_batches { + if let Some(material) = render_materials.get(&batch.material) { + let pipeline = pipelines.specialize( + &pipeline_cache, + &ui_material_pipeline, + UiMaterialKey { + hdr: view.hdr, + bind_group_data: material.key.clone(), + }, + ); + transparent_phase.add(TransparentUi { + sort_key: FloatOrd(batch.z), + entity, + pipeline, + draw_function: draw_ui_function, + }); + } + } + } + } +} diff --git a/examples/ui/ui_material.rs b/examples/ui/ui_material.rs index acda04c9ccdd7..1a2b78f738e7f 100644 --- a/examples/ui/ui_material.rs +++ b/examples/ui/ui_material.rs @@ -51,6 +51,18 @@ fn setup(mut commands: Commands, mut ui_materials: ResMut Date: Tue, 22 Aug 2023 15:04:43 +0200 Subject: [PATCH 13/55] Apply suggestions from code review Co-authored-by: ickshonpe --- crates/bevy_ui/src/node_bundles.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ui/src/node_bundles.rs b/crates/bevy_ui/src/node_bundles.rs index 061e1d7f0a9a5..86c7f78b5a2b0 100644 --- a/crates/bevy_ui/src/node_bundles.rs +++ b/crates/bevy_ui/src/node_bundles.rs @@ -327,7 +327,7 @@ impl Default for ButtonBundle { } } -/// A Ui Node that renders a shader +/// A UI node that is rendered using a [`UiMaterial`] #[derive(Bundle, Clone, Debug)] pub struct MaterialNodeBundle { /// Describes the logical size of the node @@ -335,7 +335,7 @@ pub struct MaterialNodeBundle { /// 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. + /// The [`UiMaterial`] used to render the node. pub material: Handle, /// Whether this node should block interaction with lower nodes pub focus_policy: FocusPolicy, From 62a51e0df31653bbc344b19c9218062ce74a5f45 Mon Sep 17 00:00:00 2001 From: Markus Ort Date: Tue, 22 Aug 2023 15:24:06 +0200 Subject: [PATCH 14/55] removes export for UiVertex --- crates/bevy_ui/src/render/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index d41fa90c8419d..05637cbb8aedf 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -581,7 +581,7 @@ pub fn extract_text_uinodes( #[repr(C)] #[derive(Copy, Clone, Pod, Zeroable)] -pub struct UiVertex { +struct UiVertex { pub position: [f32; 3], pub uv: [f32; 2], pub color: [f32; 4], From d4327ccb475e980e076e87ab220708b6bd25b5a8 Mon Sep 17 00:00:00 2001 From: Markus Ort Date: Tue, 22 Aug 2023 15:27:51 +0200 Subject: [PATCH 15/55] changed example `ui_material` back to single MaterialNodeBundle --- examples/ui/ui_material.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/examples/ui/ui_material.rs b/examples/ui/ui_material.rs index 1a2b78f738e7f..acda04c9ccdd7 100644 --- a/examples/ui/ui_material.rs +++ b/examples/ui/ui_material.rs @@ -51,18 +51,6 @@ fn setup(mut commands: Commands, mut ui_materials: ResMut Date: Wed, 23 Aug 2023 18:05:08 +0200 Subject: [PATCH 16/55] add `UiVertexOutput` shader export --- assets/shaders/circle_shader.wgsl | 9 ++------- crates/bevy_ui/src/render/material.wgsl | 9 +++++---- examples/ui/ui_material.rs | 2 +- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/assets/shaders/circle_shader.wgsl b/assets/shaders/circle_shader.wgsl index 470b469cab49b..e6764eec7c6f2 100644 --- a/assets/shaders/circle_shader.wgsl +++ b/assets/shaders/circle_shader.wgsl @@ -1,11 +1,6 @@ // This shader draws a circular progress bar #import bevy_render::view View - -struct VertexOutput { - @location(0) uv: vec2, - @location(1) color: vec4, - @builtin(position) position: vec4, -}; +#import bevy_ui::ui_vertex_output UiVertexOutput struct CustomUiMaterial { @location(0) fill_amount: f32, @@ -25,7 +20,7 @@ const PI = 3.141592656; const TAU = 6.283185312; @fragment -fn fragment(in: VertexOutput) -> @location(0) vec4 { +fn fragment(in: UiVertexOutput) -> @location(0) vec4 { let fill_amount = input.fill_amount; let fill_angle = fill_amount * TAU; let uv = in.uv * 2.0 - 1.0; diff --git a/crates/bevy_ui/src/render/material.wgsl b/crates/bevy_ui/src/render/material.wgsl index 09a4dd8cd53d3..65b5afe475ce7 100644 --- a/crates/bevy_ui/src/render/material.wgsl +++ b/crates/bevy_ui/src/render/material.wgsl @@ -1,9 +1,10 @@ #import bevy_render::view View +#define_import_path bevy_ui::ui_vertex_output @group(0) @binding(0) var view: View; -struct VertexOutput { +struct UiVertexOutput { @location(0) uv: vec2, @location(1) color: vec4, @builtin(position) position: vec4, @@ -14,8 +15,8 @@ fn vertex( @location(0) vertex_position: vec3, @location(1) vertex_uv: vec2, @location(2) vertex_color: vec4, -) -> VertexOutput { - var out: VertexOutput; +) -> UiVertexOutput { + var out: UiVertexOutput; out.uv = vertex_uv; out.position = view.view_proj * vec4(vertex_position, 1.0); out.color = vertex_color; @@ -23,6 +24,6 @@ fn vertex( } @fragment -fn fragment(in: VertexOutput) -> @location(0) vec4 { +fn fragment(in: UiVertexOutput) -> @location(0) vec4 { return vec4(1.0); } diff --git a/examples/ui/ui_material.rs b/examples/ui/ui_material.rs index acda04c9ccdd7..8b8c19782c92a 100644 --- a/examples/ui/ui_material.rs +++ b/examples/ui/ui_material.rs @@ -1,4 +1,4 @@ -//! This example illustrates how to create a Node that shows a Material and driver parameters from +//! This example illustrates how to create a Node that shows a Material and drives parameters from //! it use bevy::prelude::*; From 493d3cc159bc0fdee196e72614c5f74625f589dc Mon Sep 17 00:00:00 2001 From: Markus Ort Date: Thu, 24 Aug 2023 20:17:57 +0200 Subject: [PATCH 17/55] move `UiVertexOutput` into its own file (`ui_vertex_output.wgsl`) --- crates/bevy_ui/src/render/material.wgsl | 8 +------- crates/bevy_ui/src/render/material_pipeline.rs | 10 ++++++++++ crates/bevy_ui/src/render/ui_vertex_output.wgsl | 7 +++++++ 3 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 crates/bevy_ui/src/render/ui_vertex_output.wgsl diff --git a/crates/bevy_ui/src/render/material.wgsl b/crates/bevy_ui/src/render/material.wgsl index 65b5afe475ce7..aa75717ec996e 100644 --- a/crates/bevy_ui/src/render/material.wgsl +++ b/crates/bevy_ui/src/render/material.wgsl @@ -1,15 +1,9 @@ #import bevy_render::view View -#define_import_path bevy_ui::ui_vertex_output +#import bevy_ui::ui_vertex_output UiVertexOutput @group(0) @binding(0) var view: View; -struct UiVertexOutput { - @location(0) uv: vec2, - @location(1) color: vec4, - @builtin(position) position: vec4, -}; - @vertex fn vertex( @location(0) vertex_position: vec3, diff --git a/crates/bevy_ui/src/render/material_pipeline.rs b/crates/bevy_ui/src/render/material_pipeline.rs index 9268a2e88b8e7..57c7143662e6e 100644 --- a/crates/bevy_ui/src/render/material_pipeline.rs +++ b/crates/bevy_ui/src/render/material_pipeline.rs @@ -52,6 +52,10 @@ use crate::{ pub const MATERIAL_UI_SHADER_HANDLE: HandleUntyped = HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 10074188772096983955); + +const UI_VERTEX_OUTPUT_SHADER_HANDLE: HandleUntyped = + HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 10123618247720234751); + /// Adds the necessary ECS resources and render logic to enable rendering entities using the given /// [`UiMaterial`] asset type (which includes [`UiMaterial`] types). pub struct UiMaterialPlugin(PhantomData); @@ -67,6 +71,12 @@ where M::Data: PartialEq + Eq + Hash + Clone, { fn build(&self, app: &mut bevy_app::App) { + load_internal_asset!( + app, + UI_VERTEX_OUTPUT_SHADER_HANDLE, + "ui_vertex_output.wgsl", + Shader::from_wgsl + ); load_internal_asset!( app, MATERIAL_UI_SHADER_HANDLE, diff --git a/crates/bevy_ui/src/render/ui_vertex_output.wgsl b/crates/bevy_ui/src/render/ui_vertex_output.wgsl new file mode 100644 index 0000000000000..95da057b8a0ad --- /dev/null +++ b/crates/bevy_ui/src/render/ui_vertex_output.wgsl @@ -0,0 +1,7 @@ +#define_import_path bevy_ui::ui_vertex_output + +struct UiVertexOutput { + @location(0) uv: vec2, + @location(1) color: vec4, + @builtin(position) position: vec4, +}; From f637d7af1b51850f7e372d048f4d18ad330a208b Mon Sep 17 00:00:00 2001 From: Markus Ort Date: Sun, 27 Aug 2023 12:14:10 +0200 Subject: [PATCH 18/55] adds UiMaterialNode marker component --- crates/bevy_ui/src/node_bundles.rs | 5 ++- .../bevy_ui/src/render/material_pipeline.rs | 13 +++++-- crates/bevy_ui/src/render/mod.rs | 34 +++++++++++++++---- 3 files changed, 42 insertions(+), 10 deletions(-) diff --git a/crates/bevy_ui/src/node_bundles.rs b/crates/bevy_ui/src/node_bundles.rs index 86c7f78b5a2b0..3e0eb8d8e03a0 100644 --- a/crates/bevy_ui/src/node_bundles.rs +++ b/crates/bevy_ui/src/node_bundles.rs @@ -5,7 +5,7 @@ use crate::widget::TextFlags; use crate::{ widget::{Button, UiImageSize}, BackgroundColor, BorderColor, ContentSize, FocusPolicy, Interaction, Node, Style, UiImage, - UiMaterial, UiTextureAtlasImage, ZIndex, + UiMaterial, UiTextureAtlasImage, ZIndex, UiMaterialNode, }; use bevy_asset::Handle; use bevy_ecs::bundle::Bundle; @@ -355,6 +355,8 @@ pub struct MaterialNodeBundle { pub computed_visibility: ComputedVisibility, /// Indicates the depth at which the node should appear in the UI pub z_index: ZIndex, + /// Indicates that this node is using the `UiMaterialPipeline` + pub material_node: UiMaterialNode, } impl Default for MaterialNodeBundle { @@ -369,6 +371,7 @@ impl Default for MaterialNodeBundle { visibility: Default::default(), computed_visibility: Default::default(), z_index: Default::default(), + material_node: Default::default() } } } diff --git a/crates/bevy_ui/src/render/material_pipeline.rs b/crates/bevy_ui/src/render/material_pipeline.rs index 57c7143662e6e..ccb050f63c161 100644 --- a/crates/bevy_ui/src/render/material_pipeline.rs +++ b/crates/bevy_ui/src/render/material_pipeline.rs @@ -147,13 +147,20 @@ pub struct UiMaterialVertex { pub color: [f32; 4], } +/// in this [`UiMaterialPipeline`] there is (currently) no batching going on. +/// Therefore the [`UiMaterialBatch`] is more akin to a draw call. #[derive(Component)] pub struct UiMaterialBatch { + /// The range of vertices inside the [`UiMaterialMeta`] pub range: Range, pub material: Handle, - pub z: f32, + pub z: u32, } +/// Marker component that is supposed to make batching easier +#[derive(Component, Default, Clone, Debug)] +pub struct UiMaterialNode; + #[derive(Resource)] pub struct UiMaterialPipeline { pub ui_layout: BindGroupLayout, @@ -503,7 +510,7 @@ pub fn prepare_uimaterial_nodes( commands.spawn(UiMaterialBatch { range: start..end, material: extracted_uinode.material, - z: extracted_uinode.transform.w_axis[2], + z: extracted_uinode.stack_index as u32, }); start = end; } @@ -686,7 +693,7 @@ pub fn queue_ui_material_nodes( }, ); transparent_phase.add(TransparentUi { - sort_key: FloatOrd(batch.z), + sort_key: FloatOrd((batch.z as i16).into()), entity, pipeline, draw_function: draw_ui_function, diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 05637cbb8aedf..3cb653970cb98 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -164,6 +164,7 @@ pub struct ExtractedUiNode { pub clip: Option, pub flip_x: bool, pub flip_y: bool, + pub is_material: bool } #[derive(Resource, Default)] @@ -242,6 +243,7 @@ pub fn extract_atlas_uinodes( atlas_size: Some(atlas_size), flip_x: atlas_image.flip_x, flip_y: atlas_image.flip_y, + is_material: false }); } } @@ -371,6 +373,7 @@ pub fn extract_uinode_borders( clip: clip.map(|clip| clip.clip), flip_x: false, flip_y: false, + is_material: false }); } } @@ -387,21 +390,22 @@ pub fn extract_uinodes( ( &Node, &GlobalTransform, - &BackgroundColor, + Option<&BackgroundColor>, Option<&UiImage>, &ComputedVisibility, Option<&CalculatedClip>, + Option<&UiMaterialNode> ), Without, >, >, ) { for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() { - if let Ok((uinode, transform, color, maybe_image, visibility, clip)) = + if let Ok((uinode, transform, maybe_color, maybe_image, visibility, clip, maybe_material)) = uinode_query.get(*entity) { // Skip invisible and completely transparent nodes - if !visibility.is_visible() || color.0.a() == 0.0 { + if !visibility.is_visible() || maybe_color.is_some_and(|f| f.0.a() == 0.0) { continue; } @@ -414,11 +418,15 @@ pub fn extract_uinodes( } else { (DEFAULT_IMAGE_HANDLE.typed(), false, false) }; - + let color = if let Some(color) = maybe_color { + color.0 + } else { + Color::WHITE + }; extracted_uinodes.uinodes.push(ExtractedUiNode { stack_index, transform: transform.compute_matrix(), - color: color.0, + color, rect: Rect { min: Vec2::ZERO, max: uinode.calculated_size, @@ -428,6 +436,7 @@ pub fn extract_uinodes( atlas_size: None, flip_x, flip_y, + is_material: maybe_material.is_some() }); }; } @@ -573,6 +582,7 @@ pub fn extract_text_uinodes( clip: clip.map(|clip| clip.clip), flip_x: false, flip_y: false, + is_material: false }); } } @@ -640,6 +650,7 @@ pub fn prepare_uinodes( let mut end = 0; let mut current_batch_image = DEFAULT_IMAGE_HANDLE.typed(); let mut last_z = 0.0; + #[inline] fn is_textured(image: &Handle) -> bool { @@ -661,6 +672,17 @@ pub fn prepare_uinodes( } TEXTURED_QUAD } else { + // If a Material based node is encountered, end the current batch and start a new one + // so that the Z-Order is still valid. + if extracted_uinode.is_material { + commands.spawn(UiBatch { + range: start..end, + image: current_batch_image.clone_weak(), + z: last_z + }); + start = end; + continue; + } // Untextured `UiBatch`es are never spawned within the loop. // If all the `extracted_uinodes` are untextured a single untextured UiBatch will be spawned after the loop terminates. UNTEXTURED_QUAD @@ -771,7 +793,7 @@ pub fn prepare_uinodes( }); } - last_z = extracted_uinode.transform.w_axis[2]; + last_z = (extracted_uinode.stack_index as i16).into(); end += QUAD_INDICES.len() as u32; } From 86ae1b862b5b157dcd543d68f69bb5fef959285e Mon Sep 17 00:00:00 2001 From: Markus Ort Date: Sun, 27 Aug 2023 12:19:30 +0200 Subject: [PATCH 19/55] ran cargo fmt --- crates/bevy_ui/src/node_bundles.rs | 4 ++-- crates/bevy_ui/src/render/material_pipeline.rs | 2 +- crates/bevy_ui/src/render/mod.rs | 15 +++++++-------- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/crates/bevy_ui/src/node_bundles.rs b/crates/bevy_ui/src/node_bundles.rs index 5f7ca11212172..4081599750dfc 100644 --- a/crates/bevy_ui/src/node_bundles.rs +++ b/crates/bevy_ui/src/node_bundles.rs @@ -5,7 +5,7 @@ use crate::widget::TextFlags; use crate::{ widget::{Button, UiImageSize}, BackgroundColor, BorderColor, ContentSize, FocusPolicy, Interaction, Node, Style, UiImage, - UiMaterial, UiTextureAtlasImage, ZIndex, UiMaterialNode, + UiMaterial, UiMaterialNode, UiTextureAtlasImage, ZIndex, }; use bevy_asset::Handle; use bevy_ecs::bundle::Bundle; @@ -365,7 +365,7 @@ impl Default for MaterialNodeBundle { visibility: Default::default(), computed_visibility: Default::default(), z_index: Default::default(), - material_node: Default::default() + material_node: Default::default(), } } } diff --git a/crates/bevy_ui/src/render/material_pipeline.rs b/crates/bevy_ui/src/render/material_pipeline.rs index ccb050f63c161..fedec075ee86c 100644 --- a/crates/bevy_ui/src/render/material_pipeline.rs +++ b/crates/bevy_ui/src/render/material_pipeline.rs @@ -53,7 +53,7 @@ use crate::{ pub const MATERIAL_UI_SHADER_HANDLE: HandleUntyped = HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 10074188772096983955); -const UI_VERTEX_OUTPUT_SHADER_HANDLE: HandleUntyped = +const UI_VERTEX_OUTPUT_SHADER_HANDLE: HandleUntyped = HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 10123618247720234751); /// Adds the necessary ECS resources and render logic to enable rendering entities using the given diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 3cb653970cb98..3f80f49c775ec 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -164,7 +164,7 @@ pub struct ExtractedUiNode { pub clip: Option, pub flip_x: bool, pub flip_y: bool, - pub is_material: bool + pub is_material: bool, } #[derive(Resource, Default)] @@ -243,7 +243,7 @@ pub fn extract_atlas_uinodes( atlas_size: Some(atlas_size), flip_x: atlas_image.flip_x, flip_y: atlas_image.flip_y, - is_material: false + is_material: false, }); } } @@ -373,7 +373,7 @@ pub fn extract_uinode_borders( clip: clip.map(|clip| clip.clip), flip_x: false, flip_y: false, - is_material: false + is_material: false, }); } } @@ -394,7 +394,7 @@ pub fn extract_uinodes( Option<&UiImage>, &ComputedVisibility, Option<&CalculatedClip>, - Option<&UiMaterialNode> + Option<&UiMaterialNode>, ), Without, >, @@ -436,7 +436,7 @@ pub fn extract_uinodes( atlas_size: None, flip_x, flip_y, - is_material: maybe_material.is_some() + is_material: maybe_material.is_some(), }); }; } @@ -582,7 +582,7 @@ pub fn extract_text_uinodes( clip: clip.map(|clip| clip.clip), flip_x: false, flip_y: false, - is_material: false + is_material: false, }); } } @@ -650,7 +650,6 @@ pub fn prepare_uinodes( let mut end = 0; let mut current_batch_image = DEFAULT_IMAGE_HANDLE.typed(); let mut last_z = 0.0; - #[inline] fn is_textured(image: &Handle) -> bool { @@ -678,7 +677,7 @@ pub fn prepare_uinodes( commands.spawn(UiBatch { range: start..end, image: current_batch_image.clone_weak(), - z: last_z + z: last_z, }); start = end; continue; From 66a4cd955eb5ddd2e9c418d9337fffc5f20e29ce Mon Sep 17 00:00:00 2001 From: Markus Ort Date: Sun, 27 Aug 2023 13:17:14 +0200 Subject: [PATCH 20/55] Fix crash where a empty UiBatch spawns --- .../bevy_ui/src/render/material_pipeline.rs | 3 ++- crates/bevy_ui/src/render/mod.rs | 24 ++++++++++--------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/crates/bevy_ui/src/render/material_pipeline.rs b/crates/bevy_ui/src/render/material_pipeline.rs index fedec075ee86c..a0be8081f0f5e 100644 --- a/crates/bevy_ui/src/render/material_pipeline.rs +++ b/crates/bevy_ui/src/render/material_pipeline.rs @@ -389,11 +389,12 @@ pub fn extract_material_uinodes( &Handle, &ComputedVisibility, Option<&CalculatedClip>, + &UiMaterialNode, )>, >, ) { for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() { - if let Ok((uinode, transform, handle, visibility, clip)) = uinode_query.get(*entity) { + if let Ok((uinode, transform, handle, visibility, clip, _)) = uinode_query.get(*entity) { if !visibility.is_visible() { continue; } diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 3f80f49c775ec..daeb2e8040391 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -657,6 +657,19 @@ pub fn prepare_uinodes( } for extracted_uinode in extracted_uinodes.uinodes.drain(..) { + // If a Material based node is encountered, end the current batch and start a new one + // so that the Z-Order is still valid. + if extracted_uinode.is_material { + if start != end { + commands.spawn(UiBatch { + range: start..end, + image: current_batch_image.clone_weak(), + z: last_z, + }); + } + start = end; + continue; + } let mode = if is_textured(&extracted_uinode.image) { if current_batch_image.id() != extracted_uinode.image.id() { if is_textured(¤t_batch_image) && start != end { @@ -671,17 +684,6 @@ pub fn prepare_uinodes( } TEXTURED_QUAD } else { - // If a Material based node is encountered, end the current batch and start a new one - // so that the Z-Order is still valid. - if extracted_uinode.is_material { - commands.spawn(UiBatch { - range: start..end, - image: current_batch_image.clone_weak(), - z: last_z, - }); - start = end; - continue; - } // Untextured `UiBatch`es are never spawned within the loop. // If all the `extracted_uinodes` are untextured a single untextured UiBatch will be spawned after the loop terminates. UNTEXTURED_QUAD From d800744e089e116dd267dbd97420d82493369444 Mon Sep 17 00:00:00 2001 From: Markus Ort Date: Sun, 10 Sep 2023 20:03:55 +0200 Subject: [PATCH 21/55] Fix compiler errors, disables lots of functionality --- crates/bevy_ui/src/material.rs | 11 +- crates/bevy_ui/src/node_bundles.rs | 6 +- .../bevy_ui/src/render/material_pipeline.rs | 365 ++++++++++-------- crates/bevy_ui/src/render/mod.rs | 9 +- examples/ui/ui_material.rs | 9 +- 5 files changed, 218 insertions(+), 182 deletions(-) diff --git a/crates/bevy_ui/src/material.rs b/crates/bevy_ui/src/material.rs index 1d193bdf73d34..ebed001952544 100644 --- a/crates/bevy_ui/src/material.rs +++ b/crates/bevy_ui/src/material.rs @@ -1,6 +1,6 @@ use std::hash::Hash; -use bevy_reflect::{TypePath, TypeUuid}; +use bevy_asset::Asset; use bevy_render::render_resource::{AsBindGroup, RenderPipelineDescriptor, ShaderRef}; /// Materials are used alongside `UiMaterialPlugin` and `MaterialNodeBundle` /// to spawn entities that are rendered with a specific [`UiMaterial`] type. They serve as an easy to use high level @@ -18,12 +18,11 @@ use bevy_render::render_resource::{AsBindGroup, RenderPipelineDescriptor, Shader /// ``` /// # use bevy_ui::prelude::*; /// # use bevy_ecs::prelude::*; -/// # use bevy_reflect::{TypeUuid, TypePath}; +/// # use bevy_reflect::TypePath; /// # use bevy_render::{render_resource::{AsBindGroup, ShaderRef}, texture::Image, color::Color}; -/// # use bevy_asset::{Handle, AssetServer, Assets}; +/// # use bevy_asset::{Handle, AssetServer, Assets, Asset}; /// -/// #[derive(AsBindGroup, TypeUuid, TypePath, Debug, Clone)] -/// #[uuid = "af2e2cb1-1764-4fd4-87ad-d6d412955a43"] +/// #[derive(AsBindGroup, Debug, Clone, Asset)] /// 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`. @@ -73,7 +72,7 @@ use bevy_render::render_resource::{AsBindGroup, RenderPipelineDescriptor, Shader /// @group(1) @binding(2) /// var color_sampler: sampler; /// ``` -pub trait UiMaterial: AsBindGroup + Send + Sync + Clone + TypeUuid + TypePath + Sized { +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 { diff --git a/crates/bevy_ui/src/node_bundles.rs b/crates/bevy_ui/src/node_bundles.rs index 6caa1faa48708..a52898835fc7f 100644 --- a/crates/bevy_ui/src/node_bundles.rs +++ b/crates/bevy_ui/src/node_bundles.rs @@ -7,7 +7,7 @@ use crate::{ BackgroundColor, BorderColor, ContentSize, FocusPolicy, Interaction, Node, Style, UiImage, UiMaterial, UiMaterialNode, UiTextureAtlasImage, ZIndex, }; -use bevy_asset::Handle; +use bevy_asset::{AssetId, Handle}; use bevy_ecs::bundle::Bundle; use bevy_render::{ prelude::Color, @@ -355,7 +355,7 @@ pub struct MaterialNodeBundle { /// 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, + pub view_visibility: ViewVisibility, /// Indicates the depth at which the node should appear in the UI pub z_index: ZIndex, /// Indicates that this node is using the `UiMaterialPipeline` @@ -372,7 +372,7 @@ impl Default for MaterialNodeBundle { transform: Default::default(), global_transform: Default::default(), visibility: Default::default(), - computed_visibility: Default::default(), + view_visibility: Default::default(), z_index: Default::default(), material_node: Default::default(), } diff --git a/crates/bevy_ui/src/render/material_pipeline.rs b/crates/bevy_ui/src/render/material_pipeline.rs index a0be8081f0f5e..6331562a108b7 100644 --- a/crates/bevy_ui/src/render/material_pipeline.rs +++ b/crates/bevy_ui/src/render/material_pipeline.rs @@ -2,13 +2,14 @@ use std::{hash::Hash, marker::PhantomData, ops::Range}; use bevy_app::{App, Plugin}; use bevy_asset::{ - load_internal_asset, AddAsset, AssetEvent, AssetServer, Assets, Handle, HandleUntyped, + load_internal_asset, AssetApp, AssetEvent, AssetId, AssetServer, Assets, Handle, }; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ prelude::{Component, Entity, EventReader}, query::ROQueryItem, schedule::IntoSystemConfigs, + storage::SparseSet, system::{ lifetimeless::{Read, SRes}, Commands, Local, Query, Res, ResMut, Resource, SystemParamItem, @@ -16,11 +17,10 @@ use bevy_ecs::{ world::{FromWorld, World}, }; use bevy_math::{Mat4, Rect, Vec2, Vec4Swizzles}; -use bevy_reflect::TypeUuid; use bevy_render::{ extract_component::ExtractComponentPlugin, prelude::Color, - render_asset::{PrepareAssetSet, RenderAssets}, + render_asset::{prepare_assets, RenderAssets}, render_phase::{ AddRenderCommand, DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult, RenderPhase, SetItemPipeline, TrackedRenderPass, @@ -37,12 +37,12 @@ use bevy_render::{ renderer::{RenderDevice, RenderQueue}, texture::{BevyDefault, FallbackImage, Image}, view::{ - ComputedVisibility, ExtractedView, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms, + ExtractedView, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms, ViewVisibility, }, Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; use bevy_transform::prelude::GlobalTransform; -use bevy_utils::{FloatOrd, HashMap, HashSet}; +use bevy_utils::{HashMap, HashSet}; use bytemuck::{Pod, Zeroable}; use crate::{ @@ -50,11 +50,9 @@ use crate::{ QUAD_INDICES, QUAD_VERTEX_POSITIONS, }; -pub const MATERIAL_UI_SHADER_HANDLE: HandleUntyped = - HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 10074188772096983955); +pub const MATERIAL_UI_SHADER_HANDLE: Handle = Handle::weak_from_u128(10074188772096983955); -const UI_VERTEX_OUTPUT_SHADER_HANDLE: HandleUntyped = - HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 10123618247720234751); +const UI_VERTEX_OUTPUT_SHADER_HANDLE: Handle = Handle::weak_from_u128(10123618247720234751); /// Adds the necessary ECS resources and render logic to enable rendering entities using the given /// [`UiMaterial`] asset type (which includes [`UiMaterial`] types). @@ -83,8 +81,7 @@ where "material.wgsl", Shader::from_wgsl ); - - app.add_asset::() + app.init_asset::() .add_plugins(ExtractComponentPlugin::>::extract_visible()); if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { @@ -105,10 +102,8 @@ where .add_systems( Render, ( - prepare_ui_materials:: - .in_set(RenderSet::Prepare) - .after(PrepareAssetSet::PreAssetPrepare), - prepare_uimaterial_nodes::.in_set(RenderSet::Prepare), + prepare_ui_materials::.in_set(RenderSet::PrepareAssets), + prepare_uimaterial_nodes::.in_set(RenderSet::PrepareBindGroups), queue_ui_material_nodes::.in_set(RenderSet::Queue), ), ); @@ -192,13 +187,13 @@ where let mut descriptor = RenderPipelineDescriptor { vertex: VertexState { - shader: MATERIAL_UI_SHADER_HANDLE.typed::(), + shader: MATERIAL_UI_SHADER_HANDLE, entry_point: "vertex".into(), shader_defs: shader_defs.clone(), buffers: vec![vertex_layout], }, fragment: Some(FragmentState { - shader: MATERIAL_UI_SHADER_HANDLE.typed::(), + shader: MATERIAL_UI_SHADER_HANDLE, shader_defs, entry_point: "fragment".into(), targets: vec![Some(ColorTargetState { @@ -330,7 +325,7 @@ impl RenderCommand

) -> RenderCommandResult { let material = materials .into_inner() - .get(&material_handle.material) + .get(&material_handle.material.id()) .unwrap(); pass.set_bind_group(I, &material.bind_group, &[]); RenderCommandResult::Success @@ -361,13 +356,13 @@ pub struct ExtractedUiMaterialNode { pub stack_index: usize, pub transform: Mat4, pub rect: Rect, - pub material: Handle, + pub material: AssetId, pub clip: Option, } #[derive(Resource)] pub struct ExtractedUiMaterialNodes { - pub uinodes: Vec>, + pub uinodes: SparseSet>, } impl Default for ExtractedUiMaterialNodes { @@ -384,18 +379,21 @@ pub fn extract_material_uinodes( ui_stack: Extract>, uinode_query: Extract< Query<( + Entity, &Node, &GlobalTransform, &Handle, - &ComputedVisibility, + &ViewVisibility, Option<&CalculatedClip>, &UiMaterialNode, )>, >, ) { for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() { - if let Ok((uinode, transform, handle, visibility, clip, _)) = uinode_query.get(*entity) { - if !visibility.is_visible() { + if let Ok((entity, uinode, transform, handle, view_visibility, clip, _)) = + uinode_query.get(*entity) + { + if !view_visibility.get() { continue; } @@ -404,16 +402,19 @@ pub fn extract_material_uinodes( continue; } // Skip invisible and completely transparent nodes - extracted_uinodes.uinodes.push(ExtractedUiMaterialNode { - stack_index, - transform: transform.compute_matrix(), - material: handle.clone_weak(), - rect: Rect { - min: Vec2::ZERO, - max: uinode.calculated_size, + extracted_uinodes.uinodes.insert( + entity, + ExtractedUiMaterialNode { + stack_index, + transform: transform.compute_matrix(), + material: handle.id(), + rect: Rect { + min: Vec2::ZERO, + max: uinode.calculated_size, + }, + clip: clip.map(|clip| clip.clip), }, - clip: clip.map(|clip| clip.clip), - }); + ); }; } } @@ -424,102 +425,133 @@ pub fn prepare_uimaterial_nodes( render_queue: Res, mut ui_meta: ResMut>, mut extracted_uinodes: ResMut>, + view_uniforms: Res, + ui_material_pipeline: Res>, + mut phases: Query<&mut RenderPhase>, + mut previous_len: Local, ) { - ui_meta.vertices.clear(); - - extracted_uinodes - .uinodes - .sort_by_key(|node| node.stack_index); - - let mut start = 0; - let mut end = 0; - for extracted_uinode in extracted_uinodes.uinodes.drain(..) { - let uinode_rect = extracted_uinode.rect; - - let rect_size = uinode_rect.size().extend(1.0); - - let positions = QUAD_VERTEX_POSITIONS - .map(|pos| (extracted_uinode.transform * (pos * rect_size).extend(1.)).xyz()); - - let positions_diff = if let Some(clip) = extracted_uinode.clip { - [ - Vec2::new( - f32::max(clip.min.x - positions[0].x, 0.), - f32::max(clip.min.y - positions[0].y, 0.), - ), - Vec2::new( - f32::min(clip.max.x - positions[1].x, 0.), - f32::max(clip.min.y - positions[1].y, 0.), - ), - Vec2::new( - f32::min(clip.max.x - positions[2].x, 0.), - f32::min(clip.max.y - positions[2].y, 0.), - ), - Vec2::new( - f32::max(clip.min.x - positions[3].x, 0.), - f32::min(clip.max.y - positions[3].y, 0.), - ), - ] - } else { - [Vec2::ZERO; 4] - }; + if let Some(view_binding) = view_uniforms.uniforms.binding() { + let mut batches: Vec<(Entity, UiMaterialBatch)> = Vec::with_capacity(*previous_len); - let positions_clipped = [ - positions[0] + positions_diff[0].extend(0.), - positions[1] + positions_diff[1].extend(0.), - positions[2] + positions_diff[2].extend(0.), - positions[3] + positions_diff[3].extend(0.), - ]; + ui_meta.vertices.clear(); + ui_meta.view_bind_group = Some(render_device.create_bind_group(&BindGroupDescriptor { + entries: &[BindGroupEntry { + binding: 0, + resource: view_binding, + }], + label: Some("ui_material_view_bind_group"), + layout: &ui_material_pipeline.view_layout, + })); + let mut index = 0; - let transformed_rect_size = extracted_uinode.transform.transform_vector3(rect_size); + for mut ui_phase in &mut phases { + let mut batch_item_index = 0; + let mut batch_shader_handle = AssetId::invalid(); - if extracted_uinode.transform.x_axis[1] == 0.0 - && positions_diff[0].x - positions_diff[1].x >= transformed_rect_size.x - || positions_diff[1].y - positions_diff[2].y >= transformed_rect_size.y - { - continue; - } - let extent = uinode_rect.max; - let uvs = [ - Vec2::new( - uinode_rect.min.x + positions_diff[0].x, - uinode_rect.min.y + positions_diff[0].y, - ), - Vec2::new( - uinode_rect.max.x + positions_diff[1].x, - uinode_rect.min.y + positions_diff[1].y, - ), - Vec2::new( - uinode_rect.max.x + positions_diff[2].x, - uinode_rect.max.y + positions_diff[2].y, - ), - Vec2::new( - uinode_rect.min.x + positions_diff[3].x, - uinode_rect.max.y + positions_diff[3].y, - ), - ] - .map(|pos| pos / extent); - - for i in QUAD_INDICES { - ui_meta.vertices.push(UiMaterialVertex { - position: positions_clipped[i].into(), - uv: uvs[i].into(), - color: Color::WHITE.into(), - }); + for item_index in 0..ui_phase.items.len() { + let item = &mut ui_phase.items[item_index]; + if let Some(extracted_uinode) = extracted_uinodes.uinodes.get(item.entity) { + let mut existing_batch = batches + .last_mut() + .filter(|_| batch_shader_handle == extracted_uinode.material); + + if existing_batch.is_none() {} + } + } } - end += QUAD_INDICES.len() as u32; - commands.spawn(UiMaterialBatch { - range: start..end, - material: extracted_uinode.material, - z: extracted_uinode.stack_index as u32, - }); - start = end; } + + // let mut start = 0; + // let mut end = 0; + // for (_, extracted_uinode) in extracted_uinodes.uinodes.drain(..) { + // let uinode_rect = extracted_uinode.rect; + // + // let rect_size = uinode_rect.size().extend(1.0); + // + // let positions = QUAD_VERTEX_POSITIONS + // .map(|pos| (extracted_uinode.transform * (pos * rect_size).extend(1.)).xyz()); + // + // let positions_diff = if let Some(clip) = extracted_uinode.clip { + // [ + // Vec2::new( + // f32::max(clip.min.x - positions[0].x, 0.), + // f32::max(clip.min.y - positions[0].y, 0.), + // ), + // Vec2::new( + // f32::min(clip.max.x - positions[1].x, 0.), + // f32::max(clip.min.y - positions[1].y, 0.), + // ), + // Vec2::new( + // f32::min(clip.max.x - positions[2].x, 0.), + // f32::min(clip.max.y - positions[2].y, 0.), + // ), + // Vec2::new( + // f32::max(clip.min.x - positions[3].x, 0.), + // f32::min(clip.max.y - positions[3].y, 0.), + // ), + // ] + // } else { + // [Vec2::ZERO; 4] + // }; + // + // let positions_clipped = [ + // positions[0] + positions_diff[0].extend(0.), + // positions[1] + positions_diff[1].extend(0.), + // positions[2] + positions_diff[2].extend(0.), + // positions[3] + positions_diff[3].extend(0.), + // ]; + // + // let transformed_rect_size = extracted_uinode.transform.transform_vector3(rect_size); + // + // if extracted_uinode.transform.x_axis[1] == 0.0 + // && positions_diff[0].x - positions_diff[1].x >= transformed_rect_size.x + // || positions_diff[1].y - positions_diff[2].y >= transformed_rect_size.y + // { + // continue; + // } + // let extent = uinode_rect.max; + // let uvs = [ + // Vec2::new( + // uinode_rect.min.x + positions_diff[0].x, + // uinode_rect.min.y + positions_diff[0].y, + // ), + // Vec2::new( + // uinode_rect.max.x + positions_diff[1].x, + // uinode_rect.min.y + positions_diff[1].y, + // ), + // Vec2::new( + // uinode_rect.max.x + positions_diff[2].x, + // uinode_rect.max.y + positions_diff[2].y, + // ), + // Vec2::new( + // uinode_rect.min.x + positions_diff[3].x, + // uinode_rect.max.y + positions_diff[3].y, + // ), + // ] + // .map(|pos| pos / extent); + // + // for i in QUAD_INDICES { + // ui_meta.vertices.push(UiMaterialVertex { + // position: positions_clipped[i].into(), + // uv: uvs[i].into(), + // color: Color::WHITE.into(), + // }); + // } + // end += QUAD_INDICES.len() as u32; + // commands.spawn(UiMaterialBatch { + // range: start..end, + // material: extracted_uinode.material, + // z: extracted_uinode.stack_index as u32, + // }); + // start = end; + // } ui_meta.vertices.write_buffer(&render_device, &render_queue); + + extracted_uinodes.uinodes.clear(); } #[derive(Resource, Deref, DerefMut)] -pub struct RenderUiMaterials(HashMap, PreparedUiMaterial>); +pub struct RenderUiMaterials(HashMap, PreparedUiMaterial>); impl Default for RenderUiMaterials { fn default() -> Self { @@ -535,8 +567,8 @@ pub struct PreparedUiMaterial { #[derive(Resource)] pub struct ExtractedUiMaterials { - extracted: Vec<(Handle, M)>, - removed: Vec>, + extracted: Vec<(AssetId, M)>, + removed: Vec>, } impl Default for ExtractedUiMaterials { @@ -555,22 +587,25 @@ pub fn extract_ui_materials( ) { let mut changed_assets = HashSet::default(); let mut removed = Vec::new(); - for event in events.iter() { + for event in events.read() { match event { - AssetEvent::Created { handle } | AssetEvent::Modified { handle } => { - changed_assets.insert(handle.clone_weak()); + AssetEvent::Added { id } | AssetEvent::Modified { id } => { + changed_assets.insert(*id); + } + AssetEvent::Removed { id } => { + changed_assets.remove(id); + removed.push(*id); } - AssetEvent::Removed { handle } => { - changed_assets.remove(handle); - removed.push(handle.clone_weak()); + AssetEvent::LoadedWithDependencies { .. } => { + // not implemented } } } let mut extracted_assets = Vec::new(); - for handle in changed_assets.drain() { - if let Some(asset) = assets.get(&handle) { - extracted_assets.push((handle, asset.clone())); + for id in changed_assets.drain() { + if let Some(asset) = assets.get(id) { + extracted_assets.push((id, asset.clone())); } } @@ -581,7 +616,7 @@ pub fn extract_ui_materials( } pub struct PrepareNextFrameMaterials { - assets: Vec<(Handle, M)>, + assets: Vec<(AssetId, M)>, } impl Default for PrepareNextFrameMaterials { @@ -602,7 +637,7 @@ pub fn prepare_ui_materials( pipeline: Res>, ) { let queued_assets = std::mem::take(&mut prepare_next_frame.assets); - for (handle, material) in queued_assets { + for (id, material) in queued_assets { match prepare_ui_material( &material, &render_device, @@ -611,10 +646,10 @@ pub fn prepare_ui_materials( &pipeline, ) { Ok(prepared_asset) => { - render_materials.insert(handle, prepared_asset); + render_materials.insert(id, prepared_asset); } Err(AsBindGroupError::RetryNextUpdate) => { - prepare_next_frame.assets.push((handle, material)); + prepare_next_frame.assets.push((id, material)); } } } @@ -672,35 +707,39 @@ pub fn queue_ui_material_nodes( ) where M::Data: PartialEq + Eq + Hash + Clone, { - if let Some(view_binding) = view_uniforms.uniforms.binding() { - ui_meta.view_bind_group = Some(render_device.create_bind_group(&BindGroupDescriptor { - entries: &[BindGroupEntry { - binding: 0, - resource: view_binding, - }], - label: Some("ui_view_bind_group"), - layout: &ui_material_pipeline.view_layout, - })); - let draw_ui_function = draw_functions.read().id::>(); - for (view, mut transparent_phase) in &mut views { - for (entity, batch) in &ui_batches { - if let Some(material) = render_materials.get(&batch.material) { - let pipeline = pipelines.specialize( - &pipeline_cache, - &ui_material_pipeline, - UiMaterialKey { - hdr: view.hdr, - bind_group_data: material.key.clone(), - }, - ); - transparent_phase.add(TransparentUi { - sort_key: FloatOrd((batch.z as i16).into()), - entity, - pipeline, - draw_function: draw_ui_function, - }); - } - } - } + if ui_batches.is_empty() { + return; } + + //if let Some(view_binding) = view_uniforms.uniforms.binding() { + // ui_meta.view_bind_group = Some(render_device.create_bind_group(&BindGroupDescriptor { + // entries: &[BindGroupEntry { + // binding: 0, + // resource: view_binding, + // }], + // label: Some("ui_view_bind_group"), + // layout: &ui_material_pipeline.view_layout, + // })); + // let draw_ui_function = draw_functions.read().id::>(); + // for (view, mut transparent_phase) in &mut views { + // for (entity, batch) in &ui_batches { + // if let Some(material) = render_materials.get(&batch.material) { + // let pipeline = pipelines.specialize( + // &pipeline_cache, + // &ui_material_pipeline, + // UiMaterialKey { + // hdr: view.hdr, + // bind_group_data: material.key.clone(), + // }, + // ); + // transparent_phase.add(TransparentUi { + // sort_key: FloatOrd((batch.z as i16).into()), + // entity, + // pipeline, + // draw_function: draw_ui_function, + // }); + // } + // } + // } + //} } diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 420f3a9e7079f..7c52f06161b7a 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -416,17 +416,18 @@ pub fn extract_uinodes( ) { for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() { if let Ok(( + entity, uinode, transform, maybe_color, maybe_image, - visibility, view_visibility, + clip, maybe_material, )) = uinode_query.get(*entity) { // Skip invisible and completely transparent nodes - if !view_visibility.get() || color.0.a() == 0.0 { + if !view_visibility.get() || maybe_color.is_some_and(|f| f.0.a() == 0.0) { continue; } @@ -451,7 +452,7 @@ pub fn extract_uinodes( ExtractedUiNode { stack_index, transform: transform.compute_matrix(), - color: color.0, + color, rect: Rect { min: Vec2::ZERO, max: uinode.calculated_size, @@ -695,7 +696,7 @@ pub fn queue_uinodes( #[derive(Resource, Default)] pub struct UiImageBindGroups { - pub values: HashMap, BindGroup>, + pub values: HashMap, BindGroup>, } #[allow(clippy::too_many_arguments)] diff --git a/examples/ui/ui_material.rs b/examples/ui/ui_material.rs index 8b8c19782c92a..2d2d0bc603666 100644 --- a/examples/ui/ui_material.rs +++ b/examples/ui/ui_material.rs @@ -2,10 +2,8 @@ //! it use bevy::prelude::*; -use bevy_internal::{ - reflect::{TypePath, TypeUuid}, - render::render_resource::*, -}; +use bevy::ui::UiMaterialPlugin; +use bevy_internal::{reflect::TypePath, render::render_resource::*}; fn main() { App::new() @@ -54,8 +52,7 @@ fn setup(mut commands: Commands, mut ui_materials: ResMut Date: Sun, 10 Sep 2023 20:07:02 +0200 Subject: [PATCH 22/55] change `extract_uinodes` query to `Has` --- crates/bevy_ui/src/render/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 7c52f06161b7a..aefc7a619b988 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -408,7 +408,7 @@ pub fn extract_uinodes( Option<&UiImage>, &ViewVisibility, Option<&CalculatedClip>, - Option<&UiMaterialNode>, + Has, ), Without, >, @@ -423,7 +423,7 @@ pub fn extract_uinodes( maybe_image, view_visibility, clip, - maybe_material, + is_material, )) = uinode_query.get(*entity) { // Skip invisible and completely transparent nodes @@ -462,7 +462,7 @@ pub fn extract_uinodes( atlas_size: None, flip_x, flip_y, - is_material: maybe_material.is_some(), + is_material, }, ); }; From 794b72240c6ef6de6b2e91abb44a8ea29b133662 Mon Sep 17 00:00:00 2001 From: Markus Ort Date: Sun, 10 Sep 2023 21:39:45 +0200 Subject: [PATCH 23/55] adds back material rendering capabilities --- .../bevy_ui/src/render/material_pipeline.rs | 339 ++++++++++-------- crates/bevy_ui/src/render/mod.rs | 7 +- 2 files changed, 197 insertions(+), 149 deletions(-) diff --git a/crates/bevy_ui/src/render/material_pipeline.rs b/crates/bevy_ui/src/render/material_pipeline.rs index 6331562a108b7..eea0d00d19f78 100644 --- a/crates/bevy_ui/src/render/material_pipeline.rs +++ b/crates/bevy_ui/src/render/material_pipeline.rs @@ -1,9 +1,7 @@ use std::{hash::Hash, marker::PhantomData, ops::Range}; use bevy_app::{App, Plugin}; -use bevy_asset::{ - load_internal_asset, AssetApp, AssetEvent, AssetId, AssetServer, Assets, Handle, -}; +use bevy_asset::{load_internal_asset, AssetApp, AssetEvent, AssetId, AssetServer, Assets, Handle}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ prelude::{Component, Entity, EventReader}, @@ -22,17 +20,19 @@ use bevy_render::{ prelude::Color, render_asset::{prepare_assets, RenderAssets}, render_phase::{ - AddRenderCommand, DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult, - RenderPhase, SetItemPipeline, TrackedRenderPass, + sort_phase_system, AddRenderCommand, CachedRenderPipelinePhaseItem, DrawFunctionId, + DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult, RenderPhase, SetItemPipeline, + TrackedRenderPass, }, render_resource::{ AsBindGroupError, BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, BlendState, - BufferBindingType, BufferUsages, BufferVec, ColorTargetState, ColorWrites, FragmentState, - FrontFace, MultisampleState, OwnedBindingResource, PipelineCache, PolygonMode, - PrimitiveState, PrimitiveTopology, RenderPipelineDescriptor, Shader, ShaderRef, - ShaderStages, ShaderType, SpecializedRenderPipeline, SpecializedRenderPipelines, - TextureFormat, VertexBufferLayout, VertexFormat, VertexState, VertexStepMode, + BufferBindingType, BufferUsages, BufferVec, CachedRenderPipelineId, ColorTargetState, + ColorWrites, FragmentState, FrontFace, MultisampleState, OwnedBindingResource, + PipelineCache, PolygonMode, PrimitiveState, PrimitiveTopology, RenderPipelineDescriptor, + Shader, ShaderRef, ShaderStages, ShaderType, SpecializedRenderPipeline, + SpecializedRenderPipelines, TextureFormat, VertexBufferLayout, VertexFormat, VertexState, + VertexStepMode, }, renderer::{RenderDevice, RenderQueue}, texture::{BevyDefault, FallbackImage, Image}, @@ -42,7 +42,7 @@ use bevy_render::{ Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; use bevy_transform::prelude::GlobalTransform; -use bevy_utils::{HashMap, HashSet}; +use bevy_utils::{FloatOrd, HashMap, HashSet}; use bytemuck::{Pod, Zeroable}; use crate::{ @@ -86,6 +86,7 @@ where if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app + // .init_resource::>() .add_render_command::>() .init_resource::>() .init_resource::>() @@ -96,15 +97,16 @@ where ExtractSchedule, ( extract_ui_materials::, - extract_material_uinodes::.in_set(RenderUiSystem::ExtractNode), + extract_ui_material_nodes::.in_set(RenderUiSystem::ExtractNode), ), ) .add_systems( Render, ( prepare_ui_materials::.in_set(RenderSet::PrepareAssets), - prepare_uimaterial_nodes::.in_set(RenderSet::PrepareBindGroups), queue_ui_material_nodes::.in_set(RenderSet::Queue), + // sort_phase_system::.in_set(RenderSet::PhaseSort), + prepare_uimaterial_nodes::.in_set(RenderSet::PrepareBindGroups), ), ); } @@ -148,8 +150,7 @@ pub struct UiMaterialVertex { pub struct UiMaterialBatch { /// The range of vertices inside the [`UiMaterialMeta`] pub range: Range, - pub material: Handle, - pub z: u32, + pub material: AssetId, } /// Marker component that is supposed to make batching easier @@ -260,7 +261,6 @@ impl FromWorld for UiMaterialPipeline { }], label: Some("ui_view_layout"), }); - UiMaterialPipeline { ui_layout, view_layout, @@ -325,7 +325,7 @@ impl RenderCommand

) -> RenderCommandResult { let material = materials .into_inner() - .get(&material_handle.material.id()) + .get(&material_handle.material) .unwrap(); pass.set_bind_group(I, &material.bind_group, &[]); RenderCommandResult::Success @@ -373,7 +373,7 @@ impl Default for ExtractedUiMaterialNodes { } } -pub fn extract_material_uinodes( +pub fn extract_ui_material_nodes( mut extracted_uinodes: ResMut>, materials: Extract>>, ui_stack: Extract>, @@ -393,14 +393,13 @@ pub fn extract_material_uinodes( if let Ok((entity, uinode, transform, handle, view_visibility, clip, _)) = uinode_query.get(*entity) { - if !view_visibility.get() { - continue; - } + if !view_visibility.get() {} // Skip loading materials if !materials.contains(handle) { continue; } + // Skip invisible and completely transparent nodes extracted_uinodes.uinodes.insert( entity, @@ -455,98 +454,115 @@ pub fn prepare_uimaterial_nodes( .last_mut() .filter(|_| batch_shader_handle == extracted_uinode.material); - if existing_batch.is_none() {} + if existing_batch.is_none() { + batch_item_index = item_index; + batch_shader_handle = extracted_uinode.material; + + let new_batch = UiMaterialBatch { + range: index..index, + material: extracted_uinode.material, + }; + + batches.push((item.entity, new_batch)); + + existing_batch = batches.last_mut(); + } else { + continue; + } + + let uinode_rect = extracted_uinode.rect; + + let rect_size = uinode_rect.size().extend(1.0); + + let positions = QUAD_VERTEX_POSITIONS.map(|pos| { + (extracted_uinode.transform * (pos * rect_size).extend(1.0)).xyz() + }); + + let positions_diff = if let Some(clip) = extracted_uinode.clip { + [ + Vec2::new( + f32::max(clip.min.x - positions[0].x, 0.), + f32::max(clip.min.y - positions[0].y, 0.), + ), + Vec2::new( + f32::min(clip.max.x - positions[1].x, 0.), + f32::max(clip.min.y - positions[1].y, 0.), + ), + Vec2::new( + f32::min(clip.max.x - positions[2].x, 0.), + f32::min(clip.max.y - positions[2].y, 0.), + ), + Vec2::new( + f32::max(clip.min.x - positions[3].x, 0.), + f32::min(clip.max.y - positions[3].y, 0.), + ), + ] + } else { + [Vec2::ZERO; 4] + }; + + let positions_clipped = [ + positions[0] + positions_diff[0].extend(0.), + positions[1] + positions_diff[1].extend(0.), + positions[2] + positions_diff[2].extend(0.), + positions[3] + positions_diff[3].extend(0.), + ]; + + let transformed_rect_size = + extracted_uinode.transform.transform_vector3(rect_size); + + // Don't try to cull nodes that have a rotation + // In a rotation around the Z-axis, this value is 0.0 for an angle of 0.0 or π + // In those two cases, the culling check can proceed normally as corners will be on + // horizontal / vertical lines + // For all other angles, bypass the culling check + // This does not properly handles all rotations on all axis + if extracted_uinode.transform.x_axis[1] == 0.0 { + // Cull nodes that are completely clipped + if positions_diff[0].x - positions_diff[1].x >= transformed_rect_size.x + || positions_diff[1].y - positions_diff[2].y >= transformed_rect_size.y + { + continue; + } + } + let uvs = [ + Vec2::new( + uinode_rect.min.x + positions_diff[0].x, + uinode_rect.min.y + positions_diff[0].y, + ), + Vec2::new( + uinode_rect.max.x + positions_diff[1].x, + uinode_rect.min.y + positions_diff[1].y, + ), + Vec2::new( + uinode_rect.max.x + positions_diff[2].x, + uinode_rect.max.y + positions_diff[2].y, + ), + Vec2::new( + uinode_rect.min.x + positions_diff[3].x, + uinode_rect.max.y + positions_diff[3].y, + ), + ] + .map(|pos| pos / uinode_rect.max); + for i in QUAD_INDICES { + ui_meta.vertices.push(UiMaterialVertex { + position: positions_clipped[i].into(), + uv: uvs[i].into(), + color: Color::WHITE.as_linear_rgba_f32(), + }); + } + index += QUAD_INDICES.len() as u32; + existing_batch.unwrap().1.range.end = index; + ui_phase.items[batch_item_index].batch_size += 1; + } else { + batch_shader_handle = AssetId::invalid(); } } } + ui_meta.vertices.write_buffer(&render_device, &render_queue); + *previous_len = batches.len(); + commands.insert_or_spawn_batch(batches); } - - // let mut start = 0; - // let mut end = 0; - // for (_, extracted_uinode) in extracted_uinodes.uinodes.drain(..) { - // let uinode_rect = extracted_uinode.rect; - // - // let rect_size = uinode_rect.size().extend(1.0); - // - // let positions = QUAD_VERTEX_POSITIONS - // .map(|pos| (extracted_uinode.transform * (pos * rect_size).extend(1.)).xyz()); - // - // let positions_diff = if let Some(clip) = extracted_uinode.clip { - // [ - // Vec2::new( - // f32::max(clip.min.x - positions[0].x, 0.), - // f32::max(clip.min.y - positions[0].y, 0.), - // ), - // Vec2::new( - // f32::min(clip.max.x - positions[1].x, 0.), - // f32::max(clip.min.y - positions[1].y, 0.), - // ), - // Vec2::new( - // f32::min(clip.max.x - positions[2].x, 0.), - // f32::min(clip.max.y - positions[2].y, 0.), - // ), - // Vec2::new( - // f32::max(clip.min.x - positions[3].x, 0.), - // f32::min(clip.max.y - positions[3].y, 0.), - // ), - // ] - // } else { - // [Vec2::ZERO; 4] - // }; - // - // let positions_clipped = [ - // positions[0] + positions_diff[0].extend(0.), - // positions[1] + positions_diff[1].extend(0.), - // positions[2] + positions_diff[2].extend(0.), - // positions[3] + positions_diff[3].extend(0.), - // ]; - // - // let transformed_rect_size = extracted_uinode.transform.transform_vector3(rect_size); - // - // if extracted_uinode.transform.x_axis[1] == 0.0 - // && positions_diff[0].x - positions_diff[1].x >= transformed_rect_size.x - // || positions_diff[1].y - positions_diff[2].y >= transformed_rect_size.y - // { - // continue; - // } - // let extent = uinode_rect.max; - // let uvs = [ - // Vec2::new( - // uinode_rect.min.x + positions_diff[0].x, - // uinode_rect.min.y + positions_diff[0].y, - // ), - // Vec2::new( - // uinode_rect.max.x + positions_diff[1].x, - // uinode_rect.min.y + positions_diff[1].y, - // ), - // Vec2::new( - // uinode_rect.max.x + positions_diff[2].x, - // uinode_rect.max.y + positions_diff[2].y, - // ), - // Vec2::new( - // uinode_rect.min.x + positions_diff[3].x, - // uinode_rect.max.y + positions_diff[3].y, - // ), - // ] - // .map(|pos| pos / extent); - // - // for i in QUAD_INDICES { - // ui_meta.vertices.push(UiMaterialVertex { - // position: positions_clipped[i].into(), - // uv: uvs[i].into(), - // color: Color::WHITE.into(), - // }); - // } - // end += QUAD_INDICES.len() as u32; - // commands.spawn(UiMaterialBatch { - // range: start..end, - // material: extracted_uinode.material, - // z: extracted_uinode.stack_index as u32, - // }); - // start = end; - // } - ui_meta.vertices.write_buffer(&render_device, &render_queue); - extracted_uinodes.uinodes.clear(); } @@ -694,52 +710,83 @@ fn prepare_ui_material( #[allow(clippy::too_many_arguments)] pub fn queue_ui_material_nodes( + extracted_uinodes: Res>, draw_functions: Res>, - render_device: Res, ui_material_pipeline: Res>, - mut ui_meta: ResMut>, - view_uniforms: Res, mut pipelines: ResMut>>, pipeline_cache: Res, render_materials: Res>, - ui_batches: Query<(Entity, &UiMaterialBatch)>, mut views: Query<(&ExtractedView, &mut RenderPhase)>, ) where M::Data: PartialEq + Eq + Hash + Clone, { - if ui_batches.is_empty() { - return; + let draw_function = draw_functions.read().id::>(); + + for (entity, extraced_uinode) in extracted_uinodes.uinodes.iter() { + let material = render_materials.get(&extraced_uinode.material).unwrap(); + for (view, mut transparent_phase) in &mut views { + let pipeline = pipelines.specialize( + &pipeline_cache, + &ui_material_pipeline, + UiMaterialKey { + hdr: view.hdr, + bind_group_data: material.key.clone(), + }, + ); + transparent_phase + .items + .reserve(extracted_uinodes.uinodes.len()); + transparent_phase.add(TransparentUi { + draw_function, + pipeline, + entity: *entity, + sort_key: FloatOrd(extraced_uinode.stack_index as f32), + batch_size: 0, + }) + } } +} - //if let Some(view_binding) = view_uniforms.uniforms.binding() { - // ui_meta.view_bind_group = Some(render_device.create_bind_group(&BindGroupDescriptor { - // entries: &[BindGroupEntry { - // binding: 0, - // resource: view_binding, - // }], - // label: Some("ui_view_bind_group"), - // layout: &ui_material_pipeline.view_layout, - // })); - // let draw_ui_function = draw_functions.read().id::>(); - // for (view, mut transparent_phase) in &mut views { - // for (entity, batch) in &ui_batches { - // if let Some(material) = render_materials.get(&batch.material) { - // let pipeline = pipelines.specialize( - // &pipeline_cache, - // &ui_material_pipeline, - // UiMaterialKey { - // hdr: view.hdr, - // bind_group_data: material.key.clone(), - // }, - // ); - // transparent_phase.add(TransparentUi { - // sort_key: FloatOrd((batch.z as i16).into()), - // entity, - // pipeline, - // draw_function: draw_ui_function, - // }); - // } - // } - // } - //} +pub struct TransparentMaterialUi { + pub sort_key: FloatOrd, + pub entity: Entity, + pub pipeline: CachedRenderPipelineId, + pub draw_function: DrawFunctionId, + pub batch_size: usize, +} + +impl PhaseItem for TransparentMaterialUi { + type SortKey = FloatOrd; + + #[inline] + fn entity(&self) -> Entity { + self.entity + } + + #[inline] + fn sort_key(&self) -> Self::SortKey { + self.sort_key + } + + #[inline] + fn draw_function(&self) -> DrawFunctionId { + self.draw_function + } + + #[inline] + fn sort(items: &mut [Self]) { + items.sort_by_key(|item| item.sort_key()); + } + + #[inline] + fn batch_size(&self) -> usize { + self.batch_size + } +} + +impl CachedRenderPipelinePhaseItem for TransparentMaterialUi { + #[inline] + fn cached_pipeline(&self) -> CachedRenderPipelineId { + self.pipeline + } } diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index aefc7a619b988..06347014e459d 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -427,7 +427,7 @@ pub fn extract_uinodes( )) = uinode_query.get(*entity) { // Skip invisible and completely transparent nodes - if !view_visibility.get() || maybe_color.is_some_and(|f| f.0.a() == 0.0) { + if !view_visibility.get() || maybe_color.is_some_and(|f| f.0.a() == 0.0) || is_material { continue; } @@ -447,6 +447,7 @@ pub fn extract_uinodes( Color::WHITE }; + extracted_uinodes.uinodes.insert( entity, ExtractedUiNode { @@ -757,7 +758,7 @@ pub fn prepare_uinodes( let mut existing_batch = batches .last_mut() .filter(|_| batch_image_handle == extracted_uinode.image); - + if existing_batch.is_none() { if let Some(gpu_image) = gpu_images.get(extracted_uinode.image) { batch_item_index = item_index; @@ -793,7 +794,7 @@ pub fn prepare_uinodes( layout: &ui_pipeline.image_layout, }) }); - + existing_batch = batches.last_mut(); } else { continue; From 4a150cd0942f762bca762b8cf4ad7c769a829d40 Mon Sep 17 00:00:00 2001 From: Markus Ort Date: Sun, 10 Sep 2023 21:43:18 +0200 Subject: [PATCH 24/55] ran cargo fmt --- crates/bevy_ui/src/render/material_pipeline.rs | 4 ++-- crates/bevy_ui/src/render/mod.rs | 8 ++++---- examples/ui/ui_material.rs | 3 ++- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/crates/bevy_ui/src/render/material_pipeline.rs b/crates/bevy_ui/src/render/material_pipeline.rs index eea0d00d19f78..9a826561ea07e 100644 --- a/crates/bevy_ui/src/render/material_pipeline.rs +++ b/crates/bevy_ui/src/render/material_pipeline.rs @@ -86,7 +86,7 @@ where if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app - // .init_resource::>() + // .init_resource::>() .add_render_command::>() .init_resource::>() .init_resource::>() @@ -105,7 +105,7 @@ where ( prepare_ui_materials::.in_set(RenderSet::PrepareAssets), queue_ui_material_nodes::.in_set(RenderSet::Queue), - // sort_phase_system::.in_set(RenderSet::PhaseSort), + // sort_phase_system::.in_set(RenderSet::PhaseSort), prepare_uimaterial_nodes::.in_set(RenderSet::PrepareBindGroups), ), ); diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 06347014e459d..45fe7d1b3ef3a 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -427,7 +427,8 @@ pub fn extract_uinodes( )) = uinode_query.get(*entity) { // Skip invisible and completely transparent nodes - if !view_visibility.get() || maybe_color.is_some_and(|f| f.0.a() == 0.0) || is_material { + if !view_visibility.get() || maybe_color.is_some_and(|f| f.0.a() == 0.0) || is_material + { continue; } @@ -447,7 +448,6 @@ pub fn extract_uinodes( Color::WHITE }; - extracted_uinodes.uinodes.insert( entity, ExtractedUiNode { @@ -758,7 +758,7 @@ pub fn prepare_uinodes( let mut existing_batch = batches .last_mut() .filter(|_| batch_image_handle == extracted_uinode.image); - + if existing_batch.is_none() { if let Some(gpu_image) = gpu_images.get(extracted_uinode.image) { batch_item_index = item_index; @@ -794,7 +794,7 @@ pub fn prepare_uinodes( layout: &ui_pipeline.image_layout, }) }); - + existing_batch = batches.last_mut(); } else { continue; diff --git a/examples/ui/ui_material.rs b/examples/ui/ui_material.rs index 2d2d0bc603666..64f09479c10cd 100644 --- a/examples/ui/ui_material.rs +++ b/examples/ui/ui_material.rs @@ -2,8 +2,9 @@ //! it use bevy::prelude::*; +use bevy::reflect::TypePath; +use bevy::render::render_resource::*; use bevy::ui::UiMaterialPlugin; -use bevy_internal::{reflect::TypePath, render::render_resource::*}; fn main() { App::new() From c9fcabee4f0d708fb63ece9b5465ee37ca9ce1db Mon Sep 17 00:00:00 2001 From: Markus Ort Date: Sun, 10 Sep 2023 22:01:52 +0200 Subject: [PATCH 25/55] fix compiler warnings --- crates/bevy_ui/src/lib.rs | 2 +- crates/bevy_ui/src/material.rs | 2 +- crates/bevy_ui/src/node_bundles.rs | 2 +- .../bevy_ui/src/render/material_pipeline.rs | 69 +++---------------- examples/ui/ui_material.rs | 1 - 5 files changed, 13 insertions(+), 63 deletions(-) diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index d029ee1b62610..8a4052932a4bf 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -37,7 +37,7 @@ pub mod prelude { #[doc(hidden)] pub use crate::{ camera_config::*, geometry::*, material::*, node_bundles::*, ui_node::*, widget::Button, - widget::Label, Interaction, UiScale, + widget::Label, Interaction, UiScale, UiMaterialPlugin }; } diff --git a/crates/bevy_ui/src/material.rs b/crates/bevy_ui/src/material.rs index ebed001952544..f756be82e0321 100644 --- a/crates/bevy_ui/src/material.rs +++ b/crates/bevy_ui/src/material.rs @@ -22,7 +22,7 @@ use bevy_render::render_resource::{AsBindGroup, RenderPipelineDescriptor, Shader /// # use bevy_render::{render_resource::{AsBindGroup, ShaderRef}, texture::Image, color::Color}; /// # use bevy_asset::{Handle, AssetServer, Assets, Asset}; /// -/// #[derive(AsBindGroup, Debug, Clone, 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`. diff --git a/crates/bevy_ui/src/node_bundles.rs b/crates/bevy_ui/src/node_bundles.rs index a52898835fc7f..640476064076e 100644 --- a/crates/bevy_ui/src/node_bundles.rs +++ b/crates/bevy_ui/src/node_bundles.rs @@ -7,7 +7,7 @@ use crate::{ BackgroundColor, BorderColor, ContentSize, FocusPolicy, Interaction, Node, Style, UiImage, UiMaterial, UiMaterialNode, UiTextureAtlasImage, ZIndex, }; -use bevy_asset::{AssetId, Handle}; +use bevy_asset::{Handle}; use bevy_ecs::bundle::Bundle; use bevy_render::{ prelude::Color, diff --git a/crates/bevy_ui/src/render/material_pipeline.rs b/crates/bevy_ui/src/render/material_pipeline.rs index 9a826561ea07e..a62a78767f0d3 100644 --- a/crates/bevy_ui/src/render/material_pipeline.rs +++ b/crates/bevy_ui/src/render/material_pipeline.rs @@ -18,21 +18,19 @@ use bevy_math::{Mat4, Rect, Vec2, Vec4Swizzles}; use bevy_render::{ extract_component::ExtractComponentPlugin, prelude::Color, - render_asset::{prepare_assets, RenderAssets}, + render_asset::RenderAssets, render_phase::{ - sort_phase_system, AddRenderCommand, CachedRenderPipelinePhaseItem, DrawFunctionId, - DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult, RenderPhase, SetItemPipeline, - TrackedRenderPass, + AddRenderCommand, DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult, + RenderPhase, SetItemPipeline, TrackedRenderPass, }, render_resource::{ AsBindGroupError, BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, BlendState, - BufferBindingType, BufferUsages, BufferVec, CachedRenderPipelineId, ColorTargetState, - ColorWrites, FragmentState, FrontFace, MultisampleState, OwnedBindingResource, - PipelineCache, PolygonMode, PrimitiveState, PrimitiveTopology, RenderPipelineDescriptor, - Shader, ShaderRef, ShaderStages, ShaderType, SpecializedRenderPipeline, - SpecializedRenderPipelines, TextureFormat, VertexBufferLayout, VertexFormat, VertexState, - VertexStepMode, + BufferBindingType, BufferUsages, BufferVec, ColorTargetState, ColorWrites, FragmentState, + FrontFace, MultisampleState, OwnedBindingResource, PipelineCache, PolygonMode, + PrimitiveState, PrimitiveTopology, RenderPipelineDescriptor, Shader, ShaderRef, + ShaderStages, ShaderType, SpecializedRenderPipeline, SpecializedRenderPipelines, + TextureFormat, VertexBufferLayout, VertexFormat, VertexState, VertexStepMode, }, renderer::{RenderDevice, RenderQueue}, texture::{BevyDefault, FallbackImage, Image}, @@ -86,7 +84,6 @@ where if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app - // .init_resource::>() .add_render_command::>() .init_resource::>() .init_resource::>() @@ -105,7 +102,6 @@ where ( prepare_ui_materials::.in_set(RenderSet::PrepareAssets), queue_ui_material_nodes::.in_set(RenderSet::Queue), - // sort_phase_system::.in_set(RenderSet::PhaseSort), prepare_uimaterial_nodes::.in_set(RenderSet::PrepareBindGroups), ), ); @@ -466,9 +462,7 @@ pub fn prepare_uimaterial_nodes( batches.push((item.entity, new_batch)); existing_batch = batches.last_mut(); - } else { - continue; - } + } let uinode_rect = extracted_uinode.rect; @@ -551,6 +545,7 @@ pub fn prepare_uimaterial_nodes( color: Color::WHITE.as_linear_rgba_f32(), }); } + index += QUAD_INDICES.len() as u32; existing_batch.unwrap().1.range.end = index; ui_phase.items[batch_item_index].batch_size += 1; @@ -746,47 +741,3 @@ pub fn queue_ui_material_nodes( } } } - -pub struct TransparentMaterialUi { - pub sort_key: FloatOrd, - pub entity: Entity, - pub pipeline: CachedRenderPipelineId, - pub draw_function: DrawFunctionId, - pub batch_size: usize, -} - -impl PhaseItem for TransparentMaterialUi { - type SortKey = FloatOrd; - - #[inline] - fn entity(&self) -> Entity { - self.entity - } - - #[inline] - fn sort_key(&self) -> Self::SortKey { - self.sort_key - } - - #[inline] - fn draw_function(&self) -> DrawFunctionId { - self.draw_function - } - - #[inline] - fn sort(items: &mut [Self]) { - items.sort_by_key(|item| item.sort_key()); - } - - #[inline] - fn batch_size(&self) -> usize { - self.batch_size - } -} - -impl CachedRenderPipelinePhaseItem for TransparentMaterialUi { - #[inline] - fn cached_pipeline(&self) -> CachedRenderPipelineId { - self.pipeline - } -} diff --git a/examples/ui/ui_material.rs b/examples/ui/ui_material.rs index 64f09479c10cd..36b3eb1af8d28 100644 --- a/examples/ui/ui_material.rs +++ b/examples/ui/ui_material.rs @@ -4,7 +4,6 @@ use bevy::prelude::*; use bevy::reflect::TypePath; use bevy::render::render_resource::*; -use bevy::ui::UiMaterialPlugin; fn main() { App::new() From 193197a3672fd6571b94dd8f0e0f704e1efca59c Mon Sep 17 00:00:00 2001 From: Markus Ort Date: Sun, 10 Sep 2023 22:03:34 +0200 Subject: [PATCH 26/55] ran cargo fmt --- crates/bevy_ui/src/lib.rs | 2 +- crates/bevy_ui/src/node_bundles.rs | 2 +- crates/bevy_ui/src/render/material_pipeline.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index 8a4052932a4bf..9dee147609f24 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -37,7 +37,7 @@ pub mod prelude { #[doc(hidden)] pub use crate::{ camera_config::*, geometry::*, material::*, node_bundles::*, ui_node::*, widget::Button, - widget::Label, Interaction, UiScale, UiMaterialPlugin + widget::Label, Interaction, UiMaterialPlugin, UiScale, }; } diff --git a/crates/bevy_ui/src/node_bundles.rs b/crates/bevy_ui/src/node_bundles.rs index 640476064076e..a86efe6b9f529 100644 --- a/crates/bevy_ui/src/node_bundles.rs +++ b/crates/bevy_ui/src/node_bundles.rs @@ -7,7 +7,7 @@ use crate::{ BackgroundColor, BorderColor, ContentSize, FocusPolicy, Interaction, Node, Style, UiImage, UiMaterial, UiMaterialNode, UiTextureAtlasImage, ZIndex, }; -use bevy_asset::{Handle}; +use bevy_asset::Handle; use bevy_ecs::bundle::Bundle; use bevy_render::{ prelude::Color, diff --git a/crates/bevy_ui/src/render/material_pipeline.rs b/crates/bevy_ui/src/render/material_pipeline.rs index a62a78767f0d3..5a30aef4a83ef 100644 --- a/crates/bevy_ui/src/render/material_pipeline.rs +++ b/crates/bevy_ui/src/render/material_pipeline.rs @@ -462,7 +462,7 @@ pub fn prepare_uimaterial_nodes( batches.push((item.entity, new_batch)); existing_batch = batches.last_mut(); - } + } let uinode_rect = extracted_uinode.rect; From d2fbd1db4f6737dc4ba4fd5d182ff46c781aa364 Mon Sep 17 00:00:00 2001 From: Markus Ort Date: Sun, 10 Sep 2023 22:30:45 +0200 Subject: [PATCH 27/55] improve docs surrounding `UiMaterial` --- crates/bevy_ui/src/material.rs | 4 ++-- crates/bevy_ui/src/render/material_pipeline.rs | 10 ++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/crates/bevy_ui/src/material.rs b/crates/bevy_ui/src/material.rs index f756be82e0321..8125205463292 100644 --- a/crates/bevy_ui/src/material.rs +++ b/crates/bevy_ui/src/material.rs @@ -2,14 +2,14 @@ use std::hash::Hash; use bevy_asset::Asset; use bevy_render::render_resource::{AsBindGroup, RenderPipelineDescriptor, ShaderRef}; -/// Materials are used alongside `UiMaterialPlugin` and `MaterialNodeBundle` +/// Materials are used alongside [`UiMaterialPlugin`](bevy_ui::UiMaterialPipeline) and [`MaterialNodeBundle`](bevy_ui::MaterialNodeBundle) /// 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 [`TypeUuid`] so they can be treated as an [`Asset`](bevy_asset::Asset). +/// Materials must also implement [`Asset`] so they can be treated as such. /// /// # Example /// diff --git a/crates/bevy_ui/src/render/material_pipeline.rs b/crates/bevy_ui/src/render/material_pipeline.rs index 5a30aef4a83ef..6390340277041 100644 --- a/crates/bevy_ui/src/render/material_pipeline.rs +++ b/crates/bevy_ui/src/render/material_pipeline.rs @@ -153,6 +153,7 @@ pub struct UiMaterialBatch { #[derive(Component, Default, Clone, Debug)] pub struct UiMaterialNode; +/// Render pipeline data for a given [`UiMaterial`] #[derive(Resource)] pub struct UiMaterialPipeline { pub ui_layout: BindGroupLayout, @@ -386,11 +387,7 @@ pub fn extract_ui_material_nodes( >, ) { for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() { - if let Ok((entity, uinode, transform, handle, view_visibility, clip, _)) = - uinode_query.get(*entity) - { - if !view_visibility.get() {} - + if let Ok((entity, uinode, transform, handle, _, clip, _)) = uinode_query.get(*entity) { // Skip loading materials if !materials.contains(handle) { continue; @@ -414,6 +411,7 @@ pub fn extract_ui_material_nodes( } } +#[allow(clippy::too_many_arguments)] pub fn prepare_uimaterial_nodes( mut commands: Commands, render_device: Res, @@ -737,7 +735,7 @@ pub fn queue_ui_material_nodes( entity: *entity, sort_key: FloatOrd(extraced_uinode.stack_index as f32), batch_size: 0, - }) + }); } } } From 930ec952543b4d85a6c2439a1afba784fde0dde0 Mon Sep 17 00:00:00 2001 From: Markus Ort Date: Sun, 10 Sep 2023 22:42:33 +0200 Subject: [PATCH 28/55] fix typo in doc comment --- crates/bevy_ui/src/material.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ui/src/material.rs b/crates/bevy_ui/src/material.rs index 8125205463292..40e723624bd4a 100644 --- a/crates/bevy_ui/src/material.rs +++ b/crates/bevy_ui/src/material.rs @@ -2,7 +2,7 @@ use std::hash::Hash; use bevy_asset::Asset; use bevy_render::render_resource::{AsBindGroup, RenderPipelineDescriptor, ShaderRef}; -/// Materials are used alongside [`UiMaterialPlugin`](bevy_ui::UiMaterialPipeline) and [`MaterialNodeBundle`](bevy_ui::MaterialNodeBundle) +/// Materials are used alongside [`UiMaterialPlugin`](crate::UiMaterialPipeline) and [`MaterialNodeBundle`](crate::prelude::MaterialNodeBundle) /// 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. /// From fef7fac8cd2b422c437c2836d45c16a3b435b66d Mon Sep 17 00:00:00 2001 From: Markus Ort Date: Mon, 11 Sep 2023 10:22:59 +0200 Subject: [PATCH 29/55] remove `is_material` from `ExtracedUiNode` --- crates/bevy_ui/src/render/mod.rs | 5 --- examples/ui/ui_material.rs | 71 +++++++++++++++++++++++--------- 2 files changed, 52 insertions(+), 24 deletions(-) diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 45fe7d1b3ef3a..f5f66d99caf56 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -161,7 +161,6 @@ pub struct ExtractedUiNode { pub clip: Option, pub flip_x: bool, pub flip_y: bool, - pub is_material: bool, } #[derive(Resource, Default)] @@ -251,7 +250,6 @@ pub fn extract_atlas_uinodes( atlas_size: Some(atlas_size), flip_x: atlas_image.flip_x, flip_y: atlas_image.flip_y, - is_material: false, }, ); } @@ -385,7 +383,6 @@ pub fn extract_uinode_borders( clip: clip.map(|clip| clip.clip), flip_x: false, flip_y: false, - is_material: false, }, ); } @@ -463,7 +460,6 @@ pub fn extract_uinodes( atlas_size: None, flip_x, flip_y, - is_material, }, ); }; @@ -613,7 +609,6 @@ pub fn extract_text_uinodes( clip: clip.map(|clip| clip.clip), flip_x: false, flip_y: false, - is_material: false, }, ); } diff --git a/examples/ui/ui_material.rs b/examples/ui/ui_material.rs index 36b3eb1af8d28..d886e52cf365e 100644 --- a/examples/ui/ui_material.rs +++ b/examples/ui/ui_material.rs @@ -1,9 +1,5 @@ -//! This example illustrates how to create a Node that shows a Material and drives parameters from -//! it - use bevy::prelude::*; -use bevy::reflect::TypePath; -use bevy::render::render_resource::*; +use bevy_internal::{reflect::TypePath, render::render_resource::*}; fn main() { App::new() @@ -17,7 +13,7 @@ fn main() { fn update(time: Res

for SetMatUiViewBindGroup { - type Param = SRes>; + type Param = SRes; type ViewWorldQuery = Read; type ItemWorldQuery = (); @@ -327,7 +325,7 @@ impl RenderCommand

pub struct DrawUiMaterialNode(PhantomData); impl RenderCommand

for DrawUiMaterialNode { - type Param = SRes>; + type Param = SRes; type ViewWorldQuery = (); type ItemWorldQuery = Read>; @@ -449,7 +447,7 @@ pub fn prepare_uimaterial_nodes( mut commands: Commands, render_device: Res, render_queue: Res, - mut ui_meta: ResMut>, + mut ui_meta: ResMut, mut extracted_uinodes: ResMut>, view_uniforms: Res, ui_material_pipeline: Res>, From 349af3c5927632d394909fe56843cd8a466ee95d Mon Sep 17 00:00:00 2001 From: Markus Ort Date: Sat, 21 Oct 2023 19:20:00 +0200 Subject: [PATCH 52/55] fix compile errors, fix wgsl imports --- assets/shaders/circle_shader.wgsl | 4 +++- crates/bevy_ui/src/render/ui_material.wgsl | 4 ++-- .../src/render/ui_material_pipeline.rs | 24 +++++++++---------- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/assets/shaders/circle_shader.wgsl b/assets/shaders/circle_shader.wgsl index 3ad26ca970e53..219d07359965e 100644 --- a/assets/shaders/circle_shader.wgsl +++ b/assets/shaders/circle_shader.wgsl @@ -1,5 +1,7 @@ // This shader draws a circle with a given input color -#import bevy_ui::ui_vertex_output UiVertexOutput +//#import bevy_ui::ui_vertex_output UiVertexOutput + +#import bevy_ui::ui_vertex_output::UiVertexOutput struct CustomUiMaterial { @location(0) color: vec4 diff --git a/crates/bevy_ui/src/render/ui_material.wgsl b/crates/bevy_ui/src/render/ui_material.wgsl index 20014b0091e6d..db9628559de36 100644 --- a/crates/bevy_ui/src/render/ui_material.wgsl +++ b/crates/bevy_ui/src/render/ui_material.wgsl @@ -1,5 +1,5 @@ -#import bevy_render::view View -#import bevy_ui::ui_vertex_output UiVertexOutput +#import bevy_render::view::View +#import bevy_ui::ui_vertex_output::UiVertexOutput @group(0) @binding(0) var view: View; diff --git a/crates/bevy_ui/src/render/ui_material_pipeline.rs b/crates/bevy_ui/src/render/ui_material_pipeline.rs index 0789732bc0068..d8764ee415947 100644 --- a/crates/bevy_ui/src/render/ui_material_pipeline.rs +++ b/crates/bevy_ui/src/render/ui_material_pipeline.rs @@ -23,13 +23,13 @@ use bevy_render::{ RenderPhase, SetItemPipeline, TrackedRenderPass, }, render_resource::{ - AsBindGroupError, BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, - BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, BlendState, - BufferBindingType, BufferUsages, BufferVec, ColorTargetState, ColorWrites, FragmentState, - FrontFace, MultisampleState, OwnedBindingResource, PipelineCache, PolygonMode, - PrimitiveState, PrimitiveTopology, RenderPipelineDescriptor, Shader, ShaderRef, - ShaderStages, ShaderType, SpecializedRenderPipeline, SpecializedRenderPipelines, - TextureFormat, VertexBufferLayout, VertexFormat, VertexState, VertexStepMode, + AsBindGroupError, BindGroup, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, + BindGroupLayoutEntry, BindingType, BlendState, BufferBindingType, BufferUsages, BufferVec, + ColorTargetState, ColorWrites, FragmentState, FrontFace, MultisampleState, + OwnedBindingResource, PipelineCache, PolygonMode, PrimitiveState, PrimitiveTopology, + RenderPipelineDescriptor, Shader, ShaderRef, ShaderStages, ShaderType, + SpecializedRenderPipeline, SpecializedRenderPipelines, TextureFormat, VertexBufferLayout, + VertexFormat, VertexState, VertexStepMode, }, renderer::{RenderDevice, RenderQueue}, texture::{BevyDefault, FallbackImage, Image}, @@ -458,14 +458,14 @@ pub fn prepare_uimaterial_nodes( let mut batches: Vec<(Entity, UiMaterialBatch)> = Vec::with_capacity(*previous_len); ui_meta.vertices.clear(); - ui_meta.view_bind_group = Some(render_device.create_bind_group(&BindGroupDescriptor { - entries: &[BindGroupEntry { + ui_meta.view_bind_group = Some(render_device.create_bind_group( + "ui_material_view_bind_group", + &ui_material_pipeline.view_layout, + &[BindGroupEntry { binding: 0, resource: view_binding, }], - label: Some("ui_material_view_bind_group"), - layout: &ui_material_pipeline.view_layout, - })); + )); let mut index = 0; for mut ui_phase in &mut phases { From d5909babd9d0e86bfc87539a8fa1b157968ccc81 Mon Sep 17 00:00:00 2001 From: Markus Ort Date: Sat, 21 Oct 2023 19:43:33 +0200 Subject: [PATCH 53/55] adopt new bind group API --- crates/bevy_ui/src/render/ui_material_pipeline.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/crates/bevy_ui/src/render/ui_material_pipeline.rs b/crates/bevy_ui/src/render/ui_material_pipeline.rs index d8764ee415947..d6d1326497b36 100644 --- a/crates/bevy_ui/src/render/ui_material_pipeline.rs +++ b/crates/bevy_ui/src/render/ui_material_pipeline.rs @@ -23,7 +23,7 @@ use bevy_render::{ RenderPhase, SetItemPipeline, TrackedRenderPass, }, render_resource::{ - AsBindGroupError, BindGroup, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, + AsBindGroupError, BindGroup, BindGroupEntries, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, BlendState, BufferBindingType, BufferUsages, BufferVec, ColorTargetState, ColorWrites, FragmentState, FrontFace, MultisampleState, OwnedBindingResource, PipelineCache, PolygonMode, PrimitiveState, PrimitiveTopology, @@ -461,10 +461,7 @@ pub fn prepare_uimaterial_nodes( ui_meta.view_bind_group = Some(render_device.create_bind_group( "ui_material_view_bind_group", &ui_material_pipeline.view_layout, - &[BindGroupEntry { - binding: 0, - resource: view_binding, - }], + &BindGroupEntries::single(view_binding), )); let mut index = 0; From c61a2aab70b1e93ca6a244470b7e2f3f24649a51 Mon Sep 17 00:00:00 2001 From: Markus Ort Date: Mon, 23 Oct 2023 18:45:19 +0200 Subject: [PATCH 54/55] remove old import from example shader --- assets/shaders/circle_shader.wgsl | 2 -- 1 file changed, 2 deletions(-) diff --git a/assets/shaders/circle_shader.wgsl b/assets/shaders/circle_shader.wgsl index 219d07359965e..94f7445e08409 100644 --- a/assets/shaders/circle_shader.wgsl +++ b/assets/shaders/circle_shader.wgsl @@ -1,6 +1,4 @@ // This shader draws a circle with a given input color -//#import bevy_ui::ui_vertex_output UiVertexOutput - #import bevy_ui::ui_vertex_output::UiVertexOutput struct CustomUiMaterial { From f8267d147c6c11baf6eb6892dd0675bd979fc46a Mon Sep 17 00:00:00 2001 From: Markus Ort Date: Sat, 28 Oct 2023 21:32:26 +0200 Subject: [PATCH 55/55] apply suggestions from code review --- .../src/render/ui_material_pipeline.rs | 32 ++++--------------- .../bevy_ui/src/render/ui_vertex_output.wgsl | 2 +- crates/bevy_ui/src/ui_material.rs | 2 +- 3 files changed, 9 insertions(+), 27 deletions(-) diff --git a/crates/bevy_ui/src/render/ui_material_pipeline.rs b/crates/bevy_ui/src/render/ui_material_pipeline.rs index d6d1326497b36..f952f726e1aeb 100644 --- a/crates/bevy_ui/src/render/ui_material_pipeline.rs +++ b/crates/bevy_ui/src/render/ui_material_pipeline.rs @@ -1,41 +1,26 @@ use std::{hash::Hash, marker::PhantomData, ops::Range}; use bevy_app::{App, Plugin}; -use bevy_asset::{load_internal_asset, AssetApp, AssetEvent, AssetId, AssetServer, Assets, Handle}; +use bevy_asset::*; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ prelude::{Component, Entity, EventReader}, query::{ROQueryItem, With}, schedule::IntoSystemConfigs, storage::SparseSet, - system::{ - lifetimeless::{Read, SRes}, - Commands, Local, Query, Res, ResMut, Resource, SystemParamItem, - }, + system::lifetimeless::{Read, SRes}, + system::*, world::{FromWorld, World}, }; use bevy_math::{Mat4, Rect, Vec2, Vec4Swizzles}; use bevy_render::{ extract_component::ExtractComponentPlugin, render_asset::RenderAssets, - render_phase::{ - AddRenderCommand, DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult, - RenderPhase, SetItemPipeline, TrackedRenderPass, - }, - render_resource::{ - AsBindGroupError, BindGroup, BindGroupEntries, BindGroupLayout, BindGroupLayoutDescriptor, - BindGroupLayoutEntry, BindingType, BlendState, BufferBindingType, BufferUsages, BufferVec, - ColorTargetState, ColorWrites, FragmentState, FrontFace, MultisampleState, - OwnedBindingResource, PipelineCache, PolygonMode, PrimitiveState, PrimitiveTopology, - RenderPipelineDescriptor, Shader, ShaderRef, ShaderStages, ShaderType, - SpecializedRenderPipeline, SpecializedRenderPipelines, TextureFormat, VertexBufferLayout, - VertexFormat, VertexState, VertexStepMode, - }, + render_phase::*, + render_resource::*, renderer::{RenderDevice, RenderQueue}, texture::{BevyDefault, FallbackImage, Image}, - view::{ - ExtractedView, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms, ViewVisibility, - }, + view::*, Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; use bevy_transform::prelude::GlobalTransform; @@ -43,10 +28,7 @@ use bevy_utils::{FloatOrd, HashMap, HashSet}; use bevy_window::{PrimaryWindow, Window}; use bytemuck::{Pod, Zeroable}; -use crate::{ - resolve_border_thickness, CalculatedClip, Node, RenderUiSystem, Style, TransparentUi, - UiMaterial, UiMaterialKey, UiScale, UiStack, QUAD_INDICES, QUAD_VERTEX_POSITIONS, -}; +use crate::*; pub const UI_MATERIAL_SHADER_HANDLE: Handle = Handle::weak_from_u128(10074188772096983955); diff --git a/crates/bevy_ui/src/render/ui_vertex_output.wgsl b/crates/bevy_ui/src/render/ui_vertex_output.wgsl index 9a355ed4cbf0d..de41c52819c64 100644 --- a/crates/bevy_ui/src/render/ui_vertex_output.wgsl +++ b/crates/bevy_ui/src/render/ui_vertex_output.wgsl @@ -3,7 +3,7 @@ // The Vertex output of the default vertex shader for the Ui Material pipeline. struct UiVertexOutput { @location(0) uv: vec2, - // The size of the borders in UV space. order is Left, Right, Top, Bottom. + // The size of the borders in UV space. Order is Left, Right, Top, Bottom. @location(1) border_widths: vec4, @builtin(position) position: vec4, }; diff --git a/crates/bevy_ui/src/ui_material.rs b/crates/bevy_ui/src/ui_material.rs index e57f7726bce2e..680d4aa4100e6 100644 --- a/crates/bevy_ui/src/ui_material.rs +++ b/crates/bevy_ui/src/ui_material.rs @@ -67,7 +67,7 @@ use bevy_render::render_resource::{AsBindGroup, RenderPipelineDescriptor, Shader /// /// If you only use the fragment shader make sure to import `UiVertexOutput` from /// `bevy_ui::ui_vertex_output` in your wgsl shader. -/// Also note that binding group 0 is always taken with the view matrix. +/// Also note that bind group 0 is always bound to the [`View Uniform`](bevy_render::view::ViewUniform). /// /// ```wgsl /// #import bevy_ui::ui_vertex_output UiVertexOutput