1use crate::render::{
2 DrawPrimitive, EguiViewTarget,
3 systems::{EguiPipelines, EguiRenderData, EguiTextureBindGroups, EguiTransforms},
4};
5use bevy_camera::Viewport;
6use bevy_ecs::{
7 query::QueryState,
8 world::{Mut, World},
9};
10use bevy_math::{URect, UVec2};
11use bevy_render::{
12 camera::ExtractedCamera,
13 render_graph::{Node, NodeRunError, RenderGraphContext},
14 render_resource::{PipelineCache, RenderPassDescriptor},
15 renderer::RenderContext,
16 sync_world::RenderEntity,
17 view::{ExtractedView, ViewTarget},
18};
19use wgpu_types::{IndexFormat, ShaderStages};
20
21pub struct EguiPassNode {
23 egui_view_query: QueryState<(&'static ExtractedView, &'static EguiViewTarget)>,
24 egui_view_target_query: QueryState<(&'static ViewTarget, &'static ExtractedCamera)>,
25}
26
27impl EguiPassNode {
28 pub fn new(world: &mut World) -> Self {
30 Self {
31 egui_view_query: world.query_filtered(),
32 egui_view_target_query: world.query(),
33 }
34 }
35}
36
37impl Node for EguiPassNode {
38 fn update(&mut self, world: &mut World) {
39 self.egui_view_query.update_archetypes(world);
40 self.egui_view_target_query.update_archetypes(world);
41
42 world.resource_scope(|world, mut render_data: Mut<EguiRenderData>| {
43 for (_main_entity, data) in &mut render_data.0 {
44 let Some(key) = data.key else {
45 bevy_log::warn!("Failed to retrieve egui node data!");
46 return;
47 };
48
49 for (clip_rect, command) in data.postponed_updates.drain(..) {
50 let info = egui::PaintCallbackInfo {
51 viewport: command.rect,
52 clip_rect,
53 pixels_per_point: data.pixels_per_point,
54 screen_size_px: data.target_size.to_array(),
55 };
56 command
57 .callback
58 .cb()
59 .update(info, data.render_entity, key, world);
60 }
61 }
62 });
63 }
64
65 fn run<'w>(
66 &self,
67 graph: &mut RenderGraphContext,
68 render_context: &mut RenderContext<'w>,
69 world: &'w World,
70 ) -> Result<(), NodeRunError> {
71 let egui_pipelines = &world.resource::<EguiPipelines>().0;
72 let pipeline_cache = world.resource::<PipelineCache>();
73 let render_data = world.resource::<EguiRenderData>();
74
75 let input_view_entity = graph.view_entity();
77
78 let Ok((view, view_target)) = self.egui_view_query.get_manual(world, input_view_entity)
80 else {
81 return Ok(());
82 };
83
84 let Ok((target, camera)) = self.egui_view_target_query.get_manual(world, view_target.0)
85 else {
86 return Ok(());
87 };
88
89 let Some(data) = render_data.0.get(&view.retained_view_entity.main_entity) else {
90 bevy_log::warn!("Failed to retrieve render data for egui node rendering!");
91 return Ok(());
92 };
93
94 let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
95 label: Some("egui_pass"),
96 color_attachments: &[Some(target.get_unsampled_color_attachment())],
97 depth_stencil_attachment: None,
98 timestamp_writes: None,
99 occlusion_query_set: None,
100 });
101 let Some(viewport) = camera.viewport.clone().or_else(|| {
102 camera.physical_viewport_size.map(|size| Viewport {
103 physical_position: UVec2::ZERO,
104 physical_size: size,
105 ..Default::default()
106 })
107 }) else {
108 return Ok(());
109 };
110 render_pass.set_camera_viewport(&Viewport {
111 physical_position: UVec2::ZERO,
112 physical_size: camera.physical_target_size.unwrap(),
113 ..Default::default()
114 });
115
116 let mut requires_reset = true;
117 let mut last_scissor_rect = None;
118 let mut last_bindless_offset = None;
119
120 let pipeline_id = egui_pipelines
121 .get(&view.retained_view_entity.main_entity)
122 .expect("Expected a queued pipeline");
123 let Some(pipeline) = pipeline_cache.get_render_pipeline(*pipeline_id) else {
124 return Ok(());
125 };
126
127 let bind_groups = world.resource::<EguiTextureBindGroups>();
128 let egui_transforms = world.resource::<EguiTransforms>();
129 let transform_buffer_offset =
130 egui_transforms.offsets[&view.retained_view_entity.main_entity];
131 let transform_buffer_bind_group = &egui_transforms
132 .bind_group
133 .as_ref()
134 .expect("Expected a prepared bind group")
135 .1;
136
137 let (vertex_buffer, index_buffer) = match (&data.vertex_buffer, &data.index_buffer) {
138 (Some(vertex), Some(index)) => (vertex, index),
139 _ => {
140 return Ok(());
141 }
142 };
143
144 let mut vertex_offset: u32 = 0;
145 for draw_command in &data.draw_commands {
146 if requires_reset {
147 render_pass.set_render_pipeline(pipeline);
148 render_pass.set_bind_group(
149 0,
150 transform_buffer_bind_group,
151 &[transform_buffer_offset],
152 );
153 render_pass.set_camera_viewport(&Viewport {
154 physical_position: UVec2::ZERO,
155 physical_size: camera.physical_target_size.unwrap(),
156 ..Default::default()
157 });
158 requires_reset = false;
159
160 last_bindless_offset = None;
161 last_scissor_rect = None;
162 }
163
164 let clip_urect = URect {
165 min: UVec2 {
166 x: (draw_command.clip_rect.min.x * data.pixels_per_point).round() as u32,
167 y: (draw_command.clip_rect.min.y * data.pixels_per_point).round() as u32,
168 },
169 max: UVec2 {
170 x: (draw_command.clip_rect.max.x * data.pixels_per_point).round() as u32,
171 y: (draw_command.clip_rect.max.y * data.pixels_per_point).round() as u32,
172 },
173 };
174
175 let scissor_rect = clip_urect.intersect(URect {
176 min: viewport.physical_position,
177 max: viewport.physical_position + viewport.physical_size,
178 });
179 if scissor_rect.is_empty() {
180 continue;
181 }
182
183 if Some(scissor_rect) != last_scissor_rect {
184 last_scissor_rect = Some(scissor_rect);
185
186 render_pass.set_scissor_rect(
189 scissor_rect.min.x,
190 scissor_rect.min.y,
191 scissor_rect.width(),
192 scissor_rect.height(),
193 );
194 }
195
196 let Some(pipeline_key) = data.key else {
197 continue;
198 };
199 match &draw_command.primitive {
200 DrawPrimitive::Egui(command) => {
201 let Some((texture_bind_group, bindless_offset)) =
202 bind_groups.get(&command.egui_texture)
203 else {
204 vertex_offset += command.vertices_count as u32;
205 continue;
206 };
207
208 render_pass.set_bind_group(1, texture_bind_group, &[]);
209 render_pass.set_vertex_buffer(0, vertex_buffer.slice(..));
210 render_pass.set_index_buffer(index_buffer.slice(..), 0, IndexFormat::Uint32);
211
212 if let Some(bindless_offset) = bindless_offset {
213 if last_bindless_offset != Some(bindless_offset) {
214 last_bindless_offset = Some(bindless_offset);
215
216 render_pass.set_push_constants(
220 ShaderStages::FRAGMENT,
221 0,
222 bytemuck::bytes_of(bindless_offset),
223 );
224 }
225 }
226
227 render_pass.draw_indexed(
228 vertex_offset..(vertex_offset + command.vertices_count as u32),
229 0,
230 0..1,
231 );
232
233 vertex_offset += command.vertices_count as u32;
234 }
235 DrawPrimitive::PaintCallback(command) => {
236 let info = egui::PaintCallbackInfo {
237 viewport: command.rect,
238 clip_rect: draw_command.clip_rect,
239 pixels_per_point: data.pixels_per_point,
240 screen_size_px: [viewport.physical_size.x, viewport.physical_size.y],
241 };
242
243 let viewport = info.viewport_in_pixels();
244 if viewport.width_px > 0 && viewport.height_px > 0 {
245 requires_reset = true;
246 render_pass.set_viewport(
247 viewport.left_px as f32,
248 viewport.top_px as f32,
249 viewport.width_px as f32,
250 viewport.height_px as f32,
251 0.,
252 1.,
253 );
254
255 command.callback.cb().render(
256 info,
257 &mut render_pass,
258 RenderEntity::from(input_view_entity),
259 pipeline_key,
260 world,
261 );
262 }
263 }
264 }
265 }
266
267 Ok(())
268 }
269}