// Copyright (c) 2021 Vladyslav Batyrenko // SPDX-License-Identifier: MIT // // This code is vendored from bevy_egui: https://github.com/vladbat00/bevy_egui // Original author: Vladyslav Batyrenko use crate::debug_ui::render::{ DrawPrimitive, EguiViewTarget, systems::{EguiPipelines, EguiRenderData, EguiTextureBindGroups, EguiTransforms}, }; use bevy::camera::Viewport; use bevy::ecs::{ query::QueryState, world::{Mut, World}, }; use bevy::math::{URect, UVec2}; use bevy::render::{ camera::ExtractedCamera, render_graph::{Node, NodeRunError, RenderGraphContext}, render_resource::{PipelineCache, RenderPassDescriptor}, renderer::RenderContext, sync_world::RenderEntity, view::{ExtractedView, ViewTarget}, }; use wgpu_types::{IndexFormat, ShaderStages}; /// Egui pass node. pub struct EguiPassNode { egui_view_query: QueryState<(&'static ExtractedView, &'static EguiViewTarget)>, egui_view_target_query: QueryState<(&'static ViewTarget, &'static ExtractedCamera)>, } impl EguiPassNode { /// Creates an Egui pass node. pub fn new(world: &mut World) -> Self { Self { egui_view_query: world.query_filtered(), egui_view_target_query: world.query(), } } } impl Node for EguiPassNode { fn update(&mut self, world: &mut World) { self.egui_view_query.update_archetypes(world); self.egui_view_target_query.update_archetypes(world); world.resource_scope(|world, mut render_data: Mut| { for (_main_entity, data) in &mut render_data.0 { let Some(key) = data.key else { bevy::log::warn!("Failed to retrieve egui node data!"); return; }; for (clip_rect, command) in data.postponed_updates.drain(..) { let info = egui::PaintCallbackInfo { viewport: command.rect, clip_rect, pixels_per_point: data.pixels_per_point, screen_size_px: data.target_size.to_array(), }; command .callback .cb() .update(info, data.render_entity, key, world); } } }); } fn run<'w>( &self, graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, world: &'w World, ) -> Result<(), NodeRunError> { let egui_pipelines = &world.resource::().0; let pipeline_cache = world.resource::(); let render_data = world.resource::(); // Extract the UI view. let input_view_entity = graph.view_entity(); // Query the UI view components. let Ok((view, view_target)) = self.egui_view_query.get_manual(world, input_view_entity) else { return Ok(()); }; let Ok((target, camera)) = self.egui_view_target_query.get_manual(world, view_target.0) else { return Ok(()); }; let Some(data) = render_data.0.get(&view.retained_view_entity.main_entity) else { return Ok(()); }; let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { label: Some("egui_pass"), color_attachments: &[Some(target.get_unsampled_color_attachment())], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, }); let Some(viewport) = camera.viewport.clone().or_else(|| { camera.physical_viewport_size.map(|size| Viewport { physical_position: UVec2::ZERO, physical_size: size, ..Default::default() }) }) else { return Ok(()); }; render_pass.set_camera_viewport(&Viewport { physical_position: UVec2::ZERO, physical_size: camera.physical_target_size.unwrap(), ..Default::default() }); let mut requires_reset = true; let mut last_scissor_rect = None; let mut last_bindless_offset = None; let pipeline_id = egui_pipelines .get(&view.retained_view_entity.main_entity) .expect("Expected a queued pipeline"); let Some(pipeline) = pipeline_cache.get_render_pipeline(*pipeline_id) else { return Ok(()); }; let bind_groups = world.resource::(); let egui_transforms = world.resource::(); let transform_buffer_offset = egui_transforms.offsets[&view.retained_view_entity.main_entity]; let transform_buffer_bind_group = &egui_transforms .bind_group .as_ref() .expect("Expected a prepared bind group") .1; let (vertex_buffer, index_buffer) = match (&data.vertex_buffer, &data.index_buffer) { (Some(vertex), Some(index)) => (vertex, index), _ => { return Ok(()); } }; let mut index_offset: u32 = 0; for draw_command in &data.draw_commands { if requires_reset { render_pass.set_render_pipeline(pipeline); render_pass.set_bind_group( 0, transform_buffer_bind_group, &[transform_buffer_offset], ); render_pass.set_camera_viewport(&Viewport { physical_position: UVec2::ZERO, physical_size: camera.physical_target_size.unwrap(), ..Default::default() }); requires_reset = false; last_bindless_offset = None; last_scissor_rect = None; } let clip_urect = URect { min: UVec2 { x: (draw_command.clip_rect.min.x * data.pixels_per_point).round() as u32, y: (draw_command.clip_rect.min.y * data.pixels_per_point).round() as u32, }, max: UVec2 { x: (draw_command.clip_rect.max.x * data.pixels_per_point).round() as u32, y: (draw_command.clip_rect.max.y * data.pixels_per_point).round() as u32, }, }; let scissor_rect = clip_urect.intersect(URect { min: viewport.physical_position, max: viewport.physical_position + viewport.physical_size, }); if scissor_rect.is_empty() { continue; } if Some(scissor_rect) != last_scissor_rect { last_scissor_rect = Some(scissor_rect); // Bevy TrackedRenderPass doesn't track set_scissor_rect calls, // so set_scissor_rect is updated only when it is needed. render_pass.set_scissor_rect( scissor_rect.min.x, scissor_rect.min.y, scissor_rect.width(), scissor_rect.height(), ); } let Some(pipeline_key) = data.key else { continue; }; match &draw_command.primitive { DrawPrimitive::Egui(command) => { let Some((texture_bind_group, bindless_offset)) = bind_groups.get(&command.egui_texture) else { index_offset += command.vertices_count as u32; continue; }; render_pass.set_bind_group(1, texture_bind_group, &[]); render_pass.set_vertex_buffer(0, vertex_buffer.slice(..)); render_pass.set_index_buffer(index_buffer.slice(..), 0, IndexFormat::Uint32); if let Some(bindless_offset) = bindless_offset && last_bindless_offset != Some(bindless_offset) { last_bindless_offset = Some(bindless_offset); // Use push constant to cheaply provide which texture to use inside // binding array. This is used to avoid costly set_bind_group operations // when frequent switching between textures is being done render_pass.set_push_constants( ShaderStages::FRAGMENT, 0, bytemuck::bytes_of(bindless_offset), ); } // NOTE: vertices_count is actually the indices count (poorly named in EguiDraw struct) render_pass.draw_indexed( index_offset..(index_offset + command.vertices_count as u32), 0, 0..1, ); index_offset += command.vertices_count as u32; } DrawPrimitive::PaintCallback(command) => { let info = egui::PaintCallbackInfo { viewport: command.rect, clip_rect: draw_command.clip_rect, pixels_per_point: data.pixels_per_point, screen_size_px: [viewport.physical_size.x, viewport.physical_size.y], }; let viewport = info.viewport_in_pixels(); if viewport.width_px > 0 && viewport.height_px > 0 { requires_reset = true; render_pass.set_viewport( viewport.left_px as f32, viewport.top_px as f32, viewport.width_px as f32, viewport.height_px as f32, 0., 1., ); command.callback.cb().render( info, &mut render_pass, RenderEntity::from(input_view_entity), pipeline_key, world, ); } } } } Ok(()) } }