From f2ae66270d08deb882d8960c80246831939a7b73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joscha=20P=C3=BCtz?= <62256001+lynn-lumen@users.noreply.github.com> Date: Wed, 28 Aug 2024 20:29:34 +0200 Subject: [PATCH 01/12] Move line gizmos to its own module --- crates/bevy_gizmos/src/lib.rs | 583 +---------------- .../src/{ => lines}/line_joints.wgsl | 0 crates/bevy_gizmos/src/{ => lines}/lines.wgsl | 0 crates/bevy_gizmos/src/lines/mod.rs | 586 ++++++++++++++++++ .../src/{ => lines}/pipeline_2d.rs | 10 +- .../src/{ => lines}/pipeline_3d.rs | 10 +- 6 files changed, 611 insertions(+), 578 deletions(-) rename crates/bevy_gizmos/src/{ => lines}/line_joints.wgsl (100%) rename crates/bevy_gizmos/src/{ => lines}/lines.wgsl (100%) create mode 100644 crates/bevy_gizmos/src/lines/mod.rs rename crates/bevy_gizmos/src/{ => lines}/pipeline_2d.rs (98%) rename crates/bevy_gizmos/src/{ => lines}/pipeline_3d.rs (98%) diff --git a/crates/bevy_gizmos/src/lib.rs b/crates/bevy_gizmos/src/lib.rs index 3e28516b608b7..166663562246b 100644 --- a/crates/bevy_gizmos/src/lib.rs +++ b/crates/bevy_gizmos/src/lib.rs @@ -39,17 +39,13 @@ pub mod config; pub mod cross; pub mod gizmos; pub mod grid; +pub mod lines; pub mod primitives; pub mod rounded_box; #[cfg(all(feature = "bevy_pbr", feature = "bevy_render"))] pub mod light; -#[cfg(all(feature = "bevy_sprite", feature = "bevy_render"))] -mod pipeline_2d; -#[cfg(all(feature = "bevy_pbr", feature = "bevy_render"))] -mod pipeline_3d; - /// The `bevy_gizmos` prelude. pub mod prelude { #[cfg(feature = "bevy_render")] @@ -69,61 +65,19 @@ pub mod prelude { pub use crate::light::{LightGizmoColor, LightGizmoConfigGroup, ShowLightGizmo}; } -#[cfg(feature = "bevy_render")] -use bevy_ecs::{ - query::ROQueryItem, - system::{ - lifetimeless::{Read, SRes}, - Commands, SystemParamItem, - }, -}; - use bevy_app::{App, FixedFirst, FixedLast, Last, Plugin, RunFixedMainLoop}; -use bevy_asset::{Asset, AssetApp, Assets, Handle}; -use bevy_color::LinearRgba; -#[cfg(feature = "bevy_render")] -use bevy_ecs::component::Component; use bevy_ecs::{ schedule::{IntoSystemConfigs, SystemSet}, - system::{Res, ResMut, Resource}, -}; -use bevy_math::Vec3; -use bevy_reflect::TypePath; -#[cfg(all( - feature = "bevy_render", - any(feature = "bevy_pbr", feature = "bevy_sprite"), -))] -use bevy_render::render_resource::{VertexAttribute, VertexBufferLayout, VertexStepMode}; -#[cfg(feature = "bevy_render")] -use bevy_render::{ - extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin}, - render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets}, - render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass}, - render_resource::{ - binding_types::uniform_buffer, BindGroup, BindGroupEntries, BindGroupLayout, - BindGroupLayoutEntries, Buffer, BufferInitDescriptor, BufferUsages, Shader, ShaderStages, - ShaderType, VertexFormat, - }, - renderer::RenderDevice, - Extract, ExtractSchedule, Render, RenderApp, RenderSet, + system::{Res, ResMut}, }; - use bevy_time::Fixed; -use bevy_utils::TypeIdMap; -#[cfg(feature = "bevy_render")] -use bytemuck::cast_slice; use config::{ - DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore, GizmoLineJoint, + DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore, }; use gizmos::{GizmoStorage, Swap}; #[cfg(all(feature = "bevy_pbr", feature = "bevy_render"))] use light::LightGizmoPlugin; -use std::{any::TypeId, mem}; - -#[cfg(feature = "bevy_render")] -const LINE_SHADER_HANDLE: Handle = Handle::weak_from_u128(7414812689238026784); -#[cfg(feature = "bevy_render")] -const LINE_JOINT_SHADER_HANDLE: Handle = Handle::weak_from_u128(1162780797909187908); +use lines::{AppLineGizmoBuilder, GizmoLinePlugin}; /// A [`Plugin`] that provides an immediate mode drawing api for visual debugging. /// @@ -133,77 +87,17 @@ pub struct GizmoPlugin; impl Plugin for GizmoPlugin { fn build(&self, app: &mut App) { - #[cfg(feature = "bevy_render")] - { - use bevy_asset::load_internal_asset; - load_internal_asset!(app, LINE_SHADER_HANDLE, "lines.wgsl", Shader::from_wgsl); - load_internal_asset!( - app, - LINE_JOINT_SHADER_HANDLE, - "line_joints.wgsl", - Shader::from_wgsl - ); - } - - app.register_type::() + app.add_plugins(GizmoLinePlugin) + .register_type::() .register_type::() - .init_asset::() - .init_resource::() // We insert the Resource GizmoConfigStore into the world implicitly here if it does not exist. .init_gizmo_group::(); #[cfg(feature = "bevy_render")] - app.add_plugins(aabb::AabbGizmoPlugin) - .add_plugins(UniformComponentPlugin::::default()) - .add_plugins(RenderAssetPlugin::::default()); + app.add_plugins(aabb::AabbGizmoPlugin); #[cfg(all(feature = "bevy_pbr", feature = "bevy_render"))] app.add_plugins(LightGizmoPlugin); - - #[cfg(feature = "bevy_render")] - if let Some(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.add_systems( - Render, - prepare_line_gizmo_bind_group.in_set(RenderSet::PrepareBindGroups), - ); - - render_app.add_systems(ExtractSchedule, extract_gizmo_data); - - #[cfg(feature = "bevy_sprite")] - if app.is_plugin_added::() { - app.add_plugins(pipeline_2d::LineGizmo2dPlugin); - } else { - bevy_utils::tracing::warn!("bevy_sprite feature is enabled but bevy_sprite::SpritePlugin was not detected. Are you sure you loaded GizmoPlugin after SpritePlugin?"); - } - #[cfg(feature = "bevy_pbr")] - if app.is_plugin_added::() { - app.add_plugins(pipeline_3d::LineGizmo3dPlugin); - } else { - bevy_utils::tracing::warn!("bevy_pbr feature is enabled but bevy_pbr::PbrPlugin was not detected. Are you sure you loaded GizmoPlugin after PbrPlugin?"); - } - } else { - bevy_utils::tracing::warn!("bevy_render feature is enabled but RenderApp was not detected. Are you sure you loaded GizmoPlugin after RenderPlugin?"); - } - } - - #[cfg(feature = "bevy_render")] - fn finish(&self, app: &mut App) { - let Some(render_app) = app.get_sub_app_mut(RenderApp) else { - return; - }; - - let render_device = render_app.world().resource::(); - let line_layout = render_device.create_bind_group_layout( - "LineGizmoUniform layout", - &BindGroupLayoutEntries::single( - ShaderStages::VERTEX, - uniform_buffer::(true), - ), - ); - - render_app.insert_resource(LineGizmoUniformBindgroupLayout { - layout: line_layout, - }); } } @@ -234,14 +128,10 @@ impl AppGizmoBuilder for App { .get_resource_or_insert_with::(Default::default) .register::(); - let mut handles = self - .world_mut() - .get_resource_or_insert_with::(Default::default); - - handles.list.insert(TypeId::of::(), None); - handles.strip.insert(TypeId::of::(), None); + self.init_line_gizmo_group::(); - self.init_resource::>() + self + .init_resource::>() .init_resource::>() .init_resource::>>() .add_systems( @@ -260,7 +150,6 @@ impl AppGizmoBuilder for App { Last, ( propagate_gizmos::.before(UpdateGizmoMeshes), - update_gizmo_meshes::.in_set(UpdateGizmoMeshes), ), ); @@ -274,6 +163,7 @@ impl AppGizmoBuilder for App { ) -> &mut Self { self.init_gizmo_group::(); + self.world_mut() .get_resource_or_insert_with::(Default::default) .insert(config, group); @@ -282,17 +172,6 @@ impl AppGizmoBuilder for App { } } -/// Holds handles to the line gizmos for each gizmo configuration group -// As `TypeIdMap` iteration order depends on the order of insertions and deletions, this uses -// `Option` to be able to reserve the slot when creating the gizmo configuration group. -// That way iteration order is stable across executions and depends on the order of configuration -// group creation. -#[derive(Resource, Default)] -struct LineGizmoHandles { - list: TypeIdMap>>, - strip: TypeIdMap>>, -} - /// Start a new gizmo clearing context. /// /// Internally this pushes the parent default context into a swap buffer. @@ -361,443 +240,3 @@ pub fn propagate_gizmos( /// System set for updating the rendering meshes for drawing gizmos. #[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)] pub struct UpdateGizmoMeshes; - -/// Prepare gizmos for rendering. -/// -/// This also clears the default `GizmoStorage`. -fn update_gizmo_meshes( - mut line_gizmos: ResMut>, - mut handles: ResMut, - mut storage: ResMut>, - config_store: Res, -) { - if storage.list_positions.is_empty() { - handles.list.insert(TypeId::of::(), None); - } else if let Some(handle) = handles.list.get_mut(&TypeId::of::()) { - if let Some(handle) = handle { - let list = line_gizmos.get_mut(handle.id()).unwrap(); - - list.positions = mem::take(&mut storage.list_positions); - list.colors = mem::take(&mut storage.list_colors); - } else { - let list = LineGizmo { - strip: false, - config_ty: TypeId::of::(), - positions: mem::take(&mut storage.list_positions), - colors: mem::take(&mut storage.list_colors), - joints: GizmoLineJoint::None, - }; - - *handle = Some(line_gizmos.add(list)); - } - } - - let (config, _) = config_store.config::(); - if storage.strip_positions.is_empty() { - handles.strip.insert(TypeId::of::(), None); - } else if let Some(handle) = handles.strip.get_mut(&TypeId::of::()) { - if let Some(handle) = handle { - let strip = line_gizmos.get_mut(handle.id()).unwrap(); - - strip.positions = mem::take(&mut storage.strip_positions); - strip.colors = mem::take(&mut storage.strip_colors); - strip.joints = config.line_joints; - } else { - let strip = LineGizmo { - strip: true, - joints: config.line_joints, - config_ty: TypeId::of::(), - positions: mem::take(&mut storage.strip_positions), - colors: mem::take(&mut storage.strip_colors), - }; - - *handle = Some(line_gizmos.add(strip)); - } - } -} - -#[cfg(feature = "bevy_render")] -fn extract_gizmo_data( - mut commands: Commands, - handles: Extract>, - config: Extract>, -) { - for (group_type_id, handle) in handles.list.iter().chain(handles.strip.iter()) { - let Some((config, _)) = config.get_config_dyn(group_type_id) else { - continue; - }; - - if !config.enabled { - continue; - } - - let Some(handle) = handle else { - continue; - }; - - let joints_resolution = if let GizmoLineJoint::Round(resolution) = config.line_joints { - resolution - } else { - 0 - }; - - commands.spawn(( - LineGizmoUniform { - line_width: config.line_width, - depth_bias: config.depth_bias, - joints_resolution, - #[cfg(feature = "webgl")] - _padding: Default::default(), - }, - (*handle).clone_weak(), - #[cfg(any(feature = "bevy_pbr", feature = "bevy_sprite"))] - config::GizmoMeshConfig::from(config), - )); - } -} - -#[cfg(feature = "bevy_render")] -#[derive(Component, ShaderType, Clone, Copy)] -struct LineGizmoUniform { - line_width: f32, - depth_bias: f32, - // Only used by gizmo line t if the current configs `line_joints` is set to `GizmoLineJoint::Round(_)` - joints_resolution: u32, - /// WebGL2 structs must be 16 byte aligned. - #[cfg(feature = "webgl")] - _padding: f32, -} - -/// A gizmo asset that represents a line. -#[derive(Asset, Debug, Clone, TypePath)] -pub struct LineGizmo { - /// Positions of the gizmo's vertices - pub positions: Vec, - /// Colors of the gizmo's vertices - pub colors: Vec, - /// Whether this gizmo's topology is a line-strip or line-list - pub strip: bool, - /// Whether this gizmo should draw line joints. This is only applicable if the gizmo's topology is line-strip. - pub joints: GizmoLineJoint, - /// The type of the gizmo's configuration group - pub config_ty: TypeId, -} - -#[cfg(feature = "bevy_render")] -#[derive(Debug, Clone)] -struct GpuLineGizmo { - position_buffer: Buffer, - color_buffer: Buffer, - vertex_count: u32, - strip: bool, - joints: GizmoLineJoint, -} - -#[cfg(feature = "bevy_render")] -impl RenderAsset for GpuLineGizmo { - type SourceAsset = LineGizmo; - type Param = SRes; - - fn prepare_asset( - gizmo: Self::SourceAsset, - render_device: &mut SystemParamItem, - ) -> Result> { - let position_buffer_data = cast_slice(&gizmo.positions); - let position_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor { - usage: BufferUsages::VERTEX, - label: Some("LineGizmo Position Buffer"), - contents: position_buffer_data, - }); - - let color_buffer_data = cast_slice(&gizmo.colors); - let color_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor { - usage: BufferUsages::VERTEX, - label: Some("LineGizmo Color Buffer"), - contents: color_buffer_data, - }); - - Ok(GpuLineGizmo { - position_buffer, - color_buffer, - vertex_count: gizmo.positions.len() as u32, - strip: gizmo.strip, - joints: gizmo.joints, - }) - } -} - -#[cfg(feature = "bevy_render")] -#[derive(Resource)] -struct LineGizmoUniformBindgroupLayout { - layout: BindGroupLayout, -} - -#[cfg(feature = "bevy_render")] -#[derive(Resource)] -struct LineGizmoUniformBindgroup { - bindgroup: BindGroup, -} - -#[cfg(feature = "bevy_render")] -fn prepare_line_gizmo_bind_group( - mut commands: Commands, - line_gizmo_uniform_layout: Res, - render_device: Res, - line_gizmo_uniforms: Res>, -) { - if let Some(binding) = line_gizmo_uniforms.uniforms().binding() { - commands.insert_resource(LineGizmoUniformBindgroup { - bindgroup: render_device.create_bind_group( - "LineGizmoUniform bindgroup", - &line_gizmo_uniform_layout.layout, - &BindGroupEntries::single(binding), - ), - }); - } -} - -#[cfg(feature = "bevy_render")] -struct SetLineGizmoBindGroup; -#[cfg(feature = "bevy_render")] -impl RenderCommand

for SetLineGizmoBindGroup { - type Param = SRes; - type ViewQuery = (); - type ItemQuery = Read>; - - #[inline] - fn render<'w>( - _item: &P, - _view: ROQueryItem<'w, Self::ViewQuery>, - uniform_index: Option>, - bind_group: SystemParamItem<'w, '_, Self::Param>, - pass: &mut TrackedRenderPass<'w>, - ) -> RenderCommandResult { - let Some(uniform_index) = uniform_index else { - return RenderCommandResult::Skip; - }; - pass.set_bind_group( - I, - &bind_group.into_inner().bindgroup, - &[uniform_index.index()], - ); - RenderCommandResult::Success - } -} - -#[cfg(feature = "bevy_render")] -struct DrawLineGizmo; -#[cfg(feature = "bevy_render")] -impl RenderCommand

for DrawLineGizmo { - type Param = SRes>; - type ViewQuery = (); - type ItemQuery = Read>; - - #[inline] - fn render<'w>( - _item: &P, - _view: ROQueryItem<'w, Self::ViewQuery>, - handle: Option>, - line_gizmos: SystemParamItem<'w, '_, Self::Param>, - pass: &mut TrackedRenderPass<'w>, - ) -> RenderCommandResult { - let Some(handle) = handle else { - return RenderCommandResult::Skip; - }; - let Some(line_gizmo) = line_gizmos.into_inner().get(handle) else { - return RenderCommandResult::Skip; - }; - - if line_gizmo.vertex_count < 2 { - return RenderCommandResult::Success; - } - - let instances = if line_gizmo.strip { - let item_size = VertexFormat::Float32x3.size(); - let buffer_size = line_gizmo.position_buffer.size() - item_size; - - pass.set_vertex_buffer(0, line_gizmo.position_buffer.slice(..buffer_size)); - pass.set_vertex_buffer(1, line_gizmo.position_buffer.slice(item_size..)); - - let item_size = VertexFormat::Float32x4.size(); - let buffer_size = line_gizmo.color_buffer.size() - item_size; - pass.set_vertex_buffer(2, line_gizmo.color_buffer.slice(..buffer_size)); - pass.set_vertex_buffer(3, line_gizmo.color_buffer.slice(item_size..)); - - u32::max(line_gizmo.vertex_count, 1) - 1 - } else { - pass.set_vertex_buffer(0, line_gizmo.position_buffer.slice(..)); - pass.set_vertex_buffer(1, line_gizmo.color_buffer.slice(..)); - - line_gizmo.vertex_count / 2 - }; - - pass.draw(0..6, 0..instances); - - RenderCommandResult::Success - } -} - -#[cfg(feature = "bevy_render")] -struct DrawLineJointGizmo; -#[cfg(feature = "bevy_render")] -impl RenderCommand

for DrawLineJointGizmo { - type Param = SRes>; - type ViewQuery = (); - type ItemQuery = Read>; - - #[inline] - fn render<'w>( - _item: &P, - _view: ROQueryItem<'w, Self::ViewQuery>, - handle: Option>, - line_gizmos: SystemParamItem<'w, '_, Self::Param>, - pass: &mut TrackedRenderPass<'w>, - ) -> RenderCommandResult { - let Some(handle) = handle else { - return RenderCommandResult::Skip; - }; - let Some(line_gizmo) = line_gizmos.into_inner().get(handle) else { - return RenderCommandResult::Skip; - }; - - if line_gizmo.vertex_count <= 2 || !line_gizmo.strip { - return RenderCommandResult::Success; - }; - - if line_gizmo.joints == GizmoLineJoint::None { - return RenderCommandResult::Success; - }; - - let instances = { - let item_size = VertexFormat::Float32x3.size(); - // position_a - let buffer_size_a = line_gizmo.position_buffer.size() - item_size * 2; - pass.set_vertex_buffer(0, line_gizmo.position_buffer.slice(..buffer_size_a)); - // position_b - let buffer_size_b = line_gizmo.position_buffer.size() - item_size; - pass.set_vertex_buffer( - 1, - line_gizmo.position_buffer.slice(item_size..buffer_size_b), - ); - // position_c - pass.set_vertex_buffer(2, line_gizmo.position_buffer.slice(item_size * 2..)); - - // color - let item_size = VertexFormat::Float32x4.size(); - let buffer_size = line_gizmo.color_buffer.size() - item_size; - // This corresponds to the color of position_b, hence starts from `item_size` - pass.set_vertex_buffer(3, line_gizmo.color_buffer.slice(item_size..buffer_size)); - - u32::max(line_gizmo.vertex_count, 2) - 2 - }; - - let vertices = match line_gizmo.joints { - GizmoLineJoint::None => unreachable!(), - GizmoLineJoint::Miter => 6, - GizmoLineJoint::Round(resolution) => resolution * 3, - GizmoLineJoint::Bevel => 3, - }; - - pass.draw(0..vertices, 0..instances); - - RenderCommandResult::Success - } -} - -#[cfg(all( - feature = "bevy_render", - any(feature = "bevy_pbr", feature = "bevy_sprite") -))] -fn line_gizmo_vertex_buffer_layouts(strip: bool) -> Vec { - use VertexFormat::*; - let mut position_layout = VertexBufferLayout { - array_stride: Float32x3.size(), - step_mode: VertexStepMode::Instance, - attributes: vec![VertexAttribute { - format: Float32x3, - offset: 0, - shader_location: 0, - }], - }; - - let mut color_layout = VertexBufferLayout { - array_stride: Float32x4.size(), - step_mode: VertexStepMode::Instance, - attributes: vec![VertexAttribute { - format: Float32x4, - offset: 0, - shader_location: 2, - }], - }; - - if strip { - vec![ - position_layout.clone(), - { - position_layout.attributes[0].shader_location = 1; - position_layout - }, - color_layout.clone(), - { - color_layout.attributes[0].shader_location = 3; - color_layout - }, - ] - } else { - position_layout.array_stride *= 2; - position_layout.attributes.push(VertexAttribute { - format: Float32x3, - offset: Float32x3.size(), - shader_location: 1, - }); - - color_layout.array_stride *= 2; - color_layout.attributes.push(VertexAttribute { - format: Float32x4, - offset: Float32x4.size(), - shader_location: 3, - }); - - vec![position_layout, color_layout] - } -} - -#[cfg(all( - feature = "bevy_render", - any(feature = "bevy_pbr", feature = "bevy_sprite") -))] -fn line_joint_gizmo_vertex_buffer_layouts() -> Vec { - use VertexFormat::*; - let mut position_layout = VertexBufferLayout { - array_stride: Float32x3.size(), - step_mode: VertexStepMode::Instance, - attributes: vec![VertexAttribute { - format: Float32x3, - offset: 0, - shader_location: 0, - }], - }; - - let color_layout = VertexBufferLayout { - array_stride: Float32x4.size(), - step_mode: VertexStepMode::Instance, - attributes: vec![VertexAttribute { - format: Float32x4, - offset: 0, - shader_location: 3, - }], - }; - - vec![ - position_layout.clone(), - { - position_layout.attributes[0].shader_location = 1; - position_layout.clone() - }, - { - position_layout.attributes[0].shader_location = 2; - position_layout - }, - color_layout.clone(), - ] -} diff --git a/crates/bevy_gizmos/src/line_joints.wgsl b/crates/bevy_gizmos/src/lines/line_joints.wgsl similarity index 100% rename from crates/bevy_gizmos/src/line_joints.wgsl rename to crates/bevy_gizmos/src/lines/line_joints.wgsl diff --git a/crates/bevy_gizmos/src/lines.wgsl b/crates/bevy_gizmos/src/lines/lines.wgsl similarity index 100% rename from crates/bevy_gizmos/src/lines.wgsl rename to crates/bevy_gizmos/src/lines/lines.wgsl diff --git a/crates/bevy_gizmos/src/lines/mod.rs b/crates/bevy_gizmos/src/lines/mod.rs new file mode 100644 index 0000000000000..5ee4f92d141bb --- /dev/null +++ b/crates/bevy_gizmos/src/lines/mod.rs @@ -0,0 +1,586 @@ +//! A module adding line drawing capabilities to gizmos + +use std::{any::TypeId, mem}; + +use bevy_app::{App, Last, Plugin}; +use bevy_asset::{Asset, AssetApp, Assets, Handle}; +use bevy_color::LinearRgba; +use bevy_ecs::{ + component::Component, query::ROQueryItem, schedule::IntoSystemConfigs, system::{lifetimeless::{Read, SRes}, Commands, Res, ResMut, Resource, SystemParamItem} +}; +use bevy_math::Vec3; +use bevy_reflect::TypePath; +use bevy_render::{ + extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin}, render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets}, render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass}, render_resource::{ + binding_types::uniform_buffer, BindGroup, BindGroupEntries, BindGroupLayout, BindGroupLayoutEntries, Buffer, BufferInitDescriptor, BufferUsages, Shader, ShaderStages, ShaderType, VertexAttribute, VertexBufferLayout, VertexFormat, VertexStepMode + }, renderer::RenderDevice, Extract, ExtractSchedule, Render, RenderApp, RenderSet +}; +use bevy_utils::TypeIdMap; +use bytemuck::cast_slice; + +use crate::{ + config::{GizmoConfigGroup, GizmoConfigStore, GizmoLineJoint}, + gizmos::GizmoStorage, UpdateGizmoMeshes, +}; + +#[cfg(feature = "bevy_render")] +const LINE_SHADER_HANDLE: Handle = Handle::weak_from_u128(7414812689238026784); +#[cfg(feature = "bevy_render")] +const LINE_JOINT_SHADER_HANDLE: Handle = Handle::weak_from_u128(1162780797909187908); + +/// A [`Plugin`] that provides an immediate mode line drawing api for visual debugging. +/// +/// Requires to be loaded after [`PbrPlugin`](bevy_pbr::PbrPlugin) or [`SpritePlugin`](bevy_sprite::SpritePlugin). +#[derive(Default)] +pub struct GizmoLinePlugin; + +#[cfg(all(feature = "bevy_sprite", feature = "bevy_render"))] +mod pipeline_2d; +#[cfg(all(feature = "bevy_pbr", feature = "bevy_render"))] +mod pipeline_3d; + +impl Plugin for GizmoLinePlugin { + fn build(&self, app: &mut App) { + #[cfg(feature = "bevy_render")] + { + use bevy_asset::load_internal_asset; + load_internal_asset!(app, LINE_SHADER_HANDLE, "lines.wgsl", Shader::from_wgsl); + load_internal_asset!( + app, + LINE_JOINT_SHADER_HANDLE, + "line_joints.wgsl", + Shader::from_wgsl + ); + } + + app.init_asset::() + .init_resource::(); + + #[cfg(feature = "bevy_render")] + app.add_plugins(UniformComponentPlugin::::default()) + .add_plugins(RenderAssetPlugin::::default()); + + #[cfg(feature = "bevy_render")] + if let Some(render_app) = app.get_sub_app_mut(RenderApp) { + render_app.add_systems( + Render, + prepare_line_gizmo_bind_group.in_set(RenderSet::PrepareBindGroups), + ); + + render_app.add_systems(ExtractSchedule, extract_gizmo_data); + + #[cfg(feature = "bevy_sprite")] + if app.is_plugin_added::() { + app.add_plugins(pipeline_2d::LineGizmo2dPlugin); + } else { + bevy_utils::tracing::warn!("bevy_sprite feature is enabled but bevy_sprite::SpritePlugin was not detected. Are you sure you loaded GizmoPlugin after SpritePlugin?"); + } + #[cfg(feature = "bevy_pbr")] + if app.is_plugin_added::() { + app.add_plugins(pipeline_3d::LineGizmo3dPlugin); + } else { + bevy_utils::tracing::warn!("bevy_pbr feature is enabled but bevy_pbr::PbrPlugin was not detected. Are you sure you loaded GizmoPlugin after PbrPlugin?"); + } + } else { + bevy_utils::tracing::warn!("bevy_render feature is enabled but RenderApp was not detected. Are you sure you loaded GizmoPlugin after RenderPlugin?"); + } + } + + #[cfg(feature = "bevy_render")] + fn finish(&self, app: &mut App) { + let Some(render_app) = app.get_sub_app_mut(RenderApp) else { + return; + }; + + let render_device = render_app.world().resource::(); + let line_layout = render_device.create_bind_group_layout( + "LineGizmoUniform layout", + &BindGroupLayoutEntries::single( + ShaderStages::VERTEX, + uniform_buffer::(true), + ), + ); + + render_app.insert_resource(LineGizmoUniformBindgroupLayout { + layout: line_layout, + }); + } +} + +/// A extension trait adding `App::init_gizmo_group` and `App::insert_gizmo_config`. +pub(crate) trait AppLineGizmoBuilder { + /// Registers [`GizmoConfigGroup`] in the app enabling the use of [Gizmos<Config>](crate::gizmos::Gizmos). + /// + /// Configurations can be set using the [`GizmoConfigStore`] [`Resource`]. + fn init_line_gizmo_group(&mut self) -> &mut Self; +} + +impl AppLineGizmoBuilder for App { + fn init_line_gizmo_group(&mut self) -> &mut Self { + if self.world().contains_resource::>() { + return self; + } + + let mut handles = self + .world_mut() + .get_resource_or_insert_with::(Default::default); + + handles.list.insert(TypeId::of::(), None); + handles.strip.insert(TypeId::of::(), None); + + self.add_systems(Last, update_gizmo_meshes::.in_set(UpdateGizmoMeshes)); + + self + } +} + +/// Holds handles to the line gizmos for each gizmo configuration group +// As `TypeIdMap` iteration order depends on the order of insertions and deletions, this uses +// `Option` to be able to reserve the slot when creating the gizmo configuration group. +// That way iteration order is stable across executions and depends on the order of configuration +// group creation. +#[derive(Resource, Default)] +struct LineGizmoHandles { + list: TypeIdMap>>, + strip: TypeIdMap>>, +} + +/// Prepare gizmos for rendering. +/// +/// This also clears the default `GizmoStorage`. +fn update_gizmo_meshes( + mut line_gizmos: ResMut>, + mut handles: ResMut, + mut storage: ResMut>, + config_store: Res, +) { + if storage.list_positions.is_empty() { + handles.list.insert(TypeId::of::(), None); + } else if let Some(handle) = handles.list.get_mut(&TypeId::of::()) { + if let Some(handle) = handle { + let list = line_gizmos.get_mut(handle.id()).unwrap(); + + list.positions = mem::take(&mut storage.list_positions); + list.colors = mem::take(&mut storage.list_colors); + } else { + let list = LineGizmo { + strip: false, + config_ty: TypeId::of::(), + positions: mem::take(&mut storage.list_positions), + colors: mem::take(&mut storage.list_colors), + joints: GizmoLineJoint::None, + }; + + *handle = Some(line_gizmos.add(list)); + } + } + + let (config, _) = config_store.config::(); + if storage.strip_positions.is_empty() { + handles.strip.insert(TypeId::of::(), None); + } else if let Some(handle) = handles.strip.get_mut(&TypeId::of::()) { + if let Some(handle) = handle { + let strip = line_gizmos.get_mut(handle.id()).unwrap(); + + strip.positions = mem::take(&mut storage.strip_positions); + strip.colors = mem::take(&mut storage.strip_colors); + strip.joints = config.line_joints; + } else { + let strip = LineGizmo { + strip: true, + joints: config.line_joints, + config_ty: TypeId::of::(), + positions: mem::take(&mut storage.strip_positions), + colors: mem::take(&mut storage.strip_colors), + }; + + *handle = Some(line_gizmos.add(strip)); + } + } +} + +#[cfg(feature = "bevy_render")] +fn extract_gizmo_data( + mut commands: Commands, + handles: Extract>, + config: Extract>, +) { + for (group_type_id, handle) in handles.list.iter().chain(handles.strip.iter()) { + let Some((config, _)) = config.get_config_dyn(group_type_id) else { + continue; + }; + + if !config.enabled { + continue; + } + + let Some(handle) = handle else { + continue; + }; + + let joints_resolution = if let GizmoLineJoint::Round(resolution) = config.line_joints { + resolution + } else { + 0 + }; + + commands.spawn(( + LineGizmoUniform { + line_width: config.line_width, + depth_bias: config.depth_bias, + joints_resolution, + #[cfg(feature = "webgl")] + _padding: Default::default(), + }, + (*handle).clone_weak(), + #[cfg(any(feature = "bevy_pbr", feature = "bevy_sprite"))] + crate::config::GizmoMeshConfig::from(config), + )); + } +} + +#[cfg(feature = "bevy_render")] +#[derive(Component, ShaderType, Clone, Copy)] +struct LineGizmoUniform { + line_width: f32, + depth_bias: f32, + // Only used by gizmo line t if the current configs `line_joints` is set to `GizmoLineJoint::Round(_)` + joints_resolution: u32, + /// WebGL2 structs must be 16 byte aligned. + #[cfg(feature = "webgl")] + _padding: f32, +} + +/// A gizmo asset that represents a line. +#[derive(Asset, Debug, Clone, TypePath)] +pub struct LineGizmo { + /// Positions of the gizmo's vertices + pub positions: Vec, + /// Colors of the gizmo's vertices + pub colors: Vec, + /// Whether this gizmo's topology is a line-strip or line-list + pub strip: bool, + /// Whether this gizmo should draw line joints. This is only applicable if the gizmo's topology is line-strip. + pub joints: GizmoLineJoint, + /// The type of the gizmo's configuration group + pub config_ty: TypeId, +} + +#[cfg(feature = "bevy_render")] +#[derive(Debug, Clone)] +struct GpuLineGizmo { + position_buffer: Buffer, + color_buffer: Buffer, + vertex_count: u32, + strip: bool, + joints: GizmoLineJoint, +} + +#[cfg(feature = "bevy_render")] +impl RenderAsset for GpuLineGizmo { + type SourceAsset = LineGizmo; + type Param = SRes; + + fn prepare_asset( + gizmo: Self::SourceAsset, + render_device: &mut SystemParamItem, + ) -> Result> { + let position_buffer_data = cast_slice(&gizmo.positions); + let position_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor { + usage: BufferUsages::VERTEX, + label: Some("LineGizmo Position Buffer"), + contents: position_buffer_data, + }); + + let color_buffer_data = cast_slice(&gizmo.colors); + let color_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor { + usage: BufferUsages::VERTEX, + label: Some("LineGizmo Color Buffer"), + contents: color_buffer_data, + }); + + Ok(GpuLineGizmo { + position_buffer, + color_buffer, + vertex_count: gizmo.positions.len() as u32, + strip: gizmo.strip, + joints: gizmo.joints, + }) + } +} + +#[cfg(feature = "bevy_render")] +#[derive(Resource)] +struct LineGizmoUniformBindgroupLayout { + layout: BindGroupLayout, +} + +#[cfg(feature = "bevy_render")] +#[derive(Resource)] +struct LineGizmoUniformBindgroup { + bindgroup: BindGroup, +} + +#[cfg(feature = "bevy_render")] +fn prepare_line_gizmo_bind_group( + mut commands: Commands, + line_gizmo_uniform_layout: Res, + render_device: Res, + line_gizmo_uniforms: Res>, +) { + if let Some(binding) = line_gizmo_uniforms.uniforms().binding() { + commands.insert_resource(LineGizmoUniformBindgroup { + bindgroup: render_device.create_bind_group( + "LineGizmoUniform bindgroup", + &line_gizmo_uniform_layout.layout, + &BindGroupEntries::single(binding), + ), + }); + } +} + +#[cfg(feature = "bevy_render")] +struct SetLineGizmoBindGroup; +#[cfg(feature = "bevy_render")] +impl RenderCommand

for SetLineGizmoBindGroup { + type Param = SRes; + type ViewQuery = (); + type ItemQuery = Read>; + + #[inline] + fn render<'w>( + _item: &P, + _view: ROQueryItem<'w, Self::ViewQuery>, + uniform_index: Option>, + bind_group: SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, + ) -> RenderCommandResult { + let Some(uniform_index) = uniform_index else { + return RenderCommandResult::Skip; + }; + pass.set_bind_group( + I, + &bind_group.into_inner().bindgroup, + &[uniform_index.index()], + ); + RenderCommandResult::Success + } +} + +#[cfg(feature = "bevy_render")] +struct DrawLineGizmo; +#[cfg(feature = "bevy_render")] +impl RenderCommand

for DrawLineGizmo { + type Param = SRes>; + type ViewQuery = (); + type ItemQuery = Read>; + + #[inline] + fn render<'w>( + _item: &P, + _view: ROQueryItem<'w, Self::ViewQuery>, + handle: Option>, + line_gizmos: SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, + ) -> RenderCommandResult { + let Some(handle) = handle else { + return RenderCommandResult::Skip; + }; + let Some(line_gizmo) = line_gizmos.into_inner().get(handle) else { + return RenderCommandResult::Skip; + }; + + if line_gizmo.vertex_count < 2 { + return RenderCommandResult::Success; + } + + let instances = if line_gizmo.strip { + let item_size = VertexFormat::Float32x3.size(); + let buffer_size = line_gizmo.position_buffer.size() - item_size; + + pass.set_vertex_buffer(0, line_gizmo.position_buffer.slice(..buffer_size)); + pass.set_vertex_buffer(1, line_gizmo.position_buffer.slice(item_size..)); + + let item_size = VertexFormat::Float32x4.size(); + let buffer_size = line_gizmo.color_buffer.size() - item_size; + pass.set_vertex_buffer(2, line_gizmo.color_buffer.slice(..buffer_size)); + pass.set_vertex_buffer(3, line_gizmo.color_buffer.slice(item_size..)); + + u32::max(line_gizmo.vertex_count, 1) - 1 + } else { + pass.set_vertex_buffer(0, line_gizmo.position_buffer.slice(..)); + pass.set_vertex_buffer(1, line_gizmo.color_buffer.slice(..)); + + line_gizmo.vertex_count / 2 + }; + + pass.draw(0..6, 0..instances); + + RenderCommandResult::Success + } +} + +#[cfg(feature = "bevy_render")] +struct DrawLineJointGizmo; +#[cfg(feature = "bevy_render")] +impl RenderCommand

for DrawLineJointGizmo { + type Param = SRes>; + type ViewQuery = (); + type ItemQuery = Read>; + + #[inline] + fn render<'w>( + _item: &P, + _view: ROQueryItem<'w, Self::ViewQuery>, + handle: Option>, + line_gizmos: SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, + ) -> RenderCommandResult { + let Some(handle) = handle else { + return RenderCommandResult::Skip; + }; + let Some(line_gizmo) = line_gizmos.into_inner().get(handle) else { + return RenderCommandResult::Skip; + }; + + if line_gizmo.vertex_count <= 2 || !line_gizmo.strip { + return RenderCommandResult::Success; + }; + + if line_gizmo.joints == GizmoLineJoint::None { + return RenderCommandResult::Success; + }; + + let instances = { + let item_size = VertexFormat::Float32x3.size(); + // position_a + let buffer_size_a = line_gizmo.position_buffer.size() - item_size * 2; + pass.set_vertex_buffer(0, line_gizmo.position_buffer.slice(..buffer_size_a)); + // position_b + let buffer_size_b = line_gizmo.position_buffer.size() - item_size; + pass.set_vertex_buffer( + 1, + line_gizmo.position_buffer.slice(item_size..buffer_size_b), + ); + // position_c + pass.set_vertex_buffer(2, line_gizmo.position_buffer.slice(item_size * 2..)); + + // color + let item_size = VertexFormat::Float32x4.size(); + let buffer_size = line_gizmo.color_buffer.size() - item_size; + // This corresponds to the color of position_b, hence starts from `item_size` + pass.set_vertex_buffer(3, line_gizmo.color_buffer.slice(item_size..buffer_size)); + + u32::max(line_gizmo.vertex_count, 2) - 2 + }; + + let vertices = match line_gizmo.joints { + GizmoLineJoint::None => unreachable!(), + GizmoLineJoint::Miter => 6, + GizmoLineJoint::Round(resolution) => resolution * 3, + GizmoLineJoint::Bevel => 3, + }; + + pass.draw(0..vertices, 0..instances); + + RenderCommandResult::Success + } +} + +#[cfg(all( + feature = "bevy_render", + any(feature = "bevy_pbr", feature = "bevy_sprite") +))] +fn line_gizmo_vertex_buffer_layouts(strip: bool) -> Vec { + use VertexFormat::*; + let mut position_layout = VertexBufferLayout { + array_stride: Float32x3.size(), + step_mode: VertexStepMode::Instance, + attributes: vec![VertexAttribute { + format: Float32x3, + offset: 0, + shader_location: 0, + }], + }; + + let mut color_layout = VertexBufferLayout { + array_stride: Float32x4.size(), + step_mode: VertexStepMode::Instance, + attributes: vec![VertexAttribute { + format: Float32x4, + offset: 0, + shader_location: 2, + }], + }; + + if strip { + vec![ + position_layout.clone(), + { + position_layout.attributes[0].shader_location = 1; + position_layout + }, + color_layout.clone(), + { + color_layout.attributes[0].shader_location = 3; + color_layout + }, + ] + } else { + position_layout.array_stride *= 2; + position_layout.attributes.push(VertexAttribute { + format: Float32x3, + offset: Float32x3.size(), + shader_location: 1, + }); + + color_layout.array_stride *= 2; + color_layout.attributes.push(VertexAttribute { + format: Float32x4, + offset: Float32x4.size(), + shader_location: 3, + }); + + vec![position_layout, color_layout] + } +} + +#[cfg(all( + feature = "bevy_render", + any(feature = "bevy_pbr", feature = "bevy_sprite") +))] +fn line_joint_gizmo_vertex_buffer_layouts() -> Vec { + use VertexFormat::*; + let mut position_layout = VertexBufferLayout { + array_stride: Float32x3.size(), + step_mode: VertexStepMode::Instance, + attributes: vec![VertexAttribute { + format: Float32x3, + offset: 0, + shader_location: 0, + }], + }; + + let color_layout = VertexBufferLayout { + array_stride: Float32x4.size(), + step_mode: VertexStepMode::Instance, + attributes: vec![VertexAttribute { + format: Float32x4, + offset: 0, + shader_location: 3, + }], + }; + + vec![ + position_layout.clone(), + { + position_layout.attributes[0].shader_location = 1; + position_layout.clone() + }, + { + position_layout.attributes[0].shader_location = 2; + position_layout + }, + color_layout.clone(), + ] +} diff --git a/crates/bevy_gizmos/src/pipeline_2d.rs b/crates/bevy_gizmos/src/lines/pipeline_2d.rs similarity index 98% rename from crates/bevy_gizmos/src/pipeline_2d.rs rename to crates/bevy_gizmos/src/lines/pipeline_2d.rs index 0f6552f787406..41ba744c59cf6 100644 --- a/crates/bevy_gizmos/src/pipeline_2d.rs +++ b/crates/bevy_gizmos/src/lines/pipeline_2d.rs @@ -1,9 +1,11 @@ use crate::{ config::{GizmoLineJoint, GizmoLineStyle, GizmoMeshConfig}, + GizmoRenderSystem, +}; +use super::{ line_gizmo_vertex_buffer_layouts, line_joint_gizmo_vertex_buffer_layouts, DrawLineGizmo, - DrawLineJointGizmo, GizmoRenderSystem, GpuLineGizmo, LineGizmo, - LineGizmoUniformBindgroupLayout, SetLineGizmoBindGroup, LINE_JOINT_SHADER_HANDLE, - LINE_SHADER_HANDLE, + DrawLineJointGizmo, GpuLineGizmo, LineGizmo, + LineGizmoUniformBindgroupLayout, SetLineGizmoBindGroup, }; use bevy_app::{App, Plugin}; use bevy_asset::Handle; @@ -30,6 +32,8 @@ use bevy_render::{ use bevy_sprite::{Mesh2dPipeline, Mesh2dPipelineKey, SetMesh2dViewBindGroup}; use bevy_utils::tracing::error; +use super::{LINE_JOINT_SHADER_HANDLE, LINE_SHADER_HANDLE}; + pub struct LineGizmo2dPlugin; impl Plugin for LineGizmo2dPlugin { diff --git a/crates/bevy_gizmos/src/pipeline_3d.rs b/crates/bevy_gizmos/src/lines/pipeline_3d.rs similarity index 98% rename from crates/bevy_gizmos/src/pipeline_3d.rs rename to crates/bevy_gizmos/src/lines/pipeline_3d.rs index 8197623b3618c..bd6bb780e8bb8 100644 --- a/crates/bevy_gizmos/src/pipeline_3d.rs +++ b/crates/bevy_gizmos/src/lines/pipeline_3d.rs @@ -1,9 +1,11 @@ use crate::{ config::{GizmoLineJoint, GizmoLineStyle, GizmoMeshConfig}, + GizmoRenderSystem, +}; +use super::{ line_gizmo_vertex_buffer_layouts, line_joint_gizmo_vertex_buffer_layouts, DrawLineGizmo, - DrawLineJointGizmo, GizmoRenderSystem, GpuLineGizmo, LineGizmo, - LineGizmoUniformBindgroupLayout, SetLineGizmoBindGroup, LINE_JOINT_SHADER_HANDLE, - LINE_SHADER_HANDLE, + DrawLineJointGizmo, GpuLineGizmo, LineGizmo, + LineGizmoUniformBindgroupLayout, SetLineGizmoBindGroup, }; use bevy_app::{App, Plugin}; use bevy_asset::Handle; @@ -33,6 +35,8 @@ use bevy_render::{ }; use bevy_utils::tracing::error; +use super::{LINE_JOINT_SHADER_HANDLE, LINE_SHADER_HANDLE}; + pub struct LineGizmo3dPlugin; impl Plugin for LineGizmo3dPlugin { fn build(&self, app: &mut App) { From 7d518a7156273eefd22290751da5e64234b78627 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joscha=20P=C3=BCtz?= <62256001+lynn-lumen@users.noreply.github.com> Date: Wed, 28 Aug 2024 20:45:18 +0200 Subject: [PATCH 02/12] Add billboard gizmo storage --- crates/bevy_gizmos/src/gizmos.rs | 40 ++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/crates/bevy_gizmos/src/gizmos.rs b/crates/bevy_gizmos/src/gizmos.rs index 50998cc6d8a08..fc962f02afad5 100644 --- a/crates/bevy_gizmos/src/gizmos.rs +++ b/crates/bevy_gizmos/src/gizmos.rs @@ -25,6 +25,8 @@ pub struct GizmoStorage { pub(crate) list_colors: Vec, pub(crate) strip_positions: Vec, pub(crate) strip_colors: Vec, + pub(crate) billboard_positions: Vec, + pub(crate) billboard_colors: Vec, marker: PhantomData<(Config, Clear)>, } @@ -35,6 +37,8 @@ impl Default for GizmoStorage { list_colors: default(), strip_positions: default(), strip_colors: default(), + billboard_positions: default(), + billboard_colors: default(), marker: PhantomData, } } @@ -54,6 +58,8 @@ where self.list_colors.extend(other.list_colors.iter()); self.strip_positions.extend(other.strip_positions.iter()); self.strip_colors.extend(other.strip_colors.iter()); + self.billboard_positions.extend(other.billboard_positions.iter()); + self.billboard_colors.extend(other.billboard_colors.iter()); } pub(crate) fn swap( @@ -64,6 +70,8 @@ where mem::swap(&mut self.list_colors, &mut other.list_colors); mem::swap(&mut self.strip_positions, &mut other.strip_positions); mem::swap(&mut self.strip_colors, &mut other.strip_colors); + mem::swap(&mut self.billboard_positions, &mut other.billboard_positions); + mem::swap(&mut self.billboard_colors, &mut other.billboard_colors); } /// Clear this gizmo storage of any requested gizmos. @@ -72,6 +80,8 @@ where self.list_colors.clear(); self.strip_positions.clear(); self.strip_colors.clear(); + self.billboard_positions.clear(); + self.billboard_colors.clear(); } } @@ -235,6 +245,8 @@ where list_colors: Vec, strip_positions: Vec, strip_colors: Vec, + billboard_positions: Vec, + billboard_colors: Vec, marker: PhantomData<(Config, Clear)>, } @@ -249,6 +261,8 @@ where list_colors: default(), strip_positions: default(), strip_colors: default(), + billboard_positions: default(), + billboard_colors: default(), marker: PhantomData, } } @@ -265,6 +279,8 @@ where storage.list_colors.append(&mut self.list_colors); storage.strip_positions.append(&mut self.strip_positions); storage.strip_colors.append(&mut self.strip_colors); + storage.billboard_positions.append(&mut self.billboard_positions); + storage.billboard_colors.append(&mut self.billboard_colors); } } @@ -273,6 +289,30 @@ where Config: GizmoConfigGroup, Clear: 'static + Send + Sync, { + /// Draw a billboard in 3D at `position`. + /// + /// This should be called for each frame the line needs to be rendered. + /// + /// # Example + /// ``` + /// # use bevy_gizmos::prelude::*; + /// # use bevy_math::prelude::*; + /// # use bevy_color::palettes::basic::GREEN; + /// fn system(mut gizmos: Gizmos) { + /// gizmos.billboard(Vec3::ZERO, GREEN); + /// } + /// # bevy_ecs::system::assert_is_system(system); + /// ``` + pub fn billboard(&mut self, position: Vec3, color: impl Into) { + if !self.enabled { + return; + } + self.buffer.billboard_positions.push(position); + let polymorphic_color: Color = color.into(); + let linear_color = LinearRgba::from(polymorphic_color); + self.buffer.billboard_colors.push(linear_color); + } + /// Draw a line in 3D from `start` to `end`. /// /// This should be called for each frame the line needs to be rendered. From 3a3c2212cd3ea79a563be4410264aff88c049a9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joscha=20P=C3=BCtz?= <62256001+lynn-lumen@users.noreply.github.com> Date: Wed, 28 Aug 2024 21:32:48 +0200 Subject: [PATCH 03/12] Add billboard shader --- .../bevy_gizmos/src/billboard/billboards.wgsl | 90 +++++++++++++++++++ crates/bevy_gizmos/src/billboard/mod.rs | 0 crates/bevy_gizmos/src/lib.rs | 5 +- 3 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 crates/bevy_gizmos/src/billboard/billboards.wgsl create mode 100644 crates/bevy_gizmos/src/billboard/mod.rs diff --git a/crates/bevy_gizmos/src/billboard/billboards.wgsl b/crates/bevy_gizmos/src/billboard/billboards.wgsl new file mode 100644 index 0000000000000..c97aff18584d8 --- /dev/null +++ b/crates/bevy_gizmos/src/billboard/billboards.wgsl @@ -0,0 +1,90 @@ +// TODO use common view binding +#import bevy_render::view::View + +@group(0) @binding(0) var view: View; + + +struct BillboardGizmoUniform { + size: f32, + depth_bias: f32, +#ifdef SIXTEEN_BYTE_ALIGNMENT + // WebGL2 structs must be 16 byte aligned. + _padding: vec2, +#endif +} + +@group(1) @binding(0) var billboard_gizmo: BillboardGizmoUniform; + +struct VertexInput { + @location(0) position: vec3, + @location(1) color: vec4, + @builtin(vertex_index) index: u32, +}; + +struct VertexOutput { + @builtin(position) clip_position: vec4, + @location(0) color: vec4, +}; + +@vertex +fn vertex(vertex: VertexInput) -> VertexOutput { + var positions = array, 6>( + vec2(-0.5, -0.5), + vec2(-0.5, 0.5), + vec2(0.5, 0.5), + vec2(-0.5, -0.5), + vec2(0.5, 0.5), + vec2(0.5, -0.5) + ); + let position = positions[vertex.index]; + + let clip = view.view_proj * vec4(vertex.position, 1.); + + let resolution = view.viewport.zw; + let screen_center = resolution * (0.5 * clip.xy / clip.w + 0.5); + + var color = vertex.color; + + var billboard_size = billboard_gizmo.size; + var alpha = 1.; + +#ifdef PERSPECTIVE + billboard_size /= clip.w; +#endif + + // Billboard size fade based on https://acegikmo.com/shapes/docs/#anti-aliasing + let abs_size = length(billboard_size); + if abs_size < SQRT2 { + color.a *= abs_size / SQRT2; + billboard_size = vec2(1., 1.); + } + + let screen = screen_center + position * billboard_size; + + var depth: f32; + if billboard_gizmo.depth_bias >= 0. { + depth = clip.z * (1. - billboard_gizmo.depth_bias); + } else { + // depth * (clip.w / depth)^-depth_bias. So that when -depth_bias is 1.0, this is equal to clip.w + // and when equal to 0.0, it is exactly equal to depth. + // the epsilon is here to prevent the depth from exceeding clip.w when -depth_bias = 1.0 + // clip.w represents the near plane in homogeneous clip space in bevy, having a depth + // of this value means nothing can be in front of this + // The reason this uses an exponential function is that it makes it much easier for the + // user to chose a value that is convenient for them + depth = clip.z * exp2(-billboard_gizmo.depth_bias * log2(clip.w / clip.z - EPSILON)); + } + + var clip_position = vec4(clip.w * ((2. * screen) / resolution - 1.), depth, clip.w); + + return VertexOutput(clip_position, color); +} + +struct FragmentOutput { + @location(0) color: vec4, +}; + +@fragment +fn fragment(in: VertexOutput) -> FragmentOutput { + return FragmentOutput(in.color); +} \ No newline at end of file diff --git a/crates/bevy_gizmos/src/billboard/mod.rs b/crates/bevy_gizmos/src/billboard/mod.rs new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/crates/bevy_gizmos/src/lib.rs b/crates/bevy_gizmos/src/lib.rs index 166663562246b..68d363b18778c 100644 --- a/crates/bevy_gizmos/src/lib.rs +++ b/crates/bevy_gizmos/src/lib.rs @@ -30,6 +30,9 @@ pub enum GizmoRenderSystem { QueueLineGizmos3d, } +mod lines; +mod billboard; + #[cfg(feature = "bevy_render")] pub mod aabb; pub mod arcs; @@ -39,10 +42,8 @@ pub mod config; pub mod cross; pub mod gizmos; pub mod grid; -pub mod lines; pub mod primitives; pub mod rounded_box; - #[cfg(all(feature = "bevy_pbr", feature = "bevy_render"))] pub mod light; From d9737756f9f97817c622fd1463ef02aace3dd608 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joscha=20P=C3=BCtz?= <62256001+lynn-lumen@users.noreply.github.com> Date: Thu, 29 Aug 2024 11:39:45 +0200 Subject: [PATCH 04/12] Add billboard gizmos --- .../bevy_gizmos/src/billboard/billboards.wgsl | 7 +- crates/bevy_gizmos/src/billboard/mod.rs | 376 ++++++++++++++++++ .../bevy_gizmos/src/billboard/pipeline_2d.rs | 212 ++++++++++ .../bevy_gizmos/src/billboard/pipeline_3d.rs | 245 ++++++++++++ crates/bevy_gizmos/src/config.rs | 16 + crates/bevy_gizmos/src/lib.rs | 13 +- crates/bevy_gizmos/src/lines/mod.rs | 22 +- crates/bevy_gizmos/src/lines/pipeline_2d.rs | 4 +- crates/bevy_gizmos/src/lines/pipeline_3d.rs | 4 +- 9 files changed, 874 insertions(+), 25 deletions(-) create mode 100644 crates/bevy_gizmos/src/billboard/pipeline_2d.rs create mode 100644 crates/bevy_gizmos/src/billboard/pipeline_3d.rs diff --git a/crates/bevy_gizmos/src/billboard/billboards.wgsl b/crates/bevy_gizmos/src/billboard/billboards.wgsl index c97aff18584d8..ff7c77bf53497 100644 --- a/crates/bevy_gizmos/src/billboard/billboards.wgsl +++ b/crates/bevy_gizmos/src/billboard/billboards.wgsl @@ -26,6 +26,9 @@ struct VertexOutput { @location(0) color: vec4, }; +const SQRT2: f32 = 1.4142135; +const EPSILON: f32 = 4.88e-04; + @vertex fn vertex(vertex: VertexInput) -> VertexOutput { var positions = array, 6>( @@ -38,7 +41,7 @@ fn vertex(vertex: VertexInput) -> VertexOutput { ); let position = positions[vertex.index]; - let clip = view.view_proj * vec4(vertex.position, 1.); + let clip = view.clip_from_world * vec4(vertex.position, 1.); let resolution = view.viewport.zw; let screen_center = resolution * (0.5 * clip.xy / clip.w + 0.5); @@ -56,7 +59,7 @@ fn vertex(vertex: VertexInput) -> VertexOutput { let abs_size = length(billboard_size); if abs_size < SQRT2 { color.a *= abs_size / SQRT2; - billboard_size = vec2(1., 1.); + billboard_size = 1.; } let screen = screen_center + position * billboard_size; diff --git a/crates/bevy_gizmos/src/billboard/mod.rs b/crates/bevy_gizmos/src/billboard/mod.rs index e69de29bb2d1d..3006265b34884 100644 --- a/crates/bevy_gizmos/src/billboard/mod.rs +++ b/crates/bevy_gizmos/src/billboard/mod.rs @@ -0,0 +1,376 @@ +use std::{any::TypeId, mem}; + +use bevy_app::{App, Last, Plugin}; +use bevy_asset::{Asset, AssetApp, Assets, Handle}; +use bevy_color::LinearRgba; +use bevy_ecs::{ + component::Component, query::ROQueryItem, schedule::IntoSystemConfigs, system::{lifetimeless::{Read, SRes}, Commands, Res, ResMut, Resource, SystemParamItem} +}; +use bevy_math::Vec3; +use bevy_reflect::TypePath; +use bevy_render::{ + extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin}, render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets}, render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass}, render_resource::{ + binding_types::uniform_buffer, BindGroup, BindGroupEntries, BindGroupLayout, BindGroupLayoutEntries, Buffer, BufferInitDescriptor, BufferUsages, Shader, ShaderStages, ShaderType, VertexAttribute, VertexBufferLayout, VertexFormat, VertexStepMode + }, renderer::RenderDevice, Extract, ExtractSchedule, Render, RenderApp, RenderSet +}; +use bevy_utils::TypeIdMap; +use bytemuck::cast_slice; + +use crate::{ + config::{GizmoConfigGroup, GizmoConfigStore}, + gizmos::GizmoStorage, UpdateGizmoMeshes, +}; + +#[cfg(all(feature = "bevy_sprite", feature = "bevy_render"))] +mod pipeline_2d; +#[cfg(all(feature = "bevy_pbr", feature = "bevy_render"))] +mod pipeline_3d; + +#[cfg(feature = "bevy_render")] +const BILLBOARD_SHADER_HANDLE: Handle = Handle::weak_from_u128(6006413002665766670); + +/// A [`Plugin`] that provides an immediate mode billboard drawing api for visual debugging. +#[derive(Default)] +pub struct BillboardGizmoPlugin; + +impl Plugin for BillboardGizmoPlugin { + fn build(&self, app: &mut App) { + #[cfg(feature = "bevy_render")] + { + use bevy_asset::load_internal_asset; + load_internal_asset!(app, BILLBOARD_SHADER_HANDLE, "billboards.wgsl", Shader::from_wgsl); + } + app.init_asset::() + .init_resource::(); + + #[cfg(feature = "bevy_render")] + app.add_plugins(UniformComponentPlugin::::default()) + .add_plugins(RenderAssetPlugin::::default()); + + #[cfg(feature = "bevy_render")] + if let Some(render_app) = app.get_sub_app_mut(RenderApp) { + render_app.add_systems( + Render, + prepare_billboard_gizmo_bind_group.in_set(RenderSet::PrepareBindGroups), + ); + + render_app.add_systems(ExtractSchedule, extract_gizmo_data); + + #[cfg(feature = "bevy_sprite")] + if app.is_plugin_added::() { + app.add_plugins(pipeline_2d::BillboardGizmo2dPlugin); + } else { + bevy_utils::tracing::warn!("bevy_sprite feature is enabled but bevy_sprite::SpritePlugin was not detected. Are you sure you loaded GizmoPlugin after SpritePlugin?"); + } + #[cfg(feature = "bevy_pbr")] + if app.is_plugin_added::() { + app.add_plugins(pipeline_3d::BillboardGizmo3dPlugin); + } else { + bevy_utils::tracing::warn!("bevy_pbr feature is enabled but bevy_pbr::PbrPlugin was not detected. Are you sure you loaded GizmoPlugin after PbrPlugin?"); + } + } else { + bevy_utils::tracing::warn!("bevy_render feature is enabled but RenderApp was not detected. Are you sure you loaded GizmoPlugin after RenderPlugin?"); + } + } + + #[cfg(feature = "bevy_render")] + fn finish(&self, app: &mut App) { + let Some(render_app) = app.get_sub_app_mut(RenderApp) else { + return; + }; + + let render_device = render_app.world().resource::(); + let billboard_layout = render_device.create_bind_group_layout( + "BillboardGizmoUniform layout", + &BindGroupLayoutEntries::single( + ShaderStages::VERTEX, + uniform_buffer::(true), + ), + ); + + render_app.insert_resource(BillboardGizmoUniformBindgroupLayout { + layout: billboard_layout, + }); + } +} + +/// An internal extension trait adding `App::init_billboard_gizmo_group`. +pub(crate) trait AppBillboardGizmoBuilder { + /// Registers [`GizmoConfigGroup`] in the app enabling the use of [Gizmos<Config>](crate::gizmos::Gizmos). + /// + /// Configurations can be set using the [`GizmoConfigStore`] [`Resource`]. + fn init_billboard_gizmo_group(&mut self) -> &mut Self; +} + +impl AppBillboardGizmoBuilder for App { + fn init_billboard_gizmo_group(&mut self) -> &mut Self { + if self.world().contains_resource::>() { + return self; + } + + let mut handles = self + .world_mut() + .get_resource_or_insert_with::(Default::default); + + handles.billboards.insert(TypeId::of::(), None); + + self.add_systems(Last, update_gizmo_meshes::.in_set(UpdateGizmoMeshes)); + + self + } +} + +/// Holds handles to the billboard gizmos for each gizmo configuration group +// As `TypeIdMap` iteration order depends on the order of insertions and deletions, this uses +// `Option` to be able to reserve the slot when creating the gizmo configuration group. +// That way iteration order is stable across executions and depends on the order of configuration +// group creation. +#[derive(Resource, Default)] +struct BillboardGizmoHandles { + billboards: TypeIdMap>>, +} + +/// Prepare gizmos for rendering. +/// +/// This also clears the default `GizmoStorage`. +fn update_gizmo_meshes( + mut billboard_gizmos: ResMut>, + mut handles: ResMut, + mut storage: ResMut>, +) { + if storage.list_positions.is_empty() { + handles.billboards.insert(TypeId::of::(), None); + } else if let Some(handle) = handles.billboards.get_mut(&TypeId::of::()) { + if let Some(handle) = handle { + let billboards = billboard_gizmos.get_mut(handle.id()).unwrap(); + + billboards.positions = mem::take(&mut storage.billboard_positions); + billboards.colors = mem::take(&mut storage.billboard_colors); + } else { + let billboards = BillboardGizmo { + positions: mem::take(&mut storage.billboard_positions), + colors: mem::take(&mut storage.billboard_colors), + }; + + *handle = Some(billboard_gizmos.add(billboards)); + } + } +} + +#[cfg(feature = "bevy_render")] +fn extract_gizmo_data( + mut commands: Commands, + handles: Extract>, + config: Extract>, +) { + for (group_type_id, handle) in handles.billboards.iter() { + let Some((config, _)) = config.get_config_dyn(group_type_id) else { + continue; + }; + + if !config.enabled { + continue; + } + + let Some(handle) = handle else { + continue; + }; + + commands.spawn(( + BillboardGizmoUniform { + size: config.billboard_size, + depth_bias: config.depth_bias, + #[cfg(feature = "webgl")] + _padding: Default::default(), + }, + (*handle).clone_weak(), + #[cfg(any(feature = "bevy_pbr", feature = "bevy_sprite"))] + crate::config::GizmoMeshConfig::from(config), + )); + } +} + +#[cfg(feature = "bevy_render")] +#[derive(Component, ShaderType, Clone, Copy)] +struct BillboardGizmoUniform { + size: f32, + depth_bias: f32, + /// WebGL2 structs must be 16 byte aligned. + #[cfg(feature = "webgl")] + _padding: bevy_math::Vec2, +} + +/// A gizmo asset that represents a billboard. +#[derive(Asset, Debug, Clone, TypePath)] +pub struct BillboardGizmo { + /// Positions of the gizmo's vertices + pub positions: Vec, + /// Colors of the gizmo's vertices + pub colors: Vec, +} + +#[cfg(feature = "bevy_render")] +#[derive(Debug, Clone)] +struct GpuBillboardGizmo { + position_buffer: Buffer, + color_buffer: Buffer, + vertex_count: u32, +} + +#[cfg(feature = "bevy_render")] +impl RenderAsset for GpuBillboardGizmo { + type SourceAsset = BillboardGizmo; + type Param = SRes; + + fn prepare_asset( + gizmo: Self::SourceAsset, + render_device: &mut SystemParamItem, + ) -> Result> { + let position_buffer_data = cast_slice(&gizmo.positions); + let position_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor { + usage: BufferUsages::VERTEX, + label: Some("BillboardGizmo Position Buffer"), + contents: position_buffer_data, + }); + + let color_buffer_data = cast_slice(&gizmo.colors); + let color_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor { + usage: BufferUsages::VERTEX, + label: Some("BillboardGizmo Color Buffer"), + contents: color_buffer_data, + }); + + Ok(GpuBillboardGizmo { + position_buffer, + color_buffer, + vertex_count: gizmo.positions.len() as u32, + }) + } +} + +#[cfg(feature = "bevy_render")] +#[derive(Resource)] +struct BillboardGizmoUniformBindgroupLayout { + layout: BindGroupLayout, +} + +#[cfg(feature = "bevy_render")] +#[derive(Resource)] +struct BillboardGizmoUniformBindgroup { + bindgroup: BindGroup, +} + +#[cfg(feature = "bevy_render")] +fn prepare_billboard_gizmo_bind_group( + mut commands: Commands, + billboard_gizmo_uniform_layout: Res, + render_device: Res, + billboard_gizmo_uniforms: Res>, +) { + if let Some(binding) = billboard_gizmo_uniforms.uniforms().binding() { + commands.insert_resource(BillboardGizmoUniformBindgroup { + bindgroup: render_device.create_bind_group( + "BillboardGizmoUniform bindgroup", + &billboard_gizmo_uniform_layout.layout, + &BindGroupEntries::single(binding), + ), + }); + } +} + +#[cfg(feature = "bevy_render")] +struct SetBillboardGizmoBindGroup; +#[cfg(feature = "bevy_render")] +impl RenderCommand

for SetBillboardGizmoBindGroup { + type Param = SRes; + type ViewQuery = (); + type ItemQuery = Read>; + + #[inline] + fn render<'w>( + _item: &P, + _view: ROQueryItem<'w, Self::ViewQuery>, + uniform_index: Option>, + bind_group: SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, + ) -> RenderCommandResult { + let Some(uniform_index) = uniform_index else { + return RenderCommandResult::Skip; + }; + pass.set_bind_group( + I, + &bind_group.into_inner().bindgroup, + &[uniform_index.index()], + ); + RenderCommandResult::Success + } +} + +#[cfg(feature = "bevy_render")] +struct DrawBillboardGizmo; +#[cfg(feature = "bevy_render")] +impl RenderCommand

for DrawBillboardGizmo { + type Param = SRes>; + type ViewQuery = (); + type ItemQuery = Read>; + + #[inline] + fn render<'w>( + _item: &P, + _view: ROQueryItem<'w, Self::ViewQuery>, + handle: Option>, + billboard_gizmos: SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, + ) -> RenderCommandResult { + let Some(handle) = handle else { + return RenderCommandResult::Skip; + }; + let Some(billboard_gizmo) = billboard_gizmos.into_inner().get(handle) else { + return RenderCommandResult::Skip; + }; + + if billboard_gizmo.vertex_count == 0 { + return RenderCommandResult::Success; + } + + let instances = { + pass.set_vertex_buffer(0, billboard_gizmo.position_buffer.slice(..)); + pass.set_vertex_buffer(1, billboard_gizmo.color_buffer.slice(..)); + + billboard_gizmo.vertex_count + }; + + pass.draw(0..6, 0..instances); + + RenderCommandResult::Success + } +} + +#[cfg(all( + feature = "bevy_render", + any(feature = "bevy_pbr", feature = "bevy_sprite") +))] +fn billboard_gizmo_vertex_buffer_layouts() -> Vec { + use VertexFormat::*; + let position_layout = VertexBufferLayout { + array_stride: Float32x3.size(), + step_mode: VertexStepMode::Instance, + attributes: vec![VertexAttribute { + format: Float32x3, + offset: 0, + shader_location: 0, + }], + }; + + let color_layout = VertexBufferLayout { + array_stride: Float32x4.size(), + step_mode: VertexStepMode::Instance, + attributes: vec![VertexAttribute { + format: Float32x4, + offset: 0, + shader_location: 1, + }], + }; + + vec![position_layout, color_layout] +} diff --git a/crates/bevy_gizmos/src/billboard/pipeline_2d.rs b/crates/bevy_gizmos/src/billboard/pipeline_2d.rs new file mode 100644 index 0000000000000..2fe919e9d67a6 --- /dev/null +++ b/crates/bevy_gizmos/src/billboard/pipeline_2d.rs @@ -0,0 +1,212 @@ +use crate::{ + config::GizmoMeshConfig, + GizmoRenderSystem, +}; +use super::{ + billboard_gizmo_vertex_buffer_layouts, DrawBillboardGizmo, + GpuBillboardGizmo, + BillboardGizmoUniformBindgroupLayout, SetBillboardGizmoBindGroup, +}; +use bevy_app::{App, Plugin}; +use bevy_core_pipeline::core_2d::{Transparent2d, CORE_2D_DEPTH_FORMAT}; + +use bevy_ecs::{ + prelude::Entity, + schedule::{IntoSystemConfigs, IntoSystemSetConfigs}, + system::{Query, Res, ResMut, Resource}, + world::{FromWorld, World}, +}; +use bevy_math::FloatOrd; +use bevy_render::{ + render_asset::prepare_assets, + render_phase::{ + AddRenderCommand, DrawFunctions, PhaseItemExtraIndex, SetItemPipeline, + ViewSortedRenderPhases, + }, + render_resource::*, + texture::BevyDefault, + view::{ExtractedView, Msaa, RenderLayers, ViewTarget}, + Render, RenderApp, RenderSet, +}; +use bevy_sprite::{Mesh2dPipeline, Mesh2dPipelineKey, SetMesh2dViewBindGroup}; + +use super::BILLBOARD_SHADER_HANDLE; + +pub struct BillboardGizmo2dPlugin; + +impl Plugin for BillboardGizmo2dPlugin { + fn build(&self, app: &mut App) { + let Some(render_app) = app.get_sub_app_mut(RenderApp) else { + return; + }; + + render_app + .add_render_command::() + .init_resource::>() + .configure_sets( + Render, + GizmoRenderSystem::QueueGizmos2d + .in_set(RenderSet::Queue) + .ambiguous_with(bevy_sprite::queue_sprites) + .ambiguous_with( + bevy_sprite::queue_material2d_meshes::, + ), + ) + .add_systems( + Render, + queue_billboard_gizmos_2d + .in_set(GizmoRenderSystem::QueueGizmos2d) + .after(prepare_assets::), + ); + } + + fn finish(&self, app: &mut App) { + let Some(render_app) = app.get_sub_app_mut(RenderApp) else { + return; + }; + + render_app.init_resource::(); + } +} + +#[derive(Clone, Resource)] +struct BillboardGizmoPipeline { + mesh_pipeline: Mesh2dPipeline, + uniform_layout: BindGroupLayout, +} + +impl FromWorld for BillboardGizmoPipeline { + fn from_world(render_world: &mut World) -> Self { + BillboardGizmoPipeline { + mesh_pipeline: render_world.resource::().clone(), + uniform_layout: render_world + .resource::() + .layout + .clone(), + } + } +} + +#[derive(PartialEq, Eq, Hash, Clone)] +struct BillboardGizmoPipelineKey { + mesh_key: Mesh2dPipelineKey, +} + +impl SpecializedRenderPipeline for BillboardGizmoPipeline { + type Key = BillboardGizmoPipelineKey; + + fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { + let format = if key.mesh_key.contains(Mesh2dPipelineKey::HDR) { + ViewTarget::TEXTURE_FORMAT_HDR + } else { + TextureFormat::bevy_default() + }; + + let shader_defs = vec![ + #[cfg(feature = "webgl")] + "SIXTEEN_BYTE_ALIGNMENT".into(), + ]; + + let layout = vec![ + self.mesh_pipeline.view_layout.clone(), + self.uniform_layout.clone(), + ]; + + RenderPipelineDescriptor { + vertex: VertexState { + shader: BILLBOARD_SHADER_HANDLE, + entry_point: "vertex".into(), + shader_defs: shader_defs.clone(), + buffers: billboard_gizmo_vertex_buffer_layouts(), + }, + fragment: Some(FragmentState { + shader: BILLBOARD_SHADER_HANDLE, + shader_defs, + entry_point: "fragment".into(), + targets: vec![Some(ColorTargetState { + format, + blend: Some(BlendState::ALPHA_BLENDING), + write_mask: ColorWrites::ALL, + })], + }), + layout, + primitive: PrimitiveState::default(), + depth_stencil: Some(DepthStencilState { + format: CORE_2D_DEPTH_FORMAT, + depth_write_enabled: true, + depth_compare: CompareFunction::GreaterEqual, + stencil: StencilState { + front: StencilFaceState::IGNORE, + back: StencilFaceState::IGNORE, + read_mask: 0, + write_mask: 0, + }, + bias: DepthBiasState { + constant: 0, + slope_scale: 0.0, + clamp: 0.0, + }, + }), + multisample: MultisampleState { + count: key.mesh_key.msaa_samples(), + mask: !0, + alpha_to_coverage_enabled: false, + }, + label: Some("BillboardGizmo Pipeline 2D".into()), + push_constant_ranges: vec![], + } + } +} + +type DrawBillboardGizmo2d = ( + SetItemPipeline, + SetMesh2dViewBindGroup<0>, + SetBillboardGizmoBindGroup<1>, + DrawBillboardGizmo, +); + +#[allow(clippy::too_many_arguments)] +fn queue_billboard_gizmos_2d( + draw_functions: Res>, + pipeline: Res, + mut pipelines: ResMut>, + pipeline_cache: Res, + billboard_gizmos: Query<(Entity, &GizmoMeshConfig)>, + mut transparent_render_phases: ResMut>, + mut views: Query<(Entity, &ExtractedView, &Msaa, Option<&RenderLayers>)>, +) { + let draw_function = draw_functions.read().get_id::().unwrap(); + + for (view_entity, view, msaa, render_layers) in &mut views { + let Some(transparent_phase) = transparent_render_phases.get_mut(&view_entity) else { + continue; + }; + + let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples()) + | Mesh2dPipelineKey::from_hdr(view.hdr); + + let render_layers = render_layers.unwrap_or_default(); + for (entity, config) in &billboard_gizmos { + if !config.render_layers.intersects(render_layers) { + continue; + } + + let pipeline = pipelines.specialize( + &pipeline_cache, + &pipeline, + BillboardGizmoPipelineKey { + mesh_key, + }, + ); + + transparent_phase.add(Transparent2d { + entity, + draw_function, + pipeline, + sort_key: FloatOrd(f32::INFINITY), + batch_range: 0..1, + extra_index: PhaseItemExtraIndex::NONE, + }); + } + } +} diff --git a/crates/bevy_gizmos/src/billboard/pipeline_3d.rs b/crates/bevy_gizmos/src/billboard/pipeline_3d.rs new file mode 100644 index 0000000000000..4b8a68fcae8a3 --- /dev/null +++ b/crates/bevy_gizmos/src/billboard/pipeline_3d.rs @@ -0,0 +1,245 @@ +use crate::{ + config::GizmoMeshConfig, + GizmoRenderSystem, +}; +use super::{ + billboard_gizmo_vertex_buffer_layouts, DrawBillboardGizmo, + GpuBillboardGizmo, + BillboardGizmoUniformBindgroupLayout, SetBillboardGizmoBindGroup, +}; +use bevy_app::{App, Plugin}; +use bevy_core_pipeline::{ + core_3d::{Transparent3d, CORE_3D_DEPTH_FORMAT}, + prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass}, +}; + +use bevy_ecs::{ + prelude::Entity, + query::Has, + schedule::{IntoSystemConfigs, IntoSystemSetConfigs}, + system::{Query, Res, ResMut, Resource}, + world::{FromWorld, World}, +}; +use bevy_pbr::{MeshPipeline, MeshPipelineKey, SetMeshViewBindGroup}; +use bevy_render::{ + render_asset::prepare_assets, + render_phase::{ + AddRenderCommand, DrawFunctions, PhaseItemExtraIndex, SetItemPipeline, + ViewSortedRenderPhases, + }, + render_resource::*, + texture::BevyDefault, + view::{ExtractedView, Msaa, RenderLayers, ViewTarget}, + Render, RenderApp, RenderSet, +}; + +use super::BILLBOARD_SHADER_HANDLE; + +pub struct BillboardGizmo3dPlugin; +impl Plugin for BillboardGizmo3dPlugin { + fn build(&self, app: &mut App) { + let Some(render_app) = app.get_sub_app_mut(RenderApp) else { + return; + }; + + render_app + .add_render_command::() + .init_resource::>() + .configure_sets( + Render, + GizmoRenderSystem::QueueGizmos3d + .in_set(RenderSet::Queue) + .ambiguous_with(bevy_pbr::queue_material_meshes::), + ) + .add_systems( + Render, + queue_billboard_gizmos_3d + .in_set(GizmoRenderSystem::QueueGizmos3d) + .after(prepare_assets::), + ); + } + + fn finish(&self, app: &mut App) { + let Some(render_app) = app.get_sub_app_mut(RenderApp) else { + return; + }; + + render_app.init_resource::(); + } +} + +#[derive(Clone, Resource)] +struct LineGizmoPipeline { + mesh_pipeline: MeshPipeline, + uniform_layout: BindGroupLayout, +} + +impl FromWorld for LineGizmoPipeline { + fn from_world(render_world: &mut World) -> Self { + LineGizmoPipeline { + mesh_pipeline: render_world.resource::().clone(), + uniform_layout: render_world + .resource::() + .layout + .clone(), + } + } +} + +#[derive(PartialEq, Eq, Hash, Clone)] +struct LineGizmoPipelineKey { + view_key: MeshPipelineKey, + perspective: bool, +} + +impl SpecializedRenderPipeline for LineGizmoPipeline { + type Key = LineGizmoPipelineKey; + + fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { + let mut shader_defs = vec![ + #[cfg(feature = "webgl")] + "SIXTEEN_BYTE_ALIGNMENT".into(), + ]; + + if key.perspective { + shader_defs.push("PERSPECTIVE".into()); + } + + let format = if key.view_key.contains(MeshPipelineKey::HDR) { + ViewTarget::TEXTURE_FORMAT_HDR + } else { + TextureFormat::bevy_default() + }; + + let view_layout = self + .mesh_pipeline + .get_view_layout(key.view_key.into()) + .clone(); + + let layout = vec![view_layout, self.uniform_layout.clone()]; + + RenderPipelineDescriptor { + vertex: VertexState { + shader: BILLBOARD_SHADER_HANDLE, + entry_point: "vertex".into(), + shader_defs: shader_defs.clone(), + buffers: billboard_gizmo_vertex_buffer_layouts(), + }, + fragment: Some(FragmentState { + shader: BILLBOARD_SHADER_HANDLE, + shader_defs, + entry_point: "fragment".into(), + targets: vec![Some(ColorTargetState { + format, + blend: Some(BlendState::ALPHA_BLENDING), + write_mask: ColorWrites::ALL, + })], + }), + layout, + primitive: PrimitiveState::default(), + depth_stencil: Some(DepthStencilState { + format: CORE_3D_DEPTH_FORMAT, + depth_write_enabled: true, + depth_compare: CompareFunction::Greater, + stencil: StencilState::default(), + bias: DepthBiasState::default(), + }), + multisample: MultisampleState { + count: key.view_key.msaa_samples(), + mask: !0, + alpha_to_coverage_enabled: false, + }, + label: Some("LineGizmo Pipeline".into()), + push_constant_ranges: vec![], + } + } +} + +type DrawBillboardGizmo3d = ( + SetItemPipeline, + SetMeshViewBindGroup<0>, + SetBillboardGizmoBindGroup<1>, + DrawBillboardGizmo, +); + +#[allow(clippy::too_many_arguments)] +fn queue_billboard_gizmos_3d( + draw_functions: Res>, + pipeline: Res, + mut pipelines: ResMut>, + pipeline_cache: Res, + billboard_gizmos: Query<(Entity, &GizmoMeshConfig)>, + mut transparent_render_phases: ResMut>, + mut views: Query<( + Entity, + &ExtractedView, + &Msaa, + Option<&RenderLayers>, + ( + Has, + Has, + Has, + Has, + ), + )>, +) { + let draw_function = draw_functions.read().get_id::().unwrap(); + + for ( + view_entity, + view, + msaa, + render_layers, + (normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass), + ) in &mut views + { + let Some(transparent_phase) = transparent_render_phases.get_mut(&view_entity) else { + continue; + }; + + let render_layers = render_layers.unwrap_or_default(); + + let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()) + | MeshPipelineKey::from_hdr(view.hdr); + + if normal_prepass { + view_key |= MeshPipelineKey::NORMAL_PREPASS; + } + + if depth_prepass { + view_key |= MeshPipelineKey::DEPTH_PREPASS; + } + + if motion_vector_prepass { + view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS; + } + + if deferred_prepass { + view_key |= MeshPipelineKey::DEFERRED_PREPASS; + } + + for (entity, config) in &billboard_gizmos { + if !config.render_layers.intersects(render_layers) { + continue; + } + + let pipeline = pipelines.specialize( + &pipeline_cache, + &pipeline, + LineGizmoPipelineKey { + view_key, + perspective: config.billboard_perspective, + }, + ); + + transparent_phase.add(Transparent3d { + entity, + draw_function, + pipeline, + distance: 0., + batch_range: 0..1, + extra_index: PhaseItemExtraIndex::NONE, + }); + } + } +} diff --git a/crates/bevy_gizmos/src/config.rs b/crates/bevy_gizmos/src/config.rs index 0705029ddf19c..0d57c5a7763c8 100644 --- a/crates/bevy_gizmos/src/config.rs +++ b/crates/bevy_gizmos/src/config.rs @@ -141,6 +141,18 @@ pub struct GizmoConfig { pub enabled: bool, /// Line width specified in pixels. /// + /// If `billboard_perspective` is `true` then this is the size in pixels at the camera's near plane. + /// + /// Defaults to `10.0`. + pub billboard_size: f32, + /// Apply perspective to gizmo billboards. + /// + /// This setting only affects 3D, non-orthographic cameras. + /// + /// Defaults to `false`. + pub billboard_perspective: bool, + /// Billboard size specified in pixels. + /// /// If `line_perspective` is `true` then this is the size in pixels at the camera's near plane. /// /// Defaults to `2.0`. @@ -180,6 +192,8 @@ impl Default for GizmoConfig { fn default() -> Self { Self { enabled: true, + billboard_size: 10., + billboard_perspective: false, line_width: 2., line_perspective: false, line_style: GizmoLineStyle::Solid, @@ -198,6 +212,7 @@ impl Default for GizmoConfig { ))] #[derive(Component)] pub(crate) struct GizmoMeshConfig { + pub billboard_perspective: bool, pub line_perspective: bool, pub line_style: GizmoLineStyle, pub render_layers: bevy_render::view::RenderLayers, @@ -210,6 +225,7 @@ pub(crate) struct GizmoMeshConfig { impl From<&GizmoConfig> for GizmoMeshConfig { fn from(item: &GizmoConfig) -> Self { GizmoMeshConfig { + billboard_perspective: item.billboard_perspective, line_perspective: item.line_perspective, line_style: item.line_style, render_layers: item.render_layers.clone(), diff --git a/crates/bevy_gizmos/src/lib.rs b/crates/bevy_gizmos/src/lib.rs index 68d363b18778c..c192ac41d9acc 100644 --- a/crates/bevy_gizmos/src/lib.rs +++ b/crates/bevy_gizmos/src/lib.rs @@ -24,10 +24,10 @@ pub enum GizmoRenderSystem { /// Adds gizmos to the [`Transparent2d`](bevy_core_pipeline::core_2d::Transparent2d) render phase #[cfg(feature = "bevy_sprite")] - QueueLineGizmos2d, + QueueGizmos2d, /// Adds gizmos to the [`Transparent3d`](bevy_core_pipeline::core_3d::Transparent3d) render phase #[cfg(feature = "bevy_pbr")] - QueueLineGizmos3d, + QueueGizmos3d, } mod lines; @@ -72,13 +72,14 @@ use bevy_ecs::{ system::{Res, ResMut}, }; use bevy_time::Fixed; +use billboard::{AppBillboardGizmoBuilder, BillboardGizmoPlugin}; use config::{ DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore, }; use gizmos::{GizmoStorage, Swap}; #[cfg(all(feature = "bevy_pbr", feature = "bevy_render"))] use light::LightGizmoPlugin; -use lines::{AppLineGizmoBuilder, GizmoLinePlugin}; +use lines::{AppLineGizmoBuilder, LineGizmoPlugin}; /// A [`Plugin`] that provides an immediate mode drawing api for visual debugging. /// @@ -88,7 +89,8 @@ pub struct GizmoPlugin; impl Plugin for GizmoPlugin { fn build(&self, app: &mut App) { - app.add_plugins(GizmoLinePlugin) + app.add_plugins(LineGizmoPlugin) + .add_plugins(BillboardGizmoPlugin) .register_type::() .register_type::() // We insert the Resource GizmoConfigStore into the world implicitly here if it does not exist. @@ -102,7 +104,7 @@ impl Plugin for GizmoPlugin { } } -/// A extension trait adding `App::init_gizmo_group` and `App::insert_gizmo_config`. +/// An extension trait adding `App::init_gizmo_group` and `App::insert_gizmo_config`. pub trait AppGizmoBuilder { /// Registers [`GizmoConfigGroup`] in the app enabling the use of [Gizmos<Config>](crate::gizmos::Gizmos). /// @@ -130,6 +132,7 @@ impl AppGizmoBuilder for App { .register::(); self.init_line_gizmo_group::(); + self.init_billboard_gizmo_group::(); self .init_resource::>() diff --git a/crates/bevy_gizmos/src/lines/mod.rs b/crates/bevy_gizmos/src/lines/mod.rs index 5ee4f92d141bb..6483417983ab2 100644 --- a/crates/bevy_gizmos/src/lines/mod.rs +++ b/crates/bevy_gizmos/src/lines/mod.rs @@ -23,23 +23,21 @@ use crate::{ gizmos::GizmoStorage, UpdateGizmoMeshes, }; +#[cfg(all(feature = "bevy_sprite", feature = "bevy_render"))] +mod pipeline_2d; +#[cfg(all(feature = "bevy_pbr", feature = "bevy_render"))] +mod pipeline_3d; + #[cfg(feature = "bevy_render")] const LINE_SHADER_HANDLE: Handle = Handle::weak_from_u128(7414812689238026784); #[cfg(feature = "bevy_render")] const LINE_JOINT_SHADER_HANDLE: Handle = Handle::weak_from_u128(1162780797909187908); /// A [`Plugin`] that provides an immediate mode line drawing api for visual debugging. -/// -/// Requires to be loaded after [`PbrPlugin`](bevy_pbr::PbrPlugin) or [`SpritePlugin`](bevy_sprite::SpritePlugin). #[derive(Default)] -pub struct GizmoLinePlugin; - -#[cfg(all(feature = "bevy_sprite", feature = "bevy_render"))] -mod pipeline_2d; -#[cfg(all(feature = "bevy_pbr", feature = "bevy_render"))] -mod pipeline_3d; +pub struct LineGizmoPlugin; -impl Plugin for GizmoLinePlugin { +impl Plugin for LineGizmoPlugin { fn build(&self, app: &mut App) { #[cfg(feature = "bevy_render")] { @@ -107,7 +105,7 @@ impl Plugin for GizmoLinePlugin { } } -/// A extension trait adding `App::init_gizmo_group` and `App::insert_gizmo_config`. +/// An internal extension trait adding `App::init_line_gizmo_group`. pub(crate) trait AppLineGizmoBuilder { /// Registers [`GizmoConfigGroup`] in the app enabling the use of [Gizmos<Config>](crate::gizmos::Gizmos). /// @@ -165,7 +163,6 @@ fn update_gizmo_meshes( } else { let list = LineGizmo { strip: false, - config_ty: TypeId::of::(), positions: mem::take(&mut storage.list_positions), colors: mem::take(&mut storage.list_colors), joints: GizmoLineJoint::None, @@ -189,7 +186,6 @@ fn update_gizmo_meshes( let strip = LineGizmo { strip: true, joints: config.line_joints, - config_ty: TypeId::of::(), positions: mem::take(&mut storage.strip_positions), colors: mem::take(&mut storage.strip_colors), }; @@ -262,8 +258,6 @@ pub struct LineGizmo { pub strip: bool, /// Whether this gizmo should draw line joints. This is only applicable if the gizmo's topology is line-strip. pub joints: GizmoLineJoint, - /// The type of the gizmo's configuration group - pub config_ty: TypeId, } #[cfg(feature = "bevy_render")] diff --git a/crates/bevy_gizmos/src/lines/pipeline_2d.rs b/crates/bevy_gizmos/src/lines/pipeline_2d.rs index 41ba744c59cf6..e367475786c69 100644 --- a/crates/bevy_gizmos/src/lines/pipeline_2d.rs +++ b/crates/bevy_gizmos/src/lines/pipeline_2d.rs @@ -49,7 +49,7 @@ impl Plugin for LineGizmo2dPlugin { .init_resource::>() .configure_sets( Render, - GizmoRenderSystem::QueueLineGizmos2d + GizmoRenderSystem::QueueGizmos2d .in_set(RenderSet::Queue) .ambiguous_with(bevy_sprite::queue_sprites) .ambiguous_with( @@ -59,7 +59,7 @@ impl Plugin for LineGizmo2dPlugin { .add_systems( Render, (queue_line_gizmos_2d, queue_line_joint_gizmos_2d) - .in_set(GizmoRenderSystem::QueueLineGizmos2d) + .in_set(GizmoRenderSystem::QueueGizmos2d) .after(prepare_assets::), ); } diff --git a/crates/bevy_gizmos/src/lines/pipeline_3d.rs b/crates/bevy_gizmos/src/lines/pipeline_3d.rs index bd6bb780e8bb8..065dfaff027b2 100644 --- a/crates/bevy_gizmos/src/lines/pipeline_3d.rs +++ b/crates/bevy_gizmos/src/lines/pipeline_3d.rs @@ -51,14 +51,14 @@ impl Plugin for LineGizmo3dPlugin { .init_resource::>() .configure_sets( Render, - GizmoRenderSystem::QueueLineGizmos3d + GizmoRenderSystem::QueueGizmos3d .in_set(RenderSet::Queue) .ambiguous_with(bevy_pbr::queue_material_meshes::), ) .add_systems( Render, (queue_line_gizmos_3d, queue_line_joint_gizmos_3d) - .in_set(GizmoRenderSystem::QueueLineGizmos3d) + .in_set(GizmoRenderSystem::QueueGizmos3d) .after(prepare_assets::), ); } From 8dc1693b4f40766b5a7f138e3b301ec93af283f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lynn=20B=C3=BCttgenbach?= <62256001+lynn-lumen@users.noreply.github.com> Date: Thu, 29 Aug 2024 14:18:54 +0200 Subject: [PATCH 05/12] Mionr fixes --- .../bevy_gizmos/src/billboard/billboards.wgsl | 3 +- crates/bevy_gizmos/src/billboard/mod.rs | 43 ++++++++++++++++--- .../bevy_gizmos/src/billboard/pipeline_2d.rs | 19 ++++---- .../bevy_gizmos/src/billboard/pipeline_3d.rs | 15 +++---- crates/bevy_gizmos/src/gizmos.rs | 14 ++++-- crates/bevy_gizmos/src/lib.rs | 18 +++----- crates/bevy_gizmos/src/lines/mod.rs | 29 ++++++++++--- crates/bevy_gizmos/src/lines/pipeline_2d.rs | 10 ++--- crates/bevy_gizmos/src/lines/pipeline_3d.rs | 10 ++--- examples/gizmos/2d_gizmos.rs | 2 + 10 files changed, 104 insertions(+), 59 deletions(-) diff --git a/crates/bevy_gizmos/src/billboard/billboards.wgsl b/crates/bevy_gizmos/src/billboard/billboards.wgsl index ff7c77bf53497..084edf97c4012 100644 --- a/crates/bevy_gizmos/src/billboard/billboards.wgsl +++ b/crates/bevy_gizmos/src/billboard/billboards.wgsl @@ -80,7 +80,8 @@ fn vertex(vertex: VertexInput) -> VertexOutput { var clip_position = vec4(clip.w * ((2. * screen) / resolution - 1.), depth, clip.w); - return VertexOutput(clip_position, color); + // return VertexOutput(clip_position, color); + return VertexOutput(vec4(position, 0.0, 0.), color); } struct FragmentOutput { diff --git a/crates/bevy_gizmos/src/billboard/mod.rs b/crates/bevy_gizmos/src/billboard/mod.rs index 3006265b34884..77aeb36af1dff 100644 --- a/crates/bevy_gizmos/src/billboard/mod.rs +++ b/crates/bevy_gizmos/src/billboard/mod.rs @@ -4,21 +4,35 @@ use bevy_app::{App, Last, Plugin}; use bevy_asset::{Asset, AssetApp, Assets, Handle}; use bevy_color::LinearRgba; use bevy_ecs::{ - component::Component, query::ROQueryItem, schedule::IntoSystemConfigs, system::{lifetimeless::{Read, SRes}, Commands, Res, ResMut, Resource, SystemParamItem} + component::Component, + query::ROQueryItem, + schedule::IntoSystemConfigs, + system::{ + lifetimeless::{Read, SRes}, + Commands, Res, ResMut, Resource, SystemParamItem, + }, }; use bevy_math::Vec3; use bevy_reflect::TypePath; use bevy_render::{ - extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin}, render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets}, render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass}, render_resource::{ - binding_types::uniform_buffer, BindGroup, BindGroupEntries, BindGroupLayout, BindGroupLayoutEntries, Buffer, BufferInitDescriptor, BufferUsages, Shader, ShaderStages, ShaderType, VertexAttribute, VertexBufferLayout, VertexFormat, VertexStepMode - }, renderer::RenderDevice, Extract, ExtractSchedule, Render, RenderApp, RenderSet + extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin}, + render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets}, + render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass}, + render_resource::{ + binding_types::uniform_buffer, BindGroup, BindGroupEntries, BindGroupLayout, + BindGroupLayoutEntries, Buffer, BufferInitDescriptor, BufferUsages, Shader, ShaderStages, + ShaderType, VertexAttribute, VertexBufferLayout, VertexFormat, VertexStepMode, + }, + renderer::RenderDevice, + Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; use bevy_utils::TypeIdMap; use bytemuck::cast_slice; use crate::{ config::{GizmoConfigGroup, GizmoConfigStore}, - gizmos::GizmoStorage, UpdateGizmoMeshes, + gizmos::GizmoStorage, + UpdateGizmoMeshes, }; #[cfg(all(feature = "bevy_sprite", feature = "bevy_render"))] @@ -38,7 +52,12 @@ impl Plugin for BillboardGizmoPlugin { #[cfg(feature = "bevy_render")] { use bevy_asset::load_internal_asset; - load_internal_asset!(app, BILLBOARD_SHADER_HANDLE, "billboards.wgsl", Shader::from_wgsl); + load_internal_asset!( + app, + BILLBOARD_SHADER_HANDLE, + "billboards.wgsl", + Shader::from_wgsl + ); } app.init_asset::() .init_resource::(); @@ -114,7 +133,10 @@ impl AppBillboardGizmoBuilder for App { handles.billboards.insert(TypeId::of::(), None); - self.add_systems(Last, update_gizmo_meshes::.in_set(UpdateGizmoMeshes)); + self.add_systems( + Last, + update_gizmo_meshes::.in_set(UpdateGizmoMeshes), + ); self } @@ -141,6 +163,11 @@ fn update_gizmo_meshes( if storage.list_positions.is_empty() { handles.billboards.insert(TypeId::of::(), None); } else if let Some(handle) = handles.billboards.get_mut(&TypeId::of::()) { + println!( + "took {} billboards from {}", + storage.billboard_positions.len(), + std::any::type_name::() + ); if let Some(handle) = handle { let billboards = billboard_gizmos.get_mut(handle.id()).unwrap(); @@ -340,6 +367,8 @@ impl RenderCommand

for DrawBillboardGizmo { billboard_gizmo.vertex_count }; + println!("Drawing {} billboard(s)", instances); + pass.draw(0..6, 0..instances); RenderCommandResult::Success diff --git a/crates/bevy_gizmos/src/billboard/pipeline_2d.rs b/crates/bevy_gizmos/src/billboard/pipeline_2d.rs index 2fe919e9d67a6..96c9c74174aed 100644 --- a/crates/bevy_gizmos/src/billboard/pipeline_2d.rs +++ b/crates/bevy_gizmos/src/billboard/pipeline_2d.rs @@ -1,12 +1,8 @@ -use crate::{ - config::GizmoMeshConfig, - GizmoRenderSystem, -}; use super::{ - billboard_gizmo_vertex_buffer_layouts, DrawBillboardGizmo, - GpuBillboardGizmo, - BillboardGizmoUniformBindgroupLayout, SetBillboardGizmoBindGroup, + billboard_gizmo_vertex_buffer_layouts, BillboardGizmoUniformBindgroupLayout, + DrawBillboardGizmo, GpuBillboardGizmo, SetBillboardGizmoBindGroup, }; +use crate::{config::GizmoMeshConfig, GizmoRenderSystem}; use bevy_app::{App, Plugin}; use bevy_core_pipeline::core_2d::{Transparent2d, CORE_2D_DEPTH_FORMAT}; @@ -175,7 +171,10 @@ fn queue_billboard_gizmos_2d( mut transparent_render_phases: ResMut>, mut views: Query<(Entity, &ExtractedView, &Msaa, Option<&RenderLayers>)>, ) { - let draw_function = draw_functions.read().get_id::().unwrap(); + let draw_function = draw_functions + .read() + .get_id::() + .unwrap(); for (view_entity, view, msaa, render_layers) in &mut views { let Some(transparent_phase) = transparent_render_phases.get_mut(&view_entity) else { @@ -194,9 +193,7 @@ fn queue_billboard_gizmos_2d( let pipeline = pipelines.specialize( &pipeline_cache, &pipeline, - BillboardGizmoPipelineKey { - mesh_key, - }, + BillboardGizmoPipelineKey { mesh_key }, ); transparent_phase.add(Transparent2d { diff --git a/crates/bevy_gizmos/src/billboard/pipeline_3d.rs b/crates/bevy_gizmos/src/billboard/pipeline_3d.rs index 4b8a68fcae8a3..b6ec370ae8d46 100644 --- a/crates/bevy_gizmos/src/billboard/pipeline_3d.rs +++ b/crates/bevy_gizmos/src/billboard/pipeline_3d.rs @@ -1,12 +1,8 @@ -use crate::{ - config::GizmoMeshConfig, - GizmoRenderSystem, -}; use super::{ - billboard_gizmo_vertex_buffer_layouts, DrawBillboardGizmo, - GpuBillboardGizmo, - BillboardGizmoUniformBindgroupLayout, SetBillboardGizmoBindGroup, + billboard_gizmo_vertex_buffer_layouts, BillboardGizmoUniformBindgroupLayout, + DrawBillboardGizmo, GpuBillboardGizmo, SetBillboardGizmoBindGroup, }; +use crate::{config::GizmoMeshConfig, GizmoRenderSystem}; use bevy_app::{App, Plugin}; use bevy_core_pipeline::{ core_3d::{Transparent3d, CORE_3D_DEPTH_FORMAT}, @@ -183,7 +179,10 @@ fn queue_billboard_gizmos_3d( ), )>, ) { - let draw_function = draw_functions.read().get_id::().unwrap(); + let draw_function = draw_functions + .read() + .get_id::() + .unwrap(); for ( view_entity, diff --git a/crates/bevy_gizmos/src/gizmos.rs b/crates/bevy_gizmos/src/gizmos.rs index fc962f02afad5..eee84688fceb0 100644 --- a/crates/bevy_gizmos/src/gizmos.rs +++ b/crates/bevy_gizmos/src/gizmos.rs @@ -58,7 +58,8 @@ where self.list_colors.extend(other.list_colors.iter()); self.strip_positions.extend(other.strip_positions.iter()); self.strip_colors.extend(other.strip_colors.iter()); - self.billboard_positions.extend(other.billboard_positions.iter()); + self.billboard_positions + .extend(other.billboard_positions.iter()); self.billboard_colors.extend(other.billboard_colors.iter()); } @@ -70,7 +71,10 @@ where mem::swap(&mut self.list_colors, &mut other.list_colors); mem::swap(&mut self.strip_positions, &mut other.strip_positions); mem::swap(&mut self.strip_colors, &mut other.strip_colors); - mem::swap(&mut self.billboard_positions, &mut other.billboard_positions); + mem::swap( + &mut self.billboard_positions, + &mut other.billboard_positions, + ); mem::swap(&mut self.billboard_colors, &mut other.billboard_colors); } @@ -279,7 +283,9 @@ where storage.list_colors.append(&mut self.list_colors); storage.strip_positions.append(&mut self.strip_positions); storage.strip_colors.append(&mut self.strip_colors); - storage.billboard_positions.append(&mut self.billboard_positions); + storage + .billboard_positions + .append(&mut self.billboard_positions); storage.billboard_colors.append(&mut self.billboard_colors); } } @@ -289,7 +295,7 @@ where Config: GizmoConfigGroup, Clear: 'static + Send + Sync, { - /// Draw a billboard in 3D at `position`. + /// Draw a billboard in 3D at `position`. /// /// This should be called for each frame the line needs to be rendered. /// diff --git a/crates/bevy_gizmos/src/lib.rs b/crates/bevy_gizmos/src/lib.rs index c192ac41d9acc..afc7fefbb2cd5 100644 --- a/crates/bevy_gizmos/src/lib.rs +++ b/crates/bevy_gizmos/src/lib.rs @@ -30,8 +30,8 @@ pub enum GizmoRenderSystem { QueueGizmos3d, } -mod lines; mod billboard; +mod lines; #[cfg(feature = "bevy_render")] pub mod aabb; @@ -42,10 +42,10 @@ pub mod config; pub mod cross; pub mod gizmos; pub mod grid; -pub mod primitives; -pub mod rounded_box; #[cfg(all(feature = "bevy_pbr", feature = "bevy_render"))] pub mod light; +pub mod primitives; +pub mod rounded_box; /// The `bevy_gizmos` prelude. pub mod prelude { @@ -73,9 +73,7 @@ use bevy_ecs::{ }; use bevy_time::Fixed; use billboard::{AppBillboardGizmoBuilder, BillboardGizmoPlugin}; -use config::{ - DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore, -}; +use config::{DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore}; use gizmos::{GizmoStorage, Swap}; #[cfg(all(feature = "bevy_pbr", feature = "bevy_render"))] use light::LightGizmoPlugin; @@ -134,8 +132,7 @@ impl AppGizmoBuilder for App { self.init_line_gizmo_group::(); self.init_billboard_gizmo_group::(); - self - .init_resource::>() + self.init_resource::>() .init_resource::>() .init_resource::>>() .add_systems( @@ -152,9 +149,7 @@ impl AppGizmoBuilder for App { ) .add_systems( Last, - ( - propagate_gizmos::.before(UpdateGizmoMeshes), - ), + (propagate_gizmos::.before(UpdateGizmoMeshes),), ); self @@ -167,7 +162,6 @@ impl AppGizmoBuilder for App { ) -> &mut Self { self.init_gizmo_group::(); - self.world_mut() .get_resource_or_insert_with::(Default::default) .insert(config, group); diff --git a/crates/bevy_gizmos/src/lines/mod.rs b/crates/bevy_gizmos/src/lines/mod.rs index 6483417983ab2..75d6b3fa60c11 100644 --- a/crates/bevy_gizmos/src/lines/mod.rs +++ b/crates/bevy_gizmos/src/lines/mod.rs @@ -6,21 +6,35 @@ use bevy_app::{App, Last, Plugin}; use bevy_asset::{Asset, AssetApp, Assets, Handle}; use bevy_color::LinearRgba; use bevy_ecs::{ - component::Component, query::ROQueryItem, schedule::IntoSystemConfigs, system::{lifetimeless::{Read, SRes}, Commands, Res, ResMut, Resource, SystemParamItem} + component::Component, + query::ROQueryItem, + schedule::IntoSystemConfigs, + system::{ + lifetimeless::{Read, SRes}, + Commands, Res, ResMut, Resource, SystemParamItem, + }, }; use bevy_math::Vec3; use bevy_reflect::TypePath; use bevy_render::{ - extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin}, render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets}, render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass}, render_resource::{ - binding_types::uniform_buffer, BindGroup, BindGroupEntries, BindGroupLayout, BindGroupLayoutEntries, Buffer, BufferInitDescriptor, BufferUsages, Shader, ShaderStages, ShaderType, VertexAttribute, VertexBufferLayout, VertexFormat, VertexStepMode - }, renderer::RenderDevice, Extract, ExtractSchedule, Render, RenderApp, RenderSet + extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin}, + render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets}, + render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass}, + render_resource::{ + binding_types::uniform_buffer, BindGroup, BindGroupEntries, BindGroupLayout, + BindGroupLayoutEntries, Buffer, BufferInitDescriptor, BufferUsages, Shader, ShaderStages, + ShaderType, VertexAttribute, VertexBufferLayout, VertexFormat, VertexStepMode, + }, + renderer::RenderDevice, + Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; use bevy_utils::TypeIdMap; use bytemuck::cast_slice; use crate::{ config::{GizmoConfigGroup, GizmoConfigStore, GizmoLineJoint}, - gizmos::GizmoStorage, UpdateGizmoMeshes, + gizmos::GizmoStorage, + UpdateGizmoMeshes, }; #[cfg(all(feature = "bevy_sprite", feature = "bevy_render"))] @@ -126,7 +140,10 @@ impl AppLineGizmoBuilder for App { handles.list.insert(TypeId::of::(), None); handles.strip.insert(TypeId::of::(), None); - self.add_systems(Last, update_gizmo_meshes::.in_set(UpdateGizmoMeshes)); + self.add_systems( + Last, + update_gizmo_meshes::.in_set(UpdateGizmoMeshes), + ); self } diff --git a/crates/bevy_gizmos/src/lines/pipeline_2d.rs b/crates/bevy_gizmos/src/lines/pipeline_2d.rs index e367475786c69..0452e2dd65eb4 100644 --- a/crates/bevy_gizmos/src/lines/pipeline_2d.rs +++ b/crates/bevy_gizmos/src/lines/pipeline_2d.rs @@ -1,12 +1,12 @@ +use super::{ + line_gizmo_vertex_buffer_layouts, line_joint_gizmo_vertex_buffer_layouts, DrawLineGizmo, + DrawLineJointGizmo, GpuLineGizmo, LineGizmo, LineGizmoUniformBindgroupLayout, + SetLineGizmoBindGroup, +}; use crate::{ config::{GizmoLineJoint, GizmoLineStyle, GizmoMeshConfig}, GizmoRenderSystem, }; -use super::{ - line_gizmo_vertex_buffer_layouts, line_joint_gizmo_vertex_buffer_layouts, DrawLineGizmo, - DrawLineJointGizmo, GpuLineGizmo, LineGizmo, - LineGizmoUniformBindgroupLayout, SetLineGizmoBindGroup, -}; use bevy_app::{App, Plugin}; use bevy_asset::Handle; use bevy_core_pipeline::core_2d::{Transparent2d, CORE_2D_DEPTH_FORMAT}; diff --git a/crates/bevy_gizmos/src/lines/pipeline_3d.rs b/crates/bevy_gizmos/src/lines/pipeline_3d.rs index 065dfaff027b2..7ea503e6240e9 100644 --- a/crates/bevy_gizmos/src/lines/pipeline_3d.rs +++ b/crates/bevy_gizmos/src/lines/pipeline_3d.rs @@ -1,12 +1,12 @@ +use super::{ + line_gizmo_vertex_buffer_layouts, line_joint_gizmo_vertex_buffer_layouts, DrawLineGizmo, + DrawLineJointGizmo, GpuLineGizmo, LineGizmo, LineGizmoUniformBindgroupLayout, + SetLineGizmoBindGroup, +}; use crate::{ config::{GizmoLineJoint, GizmoLineStyle, GizmoMeshConfig}, GizmoRenderSystem, }; -use super::{ - line_gizmo_vertex_buffer_layouts, line_joint_gizmo_vertex_buffer_layouts, DrawLineGizmo, - DrawLineJointGizmo, GpuLineGizmo, LineGizmo, - LineGizmoUniformBindgroupLayout, SetLineGizmoBindGroup, -}; use bevy_app::{App, Plugin}; use bevy_asset::Handle; use bevy_core_pipeline::{ diff --git a/examples/gizmos/2d_gizmos.rs b/examples/gizmos/2d_gizmos.rs index 23d07a8b1a06f..22a5e31e25861 100644 --- a/examples/gizmos/2d_gizmos.rs +++ b/examples/gizmos/2d_gizmos.rs @@ -73,6 +73,8 @@ fn draw_example_collection( FUCHSIA, ); + gizmos.billboard(Vec3::new(200., 160., 0.), RED); + my_gizmos .rounded_rect_2d(Isometry2d::IDENTITY, Vec2::splat(630.), BLACK) .corner_radius((time.elapsed_seconds() / 3.).cos() * 100.); From 55bdac935909126e8e6393180a20cc70c768cd51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lynn=20B=C3=BCttgenbach?= <62256001+lynn-lumen@users.noreply.github.com> Date: Thu, 29 Aug 2024 17:34:04 +0200 Subject: [PATCH 06/12] Fix shader --- .../bevy_gizmos/src/billboard/billboards.wgsl | 40 +++++++++---------- crates/bevy_gizmos/src/billboard/mod.rs | 7 ---- crates/bevy_gizmos/src/lib.rs | 2 +- 3 files changed, 19 insertions(+), 30 deletions(-) diff --git a/crates/bevy_gizmos/src/billboard/billboards.wgsl b/crates/bevy_gizmos/src/billboard/billboards.wgsl index 084edf97c4012..dc4533212ae03 100644 --- a/crates/bevy_gizmos/src/billboard/billboards.wgsl +++ b/crates/bevy_gizmos/src/billboard/billboards.wgsl @@ -26,7 +26,6 @@ struct VertexOutput { @location(0) color: vec4, }; -const SQRT2: f32 = 1.4142135; const EPSILON: f32 = 4.88e-04; @vertex @@ -39,49 +38,45 @@ fn vertex(vertex: VertexInput) -> VertexOutput { vec2(0.5, 0.5), vec2(0.5, -0.5) ); - let position = positions[vertex.index]; + var offset = positions[vertex.index]; - let clip = view.clip_from_world * vec4(vertex.position, 1.); + let center_clip = view.clip_from_world * vec4(vertex.position, 1.0); // vec4(offset.x * clip_size / aspect_ratio, offset.y * clip_size, 0., 0.); let resolution = view.viewport.zw; - let screen_center = resolution * (0.5 * clip.xy / clip.w + 0.5); + let screen_center = resolution * (0.5 * center_clip.xy / center_clip.w + 0.5); var color = vertex.color; - var billboard_size = billboard_gizmo.size; - var alpha = 1.; - #ifdef PERSPECTIVE - billboard_size /= clip.w; + billboard_size /= center_clip.w; #endif - - // Billboard size fade based on https://acegikmo.com/shapes/docs/#anti-aliasing - let abs_size = length(billboard_size); - if abs_size < SQRT2 { - color.a *= abs_size / SQRT2; + if billboard_size < 1. { + color.a *= billboard_size; billboard_size = 1.; } - let screen = screen_center + position * billboard_size; + let screen = screen_center + offset * billboard_size; var depth: f32; if billboard_gizmo.depth_bias >= 0. { - depth = clip.z * (1. - billboard_gizmo.depth_bias); + depth = center_clip.z * (1. - billboard_gizmo.depth_bias); } else { - // depth * (clip.w / depth)^-depth_bias. So that when -depth_bias is 1.0, this is equal to clip.w + // depth * (center_clip.w / depth)^-depth_bias. So that when -depth_bias is 1.0, this is equal to center_clip.w // and when equal to 0.0, it is exactly equal to depth. - // the epsilon is here to prevent the depth from exceeding clip.w when -depth_bias = 1.0 - // clip.w represents the near plane in homogeneous clip space in bevy, having a depth + // the epsilon is here to prevent the depth from exceeding center_clip.w when -depth_bias = 1.0 + // center_clip.w represents the near plane in homogeneous clip space in bevy, having a depth // of this value means nothing can be in front of this // The reason this uses an exponential function is that it makes it much easier for the // user to chose a value that is convenient for them - depth = clip.z * exp2(-billboard_gizmo.depth_bias * log2(clip.w / clip.z - EPSILON)); + depth = center_clip.z * exp2(-billboard_gizmo.depth_bias * log2(center_clip.w / center_clip.z - EPSILON)); } - var clip_position = vec4(clip.w * ((2. * screen) / resolution - 1.), depth, clip.w); + var clip_position = vec4(center_clip.w * ((2. * screen) / resolution - 1.), depth, center_clip.w); - // return VertexOutput(clip_position, color); - return VertexOutput(vec4(position, 0.0, 0.), color); + var result: VertexOutput; + result.clip_position = clip_position; + result.color = color; + return result; } struct FragmentOutput { @@ -90,5 +85,6 @@ struct FragmentOutput { @fragment fn fragment(in: VertexOutput) -> FragmentOutput { + // return FragmentOutput(vec4(1., 1., 1., 1.)); return FragmentOutput(in.color); } \ No newline at end of file diff --git a/crates/bevy_gizmos/src/billboard/mod.rs b/crates/bevy_gizmos/src/billboard/mod.rs index 77aeb36af1dff..75d223552a59f 100644 --- a/crates/bevy_gizmos/src/billboard/mod.rs +++ b/crates/bevy_gizmos/src/billboard/mod.rs @@ -163,11 +163,6 @@ fn update_gizmo_meshes( if storage.list_positions.is_empty() { handles.billboards.insert(TypeId::of::(), None); } else if let Some(handle) = handles.billboards.get_mut(&TypeId::of::()) { - println!( - "took {} billboards from {}", - storage.billboard_positions.len(), - std::any::type_name::() - ); if let Some(handle) = handle { let billboards = billboard_gizmos.get_mut(handle.id()).unwrap(); @@ -367,8 +362,6 @@ impl RenderCommand

for DrawBillboardGizmo { billboard_gizmo.vertex_count }; - println!("Drawing {} billboard(s)", instances); - pass.draw(0..6, 0..instances); RenderCommandResult::Success diff --git a/crates/bevy_gizmos/src/lib.rs b/crates/bevy_gizmos/src/lib.rs index afc7fefbb2cd5..8c7dbb71e4009 100644 --- a/crates/bevy_gizmos/src/lib.rs +++ b/crates/bevy_gizmos/src/lib.rs @@ -129,8 +129,8 @@ impl AppGizmoBuilder for App { .get_resource_or_insert_with::(Default::default) .register::(); - self.init_line_gizmo_group::(); self.init_billboard_gizmo_group::(); + self.init_line_gizmo_group::(); self.init_resource::>() .init_resource::>() From 8c35979a00f0b3541cc261603ed797efcc11c75c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lynn=20B=C3=BCttgenbach?= <62256001+lynn-lumen@users.noreply.github.com> Date: Sat, 31 Aug 2024 19:13:02 +0200 Subject: [PATCH 07/12] Add billboard 2d --- crates/bevy_gizmos/src/gizmos.rs | 40 +++++++++++++++++++++++++++----- examples/gizmos/2d_gizmos.rs | 2 -- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/crates/bevy_gizmos/src/gizmos.rs b/crates/bevy_gizmos/src/gizmos.rs index eee84688fceb0..caba57d8573ee 100644 --- a/crates/bevy_gizmos/src/gizmos.rs +++ b/crates/bevy_gizmos/src/gizmos.rs @@ -295,6 +295,28 @@ where Config: GizmoConfigGroup, Clear: 'static + Send + Sync, { + /// Draw a billboard in 2D at `position`. + /// + /// This should be called for each frame the line needs to be rendered. + /// + /// # Example + /// ``` + /// # use bevy_gizmos::prelude::*; + /// # use bevy_math::prelude::*; + /// # use bevy_color::palettes::basic::GREEN; + /// fn system(mut gizmos: Gizmos) { + /// gizmos.billboard(Vec2::ZERO, GREEN); + /// } + /// # bevy_ecs::system::assert_is_system(system); + /// ``` + pub fn billboard_2d(&mut self, position: Vec2, color: impl Into) { + if !self.enabled { + return; + } + self.buffer.billboard_positions.push(position.extend(0.)); + self.add_billboard_color(color); + } + /// Draw a billboard in 3D at `position`. /// /// This should be called for each frame the line needs to be rendered. @@ -314,9 +336,7 @@ where return; } self.buffer.billboard_positions.push(position); - let polymorphic_color: Color = color.into(); - let linear_color = LinearRgba::from(polymorphic_color); - self.buffer.billboard_colors.push(linear_color); + self.add_billboard_color(color); } /// Draw a line in 3D from `start` to `end`. @@ -339,7 +359,7 @@ where return; } self.extend_list_positions([start, end]); - self.add_list_color(color, 2); + self.extent_list_color(color, 2); } /// Draw a line in 3D with a color gradient from `start` to `end`. @@ -563,7 +583,7 @@ where ]; self.extend_list_positions(list_positions); - self.add_list_color(polymorphic_color, 6); + self.extent_list_color(polymorphic_color, 6); } /// Draw a line in 2D from `start` to `end`. @@ -768,7 +788,7 @@ where } #[inline] - fn add_list_color(&mut self, color: impl Into, count: usize) { + fn extent_list_color(&mut self, color: impl Into, count: usize) { let polymorphic_color: Color = color.into(); let linear_color = LinearRgba::from(polymorphic_color); @@ -777,6 +797,14 @@ where .extend(iter::repeat(linear_color).take(count)); } + #[inline] + fn add_billboard_color(&mut self, color: impl Into) { + let polymorphic_color: Color = color.into(); + let linear_color = LinearRgba::from(polymorphic_color); + + self.buffer.billboard_colors.push(linear_color); + } + #[inline] fn extend_strip_positions(&mut self, positions: impl IntoIterator) { self.buffer.strip_positions.extend(positions); diff --git a/examples/gizmos/2d_gizmos.rs b/examples/gizmos/2d_gizmos.rs index 22a5e31e25861..23d07a8b1a06f 100644 --- a/examples/gizmos/2d_gizmos.rs +++ b/examples/gizmos/2d_gizmos.rs @@ -73,8 +73,6 @@ fn draw_example_collection( FUCHSIA, ); - gizmos.billboard(Vec3::new(200., 160., 0.), RED); - my_gizmos .rounded_rect_2d(Isometry2d::IDENTITY, Vec2::splat(630.), BLACK) .corner_radius((time.elapsed_seconds() / 3.).cos() * 100.); From f8902b1ba0e8a555ca6fbfd8f21a07346657efd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lynn=20B=C3=BCttgenbach?= <62256001+lynn-lumen@users.noreply.github.com> Date: Sat, 31 Aug 2024 19:48:49 +0200 Subject: [PATCH 08/12] Fix doc tests --- crates/bevy_gizmos/src/gizmos.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_gizmos/src/gizmos.rs b/crates/bevy_gizmos/src/gizmos.rs index caba57d8573ee..3b8b82ae2ef10 100644 --- a/crates/bevy_gizmos/src/gizmos.rs +++ b/crates/bevy_gizmos/src/gizmos.rs @@ -305,7 +305,7 @@ where /// # use bevy_math::prelude::*; /// # use bevy_color::palettes::basic::GREEN; /// fn system(mut gizmos: Gizmos) { - /// gizmos.billboard(Vec2::ZERO, GREEN); + /// gizmos.billboard_2d(Vec2::ZERO, GREEN); /// } /// # bevy_ecs::system::assert_is_system(system); /// ``` From 59e63107df20a14494bb1868f691b346065ad277 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lynn=20B=C3=BCttgenbach?= <62256001+lynn-lumen@users.noreply.github.com> Date: Sat, 31 Aug 2024 21:57:37 +0200 Subject: [PATCH 09/12] Remove double configuration of sets --- .../bevy_gizmos/src/billboard/pipeline_2d.rs | 13 ++-------- .../bevy_gizmos/src/billboard/pipeline_3d.rs | 10 ++------ crates/bevy_gizmos/src/lib.rs | 24 +++++++++++++++++-- crates/bevy_gizmos/src/lines/pipeline_2d.rs | 13 ++-------- crates/bevy_gizmos/src/lines/pipeline_3d.rs | 10 ++------ 5 files changed, 30 insertions(+), 40 deletions(-) diff --git a/crates/bevy_gizmos/src/billboard/pipeline_2d.rs b/crates/bevy_gizmos/src/billboard/pipeline_2d.rs index 96c9c74174aed..e6b78db08a3cb 100644 --- a/crates/bevy_gizmos/src/billboard/pipeline_2d.rs +++ b/crates/bevy_gizmos/src/billboard/pipeline_2d.rs @@ -8,7 +8,7 @@ use bevy_core_pipeline::core_2d::{Transparent2d, CORE_2D_DEPTH_FORMAT}; use bevy_ecs::{ prelude::Entity, - schedule::{IntoSystemConfigs, IntoSystemSetConfigs}, + schedule::IntoSystemConfigs, system::{Query, Res, ResMut, Resource}, world::{FromWorld, World}, }; @@ -22,7 +22,7 @@ use bevy_render::{ render_resource::*, texture::BevyDefault, view::{ExtractedView, Msaa, RenderLayers, ViewTarget}, - Render, RenderApp, RenderSet, + Render, RenderApp, }; use bevy_sprite::{Mesh2dPipeline, Mesh2dPipelineKey, SetMesh2dViewBindGroup}; @@ -39,15 +39,6 @@ impl Plugin for BillboardGizmo2dPlugin { render_app .add_render_command::() .init_resource::>() - .configure_sets( - Render, - GizmoRenderSystem::QueueGizmos2d - .in_set(RenderSet::Queue) - .ambiguous_with(bevy_sprite::queue_sprites) - .ambiguous_with( - bevy_sprite::queue_material2d_meshes::, - ), - ) .add_systems( Render, queue_billboard_gizmos_2d diff --git a/crates/bevy_gizmos/src/billboard/pipeline_3d.rs b/crates/bevy_gizmos/src/billboard/pipeline_3d.rs index b6ec370ae8d46..3ec4a67590034 100644 --- a/crates/bevy_gizmos/src/billboard/pipeline_3d.rs +++ b/crates/bevy_gizmos/src/billboard/pipeline_3d.rs @@ -12,7 +12,7 @@ use bevy_core_pipeline::{ use bevy_ecs::{ prelude::Entity, query::Has, - schedule::{IntoSystemConfigs, IntoSystemSetConfigs}, + schedule::IntoSystemConfigs, system::{Query, Res, ResMut, Resource}, world::{FromWorld, World}, }; @@ -26,7 +26,7 @@ use bevy_render::{ render_resource::*, texture::BevyDefault, view::{ExtractedView, Msaa, RenderLayers, ViewTarget}, - Render, RenderApp, RenderSet, + Render, RenderApp, }; use super::BILLBOARD_SHADER_HANDLE; @@ -41,12 +41,6 @@ impl Plugin for BillboardGizmo3dPlugin { render_app .add_render_command::() .init_resource::>() - .configure_sets( - Render, - GizmoRenderSystem::QueueGizmos3d - .in_set(RenderSet::Queue) - .ambiguous_with(bevy_pbr::queue_material_meshes::), - ) .add_systems( Render, queue_billboard_gizmos_3d diff --git a/crates/bevy_gizmos/src/lib.rs b/crates/bevy_gizmos/src/lib.rs index 8c7dbb71e4009..4f108783046f3 100644 --- a/crates/bevy_gizmos/src/lib.rs +++ b/crates/bevy_gizmos/src/lib.rs @@ -68,9 +68,10 @@ pub mod prelude { use bevy_app::{App, FixedFirst, FixedLast, Last, Plugin, RunFixedMainLoop}; use bevy_ecs::{ - schedule::{IntoSystemConfigs, SystemSet}, + schedule::{IntoSystemConfigs, IntoSystemSetConfigs, SystemSet}, system::{Res, ResMut}, }; +use bevy_render::{Render, RenderSet}; use bevy_time::Fixed; use billboard::{AppBillboardGizmoBuilder, BillboardGizmoPlugin}; use config::{DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore}; @@ -87,6 +88,25 @@ pub struct GizmoPlugin; impl Plugin for GizmoPlugin { fn build(&self, app: &mut App) { + #[cfg(feature = "bevy_pbr")] + app.configure_sets( + Render, + GizmoRenderSystem::QueueGizmos3d + .in_set(RenderSet::Queue) + .ambiguous_with(bevy_pbr::queue_material_meshes::), + ); + + #[cfg(feature = "bevy_sprite")] + app.configure_sets( + Render, + GizmoRenderSystem::QueueGizmos2d + .in_set(RenderSet::Queue) + .ambiguous_with(bevy_sprite::queue_sprites) + .ambiguous_with( + bevy_sprite::queue_material2d_meshes::, + ), + ); + app.add_plugins(LineGizmoPlugin) .add_plugins(BillboardGizmoPlugin) .register_type::() @@ -106,7 +126,7 @@ impl Plugin for GizmoPlugin { pub trait AppGizmoBuilder { /// Registers [`GizmoConfigGroup`] in the app enabling the use of [Gizmos<Config>](crate::gizmos::Gizmos). /// - /// Configurations can be set using the [`GizmoConfigStore`] [`Resource`]. + /// Configurations can be set using the [`GizmoConfigStore`] [`Resource`](bevy_ecs::system::Resource). fn init_gizmo_group(&mut self) -> &mut Self; /// Insert a [`GizmoConfig`] into a specific [`GizmoConfigGroup`]. diff --git a/crates/bevy_gizmos/src/lines/pipeline_2d.rs b/crates/bevy_gizmos/src/lines/pipeline_2d.rs index 0452e2dd65eb4..15e65bdbd3661 100644 --- a/crates/bevy_gizmos/src/lines/pipeline_2d.rs +++ b/crates/bevy_gizmos/src/lines/pipeline_2d.rs @@ -13,7 +13,7 @@ use bevy_core_pipeline::core_2d::{Transparent2d, CORE_2D_DEPTH_FORMAT}; use bevy_ecs::{ prelude::Entity, - schedule::{IntoSystemConfigs, IntoSystemSetConfigs}, + schedule::IntoSystemConfigs, system::{Query, Res, ResMut, Resource}, world::{FromWorld, World}, }; @@ -27,7 +27,7 @@ use bevy_render::{ render_resource::*, texture::BevyDefault, view::{ExtractedView, Msaa, RenderLayers, ViewTarget}, - Render, RenderApp, RenderSet, + Render, RenderApp, }; use bevy_sprite::{Mesh2dPipeline, Mesh2dPipelineKey, SetMesh2dViewBindGroup}; use bevy_utils::tracing::error; @@ -47,15 +47,6 @@ impl Plugin for LineGizmo2dPlugin { .add_render_command::() .init_resource::>() .init_resource::>() - .configure_sets( - Render, - GizmoRenderSystem::QueueGizmos2d - .in_set(RenderSet::Queue) - .ambiguous_with(bevy_sprite::queue_sprites) - .ambiguous_with( - bevy_sprite::queue_material2d_meshes::, - ), - ) .add_systems( Render, (queue_line_gizmos_2d, queue_line_joint_gizmos_2d) diff --git a/crates/bevy_gizmos/src/lines/pipeline_3d.rs b/crates/bevy_gizmos/src/lines/pipeline_3d.rs index 7ea503e6240e9..63152529e27ae 100644 --- a/crates/bevy_gizmos/src/lines/pipeline_3d.rs +++ b/crates/bevy_gizmos/src/lines/pipeline_3d.rs @@ -17,7 +17,7 @@ use bevy_core_pipeline::{ use bevy_ecs::{ prelude::Entity, query::Has, - schedule::{IntoSystemConfigs, IntoSystemSetConfigs}, + schedule::IntoSystemConfigs, system::{Query, Res, ResMut, Resource}, world::{FromWorld, World}, }; @@ -31,7 +31,7 @@ use bevy_render::{ render_resource::*, texture::BevyDefault, view::{ExtractedView, Msaa, RenderLayers, ViewTarget}, - Render, RenderApp, RenderSet, + Render, RenderApp, }; use bevy_utils::tracing::error; @@ -49,12 +49,6 @@ impl Plugin for LineGizmo3dPlugin { .add_render_command::() .init_resource::>() .init_resource::>() - .configure_sets( - Render, - GizmoRenderSystem::QueueGizmos3d - .in_set(RenderSet::Queue) - .ambiguous_with(bevy_pbr::queue_material_meshes::), - ) .add_systems( Render, (queue_line_gizmos_3d, queue_line_joint_gizmos_3d) From 683aaef71ecb5d7f585bc79a719a8829ae7cc776 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lynn=20B=C3=BCttgenbach?= <62256001+lynn-lumen@users.noreply.github.com> Date: Sat, 31 Aug 2024 22:14:04 +0200 Subject: [PATCH 10/12] Fix format --- crates/bevy_gizmos/src/lib.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/crates/bevy_gizmos/src/lib.rs b/crates/bevy_gizmos/src/lib.rs index 4f108783046f3..205f2e40c9bf1 100644 --- a/crates/bevy_gizmos/src/lib.rs +++ b/crates/bevy_gizmos/src/lib.rs @@ -94,17 +94,15 @@ impl Plugin for GizmoPlugin { GizmoRenderSystem::QueueGizmos3d .in_set(RenderSet::Queue) .ambiguous_with(bevy_pbr::queue_material_meshes::), - ); - + ); + #[cfg(feature = "bevy_sprite")] app.configure_sets( Render, GizmoRenderSystem::QueueGizmos2d .in_set(RenderSet::Queue) .ambiguous_with(bevy_sprite::queue_sprites) - .ambiguous_with( - bevy_sprite::queue_material2d_meshes::, - ), + .ambiguous_with(bevy_sprite::queue_material2d_meshes::), ); app.add_plugins(LineGizmoPlugin) From 39cb22624e158fff48496b44eb823bb33daf0e5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lynn=20B=C3=BCttgenbach?= <62256001+lynn-lumen@users.noreply.github.com> Date: Sun, 1 Sep 2024 13:51:30 +0200 Subject: [PATCH 11/12] Fix format --- crates/bevy_gizmos/src/billboard/billboards.wgsl | 3 +-- crates/bevy_gizmos/src/billboard/mod.rs | 2 +- crates/bevy_gizmos/src/lib.rs | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/bevy_gizmos/src/billboard/billboards.wgsl b/crates/bevy_gizmos/src/billboard/billboards.wgsl index dc4533212ae03..a961c2e48a363 100644 --- a/crates/bevy_gizmos/src/billboard/billboards.wgsl +++ b/crates/bevy_gizmos/src/billboard/billboards.wgsl @@ -40,7 +40,7 @@ fn vertex(vertex: VertexInput) -> VertexOutput { ); var offset = positions[vertex.index]; - let center_clip = view.clip_from_world * vec4(vertex.position, 1.0); // vec4(offset.x * clip_size / aspect_ratio, offset.y * clip_size, 0., 0.); + let center_clip = view.clip_from_world * vec4(vertex.position, 1.0); let resolution = view.viewport.zw; let screen_center = resolution * (0.5 * center_clip.xy / center_clip.w + 0.5); @@ -85,6 +85,5 @@ struct FragmentOutput { @fragment fn fragment(in: VertexOutput) -> FragmentOutput { - // return FragmentOutput(vec4(1., 1., 1., 1.)); return FragmentOutput(in.color); } \ No newline at end of file diff --git a/crates/bevy_gizmos/src/billboard/mod.rs b/crates/bevy_gizmos/src/billboard/mod.rs index 75d223552a59f..b087209a58cab 100644 --- a/crates/bevy_gizmos/src/billboard/mod.rs +++ b/crates/bevy_gizmos/src/billboard/mod.rs @@ -160,7 +160,7 @@ fn update_gizmo_meshes( mut handles: ResMut, mut storage: ResMut>, ) { - if storage.list_positions.is_empty() { + if storage.billboard_positions.is_empty() { handles.billboards.insert(TypeId::of::(), None); } else if let Some(handle) = handles.billboards.get_mut(&TypeId::of::()) { if let Some(handle) = handle { diff --git a/crates/bevy_gizmos/src/lib.rs b/crates/bevy_gizmos/src/lib.rs index 205f2e40c9bf1..927cc866e5071 100644 --- a/crates/bevy_gizmos/src/lib.rs +++ b/crates/bevy_gizmos/src/lib.rs @@ -88,7 +88,7 @@ pub struct GizmoPlugin; impl Plugin for GizmoPlugin { fn build(&self, app: &mut App) { - #[cfg(feature = "bevy_pbr")] + #[cfg(all(feature = "bevy_pbr", feature = "bevy_render"))] app.configure_sets( Render, GizmoRenderSystem::QueueGizmos3d @@ -96,7 +96,7 @@ impl Plugin for GizmoPlugin { .ambiguous_with(bevy_pbr::queue_material_meshes::), ); - #[cfg(feature = "bevy_sprite")] + #[cfg(all(feature = "bevy_sprite", feature = "bevy_render"))] app.configure_sets( Render, GizmoRenderSystem::QueueGizmos2d From 43f4ca9a1e5bd6ae2c86c369925549b6ac030508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lynn=20B=C3=BCttgenbach?= <62256001+lynn-lumen@users.noreply.github.com> Date: Wed, 4 Sep 2024 21:49:22 +0200 Subject: [PATCH 12/12] Fix ambiguities pls :) --- crates/bevy_gizmos/src/billboard/mod.rs | 3 +++ crates/bevy_gizmos/src/lib.rs | 5 +++-- crates/bevy_gizmos/src/lines/mod.rs | 3 +++ examples/gizmos/2d_gizmos.rs | 2 ++ 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/crates/bevy_gizmos/src/billboard/mod.rs b/crates/bevy_gizmos/src/billboard/mod.rs index b087209a58cab..ee96f2be8b778 100644 --- a/crates/bevy_gizmos/src/billboard/mod.rs +++ b/crates/bevy_gizmos/src/billboard/mod.rs @@ -62,6 +62,9 @@ impl Plugin for BillboardGizmoPlugin { app.init_asset::() .init_resource::(); + // These handles are safe to mutate in any order + app.allow_ambiguous_resource::(); + #[cfg(feature = "bevy_render")] app.add_plugins(UniformComponentPlugin::::default()) .add_plugins(RenderAssetPlugin::::default()); diff --git a/crates/bevy_gizmos/src/lib.rs b/crates/bevy_gizmos/src/lib.rs index bac5b8211b613..326e794dfdb56 100644 --- a/crates/bevy_gizmos/src/lib.rs +++ b/crates/bevy_gizmos/src/lib.rs @@ -151,8 +151,9 @@ impl AppGizmoBuilder for App { self.init_billboard_gizmo_group::(); self.init_line_gizmo_group::(); - // These handles are safe to mutate in any order - self.allow_ambiguous_resource::(); + self.allow_ambiguous_resource::>(); + self.allow_ambiguous_resource::>(); + self.allow_ambiguous_resource::>>(); self.init_resource::>() .init_resource::>() diff --git a/crates/bevy_gizmos/src/lines/mod.rs b/crates/bevy_gizmos/src/lines/mod.rs index 75d6b3fa60c11..dcf7319ff0ccb 100644 --- a/crates/bevy_gizmos/src/lines/mod.rs +++ b/crates/bevy_gizmos/src/lines/mod.rs @@ -68,6 +68,9 @@ impl Plugin for LineGizmoPlugin { app.init_asset::() .init_resource::(); + // These handles are safe to mutate in any order + app.allow_ambiguous_resource::(); + #[cfg(feature = "bevy_render")] app.add_plugins(UniformComponentPlugin::::default()) .add_plugins(RenderAssetPlugin::::default()); diff --git a/examples/gizmos/2d_gizmos.rs b/examples/gizmos/2d_gizmos.rs index 0fa75863d50f8..a77750a4a1faa 100644 --- a/examples/gizmos/2d_gizmos.rs +++ b/examples/gizmos/2d_gizmos.rs @@ -82,6 +82,8 @@ fn draw_example_collection( .map(|t| (t, TEAL.mix(&HOT_PINK, (t + 300.0) / 600.0))); gizmos.curve_gradient_2d(curve, times_and_colors); + gizmos.billboard_2d(Vec2::new(200., 160.), RED); + my_gizmos .rounded_rect_2d(Isometry2d::IDENTITY, Vec2::splat(630.), BLACK) .corner_radius((time.elapsed_seconds() / 3.).cos() * 100.);