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 new file mode 100644 index 00000000..c68e04bd --- /dev/null +++ b/crates/yakui-vulkan/examples/demo.rs @@ -0,0 +1,856 @@ +use ash::vk; +use yakui::geometry::{UVec2, Vec2}; +use yakui_vulkan::*; + +use winit::{ + event::{ElementState, Event, KeyEvent, WindowEvent}, + event_loop::ControlFlow, + keyboard::{KeyCode, PhysicalKey}, +}; +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 (event_loop, window) = init_winit(width, height); + let mut vulkan_test = VulkanTest::new(width, height, &window); + + 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.device_memory_properties, + ); + 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 { + 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.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::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(); + + 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(); + } + 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 { + 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 { + _entry: ash::Entry, + device: ash::Device, + physical_device: vk::PhysicalDevice, + instance: ash::Instance, + surface_loader: ash::extensions::khr::Surface, + device_memory_properties: vk::PhysicalDeviceMemoryProperties, + + present_queue: vk::Queue, + + surface: vk::SurfaceKHR, + swapchain_info: SwapchainInfo, + + swapchain: vk::SwapchainKHR, + present_image_views: Vec, + + render_pass: vk::RenderPass, + framebuffers: Vec, + + command_pool: vk::CommandPool, + draw_command_buffer: vk::CommandBuffer, + + present_complete_semaphore: vk::Semaphore, + rendering_complete_semaphore: vk::Semaphore, + + draw_commands_reuse_fence: vk::Fence, + 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 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); + + 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, + physical_device, + present_queue, + _entry: entry, + instance, + surface_loader, + swapchain_info, + device_memory_properties, + surface, + swapchain, + present_image_views, + render_pass, + framebuffers, + 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(); + 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( + &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); + } + self.swapchain_info + .swapchain_loader + .destroy_swapchain(swapchain, None); + } + + pub fn cmd_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_begin(&self, present_index: u32) -> u32 { + let device = &self.device; + unsafe { + 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 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) + .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, + ); + 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 + } + + 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]; + 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().unwrap(); + + 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_render_pass(self.render_pass, None); + 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, + } + } +} + +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/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 +} diff --git a/crates/yakui-vulkan/src/buffer.rs b/crates/yakui-vulkan/src/buffer.rs index 7cd25291..99449147 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}; @@ -22,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); @@ -55,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 @@ -68,21 +64,33 @@ impl Buffer { memory, _usage: usage, size, - len: initial_data.len(), + len: elements, ptr, } } - pub unsafe fn overwrite(&mut self, _vulkan_context: &VulkanContext, new_data: &[T]) { + 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 new_data_size > self.size as usize { + if std::mem::size_of::() * offset + new_data_size > self.size as usize { 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.len = new_data.len() + self.ptr + .as_ptr() + .add(offset) + .copy_from_nonoverlapping(new_data.as_ptr(), new_data.len()); } /// ## Safety @@ -92,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 73136529..7126ec0b 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; @@ -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}; @@ -40,20 +40,11 @@ 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) - render_surface: RenderSurface, /// The pipeline layout used to draw pipeline_layout: vk::PipelineLayout, /// The graphics pipeline used to draw @@ -70,19 +61,17 @@ pub struct YakuiVulkan { user_textures: thunderdome::Arena, /// A wrapper around descriptor set functionality descriptors: Descriptors, + uploads: UploadQueue, } -#[derive(Clone)] -/// The surface for yakui to draw on. Currently only supports present surfaces (ie. the swapchain) -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, +/// 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)] @@ -155,54 +144,15 @@ 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 /// - 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, options: Options) -> Self { 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, &[]); @@ -297,18 +247,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, @@ -354,7 +295,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) @@ -364,8 +305,22 @@ impl YakuiVulkan { .depth_stencil_state(&depth_state_info) .color_blend_state(&color_blend_state) .dynamic_state(&dynamic_state_info) - .layout(pipeline_layout) - .render_pass(render_pass); + .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(options.render_pass); + } let graphics_pipelines = unsafe { device @@ -385,10 +340,7 @@ impl YakuiVulkan { } Self { - render_pass, descriptors, - framebuffers, - render_surface, pipeline_layout, graphics_pipeline, index_buffer, @@ -396,24 +348,54 @@ impl YakuiVulkan { user_textures: Default::default(), yakui_managed_textures: Default::default(), initial_textures_synced: false, + uploads: UploadQueue::new(), } } - /// Paint the yakui GUI using the provided [`VulkanContext`] + /// 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 - /// - `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( + pub unsafe fn transfer( &mut self, - yak: &mut yakui_core::Yakui, + paint: &yakui_core::paint::PaintDom, vulkan_context: &VulkanContext, - present_index: u32, + cmd: vk::CommandBuffer, ) { - let paint = yak.paint(); - self.update_textures(vulkan_context, paint); + self.uploads.record(vulkan_context, cmd); + } + + /// 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 + /// - `cmd` must be in rendering state, with viewport and scissor dynamic states set. + pub unsafe fn paint( + &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! let layers = paint.layers(); if layers.iter().all(|layer| layer.calls.is_empty()) { @@ -422,68 +404,29 @@ impl YakuiVulkan { let draw_calls = self.build_draw_calls(vulkan_context, paint); - self.render(vulkan_context, present_index, &draw_calls); + self.render(vulkan_context, resolution, cmd, &draw_calls); } /// Render the draw calls we've built up fn render( &self, vulkan_context: &VulkanContext, - framebuffer_index: u32, + resolution: vk::Extent2D, + command_buffer: vk::CommandBuffer, 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, - width: surface.resolution.width as f32, - height: surface.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_begin_render_pass( - command_buffer, - &render_pass_begin_info, - vk::SubpassContents::INLINE, - ); device.cmd_bind_pipeline( command_buffer, vk::PipelineBindPoint::GRAPHICS, 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); device.cmd_bind_vertex_buffers(command_buffer, 0, &[self.vertex_buffer.handle], &[0]); device.cmd_bind_index_buffer( command_buffer, @@ -531,7 +474,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); @@ -563,7 +506,6 @@ impl YakuiVulkan { 1, ); } - device.cmd_end_render_pass(command_buffer); } } @@ -577,8 +519,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()) } @@ -612,8 +558,7 @@ 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); + self.uploads.cleanup(device); } /// Provides access to the descriptors used by `YakuiVulkan` to manage textures. @@ -631,6 +576,7 @@ impl YakuiVulkan { vulkan_context, &mut self.descriptors, texture, + &mut self.uploads, ); self.yakui_managed_textures.insert(id, texture); } @@ -646,13 +592,16 @@ impl YakuiVulkan { vulkan_context, &mut self.descriptors, texture, + &mut self.uploads, ); self.yakui_managed_textures.insert(id, texture); } TextureChange::Removed => { if let Some(removed) = self.yakui_managed_textures.remove(&id) { - unsafe { removed.cleanup(vulkan_context.device) }; + unsafe { + self.uploads.dispose(removed); + } } } @@ -665,6 +614,7 @@ impl YakuiVulkan { vulkan_context, &mut self.descriptors, new, + &mut self.uploads, ); self.yakui_managed_textures.insert(id, texture); } @@ -721,296 +671,10 @@ 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 } - - /// 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); - 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 -} - -#[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, - } - } -} diff --git a/crates/yakui-vulkan/src/vulkan_context.rs b/crates/yakui-vulkan/src/vulkan_context.rs index 64dd9e22..66c47520 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`] @@ -16,9 +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 draw_command_buffer: vk::CommandBuffer, - command_pool: vk::CommandPool, /// Memory properties used for [`crate::YakuiVulkan`]'s allocation commands pub memory_properties: vk::PhysicalDeviceMemoryProperties, } @@ -28,26 +25,20 @@ impl<'a> VulkanContext<'a> { pub fn new( device: &'a ash::Device, queue: vk::Queue, - draw_command_buffer: vk::CommandBuffer, - command_pool: vk::CommandPool, memory_properties: vk::PhysicalDeviceMemoryProperties, ) -> Self { Self { device, queue, - draw_command_buffer, - command_pool, memory_properties, } } 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 @@ -78,116 +69,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..7c596c96 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,164 @@ 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); + } + + /// 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, + 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, cmd: vk::CommandBuffer) { + let device = vulkan_context.device; + 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)>, + graveyard: Vec, +} + +impl UploadPhase { + unsafe fn cleanup(&mut self, device: &ash::Device) { + 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) { + 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) + } +}