imgui-ash 0.1.0

ash/VMA renderer for imgui-rs
Documentation
use ash::vk::{self, Handle};
use imgui::TextureId;
use vk_mem::Alloc;

use crate::util::RaiiWrapper;

use super::{FontsCreateError, Texture};

impl<A> super::Renderer<A>
where
    A: vk_mem::Alloc,
{
    /// # Safety
    ///
    /// - the device and allocator from create info must still be valid
    /// - the queue submitted must still be valid
    pub(crate) unsafe fn create_fonts_texture(
        &mut self,
        context: &mut imgui::Context,
    ) -> Result<(), FontsCreateError> {
        if self.fonts_texture.is_some() {
            unsafe { self.destroy_fonts_texture(Some(context)) };
        }
        let device = &self.device;
        // reset commands
        {
            unsafe {
                device.reset_command_pool(
                    self.device_objects.tex_command_pool,
                    vk::CommandPoolResetFlags::empty(),
                )
            }
            .map_err(FontsCreateError::CommandPoolResetError)?;

            let begin_info = vk::CommandBufferBeginInfo::default()
                .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT);
            unsafe {
                device.begin_command_buffer(self.device_objects.tex_command_buffer, &begin_info)
            }
            .map_err(FontsCreateError::CommandBeginError)?;
        }

        let font_atlas = context.fonts().build_rgba32_texture();
        let alloc = self.allocator.allocator();
        let image_and_alloc = {
            let create_info = vk::ImageCreateInfo::default()
                .image_type(vk::ImageType::TYPE_2D)
                .format(vk::Format::R8G8B8A8_UNORM)
                .extent(
                    vk::Extent3D::default()
                        .width(font_atlas.width)
                        .height(font_atlas.height)
                        .depth(1),
                )
                .mip_levels(1)
                .array_layers(1)
                .samples(vk::SampleCountFlags::TYPE_1)
                .tiling(vk::ImageTiling::OPTIMAL)
                .usage(vk::ImageUsageFlags::SAMPLED | vk::ImageUsageFlags::TRANSFER_DST)
                .sharing_mode(vk::SharingMode::EXCLUSIVE)
                .initial_layout(vk::ImageLayout::UNDEFINED);

            let alloc_info = vk_mem::AllocationCreateInfo {
                usage: vk_mem::MemoryUsage::AutoPreferDevice,
                ..Default::default()
            };

            RaiiWrapper::new(
                unsafe { alloc.create_image(&create_info, &alloc_info) }
                    .map_err(FontsCreateError::ImageAllocError)?,
                |(img, mut allocation)| unsafe {
                    alloc.destroy_image(img, &mut allocation);
                },
            )
        };

        let view = {
            let create_info = vk::ImageViewCreateInfo::default()
                .image(image_and_alloc.0)
                .view_type(vk::ImageViewType::TYPE_2D)
                .format(vk::Format::R8G8B8A8_UNORM)
                .subresource_range(
                    vk::ImageSubresourceRange::default()
                        .aspect_mask(vk::ImageAspectFlags::COLOR)
                        .level_count(1)
                        .layer_count(1),
                );

            RaiiWrapper::new(
                unsafe { device.create_image_view(&create_info, None) }
                    .map_err(FontsCreateError::ImageViewCreateError)?,
                |v| unsafe { device.destroy_image_view(v, None) },
            )
        };

        let sampler = self.device_objects.tex_sampler;
        let texture =
            self.add_texture(sampler, *view, vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)?;
        let desriptor_set = RaiiWrapper::new(texture, |s| unsafe {
            self.remove_texture(s);
        });

        let mut upload_buffer_and_alloc = {
            let create_info = vk::BufferCreateInfo::default()
                .size(font_atlas.data.len() as _)
                .usage(vk::BufferUsageFlags::TRANSFER_SRC)
                .sharing_mode(vk::SharingMode::EXCLUSIVE);

            let alloc_info = vk_mem::AllocationCreateInfo {
                usage: vk_mem::MemoryUsage::AutoPreferDevice,
                flags: vk_mem::AllocationCreateFlags::HOST_ACCESS_SEQUENTIAL_WRITE,
                ..Default::default()
            };

            RaiiWrapper::new(
                unsafe { alloc.create_buffer(&create_info, &alloc_info) }
                    .map_err(FontsCreateError::TransferbufferCreateError)?,
                |(buf, mut allocation)| unsafe { alloc.destroy_buffer(buf, &mut allocation) },
            )
        };

        // copy atlas into GPU memory
        {
            let memory = unsafe { alloc.map_memory(&mut upload_buffer_and_alloc.1) }
                .map_err(FontsCreateError::MmapError)?;
            unsafe {
                core::ptr::copy_nonoverlapping(
                    font_atlas.data.as_ptr(),
                    memory,
                    font_atlas.data.len(),
                );
            }

            unsafe {
                alloc.unmap_memory(&mut upload_buffer_and_alloc.1);
                alloc.flush_allocation(&upload_buffer_and_alloc.1, 0, font_atlas.data.len() as _)
            }
            .map_err(FontsCreateError::FlushError)?;
        }

        // copy from transfer buffer -> image
        {
            let copy_barrier = [vk::ImageMemoryBarrier::default()
                .dst_access_mask(vk::AccessFlags::TRANSFER_WRITE)
                .old_layout(vk::ImageLayout::UNDEFINED)
                .new_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL)
                .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
                .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
                .image(image_and_alloc.0)
                .subresource_range(
                    vk::ImageSubresourceRange::default()
                        .aspect_mask(vk::ImageAspectFlags::COLOR)
                        .level_count(1)
                        .layer_count(1),
                )];
            unsafe {
                device.cmd_pipeline_barrier(
                    self.device_objects.tex_command_buffer,
                    vk::PipelineStageFlags::HOST,
                    vk::PipelineStageFlags::TRANSFER,
                    vk::DependencyFlags::empty(),
                    &[],
                    &[],
                    &copy_barrier,
                );
            }

            let region = vk::BufferImageCopy::default()
                .image_subresource(
                    vk::ImageSubresourceLayers::default()
                        .aspect_mask(vk::ImageAspectFlags::COLOR)
                        .layer_count(1),
                )
                .image_extent(
                    vk::Extent3D::default()
                        .width(font_atlas.width)
                        .height(font_atlas.height)
                        .depth(1),
                );

            unsafe {
                self.device.cmd_copy_buffer_to_image(
                    self.device_objects.tex_command_buffer,
                    upload_buffer_and_alloc.0,
                    image_and_alloc.0,
                    vk::ImageLayout::TRANSFER_DST_OPTIMAL,
                    &[region],
                );
            }

            let use_barrier = [vk::ImageMemoryBarrier::default()
                .src_access_mask(vk::AccessFlags::TRANSFER_WRITE)
                .dst_access_mask(vk::AccessFlags::SHADER_READ)
                .old_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL)
                .new_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
                .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
                .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
                .image(image_and_alloc.0)
                .subresource_range(
                    vk::ImageSubresourceRange::default()
                        .aspect_mask(vk::ImageAspectFlags::COLOR)
                        .level_count(1)
                        .layer_count(1),
                )];

            unsafe {
                self.device.cmd_pipeline_barrier(
                    self.device_objects.tex_command_buffer,
                    vk::PipelineStageFlags::TRANSFER,
                    vk::PipelineStageFlags::FRAGMENT_SHADER,
                    vk::DependencyFlags::empty(),
                    &[],
                    &[],
                    &use_barrier,
                );
            }
        }

        unsafe { device.end_command_buffer(self.device_objects.tex_command_buffer) }
            .map_err(FontsCreateError::CommandEndError)?;
        let tex_buffers = [self.device_objects.tex_command_buffer];
        let submit_info = vk::SubmitInfo::default().command_buffers(&tex_buffers);
        unsafe {
            device.queue_submit(
                self.create_info.queue,
                &[submit_info],
                self.device_objects.wait_fence,
            )
        }
        .map_err(FontsCreateError::SubmitError)?;

        unsafe {
            device
                .wait_for_fences(
                    &[self.device_objects.wait_fence],
                    true,
                    core::time::Duration::from_secs(10).as_nanos() as u64,
                )
                .unwrap()
        }

        context.fonts().tex_id = TextureId::new(desriptor_set.as_raw() as _);

        let (image, alloc) = image_and_alloc.finalise();
        let texture = Texture {
            image,
            memory: alloc,
            descriptor_set: desriptor_set.finalise(),
            image_view: view.finalise(),
        };
        self.fonts_texture = Some(texture);
        Ok(())
    }

    /// # Safety
    ///
    /// - the device and allocator from create info must still be valid
    /// - the queue submitted must still be valid
    pub(crate) unsafe fn destroy_fonts_texture(&mut self, context: Option<&mut imgui::Context>) {
        if let Some(mut texture) = self.fonts_texture.take() {
            unsafe { self.remove_texture(texture.descriptor_set) };
            if let Some(context) = context {
                context.fonts().tex_id = TextureId::new(0);
            }
            unsafe {
                self.device.destroy_image_view(texture.image_view, None);
                self.allocator
                    .allocator()
                    .destroy_image(texture.image, &mut texture.memory);
            }
        }
    }
}