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#[derive(Resource, Deref, DerefMut, Default)]
36pub struct ExtractedEguiSettings(pub EguiContextSettings);
37
38#[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#[derive(Debug, PartialEq, Eq, Hash)]
51pub enum EguiTextureId {
52 Managed(MainEntity, u64),
54 User(u64),
56}
57
58#[derive(SystemParam)]
60pub struct ExtractedEguiTextures<'w> {
61 pub egui_textures: Res<'w, ExtractedEguiManagedTextures>,
63 pub user_textures: Res<'w, EguiUserTextures>,
65}
66
67#[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)]
69pub struct EguiPass {
70 pub entity_index: u32,
72 pub entity_generation: u32,
74 pub render_target_type: EguiRenderTargetType,
76}
77
78impl EguiPass {
79 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 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 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
119pub 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
150pub 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
162pub 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#[derive(Resource, Default)]
178pub struct EguiTransforms {
179 pub buffer: DynamicUniformBuffer<EguiTransform>,
181 pub offsets: HashMap<MainEntity, u32>,
183 pub bind_group: Option<(BufferId, BindGroup)>,
185}
186
187#[derive(encase::ShaderType, Default)]
190pub struct EguiTransform {
191 pub scale: Vec2,
193 pub translation: Vec2,
195}
196
197impl EguiTransform {
198 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
213pub 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#[derive(Resource, Deref, DerefMut, Default)]
260pub struct EguiTextureBindGroups(pub HashMap<EguiTextureId, BindGroup>);
261
262pub 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#[derive(Resource)]
297pub struct EguiPipelines(pub HashMap<MainEntity, CachedRenderPipelineId>);
298
299pub 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#[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
355pub 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 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}