diff --git a/src/context.rs b/src/context.rs index ac9b8b8e36bdf32896d70b976966ce47c12eb691..653b035eb6d96b70dd5f4485cf7cff41ea29f040 100644 --- a/src/context.rs +++ b/src/context.rs @@ -90,17 +90,18 @@ pub struct KayakRootContext { pub(crate) order_tree: Arc<RwLock<Tree>>, pub(crate) index: Arc<RwLock<HashMap<Entity, usize>>>, pub(crate) uninitilized_systems: HashSet<String>, + pub camera_entity: Entity, } impl Default for KayakRootContext { fn default() -> Self { - Self::new() + Self::new(Entity::from_raw(0)) } } impl KayakRootContext { /// Creates a new widget context. - pub fn new() -> Self { + pub fn new(camera_entity: Entity) -> Self { Self { tree: Arc::new(RwLock::new(Tree::default())), layout_cache: Arc::new(RwLock::new(LayoutCache::default())), @@ -115,6 +116,7 @@ impl KayakRootContext { index: Default::default(), order_tree: Default::default(), uninitilized_systems: Default::default(), + camera_entity, } } @@ -483,7 +485,7 @@ fn update_widgets_sys(world: &mut World) { world, ); - for (camera_entity, mut context) in context_data.drain(..) { + for (entity, mut context) in context_data.drain(..) { for system_id in context.uninitilized_systems.drain() { if let Some(system) = context.systems.get_mut(&system_id) { system.0.initialize(world); @@ -516,7 +518,7 @@ fn update_widgets_sys(world: &mut World) { // dbg!("Updating widgets!"); update_widgets( - camera_entity, + context.camera_entity, world, &context.tree, &context.layout_cache, @@ -564,7 +566,7 @@ fn update_widgets_sys(world: &mut World) { indices.clear(); } - world.entity_mut(camera_entity).insert(context); + world.entity_mut(entity).insert(context); } } @@ -586,14 +588,14 @@ fn update_widgets( ) { for entity in widgets.iter() { // A small hack to add parents to widgets - let mut command_queue = CommandQueue::default(); - { - let mut commands = Commands::new(&mut command_queue, &world); - if let Some(mut entity_commands) = commands.get_entity(entity.0) { - entity_commands.set_parent(camera_entity); - } - } - command_queue.apply(world); + // let mut command_queue = CommandQueue::default(); + // { + // let mut commands = Commands::new(&mut command_queue, &world); + // if let Some(mut entity_commands) = commands.get_entity(entity.0) { + // entity_commands.set_parent(camera_entity); + // } + // } + // command_queue.apply(world); if let Some(entity_ref) = world.get_entity(entity.0) { if let Some(widget_type) = entity_ref.get::<WidgetName>() { @@ -808,6 +810,7 @@ fn update_widget( let new_tick = widget_update_system.get_last_change_tick(); new_ticks.insert(widget_type.clone(), new_tick); widget_update_system.set_last_change_tick(old_tick); + widget_update_system.apply_buffers(world); if should_rerender { if let Ok(cloned_widget_entities) = cloned_widget_entities.try_read() { @@ -912,7 +915,7 @@ fn update_widget( // Mark node as needing a recalculation of rendering/layout. commands.entity(entity.0).insert(DirtyNode); - for (_index, changed_entity, _parent, changes) in diff.changes.iter() { + for (_index, changed_entity, parent, changes) in diff.changes.iter() { if changes.iter().any(|change| *change == Change::Deleted) { // commands.entity(changed_entity.0).despawn(); // commands.entity(changed_entity.0).remove::<DirtyNode>(); @@ -921,6 +924,10 @@ fn update_widget( if changes.iter().any(|change| *change == Change::Inserted) { if let Some(mut entity_commands) = commands.get_entity(changed_entity.0) { entity_commands.insert(Mounted); + entity_commands.set_parent(parent.0); + } + if let Some(mut parent_commands) = commands.get_entity(parent.0) { + parent_commands.add_child(changed_entity.0); } } } diff --git a/src/lib.rs b/src/lib.rs index d6e61f6eab0c82e6bbe55c357cc04a1579d96faf..3f478f47f4ddfcfc3ec46b1ce935af96c79e3d5b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,6 +62,7 @@ pub mod prelude { pub use crate::widgets; pub use kayak_font::Alignment; pub use kayak_ui_macros::{constructor, rsx}; + pub use crate::render::draw_ui_graph; } pub use focus_tree::Focusable; diff --git a/src/render/extract.rs b/src/render/extract.rs index e0a4ded369621cd31000a147291965289020d94e..3b2a2f6103ceacc5c9a1bb92b0131390466142e3 100644 --- a/src/render/extract.rs +++ b/src/render/extract.rs @@ -2,12 +2,12 @@ use crate::{ context::{KayakRootContext, WidgetName}, node::Node, render_primitive::RenderPrimitive, - styles::Corner, + styles::Corner, CameraUIKayak, }; use bevy::{ - prelude::{Assets, Camera, Color, Commands, Entity, Image, Plugin, Query, Rect, Res, Vec2}, - render::{Extract, RenderApp, RenderStage}, - window::Windows, + prelude::*, + render::{Extract, RenderApp, RenderStage, view::ExtractedView, render_phase::RenderPhase}, + window::Windows, ui::TransparentUi, }; use kayak_font::KayakFont; @@ -26,33 +26,40 @@ impl Plugin for BevyKayakUIExtractPlugin { fn build(&self, app: &mut bevy::prelude::App) { let render_app = app.sub_app_mut(RenderApp); render_app.add_system_to_stage(RenderStage::Extract, extract); + render_app.add_system_to_stage(RenderStage::Extract, extract_default_ui_camera_view::<Camera2d>); + render_app.add_system_to_stage(RenderStage::Extract, extract_default_ui_camera_view::<Camera3d>); } } pub fn extract( mut commands: Commands, - context_query: Extract<Query<(Entity, &KayakRootContext, &Camera)>>, + context_query: Extract<Query<(Entity, &KayakRootContext)>>, fonts: Extract<Res<Assets<KayakFont>>>, font_mapping: Extract<Res<FontMapping>>, node_query: Extract<Query<&Node>>, widget_names: Extract<Query<&WidgetName>>, images: Extract<Res<Assets<Image>>>, windows: Extract<Res<Windows>>, + cameras: Extract<Query<&Camera>>, ) { let mut render_primitives = Vec::new(); - for (entity, context, camera) in context_query.iter() { - let dpi = match &camera.target { - bevy::render::camera::RenderTarget::Window(window_id) => { - if let Some(window) = windows.get(*window_id) { - window.scale_factor() as f32 - } else { - 1.0 + for (_, context) in context_query.iter() { + let dpi = if let Ok(camera) = cameras.get(context.camera_entity) { + match &camera.target { + bevy::render::camera::RenderTarget::Window(window_id) => { + if let Some(window) = windows.get(*window_id) { + window.scale_factor() as f32 + } else { + 1.0 + } } + _ => 1.0, } - _ => 1.0, + } else { + 1.0 }; let mut new_render_primitives = context.build_render_primitives(&node_query, &widget_names); - render_primitives.extend(new_render_primitives.drain(..).map(|r| (entity, dpi, r))); + render_primitives.extend(new_render_primitives.drain(..).map(|r| (context.camera_entity, dpi, r))); } let mut extracted_quads = Vec::new(); @@ -120,3 +127,48 @@ pub fn extract( // dbg!(&extracted_quads); commands.spawn_batch(extracted_quads); } + +#[derive(Component)] +pub struct DefaultCameraView(pub Entity); + + +const UI_CAMERA_TRANSFORM_OFFSET: f32 = -0.1; + +pub fn extract_default_ui_camera_view<T: Component>( + mut commands: Commands, + query: Extract<Query<(Entity, &Camera, &CameraUIKayak), With<T>>>, +) { + for (entity, camera, camera_ui) in &query { + + if let (Some(logical_size), Some((physical_origin, _)), Some(physical_size)) = ( + camera.logical_viewport_size(), + camera.physical_viewport_rect(), + camera.physical_viewport_size(), + ) { + // use a projection matrix with the origin in the top left instead of the bottom left that comes with OrthographicProjection + let projection_matrix = + Mat4::orthographic_rh(0.0, logical_size.x, logical_size.y, 0.0, 0.0, 1000.0); + let default_camera_view = commands + .spawn(ExtractedView { + projection: projection_matrix, + transform: GlobalTransform::from_xyz( + 0.0, + 0.0, + 1000.0 + UI_CAMERA_TRANSFORM_OFFSET, + ), + hdr: camera.hdr, + viewport: UVec4::new( + physical_origin.x, + physical_origin.y, + physical_size.x, + physical_size.y, + ), + }) + .id(); + commands.get_or_spawn(entity).insert(( + DefaultCameraView(default_camera_view), + RenderPhase::<TransparentUi>::default(), + )); + } + } +} \ No newline at end of file diff --git a/src/render/mod.rs b/src/render/mod.rs index 67dbf1cfd342bc94164963625f34aa1027682ca9..422a2c0093bc7f31dff23d32a419ea923d9e5509 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -94,7 +94,7 @@ impl Plugin for BevyKayakUIRenderPlugin { .unwrap(); graph_2d .add_node_edge( - bevy::core_pipeline::core_2d::graph::node::TONEMAPPING, + bevy::core_pipeline::core_2d::graph::node::END_MAIN_PASS_POST_PROCESSING, draw_ui_graph::node::MAIN_PASS, ) .unwrap(); @@ -120,7 +120,7 @@ impl Plugin for BevyKayakUIRenderPlugin { .unwrap(); graph_3d .add_node_edge( - bevy::core_pipeline::core_3d::graph::node::TONEMAPPING, + bevy::core_pipeline::core_3d::graph::node::END_MAIN_PASS_POST_PROCESSING, draw_ui_graph::node::MAIN_PASS, ) .unwrap(); diff --git a/src/render/ui_pass.rs b/src/render/ui_pass.rs index 70ed3337c8b8b7e7967a16bdbfcd4926c5d7d24b..d9310bfdd146cab07b7c12369d471bb3b2b4217d 100644 --- a/src/render/ui_pass.rs +++ b/src/render/ui_pass.rs @@ -14,6 +14,8 @@ use bevy::utils::FloatOrd; use crate::CameraUIKayak; +use super::extract::DefaultCameraView; + pub struct TransparentUI { pub sort_key: FloatOrd, pub entity: Entity, @@ -44,6 +46,7 @@ pub struct MainPassUINode { ), With<ExtractedView>, >, + default_camera_view_query: QueryState<&'static DefaultCameraView>, } impl MainPassUINode { @@ -52,6 +55,7 @@ impl MainPassUINode { pub fn new(world: &mut World) -> Self { Self { query: world.query_filtered(), + default_camera_view_query: world.query(), } } } @@ -63,6 +67,7 @@ impl Node for MainPassUINode { fn update(&mut self, world: &mut World) { self.query.update_archetypes(world); + self.default_camera_view_query.update_archetypes(world); } fn run( @@ -71,26 +76,37 @@ impl Node for MainPassUINode { render_context: &mut RenderContext, world: &World, ) -> Result<(), NodeRunError> { - let view_entity = graph.get_input_entity(Self::IN_VIEW)?; + let input_view_entity = graph.get_input_entity(Self::IN_VIEW)?; // adapted from bevy itself; // see: <https://github.com/bevyengine/bevy/commit/09a3d8abe062984479bf0e99fcc1508bb722baf6> - let (transparent_phase, target, camera_ui) = match self.query.get_manual(world, view_entity) + let (transparent_phase, target, _camera_ui) = match self.query.get_manual(world, input_view_entity) { Ok(it) => it, _ => return Ok(()), }; + + let view_entity = if let Ok(default_view) = self + .default_camera_view_query + .get_manual(world, input_view_entity) + { + default_view.0 + } else { + input_view_entity + }; + // let clear_color = world.get_resource::<ClearColor>().unwrap(); { let pass_descriptor = RenderPassDescriptor { label: Some("main_transparent_pass_UI"), color_attachments: &[Some(target.get_unsampled_color_attachment(Operations { - load: match camera_ui.clear_color { - ClearColorConfig::Default => { - LoadOp::Clear(world.resource::<ClearColor>().0.into()) - } - ClearColorConfig::Custom(color) => LoadOp::Clear(color.into()), - ClearColorConfig::None => LoadOp::Load, - }, + // load: match camera_ui.clear_color { + // ClearColorConfig::Default => { + // LoadOp::Clear(world.resource::<ClearColor>().0.into()) + // } + // ClearColorConfig::Custom(color) => LoadOp::Clear(color.into()), + // ClearColorConfig::None => LoadOp::Load, + // }, + load: LoadOp::Load, store: true, }))], depth_stencil_attachment: None, diff --git a/src/render/unified/pipeline.rs b/src/render/unified/pipeline.rs index 4d5aa330e4da2f9191d55a252257e925a26ee7c8..5d2cfc942c68e546fc129ee959b7b553e05c25c9 100644 --- a/src/render/unified/pipeline.rs +++ b/src/render/unified/pipeline.rs @@ -2,6 +2,7 @@ use bevy::prelude::{Msaa, Rect, Resource}; use bevy::render::render_resource::{ DynamicUniformBuffer, ShaderType, SpecializedRenderPipeline, SpecializedRenderPipelines, }; +use bevy::render::view::{ViewTarget, ExtractedView}; use bevy::utils::FloatOrd; use bevy::{ ecs::system::{ @@ -67,27 +68,10 @@ impl FontRenderingPipeline for UnifiedPipeline { } } -bitflags::bitflags! { - #[repr(transparent)] - pub struct UnifiedPipelineKey: u32 { - const NONE = 0; - const MSAA_RESERVED_BITS = Self::MSAA_MASK_BITS << Self::MSAA_SHIFT_BITS; - } -} - -impl UnifiedPipelineKey { - const MSAA_MASK_BITS: u32 = 0b111; - const MSAA_SHIFT_BITS: u32 = 32 - Self::MSAA_MASK_BITS.count_ones(); - - pub fn from_msaa_samples(msaa_samples: u32) -> Self { - let msaa_bits = - (msaa_samples.trailing_zeros() & Self::MSAA_MASK_BITS) << Self::MSAA_SHIFT_BITS; - Self::from_bits(msaa_bits).unwrap() - } - - pub fn msaa_samples(&self) -> u32 { - 1 << ((self.bits >> Self::MSAA_SHIFT_BITS) & Self::MSAA_MASK_BITS) - } +#[derive(Debug, Component, Clone, Copy, PartialEq, Eq, Hash)] +pub struct UnifiedPipelineKey { + pub msaa: u32, + pub hdr: bool, } impl FromWorld for UnifiedPipeline { @@ -242,7 +226,7 @@ impl FromWorld for UnifiedPipeline { impl SpecializedRenderPipeline for UnifiedPipeline { type Key = UnifiedPipelineKey; - fn specialize(&self, _key: Self::Key) -> RenderPipelineDescriptor { + fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { let vertex_buffer_layout = VertexBufferLayout { array_stride: 60, step_mode: VertexStepMode::Vertex, @@ -282,7 +266,11 @@ impl SpecializedRenderPipeline for UnifiedPipeline { shader_defs: vec![], entry_point: "fragment".into(), targets: vec![Some(ColorTargetState { - format: TextureFormat::bevy_default(), + format: if key.hdr { + ViewTarget::TEXTURE_FORMAT_HDR + } else { + TextureFormat::bevy_default() + }, blend: Some(BlendState { color: BlendComponent { src_factor: BlendFactor::SrcAlpha, @@ -315,7 +303,7 @@ impl SpecializedRenderPipeline for UnifiedPipeline { }, depth_stencil: None, multisample: MultisampleState { - count: 1, + count: key.msaa, mask: !0, alpha_to_coverage_enabled: false, }, @@ -534,7 +522,7 @@ pub fn queue_quads( mut pipelines: ResMut<SpecializedRenderPipelines<UnifiedPipeline>>, mut pipeline_cache: ResMut<PipelineCache>, mut extracted_sprites: Query<(Entity, &ExtractedQuad)>, - mut views: Query<(Entity, &mut RenderPhase<TransparentUI>)>, + mut views: Query<(Entity, &mut RenderPhase<TransparentUI>, &ExtractedView)>, mut image_bind_groups: ResMut<ImageBindGroups>, unified_pipeline: Res<UnifiedPipeline>, gpu_images: Res<RenderAssets<Image>>, @@ -562,15 +550,9 @@ pub fn queue_quads( layout: &quad_pipeline.view_layout, })); - let key = UnifiedPipelineKey::from_msaa_samples(msaa.samples); - let spec_pipeline = pipelines.specialize(&mut pipeline_cache, &quad_pipeline, key); - let draw_quad = draw_functions.read().get_id::<DrawUI>().unwrap(); - for (camera_entity, mut transparent_phase) in views.iter_mut() { + for (camera_entity, mut transparent_phase, view) in views.iter_mut() { for (entity, quad) in extracted_sprites.iter_mut() { - if quad.camera_entity != camera_entity { - continue; - } if let Some(image_handle) = quad.image.as_ref() { if let Some(gpu_image) = gpu_images.get(image_handle) { image_bind_groups @@ -596,6 +578,12 @@ pub fn queue_quads( }); } } + let key = UnifiedPipelineKey { + msaa: 1, + hdr: view.hdr, + }; + let spec_pipeline = pipelines.specialize(&mut pipeline_cache, &quad_pipeline, key); + transparent_phase.add(TransparentUI { draw_function: draw_quad, pipeline: spec_pipeline, diff --git a/src/widget_context.rs b/src/widget_context.rs index c6e64c770389ec4737fb9c45fd6fd9ada62f9f69..5f5b95ed41c68cc876f0abd06ff53e345c68d4b0 100644 --- a/src/widget_context.rs +++ b/src/widget_context.rs @@ -225,6 +225,21 @@ impl KayakWidgetContext { } } + // Despawns a widget entity and it's decedents. This is done in a safe way by keeping entity id's around. + pub fn despawn_safe(&self, commands: &mut Commands, entity: Entity) { + if let Ok(mut tree) = self.old_tree.write() { + let mut down_iter = tree.down_iter(); + down_iter.current_node = Some(WrappedIndex(entity)); + for child in down_iter { + commands.entity(child.0).despawn(); + commands.get_or_spawn(child.0); + } + commands.entity(entity).despawn(); + commands.get_or_spawn(entity); + tree.remove(WrappedIndex(entity)); + } + } + /// Attempts to get the layout rect for the widget with the given ID /// /// # Arguments