Skip to main content

imgui_vulkan_renderer_rs/
lib.rs

1//! Vulkan renderer for [Dear ImGui](https://github.com/ocornut/imgui)
2//! via the [`imgui`](https://docs.rs/imgui) crate and
3//! [`ash`](https://docs.rs/ash).
4//!
5//! This crate provides a [`Renderer`] that loads pre-compiled SPIR-V shaders,
6//! manages GPU resources (buffers, images, descriptor sets), and converts imgui
7//! draw commands into Vulkan command buffer recordings. The renderer owns all of
8//! its Vulkan objects and destroys them on [`Drop`].
9//!
10//! Pair this crate with
11//! [`imgui-glfw-rs`](https://crates.io/crates/imgui-glfw-rs) for a complete
12//! GLFW + Vulkan + Dear ImGui integration.
13//!
14//! # Example
15//!
16//! ```rust,ignore
17//! use imgui::Context;
18//! use imgui_vulkan_renderer_rs::{Renderer, RendererCreateInfo};
19//!
20//! let mut imgui = Context::create();
21//!
22//! let create_info = RendererCreateInfo {
23//!     device: device.clone(),
24//!     memory_properties: mem_props,
25//!     render_pass,
26//!     command_pool,
27//!     queue,
28//! };
29//! let mut renderer = Renderer::new(&mut imgui, &create_info)
30//!     .expect("Failed to initialize renderer");
31//!
32//! // In your main loop, after building the imgui frame:
33//! let draw_data = imgui.render();
34//! renderer.render(draw_data, command_buffer)?;
35//! ```
36
37use ash::util::read_spv;
38use ash::vk;
39use ash::vk::Handle;
40use imgui::internal::RawWrapper;
41use imgui::{Context, DrawCmd, DrawData, DrawIdx, DrawVert, TextureId};
42use std::io::Cursor;
43use std::mem;
44
45/// Errors that can occur during renderer operations.
46#[derive(Debug)]
47pub enum RendererError {
48    /// A Vulkan API call returned an error.
49    Vulkan(vk::Result),
50}
51
52impl std::fmt::Display for RendererError {
53    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54        match self {
55            RendererError::Vulkan(e) => write!(f, "Vulkan error: {e}"),
56        }
57    }
58}
59
60impl std::error::Error for RendererError {}
61
62impl From<vk::Result> for RendererError {
63    fn from(e: vk::Result) -> Self {
64        RendererError::Vulkan(e)
65    }
66}
67
68/// Parameters required to create a [`Renderer`].
69pub struct RendererCreateInfo {
70    /// A cloned `ash::Device` handle. The renderer stores its own clone.
71    pub device: ash::Device,
72    /// Physical device memory properties, used for buffer/image allocation.
73    pub memory_properties: vk::PhysicalDeviceMemoryProperties,
74    /// The render pass the imgui pipeline will be compatible with (subpass 0).
75    pub render_pass: vk::RenderPass,
76    /// A command pool for one-shot upload commands (font texture).
77    pub command_pool: vk::CommandPool,
78    /// A queue that supports graphics and transfer operations.
79    pub queue: vk::Queue,
80}
81
82/// Vulkan renderer for Dear ImGui draw data.
83pub struct Renderer {
84    device: ash::Device,
85    memory_properties: vk::PhysicalDeviceMemoryProperties,
86    pipeline_layout: vk::PipelineLayout,
87    pipeline: vk::Pipeline,
88    descriptor_set_layout: vk::DescriptorSetLayout,
89    descriptor_pool: vk::DescriptorPool,
90    #[allow(dead_code)] // Kept alive; freed when descriptor_pool is destroyed.
91    font_descriptor_set: vk::DescriptorSet,
92    font_image: vk::Image,
93    font_image_memory: vk::DeviceMemory,
94    font_image_view: vk::ImageView,
95    font_sampler: vk::Sampler,
96    vertex_buffer: vk::Buffer,
97    vertex_buffer_memory: vk::DeviceMemory,
98    vertex_buffer_size: vk::DeviceSize,
99    index_buffer: vk::Buffer,
100    index_buffer_memory: vk::DeviceMemory,
101    index_buffer_size: vk::DeviceSize,
102    /// Old buffers waiting for the GPU to finish using them before destruction.
103    retired_buffers: Vec<(vk::Buffer, vk::DeviceMemory)>,
104}
105
106const INITIAL_BUFFER_SIZE: vk::DeviceSize = 64 * 1024;
107
108impl Renderer {
109    /// Create a new Vulkan renderer.
110    ///
111    /// Builds the graphics pipeline, uploads the font atlas, and allocates
112    /// initial vertex/index buffers. The `command_pool` and `queue` in
113    /// `create_info` are used for a one-shot command buffer to upload the font
114    /// texture; they are not stored.
115    pub fn new(imgui: &mut Context, create_info: &RendererCreateInfo) -> Result<Self, RendererError> {
116        let device = &create_info.device;
117        let memory_properties = create_info.memory_properties;
118
119        // --- Descriptor set layout ---
120        let sampler_binding = vk::DescriptorSetLayoutBinding::default()
121            .binding(0)
122            .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
123            .descriptor_count(1)
124            .stage_flags(vk::ShaderStageFlags::FRAGMENT);
125        let ds_layout_info = vk::DescriptorSetLayoutCreateInfo::default()
126            .bindings(std::slice::from_ref(&sampler_binding));
127        let descriptor_set_layout =
128            unsafe { device.create_descriptor_set_layout(&ds_layout_info, None)? };
129
130        // --- Pipeline layout ---
131        let push_constant_range = vk::PushConstantRange {
132            stage_flags: vk::ShaderStageFlags::VERTEX,
133            offset: 0,
134            size: 64, // mat4
135        };
136        let pipeline_layout_info = vk::PipelineLayoutCreateInfo::default()
137            .set_layouts(std::slice::from_ref(&descriptor_set_layout))
138            .push_constant_ranges(std::slice::from_ref(&push_constant_range));
139        let pipeline_layout = unsafe {
140            device
141                .create_pipeline_layout(&pipeline_layout_info, None)
142                .inspect_err(|_| {
143                    device.destroy_descriptor_set_layout(descriptor_set_layout, None);
144                })?
145        };
146
147        // --- Shader modules ---
148        let vert_spv = read_spv(&mut Cursor::new(
149            &include_bytes!("shaders/imgui.vert.spv")[..],
150        ))
151        .expect("failed to read vertex shader SPIR-V");
152        let frag_spv = read_spv(&mut Cursor::new(
153            &include_bytes!("shaders/imgui.frag.spv")[..],
154        ))
155        .expect("failed to read fragment shader SPIR-V");
156
157        let vert_module_info = vk::ShaderModuleCreateInfo::default().code(&vert_spv);
158        let frag_module_info = vk::ShaderModuleCreateInfo::default().code(&frag_spv);
159
160        let vert_module = unsafe {
161            device
162                .create_shader_module(&vert_module_info, None)
163                .inspect_err(|_| {
164                    device.destroy_pipeline_layout(pipeline_layout, None);
165                    device.destroy_descriptor_set_layout(descriptor_set_layout, None);
166                })?
167        };
168        let frag_module = unsafe {
169            device
170                .create_shader_module(&frag_module_info, None)
171                .inspect_err(|_| {
172                    device.destroy_shader_module(vert_module, None);
173                    device.destroy_pipeline_layout(pipeline_layout, None);
174                    device.destroy_descriptor_set_layout(descriptor_set_layout, None);
175                })?
176        };
177
178        let entry_point = c"main";
179
180        let shader_stages = [
181            vk::PipelineShaderStageCreateInfo::default()
182                .stage(vk::ShaderStageFlags::VERTEX)
183                .module(vert_module)
184                .name(entry_point),
185            vk::PipelineShaderStageCreateInfo::default()
186                .stage(vk::ShaderStageFlags::FRAGMENT)
187                .module(frag_module)
188                .name(entry_point),
189        ];
190
191        // --- Vertex input ---
192        let binding_desc = vk::VertexInputBindingDescription {
193            binding: 0,
194            stride: mem::size_of::<DrawVert>() as u32,
195            input_rate: vk::VertexInputRate::VERTEX,
196        };
197        let attribute_descs = [
198            // Position: vec2 at offset 0
199            vk::VertexInputAttributeDescription {
200                location: 0,
201                binding: 0,
202                format: vk::Format::R32G32_SFLOAT,
203                offset: 0,
204            },
205            // UV: vec2 at offset 8
206            vk::VertexInputAttributeDescription {
207                location: 1,
208                binding: 0,
209                format: vk::Format::R32G32_SFLOAT,
210                offset: 8,
211            },
212            // Color: u32 packed RGBA at offset 16
213            vk::VertexInputAttributeDescription {
214                location: 2,
215                binding: 0,
216                format: vk::Format::R8G8B8A8_UNORM,
217                offset: 16,
218            },
219        ];
220        let vertex_input_info = vk::PipelineVertexInputStateCreateInfo::default()
221            .vertex_binding_descriptions(std::slice::from_ref(&binding_desc))
222            .vertex_attribute_descriptions(&attribute_descs);
223
224        let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default()
225            .topology(vk::PrimitiveTopology::TRIANGLE_LIST);
226
227        let viewport_state = vk::PipelineViewportStateCreateInfo::default()
228            .viewport_count(1)
229            .scissor_count(1);
230
231        let rasterization = vk::PipelineRasterizationStateCreateInfo::default()
232            .polygon_mode(vk::PolygonMode::FILL)
233            .cull_mode(vk::CullModeFlags::NONE)
234            .front_face(vk::FrontFace::COUNTER_CLOCKWISE)
235            .line_width(1.0);
236
237        let multisample = vk::PipelineMultisampleStateCreateInfo::default()
238            .rasterization_samples(vk::SampleCountFlags::TYPE_1);
239
240        let color_blend_attachment = vk::PipelineColorBlendAttachmentState::default()
241            .blend_enable(true)
242            .src_color_blend_factor(vk::BlendFactor::SRC_ALPHA)
243            .dst_color_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA)
244            .color_blend_op(vk::BlendOp::ADD)
245            .src_alpha_blend_factor(vk::BlendFactor::ONE)
246            .dst_alpha_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA)
247            .alpha_blend_op(vk::BlendOp::ADD)
248            .color_write_mask(vk::ColorComponentFlags::RGBA);
249
250        let color_blend = vk::PipelineColorBlendStateCreateInfo::default()
251            .attachments(std::slice::from_ref(&color_blend_attachment));
252
253        let dynamic_states = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR];
254        let dynamic_state =
255            vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states);
256
257        let depth_stencil = vk::PipelineDepthStencilStateCreateInfo::default();
258
259        let pipeline_info = vk::GraphicsPipelineCreateInfo::default()
260            .stages(&shader_stages)
261            .vertex_input_state(&vertex_input_info)
262            .input_assembly_state(&input_assembly)
263            .viewport_state(&viewport_state)
264            .rasterization_state(&rasterization)
265            .multisample_state(&multisample)
266            .depth_stencil_state(&depth_stencil)
267            .color_blend_state(&color_blend)
268            .dynamic_state(&dynamic_state)
269            .layout(pipeline_layout)
270            .render_pass(create_info.render_pass)
271            .subpass(0);
272
273        let pipeline = unsafe {
274            let result = device
275                .create_graphics_pipelines(vk::PipelineCache::null(), &[pipeline_info], None)
276                .map_err(|(_, e)| e);
277
278            // Shader modules are no longer needed regardless of success/failure.
279            device.destroy_shader_module(vert_module, None);
280            device.destroy_shader_module(frag_module, None);
281
282            result.inspect_err(|_| {
283                device.destroy_pipeline_layout(pipeline_layout, None);
284                device.destroy_descriptor_set_layout(descriptor_set_layout, None);
285            })?[0]
286        };
287
288        // From here on, if anything fails we need to clean up pipeline +
289        // layout + descriptor set layout (plus whatever else was created).
290        // Use a helper closure to avoid deep nesting.
291        let cleanup_base = |device: &ash::Device| unsafe {
292            device.destroy_pipeline(pipeline, None);
293            device.destroy_pipeline_layout(pipeline_layout, None);
294            device.destroy_descriptor_set_layout(descriptor_set_layout, None);
295        };
296
297        // --- Descriptor pool ---
298        let pool_size = vk::DescriptorPoolSize {
299            ty: vk::DescriptorType::COMBINED_IMAGE_SAMPLER,
300            descriptor_count: 1,
301        };
302        let pool_info = vk::DescriptorPoolCreateInfo::default()
303            .max_sets(1)
304            .pool_sizes(std::slice::from_ref(&pool_size));
305        let descriptor_pool = unsafe {
306            device.create_descriptor_pool(&pool_info, None).inspect_err(|_| cleanup_base(device))?
307        };
308
309        let cleanup_pool = |device: &ash::Device| unsafe {
310            device.destroy_descriptor_pool(descriptor_pool, None);
311            cleanup_base(device);
312        };
313
314        // --- Font texture upload ---
315        let fonts = imgui.fonts();
316        let font_atlas = fonts.build_rgba32_texture();
317        let (width, height) = (font_atlas.width, font_atlas.height);
318        let upload_size = (width * height * 4) as vk::DeviceSize;
319
320        // Create image
321        let image_info = vk::ImageCreateInfo::default()
322            .image_type(vk::ImageType::TYPE_2D)
323            .format(vk::Format::R8G8B8A8_UNORM)
324            .extent(vk::Extent3D {
325                width,
326                height,
327                depth: 1,
328            })
329            .mip_levels(1)
330            .array_layers(1)
331            .samples(vk::SampleCountFlags::TYPE_1)
332            .tiling(vk::ImageTiling::OPTIMAL)
333            .usage(vk::ImageUsageFlags::TRANSFER_DST | vk::ImageUsageFlags::SAMPLED)
334            .initial_layout(vk::ImageLayout::UNDEFINED);
335        let font_image = unsafe {
336            device.create_image(&image_info, None).inspect_err(|_| cleanup_pool(device))?
337        };
338
339        let mem_req = unsafe { device.get_image_memory_requirements(font_image) };
340        let font_image_memory = unsafe {
341            let alloc_info = vk::MemoryAllocateInfo::default()
342                .allocation_size(mem_req.size)
343                .memory_type_index(
344                    find_memory_type(
345                        &memory_properties,
346                        mem_req.memory_type_bits,
347                        vk::MemoryPropertyFlags::DEVICE_LOCAL,
348                    )
349                    .expect("no suitable memory type for font image"),
350                );
351            device.allocate_memory(&alloc_info, None).inspect_err(|_| {
352                device.destroy_image(font_image, None);
353                cleanup_pool(device);
354            })?
355        };
356        unsafe {
357            device.bind_image_memory(font_image, font_image_memory, 0).inspect_err(|_| {
358                device.free_memory(font_image_memory, None);
359                device.destroy_image(font_image, None);
360                cleanup_pool(device);
361            })?;
362        }
363
364        let cleanup_image = |device: &ash::Device| unsafe {
365            device.free_memory(font_image_memory, None);
366            device.destroy_image(font_image, None);
367            cleanup_pool(device);
368        };
369
370        // Staging buffer
371        let (staging_buffer, staging_memory) = create_buffer(
372            device,
373            &memory_properties,
374            upload_size,
375            vk::BufferUsageFlags::TRANSFER_SRC,
376            vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
377        )
378        .inspect_err(|_| cleanup_image(device))?;
379
380        unsafe {
381            let ptr = device
382                .map_memory(staging_memory, 0, upload_size, vk::MemoryMapFlags::empty())
383                .inspect_err(|_| {
384                    device.destroy_buffer(staging_buffer, None);
385                    device.free_memory(staging_memory, None);
386                    cleanup_image(device);
387                })?;
388            std::ptr::copy_nonoverlapping(
389                font_atlas.data.as_ptr(),
390                ptr as *mut u8,
391                upload_size as usize,
392            );
393            device.unmap_memory(staging_memory);
394        }
395
396        // Upload via one-shot command buffer
397        execute_one_time_commands(
398            device,
399            create_info.command_pool,
400            create_info.queue,
401            |cmd| {
402                // Transition UNDEFINED -> TRANSFER_DST_OPTIMAL
403                let barrier = vk::ImageMemoryBarrier::default()
404                    .image(font_image)
405                    .old_layout(vk::ImageLayout::UNDEFINED)
406                    .new_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL)
407                    .src_access_mask(vk::AccessFlags::empty())
408                    .dst_access_mask(vk::AccessFlags::TRANSFER_WRITE)
409                    .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
410                    .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
411                    .subresource_range(vk::ImageSubresourceRange {
412                        aspect_mask: vk::ImageAspectFlags::COLOR,
413                        base_mip_level: 0,
414                        level_count: 1,
415                        base_array_layer: 0,
416                        layer_count: 1,
417                    });
418                unsafe {
419                    device.cmd_pipeline_barrier(
420                        cmd,
421                        vk::PipelineStageFlags::TOP_OF_PIPE,
422                        vk::PipelineStageFlags::TRANSFER,
423                        vk::DependencyFlags::empty(),
424                        &[],
425                        &[],
426                        &[barrier],
427                    );
428                }
429
430                // Copy buffer to image
431                let region = vk::BufferImageCopy {
432                    buffer_offset: 0,
433                    buffer_row_length: 0,
434                    buffer_image_height: 0,
435                    image_subresource: vk::ImageSubresourceLayers {
436                        aspect_mask: vk::ImageAspectFlags::COLOR,
437                        mip_level: 0,
438                        base_array_layer: 0,
439                        layer_count: 1,
440                    },
441                    image_offset: vk::Offset3D { x: 0, y: 0, z: 0 },
442                    image_extent: vk::Extent3D {
443                        width,
444                        height,
445                        depth: 1,
446                    },
447                };
448                unsafe {
449                    device.cmd_copy_buffer_to_image(
450                        cmd,
451                        staging_buffer,
452                        font_image,
453                        vk::ImageLayout::TRANSFER_DST_OPTIMAL,
454                        &[region],
455                    );
456                }
457
458                // Transition TRANSFER_DST -> SHADER_READ_ONLY
459                let barrier = vk::ImageMemoryBarrier::default()
460                    .image(font_image)
461                    .old_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL)
462                    .new_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
463                    .src_access_mask(vk::AccessFlags::TRANSFER_WRITE)
464                    .dst_access_mask(vk::AccessFlags::SHADER_READ)
465                    .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
466                    .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
467                    .subresource_range(vk::ImageSubresourceRange {
468                        aspect_mask: vk::ImageAspectFlags::COLOR,
469                        base_mip_level: 0,
470                        level_count: 1,
471                        base_array_layer: 0,
472                        layer_count: 1,
473                    });
474                unsafe {
475                    device.cmd_pipeline_barrier(
476                        cmd,
477                        vk::PipelineStageFlags::TRANSFER,
478                        vk::PipelineStageFlags::FRAGMENT_SHADER,
479                        vk::DependencyFlags::empty(),
480                        &[],
481                        &[],
482                        &[barrier],
483                    );
484                }
485            },
486        )
487        .inspect_err(|_| {
488            unsafe {
489                device.destroy_buffer(staging_buffer, None);
490                device.free_memory(staging_memory, None);
491            }
492            cleanup_image(device);
493        })?;
494
495        // Destroy staging buffer
496        unsafe {
497            device.destroy_buffer(staging_buffer, None);
498            device.free_memory(staging_memory, None);
499        }
500
501        // Image view
502        let view_info = vk::ImageViewCreateInfo::default()
503            .image(font_image)
504            .view_type(vk::ImageViewType::TYPE_2D)
505            .format(vk::Format::R8G8B8A8_UNORM)
506            .subresource_range(vk::ImageSubresourceRange {
507                aspect_mask: vk::ImageAspectFlags::COLOR,
508                base_mip_level: 0,
509                level_count: 1,
510                base_array_layer: 0,
511                layer_count: 1,
512            });
513        let font_image_view = unsafe {
514            device.create_image_view(&view_info, None).inspect_err(|_| cleanup_image(device))?
515        };
516
517        let cleanup_view = |device: &ash::Device| unsafe {
518            device.destroy_image_view(font_image_view, None);
519            cleanup_image(device);
520        };
521
522        // Sampler
523        let sampler_info = vk::SamplerCreateInfo::default()
524            .mag_filter(vk::Filter::LINEAR)
525            .min_filter(vk::Filter::LINEAR)
526            .address_mode_u(vk::SamplerAddressMode::CLAMP_TO_EDGE)
527            .address_mode_v(vk::SamplerAddressMode::CLAMP_TO_EDGE)
528            .address_mode_w(vk::SamplerAddressMode::CLAMP_TO_EDGE);
529        let font_sampler = unsafe {
530            device.create_sampler(&sampler_info, None).inspect_err(|_| cleanup_view(device))?
531        };
532
533        let cleanup_sampler = |device: &ash::Device| unsafe {
534            device.destroy_sampler(font_sampler, None);
535            cleanup_view(device);
536        };
537
538        // --- Allocate font descriptor set ---
539        let alloc_info = vk::DescriptorSetAllocateInfo::default()
540            .descriptor_pool(descriptor_pool)
541            .set_layouts(std::slice::from_ref(&descriptor_set_layout));
542        let font_descriptor_set = unsafe {
543            device
544                .allocate_descriptor_sets(&alloc_info)
545                .inspect_err(|_| cleanup_sampler(device))?[0]
546        };
547
548        let image_write = vk::DescriptorImageInfo::default()
549            .sampler(font_sampler)
550            .image_view(font_image_view)
551            .image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL);
552        let write = vk::WriteDescriptorSet::default()
553            .dst_set(font_descriptor_set)
554            .dst_binding(0)
555            .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
556            .image_info(std::slice::from_ref(&image_write));
557        unsafe { device.update_descriptor_sets(&[write], &[]) };
558
559        // Set font texture ID to the descriptor set handle so we can look it up
560        // during rendering.
561        imgui
562            .fonts()
563            .tex_id = TextureId::new(font_descriptor_set.as_raw() as usize);
564
565        // --- Initial vertex/index buffers ---
566        let (vertex_buffer, vertex_buffer_memory) = create_buffer(
567            device,
568            &memory_properties,
569            INITIAL_BUFFER_SIZE,
570            vk::BufferUsageFlags::VERTEX_BUFFER,
571            vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
572        )
573        .inspect_err(|_| cleanup_sampler(device))?;
574
575        let (index_buffer, index_buffer_memory) = create_buffer(
576            device,
577            &memory_properties,
578            INITIAL_BUFFER_SIZE,
579            vk::BufferUsageFlags::INDEX_BUFFER,
580            vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
581        )
582        .inspect_err(|_| unsafe {
583            device.destroy_buffer(vertex_buffer, None);
584            device.free_memory(vertex_buffer_memory, None);
585            cleanup_sampler(device);
586        })?;
587
588        Ok(Renderer {
589            device: device.clone(),
590            memory_properties,
591            pipeline_layout,
592            pipeline,
593            descriptor_set_layout,
594            descriptor_pool,
595            font_descriptor_set,
596            font_image,
597            font_image_memory,
598            font_image_view,
599            font_sampler,
600            vertex_buffer,
601            vertex_buffer_memory,
602            vertex_buffer_size: INITIAL_BUFFER_SIZE,
603            index_buffer,
604            index_buffer_memory,
605            index_buffer_size: INITIAL_BUFFER_SIZE,
606            retired_buffers: Vec::new(),
607        })
608    }
609
610    /// Record draw commands for the given imgui draw data into `command_buffer`.
611    ///
612    /// The command buffer must already be inside a compatible render pass.
613    /// Vertex and index buffers are reallocated if the current ones are too
614    /// small; old buffers are kept alive until the *next* call to `render()`
615    /// so that any previously recorded command buffers still referencing them
616    /// can finish executing.
617    pub fn render(
618        &mut self,
619        draw_data: &DrawData,
620        command_buffer: vk::CommandBuffer,
621    ) -> Result<(), RendererError> {
622        // Destroy buffers that were retired during the *previous* render call.
623        // By now the user must have waited for the previous frame's command
624        // buffer to finish (standard Vulkan frame pacing), so these are safe
625        // to free.
626        self.flush_retired_buffers();
627
628        if draw_data.total_vtx_count <= 0 {
629            return Ok(());
630        }
631
632        let device = &self.device;
633
634        // --- Reallocate buffers if needed ---
635        let vertex_size =
636            (draw_data.total_vtx_count as usize * mem::size_of::<DrawVert>()) as vk::DeviceSize;
637        let index_size =
638            (draw_data.total_idx_count as usize * mem::size_of::<DrawIdx>()) as vk::DeviceSize;
639
640        if vertex_size > self.vertex_buffer_size {
641            // Retire the old buffer instead of destroying immediately -- a
642            // previously submitted command buffer may still reference it.
643            self.retired_buffers
644                .push((self.vertex_buffer, self.vertex_buffer_memory));
645            let (buf, mem) = create_buffer(
646                device,
647                &self.memory_properties,
648                vertex_size,
649                vk::BufferUsageFlags::VERTEX_BUFFER,
650                vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
651            )?;
652            self.vertex_buffer = buf;
653            self.vertex_buffer_memory = mem;
654            self.vertex_buffer_size = vertex_size;
655        }
656
657        if index_size > self.index_buffer_size {
658            self.retired_buffers
659                .push((self.index_buffer, self.index_buffer_memory));
660            let (buf, mem) = create_buffer(
661                device,
662                &self.memory_properties,
663                index_size,
664                vk::BufferUsageFlags::INDEX_BUFFER,
665                vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
666            )?;
667            self.index_buffer = buf;
668            self.index_buffer_memory = mem;
669            self.index_buffer_size = index_size;
670        }
671
672        // --- Upload vertex/index data ---
673        unsafe {
674            let vtx_ptr = device.map_memory(
675                self.vertex_buffer_memory,
676                0,
677                vertex_size,
678                vk::MemoryMapFlags::empty(),
679            )? as *mut DrawVert;
680            let idx_ptr = device.map_memory(
681                self.index_buffer_memory,
682                0,
683                index_size,
684                vk::MemoryMapFlags::empty(),
685            )? as *mut DrawIdx;
686
687            let mut vtx_offset = 0usize;
688            let mut idx_offset = 0usize;
689            for draw_list in draw_data.draw_lists() {
690                let vtx_buf = draw_list.vtx_buffer();
691                let idx_buf = draw_list.idx_buffer();
692                std::ptr::copy_nonoverlapping(
693                    vtx_buf.as_ptr(),
694                    vtx_ptr.add(vtx_offset),
695                    vtx_buf.len(),
696                );
697                std::ptr::copy_nonoverlapping(
698                    idx_buf.as_ptr(),
699                    idx_ptr.add(idx_offset),
700                    idx_buf.len(),
701                );
702                vtx_offset += vtx_buf.len();
703                idx_offset += idx_buf.len();
704            }
705
706            device.unmap_memory(self.vertex_buffer_memory);
707            device.unmap_memory(self.index_buffer_memory);
708        }
709
710        // --- Record commands ---
711        let fb_scale = draw_data.framebuffer_scale;
712        let display_pos = draw_data.display_pos;
713        let display_size = draw_data.display_size;
714        let fb_width = display_size[0] * fb_scale[0];
715        let fb_height = display_size[1] * fb_scale[1];
716        if fb_width <= 0.0 || fb_height <= 0.0 {
717            return Ok(());
718        }
719
720        // Pre-compute values needed both for initial setup and ResetRenderState.
721        let viewport = vk::Viewport {
722            x: 0.0,
723            y: 0.0,
724            width: fb_width,
725            height: fb_height,
726            min_depth: 0.0,
727            max_depth: 1.0,
728        };
729
730        let left = display_pos[0];
731        let right = display_pos[0] + display_size[0];
732        let top = display_pos[1];
733        let bottom = display_pos[1] + display_size[1];
734
735        #[rustfmt::skip]
736        let proj_mtx: [f32; 16] = [
737            2.0 / (right - left),              0.0,                                0.0, 0.0,
738            0.0,                               2.0 / (bottom - top),               0.0, 0.0,
739            0.0,                               0.0,                               -1.0, 0.0,
740            (right + left) / (left - right),   (top + bottom) / (top - bottom),    0.0, 1.0,
741        ];
742        let proj_bytes = bytemuck_cast_slice(&proj_mtx);
743
744        let idx_type = if mem::size_of::<DrawIdx>() == 2 {
745            vk::IndexType::UINT16
746        } else {
747            vk::IndexType::UINT32
748        };
749
750        unsafe {
751            self.setup_render_state(command_buffer, &viewport, proj_bytes, idx_type);
752        }
753
754        let clip_off = display_pos;
755        let clip_scale = fb_scale;
756
757        let mut global_vtx_offset = 0u32;
758        let mut global_idx_offset = 0u32;
759
760        for draw_list in draw_data.draw_lists() {
761            for cmd in draw_list.commands() {
762                match cmd {
763                    DrawCmd::Elements { count, cmd_params } => {
764                        let clip_min_x =
765                            (cmd_params.clip_rect[0] - clip_off[0]) * clip_scale[0];
766                        let clip_min_y =
767                            (cmd_params.clip_rect[1] - clip_off[1]) * clip_scale[1];
768                        let clip_max_x =
769                            (cmd_params.clip_rect[2] - clip_off[0]) * clip_scale[0];
770                        let clip_max_y =
771                            (cmd_params.clip_rect[3] - clip_off[1]) * clip_scale[1];
772
773                        if clip_max_x <= clip_min_x || clip_max_y <= clip_min_y {
774                            continue;
775                        }
776
777                        let scissor = vk::Rect2D {
778                            offset: vk::Offset2D {
779                                x: clip_min_x.max(0.0) as i32,
780                                y: clip_min_y.max(0.0) as i32,
781                            },
782                            extent: vk::Extent2D {
783                                width: (clip_max_x - clip_min_x.max(0.0)) as u32,
784                                height: (clip_max_y - clip_min_y.max(0.0)) as u32,
785                            },
786                        };
787
788                        // The texture_id stores the raw VkDescriptorSet handle.
789                        let descriptor_set = vk::DescriptorSet::from_raw(
790                            cmd_params.texture_id.id() as u64,
791                        );
792
793                        unsafe {
794                            self.device.cmd_set_scissor(command_buffer, 0, &[scissor]);
795                            self.device.cmd_bind_descriptor_sets(
796                                command_buffer,
797                                vk::PipelineBindPoint::GRAPHICS,
798                                self.pipeline_layout,
799                                0,
800                                &[descriptor_set],
801                                &[],
802                            );
803                            self.device.cmd_draw_indexed(
804                                command_buffer,
805                                count as u32,
806                                1,
807                                cmd_params.idx_offset as u32 + global_idx_offset,
808                                (cmd_params.vtx_offset as i32)
809                                    + (global_vtx_offset as i32),
810                                0,
811                            );
812                        }
813                    }
814                    DrawCmd::ResetRenderState => unsafe {
815                        self.setup_render_state(
816                            command_buffer,
817                            &viewport,
818                            proj_bytes,
819                            idx_type,
820                        );
821                    },
822                    DrawCmd::RawCallback { callback, raw_cmd } => unsafe {
823                        callback(draw_list.raw(), raw_cmd);
824                    },
825                }
826            }
827
828            global_vtx_offset += draw_list.vtx_buffer().len() as u32;
829            global_idx_offset += draw_list.idx_buffer().len() as u32;
830        }
831
832        Ok(())
833    }
834
835    /// Bind pipeline, buffers, viewport, scissor, and push constants.
836    ///
837    /// # Safety
838    ///
839    /// `command_buffer` must be in the recording state inside a compatible
840    /// render pass.
841    unsafe fn setup_render_state(
842        &self,
843        command_buffer: vk::CommandBuffer,
844        viewport: &vk::Viewport,
845        proj_bytes: &[u8],
846        idx_type: vk::IndexType,
847    ) {
848        unsafe {
849            self.device.cmd_bind_pipeline(
850                command_buffer,
851                vk::PipelineBindPoint::GRAPHICS,
852                self.pipeline,
853            );
854            self.device
855                .cmd_bind_vertex_buffers(command_buffer, 0, &[self.vertex_buffer], &[0]);
856            self.device.cmd_bind_index_buffer(
857                command_buffer,
858                self.index_buffer,
859                0,
860                idx_type,
861            );
862            self.device
863                .cmd_set_viewport(command_buffer, 0, std::slice::from_ref(viewport));
864            self.device.cmd_push_constants(
865                command_buffer,
866                self.pipeline_layout,
867                vk::ShaderStageFlags::VERTEX,
868                0,
869                proj_bytes,
870            );
871        }
872    }
873
874    /// Destroy buffers that were retired during a previous `render()` call.
875    fn flush_retired_buffers(&mut self) {
876        for (buffer, memory) in self.retired_buffers.drain(..) {
877            unsafe {
878                self.device.destroy_buffer(buffer, None);
879                self.device.free_memory(memory, None);
880            }
881        }
882    }
883}
884
885impl Drop for Renderer {
886    fn drop(&mut self) {
887        unsafe {
888            let d = &self.device;
889            d.device_wait_idle().ok();
890            // Flush any buffers still waiting for retirement.
891            for (buffer, memory) in self.retired_buffers.drain(..) {
892                d.destroy_buffer(buffer, None);
893                d.free_memory(memory, None);
894            }
895            d.destroy_buffer(self.index_buffer, None);
896            d.free_memory(self.index_buffer_memory, None);
897            d.destroy_buffer(self.vertex_buffer, None);
898            d.free_memory(self.vertex_buffer_memory, None);
899            d.destroy_sampler(self.font_sampler, None);
900            d.destroy_image_view(self.font_image_view, None);
901            d.free_memory(self.font_image_memory, None);
902            d.destroy_image(self.font_image, None);
903            d.destroy_descriptor_pool(self.descriptor_pool, None);
904            d.destroy_pipeline(self.pipeline, None);
905            d.destroy_pipeline_layout(self.pipeline_layout, None);
906            d.destroy_descriptor_set_layout(self.descriptor_set_layout, None);
907        }
908    }
909}
910
911// ---------------------------------------------------------------------------
912// Helpers
913// ---------------------------------------------------------------------------
914
915fn find_memory_type(
916    props: &vk::PhysicalDeviceMemoryProperties,
917    type_filter: u32,
918    flags: vk::MemoryPropertyFlags,
919) -> Option<u32> {
920    for i in 0..props.memory_type_count {
921        if (type_filter & (1 << i)) != 0
922            && props.memory_types[i as usize]
923                .property_flags
924                .contains(flags)
925        {
926            return Some(i);
927        }
928    }
929    None
930}
931
932fn create_buffer(
933    device: &ash::Device,
934    memory_properties: &vk::PhysicalDeviceMemoryProperties,
935    size: vk::DeviceSize,
936    usage: vk::BufferUsageFlags,
937    mem_flags: vk::MemoryPropertyFlags,
938) -> Result<(vk::Buffer, vk::DeviceMemory), RendererError> {
939    let buffer_info = vk::BufferCreateInfo::default()
940        .size(size)
941        .usage(usage)
942        .sharing_mode(vk::SharingMode::EXCLUSIVE);
943    let buffer = unsafe { device.create_buffer(&buffer_info, None)? };
944    let mem_req = unsafe { device.get_buffer_memory_requirements(buffer) };
945    let alloc_info = vk::MemoryAllocateInfo::default()
946        .allocation_size(mem_req.size)
947        .memory_type_index(
948            find_memory_type(memory_properties, mem_req.memory_type_bits, mem_flags)
949                .expect("no suitable memory type for buffer"),
950        );
951    let memory = unsafe {
952        device.allocate_memory(&alloc_info, None).inspect_err(|_| {
953            device.destroy_buffer(buffer, None);
954        })?
955    };
956    unsafe {
957        device
958            .bind_buffer_memory(buffer, memory, 0)
959            .inspect_err(|_| {
960                device.free_memory(memory, None);
961                device.destroy_buffer(buffer, None);
962            })?;
963    }
964    Ok((buffer, memory))
965}
966
967fn execute_one_time_commands(
968    device: &ash::Device,
969    command_pool: vk::CommandPool,
970    queue: vk::Queue,
971    f: impl FnOnce(vk::CommandBuffer),
972) -> Result<(), RendererError> {
973    let alloc_info = vk::CommandBufferAllocateInfo::default()
974        .command_pool(command_pool)
975        .level(vk::CommandBufferLevel::PRIMARY)
976        .command_buffer_count(1);
977    let cmd = unsafe { device.allocate_command_buffers(&alloc_info)?[0] };
978
979    let begin_info =
980        vk::CommandBufferBeginInfo::default().flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT);
981    unsafe { device.begin_command_buffer(cmd, &begin_info)? };
982
983    f(cmd);
984
985    unsafe { device.end_command_buffer(cmd)? };
986
987    let submit_info = vk::SubmitInfo::default().command_buffers(std::slice::from_ref(&cmd));
988    let fence_info = vk::FenceCreateInfo::default();
989    let fence = unsafe { device.create_fence(&fence_info, None)? };
990
991    unsafe {
992        device.queue_submit(queue, &[submit_info], fence)?;
993        device.wait_for_fences(&[fence], true, u64::MAX)?;
994        device.destroy_fence(fence, None);
995        device.free_command_buffers(command_pool, &[cmd]);
996    }
997
998    Ok(())
999}
1000
1001/// Reinterpret a `&[f32]` slice as `&[u8]` for push constants.
1002fn bytemuck_cast_slice(data: &[f32]) -> &[u8] {
1003    unsafe {
1004        std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * mem::size_of::<f32>())
1005    }
1006}