use bevy::{ ecs::{ query::ROQueryItem, system::{ lifetimeless::{Read, SRes}, SystemParamItem, }, }, prelude::*, render::{ render_asset::RenderAssets, render_phase::{ DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult, SetItemPipeline, TrackedRenderPass, }, render_resource::{ AsBindGroupError, BindGroup, BindGroupLayout, OwnedBindingResource, PipelineCache, RenderPipelineDescriptor, ShaderRef, SpecializedRenderPipeline, SpecializedRenderPipelines, }, renderer::{RenderDevice, RenderQueue}, texture::FallbackImage, Extract, }, utils::{FloatOrd, HashMap, HashSet}, }; use kayak_font::bevy::FontTextureCache; use std::hash::Hash; use std::marker::PhantomData; #[cfg(feature = "svg")] use crate::render::svg::RenderSvgs; use crate::render::{ extract::UIExtractedView, opacity_layer::OpacityLayerManager, ui_pass::{TransparentOpacityUI, TransparentUI, UIRenderPhase}, unified::pipeline::{ queue_quads_inner, DrawUIDraw, ExtractedQuad, ImageBindGroups, MaterialZ, PreviousClip, PreviousIndex, QuadBatch, QuadMeta, QuadTypeOffsets, SetUIViewBindGroup, UIQuadType, UnifiedPipeline, UnifiedPipelineKey, }, }; use super::{key::MaterialUIKey, MaterialUI}; /// Render pipeline data for a given [`MaterialUI`] #[derive(Resource)] pub struct MaterialUIPipeline<M: MaterialUI> { pub unified_pipeline: UnifiedPipeline, pub material_ui_layout: BindGroupLayout, pub vertex_shader: Option<Handle<Shader>>, pub fragment_shader: Option<Handle<Shader>>, marker: PhantomData<M>, } impl<M: MaterialUI> Clone for MaterialUIPipeline<M> { fn clone(&self) -> Self { Self { unified_pipeline: self.unified_pipeline.clone(), material_ui_layout: self.material_ui_layout.clone(), vertex_shader: self.vertex_shader.clone(), fragment_shader: self.fragment_shader.clone(), marker: PhantomData, } } } impl<M: MaterialUI> SpecializedRenderPipeline for MaterialUIPipeline<M> where M::Data: PartialEq + Eq + Hash + Clone, { type Key = MaterialUIKey<M>; fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { let mut descriptor = self.unified_pipeline.specialize(key.unified_key); if let Some(vertex_shader) = &self.vertex_shader { descriptor.vertex.shader = vertex_shader.clone(); } if let Some(fragment_shader) = &self.fragment_shader { descriptor.fragment.as_mut().unwrap().shader = fragment_shader.clone(); } descriptor.layout = vec![ self.unified_pipeline.view_layout.clone(), self.unified_pipeline.image_layout.clone(), self.unified_pipeline.types_layout.clone(), self.material_ui_layout.clone(), ]; M::specialize(&mut descriptor, key); descriptor } } impl<M: MaterialUI> FromWorld for MaterialUIPipeline<M> { fn from_world(world: &mut World) -> Self { let asset_server = world.resource::<AssetServer>(); let render_device = world.resource::<RenderDevice>(); let material_ui_layout = M::bind_group_layout(render_device); MaterialUIPipeline { unified_pipeline: world.resource::<UnifiedPipeline>().clone(), material_ui_layout, vertex_shader: match M::vertex_shader() { ShaderRef::Default => None, ShaderRef::Handle(handle) => Some(handle), ShaderRef::Path(path) => Some(asset_server.load(path)), }, fragment_shader: match M::fragment_shader() { ShaderRef::Default => None, ShaderRef::Handle(handle) => Some(handle), ShaderRef::Path(path) => Some(asset_server.load(path)), }, marker: PhantomData, } } } /// Data prepared for a [`MaterialUI`] instance. pub struct PreparedMaterialUI<T: MaterialUI> { pub bindings: Vec<(u32, OwnedBindingResource)>, pub bind_group: BindGroup, pub key: T::Data, } #[derive(Resource)] pub struct ExtractedMaterialsUI<M: MaterialUI> { extracted: Vec<(AssetId<M>, M)>, removed: Vec<AssetId<M>>, } impl<M: MaterialUI> Default for ExtractedMaterialsUI<M> { fn default() -> Self { Self { extracted: Default::default(), removed: Default::default(), } } } /// Stores all prepared representations of [`MaterialUI`] assets for as long as they exist. #[derive(Resource, Deref, DerefMut)] pub struct RenderMaterialsUI<T: MaterialUI>(HashMap<AssetId<T>, PreparedMaterialUI<T>>); impl<T: MaterialUI> Default for RenderMaterialsUI<T> { fn default() -> Self { Self(Default::default()) } } /// This system extracts all created or modified assets of the corresponding [`Material2d`] type /// into the "render world". pub fn extract_materials_ui<M: MaterialUI>( mut commands: Commands, mut events: Extract<EventReader<AssetEvent<M>>>, assets: Extract<Res<Assets<M>>>, ) { let mut changed_assets = HashSet::default(); let mut removed = Vec::new(); for event in events.read() { match event { AssetEvent::Added { id } | AssetEvent::LoadedWithDependencies { id } | AssetEvent::Modified { id } => { changed_assets.insert(*id); } AssetEvent::Removed { id } => { changed_assets.remove(id); removed.push(*id); } AssetEvent::Unused { id: _ } => {} } } let mut extracted_assets = Vec::new(); for handle in changed_assets.drain() { if let Some(asset) = assets.get(handle) { extracted_assets.push((handle, asset.clone())); } } commands.insert_resource(ExtractedMaterialsUI { extracted: extracted_assets, removed, }); } /// All [`MaterialUI`] values of a given type that should be prepared next frame. pub struct PrepareNextFrameMaterials<M: MaterialUI> { assets: Vec<(AssetId<M>, M)>, } impl<M: MaterialUI> Default for PrepareNextFrameMaterials<M> { fn default() -> Self { Self { assets: Default::default(), } } } /// This system prepares all assets of the corresponding [`MaterialUI`] type /// which where extracted this frame for the GPU. pub fn prepare_materials_ui<M: MaterialUI>( mut prepare_next_frame: Local<PrepareNextFrameMaterials<M>>, mut extracted_assets: ResMut<ExtractedMaterialsUI<M>>, mut render_materials: ResMut<RenderMaterialsUI<M>>, render_device: Res<RenderDevice>, images: Res<RenderAssets<Image>>, fallback_image: Res<FallbackImage>, pipeline: Res<MaterialUIPipeline<M>>, ) { let queued_assets = std::mem::take(&mut prepare_next_frame.assets); for (handle, material) in queued_assets { match prepare_materialui( &material, &render_device, &images, &fallback_image, &pipeline, ) { Ok(prepared_asset) => { render_materials.insert(handle, prepared_asset); } Err(AsBindGroupError::RetryNextUpdate) => { prepare_next_frame.assets.push((handle, material)); } } } for removed in std::mem::take(&mut extracted_assets.removed) { render_materials.remove(&removed); } for (handle, material) in std::mem::take(&mut extracted_assets.extracted) { match prepare_materialui( &material, &render_device, &images, &fallback_image, &pipeline, ) { Ok(prepared_asset) => { render_materials.insert(handle, prepared_asset); } Err(AsBindGroupError::RetryNextUpdate) => { prepare_next_frame.assets.push((handle, material)); } } } } pub fn prepare_materialui<M: MaterialUI>( material: &M, render_device: &RenderDevice, images: &RenderAssets<Image>, fallback_image: &FallbackImage, pipeline: &MaterialUIPipeline<M>, ) -> Result<PreparedMaterialUI<M>, AsBindGroupError> { let prepared = material.as_bind_group( &pipeline.material_ui_layout, render_device, images, fallback_image, )?; Ok(PreparedMaterialUI { bindings: prepared.bindings, bind_group: prepared.bind_group, key: prepared.data, }) } pub type DrawMaterialUI<M> = ( SetItemPipeline, SetUIViewBindGroup<TransparentUI, 0>, SetMaterialBindGroup<M, 3>, DrawUIDraw<TransparentUI>, ); pub type DrawMaterialUITransparent<M> = ( SetItemPipeline, SetUIViewBindGroup<TransparentOpacityUI, 0>, SetMaterialBindGroup<M, 3>, DrawUIDraw<TransparentOpacityUI>, ); pub struct SetMaterialBindGroup<M: MaterialUI, const I: usize>(PhantomData<M>); impl<P: PhaseItem, M: MaterialUI, const I: usize> RenderCommand<P> for SetMaterialBindGroup<M, I> { type Param = SRes<RenderMaterialsUI<M>>; type ViewQuery = (); type ItemQuery = Read<Handle<M>>; #[inline] fn render<'w>( _item: &P, _view: (), material2d_handle: Option<ROQueryItem<'_, Self::ItemQuery>>, materials: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { let Some(material2d_handle) = material2d_handle else { return RenderCommandResult::Failure; }; let asset_id: AssetId<M> = material2d_handle.clone_weak().into(); let material2d = materials.into_inner().get(&asset_id).unwrap(); pass.set_bind_group(I, &material2d.bind_group, &[]); RenderCommandResult::Success } } pub fn queue_material_ui_quads<M: MaterialUI>( #[cfg(feature = "svg")] render_svgs: Res<RenderSvgs>, opacity_layers: Res<OpacityLayerManager>, mut commands: Commands, draw_functions: Res<DrawFunctions<TransparentUI>>, draw_functions_opacity: Res<DrawFunctions<TransparentOpacityUI>>, render_device: Res<RenderDevice>, render_queue: Res<RenderQueue>, mut quad_meta: ResMut<QuadMeta>, quad_pipeline: Res<UnifiedPipeline>, materialui_pipeline: Res<MaterialUIPipeline<M>>, mut pipelines: ResMut<SpecializedRenderPipelines<MaterialUIPipeline<M>>>, pipeline_cache: ResMut<PipelineCache>, mut extracted_quads: Query<( &'static mut ExtractedQuad, &'static Handle<M>, &'static MaterialZ, )>, mut views: Query<( Entity, &'static mut UIRenderPhase<TransparentUI>, &'static mut UIRenderPhase<TransparentOpacityUI>, &'static UIExtractedView, )>, mut image_bind_groups: ResMut<ImageBindGroups>, ( gpu_images, font_texture_cache, quad_type_offsets, render_materials, mut prev_clip, prev_index, ): ( Res<RenderAssets<Image>>, Res<FontTextureCache>, Res<QuadTypeOffsets>, Res<RenderMaterialsUI<M>>, ResMut<PreviousClip>, Res<PreviousIndex>, ), ) where M::Data: PartialEq + Eq + Hash + Clone, { let mut current_batch = QuadBatch { image_handle_id: None, font_handle_id: None, quad_type: UIQuadType::None, type_id: quad_type_offsets.quad_type_offset, z_index: -999.0, }; let mut current_batch_entity = Entity::PLACEHOLDER; // Vertex buffer indices let mut index = prev_index.index; let mut item_start = prev_index.index; let mut item_end = prev_index.index; let mut old_item_start = prev_index.index; let mut current_clip = prev_index.last_clip; let mut last_clip = prev_index.last_clip; // let mut previous_clip_rect = Rect::default(); let draw_quad = draw_functions.read().get_id::<DrawMaterialUI<M>>().unwrap(); let draw_opacity_quad = draw_functions_opacity .read() .get_id::<DrawMaterialUITransparent<M>>() .unwrap(); for (camera_entity, mut transparent_phase, mut opacity_transparent_phase, view) in views.iter_mut() { let key = UnifiedPipelineKey { msaa: 1, hdr: view.hdr, }; let mut last_quad = ExtractedQuad::default(); let mut pipeline_id = None; for (mut quad, material_handle, material_z) in extracted_quads.iter_mut() { let asset_id: AssetId<M> = material_handle.clone_weak().into(); if let Some(materialui) = render_materials.get(&asset_id) { if quad.quad_type == UIQuadType::Clip { prev_clip.rect = quad.rect; } if prev_clip.rect.width() < 1.0 || prev_clip.rect.height() < 1.0 { continue; } pipeline_id = Some(pipelines.specialize( &pipeline_cache, &materialui_pipeline, MaterialUIKey { unified_key: key, bind_group_data: materialui.key.clone(), }, )); quad.z_index = material_z.0; queue_quads_inner( &mut commands, &render_device, &font_texture_cache, &opacity_layers, &mut image_bind_groups, &gpu_images, &quad_pipeline, #[cfg(feature = "svg")] &render_svgs, &mut transparent_phase, &mut opacity_transparent_phase, draw_opacity_quad, draw_quad, pipeline_id.unwrap(), &mut quad_meta, &quad, camera_entity, *quad_type_offsets, &mut current_batch, &mut current_batch_entity, &mut index, &mut item_start, &mut item_end, &last_quad, &mut current_clip, &mut old_item_start, &mut last_clip, ); if current_batch_entity != Entity::PLACEHOLDER { commands .entity(current_batch_entity) .insert(material_handle.clone_weak()); } last_quad = quad.clone(); } } if let Some(pipeline) = pipeline_id { #[allow(clippy::nonminimal_bool)] if last_quad.quad_type != UIQuadType::Clip && last_quad.quad_type != UIQuadType::OpacityLayer && last_quad.quad_type != UIQuadType::Clip && current_batch_entity != Entity::PLACEHOLDER { // handle old batch commands .entity(current_batch_entity) .insert(current_batch.clone()); if last_quad.opacity_layer > 0 { opacity_transparent_phase.add(TransparentOpacityUI { draw_function: draw_opacity_quad, pipeline, entity: current_batch_entity, sort_key: FloatOrd(last_quad.z_index), quad_type: last_quad.quad_type, type_index: last_quad.quad_type.get_type_index(&quad_type_offsets), rect: last_clip, batch_range: Some(old_item_start..item_end), opacity_layer: last_quad.opacity_layer, dynamic_offset: None, }); } else { transparent_phase.add(TransparentUI { draw_function: draw_quad, pipeline, entity: current_batch_entity, sort_key: FloatOrd(last_quad.z_index), quad_type: last_quad.quad_type, type_index: last_quad.quad_type.get_type_index(&quad_type_offsets), rect: last_clip, batch_range: Some(old_item_start..item_end), dynamic_offset: None, }); } } } } quad_meta .vertices .write_buffer(&render_device, &render_queue); }