From 9622f5646998575312123ad6ca1c97ebe89dbd82 Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Sun, 22 Oct 2023 09:57:13 -0700 Subject: [PATCH 01/16] Remove superfluous GLSL extensions --- crates/yakui-vulkan/shaders/main.vert | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/yakui-vulkan/shaders/main.vert b/crates/yakui-vulkan/shaders/main.vert index 1eac05f0..d6bb759c 100644 --- a/crates/yakui-vulkan/shaders/main.vert +++ b/crates/yakui-vulkan/shaders/main.vert @@ -1,6 +1,4 @@ -#version 400 -#extension GL_ARB_separate_shader_objects : enable -#extension GL_ARB_shading_language_420pack : enable +#version 450 layout (location = 0) in vec2 in_pos; layout (location = 1) in vec2 in_uv; @@ -17,4 +15,4 @@ void main() { gl_Position = vec4(in_pos * 2.0 - 1.0, 0.0, 1.0); out_color = in_color; out_uv = in_uv; -} \ No newline at end of file +} From cb41fbb8aafa83b0b5dc55cac9e9472bf18deacf Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Sun, 22 Oct 2023 10:02:58 -0700 Subject: [PATCH 02/16] Move example code into an example target --- crates/yakui-vulkan/examples/demo.rs | 712 +++++++++++++++++++++++++++ crates/yakui-vulkan/src/lib.rs | 241 +-------- crates/yakui-vulkan/src/util.rs | 492 ------------------ 3 files changed, 713 insertions(+), 732 deletions(-) create mode 100644 crates/yakui-vulkan/examples/demo.rs diff --git a/crates/yakui-vulkan/examples/demo.rs b/crates/yakui-vulkan/examples/demo.rs new file mode 100644 index 00000000..2df50349 --- /dev/null +++ b/crates/yakui-vulkan/examples/demo.rs @@ -0,0 +1,712 @@ +use ash::vk; +use yakui::geometry::{UVec2, Vec2}; +use yakui_vulkan::*; + +use winit::{ + event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, + event_loop::ControlFlow, + platform::run_return::EventLoopExtRunReturn, +}; +use yakui::image; + +const MONKEY_PNG: &[u8] = include_bytes!("../../bootstrap/assets/monkey.png"); +const DOG_JPG: &[u8] = include_bytes!("../assets/dog.jpg"); + +#[derive(Debug, Clone)] +struct GuiState { + monkey: yakui::ManagedTextureId, + dog: yakui::TextureId, + which_image: WhichImage, +} + +#[derive(Debug, Clone)] +enum WhichImage { + Monkey, + Dog, +} + +/// Simple smoke test to make sure render screen properly pixel Vulkan. +fn main() { + use winit::dpi::PhysicalSize; + + let (width, height) = (500, 500); + let (mut event_loop, window) = init_winit(width, height); + let mut vulkan_test = VulkanTest::new(width, height, &window); + let render_surface = RenderSurface { + resolution: vk::Extent2D { width, height }, + format: vulkan_test.swapchain_info.surface_format.format, + image_views: vulkan_test.present_image_views.clone(), + load_op: vk::AttachmentLoadOp::CLEAR, + }; + let mut yak = yakui::Yakui::new(); + yak.set_surface_size([width as f32, height as f32].into()); + yak.set_unscaled_viewport(yakui_core::geometry::Rect::from_pos_size( + Default::default(), + [width as f32, height as f32].into(), + )); + + let (mut yakui_vulkan, mut gui_state) = { + let vulkan_context = VulkanContext::new( + &vulkan_test.device, + vulkan_test.present_queue, + vulkan_test.draw_command_buffer, + vulkan_test.command_pool, + vulkan_test.device_memory_properties, + ); + let mut yakui_vulkan = YakuiVulkan::new(&vulkan_context, render_surface); + let gui_state = GuiState { + monkey: yak.add_texture(create_yakui_texture( + MONKEY_PNG, + yakui::paint::TextureFilter::Linear, + )), + dog: yakui_vulkan.create_user_texture( + &vulkan_context, + create_vulkan_texture_info(DOG_JPG, vk::Filter::LINEAR), + ), + which_image: WhichImage::Monkey, + }; + (yakui_vulkan, gui_state) + }; + + let mut winit_initializing = true; + + event_loop.run_return(|event, _, control_flow| { + *control_flow = ControlFlow::Poll; + match event { + Event::WindowEvent { + event: + WindowEvent::CloseRequested + | WindowEvent::KeyboardInput { + input: + KeyboardInput { + state: ElementState::Pressed, + virtual_keycode: Some(VirtualKeyCode::Escape), + .. + }, + .. + }, + .. + } => *control_flow = ControlFlow::Exit, + + Event::NewEvents(cause) => { + if cause == winit::event::StartCause::Init { + winit_initializing = true; + } else { + winit_initializing = false; + } + } + + Event::MainEventsCleared => { + let framebuffer_index = vulkan_test.render_begin(); + let vulkan_context = VulkanContext::new( + &vulkan_test.device, + vulkan_test.present_queue, + vulkan_test.draw_command_buffer, + vulkan_test.command_pool, + vulkan_test.device_memory_properties, + ); + + yak.start(); + gui(&gui_state); + yak.finish(); + + yakui_vulkan.paint(&mut yak, &vulkan_context, framebuffer_index); + vulkan_test.render_end(framebuffer_index); + } + Event::WindowEvent { + event: WindowEvent::Resized(size), + .. + } => { + if winit_initializing { + println!("Ignoring resize during init!"); + } else { + let PhysicalSize { width, height } = size; + vulkan_test.resized(width, height); + let render_surface = RenderSurface { + resolution: vk::Extent2D { width, height }, + format: vulkan_test.swapchain_info.surface_format.format, + image_views: vulkan_test.present_image_views.clone(), + load_op: vk::AttachmentLoadOp::CLEAR, + }; + yakui_vulkan.update_surface(render_surface, &vulkan_test.device); + yak.set_surface_size([width as f32, height as f32].into()); + yak.set_unscaled_viewport(yakui_core::geometry::Rect::from_pos_size( + Default::default(), + [width as f32, height as f32].into(), + )); + } + } + Event::WindowEvent { + event: WindowEvent::ScaleFactorChanged { scale_factor, .. }, + .. + } => yak.set_scale_factor(scale_factor as _), + Event::WindowEvent { + event: + WindowEvent::KeyboardInput { + input: + KeyboardInput { + state: ElementState::Released, + virtual_keycode, + .. + }, + .. + }, + .. + } => match virtual_keycode { + Some(VirtualKeyCode::A) => { + gui_state.which_image = match &gui_state.which_image { + WhichImage::Monkey => WhichImage::Dog, + WhichImage::Dog => WhichImage::Monkey, + } + } + _ => {} + }, + _ => (), + } + }); + + unsafe { + yakui_vulkan.cleanup(&vulkan_test.device); + } +} + +fn create_vulkan_texture_info( + compressed_image_bytes: &[u8], + filter: vk::Filter, +) -> VulkanTextureCreateInfo> { + let image = image::load_from_memory(compressed_image_bytes) + .unwrap() + .into_rgba8(); + let resolution = vk::Extent2D { + width: image.width(), + height: image.height(), + }; + + VulkanTextureCreateInfo::new( + image.into_raw(), + vk::Format::R8G8B8A8_UNORM, + resolution, + filter, + filter, + ) +} + +fn create_yakui_texture( + compressed_image_bytes: &[u8], + filter: yakui::paint::TextureFilter, +) -> yakui::paint::Texture { + let image = image::load_from_memory(compressed_image_bytes) + .unwrap() + .into_rgba8(); + let size = UVec2::new(image.width(), image.height()); + + let mut texture = yakui::paint::Texture::new( + yakui::paint::TextureFormat::Rgba8Srgb, + size, + image.into_raw(), + ); + texture.mag_filter = filter; + texture +} + +fn gui(gui_state: &GuiState) { + use yakui::{column, label, row, text, widgets::Text, Color}; + let (animal, texture): (&'static str, yakui::TextureId) = match gui_state.which_image { + WhichImage::Monkey => ("monkye", gui_state.monkey.into()), + WhichImage::Dog => ("dog haha good boy", gui_state.dog.into()), + }; + column(|| { + row(|| { + label("Hello, world!"); + + let mut text = Text::new(48.0, "colored text!"); + text.style.color = Color::RED; + text.show(); + }); + + text(96.0, format!("look it is a {animal}")); + + image(texture, Vec2::new(400.0, 400.0)); + }); +} + +struct VulkanTest { + pub _entry: ash::Entry, + pub device: ash::Device, + pub instance: ash::Instance, + pub surface_loader: ash::extensions::khr::Surface, + pub device_memory_properties: vk::PhysicalDeviceMemoryProperties, + + pub present_queue: vk::Queue, + + pub surface: vk::SurfaceKHR, + pub swapchain_info: SwapchainInfo, + + pub swapchain: vk::SwapchainKHR, + pub present_image_views: Vec, + + pub command_pool: vk::CommandPool, + pub draw_command_buffer: vk::CommandBuffer, + + pub present_complete_semaphore: vk::Semaphore, + pub rendering_complete_semaphore: vk::Semaphore, + + pub draw_commands_reuse_fence: vk::Fence, + pub setup_commands_reuse_fence: vk::Fence, +} + +impl VulkanTest { + /// Bring up all the Vulkan pomp and ceremony required to render things. + /// Vulkan Broadly lifted from: https://github.com/ash-rs/ash/blob/0.37.2/examples/src/lib.rs + pub fn new(window_width: u32, window_height: u32, window: &winit::window::Window) -> Self { + use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; + use std::ffi::CStr; + + let entry = unsafe { ash::Entry::load().expect("failed to load Vulkan") }; + let app_name = unsafe { CStr::from_bytes_with_nul_unchecked(b"Yakui Vulkan Test\0") }; + + let appinfo = vk::ApplicationInfo::builder() + .application_name(app_name) + .application_version(0) + .engine_name(app_name) + .engine_version(0) + .api_version(vk::make_api_version(0, 1, 3, 0)); + + let extension_names = + ash_window::enumerate_required_extensions(window.raw_display_handle()) + .unwrap() + .to_vec(); + + let create_info = vk::InstanceCreateInfo::builder() + .application_info(&appinfo) + .enabled_extension_names(&extension_names); + + let instance = unsafe { + entry + .create_instance(&create_info, None) + .expect("Instance creation error") + }; + + let surface = unsafe { + ash_window::create_surface( + &entry, + &instance, + window.raw_display_handle(), + window.raw_window_handle(), + None, + ) + .unwrap() + }; + + let pdevices = unsafe { + instance + .enumerate_physical_devices() + .expect("Physical device error") + }; + let surface_loader = ash::extensions::khr::Surface::new(&entry, &instance); + let (physical_device, queue_family_index) = unsafe { + pdevices + .iter() + .find_map(|pdevice| { + instance + .get_physical_device_queue_family_properties(*pdevice) + .iter() + .enumerate() + .find_map(|(index, info)| { + let supports_graphic_and_surface = + info.queue_flags.contains(vk::QueueFlags::GRAPHICS) + && surface_loader + .get_physical_device_surface_support( + *pdevice, + index as u32, + surface, + ) + .unwrap(); + if supports_graphic_and_surface { + Some((*pdevice, index)) + } else { + None + } + }) + }) + .expect("Couldn't find suitable device.") + }; + let queue_family_index = queue_family_index as u32; + let device_extension_names_raw = [ash::extensions::khr::Swapchain::name().as_ptr()]; + let priorities = [1.0]; + + let queue_info = vk::DeviceQueueCreateInfo::builder() + .queue_family_index(queue_family_index) + .queue_priorities(&priorities); + + let mut descriptor_indexing_features = + vk::PhysicalDeviceDescriptorIndexingFeatures::builder() + .descriptor_binding_partially_bound(true); + + let device_create_info = vk::DeviceCreateInfo::builder() + .queue_create_infos(std::slice::from_ref(&queue_info)) + .enabled_extension_names(&device_extension_names_raw) + .push_next(&mut descriptor_indexing_features); + + let device = unsafe { + instance + .create_device(physical_device, &device_create_info, None) + .unwrap() + }; + + let present_queue = unsafe { device.get_device_queue(queue_family_index, 0) }; + let surface_format = unsafe { + surface_loader + .get_physical_device_surface_formats(physical_device, surface) + .unwrap()[0] + }; + + let surface_capabilities = unsafe { + surface_loader + .get_physical_device_surface_capabilities(physical_device, surface) + .unwrap() + }; + let mut desired_image_count = surface_capabilities.min_image_count + 1; + if surface_capabilities.max_image_count > 0 + && desired_image_count > surface_capabilities.max_image_count + { + desired_image_count = surface_capabilities.max_image_count; + } + let surface_resolution = match surface_capabilities.current_extent.width { + std::u32::MAX => vk::Extent2D { + width: window_width, + height: window_height, + }, + _ => surface_capabilities.current_extent, + }; + + let present_modes = unsafe { + surface_loader + .get_physical_device_surface_present_modes(physical_device, surface) + .unwrap() + }; + let present_mode = present_modes + .iter() + .cloned() + .find(|&mode| mode == vk::PresentModeKHR::MAILBOX) + .unwrap_or(vk::PresentModeKHR::FIFO); + let swapchain_loader = ash::extensions::khr::Swapchain::new(&instance, &device); + + let swapchain_info = SwapchainInfo::new( + swapchain_loader, + surface_format, + surface_resolution, + present_mode, + surface, + desired_image_count, + ); + + let (swapchain, present_image_views) = create_swapchain(&device, None, &swapchain_info); + + let pool_create_info = vk::CommandPoolCreateInfo::builder() + .flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER) + .queue_family_index(queue_family_index); + + let pool = unsafe { device.create_command_pool(&pool_create_info, None).unwrap() }; + + let command_buffer_allocate_info = vk::CommandBufferAllocateInfo::builder() + .command_buffer_count(1) + .command_pool(pool) + .level(vk::CommandBufferLevel::PRIMARY); + + let command_buffers = unsafe { + device + .allocate_command_buffers(&command_buffer_allocate_info) + .unwrap() + }; + let draw_command_buffer = command_buffers[0]; + + let fence_create_info = + vk::FenceCreateInfo::builder().flags(vk::FenceCreateFlags::SIGNALED); + + let draw_commands_reuse_fence = unsafe { + device + .create_fence(&fence_create_info, None) + .expect("Create fence failed.") + }; + let setup_commands_reuse_fence = unsafe { + device + .create_fence(&fence_create_info, None) + .expect("Create fence failed.") + }; + + let semaphore_create_info = vk::SemaphoreCreateInfo::default(); + + let present_complete_semaphore = unsafe { + device + .create_semaphore(&semaphore_create_info, None) + .unwrap() + }; + let rendering_complete_semaphore = unsafe { + device + .create_semaphore(&semaphore_create_info, None) + .unwrap() + }; + + let device_memory_properties = + unsafe { instance.get_physical_device_memory_properties(physical_device) }; + + Self { + device, + present_queue, + _entry: entry, + instance, + surface_loader, + swapchain_info, + device_memory_properties, + surface, + swapchain, + present_image_views, + command_pool: pool, + draw_command_buffer, + present_complete_semaphore, + rendering_complete_semaphore, + draw_commands_reuse_fence, + setup_commands_reuse_fence, + } + } + + pub fn resized(&mut self, window_width: u32, window_height: u32) { + unsafe { + self.device.device_wait_idle().unwrap(); + self.swapchain_info.surface_resolution = vk::Extent2D { + width: window_width, + height: window_height, + }; + let (new_swapchain, new_present_image_views) = + create_swapchain(&self.device, Some(self.swapchain), &self.swapchain_info); + + self.destroy_swapchain(self.swapchain); + self.present_image_views = new_present_image_views; + self.swapchain = new_swapchain; + } + } + + unsafe fn destroy_swapchain(&self, swapchain: vk::SwapchainKHR) { + let device = &self.device; + for image_view in &self.present_image_views { + device.destroy_image_view(*image_view, None); + } + self.swapchain_info + .swapchain_loader + .destroy_swapchain(swapchain, None); + } + + pub fn render_begin(&self) -> u32 { + let (present_index, _) = unsafe { + self.swapchain_info + .swapchain_loader + .acquire_next_image( + self.swapchain, + std::u64::MAX, + self.present_complete_semaphore, + vk::Fence::null(), + ) + .unwrap() + }; + + let device = &self.device; + unsafe { + device + .wait_for_fences( + std::slice::from_ref(&self.draw_commands_reuse_fence), + true, + std::u64::MAX, + ) + .unwrap(); + device + .reset_fences(std::slice::from_ref(&self.draw_commands_reuse_fence)) + .unwrap(); + device + .reset_command_buffer( + self.draw_command_buffer, + vk::CommandBufferResetFlags::RELEASE_RESOURCES, + ) + .unwrap(); + device + .begin_command_buffer( + self.draw_command_buffer, + &vk::CommandBufferBeginInfo::builder() + .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT), + ) + .unwrap(); + } + present_index + } + + pub fn render_end(&self, present_index: u32) { + let device = &self.device; + unsafe { + device.end_command_buffer(self.draw_command_buffer).unwrap(); + let swapchains = [self.swapchain]; + let image_indices = [present_index]; + let submit_info = vk::SubmitInfo::builder() + .wait_semaphores(std::slice::from_ref(&self.present_complete_semaphore)) + .wait_dst_stage_mask(&[vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT]) + .command_buffers(std::slice::from_ref(&self.draw_command_buffer)) + .signal_semaphores(std::slice::from_ref(&self.rendering_complete_semaphore)); + + device + .queue_submit( + self.present_queue, + std::slice::from_ref(&submit_info), + self.draw_commands_reuse_fence, + ) + .unwrap(); + + match self.swapchain_info.swapchain_loader.queue_present( + self.present_queue, + &vk::PresentInfoKHR::builder() + .image_indices(&image_indices) + .wait_semaphores(std::slice::from_ref(&self.rendering_complete_semaphore)) + .swapchains(&swapchains), + ) { + Ok(true) | Err(vk::Result::ERROR_OUT_OF_DATE_KHR) => { + // this usually indicates the window has been resized + } + Err(e) => panic!("Error presenting: {e:?}"), + _ => {} + } + }; + } +} + +fn init_winit( + window_width: u32, + window_height: u32, +) -> (winit::event_loop::EventLoop<()>, winit::window::Window) { + use winit::{event_loop::EventLoopBuilder, window::WindowBuilder}; + + let event_loop = EventLoopBuilder::new().build(); + + let window = WindowBuilder::new() + .with_title("Yakui Vulkan - Test") + .with_inner_size(winit::dpi::LogicalSize::new( + f64::from(window_width), + f64::from(window_height), + )) + .build(&event_loop) + .unwrap(); + (event_loop, window) +} + +fn create_swapchain( + device: &ash::Device, + previous_swapchain: Option, + swapchain_info: &SwapchainInfo, +) -> (vk::SwapchainKHR, Vec) { + let SwapchainInfo { + swapchain_loader, + surface_format, + surface_resolution, + present_mode, + surface, + desired_image_count, + } = swapchain_info; + + let mut swapchain_create_info = vk::SwapchainCreateInfoKHR::builder() + .surface(*surface) + .min_image_count(*desired_image_count) + .image_color_space(surface_format.color_space) + .image_format(surface_format.format) + .image_extent(*surface_resolution) + .image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT) + .image_sharing_mode(vk::SharingMode::EXCLUSIVE) + .pre_transform(vk::SurfaceTransformFlagsKHR::IDENTITY) + .composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE) + .present_mode(*present_mode) + .clipped(true) + .image_array_layers(1); + + if let Some(old_swapchain) = previous_swapchain { + swapchain_create_info.old_swapchain = old_swapchain + } + + let swapchain = unsafe { + swapchain_loader + .create_swapchain(&swapchain_create_info, None) + .unwrap() + }; + + let present_images = unsafe { swapchain_loader.get_swapchain_images(swapchain).unwrap() }; + let present_image_views: Vec = present_images + .iter() + .map(|&image| { + let create_view_info = vk::ImageViewCreateInfo::builder() + .view_type(vk::ImageViewType::TYPE_2D) + .format(surface_format.format) + .components(vk::ComponentMapping { + r: vk::ComponentSwizzle::R, + g: vk::ComponentSwizzle::G, + b: vk::ComponentSwizzle::B, + a: vk::ComponentSwizzle::A, + }) + .subresource_range(vk::ImageSubresourceRange { + aspect_mask: vk::ImageAspectFlags::COLOR, + base_mip_level: 0, + level_count: 1, + base_array_layer: 0, + layer_count: 1, + }) + .image(image); + unsafe { device.create_image_view(&create_view_info, None).unwrap() } + }) + .collect(); + + (swapchain, present_image_views) +} + +impl Drop for VulkanTest { + fn drop(&mut self) { + unsafe { + self.device.device_wait_idle().unwrap(); + self.device + .destroy_semaphore(self.present_complete_semaphore, None); + self.device + .destroy_semaphore(self.rendering_complete_semaphore, None); + self.device + .destroy_fence(self.draw_commands_reuse_fence, None); + self.device + .destroy_fence(self.setup_commands_reuse_fence, None); + self.device.destroy_command_pool(self.command_pool, None); + self.destroy_swapchain(self.swapchain); + self.device.destroy_device(None); + self.surface_loader.destroy_surface(self.surface, None); + self.instance.destroy_instance(None); + } + } +} + +struct SwapchainInfo { + pub swapchain_loader: ash::extensions::khr::Swapchain, + pub surface_format: vk::SurfaceFormatKHR, + pub surface_resolution: vk::Extent2D, + pub present_mode: vk::PresentModeKHR, + pub surface: vk::SurfaceKHR, + pub desired_image_count: u32, +} + +impl SwapchainInfo { + pub fn new( + swapchain_loader: ash::extensions::khr::Swapchain, + surface_format: vk::SurfaceFormatKHR, + surface_resolution: vk::Extent2D, + present_mode: vk::PresentModeKHR, + surface: vk::SurfaceKHR, + desired_image_count: u32, + ) -> Self { + Self { + swapchain_loader, + surface_format, + surface_resolution, + present_mode, + surface, + desired_image_count, + } + } +} diff --git a/crates/yakui-vulkan/src/lib.rs b/crates/yakui-vulkan/src/lib.rs index 73136529..6cdb8e0d 100644 --- a/crates/yakui-vulkan/src/lib.rs +++ b/crates/yakui-vulkan/src/lib.rs @@ -15,7 +15,7 @@ //! This crate requires at least Vulkan 1.2 and a GPU with support for `VkPhysicalDeviceDescriptorIndexingFeatures.descriptorBindingPartiallyBound`. //! You should also, you know, enable that feature, or Vulkan Validation Layers will get mad at you. You definitely don't want that. //! -//! For an example of how to use this crate, check out the cleverly named `it_works` test in `lib.rs` in the GitHub repo. +//! For an example of how to use this crate, check out `examples/demo.rs` mod buffer; mod descriptors; @@ -775,242 +775,3 @@ fn create_framebuffers( .collect(); framebuffers } - -#[cfg(all(windows, test))] -mod tests { - use super::*; - use ash::vk; - use yakui::geometry::Vec2; - - use winit::{ - event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, - event_loop::ControlFlow, - platform::run_return::EventLoopExtRunReturn, - }; - use yakui::image; - - const MONKEY_PNG: &[u8] = include_bytes!("../../bootstrap/assets/monkey.png"); - const DOG_JPG: &[u8] = include_bytes!("../assets/dog.jpg"); - - #[derive(Debug, Clone)] - struct GuiState { - monkey: yakui::ManagedTextureId, - dog: yakui::TextureId, - which_image: WhichImage, - } - - #[derive(Debug, Clone)] - enum WhichImage { - Monkey, - Dog, - } - - #[test] - #[ignore] - /// Simple smoke test to make sure render screen properly pixel Vulkan. - fn it_works() { - use winit::dpi::PhysicalSize; - - use crate::util::{init_winit, VulkanTest}; - - let (width, height) = (500, 500); - let (mut event_loop, window) = init_winit(width, height); - let mut vulkan_test = VulkanTest::new(width, height, &window); - let render_surface = RenderSurface { - resolution: vk::Extent2D { width, height }, - format: vulkan_test.swapchain_info.surface_format.format, - image_views: vulkan_test.present_image_views.clone(), - load_op: vk::AttachmentLoadOp::CLEAR, - }; - let mut yak = yakui::Yakui::new(); - yak.set_surface_size([width as f32, height as f32].into()); - yak.set_unscaled_viewport(yakui_core::geometry::Rect::from_pos_size( - Default::default(), - [width as f32, height as f32].into(), - )); - - let (mut yakui_vulkan, mut gui_state) = { - let vulkan_context = VulkanContext::new( - &vulkan_test.device, - vulkan_test.present_queue, - vulkan_test.draw_command_buffer, - vulkan_test.command_pool, - vulkan_test.device_memory_properties, - ); - let mut yakui_vulkan = YakuiVulkan::new(&vulkan_context, render_surface); - let gui_state = GuiState { - monkey: yak.add_texture(create_yakui_texture( - MONKEY_PNG, - yakui::paint::TextureFilter::Linear, - )), - dog: yakui_vulkan.create_user_texture( - &vulkan_context, - create_vulkan_texture_info(DOG_JPG, vk::Filter::LINEAR), - ), - which_image: WhichImage::Monkey, - }; - (yakui_vulkan, gui_state) - }; - - let mut winit_initializing = true; - - event_loop.run_return(|event, _, control_flow| { - *control_flow = ControlFlow::Poll; - match event { - Event::WindowEvent { - event: - WindowEvent::CloseRequested - | WindowEvent::KeyboardInput { - input: - KeyboardInput { - state: ElementState::Pressed, - virtual_keycode: Some(VirtualKeyCode::Escape), - .. - }, - .. - }, - .. - } => *control_flow = ControlFlow::Exit, - - Event::NewEvents(cause) => { - if cause == winit::event::StartCause::Init { - winit_initializing = true; - } else { - winit_initializing = false; - } - } - - Event::MainEventsCleared => { - let framebuffer_index = vulkan_test.render_begin(); - let vulkan_context = VulkanContext::new( - &vulkan_test.device, - vulkan_test.present_queue, - vulkan_test.draw_command_buffer, - vulkan_test.command_pool, - vulkan_test.device_memory_properties, - ); - - yak.start(); - gui(&gui_state); - yak.finish(); - - yakui_vulkan.paint(&mut yak, &vulkan_context, framebuffer_index); - vulkan_test.render_end(framebuffer_index); - } - Event::WindowEvent { - event: WindowEvent::Resized(size), - .. - } => { - if winit_initializing { - println!("Ignoring resize during init!"); - } else { - let PhysicalSize { width, height } = size; - vulkan_test.resized(width, height); - let render_surface = RenderSurface { - resolution: vk::Extent2D { width, height }, - format: vulkan_test.swapchain_info.surface_format.format, - image_views: vulkan_test.present_image_views.clone(), - load_op: vk::AttachmentLoadOp::CLEAR, - }; - yakui_vulkan.update_surface(render_surface, &vulkan_test.device); - yak.set_surface_size([width as f32, height as f32].into()); - yak.set_unscaled_viewport(yakui_core::geometry::Rect::from_pos_size( - Default::default(), - [width as f32, height as f32].into(), - )); - } - } - Event::WindowEvent { - event: WindowEvent::ScaleFactorChanged { scale_factor, .. }, - .. - } => yak.set_scale_factor(scale_factor as _), - Event::WindowEvent { - event: - WindowEvent::KeyboardInput { - input: - KeyboardInput { - state: ElementState::Released, - virtual_keycode, - .. - }, - .. - }, - .. - } => match virtual_keycode { - Some(VirtualKeyCode::A) => { - gui_state.which_image = match &gui_state.which_image { - WhichImage::Monkey => WhichImage::Dog, - WhichImage::Dog => WhichImage::Monkey, - } - } - _ => {} - }, - _ => (), - } - }); - - unsafe { - yakui_vulkan.cleanup(&vulkan_test.device); - } - } - - fn create_vulkan_texture_info( - compressed_image_bytes: &[u8], - filter: vk::Filter, - ) -> VulkanTextureCreateInfo> { - let image = image::load_from_memory(compressed_image_bytes) - .unwrap() - .into_rgba8(); - let resolution = vk::Extent2D { - width: image.width(), - height: image.height(), - }; - - VulkanTextureCreateInfo::new( - image.into_raw(), - vk::Format::R8G8B8A8_UNORM, - resolution, - filter, - filter, - ) - } - - fn create_yakui_texture( - compressed_image_bytes: &[u8], - filter: yakui::paint::TextureFilter, - ) -> yakui::paint::Texture { - let image = image::load_from_memory(compressed_image_bytes) - .unwrap() - .into_rgba8(); - let size = UVec2::new(image.width(), image.height()); - - let mut texture = yakui::paint::Texture::new( - yakui::paint::TextureFormat::Rgba8Srgb, - size, - image.into_raw(), - ); - texture.mag_filter = filter; - texture - } - - fn gui(gui_state: &GuiState) { - use yakui::{column, label, row, text, widgets::Text, Color}; - let (animal, texture): (&'static str, yakui::TextureId) = match gui_state.which_image { - WhichImage::Monkey => ("monkye", gui_state.monkey.into()), - WhichImage::Dog => ("dog haha good boy", gui_state.dog.into()), - }; - column(|| { - row(|| { - label("Hello, world!"); - - let mut text = Text::new(48.0, "colored text!"); - text.style.color = Color::RED; - text.show(); - }); - - text(96.0, format!("look it is a {animal}")); - - image(texture, Vec2::new(400.0, 400.0)); - }); - } -} diff --git a/crates/yakui-vulkan/src/util.rs b/crates/yakui-vulkan/src/util.rs index a5374ff6..0aca7fd5 100644 --- a/crates/yakui-vulkan/src/util.rs +++ b/crates/yakui-vulkan/src/util.rs @@ -14,495 +14,3 @@ pub(crate) fn find_memorytype_index( }) .map(|(index, _memory_type)| index as _) } - -#[cfg(test)] -pub(crate) struct VulkanTest { - pub _entry: ash::Entry, - pub device: ash::Device, - pub instance: ash::Instance, - pub surface_loader: ash::extensions::khr::Surface, - pub device_memory_properties: vk::PhysicalDeviceMemoryProperties, - - pub present_queue: vk::Queue, - - pub surface: vk::SurfaceKHR, - pub swapchain_info: SwapchainInfo, - - pub swapchain: vk::SwapchainKHR, - pub present_image_views: Vec, - - pub command_pool: vk::CommandPool, - pub draw_command_buffer: vk::CommandBuffer, - - pub present_complete_semaphore: vk::Semaphore, - pub rendering_complete_semaphore: vk::Semaphore, - - pub draw_commands_reuse_fence: vk::Fence, - pub setup_commands_reuse_fence: vk::Fence, -} - -#[cfg(test)] -impl VulkanTest { - /// Bring up all the Vulkan pomp and ceremony required to render things. - /// Vulkan Broadly lifted from: https://github.com/ash-rs/ash/blob/0.37.2/examples/src/lib.rs - pub fn new(window_width: u32, window_height: u32, window: &winit::window::Window) -> Self { - use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; - use std::ffi::CStr; - - let entry = unsafe { ash::Entry::load().expect("failed to load Vulkan") }; - let app_name = unsafe { CStr::from_bytes_with_nul_unchecked(b"Yakui Vulkan Test\0") }; - - let appinfo = vk::ApplicationInfo::builder() - .application_name(app_name) - .application_version(0) - .engine_name(app_name) - .engine_version(0) - .api_version(vk::make_api_version(0, 1, 3, 0)); - - let extension_names = - ash_window::enumerate_required_extensions(window.raw_display_handle()) - .unwrap() - .to_vec(); - - let create_info = vk::InstanceCreateInfo::builder() - .application_info(&appinfo) - .enabled_extension_names(&extension_names); - - let instance = unsafe { - entry - .create_instance(&create_info, None) - .expect("Instance creation error") - }; - - let surface = unsafe { - ash_window::create_surface( - &entry, - &instance, - window.raw_display_handle(), - window.raw_window_handle(), - None, - ) - .unwrap() - }; - - let pdevices = unsafe { - instance - .enumerate_physical_devices() - .expect("Physical device error") - }; - let surface_loader = ash::extensions::khr::Surface::new(&entry, &instance); - let (physical_device, queue_family_index) = unsafe { - pdevices - .iter() - .find_map(|pdevice| { - instance - .get_physical_device_queue_family_properties(*pdevice) - .iter() - .enumerate() - .find_map(|(index, info)| { - let supports_graphic_and_surface = - info.queue_flags.contains(vk::QueueFlags::GRAPHICS) - && surface_loader - .get_physical_device_surface_support( - *pdevice, - index as u32, - surface, - ) - .unwrap(); - if supports_graphic_and_surface { - Some((*pdevice, index)) - } else { - None - } - }) - }) - .expect("Couldn't find suitable device.") - }; - let queue_family_index = queue_family_index as u32; - let device_extension_names_raw = [ash::extensions::khr::Swapchain::name().as_ptr()]; - let priorities = [1.0]; - - let queue_info = vk::DeviceQueueCreateInfo::builder() - .queue_family_index(queue_family_index) - .queue_priorities(&priorities); - - let mut descriptor_indexing_features = - vk::PhysicalDeviceDescriptorIndexingFeatures::builder() - .descriptor_binding_partially_bound(true); - - let device_create_info = vk::DeviceCreateInfo::builder() - .queue_create_infos(std::slice::from_ref(&queue_info)) - .enabled_extension_names(&device_extension_names_raw) - .push_next(&mut descriptor_indexing_features); - - let device = unsafe { - instance - .create_device(physical_device, &device_create_info, None) - .unwrap() - }; - - let present_queue = unsafe { device.get_device_queue(queue_family_index, 0) }; - let surface_format = unsafe { - surface_loader - .get_physical_device_surface_formats(physical_device, surface) - .unwrap()[0] - }; - - let surface_capabilities = unsafe { - surface_loader - .get_physical_device_surface_capabilities(physical_device, surface) - .unwrap() - }; - let mut desired_image_count = surface_capabilities.min_image_count + 1; - if surface_capabilities.max_image_count > 0 - && desired_image_count > surface_capabilities.max_image_count - { - desired_image_count = surface_capabilities.max_image_count; - } - let surface_resolution = match surface_capabilities.current_extent.width { - std::u32::MAX => vk::Extent2D { - width: window_width, - height: window_height, - }, - _ => surface_capabilities.current_extent, - }; - - let present_modes = unsafe { - surface_loader - .get_physical_device_surface_present_modes(physical_device, surface) - .unwrap() - }; - let present_mode = present_modes - .iter() - .cloned() - .find(|&mode| mode == vk::PresentModeKHR::MAILBOX) - .unwrap_or(vk::PresentModeKHR::FIFO); - let swapchain_loader = ash::extensions::khr::Swapchain::new(&instance, &device); - - let swapchain_info = SwapchainInfo::new( - swapchain_loader, - surface_format, - surface_resolution, - present_mode, - surface, - desired_image_count, - ); - - let (swapchain, present_image_views) = create_swapchain(&device, None, &swapchain_info); - - let pool_create_info = vk::CommandPoolCreateInfo::builder() - .flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER) - .queue_family_index(queue_family_index); - - let pool = unsafe { device.create_command_pool(&pool_create_info, None).unwrap() }; - - let command_buffer_allocate_info = vk::CommandBufferAllocateInfo::builder() - .command_buffer_count(1) - .command_pool(pool) - .level(vk::CommandBufferLevel::PRIMARY); - - let command_buffers = unsafe { - device - .allocate_command_buffers(&command_buffer_allocate_info) - .unwrap() - }; - let draw_command_buffer = command_buffers[0]; - - let fence_create_info = - vk::FenceCreateInfo::builder().flags(vk::FenceCreateFlags::SIGNALED); - - let draw_commands_reuse_fence = unsafe { - device - .create_fence(&fence_create_info, None) - .expect("Create fence failed.") - }; - let setup_commands_reuse_fence = unsafe { - device - .create_fence(&fence_create_info, None) - .expect("Create fence failed.") - }; - - let semaphore_create_info = vk::SemaphoreCreateInfo::default(); - - let present_complete_semaphore = unsafe { - device - .create_semaphore(&semaphore_create_info, None) - .unwrap() - }; - let rendering_complete_semaphore = unsafe { - device - .create_semaphore(&semaphore_create_info, None) - .unwrap() - }; - - let device_memory_properties = - unsafe { instance.get_physical_device_memory_properties(physical_device) }; - - Self { - device, - present_queue, - _entry: entry, - instance, - surface_loader, - swapchain_info, - device_memory_properties, - surface, - swapchain, - present_image_views, - command_pool: pool, - draw_command_buffer, - present_complete_semaphore, - rendering_complete_semaphore, - draw_commands_reuse_fence, - setup_commands_reuse_fence, - } - } - - pub fn resized(&mut self, window_width: u32, window_height: u32) { - unsafe { - self.device.device_wait_idle().unwrap(); - self.swapchain_info.surface_resolution = vk::Extent2D { - width: window_width, - height: window_height, - }; - let (new_swapchain, new_present_image_views) = - create_swapchain(&self.device, Some(self.swapchain), &self.swapchain_info); - - self.destroy_swapchain(self.swapchain); - self.present_image_views = new_present_image_views; - self.swapchain = new_swapchain; - } - } - - unsafe fn destroy_swapchain(&self, swapchain: vk::SwapchainKHR) { - let device = &self.device; - for image_view in &self.present_image_views { - device.destroy_image_view(*image_view, None); - } - self.swapchain_info - .swapchain_loader - .destroy_swapchain(swapchain, None); - } - - pub fn render_begin(&self) -> u32 { - let (present_index, _) = unsafe { - self.swapchain_info - .swapchain_loader - .acquire_next_image( - self.swapchain, - std::u64::MAX, - self.present_complete_semaphore, - vk::Fence::null(), - ) - .unwrap() - }; - - let device = &self.device; - unsafe { - device - .wait_for_fences( - std::slice::from_ref(&self.draw_commands_reuse_fence), - true, - std::u64::MAX, - ) - .unwrap(); - device - .reset_fences(std::slice::from_ref(&self.draw_commands_reuse_fence)) - .unwrap(); - device - .reset_command_buffer( - self.draw_command_buffer, - vk::CommandBufferResetFlags::RELEASE_RESOURCES, - ) - .unwrap(); - device - .begin_command_buffer( - self.draw_command_buffer, - &vk::CommandBufferBeginInfo::builder() - .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT), - ) - .unwrap(); - } - present_index - } - - pub fn render_end(&self, present_index: u32) { - let device = &self.device; - unsafe { - device.end_command_buffer(self.draw_command_buffer).unwrap(); - let swapchains = [self.swapchain]; - let image_indices = [present_index]; - let submit_info = vk::SubmitInfo::builder() - .wait_semaphores(std::slice::from_ref(&self.present_complete_semaphore)) - .wait_dst_stage_mask(&[vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT]) - .command_buffers(std::slice::from_ref(&self.draw_command_buffer)) - .signal_semaphores(std::slice::from_ref(&self.rendering_complete_semaphore)); - - device - .queue_submit( - self.present_queue, - std::slice::from_ref(&submit_info), - self.draw_commands_reuse_fence, - ) - .unwrap(); - - match self.swapchain_info.swapchain_loader.queue_present( - self.present_queue, - &vk::PresentInfoKHR::builder() - .image_indices(&image_indices) - .wait_semaphores(std::slice::from_ref(&self.rendering_complete_semaphore)) - .swapchains(&swapchains), - ) { - Ok(true) | Err(vk::Result::ERROR_OUT_OF_DATE_KHR) => { - // this usually indicates the window has been resized - } - Err(e) => panic!("Error presenting: {e:?}"), - _ => {} - } - }; - } -} - -#[cfg(all(windows, test))] -pub(crate) fn init_winit( - window_width: u32, - window_height: u32, -) -> (winit::event_loop::EventLoop<()>, winit::window::Window) { - use winit::{ - event_loop::EventLoopBuilder, platform::windows::EventLoopBuilderExtWindows, - window::WindowBuilder, - }; - - // necessary because tests are in a separate thread - let event_loop = EventLoopBuilder::new().with_any_thread(true).build(); - - let window = WindowBuilder::new() - .with_title("Yakui Vulkan - Test") - .with_inner_size(winit::dpi::LogicalSize::new( - f64::from(window_width), - f64::from(window_height), - )) - .build(&event_loop) - .unwrap(); - (event_loop, window) -} - -#[cfg(test)] -fn create_swapchain( - device: &ash::Device, - previous_swapchain: Option, - swapchain_info: &SwapchainInfo, -) -> (vk::SwapchainKHR, Vec) { - let SwapchainInfo { - swapchain_loader, - surface_format, - surface_resolution, - present_mode, - surface, - desired_image_count, - } = swapchain_info; - - let mut swapchain_create_info = vk::SwapchainCreateInfoKHR::builder() - .surface(*surface) - .min_image_count(*desired_image_count) - .image_color_space(surface_format.color_space) - .image_format(surface_format.format) - .image_extent(*surface_resolution) - .image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT) - .image_sharing_mode(vk::SharingMode::EXCLUSIVE) - .pre_transform(vk::SurfaceTransformFlagsKHR::IDENTITY) - .composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE) - .present_mode(*present_mode) - .clipped(true) - .image_array_layers(1); - - if let Some(old_swapchain) = previous_swapchain { - swapchain_create_info.old_swapchain = old_swapchain - } - - let swapchain = unsafe { - swapchain_loader - .create_swapchain(&swapchain_create_info, None) - .unwrap() - }; - - let present_images = unsafe { swapchain_loader.get_swapchain_images(swapchain).unwrap() }; - let present_image_views: Vec = present_images - .iter() - .map(|&image| { - let create_view_info = vk::ImageViewCreateInfo::builder() - .view_type(vk::ImageViewType::TYPE_2D) - .format(surface_format.format) - .components(vk::ComponentMapping { - r: vk::ComponentSwizzle::R, - g: vk::ComponentSwizzle::G, - b: vk::ComponentSwizzle::B, - a: vk::ComponentSwizzle::A, - }) - .subresource_range(vk::ImageSubresourceRange { - aspect_mask: vk::ImageAspectFlags::COLOR, - base_mip_level: 0, - level_count: 1, - base_array_layer: 0, - layer_count: 1, - }) - .image(image); - unsafe { device.create_image_view(&create_view_info, None).unwrap() } - }) - .collect(); - - (swapchain, present_image_views) -} - -#[cfg(test)] -impl Drop for VulkanTest { - fn drop(&mut self) { - unsafe { - self.device.device_wait_idle().unwrap(); - self.device - .destroy_semaphore(self.present_complete_semaphore, None); - self.device - .destroy_semaphore(self.rendering_complete_semaphore, None); - self.device - .destroy_fence(self.draw_commands_reuse_fence, None); - self.device - .destroy_fence(self.setup_commands_reuse_fence, None); - self.device.destroy_command_pool(self.command_pool, None); - self.destroy_swapchain(self.swapchain); - self.device.destroy_device(None); - self.surface_loader.destroy_surface(self.surface, None); - self.instance.destroy_instance(None); - } - } -} - -#[cfg(test)] -pub(crate) struct SwapchainInfo { - pub swapchain_loader: ash::extensions::khr::Swapchain, - pub surface_format: vk::SurfaceFormatKHR, - pub surface_resolution: vk::Extent2D, - pub present_mode: vk::PresentModeKHR, - pub surface: vk::SurfaceKHR, - pub desired_image_count: u32, -} - -#[cfg(test)] -impl SwapchainInfo { - pub fn new( - swapchain_loader: ash::extensions::khr::Swapchain, - surface_format: vk::SurfaceFormatKHR, - surface_resolution: vk::Extent2D, - present_mode: vk::PresentModeKHR, - surface: vk::SurfaceKHR, - desired_image_count: u32, - ) -> Self { - Self { - swapchain_loader, - surface_format, - surface_resolution, - present_mode, - surface, - desired_image_count, - } - } -} From b1b97375efde22f05ec7f0df67c413982e191e21 Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Sun, 22 Oct 2023 10:39:17 -0700 Subject: [PATCH 03/16] Rely on a user-supplied render pass --- crates/yakui-vulkan/examples/demo.rs | 161 ++++++++++++++++++---- crates/yakui-vulkan/src/lib.rs | 152 ++------------------ crates/yakui-vulkan/src/vulkan_context.rs | 4 + 3 files changed, 148 insertions(+), 169 deletions(-) diff --git a/crates/yakui-vulkan/examples/demo.rs b/crates/yakui-vulkan/examples/demo.rs index 2df50349..373db8ec 100644 --- a/crates/yakui-vulkan/examples/demo.rs +++ b/crates/yakui-vulkan/examples/demo.rs @@ -34,10 +34,8 @@ fn main() { let mut vulkan_test = VulkanTest::new(width, height, &window); let render_surface = RenderSurface { resolution: vk::Extent2D { width, height }, - format: vulkan_test.swapchain_info.surface_format.format, - image_views: vulkan_test.present_image_views.clone(), - load_op: vk::AttachmentLoadOp::CLEAR, }; + let mut yak = yakui::Yakui::new(); yak.set_surface_size([width as f32, height as f32].into()); yak.set_unscaled_viewport(yakui_core::geometry::Rect::from_pos_size( @@ -52,6 +50,7 @@ fn main() { vulkan_test.draw_command_buffer, vulkan_test.command_pool, vulkan_test.device_memory_properties, + vulkan_test.render_pass, ); let mut yakui_vulkan = YakuiVulkan::new(&vulkan_context, render_surface); let gui_state = GuiState { @@ -97,21 +96,22 @@ fn main() { } Event::MainEventsCleared => { - let framebuffer_index = vulkan_test.render_begin(); let vulkan_context = VulkanContext::new( &vulkan_test.device, vulkan_test.present_queue, vulkan_test.draw_command_buffer, vulkan_test.command_pool, vulkan_test.device_memory_properties, + vulkan_test.render_pass, ); yak.start(); gui(&gui_state); yak.finish(); - yakui_vulkan.paint(&mut yak, &vulkan_context, framebuffer_index); - vulkan_test.render_end(framebuffer_index); + let index = vulkan_test.render_begin(); + yakui_vulkan.paint(&mut yak, &vulkan_context); + vulkan_test.render_end(index); } Event::WindowEvent { event: WindowEvent::Resized(size), @@ -124,11 +124,8 @@ fn main() { vulkan_test.resized(width, height); let render_surface = RenderSurface { resolution: vk::Extent2D { width, height }, - format: vulkan_test.swapchain_info.surface_format.format, - image_views: vulkan_test.present_image_views.clone(), - load_op: vk::AttachmentLoadOp::CLEAR, }; - yakui_vulkan.update_surface(render_surface, &vulkan_test.device); + yakui_vulkan.update_surface(render_surface); yak.set_surface_size([width as f32, height as f32].into()); yak.set_unscaled_viewport(yakui_core::geometry::Rect::from_pos_size( Default::default(), @@ -231,28 +228,31 @@ fn gui(gui_state: &GuiState) { } struct VulkanTest { - pub _entry: ash::Entry, - pub device: ash::Device, - pub instance: ash::Instance, - pub surface_loader: ash::extensions::khr::Surface, - pub device_memory_properties: vk::PhysicalDeviceMemoryProperties, + _entry: ash::Entry, + device: ash::Device, + instance: ash::Instance, + surface_loader: ash::extensions::khr::Surface, + device_memory_properties: vk::PhysicalDeviceMemoryProperties, - pub present_queue: vk::Queue, + present_queue: vk::Queue, - pub surface: vk::SurfaceKHR, - pub swapchain_info: SwapchainInfo, + surface: vk::SurfaceKHR, + swapchain_info: SwapchainInfo, + + swapchain: vk::SwapchainKHR, + present_image_views: Vec, - pub swapchain: vk::SwapchainKHR, - pub present_image_views: Vec, + render_pass: vk::RenderPass, + framebuffers: Vec, - pub command_pool: vk::CommandPool, - pub draw_command_buffer: vk::CommandBuffer, + command_pool: vk::CommandPool, + draw_command_buffer: vk::CommandBuffer, - pub present_complete_semaphore: vk::Semaphore, - pub rendering_complete_semaphore: vk::Semaphore, + present_complete_semaphore: vk::Semaphore, + rendering_complete_semaphore: vk::Semaphore, - pub draw_commands_reuse_fence: vk::Fence, - pub setup_commands_reuse_fence: vk::Fence, + draw_commands_reuse_fence: vk::Fence, + setup_commands_reuse_fence: vk::Fence, } impl VulkanTest { @@ -403,6 +403,49 @@ impl VulkanTest { let (swapchain, present_image_views) = create_swapchain(&device, None, &swapchain_info); + let renderpass_attachments = [vk::AttachmentDescription { + format: swapchain_info.surface_format.format, + samples: vk::SampleCountFlags::TYPE_1, + load_op: vk::AttachmentLoadOp::CLEAR, + store_op: vk::AttachmentStoreOp::STORE, + final_layout: vk::ImageLayout::PRESENT_SRC_KHR, + ..Default::default() + }]; + let color_attachment_refs = [vk::AttachmentReference { + attachment: 0, + layout: vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL, + }]; + let dependencies = [vk::SubpassDependency { + src_subpass: vk::SUBPASS_EXTERNAL, + src_stage_mask: vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT, + dst_access_mask: vk::AccessFlags::COLOR_ATTACHMENT_READ + | vk::AccessFlags::COLOR_ATTACHMENT_WRITE, + dst_stage_mask: vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT, + ..Default::default() + }]; + + let subpass = vk::SubpassDescription::builder() + .color_attachments(&color_attachment_refs) + .pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS); + + let renderpass_create_info = vk::RenderPassCreateInfo::builder() + .attachments(&renderpass_attachments) + .subpasses(std::slice::from_ref(&subpass)) + .dependencies(&dependencies); + + let render_pass = unsafe { + device + .create_render_pass(&renderpass_create_info, None) + .unwrap() + }; + + let framebuffers = create_framebuffers( + &present_image_views, + surface_resolution, + render_pass, + &device, + ); + let pool_create_info = vk::CommandPoolCreateInfo::builder() .flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER) .queue_family_index(queue_family_index); @@ -462,6 +505,8 @@ impl VulkanTest { surface, swapchain, present_image_views, + render_pass, + framebuffers, command_pool: pool, draw_command_buffer, present_complete_semaphore, @@ -480,15 +525,25 @@ impl VulkanTest { }; let (new_swapchain, new_present_image_views) = create_swapchain(&self.device, Some(self.swapchain), &self.swapchain_info); + let framebuffers = create_framebuffers( + &new_present_image_views, + self.swapchain_info.surface_resolution, + self.render_pass, + &self.device, + ); self.destroy_swapchain(self.swapchain); self.present_image_views = new_present_image_views; self.swapchain = new_swapchain; + self.framebuffers = framebuffers; } } unsafe fn destroy_swapchain(&self, swapchain: vk::SwapchainKHR) { let device = &self.device; + for &fb in &self.framebuffers { + device.destroy_framebuffer(fb, None); + } for image_view in &self.present_image_views { device.destroy_image_view(*image_view, None); } @@ -535,6 +590,31 @@ impl VulkanTest { .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT), ) .unwrap(); + let clear_values = [ + vk::ClearValue { + color: vk::ClearColorValue { + float32: [0.0, 0.0, 0.0, 0.0], + }, + }, + vk::ClearValue { + depth_stencil: vk::ClearDepthStencilValue { + depth: 1.0, + stencil: 0, + }, + }, + ]; + + let render_pass_begin_info = vk::RenderPassBeginInfo::builder() + .render_pass(self.render_pass) + .framebuffer(self.framebuffers[present_index as usize]) + .render_area(self.swapchain_info.surface_resolution.into()) + .clear_values(&clear_values); + + device.cmd_begin_render_pass( + self.draw_command_buffer, + &render_pass_begin_info, + vk::SubpassContents::INLINE, + ); } present_index } @@ -542,6 +622,7 @@ impl VulkanTest { pub fn render_end(&self, present_index: u32) { let device = &self.device; unsafe { + device.cmd_end_render_pass(self.draw_command_buffer); device.end_command_buffer(self.draw_command_buffer).unwrap(); let swapchains = [self.swapchain]; let image_indices = [present_index]; @@ -675,6 +756,7 @@ impl Drop for VulkanTest { .destroy_fence(self.setup_commands_reuse_fence, None); self.device.destroy_command_pool(self.command_pool, None); self.destroy_swapchain(self.swapchain); + self.device.destroy_render_pass(self.render_pass, None); self.device.destroy_device(None); self.surface_loader.destroy_surface(self.surface, None); self.instance.destroy_instance(None); @@ -710,3 +792,30 @@ impl SwapchainInfo { } } } + +fn create_framebuffers( + views: &[vk::ImageView], + extent: vk::Extent2D, + render_pass: vk::RenderPass, + device: &ash::Device, +) -> Vec { + let framebuffers: Vec = views + .iter() + .map(|&present_image_view| { + let framebuffer_attachments = [present_image_view]; + let frame_buffer_create_info = vk::FramebufferCreateInfo::builder() + .render_pass(render_pass) + .attachments(&framebuffer_attachments) + .width(extent.width) + .height(extent.height) + .layers(1); + + unsafe { + device + .create_framebuffer(&frame_buffer_create_info, None) + .unwrap() + } + }) + .collect(); + framebuffers +} diff --git a/crates/yakui-vulkan/src/lib.rs b/crates/yakui-vulkan/src/lib.rs index 6cdb8e0d..8a5b597e 100644 --- a/crates/yakui-vulkan/src/lib.rs +++ b/crates/yakui-vulkan/src/lib.rs @@ -40,19 +40,12 @@ use yakui::{paint::Vertex as YakuiVertex, ManagedTextureId}; /// Uses a simple descriptor system that requires Vulkan 1.2 and some extensions to be enabled. See the [crate level documentation](`crate`) /// for details. /// -/// Note that this implementation is currently only able to render to a present surface (ie. the swapchain). -/// Future versions will support drawing to any [`vk::ImageView`](`ash::vk::ImageView`). -/// /// Construct the struct by populating a [`VulkanContext`] with the relevant handles to your Vulkan renderer and /// call [`YakuiVulkan::new()`]. /// /// Make sure to call [`YakuiVulkan::cleanup()`]. pub struct YakuiVulkan { - /// The render pass used to draw - render_pass: vk::RenderPass, - /// One or more framebuffers to draw on. This will match the number of `vk::ImageView` present in [`RenderSurface`] - framebuffers: Vec, - /// The surface to draw on. Currently only supports present surfaces (ie. the swapchain) + /// The surface to draw on render_surface: RenderSurface, /// The pipeline layout used to draw pipeline_layout: vk::PipelineLayout, @@ -73,16 +66,10 @@ pub struct YakuiVulkan { } #[derive(Clone)] -/// The surface for yakui to draw on. Currently only supports present surfaces (ie. the swapchain) +/// The surface for yakui to draw on pub struct RenderSurface { /// The resolution of the surface pub resolution: vk::Extent2D, - /// The image format of the surface - pub format: vk::Format, - /// The image views to render to. One framebuffer will be created per view - pub image_views: Vec, - /// What operation to perform when loading this image - pub load_op: vk::AttachmentLoadOp, } #[derive(Clone, Copy, Debug)] @@ -155,7 +142,7 @@ impl From<&YakuiVertex> for Vertex { } impl YakuiVulkan { - /// Create a new [`YakuiVulkan`] instance. Currently only supports rendering directly to the swapchain. + /// Create a new [`YakuiVulkan`] instance /// /// ## Safety /// - `vulkan_context` must have valid members @@ -164,45 +151,6 @@ impl YakuiVulkan { let device = vulkan_context.device; let descriptors = Descriptors::new(vulkan_context); - // TODO: Don't write directly to the present surface.. - let renderpass_attachments = [vk::AttachmentDescription { - format: render_surface.format, - samples: vk::SampleCountFlags::TYPE_1, - load_op: render_surface.load_op, - store_op: vk::AttachmentStoreOp::STORE, - final_layout: vk::ImageLayout::PRESENT_SRC_KHR, - ..Default::default() - }]; - let color_attachment_refs = [vk::AttachmentReference { - attachment: 0, - layout: vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL, - }]; - let dependencies = [vk::SubpassDependency { - src_subpass: vk::SUBPASS_EXTERNAL, - src_stage_mask: vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT, - dst_access_mask: vk::AccessFlags::COLOR_ATTACHMENT_READ - | vk::AccessFlags::COLOR_ATTACHMENT_WRITE, - dst_stage_mask: vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT, - ..Default::default() - }]; - - let subpass = vk::SubpassDescription::builder() - .color_attachments(&color_attachment_refs) - .pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS); - - let renderpass_create_info = vk::RenderPassCreateInfo::builder() - .attachments(&renderpass_attachments) - .subpasses(std::slice::from_ref(&subpass)) - .dependencies(&dependencies); - - let render_pass = unsafe { - device - .create_render_pass(&renderpass_create_info, None) - .unwrap() - }; - - let framebuffers = create_framebuffers(&render_surface, render_pass, device); - let index_buffer = Buffer::new(vulkan_context, vk::BufferUsageFlags::INDEX_BUFFER, &[]); let vertex_buffer = Buffer::new(vulkan_context, vk::BufferUsageFlags::VERTEX_BUFFER, &[]); @@ -365,7 +313,7 @@ impl YakuiVulkan { .color_blend_state(&color_blend_state) .dynamic_state(&dynamic_state_info) .layout(pipeline_layout) - .render_pass(render_pass); + .render_pass(vulkan_context.render_pass); let graphics_pipelines = unsafe { device @@ -385,9 +333,7 @@ impl YakuiVulkan { } Self { - render_pass, descriptors, - framebuffers, render_surface, pipeline_layout, graphics_pipeline, @@ -403,13 +349,7 @@ impl YakuiVulkan { /// /// ## Safety /// - `vulkan_context` must be the same as the one used to create this [`YakuiVulkan`] instance - /// - `present_index` must be the a valid index into the framebuffer (ie. the result of calling [`vkAcquireNextImageKHR`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkAcquireNextImageKHR.html)) - pub fn paint( - &mut self, - yak: &mut yakui_core::Yakui, - vulkan_context: &VulkanContext, - present_index: u32, - ) { + pub fn paint(&mut self, yak: &mut yakui_core::Yakui, vulkan_context: &VulkanContext) { let paint = yak.paint(); self.update_textures(vulkan_context, paint); @@ -422,41 +362,16 @@ impl YakuiVulkan { let draw_calls = self.build_draw_calls(vulkan_context, paint); - self.render(vulkan_context, present_index, &draw_calls); + self.render(vulkan_context, &draw_calls); } /// Render the draw calls we've built up - fn render( - &self, - vulkan_context: &VulkanContext, - framebuffer_index: u32, - draw_calls: &[DrawCall], - ) { + fn render(&self, vulkan_context: &VulkanContext, draw_calls: &[DrawCall]) { let device = vulkan_context.device; let command_buffer = vulkan_context.draw_command_buffer; - let clear_values = [ - vk::ClearValue { - color: vk::ClearColorValue { - float32: [0.0, 0.0, 0.0, 0.0], - }, - }, - vk::ClearValue { - depth_stencil: vk::ClearDepthStencilValue { - depth: 1.0, - stencil: 0, - }, - }, - ]; - let surface = &self.render_surface; - let render_pass_begin_info = vk::RenderPassBeginInfo::builder() - .render_pass(self.render_pass) - .framebuffer(self.framebuffers[framebuffer_index as usize]) - .render_area(surface.resolution.into()) - .clear_values(&clear_values); - let viewports = [vk::Viewport { x: 0.0, y: 0.0, @@ -469,11 +384,6 @@ impl YakuiVulkan { let surface_size = UVec2::new(surface.resolution.width, surface.resolution.height); unsafe { - device.cmd_begin_render_pass( - command_buffer, - &render_pass_begin_info, - vk::SubpassContents::INLINE, - ); device.cmd_bind_pipeline( command_buffer, vk::PipelineBindPoint::GRAPHICS, @@ -563,7 +473,6 @@ impl YakuiVulkan { 1, ); } - device.cmd_end_render_pass(command_buffer); } } @@ -612,8 +521,6 @@ impl YakuiVulkan { device.destroy_pipeline(self.graphics_pipeline, None); self.index_buffer.cleanup(device); self.vertex_buffer.cleanup(device); - self.destroy_framebuffers(device); - device.destroy_render_pass(self.render_pass, None); } /// Provides access to the descriptors used by `YakuiVulkan` to manage textures. @@ -729,49 +636,8 @@ impl YakuiVulkan { } /// Update the surface that this [`YakuiVulkan`] instance will render to. You'll probably want to call - /// this if the user resizes the window to avoid writing to an out-of-date swapchain. - /// - /// ## Safety - /// - Care must be taken to ensure that the new [`RenderSurface`] points to images from a correct swapchain - /// - You must use the same [`ash::Device`] used to create this instance - pub fn update_surface(&mut self, render_surface: RenderSurface, device: &ash::Device) { - unsafe { - self.destroy_framebuffers(device); - } - self.framebuffers = create_framebuffers(&render_surface, self.render_pass, device); + /// this if the user resizes the window. + pub fn update_surface(&mut self, render_surface: RenderSurface) { self.render_surface = render_surface; } - - unsafe fn destroy_framebuffers(&mut self, device: &ash::Device) { - for framebuffer in &self.framebuffers { - device.destroy_framebuffer(*framebuffer, None); - } - } -} - -fn create_framebuffers( - render_surface: &RenderSurface, - render_pass: vk::RenderPass, - device: &ash::Device, -) -> Vec { - let framebuffers: Vec = render_surface - .image_views - .iter() - .map(|&present_image_view| { - let framebuffer_attachments = [present_image_view]; - let frame_buffer_create_info = vk::FramebufferCreateInfo::builder() - .render_pass(render_pass) - .attachments(&framebuffer_attachments) - .width(render_surface.resolution.width) - .height(render_surface.resolution.height) - .layers(1); - - unsafe { - device - .create_framebuffer(&frame_buffer_create_info, None) - .unwrap() - } - }) - .collect(); - framebuffers } diff --git a/crates/yakui-vulkan/src/vulkan_context.rs b/crates/yakui-vulkan/src/vulkan_context.rs index 64dd9e22..99c96e11 100644 --- a/crates/yakui-vulkan/src/vulkan_context.rs +++ b/crates/yakui-vulkan/src/vulkan_context.rs @@ -21,6 +21,8 @@ pub struct VulkanContext<'a> { command_pool: vk::CommandPool, /// Memory properties used for [`crate::YakuiVulkan`]'s allocation commands pub memory_properties: vk::PhysicalDeviceMemoryProperties, + /// Render pass the GUI will be drawn in + pub render_pass: vk::RenderPass, } impl<'a> VulkanContext<'a> { @@ -31,6 +33,7 @@ impl<'a> VulkanContext<'a> { draw_command_buffer: vk::CommandBuffer, command_pool: vk::CommandPool, memory_properties: vk::PhysicalDeviceMemoryProperties, + render_pass: vk::RenderPass, ) -> Self { Self { device, @@ -38,6 +41,7 @@ impl<'a> VulkanContext<'a> { draw_command_buffer, command_pool, memory_properties, + render_pass, } } From 2e1013c826dd737029c3dff3df7e372b3cc79454 Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Sun, 22 Oct 2023 10:50:38 -0700 Subject: [PATCH 04/16] Only require resolution when strictly necessary --- crates/yakui-vulkan/examples/demo.rs | 15 +++---- crates/yakui-vulkan/src/lib.rs | 59 ++++++++++------------------ 2 files changed, 27 insertions(+), 47 deletions(-) diff --git a/crates/yakui-vulkan/examples/demo.rs b/crates/yakui-vulkan/examples/demo.rs index 373db8ec..4fcc5a44 100644 --- a/crates/yakui-vulkan/examples/demo.rs +++ b/crates/yakui-vulkan/examples/demo.rs @@ -32,9 +32,6 @@ fn main() { let (width, height) = (500, 500); let (mut event_loop, window) = init_winit(width, height); let mut vulkan_test = VulkanTest::new(width, height, &window); - let render_surface = RenderSurface { - resolution: vk::Extent2D { width, height }, - }; let mut yak = yakui::Yakui::new(); yak.set_surface_size([width as f32, height as f32].into()); @@ -52,7 +49,7 @@ fn main() { vulkan_test.device_memory_properties, vulkan_test.render_pass, ); - let mut yakui_vulkan = YakuiVulkan::new(&vulkan_context, render_surface); + let mut yakui_vulkan = YakuiVulkan::new(&vulkan_context); let gui_state = GuiState { monkey: yak.add_texture(create_yakui_texture( MONKEY_PNG, @@ -110,7 +107,11 @@ fn main() { yak.finish(); let index = vulkan_test.render_begin(); - yakui_vulkan.paint(&mut yak, &vulkan_context); + yakui_vulkan.paint( + &mut yak, + &vulkan_context, + vulkan_test.swapchain_info.surface_resolution, + ); vulkan_test.render_end(index); } Event::WindowEvent { @@ -122,10 +123,6 @@ fn main() { } else { let PhysicalSize { width, height } = size; vulkan_test.resized(width, height); - let render_surface = RenderSurface { - resolution: vk::Extent2D { width, height }, - }; - yakui_vulkan.update_surface(render_surface); yak.set_surface_size([width as f32, height as f32].into()); yak.set_unscaled_viewport(yakui_core::geometry::Rect::from_pos_size( Default::default(), diff --git a/crates/yakui-vulkan/src/lib.rs b/crates/yakui-vulkan/src/lib.rs index 8a5b597e..0bf2f1fa 100644 --- a/crates/yakui-vulkan/src/lib.rs +++ b/crates/yakui-vulkan/src/lib.rs @@ -45,8 +45,6 @@ use yakui::{paint::Vertex as YakuiVertex, ManagedTextureId}; /// /// Make sure to call [`YakuiVulkan::cleanup()`]. pub struct YakuiVulkan { - /// The surface to draw on - render_surface: RenderSurface, /// The pipeline layout used to draw pipeline_layout: vk::PipelineLayout, /// The graphics pipeline used to draw @@ -65,13 +63,6 @@ pub struct YakuiVulkan { descriptors: Descriptors, } -#[derive(Clone)] -/// The surface for yakui to draw on -pub struct RenderSurface { - /// The resolution of the surface - pub resolution: vk::Extent2D, -} - #[derive(Clone, Copy, Debug)] /// A single draw call to render a yakui mesh struct DrawCall { @@ -147,7 +138,7 @@ impl YakuiVulkan { /// ## Safety /// - `vulkan_context` must have valid members /// - the members of `render_surface` must have been created with the same [`ash::Device`] as `vulkan_context`. - pub fn new(vulkan_context: &VulkanContext, render_surface: RenderSurface) -> Self { + pub fn new(vulkan_context: &VulkanContext) -> Self { let device = vulkan_context.device; let descriptors = Descriptors::new(vulkan_context); @@ -245,18 +236,9 @@ impl YakuiVulkan { topology: vk::PrimitiveTopology::TRIANGLE_LIST, ..Default::default() }; - let viewports = [vk::Viewport { - x: 0.0, - y: 0.0, - width: render_surface.resolution.width as f32, - height: render_surface.resolution.height as f32, - min_depth: 0.0, - max_depth: 1.0, - }]; - let scissors = [render_surface.resolution.into()]; let viewport_state_info = vk::PipelineViewportStateCreateInfo::builder() - .scissors(&scissors) - .viewports(&viewports); + .scissor_count(1) + .viewport_count(1); let rasterization_info = vk::PipelineRasterizationStateCreateInfo { front_face: vk::FrontFace::COUNTER_CLOCKWISE, @@ -334,7 +316,6 @@ impl YakuiVulkan { Self { descriptors, - render_surface, pipeline_layout, graphics_pipeline, index_buffer, @@ -349,7 +330,12 @@ impl YakuiVulkan { /// /// ## Safety /// - `vulkan_context` must be the same as the one used to create this [`YakuiVulkan`] instance - pub fn paint(&mut self, yak: &mut yakui_core::Yakui, vulkan_context: &VulkanContext) { + pub fn paint( + &mut self, + yak: &mut yakui_core::Yakui, + vulkan_context: &VulkanContext, + resolution: vk::Extent2D, + ) { let paint = yak.paint(); self.update_textures(vulkan_context, paint); @@ -362,26 +348,29 @@ impl YakuiVulkan { let draw_calls = self.build_draw_calls(vulkan_context, paint); - self.render(vulkan_context, &draw_calls); + self.render(vulkan_context, resolution, &draw_calls); } /// Render the draw calls we've built up - fn render(&self, vulkan_context: &VulkanContext, draw_calls: &[DrawCall]) { + fn render( + &self, + vulkan_context: &VulkanContext, + resolution: vk::Extent2D, + draw_calls: &[DrawCall], + ) { let device = vulkan_context.device; let command_buffer = vulkan_context.draw_command_buffer; - let surface = &self.render_surface; - let viewports = [vk::Viewport { x: 0.0, y: 0.0, - width: surface.resolution.width as f32, - height: surface.resolution.height as f32, + width: resolution.width as f32, + height: resolution.height as f32, min_depth: 0.0, max_depth: 1.0, }]; - let surface_size = UVec2::new(surface.resolution.width, surface.resolution.height); + let surface_size = UVec2::new(resolution.width, resolution.height); unsafe { device.cmd_bind_pipeline( @@ -390,7 +379,7 @@ impl YakuiVulkan { self.graphics_pipeline, ); device.cmd_set_viewport(command_buffer, 0, &viewports); - let default_scissor = [surface.resolution.into()]; + let default_scissor = [resolution.into()]; // We set the scissor first here as it's against the spec not to do so. device.cmd_set_scissor(command_buffer, 0, &default_scissor); @@ -441,7 +430,7 @@ impl YakuiVulkan { x: pos.x as _, y: pos.y as _, }, - extent: surface.resolution, + extent: resolution, }]; // If there's a clip, update the scissor device.cmd_set_scissor(command_buffer, 0, &scissors); @@ -634,10 +623,4 @@ impl YakuiVulkan { draw_calls } - - /// Update the surface that this [`YakuiVulkan`] instance will render to. You'll probably want to call - /// this if the user resizes the window. - pub fn update_surface(&mut self, render_surface: RenderSurface) { - self.render_surface = render_surface; - } } From 1c936be85b9c382727154139323925c9700cb0a3 Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Sun, 22 Oct 2023 10:59:57 -0700 Subject: [PATCH 05/16] Suppress validation layer warnings on resize --- crates/yakui-vulkan/examples/demo.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/crates/yakui-vulkan/examples/demo.rs b/crates/yakui-vulkan/examples/demo.rs index 4fcc5a44..99619cd0 100644 --- a/crates/yakui-vulkan/examples/demo.rs +++ b/crates/yakui-vulkan/examples/demo.rs @@ -227,6 +227,7 @@ fn gui(gui_state: &GuiState) { struct VulkanTest { _entry: ash::Entry, device: ash::Device, + physical_device: vk::PhysicalDevice, instance: ash::Instance, surface_loader: ash::extensions::khr::Surface, device_memory_properties: vk::PhysicalDeviceMemoryProperties, @@ -493,6 +494,7 @@ impl VulkanTest { Self { device, + physical_device, present_queue, _entry: entry, instance, @@ -516,10 +518,18 @@ impl VulkanTest { pub fn resized(&mut self, window_width: u32, window_height: u32) { unsafe { self.device.device_wait_idle().unwrap(); - self.swapchain_info.surface_resolution = vk::Extent2D { - width: window_width, - height: window_height, + let surface_capabilities = self + .surface_loader + .get_physical_device_surface_capabilities(self.physical_device, self.surface) + .unwrap(); + let surface_resolution = match surface_capabilities.current_extent.width { + std::u32::MAX => vk::Extent2D { + width: window_width, + height: window_height, + }, + _ => surface_capabilities.current_extent, }; + self.swapchain_info.surface_resolution = surface_resolution; let (new_swapchain, new_present_image_views) = create_swapchain(&self.device, Some(self.swapchain), &self.swapchain_info); let framebuffers = create_framebuffers( From c7728296ddb7a9912aaf31b1fe6dc68f8fdbf8c1 Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Sun, 22 Oct 2023 11:39:09 -0700 Subject: [PATCH 06/16] Simplify Buffer::overwrite --- crates/yakui-vulkan/src/buffer.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/yakui-vulkan/src/buffer.rs b/crates/yakui-vulkan/src/buffer.rs index 7cd25291..bedb4019 100644 --- a/crates/yakui-vulkan/src/buffer.rs +++ b/crates/yakui-vulkan/src/buffer.rs @@ -1,6 +1,4 @@ -use std::mem::align_of; - -use ash::{util::Align, vk}; +use ash::vk; use crate::{util::find_memorytype_index, vulkan_context::VulkanContext}; @@ -79,8 +77,9 @@ impl Buffer { todo!("Support resizing buffers"); } - let mut slice = Align::new(self.ptr.as_ptr().cast(), align_of::() as u64, self.size); - slice.copy_from_slice(new_data); + self.ptr + .as_ptr() + .copy_from_nonoverlapping(new_data.as_ptr(), new_data.len()); self.len = new_data.len() } From 9b2528147757a46e927b3568a02a785efdeb3049 Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Sun, 22 Oct 2023 11:42:37 -0700 Subject: [PATCH 07/16] Allow random writes to buffers --- crates/yakui-vulkan/src/buffer.rs | 7 +++---- crates/yakui-vulkan/src/lib.rs | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/crates/yakui-vulkan/src/buffer.rs b/crates/yakui-vulkan/src/buffer.rs index bedb4019..79e6db75 100644 --- a/crates/yakui-vulkan/src/buffer.rs +++ b/crates/yakui-vulkan/src/buffer.rs @@ -71,17 +71,16 @@ impl Buffer { } } - pub unsafe fn overwrite(&mut self, _vulkan_context: &VulkanContext, new_data: &[T]) { + pub unsafe fn write(&mut self, _vulkan_context: &VulkanContext, offset: usize, new_data: &[T]) { let new_data_size = std::mem::size_of_val(new_data); - if new_data_size > self.size as usize { + if std::mem::size_of::() * offset + new_data_size > self.size as usize { todo!("Support resizing buffers"); } self.ptr .as_ptr() + .add(offset) .copy_from_nonoverlapping(new_data.as_ptr(), new_data.len()); - - self.len = new_data.len() } /// ## Safety diff --git a/crates/yakui-vulkan/src/lib.rs b/crates/yakui-vulkan/src/lib.rs index 0bf2f1fa..03d488ef 100644 --- a/crates/yakui-vulkan/src/lib.rs +++ b/crates/yakui-vulkan/src/lib.rs @@ -617,8 +617,8 @@ impl YakuiVulkan { } unsafe { - self.index_buffer.overwrite(vulkan_context, &indices); - self.vertex_buffer.overwrite(vulkan_context, &vertices); + self.index_buffer.write(vulkan_context, 0, &indices); + self.vertex_buffer.write(vulkan_context, 0, &vertices); } draw_calls From 44838c7367fd02678f6b66cf44eb2888708d5737 Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Sun, 22 Oct 2023 11:43:46 -0700 Subject: [PATCH 08/16] Split buffer allocation and initialization --- crates/yakui-vulkan/src/buffer.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/crates/yakui-vulkan/src/buffer.rs b/crates/yakui-vulkan/src/buffer.rs index 79e6db75..f6572a47 100644 --- a/crates/yakui-vulkan/src/buffer.rs +++ b/crates/yakui-vulkan/src/buffer.rs @@ -20,16 +20,16 @@ pub(crate) struct Buffer { } impl Buffer { - pub fn new( + pub fn with_capacity( vulkan_context: &VulkanContext, usage: vk::BufferUsageFlags, - initial_data: &[T], + elements: usize, ) -> Self { let device = vulkan_context.device; let device_memory_properties = &vulkan_context.memory_properties; let buffer_info = vk::BufferCreateInfo::builder() - .size(std::mem::size_of_val(initial_data).max(MIN_BUFFER_SIZE as _) as u64) + .size(((std::mem::size_of::() * elements) as vk::DeviceSize).max(MIN_BUFFER_SIZE)) .usage(usage) .sharing_mode(vk::SharingMode::EXCLUSIVE); @@ -53,8 +53,6 @@ impl Buffer { let ptr = device .map_memory(memory, 0, size, vk::MemoryMapFlags::empty()) .unwrap(); - let mut slice = Align::new(ptr, align_of::() as u64, size); - slice.copy_from_slice(initial_data); device.bind_buffer_memory(handle, memory, 0).unwrap(); // Safety: ptr is guaranteed to be a non-null pointer to T @@ -66,11 +64,23 @@ impl Buffer { memory, _usage: usage, size, - len: initial_data.len(), + len: elements, ptr, } } + pub fn new( + vulkan_context: &VulkanContext, + usage: vk::BufferUsageFlags, + initial_data: &[T], + ) -> Self { + let mut result = Self::with_capacity(vulkan_context, usage, initial_data.len()); + unsafe { + result.write(vulkan_context, 0, initial_data); + } + result + } + pub unsafe fn write(&mut self, _vulkan_context: &VulkanContext, offset: usize, new_data: &[T]) { let new_data_size = std::mem::size_of_val(new_data); if std::mem::size_of::() * offset + new_data_size > self.size as usize { From 86050fd5cb0f8420c4953e9270b2fe6d8fe0b591 Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Sun, 22 Oct 2023 12:47:12 -0700 Subject: [PATCH 09/16] Dynamic rendering support --- crates/yakui-vulkan/examples/demo.rs | 2 +- crates/yakui-vulkan/src/lib.rs | 25 +++++++++++++++++++++---- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/crates/yakui-vulkan/examples/demo.rs b/crates/yakui-vulkan/examples/demo.rs index 99619cd0..f083d409 100644 --- a/crates/yakui-vulkan/examples/demo.rs +++ b/crates/yakui-vulkan/examples/demo.rs @@ -49,7 +49,7 @@ fn main() { vulkan_test.device_memory_properties, vulkan_test.render_pass, ); - let mut yakui_vulkan = YakuiVulkan::new(&vulkan_context); + let mut yakui_vulkan = YakuiVulkan::new(&vulkan_context, yakui_vulkan::Options::default()); let gui_state = GuiState { monkey: yak.add_texture(create_yakui_texture( MONKEY_PNG, diff --git a/crates/yakui-vulkan/src/lib.rs b/crates/yakui-vulkan/src/lib.rs index 03d488ef..f9308a89 100644 --- a/crates/yakui-vulkan/src/lib.rs +++ b/crates/yakui-vulkan/src/lib.rs @@ -63,6 +63,14 @@ pub struct YakuiVulkan { descriptors: Descriptors, } +/// Optional Vulkan configuration +#[non_exhaustive] +#[derive(Default)] +pub struct Options { + /// Indicates that VK_KHR_dynamic_rendering is enabled and should be used with the given format + pub dynamic_rendering_format: Option, +} + #[derive(Clone, Copy, Debug)] /// A single draw call to render a yakui mesh struct DrawCall { @@ -138,7 +146,7 @@ impl YakuiVulkan { /// ## Safety /// - `vulkan_context` must have valid members /// - the members of `render_surface` must have been created with the same [`ash::Device`] as `vulkan_context`. - pub fn new(vulkan_context: &VulkanContext) -> Self { + pub fn new(vulkan_context: &VulkanContext, options: Options) -> Self { let device = vulkan_context.device; let descriptors = Descriptors::new(vulkan_context); @@ -284,7 +292,7 @@ impl YakuiVulkan { let dynamic_state_info = vk::PipelineDynamicStateCreateInfo::builder().dynamic_states(&dynamic_state); - let graphic_pipeline_info = vk::GraphicsPipelineCreateInfo::builder() + let mut graphic_pipeline_info = vk::GraphicsPipelineCreateInfo::builder() .stages(&shader_stage_create_infos) .vertex_input_state(&vertex_input_state_info) .input_assembly_state(&vertex_input_assembly_state_info) @@ -294,8 +302,17 @@ impl YakuiVulkan { .depth_stencil_state(&depth_state_info) .color_blend_state(&color_blend_state) .dynamic_state(&dynamic_state_info) - .layout(pipeline_layout) - .render_pass(vulkan_context.render_pass); + .layout(pipeline_layout); + let rendering_info_formats; + let mut rendering_info; + if let Some(format) = options.dynamic_rendering_format { + rendering_info_formats = [format]; + rendering_info = vk::PipelineRenderingCreateInfo::builder() + .color_attachment_formats(&rendering_info_formats); + graphic_pipeline_info = graphic_pipeline_info.push_next(&mut rendering_info); + } else { + graphic_pipeline_info = graphic_pipeline_info.render_pass(vulkan_context.render_pass); + } let graphics_pipelines = unsafe { device From e4bf25ed57165a4b304b69cf1709948e92d43365 Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Sun, 22 Oct 2023 14:02:47 -0700 Subject: [PATCH 10/16] Add missing `unsafe` --- crates/yakui-vulkan/examples/demo.rs | 12 +++++++----- crates/yakui-vulkan/src/lib.rs | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/yakui-vulkan/examples/demo.rs b/crates/yakui-vulkan/examples/demo.rs index f083d409..eea69028 100644 --- a/crates/yakui-vulkan/examples/demo.rs +++ b/crates/yakui-vulkan/examples/demo.rs @@ -107,11 +107,13 @@ fn main() { yak.finish(); let index = vulkan_test.render_begin(); - yakui_vulkan.paint( - &mut yak, - &vulkan_context, - vulkan_test.swapchain_info.surface_resolution, - ); + unsafe { + yakui_vulkan.paint( + &mut yak, + &vulkan_context, + vulkan_test.swapchain_info.surface_resolution, + ); + } vulkan_test.render_end(index); } Event::WindowEvent { diff --git a/crates/yakui-vulkan/src/lib.rs b/crates/yakui-vulkan/src/lib.rs index f9308a89..5cd1160b 100644 --- a/crates/yakui-vulkan/src/lib.rs +++ b/crates/yakui-vulkan/src/lib.rs @@ -347,7 +347,7 @@ impl YakuiVulkan { /// /// ## Safety /// - `vulkan_context` must be the same as the one used to create this [`YakuiVulkan`] instance - pub fn paint( + pub unsafe fn paint( &mut self, yak: &mut yakui_core::Yakui, vulkan_context: &VulkanContext, From 4c791e3b6601ac15fbb485d6fb9044606697d2e8 Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Sun, 22 Oct 2023 14:16:28 -0700 Subject: [PATCH 11/16] Don't block the CPU on texture uploads --- crates/yakui-vulkan/examples/demo.rs | 25 +++- crates/yakui-vulkan/src/buffer.rs | 4 + crates/yakui-vulkan/src/lib.rs | 55 ++++++-- crates/yakui-vulkan/src/vulkan_context.rs | 120 +--------------- crates/yakui-vulkan/src/vulkan_texture.rs | 165 +++++++++++++++++++++- 5 files changed, 236 insertions(+), 133 deletions(-) diff --git a/crates/yakui-vulkan/examples/demo.rs b/crates/yakui-vulkan/examples/demo.rs index eea69028..54dc6d16 100644 --- a/crates/yakui-vulkan/examples/demo.rs +++ b/crates/yakui-vulkan/examples/demo.rs @@ -45,11 +45,12 @@ fn main() { &vulkan_test.device, vulkan_test.present_queue, vulkan_test.draw_command_buffer, - vulkan_test.command_pool, vulkan_test.device_memory_properties, vulkan_test.render_pass, ); let mut yakui_vulkan = YakuiVulkan::new(&vulkan_context, yakui_vulkan::Options::default()); + // Prepare for one frame in flight + yakui_vulkan.transfers_submitted(); let gui_state = GuiState { monkey: yak.add_texture(create_yakui_texture( MONKEY_PNG, @@ -97,7 +98,6 @@ fn main() { &vulkan_test.device, vulkan_test.present_queue, vulkan_test.draw_command_buffer, - vulkan_test.command_pool, vulkan_test.device_memory_properties, vulkan_test.render_pass, ); @@ -106,15 +106,23 @@ fn main() { gui(&gui_state); yak.finish(); - let index = vulkan_test.render_begin(); + let paint = yak.paint(); + + let index = vulkan_test.cmd_begin(); + unsafe { + yakui_vulkan.transfers_finished(&vulkan_context); + yakui_vulkan.transfer(paint, &vulkan_context); + } + vulkan_test.render_begin(index); unsafe { yakui_vulkan.paint( - &mut yak, + paint, &vulkan_context, vulkan_test.swapchain_info.surface_resolution, ); } vulkan_test.render_end(index); + yakui_vulkan.transfers_submitted(); } Event::WindowEvent { event: WindowEvent::Resized(size), @@ -561,7 +569,7 @@ impl VulkanTest { .destroy_swapchain(swapchain, None); } - pub fn render_begin(&self) -> u32 { + pub fn cmd_begin(&self) -> u32 { let (present_index, _) = unsafe { self.swapchain_info .swapchain_loader @@ -599,6 +607,13 @@ impl VulkanTest { .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT), ) .unwrap(); + } + present_index + } + + pub fn render_begin(&self, present_index: u32) -> u32 { + let device = &self.device; + unsafe { let clear_values = [ vk::ClearValue { color: vk::ClearColorValue { diff --git a/crates/yakui-vulkan/src/buffer.rs b/crates/yakui-vulkan/src/buffer.rs index f6572a47..99449147 100644 --- a/crates/yakui-vulkan/src/buffer.rs +++ b/crates/yakui-vulkan/src/buffer.rs @@ -100,4 +100,8 @@ impl Buffer { device.free_memory(self.memory, None); device.destroy_buffer(self.handle, None); } + + pub fn capacity(&self) -> usize { + self.len + } } diff --git a/crates/yakui-vulkan/src/lib.rs b/crates/yakui-vulkan/src/lib.rs index 5cd1160b..6c8db7fc 100644 --- a/crates/yakui-vulkan/src/lib.rs +++ b/crates/yakui-vulkan/src/lib.rs @@ -30,7 +30,7 @@ use bytemuck::{bytes_of, Pod, Zeroable}; pub use descriptors::Descriptors; use std::{collections::HashMap, ffi::CStr, io::Cursor}; pub use vulkan_context::VulkanContext; -use vulkan_texture::NO_TEXTURE_ID; +use vulkan_texture::{UploadQueue, NO_TEXTURE_ID}; pub use vulkan_texture::{VulkanTexture, VulkanTextureCreateInfo}; use yakui::geometry::UVec2; use yakui::{paint::Vertex as YakuiVertex, ManagedTextureId}; @@ -61,6 +61,7 @@ pub struct YakuiVulkan { user_textures: thunderdome::Arena, /// A wrapper around descriptor set functionality descriptors: Descriptors, + uploads: UploadQueue, } /// Optional Vulkan configuration @@ -340,23 +341,51 @@ impl YakuiVulkan { user_textures: Default::default(), yakui_managed_textures: Default::default(), initial_textures_synced: false, + uploads: UploadQueue::new(), } } + /// Record transfer commands that must complete before painting this `paint` + /// + /// ## Safety + /// - `vulkan_context` must be the same as the one used to create this [`YakuiVulkan`] instance + pub unsafe fn transfer( + &mut self, + paint: &yakui_core::paint::PaintDom, + vulkan_context: &VulkanContext, + ) { + self.update_textures(vulkan_context, paint); + self.uploads.record(vulkan_context); + } + + /// Call when commands recorded by zero or more successive `transfer` calls have been submitted to + /// a queue + /// + /// To support N concurrent frames in flight, call this N times before creating any textures. + pub fn transfers_submitted(&mut self) { + self.uploads.phase_submitted(); + } + + /// Call when the commands associated with the oldest call to `commands_submitted` have finished. + /// + /// ## Safety + /// + /// Those commands recorded prior to the oldest call to `commands_submitted` not yet associated + /// with a call to `commands_finished` must not be executing. + pub unsafe fn transfers_finished(&mut self, vulkan_context: &VulkanContext) { + self.uploads.phase_executed(vulkan_context); + } + /// Paint the yakui GUI using the provided [`VulkanContext`] /// /// ## Safety /// - `vulkan_context` must be the same as the one used to create this [`YakuiVulkan`] instance pub unsafe fn paint( &mut self, - yak: &mut yakui_core::Yakui, + paint: &yakui_core::paint::PaintDom, vulkan_context: &VulkanContext, resolution: vk::Extent2D, ) { - let paint = yak.paint(); - - self.update_textures(vulkan_context, paint); - // If there's nothing to paint, well.. don't paint! let layers = paint.layers(); if layers.iter().all(|layer| layer.calls.is_empty()) { @@ -376,7 +405,7 @@ impl YakuiVulkan { draw_calls: &[DrawCall], ) { let device = vulkan_context.device; - let command_buffer = vulkan_context.draw_command_buffer; + let command_buffer = vulkan_context.command_buffer; let viewports = [vk::Viewport { x: 0.0, @@ -492,8 +521,12 @@ impl YakuiVulkan { vulkan_context: &VulkanContext, texture_create_info: VulkanTextureCreateInfo>, ) -> yakui::TextureId { - let texture = - VulkanTexture::new(vulkan_context, &mut self.descriptors, texture_create_info); + let texture = VulkanTexture::new( + vulkan_context, + &mut self.descriptors, + texture_create_info, + &mut self.uploads, + ); yakui::TextureId::User(self.user_textures.insert(texture).to_bits()) } @@ -527,6 +560,7 @@ impl YakuiVulkan { device.destroy_pipeline(self.graphics_pipeline, None); self.index_buffer.cleanup(device); self.vertex_buffer.cleanup(device); + self.uploads.cleanup(device); } /// Provides access to the descriptors used by `YakuiVulkan` to manage textures. @@ -544,6 +578,7 @@ impl YakuiVulkan { vulkan_context, &mut self.descriptors, texture, + &mut self.uploads, ); self.yakui_managed_textures.insert(id, texture); } @@ -559,6 +594,7 @@ impl YakuiVulkan { vulkan_context, &mut self.descriptors, texture, + &mut self.uploads, ); self.yakui_managed_textures.insert(id, texture); } @@ -578,6 +614,7 @@ impl YakuiVulkan { vulkan_context, &mut self.descriptors, new, + &mut self.uploads, ); self.yakui_managed_textures.insert(id, texture); } diff --git a/crates/yakui-vulkan/src/vulkan_context.rs b/crates/yakui-vulkan/src/vulkan_context.rs index 99c96e11..c8647412 100644 --- a/crates/yakui-vulkan/src/vulkan_context.rs +++ b/crates/yakui-vulkan/src/vulkan_context.rs @@ -1,6 +1,6 @@ use ash::vk; -use crate::{buffer::Buffer, util::find_memorytype_index}; +use crate::util::find_memorytype_index; #[derive(Clone)] /// A wrapper around handles into your Vulkan renderer to call various methods on [`crate::YakuiVulkan`] @@ -17,8 +17,7 @@ pub struct VulkanContext<'a> { /// A queue that can call render and transfer commands pub queue: vk::Queue, /// The command buffer that you'll ultimately submit to be presented/rendered - pub draw_command_buffer: vk::CommandBuffer, - command_pool: vk::CommandPool, + pub command_buffer: vk::CommandBuffer, /// Memory properties used for [`crate::YakuiVulkan`]'s allocation commands pub memory_properties: vk::PhysicalDeviceMemoryProperties, /// Render pass the GUI will be drawn in @@ -30,16 +29,14 @@ impl<'a> VulkanContext<'a> { pub fn new( device: &'a ash::Device, queue: vk::Queue, - draw_command_buffer: vk::CommandBuffer, - command_pool: vk::CommandPool, + command_buffer: vk::CommandBuffer, memory_properties: vk::PhysicalDeviceMemoryProperties, render_pass: vk::RenderPass, ) -> Self { Self { device, queue, - draw_command_buffer, - command_pool, + command_buffer, memory_properties, render_pass, } @@ -47,11 +44,9 @@ impl<'a> VulkanContext<'a> { pub(crate) unsafe fn create_image( &self, - image_data: &[u8], extent: vk::Extent2D, format: vk::Format, ) -> (vk::Image, vk::DeviceMemory) { - let scratch_buffer = Buffer::new(self, vk::BufferUsageFlags::TRANSFER_SRC, image_data); let device = self.device; let image = device @@ -82,116 +77,9 @@ impl<'a> VulkanContext<'a> { let image_memory = self.allocate_memory(memory_requirements.size, memory_index); device.bind_image_memory(image, image_memory, 0).unwrap(); - self.one_time_command(|command_buffer| { - let transfer_barrier = vk::ImageMemoryBarrier { - dst_access_mask: vk::AccessFlags::TRANSFER_WRITE, - new_layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL, - image, - subresource_range: vk::ImageSubresourceRange { - aspect_mask: vk::ImageAspectFlags::COLOR, - level_count: 1, - layer_count: 1, - ..Default::default() - }, - ..Default::default() - }; - device.cmd_pipeline_barrier( - command_buffer, - vk::PipelineStageFlags::BOTTOM_OF_PIPE, - vk::PipelineStageFlags::TRANSFER, - vk::DependencyFlags::empty(), - &[], - &[], - &[transfer_barrier], - ); - let buffer_copy_regions = vk::BufferImageCopy { - image_subresource: vk::ImageSubresourceLayers { - aspect_mask: vk::ImageAspectFlags::COLOR, - layer_count: 1, - ..Default::default() - }, - image_extent: extent.into(), - ..Default::default() - }; - - device.cmd_copy_buffer_to_image( - command_buffer, - scratch_buffer.handle, - image, - vk::ImageLayout::TRANSFER_DST_OPTIMAL, - std::slice::from_ref(&buffer_copy_regions), - ); - - let transition_barrier = vk::ImageMemoryBarrier { - 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, - image, - subresource_range: vk::ImageSubresourceRange { - aspect_mask: vk::ImageAspectFlags::COLOR, - level_count: 1, - layer_count: 1, - ..Default::default() - }, - ..Default::default() - }; - device.cmd_pipeline_barrier( - command_buffer, - vk::PipelineStageFlags::TRANSFER, - vk::PipelineStageFlags::FRAGMENT_SHADER, - vk::DependencyFlags::empty(), - &[], - &[], - std::slice::from_ref(&transition_barrier), - ) - }); - - scratch_buffer.cleanup(device); - (image, image_memory) } - unsafe fn one_time_command(&self, f: F) { - let device = &self.device; - let command_buffer = device - .allocate_command_buffers(&vk::CommandBufferAllocateInfo { - command_pool: self.command_pool, - command_buffer_count: 1, - level: vk::CommandBufferLevel::PRIMARY, - ..Default::default() - }) - .unwrap()[0]; - - let fence = device.create_fence(&Default::default(), None).unwrap(); - - device - .begin_command_buffer( - command_buffer, - &vk::CommandBufferBeginInfo { - flags: vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT, - ..Default::default() - }, - ) - .unwrap(); - - f(command_buffer); - - device.end_command_buffer(command_buffer).unwrap(); - - let submit_info = - vk::SubmitInfo::builder().command_buffers(std::slice::from_ref(&command_buffer)); - device - .queue_submit(self.queue, std::slice::from_ref(&submit_info), fence) - .unwrap(); - device - .wait_for_fences(std::slice::from_ref(&fence), true, u64::MAX) - .unwrap(); - - device.destroy_fence(fence, None); - device.free_command_buffers(self.command_pool, std::slice::from_ref(&command_buffer)); - } - unsafe fn allocate_memory( &self, allocation_size: vk::DeviceSize, diff --git a/crates/yakui-vulkan/src/vulkan_texture.rs b/crates/yakui-vulkan/src/vulkan_texture.rs index 45ee166b..f3d26546 100644 --- a/crates/yakui-vulkan/src/vulkan_texture.rs +++ b/crates/yakui-vulkan/src/vulkan_texture.rs @@ -1,6 +1,8 @@ +use std::{collections::VecDeque, mem}; + use ash::vk; -use crate::{descriptors::Descriptors, vulkan_context::VulkanContext}; +use crate::{buffer::Buffer, descriptors::Descriptors, vulkan_context::VulkanContext}; pub(crate) const NO_TEXTURE_ID: u32 = u32::MAX; @@ -88,6 +90,7 @@ impl VulkanTexture { vulkan_context: &VulkanContext, descriptors: &mut Descriptors, create_info: VulkanTextureCreateInfo, + queue: &mut UploadQueue, ) -> Self { let VulkanTextureCreateInfo { image_data, @@ -98,8 +101,10 @@ impl VulkanTexture { } = create_info; let address_mode = vk::SamplerAddressMode::REPEAT; - let (image, memory) = - unsafe { vulkan_context.create_image(image_data.as_ref(), resolution, format) }; + let (image, memory) = unsafe { vulkan_context.create_image(resolution, format) }; + unsafe { + queue.push(vulkan_context, image, resolution, image_data.as_ref()); + } let view = unsafe { vulkan_context.create_image_view(image, format) }; let sampler = unsafe { @@ -133,6 +138,7 @@ impl VulkanTexture { vulkan_context: &VulkanContext, descriptors: &mut Descriptors, texture: &yakui::paint::Texture, + queue: &mut UploadQueue, ) -> Self { let resolution = vk::Extent2D { width: texture.size().x, @@ -148,6 +154,7 @@ impl VulkanTexture { vulkan_context, descriptors, VulkanTextureCreateInfo::new(image_data, format, resolution, min_filter, mag_filter), + queue, ) } @@ -173,3 +180,155 @@ fn get_filter(yakui_filter: yakui::paint::TextureFilter) -> vk::Filter { yakui::paint::TextureFilter::Nearest => vk::Filter::NEAREST, } } + +#[derive(Default)] +pub(crate) struct UploadQueue { + phase: UploadPhase, + in_flight: VecDeque, + textures: Vec<(vk::Image, vk::Extent2D, vk::Buffer, usize)>, + pre_barriers: Vec, + post_barriers: Vec, +} + +impl UploadQueue { + pub fn new() -> Self { + Self::default() + } + + /// Call when a draw command buffer has begun execution + pub fn phase_submitted(&mut self) { + let phase = mem::take(&mut self.phase); + self.in_flight.push_back(phase); + } + + /// Call whenever a draw command buffer has completed execution + pub unsafe fn phase_executed(&mut self, vulkan_context: &VulkanContext) { + let mut finished = self.in_flight.pop_front().expect("no commands in flight"); + // TODO: Reuse + finished.cleanup(vulkan_context.device); + } + + unsafe fn push( + &mut self, + vulkan_context: &VulkanContext, + image: vk::Image, + extent: vk::Extent2D, + data: &[u8], + ) { + self.pre_barriers.push(vk::ImageMemoryBarrier { + dst_access_mask: vk::AccessFlags::TRANSFER_WRITE, + new_layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL, + image, + subresource_range: vk::ImageSubresourceRange { + aspect_mask: vk::ImageAspectFlags::COLOR, + level_count: 1, + layer_count: 1, + ..Default::default() + }, + ..Default::default() + }); + let (buffer, offset) = self.phase.push(vulkan_context, data); + self.textures.push((image, extent, buffer, offset)); + self.post_barriers.push(vk::ImageMemoryBarrier { + 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, + image, + subresource_range: vk::ImageSubresourceRange { + aspect_mask: vk::ImageAspectFlags::COLOR, + level_count: 1, + layer_count: 1, + ..Default::default() + }, + ..Default::default() + }); + } + + pub unsafe fn record(&mut self, vulkan_context: &VulkanContext) { + let device = vulkan_context.device; + let cmd = vulkan_context.command_buffer; + device.cmd_pipeline_barrier( + cmd, + vk::PipelineStageFlags::TOP_OF_PIPE, + vk::PipelineStageFlags::TRANSFER, + vk::DependencyFlags::empty(), + &[], + &[], + &self.pre_barriers, + ); + self.pre_barriers.clear(); + + for (image, extent, buffer, offset) in self.textures.drain(..) { + device.cmd_copy_buffer_to_image( + cmd, + buffer, + image, + vk::ImageLayout::TRANSFER_DST_OPTIMAL, + &[vk::BufferImageCopy { + image_subresource: vk::ImageSubresourceLayers { + aspect_mask: vk::ImageAspectFlags::COLOR, + layer_count: 1, + ..Default::default() + }, + image_extent: extent.into(), + buffer_offset: offset as vk::DeviceSize, + ..Default::default() + }], + ); + } + + device.cmd_pipeline_barrier( + cmd, + vk::PipelineStageFlags::TRANSFER, + vk::PipelineStageFlags::FRAGMENT_SHADER, + vk::DependencyFlags::empty(), + &[], + &[], + &self.post_barriers, + ); + self.post_barriers.clear(); + } + + pub unsafe fn cleanup(&mut self, device: &ash::Device) { + self.phase.cleanup(device); + while let Some(mut phase) = self.in_flight.pop_front() { + phase.cleanup(device); + } + } +} + +#[derive(Default)] +struct UploadPhase { + buffers: Vec<(Buffer, usize)>, +} + +impl UploadPhase { + unsafe fn cleanup(&mut self, device: &ash::Device) { + for (buffer, _) in &self.buffers { + buffer.cleanup(device); + } + } + + unsafe fn push(&mut self, vulkan_context: &VulkanContext, data: &[u8]) -> (vk::Buffer, usize) { + if self + .buffers + .last() + .map_or(true, |(buffer, fill)| fill + data.len() > buffer.capacity()) + { + self.buffers.push(( + Buffer::with_capacity( + vulkan_context, + vk::BufferUsageFlags::TRANSFER_SRC, + data.len().max(1024 * 1024), + ), + 0, + )); + } + let (buffer, offset) = self.buffers.last_mut().unwrap(); + let start = *offset; + *offset += data.len(); + buffer.write(vulkan_context, start, data); + (buffer.handle, start) + } +} From c1bc99e646e12e64dc781f0fefd8487cae07acf3 Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Sun, 22 Oct 2023 14:53:15 -0700 Subject: [PATCH 12/16] Only require render pass when strictly necessary --- crates/yakui-vulkan/examples/demo.rs | 6 +++--- crates/yakui-vulkan/src/lib.rs | 11 +++++++++-- crates/yakui-vulkan/src/vulkan_context.rs | 4 ---- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/crates/yakui-vulkan/examples/demo.rs b/crates/yakui-vulkan/examples/demo.rs index 54dc6d16..f542ce4d 100644 --- a/crates/yakui-vulkan/examples/demo.rs +++ b/crates/yakui-vulkan/examples/demo.rs @@ -46,9 +46,10 @@ fn main() { vulkan_test.present_queue, vulkan_test.draw_command_buffer, vulkan_test.device_memory_properties, - vulkan_test.render_pass, ); - let mut yakui_vulkan = YakuiVulkan::new(&vulkan_context, yakui_vulkan::Options::default()); + let mut options = yakui_vulkan::Options::default(); + options.render_pass = vulkan_test.render_pass; + let mut yakui_vulkan = YakuiVulkan::new(&vulkan_context, options); // Prepare for one frame in flight yakui_vulkan.transfers_submitted(); let gui_state = GuiState { @@ -99,7 +100,6 @@ fn main() { vulkan_test.present_queue, vulkan_test.draw_command_buffer, vulkan_test.device_memory_properties, - vulkan_test.render_pass, ); yak.start(); diff --git a/crates/yakui-vulkan/src/lib.rs b/crates/yakui-vulkan/src/lib.rs index 6c8db7fc..25ec8841 100644 --- a/crates/yakui-vulkan/src/lib.rs +++ b/crates/yakui-vulkan/src/lib.rs @@ -64,12 +64,14 @@ pub struct YakuiVulkan { uploads: UploadQueue, } -/// Optional Vulkan configuration +/// Vulkan configuration #[non_exhaustive] #[derive(Default)] pub struct Options { /// Indicates that VK_KHR_dynamic_rendering is enabled and should be used with the given format pub dynamic_rendering_format: Option, + /// Render pass that the GUI will be drawn in. Ignored if `dynamic_rendering_format` is set. + pub render_pass: vk::RenderPass, } #[derive(Clone, Copy, Debug)] @@ -306,13 +308,18 @@ impl YakuiVulkan { .layout(pipeline_layout); let rendering_info_formats; let mut rendering_info; + assert!( + options.dynamic_rendering_format.is_some() + || options.render_pass != vk::RenderPass::null(), + "either dynamic_rendering_format or render_pass must be set" + ); if let Some(format) = options.dynamic_rendering_format { rendering_info_formats = [format]; rendering_info = vk::PipelineRenderingCreateInfo::builder() .color_attachment_formats(&rendering_info_formats); graphic_pipeline_info = graphic_pipeline_info.push_next(&mut rendering_info); } else { - graphic_pipeline_info = graphic_pipeline_info.render_pass(vulkan_context.render_pass); + graphic_pipeline_info = graphic_pipeline_info.render_pass(options.render_pass); } let graphics_pipelines = unsafe { diff --git a/crates/yakui-vulkan/src/vulkan_context.rs b/crates/yakui-vulkan/src/vulkan_context.rs index c8647412..a2a8b1a4 100644 --- a/crates/yakui-vulkan/src/vulkan_context.rs +++ b/crates/yakui-vulkan/src/vulkan_context.rs @@ -20,8 +20,6 @@ pub struct VulkanContext<'a> { pub command_buffer: vk::CommandBuffer, /// Memory properties used for [`crate::YakuiVulkan`]'s allocation commands pub memory_properties: vk::PhysicalDeviceMemoryProperties, - /// Render pass the GUI will be drawn in - pub render_pass: vk::RenderPass, } impl<'a> VulkanContext<'a> { @@ -31,14 +29,12 @@ impl<'a> VulkanContext<'a> { queue: vk::Queue, command_buffer: vk::CommandBuffer, memory_properties: vk::PhysicalDeviceMemoryProperties, - render_pass: vk::RenderPass, ) -> Self { Self { device, queue, command_buffer, memory_properties, - render_pass, } } From 232a7a81acc8231c1a1ae1971deae758aa3c3fba Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Sun, 22 Oct 2023 14:58:08 -0700 Subject: [PATCH 13/16] Only require a command buffer when strictly necessary --- crates/yakui-vulkan/examples/demo.rs | 5 ++--- crates/yakui-vulkan/src/lib.rs | 8 +++++--- crates/yakui-vulkan/src/vulkan_context.rs | 4 ---- crates/yakui-vulkan/src/vulkan_texture.rs | 3 +-- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/crates/yakui-vulkan/examples/demo.rs b/crates/yakui-vulkan/examples/demo.rs index f542ce4d..511c3458 100644 --- a/crates/yakui-vulkan/examples/demo.rs +++ b/crates/yakui-vulkan/examples/demo.rs @@ -44,7 +44,6 @@ fn main() { let vulkan_context = VulkanContext::new( &vulkan_test.device, vulkan_test.present_queue, - vulkan_test.draw_command_buffer, vulkan_test.device_memory_properties, ); let mut options = yakui_vulkan::Options::default(); @@ -98,7 +97,6 @@ fn main() { let vulkan_context = VulkanContext::new( &vulkan_test.device, vulkan_test.present_queue, - vulkan_test.draw_command_buffer, vulkan_test.device_memory_properties, ); @@ -111,13 +109,14 @@ fn main() { let index = vulkan_test.cmd_begin(); unsafe { yakui_vulkan.transfers_finished(&vulkan_context); - yakui_vulkan.transfer(paint, &vulkan_context); + yakui_vulkan.transfer(paint, &vulkan_context, vulkan_test.draw_command_buffer); } vulkan_test.render_begin(index); unsafe { yakui_vulkan.paint( paint, &vulkan_context, + vulkan_test.draw_command_buffer, vulkan_test.swapchain_info.surface_resolution, ); } diff --git a/crates/yakui-vulkan/src/lib.rs b/crates/yakui-vulkan/src/lib.rs index 25ec8841..8192c542 100644 --- a/crates/yakui-vulkan/src/lib.rs +++ b/crates/yakui-vulkan/src/lib.rs @@ -360,9 +360,10 @@ impl YakuiVulkan { &mut self, paint: &yakui_core::paint::PaintDom, vulkan_context: &VulkanContext, + cmd: vk::CommandBuffer, ) { self.update_textures(vulkan_context, paint); - self.uploads.record(vulkan_context); + self.uploads.record(vulkan_context, cmd); } /// Call when commands recorded by zero or more successive `transfer` calls have been submitted to @@ -391,6 +392,7 @@ impl YakuiVulkan { &mut self, paint: &yakui_core::paint::PaintDom, vulkan_context: &VulkanContext, + cmd: vk::CommandBuffer, resolution: vk::Extent2D, ) { // If there's nothing to paint, well.. don't paint! @@ -401,7 +403,7 @@ impl YakuiVulkan { let draw_calls = self.build_draw_calls(vulkan_context, paint); - self.render(vulkan_context, resolution, &draw_calls); + self.render(vulkan_context, resolution, cmd, &draw_calls); } /// Render the draw calls we've built up @@ -409,10 +411,10 @@ impl YakuiVulkan { &self, vulkan_context: &VulkanContext, resolution: vk::Extent2D, + command_buffer: vk::CommandBuffer, draw_calls: &[DrawCall], ) { let device = vulkan_context.device; - let command_buffer = vulkan_context.command_buffer; let viewports = [vk::Viewport { x: 0.0, diff --git a/crates/yakui-vulkan/src/vulkan_context.rs b/crates/yakui-vulkan/src/vulkan_context.rs index a2a8b1a4..66c47520 100644 --- a/crates/yakui-vulkan/src/vulkan_context.rs +++ b/crates/yakui-vulkan/src/vulkan_context.rs @@ -16,8 +16,6 @@ pub struct VulkanContext<'a> { pub device: &'a ash::Device, /// A queue that can call render and transfer commands pub queue: vk::Queue, - /// The command buffer that you'll ultimately submit to be presented/rendered - pub command_buffer: vk::CommandBuffer, /// Memory properties used for [`crate::YakuiVulkan`]'s allocation commands pub memory_properties: vk::PhysicalDeviceMemoryProperties, } @@ -27,13 +25,11 @@ impl<'a> VulkanContext<'a> { pub fn new( device: &'a ash::Device, queue: vk::Queue, - command_buffer: vk::CommandBuffer, memory_properties: vk::PhysicalDeviceMemoryProperties, ) -> Self { Self { device, queue, - command_buffer, memory_properties, } } diff --git a/crates/yakui-vulkan/src/vulkan_texture.rs b/crates/yakui-vulkan/src/vulkan_texture.rs index f3d26546..a6e2aaf2 100644 --- a/crates/yakui-vulkan/src/vulkan_texture.rs +++ b/crates/yakui-vulkan/src/vulkan_texture.rs @@ -245,9 +245,8 @@ impl UploadQueue { }); } - pub unsafe fn record(&mut self, vulkan_context: &VulkanContext) { + pub unsafe fn record(&mut self, vulkan_context: &VulkanContext, cmd: vk::CommandBuffer) { let device = vulkan_context.device; - let cmd = vulkan_context.command_buffer; device.cmd_pipeline_barrier( cmd, vk::PipelineStageFlags::TOP_OF_PIPE, From bcb44638ed433acd0ff2877723d53a7900f34c31 Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Sun, 22 Oct 2023 15:05:33 -0700 Subject: [PATCH 14/16] Leave initial scissor/viewport state to the user These are usually already set. --- crates/yakui-vulkan/examples/demo.rs | 14 ++++++++++++++ crates/yakui-vulkan/src/lib.rs | 13 +------------ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/crates/yakui-vulkan/examples/demo.rs b/crates/yakui-vulkan/examples/demo.rs index 511c3458..9a3b2452 100644 --- a/crates/yakui-vulkan/examples/demo.rs +++ b/crates/yakui-vulkan/examples/demo.rs @@ -626,6 +626,14 @@ impl VulkanTest { }, }, ]; + let viewports = [vk::Viewport { + x: 0.0, + y: 0.0, + width: self.swapchain_info.surface_resolution.width as f32, + height: self.swapchain_info.surface_resolution.height as f32, + min_depth: 0.0, + max_depth: 1.0, + }]; let render_pass_begin_info = vk::RenderPassBeginInfo::builder() .render_pass(self.render_pass) @@ -638,6 +646,12 @@ impl VulkanTest { &render_pass_begin_info, vk::SubpassContents::INLINE, ); + device.cmd_set_viewport(self.draw_command_buffer, 0, &viewports); + device.cmd_set_scissor( + self.draw_command_buffer, + 0, + &[self.swapchain_info.surface_resolution.into()], + ); } present_index } diff --git a/crates/yakui-vulkan/src/lib.rs b/crates/yakui-vulkan/src/lib.rs index 8192c542..002c5e6d 100644 --- a/crates/yakui-vulkan/src/lib.rs +++ b/crates/yakui-vulkan/src/lib.rs @@ -388,6 +388,7 @@ impl YakuiVulkan { /// /// ## Safety /// - `vulkan_context` must be the same as the one used to create this [`YakuiVulkan`] instance + /// - `cmd` must be in rendering state, with viewport and scissor dynamic states set. pub unsafe fn paint( &mut self, paint: &yakui_core::paint::PaintDom, @@ -416,15 +417,6 @@ impl YakuiVulkan { ) { let device = vulkan_context.device; - let viewports = [vk::Viewport { - x: 0.0, - y: 0.0, - width: resolution.width as f32, - height: resolution.height as f32, - min_depth: 0.0, - max_depth: 1.0, - }]; - let surface_size = UVec2::new(resolution.width, resolution.height); unsafe { @@ -433,11 +425,8 @@ impl YakuiVulkan { vk::PipelineBindPoint::GRAPHICS, self.graphics_pipeline, ); - device.cmd_set_viewport(command_buffer, 0, &viewports); let default_scissor = [resolution.into()]; - // We set the scissor first here as it's against the spec not to do so. - device.cmd_set_scissor(command_buffer, 0, &default_scissor); device.cmd_bind_vertex_buffers(command_buffer, 0, &[self.vertex_buffer.handle], &[0]); device.cmd_bind_index_buffer( command_buffer, From 2685e3aaba997ae66c292b9b0e257cbd67b7b183 Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Sun, 22 Oct 2023 15:53:52 -0700 Subject: [PATCH 15/16] Update winit --- crates/yakui-vulkan/Cargo.toml | 2 +- crates/yakui-vulkan/examples/demo.rs | 188 +++++++++++++-------------- 2 files changed, 94 insertions(+), 96 deletions(-) diff --git a/crates/yakui-vulkan/Cargo.toml b/crates/yakui-vulkan/Cargo.toml index a8603ab6..f05fc58e 100644 --- a/crates/yakui-vulkan/Cargo.toml +++ b/crates/yakui-vulkan/Cargo.toml @@ -18,4 +18,4 @@ thunderdome = "0.6.0" ash-window = "0.12.0" image = "0.24.5" raw-window-handle = "0.5.0" -winit = "0.27.5" +winit = { version = "0.29.2", features = ["rwh_05"] } diff --git a/crates/yakui-vulkan/examples/demo.rs b/crates/yakui-vulkan/examples/demo.rs index 9a3b2452..c68e04bd 100644 --- a/crates/yakui-vulkan/examples/demo.rs +++ b/crates/yakui-vulkan/examples/demo.rs @@ -3,9 +3,9 @@ use yakui::geometry::{UVec2, Vec2}; use yakui_vulkan::*; use winit::{ - event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, + event::{ElementState, Event, KeyEvent, WindowEvent}, event_loop::ControlFlow, - platform::run_return::EventLoopExtRunReturn, + keyboard::{KeyCode, PhysicalKey}, }; use yakui::image; @@ -30,7 +30,7 @@ fn main() { use winit::dpi::PhysicalSize; let (width, height) = (500, 500); - let (mut event_loop, window) = init_winit(width, height); + let (event_loop, window) = init_winit(width, height); let mut vulkan_test = VulkanTest::new(width, height, &window); let mut yak = yakui::Yakui::new(); @@ -67,105 +67,103 @@ fn main() { let mut winit_initializing = true; - event_loop.run_return(|event, _, control_flow| { - *control_flow = ControlFlow::Poll; - match event { - Event::WindowEvent { - event: - WindowEvent::CloseRequested - | WindowEvent::KeyboardInput { - input: - KeyboardInput { - state: ElementState::Pressed, - virtual_keycode: Some(VirtualKeyCode::Escape), - .. - }, - .. - }, - .. - } => *control_flow = ControlFlow::Exit, - - Event::NewEvents(cause) => { - if cause == winit::event::StartCause::Init { - winit_initializing = true; - } else { - winit_initializing = false; - } + event_loop.set_control_flow(ControlFlow::Poll); + _ = event_loop.run(|event, elwt| match event { + Event::WindowEvent { + event: + WindowEvent::CloseRequested + | WindowEvent::KeyboardInput { + event: + KeyEvent { + state: ElementState::Pressed, + physical_key: PhysicalKey::Code(KeyCode::Escape), + .. + }, + .. + }, + .. + } => elwt.exit(), + + Event::NewEvents(cause) => { + if cause == winit::event::StartCause::Init { + winit_initializing = true; + } else { + winit_initializing = false; } + } - Event::MainEventsCleared => { - let vulkan_context = VulkanContext::new( - &vulkan_test.device, - vulkan_test.present_queue, - vulkan_test.device_memory_properties, - ); + Event::AboutToWait => { + let vulkan_context = VulkanContext::new( + &vulkan_test.device, + vulkan_test.present_queue, + vulkan_test.device_memory_properties, + ); - yak.start(); - gui(&gui_state); - yak.finish(); + yak.start(); + gui(&gui_state); + yak.finish(); - let paint = yak.paint(); + let paint = yak.paint(); - let index = vulkan_test.cmd_begin(); - unsafe { - yakui_vulkan.transfers_finished(&vulkan_context); - yakui_vulkan.transfer(paint, &vulkan_context, vulkan_test.draw_command_buffer); - } - vulkan_test.render_begin(index); - unsafe { - yakui_vulkan.paint( - paint, - &vulkan_context, - vulkan_test.draw_command_buffer, - vulkan_test.swapchain_info.surface_resolution, - ); - } - vulkan_test.render_end(index); - yakui_vulkan.transfers_submitted(); + let index = vulkan_test.cmd_begin(); + unsafe { + yakui_vulkan.transfers_finished(&vulkan_context); + yakui_vulkan.transfer(paint, &vulkan_context, vulkan_test.draw_command_buffer); } - Event::WindowEvent { - event: WindowEvent::Resized(size), - .. - } => { - if winit_initializing { - println!("Ignoring resize during init!"); - } else { - let PhysicalSize { width, height } = size; - vulkan_test.resized(width, height); - yak.set_surface_size([width as f32, height as f32].into()); - yak.set_unscaled_viewport(yakui_core::geometry::Rect::from_pos_size( - Default::default(), - [width as f32, height as f32].into(), - )); - } + vulkan_test.render_begin(index); + unsafe { + yakui_vulkan.paint( + paint, + &vulkan_context, + vulkan_test.draw_command_buffer, + vulkan_test.swapchain_info.surface_resolution, + ); } - Event::WindowEvent { - event: WindowEvent::ScaleFactorChanged { scale_factor, .. }, - .. - } => yak.set_scale_factor(scale_factor as _), - Event::WindowEvent { - event: - WindowEvent::KeyboardInput { - input: - KeyboardInput { - state: ElementState::Released, - virtual_keycode, - .. - }, - .. - }, - .. - } => match virtual_keycode { - Some(VirtualKeyCode::A) => { - gui_state.which_image = match &gui_state.which_image { - WhichImage::Monkey => WhichImage::Dog, - WhichImage::Dog => WhichImage::Monkey, - } - } - _ => {} - }, - _ => (), + vulkan_test.render_end(index); + yakui_vulkan.transfers_submitted(); } + Event::WindowEvent { + event: WindowEvent::Resized(size), + .. + } => { + if winit_initializing { + println!("Ignoring resize during init!"); + } else { + let PhysicalSize { width, height } = size; + vulkan_test.resized(width, height); + yak.set_surface_size([width as f32, height as f32].into()); + yak.set_unscaled_viewport(yakui_core::geometry::Rect::from_pos_size( + Default::default(), + [width as f32, height as f32].into(), + )); + } + } + Event::WindowEvent { + event: WindowEvent::ScaleFactorChanged { scale_factor, .. }, + .. + } => yak.set_scale_factor(scale_factor as _), + Event::WindowEvent { + event: + WindowEvent::KeyboardInput { + event: + KeyEvent { + state: ElementState::Released, + physical_key, + .. + }, + .. + }, + .. + } => match physical_key { + PhysicalKey::Code(KeyCode::KeyA) => { + gui_state.which_image = match &gui_state.which_image { + WhichImage::Monkey => WhichImage::Dog, + WhichImage::Dog => WhichImage::Monkey, + } + } + _ => {} + }, + _ => (), }); unsafe { @@ -700,7 +698,7 @@ fn init_winit( ) -> (winit::event_loop::EventLoop<()>, winit::window::Window) { use winit::{event_loop::EventLoopBuilder, window::WindowBuilder}; - let event_loop = EventLoopBuilder::new().build(); + let event_loop = EventLoopBuilder::new().build().unwrap(); let window = WindowBuilder::new() .with_title("Yakui Vulkan - Test") From a47dff5086681d8a39986a93e824fc771c44afe5 Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Sun, 22 Oct 2023 17:17:05 -0700 Subject: [PATCH 16/16] Don't destroy textures while they might still be in use --- crates/yakui-vulkan/src/lib.rs | 4 +++- crates/yakui-vulkan/src/vulkan_texture.rs | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/crates/yakui-vulkan/src/lib.rs b/crates/yakui-vulkan/src/lib.rs index 002c5e6d..7126ec0b 100644 --- a/crates/yakui-vulkan/src/lib.rs +++ b/crates/yakui-vulkan/src/lib.rs @@ -599,7 +599,9 @@ impl YakuiVulkan { TextureChange::Removed => { if let Some(removed) = self.yakui_managed_textures.remove(&id) { - unsafe { removed.cleanup(vulkan_context.device) }; + unsafe { + self.uploads.dispose(removed); + } } } diff --git a/crates/yakui-vulkan/src/vulkan_texture.rs b/crates/yakui-vulkan/src/vulkan_texture.rs index a6e2aaf2..7c596c96 100644 --- a/crates/yakui-vulkan/src/vulkan_texture.rs +++ b/crates/yakui-vulkan/src/vulkan_texture.rs @@ -208,6 +208,12 @@ impl UploadQueue { finished.cleanup(vulkan_context.device); } + /// Schedule `texture` to be disposed of after the previous phase completes + pub unsafe fn dispose(&mut self, texture: VulkanTexture) { + let phase = self.in_flight.back_mut().unwrap_or(&mut self.phase); + phase.graveyard.push(texture); + } + unsafe fn push( &mut self, vulkan_context: &VulkanContext, @@ -300,6 +306,7 @@ impl UploadQueue { #[derive(Default)] struct UploadPhase { buffers: Vec<(Buffer, usize)>, + graveyard: Vec, } impl UploadPhase { @@ -307,6 +314,9 @@ impl UploadPhase { for (buffer, _) in &self.buffers { buffer.cleanup(device); } + for texture in &self.graveyard { + texture.cleanup(device); + } } unsafe fn push(&mut self, vulkan_context: &VulkanContext, data: &[u8]) -> (vk::Buffer, usize) {