bevy_egui/
render_systems.rs

1use crate::{
2    egui_node::{
3        DrawCommand, DrawPrimitive, EguiBevyPaintCallback, EguiDraw, EguiNode, EguiPipeline,
4        EguiPipelineKey, EguiRenderTargetType, PaintCallbackDraw,
5    },
6    EguiContext, EguiContextSettings, EguiManagedTextures, EguiRenderOutput, EguiRenderToImage,
7    EguiUserTextures, RenderTargetSize,
8};
9use bevy_asset::prelude::*;
10use bevy_derive::{Deref, DerefMut};
11use bevy_ecs::{prelude::*, system::SystemParam};
12use bevy_image::Image;
13use bevy_log as log;
14use bevy_math::Vec2;
15use bevy_platform::collections::HashMap;
16use bevy_render::{
17    extract_resource::ExtractResource,
18    render_asset::RenderAssets,
19    render_graph::{RenderGraph, RenderLabel},
20    render_resource::{
21        BindGroup, BindGroupEntry, BindingResource, Buffer, BufferDescriptor, BufferId,
22        CachedRenderPipelineId, DynamicUniformBuffer, PipelineCache, SpecializedRenderPipelines,
23    },
24    renderer::{RenderDevice, RenderQueue},
25    sync_world::{MainEntity, RenderEntity},
26    texture::GpuImage,
27    view::ExtractedWindows,
28    Extract,
29};
30use bevy_window::Window;
31use bytemuck::cast_slice;
32use wgpu_types::{BufferAddress, BufferUsages};
33
34/// Extracted Egui settings.
35#[derive(Resource, Deref, DerefMut, Default)]
36pub struct ExtractedEguiSettings(pub EguiContextSettings);
37
38/// The extracted version of [`EguiManagedTextures`].
39#[derive(Debug, Resource)]
40pub struct ExtractedEguiManagedTextures(pub HashMap<(Entity, u64), Handle<Image>>);
41impl ExtractResource for ExtractedEguiManagedTextures {
42    type Source = EguiManagedTextures;
43
44    fn extract_resource(source: &Self::Source) -> Self {
45        Self(source.iter().map(|(k, v)| (*k, v.handle.clone())).collect())
46    }
47}
48
49/// Corresponds to Egui's [`egui::TextureId`].
50#[derive(Debug, PartialEq, Eq, Hash)]
51pub enum EguiTextureId {
52    /// Textures allocated via Egui.
53    Managed(MainEntity, u64),
54    /// Textures allocated via Bevy.
55    User(u64),
56}
57
58/// Extracted Egui textures.
59#[derive(SystemParam)]
60pub struct ExtractedEguiTextures<'w> {
61    /// Maps Egui managed texture ids to Bevy image handles.
62    pub egui_textures: Res<'w, ExtractedEguiManagedTextures>,
63    /// Maps Bevy managed texture handles to Egui user texture ids.
64    pub user_textures: Res<'w, EguiUserTextures>,
65}
66
67/// [`RenderLabel`] type for the Egui pass.
68#[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)]
69pub struct EguiPass {
70    /// Index of the window entity.
71    pub entity_index: u32,
72    /// Generation of the window entity.
73    pub entity_generation: u32,
74    /// Render target type (e.g. window, image).
75    pub render_target_type: EguiRenderTargetType,
76}
77
78impl EguiPass {
79    /// Creates a pass from a window Egui context.
80    pub fn from_window_entity(entity: Entity) -> Self {
81        Self {
82            entity_index: entity.index(),
83            entity_generation: entity.generation(),
84            render_target_type: EguiRenderTargetType::Window,
85        }
86    }
87
88    /// Creates a pass from a "render to image" Egui context.
89    pub fn from_render_to_image_entity(entity: Entity) -> Self {
90        Self {
91            entity_index: entity.index(),
92            entity_generation: entity.generation(),
93            render_target_type: EguiRenderTargetType::Image,
94        }
95    }
96}
97
98impl ExtractedEguiTextures<'_> {
99    /// Returns an iterator over all textures (both Egui and Bevy managed).
100    pub fn handles(&self) -> impl Iterator<Item = (EguiTextureId, AssetId<Image>)> + '_ {
101        self.egui_textures
102            .0
103            .iter()
104            .map(|(&(window, texture_id), managed_tex)| {
105                (
106                    EguiTextureId::Managed(MainEntity::from(window), texture_id),
107                    managed_tex.id(),
108                )
109            })
110            .chain(
111                self.user_textures
112                    .textures
113                    .iter()
114                    .map(|(handle, id)| (EguiTextureId::User(*id), handle.id())),
115            )
116    }
117}
118
119/// Sets up render nodes for newly created Egui contexts.
120pub fn setup_new_egui_nodes_system(
121    windows: Extract<
122        Query<(Entity, &RenderEntity, AnyOf<(&Window, &EguiRenderToImage)>), Added<EguiContext>>,
123    >,
124    mut render_graph: ResMut<RenderGraph>,
125) {
126    for (main_entity, render_entity, (window, render_to_image)) in windows.iter() {
127        let egui_pass = EguiPass::from_window_entity(main_entity);
128        let new_node = EguiNode::new(
129            MainEntity::from(main_entity),
130            *render_entity,
131            match (window.is_some(), render_to_image.is_some()) {
132                (true, false) => EguiRenderTargetType::Window,
133                (false, true) => EguiRenderTargetType::Image,
134                (true, true) => {
135                    log::error!(
136                        "Failed to set up an Egui node: can't render both to a window and an image"
137                    );
138                    continue;
139                }
140                (false, false) => unreachable!(),
141            },
142        );
143
144        render_graph.add_node(egui_pass.clone(), new_node);
145
146        render_graph.add_node_edge(bevy_render::graph::CameraDriverLabel, egui_pass);
147    }
148}
149
150/// Tears render nodes down for deleted window Egui contexts.
151pub fn teardown_window_nodes_system(
152    mut removed_windows: Extract<RemovedComponents<Window>>,
153    mut render_graph: ResMut<RenderGraph>,
154) {
155    for window_entity in removed_windows.read() {
156        if let Err(err) = render_graph.remove_node(EguiPass::from_window_entity(window_entity)) {
157            log::error!("Failed to remove a render graph node: {err:?}");
158        }
159    }
160}
161
162/// Tears render nodes down for deleted "render to texture" Egui contexts.
163pub fn teardown_render_to_image_nodes_system(
164    mut removed_windows: Extract<RemovedComponents<EguiRenderToImage>>,
165    mut render_graph: ResMut<RenderGraph>,
166) {
167    for window_entity in removed_windows.read() {
168        if let Err(err) =
169            render_graph.remove_node(EguiPass::from_render_to_image_entity(window_entity))
170        {
171            log::error!("Failed to remove a render graph node: {err:?}");
172        }
173    }
174}
175
176/// Describes the transform buffer.
177#[derive(Resource, Default)]
178pub struct EguiTransforms {
179    /// Uniform buffer.
180    pub buffer: DynamicUniformBuffer<EguiTransform>,
181    /// The Entity is from the main world.
182    pub offsets: HashMap<MainEntity, u32>,
183    /// Bind group.
184    pub bind_group: Option<(BufferId, BindGroup)>,
185}
186
187/// Scale and translation for rendering Egui shapes. Is needed to transform Egui coordinates from
188/// the screen space with the center at (0, 0) to the normalised viewport space.
189#[derive(encase::ShaderType, Default)]
190pub struct EguiTransform {
191    /// Is affected by window size and [`EguiContextSettings::scale_factor`].
192    pub scale: Vec2,
193    /// Normally equals `Vec2::new(-1.0, 1.0)`.
194    pub translation: Vec2,
195}
196
197impl EguiTransform {
198    /// Calculates the transform from window size and scale factor.
199    pub fn from_render_target_size(
200        render_target_size: RenderTargetSize,
201        scale_factor: f32,
202    ) -> Self {
203        EguiTransform {
204            scale: Vec2::new(
205                2.0 / (render_target_size.width() / scale_factor),
206                -2.0 / (render_target_size.height() / scale_factor),
207            ),
208            translation: Vec2::new(-1.0, 1.0),
209        }
210    }
211}
212
213/// Prepares Egui transforms.
214pub fn prepare_egui_transforms_system(
215    mut egui_transforms: ResMut<EguiTransforms>,
216    render_targets: Query<(Option<&MainEntity>, &EguiContextSettings, &RenderTargetSize)>,
217    render_device: Res<RenderDevice>,
218    render_queue: Res<RenderQueue>,
219    egui_pipeline: Res<EguiPipeline>,
220) {
221    egui_transforms.buffer.clear();
222    egui_transforms.offsets.clear();
223
224    for (window_main, egui_settings, size) in render_targets.iter() {
225        let offset = egui_transforms
226            .buffer
227            .push(&EguiTransform::from_render_target_size(
228                *size,
229                egui_settings.scale_factor,
230            ));
231        if let Some(window_main) = window_main {
232            egui_transforms.offsets.insert(*window_main, offset);
233        }
234    }
235
236    egui_transforms
237        .buffer
238        .write_buffer(&render_device, &render_queue);
239
240    if let Some(buffer) = egui_transforms.buffer.buffer() {
241        match egui_transforms.bind_group {
242            Some((id, _)) if buffer.id() == id => {}
243            _ => {
244                let transform_bind_group = render_device.create_bind_group(
245                    Some("egui transform bind group"),
246                    &egui_pipeline.transform_bind_group_layout,
247                    &[BindGroupEntry {
248                        binding: 0,
249                        resource: egui_transforms.buffer.binding().unwrap(),
250                    }],
251                );
252                egui_transforms.bind_group = Some((buffer.id(), transform_bind_group));
253            }
254        };
255    }
256}
257
258/// Maps Egui textures to bind groups.
259#[derive(Resource, Deref, DerefMut, Default)]
260pub struct EguiTextureBindGroups(pub HashMap<EguiTextureId, BindGroup>);
261
262/// Queues bind groups.
263pub fn queue_bind_groups_system(
264    mut commands: Commands,
265    egui_textures: ExtractedEguiTextures,
266    render_device: Res<RenderDevice>,
267    gpu_images: Res<RenderAssets<GpuImage>>,
268    egui_pipeline: Res<EguiPipeline>,
269) {
270    let bind_groups = egui_textures
271        .handles()
272        .filter_map(|(texture, handle_id)| {
273            let gpu_image = gpu_images.get(&Handle::Weak(handle_id))?;
274            let bind_group = render_device.create_bind_group(
275                None,
276                &egui_pipeline.texture_bind_group_layout,
277                &[
278                    BindGroupEntry {
279                        binding: 0,
280                        resource: BindingResource::TextureView(&gpu_image.texture_view),
281                    },
282                    BindGroupEntry {
283                        binding: 1,
284                        resource: BindingResource::Sampler(&gpu_image.sampler),
285                    },
286                ],
287            );
288            Some((texture, bind_group))
289        })
290        .collect();
291
292    commands.insert_resource(EguiTextureBindGroups(bind_groups))
293}
294
295/// Cached Pipeline IDs for the specialized instances of `EguiPipeline`.
296#[derive(Resource)]
297pub struct EguiPipelines(pub HashMap<MainEntity, CachedRenderPipelineId>);
298
299/// Queue [`EguiPipeline`] instances specialized on each window's swap chain texture format.
300pub fn queue_pipelines_system(
301    mut commands: Commands,
302    pipeline_cache: Res<PipelineCache>,
303    mut specialized_pipelines: ResMut<SpecializedRenderPipelines<EguiPipeline>>,
304    egui_pipeline: Res<EguiPipeline>,
305    windows: Res<ExtractedWindows>,
306    render_to_image: Query<(&MainEntity, &EguiRenderToImage)>,
307    images: Res<RenderAssets<GpuImage>>,
308) {
309    let mut pipelines: HashMap<MainEntity, CachedRenderPipelineId> = windows
310        .iter()
311        .filter_map(|(window_id, window)| {
312            let key = EguiPipelineKey::from_extracted_window(window)?;
313            let pipeline_id =
314                specialized_pipelines.specialize(&pipeline_cache, &egui_pipeline, key);
315            Some((MainEntity::from(*window_id), pipeline_id))
316        })
317        .collect();
318
319    pipelines.extend(
320        render_to_image
321            .iter()
322            .filter_map(|(main_entity, render_to_image)| {
323                let img = images.get(&render_to_image.handle)?;
324                let key = EguiPipelineKey::from_gpu_image(img);
325                let pipeline_id =
326                    specialized_pipelines.specialize(&pipeline_cache, &egui_pipeline, key);
327
328                Some((*main_entity, pipeline_id))
329            }),
330    );
331
332    commands.insert_resource(EguiPipelines(pipelines));
333}
334
335/// Cached Pipeline IDs for the specialized instances of `EguiPipeline`.
336#[derive(Default, Resource)]
337pub struct EguiRenderData(pub(crate) HashMap<MainEntity, EguiRenderTargetData>);
338
339#[derive(Default)]
340pub(crate) struct EguiRenderTargetData {
341    keep: bool,
342    pub(crate) vertex_data: Vec<u8>,
343    pub(crate) vertex_buffer_capacity: usize,
344    pub(crate) vertex_buffer: Option<Buffer>,
345    pub(crate) index_data: Vec<u32>,
346    pub(crate) index_buffer_capacity: usize,
347    pub(crate) index_buffer: Option<Buffer>,
348    pub(crate) draw_commands: Vec<DrawCommand>,
349    pub(crate) postponed_updates: Vec<(egui::Rect, PaintCallbackDraw)>,
350    pub(crate) pixels_per_point: f32,
351    pub(crate) key: Option<EguiPipelineKey>,
352    pub(crate) render_target_size: Option<RenderTargetSize>,
353}
354
355/// Prepares Egui transforms.
356pub fn prepare_egui_render_target_data(
357    mut render_data: ResMut<EguiRenderData>,
358    render_targets: Query<(
359        &MainEntity,
360        &EguiContextSettings,
361        &RenderTargetSize,
362        &EguiRenderOutput,
363        Option<&EguiRenderToImage>,
364    )>,
365    render_device: Res<RenderDevice>,
366    render_queue: Res<RenderQueue>,
367    extracted_windows: Res<ExtractedWindows>,
368    gpu_images: Res<RenderAssets<GpuImage>>,
369) {
370    let render_data = &mut render_data.0;
371    render_data.retain(|_, data| {
372        let keep = data.keep;
373        data.keep = false;
374        keep
375    });
376
377    for (main_entity, egui_settings, render_target_size, render_output, render_to_image) in
378        render_targets.iter()
379    {
380        let data = render_data.entry(*main_entity).or_default();
381
382        data.keep = true;
383
384        let render_target_size = *render_target_size;
385        let egui_settings = egui_settings.clone();
386        let image_handle =
387            render_to_image.map(|render_to_image| render_to_image.handle.clone_weak());
388
389        data.render_target_size = Some(render_target_size);
390
391        let render_target_type = if render_to_image.is_some() {
392            EguiRenderTargetType::Image
393        } else {
394            EguiRenderTargetType::Window
395        };
396
397        // Construct a pipeline key based on a render target.
398        let key = match render_target_type {
399            EguiRenderTargetType::Window => {
400                let Some(key) = extracted_windows
401                    .windows
402                    .get(&main_entity.id())
403                    .and_then(EguiPipelineKey::from_extracted_window)
404                else {
405                    continue;
406                };
407                key
408            }
409            EguiRenderTargetType::Image => {
410                let image_handle = image_handle
411                    .expect("Expected an image handle for a render to image node")
412                    .clone();
413                let Some(key) = gpu_images
414                    .get(&image_handle)
415                    .map(EguiPipelineKey::from_gpu_image)
416                else {
417                    continue;
418                };
419                key
420            }
421        };
422        data.key = Some(key);
423
424        data.pixels_per_point = render_target_size.scale_factor * egui_settings.scale_factor;
425        if render_target_size.physical_width == 0.0 || render_target_size.physical_height == 0.0 {
426            continue;
427        }
428
429        let mut index_offset = 0;
430
431        data.draw_commands.clear();
432        data.vertex_data.clear();
433        data.index_data.clear();
434        data.postponed_updates.clear();
435
436        for egui::epaint::ClippedPrimitive {
437            clip_rect,
438            primitive,
439        } in render_output.paint_jobs.as_slice()
440        {
441            let clip_rect = *clip_rect;
442
443            let clip_urect = bevy_math::URect {
444                min: bevy_math::UVec2 {
445                    x: (clip_rect.min.x * data.pixels_per_point).round() as u32,
446                    y: (clip_rect.min.y * data.pixels_per_point).round() as u32,
447                },
448                max: bevy_math::UVec2 {
449                    x: (clip_rect.max.x * data.pixels_per_point).round() as u32,
450                    y: (clip_rect.max.y * data.pixels_per_point).round() as u32,
451                },
452            };
453
454            if clip_urect
455                .intersect(bevy_math::URect::new(
456                    0,
457                    0,
458                    render_target_size.physical_width as u32,
459                    render_target_size.physical_height as u32,
460                ))
461                .is_empty()
462            {
463                continue;
464            }
465
466            let mesh = match primitive {
467                egui::epaint::Primitive::Mesh(mesh) => mesh,
468                egui::epaint::Primitive::Callback(paint_callback) => {
469                    let callback = match paint_callback
470                        .callback
471                        .clone()
472                        .downcast::<EguiBevyPaintCallback>()
473                    {
474                        Ok(callback) => callback,
475                        Err(err) => {
476                            log::error!("Unsupported Egui paint callback type: {err:?}");
477                            continue;
478                        }
479                    };
480
481                    data.postponed_updates.push((
482                        clip_rect,
483                        PaintCallbackDraw {
484                            callback: callback.clone(),
485                            rect: paint_callback.rect,
486                        },
487                    ));
488
489                    data.draw_commands.push(DrawCommand {
490                        primitive: DrawPrimitive::PaintCallback(PaintCallbackDraw {
491                            callback,
492                            rect: paint_callback.rect,
493                        }),
494                        clip_rect,
495                    });
496                    continue;
497                }
498            };
499
500            data.vertex_data
501                .extend_from_slice(cast_slice::<_, u8>(mesh.vertices.as_slice()));
502            data.index_data
503                .extend(mesh.indices.iter().map(|i| i + index_offset));
504            index_offset += mesh.vertices.len() as u32;
505
506            let texture_handle = match mesh.texture_id {
507                egui::TextureId::Managed(id) => EguiTextureId::Managed(*main_entity, id),
508                egui::TextureId::User(id) => EguiTextureId::User(id),
509            };
510
511            data.draw_commands.push(DrawCommand {
512                primitive: DrawPrimitive::Egui(EguiDraw {
513                    vertices_count: mesh.indices.len(),
514                    egui_texture: texture_handle,
515                }),
516                clip_rect,
517            });
518        }
519
520        if data.vertex_data.len() > data.vertex_buffer_capacity {
521            data.vertex_buffer_capacity = data.vertex_data.len().next_power_of_two();
522            data.vertex_buffer = Some(render_device.create_buffer(&BufferDescriptor {
523                label: Some("egui vertex buffer"),
524                size: data.vertex_buffer_capacity as BufferAddress,
525                usage: BufferUsages::COPY_DST | BufferUsages::VERTEX,
526                mapped_at_creation: false,
527            }));
528        }
529
530        let index_data_size = data.index_data.len() * std::mem::size_of::<u32>();
531        if index_data_size > data.index_buffer_capacity {
532            data.index_buffer_capacity = index_data_size.next_power_of_two();
533            data.index_buffer = Some(render_device.create_buffer(&BufferDescriptor {
534                label: Some("egui index buffer"),
535                size: data.index_buffer_capacity as BufferAddress,
536                usage: BufferUsages::COPY_DST | BufferUsages::INDEX,
537                mapped_at_creation: false,
538            }));
539        }
540
541        let (vertex_buffer, index_buffer) = match (&data.vertex_buffer, &data.index_buffer) {
542            (Some(vertex), Some(index)) => (vertex, index),
543            _ => {
544                continue;
545            }
546        };
547
548        render_queue.write_buffer(vertex_buffer, 0, &data.vertex_data);
549        render_queue.write_buffer(index_buffer, 0, cast_slice(&data.index_data));
550    }
551}