1#![deny(missing_docs)]
4#![deny(rustdoc::broken_intra_doc_links)]
5
6pub mod prelude {
8 pub use super::{Condition, Frame, ImGui, Image, ImageSource, Ui, imgui};
9}
10
11pub use imgui::{self, Condition, Ui};
12
13type DrawCmdInfo = (usize, [f32; 4], usize, usize, TextureId);
14
15use {
16 bytemuck::cast_slice,
17 imgui::{Context, DrawCmd, DrawCmdParams, TextureId},
18 imgui_winit_support::{
19 winit::{event::Event, window::Window},
20 {HiDpiMode, WinitPlatform},
21 },
22 log::warn,
23 std::{collections::HashMap, marker::PhantomData, sync::Arc, time::Duration},
24 vk_graph::{
25 Graph,
26 cmd::{LoadOp, StoreOp},
27 driver::{
28 ash::vk,
29 buffer::{Buffer, BufferInfo},
30 device::Device,
31 graphics::{BlendInfo, GraphicsPipeline, GraphicsPipelineInfo},
32 image::{Image as DriverImage, ImageInfo},
33 shader::Shader,
34 sync::AccessType,
35 },
36 node::{AnyImageNode, ImageLeaseNode, ImageNode},
37 pool::{Lease, Pool},
38 },
39 vk_shader_macros::include_glsl,
40};
41
42#[derive(Debug)]
44pub struct ImGui {
45 context: Context,
46 font_atlas_image: Option<Arc<Lease<DriverImage>>>,
47 next_texture_id: usize,
48 pipeline: GraphicsPipeline,
49 platform: WinitPlatform,
50 user_images: HashMap<TextureId, AnyImageNode>,
51}
52
53pub struct Frame<'a> {
55 next_texture_id: &'a mut usize,
56 user_images: &'a mut HashMap<TextureId, AnyImageNode>,
57}
58
59pub struct Image<'a> {
65 id: TextureId,
66 _frame: PhantomData<&'a Frame<'a>>,
67}
68
69#[derive(Clone, Copy, Debug)]
71pub struct ImageSource {
72 image: AnyImageNode,
73}
74
75fn supported_draw_cmd(draw_cmd: DrawCmd) -> Option<DrawCmdInfo> {
76 match draw_cmd {
77 DrawCmd::Elements {
78 count,
79 cmd_params:
80 DrawCmdParams {
81 clip_rect,
82 idx_offset,
83 vtx_offset,
84 texture_id,
85 ..
86 },
87 } => Some((count, clip_rect, idx_offset, vtx_offset, texture_id)),
88 DrawCmd::ResetRenderState => {
89 warn!("unsupported imgui draw command: reset render state");
90 None
91 }
92 DrawCmd::RawCallback { .. } => {
93 warn!("unsupported imgui draw command: raw callback");
94 None
95 }
96 }
97}
98
99impl<'a> Frame<'a> {
100 pub fn image(&mut self, image: impl Into<ImageSource>) -> Image<'_> {
102 let id = TextureId::new(*self.next_texture_id);
103 *self.next_texture_id += 1;
104 self.user_images.insert(id, image.into().image);
105
106 Image {
107 id,
108 _frame: PhantomData,
109 }
110 }
111}
112
113impl Image<'_> {
114 pub const fn id(&self) -> TextureId {
116 self.id
117 }
118}
119
120impl Drop for Image<'_> {
121 fn drop(&mut self) {}
122}
123
124impl From<AnyImageNode> for ImageSource {
125 fn from(image: AnyImageNode) -> Self {
126 Self { image }
127 }
128}
129
130impl From<ImageLeaseNode> for ImageSource {
131 fn from(image: ImageLeaseNode) -> Self {
132 Self {
133 image: image.into(),
134 }
135 }
136}
137
138impl From<ImageNode> for ImageSource {
139 fn from(image: ImageNode) -> Self {
140 Self {
141 image: image.into(),
142 }
143 }
144}
145
146impl ImGui {
147 pub fn new(device: &Device) -> Self {
149 let mut context = Context::create();
150 let platform = WinitPlatform::new(&mut context);
151 let pipeline = GraphicsPipeline::create(
152 device,
153 GraphicsPipelineInfo::builder()
154 .blend(BlendInfo::PRE_MULTIPLIED_ALPHA)
155 .cull_mode(vk::CullModeFlags::NONE),
156 [
157 Shader::new_vertex(include_glsl!("res/shader/imgui.vert").as_slice()),
158 Shader::new_fragment(include_glsl!("res/shader/imgui.frag").as_slice()),
159 ],
160 )
161 .expect("invalid imgui pipeline");
162
163 Self {
164 context,
165 font_atlas_image: None,
166 next_texture_id: 1,
167 pipeline,
168 platform,
169 user_images: Default::default(),
170 }
171 }
172
173 pub fn draw<P>(
179 &mut self,
180 dt: f32,
181 events: &[Event<()>],
182 window: &Window,
183 pool: &mut P,
184 graph: &mut Graph,
185 ui_func: impl FnOnce(&mut Frame<'_>, &mut Ui, &mut P, &mut Graph),
186 ) -> ImageLeaseNode
187 where
188 P: Pool<BufferInfo, Buffer> + Pool<ImageInfo, DriverImage>,
189 {
190 let hidpi = self.platform.hidpi_factor();
191
192 self.platform
193 .attach_window(self.context.io_mut(), window, HiDpiMode::Default);
194
195 if self.font_atlas_image.is_none() || self.platform.hidpi_factor() != hidpi {
196 self.lease_font_atlas_image(pool, graph);
197 }
198
199 let io = self.context.io_mut();
200 io.update_delta_time(Duration::from_secs_f32(dt));
201
202 for event in events {
203 self.platform.handle_event(io, window, event);
204 }
205
206 self.platform
207 .prepare_frame(io, window)
208 .expect("invalid imgui frame");
209
210 let ui = self.context.frame();
212 let mut frame = Frame {
213 next_texture_id: &mut self.next_texture_id,
214 user_images: &mut self.user_images,
215 };
216
217 ui_func(&mut frame, ui, pool, graph);
218
219 self.platform.prepare_render(ui, window);
220 let draw_data = self.context.render();
221
222 let image = graph.bind_resource(
223 pool.resource(ImageInfo::image_2d(
224 window.inner_size().width,
225 window.inner_size().height,
226 vk::Format::R8G8B8A8_UNORM,
227 vk::ImageUsageFlags::COLOR_ATTACHMENT
228 | vk::ImageUsageFlags::SAMPLED
229 | vk::ImageUsageFlags::STORAGE
230 | vk::ImageUsageFlags::TRANSFER_DST
231 | vk::ImageUsageFlags::TRANSFER_SRC, ))
234 .expect("missing imgui output image")
235 .with_debug_name("ImGui Output"),
236 );
237 let font_atlas_image = graph.bind_resource(
238 self.font_atlas_image
239 .as_ref()
240 .expect("missing imgui font atlas image"),
241 );
242 let display_pos = draw_data.display_pos;
243 let framebuffer_scale = draw_data.framebuffer_scale;
244
245 graph.clear_color_image(image, [0f32; 4]);
246
247 if draw_data.draw_lists_count() == 0 {
248 return image;
249 }
250
251 for draw_list in draw_data.draw_lists() {
252 let indices = cast_slice(draw_list.idx_buffer());
253 let mut index_buf = pool
254 .resource(BufferInfo::host_mem(
255 indices.len() as _,
256 vk::BufferUsageFlags::INDEX_BUFFER,
257 ))
258 .expect("missing imgui index buffer");
259
260 {
261 Buffer::mapped_slice_mut(&mut index_buf)[0..indices.len()].copy_from_slice(indices);
262 }
263
264 let index_buf = graph.bind_resource(index_buf);
265
266 let vertices = draw_list.vtx_buffer();
267 let vertex_buf_len = vertices.len() * 20;
268 let mut vertex_buf = pool
269 .resource(BufferInfo::host_mem(
270 vertex_buf_len as _,
271 vk::BufferUsageFlags::VERTEX_BUFFER,
272 ))
273 .expect("missing imgui vertex buffer");
274
275 {
276 let vertex_buf = Buffer::mapped_slice_mut(&mut vertex_buf);
277 for (idx, vertex) in vertices.iter().enumerate() {
278 let offset = idx * 20;
279 vertex_buf[offset..offset + 8].copy_from_slice(cast_slice(&vertex.pos));
280 vertex_buf[offset + 8..offset + 16].copy_from_slice(cast_slice(&vertex.uv));
281 vertex_buf[offset + 16..offset + 20].copy_from_slice(&vertex.col);
282 }
283 }
284
285 let vertex_buf = graph.bind_resource(vertex_buf);
286
287 let draw_cmds = draw_list
288 .commands()
289 .filter_map(supported_draw_cmd)
290 .collect::<Vec<_>>();
291
292 let window_width =
293 self.platform.hidpi_factor() as f32 / window.inner_size().width as f32;
294 let window_height =
295 self.platform.hidpi_factor() as f32 / window.inner_size().height as f32;
296
297 for (index_count, clip_rect, first_index, vertex_offset, texture_id) in draw_cmds {
298 let texture = self
299 .user_images
300 .get(&texture_id)
301 .copied()
302 .unwrap_or_else(|| font_atlas_image.into());
303 let clip_rect = [
304 (clip_rect[0] - display_pos[0]) * framebuffer_scale[0],
305 (clip_rect[1] - display_pos[1]) * framebuffer_scale[1],
306 (clip_rect[2] - display_pos[0]) * framebuffer_scale[0],
307 (clip_rect[3] - display_pos[1]) * framebuffer_scale[1],
308 ];
309 let x = clip_rect[0].floor() as i32;
310 let y = clip_rect[1].floor() as i32;
311 let width = (clip_rect[2] - clip_rect[0]).ceil() as u32;
312 let height = (clip_rect[3] - clip_rect[1]).ceil() as u32;
313
314 graph
315 .begin_cmd()
316 .debug_name("imgui")
317 .bind_pipeline(&self.pipeline)
318 .resource_access(index_buf, AccessType::IndexBuffer)
319 .resource_access(vertex_buf, AccessType::VertexBuffer)
320 .shader_resource_access(
321 0,
322 texture,
323 AccessType::FragmentShaderReadSampledImageOrUniformTexelBuffer,
324 )
325 .color_attachment_image(0, image, LoadOp::Load, StoreOp::Store)
326 .record_cmd(move |cmd| {
327 cmd.push_constants(0, &window_width.to_ne_bytes())
328 .push_constants(4, &window_height.to_ne_bytes())
329 .bind_index_buffer(index_buf, 0, vk::IndexType::UINT16)
330 .bind_vertex_buffer(0, vertex_buf, 0)
331 .set_scissor(
332 0,
333 &[vk::Rect2D {
334 offset: vk::Offset2D { x, y },
335 extent: vk::Extent2D { width, height },
336 }],
337 )
338 .draw_indexed(
339 index_count as _,
340 1,
341 first_index as _,
342 vertex_offset as _,
343 0,
344 );
345 });
346 }
347 }
348
349 self.user_images.clear();
350 self.next_texture_id = 1;
351
352 image
353 }
354
355 fn lease_font_atlas_image<P>(&mut self, pool: &mut P, graph: &mut Graph)
356 where
357 P: Pool<BufferInfo, Buffer> + Pool<ImageInfo, DriverImage>,
358 {
359 use imgui::{FontConfig, FontGlyphRanges, FontSource};
360
361 let hidpi_factor = self.platform.hidpi_factor();
362 self.context.io_mut().font_global_scale = (1.0 / hidpi_factor) as f32;
363
364 let font_size = (14.0 * hidpi_factor) as f32;
365 let fonts = self.context.fonts();
366 fonts.clear_fonts();
367 fonts.add_font(&[
368 FontSource::TtfData {
369 data: include_bytes!("../res/font/roboto/roboto-regular.ttf"),
370 size_pixels: font_size,
371 config: Some(FontConfig {
372 rasterizer_multiply: 2.0,
373 glyph_ranges: FontGlyphRanges::japanese(),
374 ..FontConfig::default()
375 }),
376 },
377 FontSource::TtfData {
378 data: include_bytes!("../res/font/mplus-1p/mplus-1p-regular.ttf"),
379 size_pixels: font_size,
380 config: Some(FontConfig {
381 oversample_h: 2,
382 oversample_v: 2,
383 glyph_ranges: FontGlyphRanges::japanese(),
385 ..FontConfig::default()
386 }),
387 },
388 ]);
389
390 let texture = fonts.build_rgba32_texture(); let temp_buf_len = texture.data.len();
392 let mut temp_buf = pool
393 .resource(BufferInfo::host_mem(
394 temp_buf_len as _,
395 vk::BufferUsageFlags::TRANSFER_SRC,
396 ))
397 .expect("missing imgui font atlas buffer");
398
399 {
400 let temp_buf = Buffer::mapped_slice_mut(&mut temp_buf);
401 temp_buf[0..temp_buf_len].copy_from_slice(texture.data);
402 }
403
404 let temp_buf = graph.bind_resource(temp_buf);
405 let image = pool
406 .resource(ImageInfo::image_2d(
407 texture.width,
408 texture.height,
409 vk::Format::R8G8B8A8_UNORM,
410 vk::ImageUsageFlags::SAMPLED
411 | vk::ImageUsageFlags::STORAGE
412 | vk::ImageUsageFlags::TRANSFER_DST,
413 ))
414 .expect("missing imgui font atlas image")
415 .with_debug_name("ImGui Font Atlas");
416
417 let image = graph.bind_resource(image);
418
419 graph.copy_buffer_to_image(temp_buf, image);
420
421 self.font_atlas_image = Some(graph.resource(image).clone());
422 }
423}
424
425#[cfg(test)]
426mod test {
427 use super::supported_draw_cmd;
428 use imgui::{DrawCmd, DrawCmdParams, TextureId};
429
430 #[test]
431 fn supported_draw_cmd_extracts_element_draws() {
432 let draw_cmd = DrawCmd::Elements {
433 count: 42,
434 cmd_params: DrawCmdParams {
435 clip_rect: [1.0, 2.0, 3.0, 4.0],
436 texture_id: TextureId::new(7),
437 vtx_offset: 5,
438 idx_offset: 6,
439 },
440 };
441
442 assert_eq!(
443 supported_draw_cmd(draw_cmd),
444 Some((42, [1.0, 2.0, 3.0, 4.0], 6, 5, TextureId::new(7)))
445 );
446 }
447
448 #[test]
449 fn supported_draw_cmd_skips_reset_render_state() {
450 assert_eq!(supported_draw_cmd(DrawCmd::ResetRenderState), None);
451 }
452}