1#![deny(missing_docs)]
4#![deny(rustdoc::broken_intra_doc_links)]
5
6pub use egui;
7use vk_graph::{
8 cmd::{LoadOp, StoreOp},
9 driver::{
10 ash::vk,
11 buffer::BufferInfo,
12 device::Device,
13 graphics::{BlendInfo, GraphicsPipeline, GraphicsPipelineInfo},
14 image::{Image, ImageInfo},
15 shader::Shader,
16 sync::AccessType,
17 },
18 node::AnyImageNode,
19 pool::{Lease, Pool as _, hash::HashPool},
20};
21
22use {
23 bytemuck::cast_slice,
24 egui_winit::winit::{event::Event, raw_window_handle::HasDisplayHandle, window::Window},
25 std::{borrow::Cow, collections::HashMap, sync::Arc},
26 vk_graph::Graph,
27 vk_shader_macros::include_glsl,
28};
29
30pub struct Egui {
32 pub ctx: egui::Context,
34
35 egui_winit: egui_winit::State,
36 textures: HashMap<egui::TextureId, Arc<Lease<Image>>>,
37 cache: HashPool,
38 ppl: GraphicsPipeline,
39 next_tex_id: u64,
40 user_textures: HashMap<egui::TextureId, AnyImageNode>,
41}
42
43impl Egui {
44 pub fn new(device: &Device, display_target: &dyn HasDisplayHandle) -> Self {
46 let ppl = GraphicsPipeline::create(
47 device,
48 GraphicsPipelineInfo::builder()
49 .blend(BlendInfo {
50 blend_enable: true,
51 src_color_blend_factor: vk::BlendFactor::ONE,
52 dst_color_blend_factor: vk::BlendFactor::ONE_MINUS_SRC_ALPHA,
53 color_blend_op: vk::BlendOp::ADD,
54 src_alpha_blend_factor: vk::BlendFactor::ONE,
55 dst_alpha_blend_factor: vk::BlendFactor::ONE,
56 alpha_blend_op: vk::BlendOp::ADD,
57 color_write_mask: vk::ColorComponentFlags::R
58 | vk::ColorComponentFlags::G
59 | vk::ColorComponentFlags::B
60 | vk::ColorComponentFlags::A,
61 })
62 .cull_mode(vk::CullModeFlags::NONE),
63 [
64 Shader::new_vertex(include_glsl!("shaders/egui.vert").as_slice()),
65 Shader::new_fragment(include_glsl!("shaders/egui.frag").as_slice()),
66 ],
67 )
68 .expect("invalid egui pipeline");
69
70 let ctx = egui::Context::default();
71 let max_texture_side = Some(
72 device
73 .physical
74 .properties_v1_0
75 .limits
76 .max_image_dimension2_d as usize,
77 );
78 let egui_winit = egui_winit::State::new(
79 ctx.clone(),
80 egui::ViewportId::ROOT,
81 display_target,
82 None,
83 None,
84 max_texture_side,
85 );
86
87 Self {
88 ppl,
89 ctx,
90 egui_winit,
91 textures: HashMap::default(),
92 cache: HashPool::new(device),
93 next_tex_id: 0,
94 user_textures: HashMap::default(),
95 }
96 }
97
98 fn bind_and_update_textures(
99 &mut self,
100 deltas: &egui::TexturesDelta,
101 graph: &mut Graph,
102 ) -> HashMap<egui::TextureId, AnyImageNode> {
103 let mut bound_tex = deltas
104 .set
105 .iter()
106 .map(|(id, delta)| {
107 let pixels = match &delta.image {
108 egui::ImageData::Color(image) => {
109 assert_eq!(image.width() * image.height(), image.pixels.len());
110 Cow::Borrowed(&image.pixels)
111 }
112 };
113
114 let tmp_buf = {
115 let mut buf = self
116 .cache
117 .resource(BufferInfo::host_mem(
118 (pixels.len() * delta.image.bytes_per_pixel()) as u64,
119 vk::BufferUsageFlags::TRANSFER_SRC,
120 ))
121 .expect("missing egui texture upload buffer");
122 buf.copy_from_slice(0, cast_slice(&pixels));
123 graph.bind_resource(buf)
124 };
125
126 if let Some(pos) = delta.pos {
127 let image =
128 graph.bind_resource(self.textures.remove(id).expect("missing texture"));
129
130 graph
131 .begin_cmd()
132 .debug_name("copy buffer to image")
133 .copy_buffer_to_image(
134 tmp_buf,
135 image,
136 [vk::BufferImageCopy {
137 buffer_offset: 0,
138 buffer_row_length: delta.image.width() as u32,
139 buffer_image_height: delta.image.height() as u32,
140 image_offset: vk::Offset3D {
141 x: pos[0] as i32,
142 y: pos[1] as i32,
143 z: 0,
144 },
145 image_extent: vk::Extent3D {
146 width: delta.image.width() as u32,
147 height: delta.image.height() as u32,
148 depth: 1,
149 },
150 image_subresource: vk::ImageSubresourceLayers {
151 aspect_mask: vk::ImageAspectFlags::COLOR,
152 mip_level: 0,
153 base_array_layer: 0,
154 layer_count: 1,
155 },
156 }],
157 )
158 .end_cmd();
159 (*id, AnyImageNode::from(image))
160 } else {
161 let image = graph.bind_resource(
162 self.cache
163 .resource(ImageInfo::image_2d(
164 delta.image.width() as u32,
165 delta.image.height() as u32,
166 vk::Format::R8G8B8A8_UNORM,
167 vk::ImageUsageFlags::SAMPLED | vk::ImageUsageFlags::TRANSFER_DST,
168 ))
169 .expect("missing egui texture image"),
170 );
171
172 graph.copy_buffer_to_image(tmp_buf, image);
173 (*id, AnyImageNode::from(image))
174 }
175 })
176 .collect::<HashMap<_, _>>();
177
178 for (id, image) in self.textures.drain() {
180 bound_tex.insert(id, AnyImageNode::from(graph.bind_resource(image)));
181 }
182
183 for (id, node) in self.user_textures.drain() {
185 bound_tex.insert(id, node);
186 }
187
188 bound_tex
189 }
190
191 fn unbind_and_free(
192 &mut self,
193 bound_tex: HashMap<egui::TextureId, AnyImageNode>,
194 graph: &mut Graph,
195 deltas: &egui::TexturesDelta,
196 ) {
197 for (id, tex) in bound_tex.iter() {
199 if let AnyImageNode::Pooled(tex) = tex
200 && let egui::TextureId::Managed(_) = *id
201 {
202 self.textures.insert(*id, graph.resource(*tex).clone());
203 }
204 }
205
206 for id in deltas.free.iter() {
208 self.textures.remove(id);
209 }
210
211 self.next_tex_id = 0;
212 }
213
214 fn draw_primitive(
215 &mut self,
216 shapes: Vec<egui::epaint::ClippedShape>,
217 bound_tex: &HashMap<egui::TextureId, AnyImageNode>,
218 graph: &mut Graph,
219 target: impl Into<AnyImageNode>,
220 ) {
221 let target = target.into();
222 let target_info = graph.resource(target).info;
223 for egui::ClippedPrimitive {
224 clip_rect,
225 primitive,
226 } in self.ctx.tessellate(shapes, self.ctx.pixels_per_point())
227 {
228 match primitive {
229 egui::epaint::Primitive::Mesh(mesh) => {
230 if mesh.vertices.is_empty() || mesh.indices.is_empty() {
233 continue;
234 }
235 let texture = bound_tex
236 .get(&mesh.texture_id)
237 .expect("missing egui mesh texture");
238
239 let idx_buf = {
240 let mut buf = self
241 .cache
242 .resource(BufferInfo::host_mem(
243 (mesh.indices.len() * 4) as u64,
244 vk::BufferUsageFlags::INDEX_BUFFER,
245 ))
246 .expect("missing egui index buffer");
247 buf.copy_from_slice(0, cast_slice(&mesh.indices));
248 buf
249 };
250 let idx_buf = graph.bind_resource(idx_buf);
251
252 let vert_buf = {
253 let mut buf = self
254 .cache
255 .resource(BufferInfo::host_mem(
256 (mesh.vertices.len() * std::mem::size_of::<egui::epaint::Vertex>())
257 as u64,
258 vk::BufferUsageFlags::VERTEX_BUFFER,
259 ))
260 .expect("missing egui vertex buffer");
261 buf.copy_from_slice(0, cast_slice(&mesh.vertices));
262 buf
263 };
264 let vert_buf = graph.bind_resource(vert_buf);
265
266 #[repr(C)]
267 #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
268 struct PushConstants {
269 screen_size: [f32; 2],
270 }
271
272 let pixels_per_point = self.ctx.pixels_per_point();
273
274 let push_constants = PushConstants {
275 screen_size: [
276 target_info.width as f32 / pixels_per_point,
277 target_info.height as f32 / pixels_per_point,
278 ],
279 };
280
281 let num_indices = mesh.indices.len() as u32;
282
283 let x = (clip_rect.min.x * pixels_per_point) as i32;
284 let y = (clip_rect.min.y * pixels_per_point) as i32;
285
286 let width = ((clip_rect.max.x - clip_rect.min.x) * pixels_per_point) as u32;
287 let height = ((clip_rect.max.y - clip_rect.min.y) * pixels_per_point) as u32;
288
289 graph
290 .begin_cmd()
291 .debug_name("Egui pass")
292 .bind_pipeline(&self.ppl)
293 .resource_access(idx_buf, AccessType::IndexBuffer)
294 .resource_access(vert_buf, AccessType::VertexBuffer)
295 .shader_resource_access(0, *texture, AccessType::FragmentShaderReadOther)
296 .color_attachment_image(0, target, LoadOp::Load, StoreOp::Store)
297 .record_cmd(move |cmd| {
298 cmd.bind_index_buffer(idx_buf, 0, vk::IndexType::UINT32)
299 .bind_vertex_buffer(0, vert_buf, 0)
300 .push_constants(0, cast_slice(&[push_constants]))
301 .set_scissor(
302 0,
303 &[vk::Rect2D {
304 offset: vk::Offset2D { x, y },
305 extent: vk::Extent2D { width, height },
306 }],
307 )
308 .draw_indexed(num_indices, 1, 0, 0, 0);
309 });
310 }
311 _ => panic!("Primitive callback not yet supported."),
312 }
313 }
314 }
315
316 pub fn run(
318 &mut self,
319 window: &Window,
320 events: &[Event<()>],
321 target: impl Into<AnyImageNode>,
322 graph: &mut Graph,
323 ui_fn: impl FnMut(&mut egui::Ui),
324 ) {
325 for event in events {
327 if let Event::WindowEvent { event, .. } = event {
328 #[allow(unused_must_use)]
329 {
330 self.egui_winit.on_window_event(window, event);
331 }
332 }
333 }
334 let raw_input = self.egui_winit.take_egui_input(window);
335 let full_output = self.ctx.run_ui(raw_input, ui_fn);
336
337 self.egui_winit
338 .handle_platform_output(window, full_output.platform_output);
339
340 let deltas = full_output.textures_delta;
341
342 let bound_tex = self.bind_and_update_textures(&deltas, graph);
343
344 self.draw_primitive(full_output.shapes, &bound_tex, graph, target);
345
346 self.unbind_and_free(bound_tex, graph, &deltas);
347 }
348
349 pub fn register_texture(&mut self, tex: impl Into<AnyImageNode>) -> egui::TextureId {
351 let id = egui::TextureId::User(self.next_tex_id);
352 self.next_tex_id += 1;
353 self.user_textures.insert(id, tex.into());
354 id
355 }
356}