bevy_egui/render/
render_pass.rs

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
21/// Egui pass node.
22pub 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    /// Creates an Egui pass node.
29    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        // Extract the UI view.
76        let input_view_entity = graph.view_entity();
77
78        // Query the UI view components.
79        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                // Bevy TrackedRenderPass doesn't track set_scissor_rect calls,
187                // so set_scissor_rect is updated only when it is needed.
188                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                            // Use push constant to cheaply provide which texture to use inside
217                            // binding array. This is used to avoid costly set_bind_group operations
218                            // when frequent switching between textures is being done
219                            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}