1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
mod allocator;
pub mod vulkan;

use crate::RendererError;
use ash::{vk, Device};
use imgui::{Context, DrawCmd, DrawCmdParams, DrawData, TextureId, Textures};
use mesh::*;
use ultraviolet::projection::orthographic_vk;
use vulkan::*;

use self::allocator::Allocator;

#[cfg(not(any(feature = "gpu-allocator", feature = "vk-mem")))]
use ash::Instance;

#[cfg(feature = "gpu-allocator")]
use {
    gpu_allocator::vulkan::Allocator as GpuAllocator,
    std::sync::{Arc, Mutex},
};

#[cfg(feature = "vk-mem")]
use {
    std::sync::{Arc, Mutex},
    vk_mem::Allocator as VkMemAllocator,
};

/// Convenient return type for function that can return a [`RendererError`].
///
/// [`RendererError`]: enum.RendererError.html
pub type RendererResult<T> = Result<T, RendererError>;

/// Optional parameters of the renderer.
#[derive(Debug, Clone, Copy)]
pub struct Options {
    /// The number of in flight frames of the application.
    pub in_flight_frames: usize,
    /// If true enables depth test when rendering.
    pub enable_depth_test: bool,
    /// If true enables depth writes when rendering.
    ///
    /// Note that depth writes are always disabled when enable_depth_test is false.
    /// See <https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkPipelineDepthStencilStateCreateInfo.html>
    pub enable_depth_write: bool,
}

impl Default for Options {
    fn default() -> Self {
        Self {
            in_flight_frames: 1,
            enable_depth_test: false,
            enable_depth_write: false,
        }
    }
}

/// `dynamic-rendering` feature related params
#[cfg(feature = "dynamic-rendering")]
#[derive(Debug, Clone, Copy)]
pub struct DynamicRendering {
    pub color_attachment_format: vk::Format,
    pub depth_attachment_format: Option<vk::Format>,
}

/// Vulkan renderer for imgui.
///
/// It records rendering command to the provided command buffer at each call to [`cmd_draw`].
///
/// The renderer holds a set of vertex/index buffers per in flight frames. Vertex and index buffers
/// are resized at each call to [`cmd_draw`] if draw data does not fit.
///
/// [`cmd_draw`]: #method.cmd_draw
pub struct Renderer {
    device: Device,
    allocator: Allocator,
    pipeline: vk::Pipeline,
    pipeline_layout: vk::PipelineLayout,
    descriptor_set_layout: vk::DescriptorSetLayout,
    fonts_texture: Option<Texture>,
    descriptor_pool: vk::DescriptorPool,
    descriptor_set: vk::DescriptorSet,
    textures: Textures<vk::DescriptorSet>,
    options: Options,
    frames: Option<Frames>,
}

impl Renderer {
    /// Initialize and return a new instance of the renderer.
    ///
    /// At initialization all Vulkan resources are initialized and font texture is created and
    /// uploaded to the gpu. Vertex and index buffers are not created yet.
    ///
    /// # Arguments
    ///
    /// * `instance` - A reference to a Vulkan instance.
    /// * `physical_device` - A Vulkan physical device.
    /// * `device` - A Vulkan device.
    /// * `queue` - A Vulkan queue.
    ///             It will be used to submit commands during initialization to upload
    ///             data to the gpu. The type of queue must be supported by the following
    ///             commands: [vkCmdCopyBufferToImage](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkCmdCopyBufferToImage.html),
    ///             [vkCmdPipelineBarrier](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkCmdPipelineBarrier.html)
    /// * `command_pool` - A Vulkan command pool used to allocate command buffers to upload textures to the gpu.
    /// * `render_pass` - *without dynamic-rendering feature* - The render pass used to render the gui.
    /// * `dynamic_rendering` - *with dynamic-rendering feature* - Dynamic rendeing parameters
    /// * `imgui` - The imgui context.
    /// * `options` - Optional parameters of the renderer.
    ///
    /// # Errors
    ///
    /// * [`RendererError`] - If the number of in flight frame in incorrect.
    /// * [`RendererError`] - If any Vulkan or io error is encountered during initialization.
    #[cfg(not(any(feature = "gpu-allocator", feature = "vk-mem")))]
    pub fn with_default_allocator(
        instance: &Instance,
        physical_device: vk::PhysicalDevice,
        device: Device,
        queue: vk::Queue,
        command_pool: vk::CommandPool,
        #[cfg(not(feature = "dynamic-rendering"))] render_pass: vk::RenderPass,
        #[cfg(feature = "dynamic-rendering")] dynamic_rendering: DynamicRendering,
        imgui: &mut Context,
        options: Option<Options>,
    ) -> RendererResult<Self> {
        let memory_properties =
            unsafe { instance.get_physical_device_memory_properties(physical_device) };

        Self::from_allocator(
            device,
            queue,
            command_pool,
            Allocator::new(memory_properties),
            #[cfg(not(feature = "dynamic-rendering"))]
            render_pass,
            #[cfg(feature = "dynamic-rendering")]
            dynamic_rendering,
            imgui,
            options,
        )
    }

    /// Initialize and return a new instance of the renderer.
    ///
    /// At initialization all Vulkan resources are initialized and font texture is created and
    /// uploaded to the gpu. Vertex and index buffers are not created yet.
    ///
    /// # Arguments
    ///
    /// * `gpu_allocator` - The allocator that will be used to allocator buffer and image memory.
    /// * `device` - A Vulkan device.
    /// * `queue` - A Vulkan queue.
    ///             It will be used to submit commands during initialization to upload
    ///             data to the gpu. The type of queue must be supported by the following
    ///             commands: [vkCmdCopyBufferToImage](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkCmdCopyBufferToImage.html),
    ///             [vkCmdPipelineBarrier](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkCmdPipelineBarrier.html)
    /// * `command_pool` - A Vulkan command pool used to allocate command buffers to upload textures to the gpu.
    /// * `render_pass` - *without dynamic-rendering feature* - The render pass used to render the gui.
    /// * `dynamic_rendering` - *with dynamic-rendering feature* - Dynamic rendeing parameters
    /// * `imgui` - The imgui context.
    /// * `options` - Optional parameters of the renderer.
    ///
    /// # Errors
    ///
    /// * [`RendererError`] - If the number of in flight frame in incorrect.
    /// * [`RendererError`] - If any Vulkan or io error is encountered during initialization.
    #[cfg(feature = "gpu-allocator")]
    pub fn with_gpu_allocator(
        gpu_allocator: Arc<Mutex<GpuAllocator>>, // TODO: Another way ?
        device: Device,
        queue: vk::Queue,
        command_pool: vk::CommandPool,
        #[cfg(not(feature = "dynamic-rendering"))] render_pass: vk::RenderPass,
        #[cfg(feature = "dynamic-rendering")] dynamic_rendering: DynamicRendering,
        imgui: &mut Context,
        options: Option<Options>,
    ) -> RendererResult<Self> {
        Self::from_allocator(
            device,
            queue,
            command_pool,
            Allocator::new(gpu_allocator),
            #[cfg(not(feature = "dynamic-rendering"))]
            render_pass,
            #[cfg(feature = "dynamic-rendering")]
            dynamic_rendering,
            imgui,
            options,
        )
    }

    /// Initialize and return a new instance of the renderer.
    ///
    /// At initialization all Vulkan resources are initialized and font texture is created and
    /// uploaded to the gpu. Vertex and index buffers are not created yet.
    ///
    /// # Arguments
    ///
    /// * `vk_mem_allocator` - The allocator that will be used to allocator buffer and image memory.
    /// * `device` - A Vulkan device.
    /// * `queue` - A Vulkan queue.
    ///             It will be used to submit commands during initialization to upload
    ///             data to the gpu. The type of queue must be supported by the following
    ///             commands: [vkCmdCopyBufferToImage](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkCmdCopyBufferToImage.html),
    ///             [vkCmdPipelineBarrier](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkCmdPipelineBarrier.html)
    /// * `command_pool` - A Vulkan command pool used to allocate command buffers to upload textures to the gpu.
    /// * `render_pass` - *without dynamic-rendering feature* - The render pass used to render the gui.
    /// * `dynamic_rendering` - *with dynamic-rendering feature* - Dynamic rendeing parameters
    /// * `imgui` - The imgui context.
    /// * `options` - Optional parameters of the renderer.
    ///
    /// # Errors
    ///
    /// * [`RendererError`] - If the number of in flight frame in incorrect.
    /// * [`RendererError`] - If any Vulkan or io error is encountered during initialization.
    #[cfg(feature = "vk-mem")]
    pub fn with_vk_mem_allocator(
        vk_mem_allocator: Arc<Mutex<VkMemAllocator>>, // TODO: Another way ?
        device: Device,
        queue: vk::Queue,
        command_pool: vk::CommandPool,
        #[cfg(not(feature = "dynamic-rendering"))] render_pass: vk::RenderPass,
        #[cfg(feature = "dynamic-rendering")] dynamic_rendering: DynamicRendering,
        imgui: &mut Context,
        options: Option<Options>,
    ) -> RendererResult<Self> {
        Self::from_allocator(
            device,
            queue,
            command_pool,
            Allocator::new(vk_mem_allocator),
            #[cfg(not(feature = "dynamic-rendering"))]
            render_pass,
            #[cfg(feature = "dynamic-rendering")]
            dynamic_rendering,
            imgui,
            options,
        )
    }

    fn from_allocator(
        device: Device,
        queue: vk::Queue,
        command_pool: vk::CommandPool,
        mut allocator: Allocator,
        #[cfg(not(feature = "dynamic-rendering"))] render_pass: vk::RenderPass,
        #[cfg(feature = "dynamic-rendering")] dynamic_rendering: DynamicRendering,
        imgui: &mut Context,
        options: Option<Options>,
    ) -> RendererResult<Self> {
        let options = options.unwrap_or_default();

        log::debug!("Creating imgui renderer with options {options:?}");

        if options.in_flight_frames == 0 {
            return Err(RendererError::Init(String::from(
                "'in_flight_frames' parameter should be at least one",
            )));
        }

        // Descriptor set layout
        let descriptor_set_layout = create_vulkan_descriptor_set_layout(&device)?;

        // Pipeline and layout
        let pipeline_layout = create_vulkan_pipeline_layout(&device, descriptor_set_layout)?;
        let pipeline = create_vulkan_pipeline(
            &device,
            pipeline_layout,
            #[cfg(not(feature = "dynamic-rendering"))]
            render_pass,
            #[cfg(feature = "dynamic-rendering")]
            dynamic_rendering,
            options,
        )?;

        // Fonts texture
        let fonts_texture = {
            let fonts = imgui.fonts();
            let atlas_texture = fonts.build_rgba32_texture();

            Texture::from_rgba8(
                &device,
                queue,
                command_pool,
                &mut allocator,
                atlas_texture.width,
                atlas_texture.height,
                atlas_texture.data,
            )?
        };

        let fonts = imgui.fonts();
        fonts.tex_id = TextureId::from(usize::MAX);

        // Descriptor pool
        let descriptor_pool = create_vulkan_descriptor_pool(&device, 1)?;

        // Descriptor set
        let descriptor_set = create_vulkan_descriptor_set(
            &device,
            descriptor_set_layout,
            descriptor_pool,
            fonts_texture.image_view,
            fonts_texture.sampler,
        )?;

        // Textures
        let textures = Textures::new();

        Ok(Self {
            device,
            allocator,
            pipeline,
            pipeline_layout,
            descriptor_set_layout,
            fonts_texture: Some(fonts_texture),
            descriptor_pool,
            descriptor_set,
            textures,
            options,
            frames: None,
        })
    }

    /// Change the render pass to render to.
    ///
    /// Useful if you need to render to a new render pass but don't want to rebuild
    /// the entire renderer. It will rebuild the graphics pipeline from scratch so it
    /// is an expensive operation.
    ///
    /// # Arguments
    ///
    /// * `render_pass` - The render pass used to render the gui.
    ///
    /// # Errors
    ///
    /// * [`RendererError`] - If any Vulkan error is encountered during pipeline creation.
    #[cfg(not(feature = "dynamic-rendering"))]
    pub fn set_render_pass(&mut self, render_pass: vk::RenderPass) -> RendererResult<()> {
        unsafe { self.device.destroy_pipeline(self.pipeline, None) };
        self.pipeline = create_vulkan_pipeline(
            &self.device,
            self.pipeline_layout,
            render_pass,
            self.options,
        )?;
        Ok(())
    }

    /// Returns the texture mapping used by the renderer to lookup textures.
    ///
    /// Textures are provided by the application as `vk::DescriptorSet`s.
    ///
    /// # Example
    ///
    /// ```ignore
    /// let descriptor_set = ...;
    /// // Insert a vk::DescriptorSet in the renderer textures map.
    /// // The renderer returns a generated texture id.
    /// let texture_id = renderer.textures().insert(descriptor_set);
    /// ...
    /// // Create an `Image` that references the texture by its id.
    /// Image::new(texture_id, [100, 100]).build(&ui);
    /// ```
    ///
    /// # Caveat
    ///
    /// Provided `vk::DescriptorSet`s must be created with a descriptor set layout that is compatible with the one used by the renderer.
    /// See [Pipeline Layout Compatibility](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#descriptorsets-compatibility).
    pub fn textures(&mut self) -> &mut Textures<vk::DescriptorSet> {
        &mut self.textures
    }

    fn lookup_descriptor_set(&self, texture_id: TextureId) -> RendererResult<vk::DescriptorSet> {
        if texture_id.id() == usize::MAX {
            Ok(self.descriptor_set)
        } else if let Some(descriptor_set) = self.textures.get(texture_id) {
            Ok(*descriptor_set)
        } else {
            Err(RendererError::BadTexture(texture_id))
        }
    }

    /// Update the fonts texture after having added new fonts to imgui.
    ///
    /// # Arguments
    ///
    /// * `queue` - A Vulkan queue.
    ///             It will be used to submit commands during initialization to upload
    ///             data to the gpu. The type of queue must be supported by the following
    ///             commands: [vkCmdCopyBufferToImage](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkCmdCopyBufferToImage.html),
    ///             [vkCmdPipelineBarrier](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkCmdPipelineBarrier.html)
    /// * `command_pool` - A Vulkan command pool used to allocate command buffers to upload textures to the gpu.
    /// * `imgui` - The imgui context.
    ///
    /// # Errors
    ///
    /// * [`RendererError`] - If any error is encountered during texture update.
    pub fn update_fonts_texture(
        &mut self,
        queue: vk::Queue,
        command_pool: vk::CommandPool,
        imgui: &mut Context,
    ) -> RendererResult<()> {
        // Generate the new fonts texture
        let fonts_texture = {
            let fonts = imgui.fonts();
            let atlas_texture = fonts.build_rgba32_texture();

            Texture::from_rgba8(
                &self.device,
                queue,
                command_pool,
                &mut self.allocator,
                atlas_texture.width,
                atlas_texture.height,
                atlas_texture.data,
            )?
        };

        let fonts = imgui.fonts();
        fonts.tex_id = TextureId::from(usize::MAX);

        // Free Descriptor set the create a new one
        let old_descriptor_set = self.descriptor_set;
        unsafe {
            self.device
                .free_descriptor_sets(self.descriptor_pool, &[old_descriptor_set])?
        };
        self.descriptor_set = create_vulkan_descriptor_set(
            &self.device,
            self.descriptor_set_layout,
            self.descriptor_pool,
            fonts_texture.image_view,
            fonts_texture.sampler,
        )?;

        // Free old fonts texture
        let mut old_texture = self.fonts_texture.replace(fonts_texture);
        if let Some(texture) = old_texture.take() {
            texture.destroy(&self.device, &mut self.allocator)?;
        }

        Ok(())
    }

    /// Record commands required to render the gui.RendererError.
    ///
    /// # Arguments
    ///
    /// * `command_buffer` - The Vulkan command buffer that command will be recorded to.
    /// * `draw_data` - A reference to the imgui `DrawData` containing rendering data.
    ///
    /// # Errors
    ///
    /// * [`RendererError`] - If any Vulkan error is encountered during command recording.
    pub fn cmd_draw(
        &mut self,
        command_buffer: vk::CommandBuffer,
        draw_data: &DrawData,
    ) -> RendererResult<()> {
        if draw_data.total_vtx_count == 0 {
            return Ok(());
        }

        if self.frames.is_none() {
            self.frames.replace(Frames::new(
                &self.device,
                &mut self.allocator,
                draw_data,
                self.options.in_flight_frames,
            )?);
        }

        let mesh = self.frames.as_mut().unwrap().next();
        mesh.update(&self.device, &mut self.allocator, draw_data)?;

        unsafe {
            self.device.cmd_bind_pipeline(
                command_buffer,
                vk::PipelineBindPoint::GRAPHICS,
                self.pipeline,
            )
        };

        let framebuffer_width = draw_data.framebuffer_scale[0] * draw_data.display_size[0];
        let framebuffer_height = draw_data.framebuffer_scale[1] * draw_data.display_size[1];
        let viewports = [vk::Viewport {
            width: framebuffer_width,
            height: framebuffer_height,
            max_depth: 1.0,
            ..Default::default()
        }];

        unsafe { self.device.cmd_set_viewport(command_buffer, 0, &viewports) };

        // Ortho projection
        let projection = orthographic_vk(
            0.0,
            draw_data.display_size[0],
            0.0,
            -draw_data.display_size[1],
            -1.0,
            1.0,
        );
        unsafe {
            let push = any_as_u8_slice(&projection);
            self.device.cmd_push_constants(
                command_buffer,
                self.pipeline_layout,
                vk::ShaderStageFlags::VERTEX,
                0,
                push,
            )
        };

        unsafe {
            self.device.cmd_bind_index_buffer(
                command_buffer,
                mesh.indices,
                0,
                vk::IndexType::UINT16,
            )
        };

        unsafe {
            self.device
                .cmd_bind_vertex_buffers(command_buffer, 0, &[mesh.vertices], &[0])
        };

        let mut index_offset = 0;
        let mut vertex_offset = 0;
        let mut current_texture_id: Option<TextureId> = None;
        let clip_offset = draw_data.display_pos;
        let clip_scale = draw_data.framebuffer_scale;
        for draw_list in draw_data.draw_lists() {
            for command in draw_list.commands() {
                match command {
                    DrawCmd::Elements {
                        count,
                        cmd_params:
                            DrawCmdParams {
                                clip_rect,
                                texture_id,
                                vtx_offset,
                                idx_offset,
                            },
                    } => {
                        unsafe {
                            let clip_x = (clip_rect[0] - clip_offset[0]) * clip_scale[0];
                            let clip_y = (clip_rect[1] - clip_offset[1]) * clip_scale[1];
                            let clip_w = (clip_rect[2] - clip_offset[0]) * clip_scale[0] - clip_x;
                            let clip_h = (clip_rect[3] - clip_offset[1]) * clip_scale[1] - clip_y;

                            let scissors = [vk::Rect2D {
                                offset: vk::Offset2D {
                                    x: (clip_x as i32).max(0),
                                    y: (clip_y as i32).max(0),
                                },
                                extent: vk::Extent2D {
                                    width: clip_w as _,
                                    height: clip_h as _,
                                },
                            }];
                            self.device.cmd_set_scissor(command_buffer, 0, &scissors);
                        }

                        if Some(texture_id) != current_texture_id {
                            let descriptor_set = self.lookup_descriptor_set(texture_id)?;
                            unsafe {
                                self.device.cmd_bind_descriptor_sets(
                                    command_buffer,
                                    vk::PipelineBindPoint::GRAPHICS,
                                    self.pipeline_layout,
                                    0,
                                    &[descriptor_set],
                                    &[],
                                )
                            };
                            current_texture_id = Some(texture_id);
                        }

                        unsafe {
                            self.device.cmd_draw_indexed(
                                command_buffer,
                                count as _,
                                1,
                                index_offset + idx_offset as u32,
                                vertex_offset + vtx_offset as i32,
                                0,
                            )
                        };
                    }
                    DrawCmd::ResetRenderState => {
                        log::trace!("Reset render state command not yet supported")
                    }
                    DrawCmd::RawCallback { .. } => {
                        log::trace!("Raw callback command not yet supported")
                    }
                }
            }

            index_offset += draw_list.idx_buffer().len() as u32;
            vertex_offset += draw_list.vtx_buffer().len() as i32;
        }

        Ok(())
    }
}

impl Drop for Renderer {
    fn drop(&mut self) {
        log::debug!("Destroying ImGui Renderer");
        let device = &self.device;

        unsafe {
            if let Some(frames) = self.frames.take() {
                frames
                    .destroy(device, &mut self.allocator)
                    .expect("Failed to destroy frame data");
            }
            device.destroy_pipeline(self.pipeline, None);
            device.destroy_pipeline_layout(self.pipeline_layout, None);
            device.destroy_descriptor_pool(self.descriptor_pool, None);
            self.fonts_texture
                .take()
                .unwrap()
                .destroy(device, &mut self.allocator)
                .expect("Failed to fronts data");
            device.destroy_descriptor_set_layout(self.descriptor_set_layout, None);
        }
    }
}

// Structure holding data for all frames in flight.
struct Frames {
    index: usize,
    count: usize,
    meshes: Vec<Mesh>,
}

impl Frames {
    fn new(
        device: &Device,
        allocator: &mut Allocator,
        draw_data: &DrawData,
        count: usize,
    ) -> RendererResult<Self> {
        let meshes = (0..count)
            .map(|_| Mesh::new(device, allocator, draw_data))
            .collect::<Result<Vec<_>, _>>()?;
        Ok(Self {
            index: 0,
            count,
            meshes,
        })
    }

    fn next(&mut self) -> &mut Mesh {
        let result = &mut self.meshes[self.index];
        self.index = (self.index + 1) % self.count;
        result
    }

    fn destroy(self, device: &Device, allocator: &mut Allocator) -> RendererResult<()> {
        for mesh in self.meshes.into_iter() {
            mesh.destroy(device, allocator)?;
        }
        Ok(())
    }
}

mod mesh {

    use super::allocator::{Allocate, Allocator, Memory};
    use super::vulkan::*;
    use crate::RendererResult;
    use ash::{vk, Device};
    use imgui::{DrawData, DrawVert};
    use std::mem::size_of;

    /// Vertex and index buffer resources for one frame in flight.
    pub struct Mesh {
        pub vertices: vk::Buffer,
        vertices_mem: Memory,
        vertex_count: usize,
        pub indices: vk::Buffer,
        indices_mem: Memory,
        index_count: usize,
    }

    impl Mesh {
        pub fn new(
            device: &Device,
            allocator: &mut Allocator,
            draw_data: &DrawData,
        ) -> RendererResult<Self> {
            let vertices = create_vertices(draw_data);
            let vertex_count = vertices.len();
            let indices = create_indices(draw_data);
            let index_count = indices.len();

            // Create a vertex buffer
            let (vertices, vertices_mem) = create_and_fill_buffer(
                device,
                allocator,
                &vertices,
                vk::BufferUsageFlags::VERTEX_BUFFER,
            )?;

            // Create an index buffer
            let (indices, indices_mem) = create_and_fill_buffer(
                device,
                allocator,
                &indices,
                vk::BufferUsageFlags::INDEX_BUFFER,
            )?;

            Ok(Mesh {
                vertices,
                vertices_mem,
                vertex_count,
                indices,
                indices_mem,
                index_count,
            })
        }

        pub fn update(
            &mut self,
            device: &Device,
            allocator: &mut Allocator,
            draw_data: &DrawData,
        ) -> RendererResult<()> {
            let vertices = create_vertices(draw_data);
            if draw_data.total_vtx_count as usize > self.vertex_count {
                log::trace!("Resizing vertex buffers");

                let vertex_count = vertices.len();
                let size = vertex_count * size_of::<DrawVert>();
                let (vertices, vertices_mem) =
                    allocator.create_buffer(device, size, vk::BufferUsageFlags::VERTEX_BUFFER)?;

                self.vertex_count = vertex_count;

                let old_vertices = self.vertices;
                self.vertices = vertices;

                let old_vertices_mem = std::mem::replace(&mut self.vertices_mem, vertices_mem);

                allocator.destroy_buffer(device, old_vertices, old_vertices_mem)?;
            }
            allocator.update_buffer(device, &mut self.vertices_mem, &vertices)?;

            let indices = create_indices(draw_data);
            if draw_data.total_idx_count as usize > self.index_count {
                log::trace!("Resizing index buffers");

                let index_count = indices.len();
                let size = index_count * size_of::<u16>();
                let (indices, indices_mem) =
                    allocator.create_buffer(device, size, vk::BufferUsageFlags::INDEX_BUFFER)?;

                self.index_count = index_count;

                let old_indices = self.indices;
                self.indices = indices;

                let old_indices_mem = std::mem::replace(&mut self.indices_mem, indices_mem);

                allocator.destroy_buffer(device, old_indices, old_indices_mem)?;
            }
            allocator.update_buffer(device, &mut self.indices_mem, &indices)?;

            Ok(())
        }

        pub fn destroy(self, device: &Device, allocator: &mut Allocator) -> RendererResult<()> {
            allocator.destroy_buffer(device, self.vertices, self.vertices_mem)?;
            allocator.destroy_buffer(device, self.indices, self.indices_mem)?;
            Ok(())
        }
    }

    fn create_vertices(draw_data: &DrawData) -> Vec<DrawVert> {
        let vertex_count = draw_data.total_vtx_count as usize;
        let mut vertices = Vec::with_capacity(vertex_count);
        for draw_list in draw_data.draw_lists() {
            vertices.extend_from_slice(draw_list.vtx_buffer());
        }
        vertices
    }

    fn create_indices(draw_data: &DrawData) -> Vec<u16> {
        let index_count = draw_data.total_idx_count as usize;
        let mut indices = Vec::with_capacity(index_count);
        for draw_list in draw_data.draw_lists() {
            indices.extend_from_slice(draw_list.idx_buffer());
        }
        indices
    }
}