1use crate::{
2 render_systems::{
3 EguiPipelines, EguiRenderData, EguiTextureBindGroups, EguiTextureId, EguiTransform,
4 EguiTransforms,
5 },
6 EguiRenderToImage,
7};
8use bevy_asset::{prelude::*, weak_handle};
9use bevy_ecs::{
10 prelude::*,
11 world::{FromWorld, World},
12};
13use bevy_image::{Image, ImageAddressMode, ImageFilterMode, ImageSampler, ImageSamplerDescriptor};
14use bevy_render::{
15 render_asset::{RenderAssetUsages, RenderAssets},
16 render_graph::{Node, NodeRunError, RenderGraphContext},
17 render_phase::TrackedRenderPass,
18 render_resource::{
19 BindGroupLayout, BindGroupLayoutEntry, BindingType, BlendComponent, BlendFactor,
20 BlendOperation, BlendState, BufferBindingType, ColorTargetState, ColorWrites,
21 CommandEncoderDescriptor, Extent3d, FragmentState, FrontFace, IndexFormat, LoadOp,
22 MultisampleState, Operations, PipelineCache, PrimitiveState, RenderPassColorAttachment,
23 RenderPassDescriptor, RenderPipelineDescriptor, SamplerBindingType, Shader, ShaderStages,
24 ShaderType, SpecializedRenderPipeline, StoreOp, TextureDimension, TextureFormat,
25 TextureSampleType, TextureViewDimension, VertexBufferLayout, VertexFormat, VertexState,
26 VertexStepMode,
27 },
28 renderer::{RenderContext, RenderDevice},
29 sync_world::{MainEntity, RenderEntity},
30 texture::GpuImage,
31 view::{ExtractedWindow, ExtractedWindows},
32};
33use egui::{TextureFilter, TextureOptions};
34
35pub const EGUI_SHADER_HANDLE: Handle<Shader> = weak_handle!("05a4d7a0-4f24-4d7f-b606-3f399074261f");
37
38#[derive(Resource)]
40pub struct EguiPipeline {
41 pub transform_bind_group_layout: BindGroupLayout,
43 pub texture_bind_group_layout: BindGroupLayout,
45}
46
47impl FromWorld for EguiPipeline {
48 fn from_world(render_world: &mut World) -> Self {
49 let render_device = render_world.resource::<RenderDevice>();
50
51 let transform_bind_group_layout = render_device.create_bind_group_layout(
52 "egui transform bind group layout",
53 &[BindGroupLayoutEntry {
54 binding: 0,
55 visibility: ShaderStages::VERTEX,
56 ty: BindingType::Buffer {
57 ty: BufferBindingType::Uniform,
58 has_dynamic_offset: true,
59 min_binding_size: Some(EguiTransform::min_size()),
60 },
61 count: None,
62 }],
63 );
64
65 let texture_bind_group_layout = render_device.create_bind_group_layout(
66 "egui texture bind group layout",
67 &[
68 BindGroupLayoutEntry {
69 binding: 0,
70 visibility: ShaderStages::FRAGMENT,
71 ty: BindingType::Texture {
72 sample_type: TextureSampleType::Float { filterable: true },
73 view_dimension: TextureViewDimension::D2,
74 multisampled: false,
75 },
76 count: None,
77 },
78 BindGroupLayoutEntry {
79 binding: 1,
80 visibility: ShaderStages::FRAGMENT,
81 ty: BindingType::Sampler(SamplerBindingType::Filtering),
82 count: None,
83 },
84 ],
85 );
86
87 EguiPipeline {
88 transform_bind_group_layout,
89 texture_bind_group_layout,
90 }
91 }
92}
93
94#[derive(PartialEq, Eq, Hash, Clone, Copy)]
96pub struct EguiPipelineKey {
97 pub texture_format: TextureFormat,
99 pub render_target_type: EguiRenderTargetType,
101}
102
103#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
105pub enum EguiRenderTargetType {
106 Window,
108 Image,
110}
111
112impl EguiPipelineKey {
113 pub fn from_extracted_window(window: &ExtractedWindow) -> Option<Self> {
115 Some(Self {
116 texture_format: window.swap_chain_texture_format?.add_srgb_suffix(),
117 render_target_type: EguiRenderTargetType::Window,
118 })
119 }
120
121 pub fn from_gpu_image(image: &GpuImage) -> Self {
123 EguiPipelineKey {
124 texture_format: image.texture_format.add_srgb_suffix(),
125 render_target_type: EguiRenderTargetType::Image,
126 }
127 }
128}
129
130impl SpecializedRenderPipeline for EguiPipeline {
131 type Key = EguiPipelineKey;
132
133 fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
134 RenderPipelineDescriptor {
135 label: Some("egui render pipeline".into()),
136 layout: vec![
137 self.transform_bind_group_layout.clone(),
138 self.texture_bind_group_layout.clone(),
139 ],
140 vertex: VertexState {
141 shader: EGUI_SHADER_HANDLE,
142 shader_defs: Vec::new(),
143 entry_point: "vs_main".into(),
144 buffers: vec![VertexBufferLayout::from_vertex_formats(
145 VertexStepMode::Vertex,
146 [
147 VertexFormat::Float32x2, VertexFormat::Float32x2, VertexFormat::Unorm8x4, ],
151 )],
152 },
153 fragment: Some(FragmentState {
154 shader: EGUI_SHADER_HANDLE,
155 shader_defs: Vec::new(),
156 entry_point: "fs_main".into(),
157 targets: vec![Some(ColorTargetState {
158 format: key.texture_format,
159 blend: Some(BlendState {
160 color: BlendComponent {
161 src_factor: BlendFactor::One,
162 dst_factor: BlendFactor::OneMinusSrcAlpha,
163 operation: BlendOperation::Add,
164 },
165 alpha: BlendComponent {
166 src_factor: BlendFactor::One,
167 dst_factor: BlendFactor::OneMinusSrcAlpha,
168 operation: BlendOperation::Add,
169 },
170 }),
171 write_mask: ColorWrites::ALL,
172 })],
173 }),
174 primitive: PrimitiveState {
175 front_face: FrontFace::Cw,
176 cull_mode: None,
177 ..Default::default()
178 },
179 depth_stencil: None,
180 multisample: MultisampleState::default(),
181 push_constant_ranges: vec![],
182 zero_initialize_workgroup_memory: false,
183 }
184 }
185}
186
187pub(crate) struct DrawCommand {
188 pub(crate) clip_rect: egui::Rect,
189 pub(crate) primitive: DrawPrimitive,
190}
191
192pub(crate) enum DrawPrimitive {
193 Egui(EguiDraw),
194 PaintCallback(PaintCallbackDraw),
195}
196
197pub(crate) struct PaintCallbackDraw {
198 pub(crate) callback: std::sync::Arc<EguiBevyPaintCallback>,
199 pub(crate) rect: egui::Rect,
200}
201
202pub(crate) struct EguiDraw {
203 pub(crate) vertices_count: usize,
204 pub(crate) egui_texture: EguiTextureId,
205}
206
207pub struct EguiNode {
209 render_target_main_entity: MainEntity,
210 render_target_render_entity: RenderEntity,
211 render_target_type: EguiRenderTargetType,
212}
213
214impl EguiNode {
215 pub fn new(
217 render_target_main_entity: MainEntity,
218 render_target_render_entity: RenderEntity,
219 render_target_type: EguiRenderTargetType,
220 ) -> Self {
221 EguiNode {
222 render_target_main_entity,
223 render_target_render_entity,
224 render_target_type,
225 }
226 }
227}
228
229impl Node for EguiNode {
230 fn update(&mut self, world: &mut World) {
231 world.resource_scope(|world, mut render_data: Mut<EguiRenderData>| {
232 let Some(data) = render_data.0.get_mut(&self.render_target_main_entity) else {
233 return;
234 };
235
236 let (Some(render_target_size), Some(key)) = (data.render_target_size, data.key) else {
237 bevy_log::warn!("Failed to retrieve egui node data!");
238 return;
239 };
240
241 for (clip_rect, command) in data.postponed_updates.drain(..) {
242 let info = egui::PaintCallbackInfo {
243 viewport: command.rect,
244 clip_rect,
245 pixels_per_point: data.pixels_per_point,
246 screen_size_px: [
247 render_target_size.physical_width as u32,
248 render_target_size.physical_height as u32,
249 ],
250 };
251 command
252 .callback
253 .cb()
254 .update(info, self.render_target_render_entity, key, world);
255 }
256 });
257 }
258
259 fn run<'w>(
260 &self,
261 _graph: &mut RenderGraphContext,
262 render_context: &mut RenderContext<'w>,
263 world: &'w World,
264 ) -> Result<(), NodeRunError> {
265 let egui_pipelines = &world.resource::<EguiPipelines>().0;
266 let pipeline_cache = world.resource::<PipelineCache>();
267 let render_data = world.resource::<EguiRenderData>();
268
269 let Some(data) = render_data.0.get(&self.render_target_main_entity) else {
270 bevy_log::warn!("Failed to retrieve render data for egui node rendering!");
271 return Ok(());
272 };
273
274 let (key, swap_chain_texture_view, physical_width, physical_height, load_op) =
275 match self.render_target_type {
276 EguiRenderTargetType::Window => {
277 let Some(window) = world
278 .resource::<ExtractedWindows>()
279 .windows
280 .get(&self.render_target_main_entity.id())
281 else {
282 return Ok(());
283 };
284
285 let Some(swap_chain_texture_view) = &window.swap_chain_texture_view else {
286 return Ok(());
287 };
288
289 let Some(key) = EguiPipelineKey::from_extracted_window(window) else {
290 return Ok(());
291 };
292 (
293 key,
294 swap_chain_texture_view,
295 window.physical_width,
296 window.physical_height,
297 LoadOp::Load,
298 )
299 }
300 EguiRenderTargetType::Image => {
301 let Some(extracted_render_to_image): Option<&EguiRenderToImage> =
302 world.get(self.render_target_render_entity.id())
303 else {
304 return Ok(());
305 };
306
307 let gpu_images = world.resource::<RenderAssets<GpuImage>>();
308 let Some(gpu_image) = gpu_images.get(&extracted_render_to_image.handle) else {
309 return Ok(());
310 };
311 (
312 EguiPipelineKey::from_gpu_image(gpu_image),
313 &gpu_image.texture_view,
314 gpu_image.size.width,
315 gpu_image.size.height,
316 extracted_render_to_image.load_op,
317 )
318 }
319 };
320
321 let (vertex_buffer, index_buffer) = match (&data.vertex_buffer, &data.index_buffer) {
322 (Some(vertex), Some(index)) => (vertex, index),
323 _ => {
324 return Ok(());
325 }
326 };
327
328 for draw_command in &data.draw_commands {
329 match &draw_command.primitive {
330 DrawPrimitive::Egui(_command) => {}
331 DrawPrimitive::PaintCallback(command) => {
332 let info = egui::PaintCallbackInfo {
333 viewport: command.rect,
334 clip_rect: draw_command.clip_rect,
335 pixels_per_point: data.pixels_per_point,
336 screen_size_px: [physical_width, physical_height],
337 };
338
339 command.callback.cb().prepare_render(
340 info,
341 render_context,
342 self.render_target_render_entity,
343 key,
344 world,
345 );
346 }
347 }
348 }
349
350 let pipeline_id = egui_pipelines
351 .get(&self.render_target_main_entity)
352 .expect("Expected a queued pipeline");
353 let Some(pipeline) = pipeline_cache.get_render_pipeline(*pipeline_id) else {
354 return Ok(());
355 };
356
357 let bind_groups = world.resource::<EguiTextureBindGroups>();
358 let egui_transforms = world.resource::<EguiTransforms>();
359 let transform_buffer_offset = egui_transforms.offsets[&self.render_target_main_entity];
360 let transform_buffer_bind_group = &egui_transforms
361 .bind_group
362 .as_ref()
363 .expect("Expected a prepared bind group")
364 .1;
365 let render_target_render_entity = self.render_target_render_entity;
366
367 render_context.add_command_buffer_generation_task(move |device| {
368 let mut command_encoder = device.create_command_encoder(&CommandEncoderDescriptor {
369 label: Some("egui_node_command_encoder"),
370 });
371
372 let render_pass = command_encoder.begin_render_pass(&RenderPassDescriptor {
373 label: Some("egui render pass"),
374 color_attachments: &[Some(RenderPassColorAttachment {
375 view: swap_chain_texture_view,
376 resolve_target: None,
377 ops: Operations {
378 load: load_op,
379 store: StoreOp::Store,
380 },
381 })],
382 depth_stencil_attachment: None,
383 timestamp_writes: None,
384 occlusion_query_set: None,
385 });
386 let mut render_pass = TrackedRenderPass::new(&device, render_pass);
387
388 let mut requires_reset = true;
389 let mut last_scissor_rect = None;
390
391 let mut vertex_offset: u32 = 0;
392 for draw_command in &data.draw_commands {
393 if requires_reset {
394 render_pass.set_viewport(
395 0.,
396 0.,
397 physical_width as f32,
398 physical_height as f32,
399 0.,
400 1.,
401 );
402 last_scissor_rect = None;
403 render_pass.set_render_pipeline(pipeline);
404 render_pass.set_bind_group(
405 0,
406 transform_buffer_bind_group,
407 &[transform_buffer_offset],
408 );
409
410 requires_reset = false;
411 }
412
413 let clip_urect = bevy_math::URect {
414 min: bevy_math::UVec2 {
415 x: (draw_command.clip_rect.min.x * data.pixels_per_point).round() as u32,
416 y: (draw_command.clip_rect.min.y * data.pixels_per_point).round() as u32,
417 },
418 max: bevy_math::UVec2 {
419 x: (draw_command.clip_rect.max.x * data.pixels_per_point).round() as u32,
420 y: (draw_command.clip_rect.max.y * data.pixels_per_point).round() as u32,
421 },
422 };
423
424 let scissor_rect = clip_urect.intersect(bevy_math::URect::new(
425 0,
426 0,
427 physical_width,
428 physical_height,
429 ));
430 if scissor_rect.is_empty() {
431 continue;
432 }
433
434 if Some(scissor_rect) != last_scissor_rect {
435 last_scissor_rect = Some(scissor_rect);
436
437 render_pass.set_scissor_rect(
440 scissor_rect.min.x,
441 scissor_rect.min.y,
442 scissor_rect.width(),
443 scissor_rect.height(),
444 );
445 }
446
447 match &draw_command.primitive {
448 DrawPrimitive::Egui(command) => {
449 let texture_bind_group = match bind_groups.get(&command.egui_texture) {
450 Some(texture_resource) => texture_resource,
451 None => {
452 vertex_offset += command.vertices_count as u32;
453 continue;
454 }
455 };
456
457 render_pass.set_bind_group(1, texture_bind_group, &[]);
458 render_pass.set_vertex_buffer(0, vertex_buffer.slice(..));
459 render_pass.set_index_buffer(
460 index_buffer.slice(..),
461 0,
462 IndexFormat::Uint32,
463 );
464
465 render_pass.draw_indexed(
466 vertex_offset..(vertex_offset + command.vertices_count as u32),
467 0,
468 0..1,
469 );
470
471 vertex_offset += command.vertices_count as u32;
472 }
473 DrawPrimitive::PaintCallback(command) => {
474 let info = egui::PaintCallbackInfo {
475 viewport: command.rect,
476 clip_rect: draw_command.clip_rect,
477 pixels_per_point: data.pixels_per_point,
478 screen_size_px: [physical_width, physical_height],
479 };
480
481 let viewport = info.viewport_in_pixels();
482 if viewport.width_px > 0 && viewport.height_px > 0 {
483 requires_reset = true;
484 render_pass.set_viewport(
485 viewport.left_px as f32,
486 viewport.top_px as f32,
487 viewport.width_px as f32,
488 viewport.height_px as f32,
489 0.,
490 1.,
491 );
492
493 command.callback.cb().render(
494 info,
495 &mut render_pass,
496 render_target_render_entity,
497 key,
498 world,
499 );
500 }
501 }
502 }
503 }
504
505 drop(render_pass);
506 command_encoder.finish()
507 });
508
509 Ok(())
510 }
511}
512
513pub(crate) fn as_color_image(image: &egui::ImageData) -> egui::ColorImage {
514 match image {
515 egui::ImageData::Color(image) => (**image).clone(),
516 egui::ImageData::Font(image) => alpha_image_as_color_image(image),
517 }
518}
519
520fn alpha_image_as_color_image(image: &egui::FontImage) -> egui::ColorImage {
521 egui::ColorImage {
522 size: image.size,
523 pixels: image.srgba_pixels(None).collect(),
524 }
525}
526
527pub(crate) fn color_image_as_bevy_image(
528 egui_image: &egui::ColorImage,
529 sampler_descriptor: ImageSampler,
530) -> Image {
531 let pixels = egui_image
532 .pixels
533 .iter()
534 .flat_map(|color| color.to_srgba_unmultiplied())
538 .collect();
539
540 Image {
541 sampler: sampler_descriptor,
542 ..Image::new(
543 Extent3d {
544 width: egui_image.width() as u32,
545 height: egui_image.height() as u32,
546 depth_or_array_layers: 1,
547 },
548 TextureDimension::D2,
549 pixels,
550 TextureFormat::Rgba8UnormSrgb,
551 RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD,
552 )
553 }
554}
555
556pub(crate) fn texture_options_as_sampler_descriptor(
557 options: &TextureOptions,
558) -> ImageSamplerDescriptor {
559 fn convert_filter(filter: &TextureFilter) -> ImageFilterMode {
560 match filter {
561 egui::TextureFilter::Nearest => ImageFilterMode::Nearest,
562 egui::TextureFilter::Linear => ImageFilterMode::Linear,
563 }
564 }
565 let address_mode = match options.wrap_mode {
566 egui::TextureWrapMode::ClampToEdge => ImageAddressMode::ClampToEdge,
567 egui::TextureWrapMode::Repeat => ImageAddressMode::Repeat,
568 egui::TextureWrapMode::MirroredRepeat => ImageAddressMode::MirrorRepeat,
569 };
570 ImageSamplerDescriptor {
571 mag_filter: convert_filter(&options.magnification),
572 min_filter: convert_filter(&options.minification),
573 address_mode_u: address_mode,
574 address_mode_v: address_mode,
575 ..Default::default()
576 }
577}
578
579pub struct EguiBevyPaintCallback(Box<dyn EguiBevyPaintCallbackImpl>);
585
586impl EguiBevyPaintCallback {
587 pub fn new_paint_callback<T>(rect: egui::Rect, callback: T) -> egui::epaint::PaintCallback
589 where
590 T: EguiBevyPaintCallbackImpl + 'static,
591 {
592 let callback = Self(Box::new(callback));
593 egui::epaint::PaintCallback {
594 rect,
595 callback: std::sync::Arc::new(callback),
596 }
597 }
598
599 pub(crate) fn cb(&self) -> &dyn EguiBevyPaintCallbackImpl {
600 self.0.as_ref()
601 }
602}
603
604pub trait EguiBevyPaintCallbackImpl: Send + Sync {
606 fn update(
608 &self,
609 info: egui::PaintCallbackInfo,
610 window_entity: RenderEntity,
611 pipeline_key: EguiPipelineKey,
612 world: &mut World,
613 );
614
615 fn prepare_render<'w>(
621 &self,
622 info: egui::PaintCallbackInfo,
623 render_context: &mut RenderContext<'w>,
624 window_entity: RenderEntity,
625 pipeline_key: EguiPipelineKey,
626 world: &'w World,
627 ) {
628 let _ = (info, render_context, window_entity, pipeline_key, world);
629 }
631
632 fn render<'pass>(
637 &self,
638 info: egui::PaintCallbackInfo,
639 render_pass: &mut TrackedRenderPass<'pass>,
640 window_entity: RenderEntity,
641 pipeline_key: EguiPipelineKey,
642 world: &'pass World,
643 );
644}