cen 0.0.9

Vulkan window backend
Documentation
use ash::vk;
use ash::vk::{AttachmentLoadOp, AttachmentStoreOp, ClearColorValue, ClearValue, DescriptorSet, DescriptorSetLayout, ImageLayout, Offset2D, Rect2D, RenderingAttachmentInfo};
use egui::{Context, FullOutput, TextureId, ViewportId};
use egui_ash_renderer::{DynamicRendering, Options};
use egui_ash_renderer::vulkan::{create_vulkan_descriptor_set, create_vulkan_descriptor_set_layout};
use egui_winit::State;
use crate::app::Window;
use crate::graphics::Renderer;
use crate::graphics::renderer::{RenderComponent, RenderContext};
use crate::vulkan::{Device, DescriptorPool, Image};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use log::{error, trace};

pub trait GuiComponent {
    fn gui(&mut self, gui: &GuiSystem, context: &Context);
}

pub struct GuiSystem {
    pub egui_ctx: Context,
    pub egui_winit: State,
    pub egui_renderer: egui_ash_renderer::Renderer,
    device: Device,
    renderer_descriptor_pool: DescriptorPool,
    egui_output: Option<FullOutput>,
    texture_layout: DescriptorSetLayout,
    user_textures: HashMap<TextureId, DescriptorSet>,
}

impl Drop for GuiSystem {
    fn drop(&mut self) {
        for (id, _) in self.user_textures.iter() {
            self.egui_renderer.remove_user_texture(*id);
            trace!("Destroyed user texture {:?}", id);
        }
        unsafe {
            self.device.handle().destroy_descriptor_set_layout(self.texture_layout, None);
            trace!("Destroyed gui image descriptor set layout {:?}", self.texture_layout);
        }
    }
}

impl GuiSystem {

    pub fn new(window: &Window, renderer: &mut Renderer) -> Self {

        let egui_ctx = Context::default();

        // Enable image loading
        // You will still need to add a loader to your imports. e.g.
        // image = { version = "0.25", features = ["png"] }
        egui_extras::install_image_loaders(&egui_ctx);

        let egui_winit = egui_winit::State::new(
            egui_ctx.clone(),
            ViewportId::ROOT,
            &window.display_handle(),
            None,
            None,
            None
        );

        // Renderer values

        let device = renderer.device.clone();
        let renderer_descriptor_pool = DescriptorPool::new(&renderer.device, 10000);

        #[cfg(any(target_os = "linux", target_os = "windows"))]
        let preferred_format = vk::Format::R8G8B8A8_SRGB;

        #[cfg(target_os = "macos")]
        let preferred_format = vk::Format::B8G8R8A8_SRGB;

        let egui_renderer = egui_ash_renderer::Renderer::with_gpu_allocator(
            renderer.allocator.inner.lock().unwrap().allocator.clone(),
            renderer.device.handle().clone(),
            DynamicRendering {
                color_attachment_format: preferred_format,
                depth_attachment_format: None,
            },
            Options {
                in_flight_frames: renderer.swapchain.get_image_count() as usize,
                enable_depth_test: false,
                enable_depth_write: false,
                srgb_framebuffer: true
            }
        ).unwrap();

        let texture_layout = create_vulkan_descriptor_set_layout(renderer.device.handle()).unwrap();

        Self {
            egui_ctx,
            egui_winit,
            egui_renderer,
            egui_output: None,
            device,
            renderer_descriptor_pool,
            texture_layout,
            user_textures: HashMap::new(),
        }
    }

    pub fn create_texture(&mut self, image: &Image) -> TextureId {
        let device = self.device.handle();
        let descriptor_set = create_vulkan_descriptor_set(
            device,
            self.texture_layout,
            self.renderer_descriptor_pool.handle(),
            image.image_view(),
            image.sampler(),
        ).unwrap();

        let texture_id = self.egui_renderer.add_user_texture(descriptor_set);

        self.user_textures.insert(texture_id, descriptor_set);

        texture_id
    }

    pub fn remove_texture(&mut self, texture_id: TextureId) {
        unsafe {
            let set = self.user_textures.remove(&texture_id).unwrap();
            self.device.handle().free_descriptor_sets(self.renderer_descriptor_pool.descriptor_pool, &[set]).unwrap();
        }
        self.egui_renderer.remove_user_texture(texture_id);
    }

    pub fn on_window_event(&mut self, window: &winit::window::Window, event: &winit::event::WindowEvent) {
        let _ = self.egui_winit.on_window_event(window, event);
    }

    pub fn update(&mut self, window: &winit::window::Window, components: &mut [Arc<Mutex<dyn GuiComponent>>]) {

        // Renew gui
        let raw_input = self.egui_winit.take_egui_input(window);
        self.egui_output = Some(self.egui_ctx.run(raw_input, |ctx| {
            for component in &mut *components {
                component.lock().unwrap().gui(self, ctx);
            }
        }));
    }
}

impl RenderComponent for GuiSystem {

    fn render(&mut self, ctx: &mut RenderContext) {

        if let Some(output) = self.egui_output.take() {

            // Free textures
            self.egui_renderer
                .free_textures(output.textures_delta.free.as_slice()).unwrap();

            // Set textures
            // https://docs.rs/egui-ash-renderer/0.7.0/egui_ash_renderer/#managed-textures
            self.egui_renderer.set_textures(
                *ctx.queue, ctx.command_pool.command_pool, output.textures_delta.set.as_slice()
            ).unwrap();

            let clipped_primitives = self.egui_ctx.tessellate(
                output.shapes,
                output.pixels_per_point
            );

            let color_attachments = vec![
                RenderingAttachmentInfo::default()
                    .image_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL)
                    .load_op(AttachmentLoadOp::LOAD)
                    .store_op(AttachmentStoreOp::STORE)
                    .clear_value(ClearValue { color: ClearColorValue { float32: [1f32, 0f32, 1f32, 1f32] } })
                    .image_view(ctx.swapchain_image.image_view())
            ];
            let rendering_info = vk::RenderingInfoKHR::default()
                .render_area(Rect2D { offset: Offset2D { x: 0, y: 0 }, extent: ctx.swapchain_image.extent() })
                .layer_count(1)
                .view_mask(0)
                .color_attachments(&color_attachments);
            ctx.command_buffer.begin_rendering(&rendering_info);

            // Egui draw call
            match self.egui_renderer.cmd_draw(
                ctx.command_buffer.handle(),
                ctx.swapchain_image.extent(),
                output.pixels_per_point,
                clipped_primitives.as_slice()
            ) {
                Ok(_) => (),
                Err(e) => {
                    error!("{}", e);
                }
            }

            ctx.command_buffer.end_rendering();
        }
    }
}