bevy_egui/render/
systems.rs

1use crate::{
2    EguiContextSettings, EguiManagedTextures, EguiRenderOutput, EguiUserTextures,
3    RenderComputedScaleFactor,
4    helpers::QueryHelper,
5    render::{
6        DrawCommand, DrawPrimitive, EguiBevyPaintCallback, EguiCameraView, EguiDraw, EguiPipeline,
7        EguiPipelineKey, EguiViewTarget, PaintCallbackDraw,
8    },
9};
10use bevy_asset::prelude::*;
11use bevy_derive::{Deref, DerefMut};
12use bevy_ecs::{prelude::*, system::SystemParam};
13use bevy_image::Image;
14use bevy_log as log;
15use bevy_math::{URect, UVec2, Vec2};
16use bevy_platform::collections::HashMap;
17use bevy_render::{
18    camera::ExtractedCamera,
19    extract_resource::ExtractResource,
20    render_asset::RenderAssets,
21    render_resource::{
22        BindGroup, BindGroupEntry, BindingResource, Buffer, BufferDescriptor, BufferId,
23        CachedRenderPipelineId, DynamicUniformBuffer, PipelineCache, SpecializedRenderPipelines,
24    },
25    renderer::{RenderDevice, RenderQueue},
26    sync_world::{MainEntity, RenderEntity},
27    texture::GpuImage,
28    view::ExtractedView,
29};
30use bytemuck::cast_slice;
31use itertools::Itertools;
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
67impl ExtractedEguiTextures<'_> {
68    /// Returns an iterator over all textures (both Egui and Bevy managed).
69    pub fn handles(&self) -> impl Iterator<Item = (EguiTextureId, AssetId<Image>)> + '_ {
70        self.egui_textures
71            .0
72            .iter()
73            .map(|(&(window, texture_id), managed_tex)| {
74                (
75                    EguiTextureId::Managed(MainEntity::from(window), texture_id),
76                    managed_tex.id(),
77                )
78            })
79            .chain(
80                self.user_textures
81                    .textures
82                    .iter()
83                    .map(|(handle, (_, id))| (EguiTextureId::User(*id), *handle)),
84            )
85    }
86}
87
88/// Describes the transform buffer.
89#[derive(Resource, Default)]
90pub struct EguiTransforms {
91    /// Uniform buffer.
92    pub buffer: DynamicUniformBuffer<EguiTransform>,
93    /// The Entity is from the main world.
94    pub offsets: HashMap<MainEntity, u32>,
95    /// Bind group.
96    pub bind_group: Option<(BufferId, BindGroup)>,
97}
98
99/// Scale and translation for rendering Egui shapes. Is needed to transform Egui coordinates from
100/// the screen space with the center at (0, 0) to the normalised viewport space.
101#[derive(encase::ShaderType, Default)]
102pub struct EguiTransform {
103    /// Is affected by render target size, scale factor and [`EguiContextSettings::scale_factor`].
104    pub scale: Vec2,
105    /// Normally equals `Vec2::new(-1.0, 1.0)`.
106    pub translation: Vec2,
107}
108
109impl EguiTransform {
110    /// Calculates the transform from target size and target scale factor multiplied by [`EguiContextSettings::scale_factor`].
111    pub fn new(target_size: Vec2, scale_factor: f32) -> Self {
112        EguiTransform {
113            scale: Vec2::new(
114                2.0 / (target_size.x / scale_factor),
115                -2.0 / (target_size.y / scale_factor),
116            ),
117            translation: Vec2::new(-1.0, 1.0),
118        }
119    }
120}
121
122/// Prepares Egui transforms.
123pub fn prepare_egui_transforms_system(
124    mut egui_transforms: ResMut<EguiTransforms>,
125    views: Query<&RenderComputedScaleFactor>,
126    render_targets: Query<(&ExtractedView, &ExtractedCamera, &EguiCameraView)>,
127    render_device: Res<RenderDevice>,
128    render_queue: Res<RenderQueue>,
129    egui_pipeline: Res<EguiPipeline>,
130) -> Result {
131    egui_transforms.buffer.clear();
132    egui_transforms.offsets.clear();
133
134    for (view, camera, egui_camera_view) in render_targets.iter() {
135        let Some(target_size) = camera.physical_target_size else {
136            continue;
137        };
138
139        let &RenderComputedScaleFactor { scale_factor } = views.get(egui_camera_view.0)?;
140        let offset = egui_transforms
141            .buffer
142            .push(&EguiTransform::new(target_size.as_vec2(), scale_factor));
143        egui_transforms
144            .offsets
145            .insert(view.retained_view_entity.main_entity, offset);
146    }
147
148    egui_transforms
149        .buffer
150        .write_buffer(&render_device, &render_queue);
151
152    if let Some(buffer) = egui_transforms.buffer.buffer() {
153        match egui_transforms.bind_group {
154            Some((id, _)) if buffer.id() == id => {}
155            _ => {
156                let transform_bind_group = render_device.create_bind_group(
157                    Some("egui transform bind group"),
158                    &egui_pipeline.transform_bind_group_layout,
159                    &[BindGroupEntry {
160                        binding: 0,
161                        resource: egui_transforms.buffer.binding().unwrap(),
162                    }],
163                );
164                egui_transforms.bind_group = Some((buffer.id(), transform_bind_group));
165            }
166        };
167    }
168
169    Ok(())
170}
171
172/// Maps Egui textures to bind groups.
173#[derive(Resource, Deref, DerefMut, Default)]
174pub struct EguiTextureBindGroups(pub HashMap<EguiTextureId, (BindGroup, Option<u32>)>);
175
176/// Queues bind groups.
177pub fn queue_bind_groups_system(
178    mut commands: Commands,
179    egui_textures: ExtractedEguiTextures,
180    render_device: Res<RenderDevice>,
181    gpu_images: Res<RenderAssets<GpuImage>>,
182    egui_pipeline: Res<EguiPipeline>,
183) {
184    let egui_texture_iterator = egui_textures.handles().filter_map(|(texture, handle_id)| {
185        let gpu_image = gpu_images.get(handle_id)?;
186        Some((texture, gpu_image))
187    });
188
189    let bind_groups = if let Some(bindless) = egui_pipeline.bindless {
190        let bindless = u32::from(bindless) as usize;
191        let mut bind_groups = HashMap::new();
192
193        let mut texture_array = Vec::new();
194        let mut sampler_array = Vec::new();
195        let mut egui_texture_ids = Vec::new();
196
197        for textures in egui_texture_iterator.chunks(bindless).into_iter() {
198            texture_array.clear();
199            sampler_array.clear();
200            egui_texture_ids.clear();
201
202            for (egui_texture_id, gpu_image) in textures {
203                egui_texture_ids.push(egui_texture_id);
204                // Dereference needed to convert from bevy to wgpu type
205                texture_array.push(&*gpu_image.texture_view);
206                sampler_array.push(&*gpu_image.sampler);
207            }
208
209            let bind_group = render_device.create_bind_group(
210                None,
211                &egui_pipeline.texture_bind_group_layout,
212                &[
213                    BindGroupEntry {
214                        binding: 0,
215                        resource: BindingResource::TextureViewArray(texture_array.as_slice()),
216                    },
217                    BindGroupEntry {
218                        binding: 1,
219                        resource: BindingResource::SamplerArray(sampler_array.as_slice()),
220                    },
221                ],
222            );
223
224            // Simply assign bind group to egui texture
225            // Additional code is not needed because bevy RenderPass set_bind_group
226            // removes redundant switching between bind groups
227            for (offset, egui_texture_id) in egui_texture_ids.drain(..).enumerate() {
228                bind_groups.insert(egui_texture_id, (bind_group.clone(), Some(offset as u32)));
229            }
230        }
231        bind_groups
232    } else {
233        egui_texture_iterator
234            .map(|(texture, gpu_image)| {
235                let bind_group = render_device.create_bind_group(
236                    None,
237                    &egui_pipeline.texture_bind_group_layout,
238                    &[
239                        BindGroupEntry {
240                            binding: 0,
241                            resource: BindingResource::TextureView(&gpu_image.texture_view),
242                        },
243                        BindGroupEntry {
244                            binding: 1,
245                            resource: BindingResource::Sampler(&gpu_image.sampler),
246                        },
247                    ],
248                );
249                (texture, (bind_group, None::<u32>))
250            })
251            .collect()
252    };
253    commands.insert_resource(EguiTextureBindGroups(bind_groups))
254}
255
256/// Cached Pipeline IDs for the specialized instances of `EguiPipeline`.
257#[derive(Resource)]
258pub struct EguiPipelines(pub HashMap<MainEntity, CachedRenderPipelineId>);
259
260/// Queue [`EguiPipeline`] instances.
261pub fn queue_pipelines_system(
262    mut commands: Commands,
263    pipeline_cache: Res<PipelineCache>,
264    mut specialized_pipelines: ResMut<SpecializedRenderPipelines<EguiPipeline>>,
265    egui_pipeline: Res<EguiPipeline>,
266    egui_views: Query<&EguiViewTarget, With<ExtractedView>>,
267    camera_views: Query<(&MainEntity, &ExtractedCamera)>,
268) {
269    let pipelines: HashMap<MainEntity, CachedRenderPipelineId> = egui_views
270        .iter()
271        .filter_map(|egui_camera_view| {
272            let (main_entity, extracted_camera) = camera_views.get_some(egui_camera_view.0)?;
273
274            let pipeline_id = specialized_pipelines.specialize(
275                &pipeline_cache,
276                &egui_pipeline,
277                EguiPipelineKey {
278                    hdr: extracted_camera.hdr,
279                },
280            );
281            Some((*main_entity, pipeline_id))
282        })
283        .collect();
284
285    commands.insert_resource(EguiPipelines(pipelines));
286}
287
288/// Cached Pipeline IDs for the specialized instances of `EguiPipeline`.
289#[derive(Default, Resource)]
290pub struct EguiRenderData(pub(crate) HashMap<MainEntity, EguiRenderTargetData>);
291
292pub(crate) struct EguiRenderTargetData {
293    keep: bool,
294    pub(crate) render_entity: RenderEntity,
295    pub(crate) vertex_data: Vec<u8>,
296    pub(crate) vertex_buffer_capacity: usize,
297    pub(crate) vertex_buffer: Option<Buffer>,
298    pub(crate) index_data: Vec<u32>,
299    pub(crate) index_buffer_capacity: usize,
300    pub(crate) index_buffer: Option<Buffer>,
301    pub(crate) draw_commands: Vec<DrawCommand>,
302    pub(crate) postponed_updates: Vec<(egui::Rect, PaintCallbackDraw)>,
303    pub(crate) pixels_per_point: f32,
304    pub(crate) target_size: UVec2,
305    pub(crate) key: Option<EguiPipelineKey>,
306}
307
308impl Default for EguiRenderTargetData {
309    fn default() -> Self {
310        Self {
311            keep: false,
312            render_entity: RenderEntity::from(Entity::PLACEHOLDER),
313            vertex_data: Vec::new(),
314            vertex_buffer_capacity: 0,
315            vertex_buffer: None,
316            index_data: Vec::new(),
317            index_buffer_capacity: 0,
318            index_buffer: None,
319            draw_commands: Vec::new(),
320            postponed_updates: Vec::new(),
321            pixels_per_point: 0.0,
322            target_size: UVec2::ZERO,
323            key: None,
324        }
325    }
326}
327
328/// Prepares Egui transforms.
329pub fn prepare_egui_render_target_data_system(
330    mut render_data: ResMut<EguiRenderData>,
331    render_targets: Query<(
332        Entity,
333        &ExtractedView,
334        &RenderComputedScaleFactor,
335        &EguiViewTarget,
336        &EguiRenderOutput,
337    )>,
338    extracted_cameras: Query<&ExtractedCamera>,
339    render_device: Res<RenderDevice>,
340    render_queue: Res<RenderQueue>,
341) {
342    let render_data = &mut render_data.0;
343    render_data.retain(|_, data| {
344        let keep = data.keep;
345        data.keep = false;
346        keep
347    });
348
349    for (render_entity, view, computed_scale_factor, egui_view_target, render_output) in
350        render_targets.iter()
351    {
352        let data = render_data
353            .entry(view.retained_view_entity.main_entity)
354            .or_default();
355
356        data.keep = true;
357        data.render_entity = render_entity.into();
358
359        // Construct a pipeline key based on a render target.
360        let Ok(extracted_camera) = extracted_cameras.get(egui_view_target.0) else {
361            log::warn!("ExtractedCamera entity doesn't exist for the Egui view");
362            continue;
363        };
364        data.key = Some(EguiPipelineKey {
365            hdr: extracted_camera.hdr,
366        });
367
368        data.pixels_per_point = computed_scale_factor.scale_factor;
369        if extracted_camera
370            .physical_viewport_size
371            .is_none_or(|size| size.x < 1 || size.y < 1)
372        {
373            continue;
374        }
375
376        let mut index_offset = 0;
377
378        data.draw_commands.clear();
379        data.vertex_data.clear();
380        data.index_data.clear();
381        data.postponed_updates.clear();
382
383        for egui::epaint::ClippedPrimitive {
384            clip_rect,
385            primitive,
386        } in render_output.paint_jobs.as_slice()
387        {
388            let clip_rect = *clip_rect;
389
390            let clip_urect = URect {
391                min: UVec2 {
392                    x: (clip_rect.min.x * data.pixels_per_point).round() as u32,
393                    y: (clip_rect.min.y * data.pixels_per_point).round() as u32,
394                },
395                max: UVec2 {
396                    x: (clip_rect.max.x * data.pixels_per_point).round() as u32,
397                    y: (clip_rect.max.y * data.pixels_per_point).round() as u32,
398                },
399            };
400
401            if clip_urect
402                .intersect(URect::new(
403                    view.viewport.x,
404                    view.viewport.y,
405                    view.viewport.x + view.viewport.z,
406                    view.viewport.y + view.viewport.w,
407                ))
408                .is_empty()
409            {
410                continue;
411            }
412
413            let mesh = match primitive {
414                egui::epaint::Primitive::Mesh(mesh) => mesh,
415                egui::epaint::Primitive::Callback(paint_callback) => {
416                    let callback = match paint_callback
417                        .callback
418                        .clone()
419                        .downcast::<EguiBevyPaintCallback>()
420                    {
421                        Ok(callback) => callback,
422                        Err(err) => {
423                            log::error!("Unsupported Egui paint callback type: {err:?}");
424                            continue;
425                        }
426                    };
427
428                    data.postponed_updates.push((
429                        clip_rect,
430                        PaintCallbackDraw {
431                            callback: callback.clone(),
432                            rect: paint_callback.rect,
433                        },
434                    ));
435
436                    data.draw_commands.push(DrawCommand {
437                        primitive: DrawPrimitive::PaintCallback(PaintCallbackDraw {
438                            callback,
439                            rect: paint_callback.rect,
440                        }),
441                        clip_rect,
442                    });
443                    continue;
444                }
445            };
446
447            data.vertex_data
448                .extend_from_slice(cast_slice::<_, u8>(mesh.vertices.as_slice()));
449            data.index_data
450                .extend(mesh.indices.iter().map(|i| i + index_offset));
451            index_offset += mesh.vertices.len() as u32;
452
453            let texture_handle = match mesh.texture_id {
454                egui::TextureId::Managed(id) => {
455                    EguiTextureId::Managed(view.retained_view_entity.main_entity, id)
456                }
457                egui::TextureId::User(id) => EguiTextureId::User(id),
458            };
459
460            data.draw_commands.push(DrawCommand {
461                primitive: DrawPrimitive::Egui(EguiDraw {
462                    vertices_count: mesh.indices.len(),
463                    egui_texture: texture_handle,
464                }),
465                clip_rect,
466            });
467        }
468
469        if data.vertex_data.len() > data.vertex_buffer_capacity {
470            data.vertex_buffer_capacity = data.vertex_data.len().next_power_of_two();
471            data.vertex_buffer = Some(render_device.create_buffer(&BufferDescriptor {
472                label: Some("egui vertex buffer"),
473                size: data.vertex_buffer_capacity as BufferAddress,
474                usage: BufferUsages::COPY_DST | BufferUsages::VERTEX,
475                mapped_at_creation: false,
476            }));
477        }
478
479        let index_data_size = data.index_data.len() * std::mem::size_of::<u32>();
480        if index_data_size > data.index_buffer_capacity {
481            data.index_buffer_capacity = index_data_size.next_power_of_two();
482            data.index_buffer = Some(render_device.create_buffer(&BufferDescriptor {
483                label: Some("egui index buffer"),
484                size: data.index_buffer_capacity as BufferAddress,
485                usage: BufferUsages::COPY_DST | BufferUsages::INDEX,
486                mapped_at_creation: false,
487            }));
488        }
489
490        let (vertex_buffer, index_buffer) = match (&data.vertex_buffer, &data.index_buffer) {
491            (Some(vertex), Some(index)) => (vertex, index),
492            _ => {
493                continue;
494            }
495        };
496
497        render_queue.write_buffer(vertex_buffer, 0, &data.vertex_data);
498        render_queue.write_buffer(index_buffer, 0, cast_slice(&data.index_data));
499    }
500}