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#[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
67impl ExtractedEguiTextures<'_> {
68 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#[derive(Resource, Default)]
90pub struct EguiTransforms {
91 pub buffer: DynamicUniformBuffer<EguiTransform>,
93 pub offsets: HashMap<MainEntity, u32>,
95 pub bind_group: Option<(BufferId, BindGroup)>,
97}
98
99#[derive(encase::ShaderType, Default)]
102pub struct EguiTransform {
103 pub scale: Vec2,
105 pub translation: Vec2,
107}
108
109impl EguiTransform {
110 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
122pub 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#[derive(Resource, Deref, DerefMut, Default)]
174pub struct EguiTextureBindGroups(pub HashMap<EguiTextureId, (BindGroup, Option<u32>)>);
175
176pub 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 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 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#[derive(Resource)]
258pub struct EguiPipelines(pub HashMap<MainEntity, CachedRenderPipelineId>);
259
260pub 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#[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
328pub 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 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}