diff --git a/.gitignore b/.gitignore index 9ce42ca..8fd8adb 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ #/target /Cargo.lock +.zed +.vscode diff --git a/Cargo.toml b/Cargo.toml index a47050e..d880fdf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ ash-window = "0.13.0" bytemuck = "1.13.0" glam = "0.22.0" log = "0.4.17" +offset-allocator = "0.2.0" raw-window-handle = "0.6.2" thunderdome = "0.6.0" uds_windows = "1.0.2" diff --git a/examples/remote_client.rs b/examples/remote_client.rs deleted file mode 100644 index 69c816c..0000000 --- a/examples/remote_client.rs +++ /dev/null @@ -1,376 +0,0 @@ -use lazy_vulkan::vulkan_context::VulkanContext; -use lazy_vulkan::{ - create_swapchain_image_views, DrawCall, SwapchainInfo, Vertex, Workflow, NO_TEXTURE_ID, -}; -use std::io::{Read, Write}; - -#[cfg(not(target_os = "windows"))] -use std::os::unix::net::UnixStream; -use std::sync::Mutex; -#[cfg(target_os = "windows")] -use uds_windows::UnixStream; - -use ash::vk; -use log::{debug, error, info}; - -/// Compile your own damn shaders! LazyVulkan is just as lazy as you are! -static FRAGMENT_SHADER: &'static [u8] = include_bytes!("shaders/triangle.frag.spv"); -static VERTEX_SHADER: &'static [u8] = include_bytes!("shaders/triangle.vert.spv"); - -#[derive(Debug, Clone)] -pub enum Color { - Blue, - Red, - Green, -} - -impl Color { - fn to_rgba(&self) -> [f32; 4] { - match self { - Color::Blue => [0., 0., 1., 0.], - Color::Red => [1., 0., 0., 0.], - Color::Green => [0., 1., 0., 0.], - } - } -} - -static mut COLOUR: Mutex = Mutex::new(Color::Blue); -static UNIX_SOCKET_PATH: &'_ str = "lazy_vulkan.socket"; - -pub fn main() -> std::io::Result<()> { - env_logger::builder() - .filter_level(log::LevelFilter::Info) - .init(); - - let mut vertices = [ - Vertex::new([1.0, 1.0, 0.0, 1.0], [1.0, 0.0, 0.0, 0.0], [0.0, 0.0]), - Vertex::new([-1.0, 1.0, 0.0, 1.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0]), - Vertex::new([0.0, -1.0, 0.0, 1.0], [0.0, 0.0, 1.0, 0.0], [0.0, 0.0]), - ]; - - // Your own index type?! What are you going to use, `u16`? - let indices = [0, 1, 2]; - - // Alright, let's build some stuff - let mut vulkan_context = lazy_vulkan::vulkan_context::VulkanContext::new(); - let builder = lazy_vulkan::LazyVulkan::builder() - .fragment_shader(FRAGMENT_SHADER) - .vertex_shader(VERTEX_SHADER) - .initial_indices(&indices) - .initial_vertices(&vertices); - - info!("Conecting to server at {UNIX_SOCKET_PATH}.."); - let mut stream = UnixStream::connect(UNIX_SOCKET_PATH)?; - info!("Connected!"); - - let mut buf: [u8; 1024] = [0; 1024]; - let swapchain_info = get_swapchain_info(&mut stream, &mut buf); - info!("Swapchain info is {swapchain_info:?}!"); - let swapchain_images = - get_swapchain_images(&mut stream, &vulkan_context, &swapchain_info, &mut buf); - let semaphores = get_semaphores( - &mut stream, - &vulkan_context, - swapchain_info.image_count, - &mut buf, - ); - info!("Images are: {swapchain_images:?}"); - let image_views = create_swapchain_image_views( - &swapchain_images, - swapchain_info.format, - &vulkan_context.device, - ); - - let render_surface = lazy_vulkan::lazy_renderer::RenderSurface { - resolution: swapchain_info.resolution, - format: swapchain_info.format, - image_views, - }; - - let mut renderer = - lazy_vulkan::lazy_renderer::LazyRenderer::new(&vulkan_context, render_surface, &builder); - - let draw_calls = [DrawCall::new(0, 3, NO_TEXTURE_ID, Workflow::Main)]; - let fences = create_fences(&vulkan_context, swapchain_info.image_count); - let command_buffers = create_command_buffers(&vulkan_context, swapchain_info.image_count); - - std::thread::spawn(|| { - let mut buffer = String::new(); - loop { - info!("[CLIENT] Waiting for input.."); - if let Ok(_) = std::io::stdin().read_line(&mut buffer) { - let new_color = match buffer.as_str().trim() { - "b" => Some(Color::Blue), - "g" => Some(Color::Green), - "r" => Some(Color::Red), - _ => { - error!( - "[CLIENT] Invalid input {buffer:?}. Press r, g or b then hit enter." - ); - None - } - }; - - if let Some(new_color) = new_color { - info!("[CLIENT] Triangle is now {new_color:?}"); - - // NOTE: There are *zero* fucks given here. - #[allow(static_mut_refs)] - unsafe { - *COLOUR.get_mut().unwrap() = new_color - } - } - - buffer.clear(); - } - std::thread::sleep(std::time::Duration::from_millis(100)); - } - }); - - loop { - let swapchain_image_index = get_swapchain_image_index(&mut stream, &mut buf); - let fence = fences[swapchain_image_index as usize]; - let command_buffer = command_buffers[swapchain_image_index as usize]; - let semaphore = semaphores[swapchain_image_index as usize]; - - vulkan_context.draw_command_buffer = command_buffer; - update_colour(&mut vertices, &vulkan_context, &mut renderer); - begin_frame(&vulkan_context, fence, command_buffer); - renderer.render(&vulkan_context, swapchain_image_index, &draw_calls); - end_frame(&vulkan_context, fence, command_buffer); - fake_submit(&vulkan_context, semaphore); - send_render_complete(&mut stream); - } -} - -fn fake_submit(vulkan_context: &VulkanContext, semaphore: vk::Semaphore) { - unsafe { - vulkan_context - .device - .queue_submit( - vulkan_context.queue, - std::slice::from_ref( - &vk::SubmitInfo::default().signal_semaphores(std::slice::from_ref(&semaphore)), - ), - vk::Fence::null(), - ) - .unwrap() - } -} - -fn get_semaphores( - stream: &mut UnixStream, - vulkan_context: &VulkanContext, - image_count: u32, - buf: &mut [u8], -) -> Vec { - let device = &vulkan_context.device; - stream.write(&mut [1]).unwrap(); - let len = stream.read(buf).unwrap(); - debug!("Read {len} bytes"); - let handles: &[vk::HANDLE] = - unsafe { std::slice::from_raw_parts(buf.as_ptr().cast(), image_count as _) }; - debug!("Got handle {handles:?}"); - let external_semaphore = ash::khr::external_semaphore_win32::Device::new( - &vulkan_context.instance, - &vulkan_context.device, - ); - let handle_type = vk::ExternalSemaphoreHandleTypeFlags::OPAQUE_WIN32_KMT; - - handles - .iter() - .map(|h| unsafe { - let mut external_semaphore_info = - vk::ExportSemaphoreCreateInfo::default().handle_types(handle_type); - let semaphore = device - .create_semaphore( - &vk::SemaphoreCreateInfo::default().push_next(&mut external_semaphore_info), - None, - ) - .unwrap(); - - external_semaphore - .import_semaphore_win32_handle( - &vk::ImportSemaphoreWin32HandleInfoKHR::default() - .handle(*h) - .semaphore(semaphore) - .handle_type(handle_type), - ) - .unwrap(); - - semaphore - }) - .collect() -} - -fn update_colour( - vertices: &mut [Vertex], - vulkan_context: &VulkanContext, - renderer: &mut lazy_vulkan::LazyRenderer, -) { - #[allow(static_mut_refs)] - if let Ok(colour) = unsafe { COLOUR.get_mut() } { - for v in vertices.iter_mut() { - v.colour = colour.to_rgba().into() - } - - unsafe { - renderer.vertex_buffer.overwrite(vulkan_context, &vertices); - } - } -} - -fn create_command_buffers( - vulkan_context: &VulkanContext, - image_count: u32, -) -> Vec { - unsafe { - vulkan_context - .device - .allocate_command_buffers( - &vk::CommandBufferAllocateInfo::default() - .command_pool(vulkan_context.command_pool) - .command_buffer_count(image_count), - ) - .unwrap() - } -} - -fn create_fences(vulkan_context: &VulkanContext, image_count: u32) -> Vec { - (0..image_count) - .map(|_| unsafe { - vulkan_context - .device - .create_fence( - &vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED), - None, - ) - .unwrap() - }) - .collect() -} - -fn end_frame(vulkan_context: &VulkanContext, fence: vk::Fence, command_buffer: vk::CommandBuffer) { - let device = &vulkan_context.device; - unsafe { - device.end_command_buffer(command_buffer).unwrap(); - - device - .queue_submit( - vulkan_context.queue, - &[ - vk::SubmitInfo::default() - .command_buffers(std::slice::from_ref(&command_buffer)), - ], - fence, - ) - .unwrap(); - } -} - -fn begin_frame( - vulkan_context: &VulkanContext, - fence: vk::Fence, - command_buffer: vk::CommandBuffer, -) { - let device = &vulkan_context.device; - unsafe { - device - .wait_for_fences(std::slice::from_ref(&fence), true, std::u64::MAX) - .unwrap(); - device.reset_fences(std::slice::from_ref(&fence)).unwrap(); - device - .reset_command_buffer( - command_buffer, - vk::CommandBufferResetFlags::RELEASE_RESOURCES, - ) - .unwrap(); - device - .begin_command_buffer( - command_buffer, - &vk::CommandBufferBeginInfo::default() - .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT), - ) - .unwrap(); - } -} - -fn send_render_complete(stream: &mut UnixStream) { - stream.write(&mut [3]).unwrap(); -} - -fn get_swapchain_image_index(stream: &mut UnixStream, buf: &mut [u8]) -> u32 { - stream.write(&mut [2]).unwrap(); - stream.read(buf).unwrap(); - buf[0] as _ -} - -fn get_swapchain_images( - stream: &mut UnixStream, - vulkan: &VulkanContext, - swapchain_info: &SwapchainInfo, - buf: &mut [u8; 1024], -) -> Vec { - let device = &vulkan.device; - stream.write(&mut [1]).unwrap(); - let len = stream.read(buf).unwrap(); - debug!("Read {len} bytes"); - let handles: &[vk::HANDLE] = - unsafe { std::slice::from_raw_parts(buf.as_ptr().cast(), swapchain_info.image_count as _) }; - debug!("Got handle {handles:?}"); - - handles - .into_iter() - .map(|handle| unsafe { - let handle_type = vk::ExternalMemoryHandleTypeFlags::OPAQUE_WIN32_KMT; - - let mut external_memory_image_create_info = - vk::ExternalMemoryImageCreateInfo::default().handle_types(handle_type); - let image = device - .create_image( - &vk::ImageCreateInfo { - image_type: vk::ImageType::TYPE_2D, - format: swapchain_info.format, - extent: swapchain_info.resolution.into(), - mip_levels: 1, - array_layers: 1, - samples: vk::SampleCountFlags::TYPE_1, - tiling: vk::ImageTiling::OPTIMAL, - usage: vk::ImageUsageFlags::COLOR_ATTACHMENT, - sharing_mode: vk::SharingMode::EXCLUSIVE, - p_next: &mut external_memory_image_create_info as *mut _ as *mut _, - ..Default::default() - }, - None, - ) - .unwrap(); - let requirements = device.get_image_memory_requirements(image); - let mut external_memory_allocate_info = vk::ImportMemoryWin32HandleInfoKHR::default() - .handle(*handle) - .handle_type(handle_type); - let memory = vulkan - .device - .allocate_memory( - &vk::MemoryAllocateInfo::default() - .allocation_size(requirements.size) - .push_next(&mut external_memory_allocate_info), - None, - ) - .unwrap(); - device.bind_image_memory(image, memory, 0).unwrap(); - image - }) - .collect() -} - -fn get_swapchain_info(stream: &mut UnixStream, buf: &mut [u8]) -> SwapchainInfo { - stream.write(&mut [0]).unwrap(); - let len = stream.read(buf).unwrap(); - info!("Read {len} bytes"); - from_bytes(&buf[..len]) -} - -// Pure, undiluted evil -fn from_bytes(b: &[u8]) -> T { - unsafe { std::ptr::read(b.as_ptr().cast::()) }.clone() -} diff --git a/examples/remote_host.rs b/examples/remote_host.rs deleted file mode 100644 index b95137a..0000000 --- a/examples/remote_host.rs +++ /dev/null @@ -1,372 +0,0 @@ -use ash::vk; -use lazy_vulkan::{ - find_memorytype_index, vulkan_texture::VulkanTexture, DrawCall, LazyRenderer, LazyVulkan, - SwapchainInfo, Vertex, -}; -use log::{debug, info}; -use std::io::{Read, Write}; -#[cfg(not(target_os = "windows"))] -use std::os::unix::net::{UnixListener, UnixStream}; -#[cfg(target_os = "windows")] -use uds_windows::{UnixListener, UnixStream}; -use winit::{ - application::ApplicationHandler, - event::{ElementState, KeyEvent, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - keyboard::{KeyCode, PhysicalKey}, -}; - -/// Compile your own damn shaders! LazyVulkan is just as lazy as you are! -static FRAGMENT_SHADER: &'_ [u8] = include_bytes!("shaders/triangle.frag.spv"); -static VERTEX_SHADER: &'_ [u8] = include_bytes!("shaders/triangle.vert.spv"); -const SWAPCHAIN_FORMAT: vk::Format = vk::Format::R8G8B8A8_UNORM; -static UNIX_SOCKET_PATH: &'_ str = "lazy_vulkan.socket"; - -// Fucking winit -struct App { - lazy_vulkan: Option, - lazy_renderer: Option, - textures: Vec, - semaphores: Vec, - stream: UnixStream, - buf: [u8; 1024], -} - -impl App { - fn new() -> Self { - if std::fs::remove_file(UNIX_SOCKET_PATH).is_ok() { - debug!("Removed pre-existing unix socket at {UNIX_SOCKET_PATH}"); - } - // Hello client? Are you there? - let listener = UnixListener::bind(UNIX_SOCKET_PATH).unwrap(); - info!("Listening on {UNIX_SOCKET_PATH} - waiting for client.."); - - // Bonjour, monsieur client! - let (stream, _) = listener.accept().unwrap(); - info!("Client connected!"); - let buf = [0; 1024]; - - Self { - lazy_vulkan: None, - lazy_renderer: None, - stream, - textures: Default::default(), - semaphores: Default::default(), - buf, - } - } - - fn get_render_complete(&mut self) { - self.stream.read(&mut self.buf).unwrap(); - } - - fn send_swapchain_image_index(&mut self, framebuffer_index: u32) { - self.stream.read(&mut self.buf).unwrap(); - self.stream.write(&mut [framebuffer_index as u8]).unwrap(); - } - - fn send_swapchain_info(&mut self, swapchain_info: &SwapchainInfo) -> std::io::Result<()> { - self.stream.read(&mut self.buf)?; - let value = self.buf[0]; - debug!("Read {value}"); - - if value == 0 { - let write = self.stream.write(bytes_of(swapchain_info)).unwrap(); - debug!("Write {write} bytes"); - return Ok(()); - } else { - panic!("Invalid request!"); - } - } - - fn send_image_memory_handles(&mut self, image_memory_handles: &[vk::HANDLE]) { - self.stream.read(&mut self.buf).unwrap(); - let value = self.buf[0]; - debug!("Read {value}"); - - if value == 1 { - let write = self - .stream - .write(bytes_of_slice(image_memory_handles)) - .unwrap(); - debug!("Write {write} bytes"); - } else { - panic!("Invalid request!"); - } - } - - fn send_semaphore_handles(&mut self, semaphore_handles: &[vk::HANDLE]) { - self.stream.read(&mut self.buf).unwrap(); - let value = self.buf[0]; - debug!("Read {value}"); - - debug!("Sending handles: {semaphore_handles:?}"); - let write = self - .stream - .write(bytes_of_slice(semaphore_handles)) - .unwrap(); - debug!("Wrote {write} bytes"); - } -} - -impl ApplicationHandler for App { - fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { - // it's a square (you can call it a quad if you're fancy) - let vertices = [ - Vertex::new([1.0, 1.0, 0.0, 1.0], [1.0, 1.0, 1.0, 0.0], [1.0, 1.0]), // bottom right - Vertex::new([-1.0, 1.0, 0.0, 1.0], [1.0, 1.0, 1.0, 0.0], [0.0, 1.0]), // bottom left - Vertex::new([1.0, -1.0, 0.0, 1.0], [1.0, 1.0, 1.0, 0.0], [1.0, 0.0]), // top right - Vertex::new([-1.0, -1.0, 0.0, 1.0], [1.0, 1.0, 1.0, 0.0], [0.0, 0.0]), // top left - ]; - let indices = [0, 1, 2, 2, 1, 3]; - - // Alright, let's build some stuff - let (lazy_vulkan, mut lazy_renderer) = LazyVulkan::builder() - .initial_vertices(&vertices) - .initial_indices(&indices) - .fragment_shader(FRAGMENT_SHADER) - .vertex_shader(VERTEX_SHADER) - .with_present(true) - .build(event_loop); - - let swapchain_info = SwapchainInfo { - image_count: lazy_vulkan.surface.desired_image_count, - resolution: lazy_vulkan.surface.surface_resolution, - format: SWAPCHAIN_FORMAT, - }; - let (images, image_memory_handles) = - unsafe { create_render_images(lazy_vulkan.context(), &swapchain_info) }; - let (semaphores, semaphore_handles) = - unsafe { create_semaphores(lazy_vulkan.context(), swapchain_info.image_count) }; - self.textures = create_render_textures(lazy_vulkan.context(), &mut lazy_renderer, images); - - self.send_swapchain_info(&swapchain_info).unwrap(); - self.send_image_memory_handles(&image_memory_handles); - self.send_semaphore_handles(&semaphore_handles); - - self.semaphores = semaphores; - self.lazy_renderer = Some(lazy_renderer); - self.lazy_vulkan = Some(lazy_vulkan); - } - - fn about_to_wait(&mut self, _: &winit::event_loop::ActiveEventLoop) { - let framebuffer_index = self.lazy_vulkan.as_ref().unwrap().render_begin(); - self.send_swapchain_image_index(framebuffer_index); - self.get_render_complete(); - - let lazy_renderer = self.lazy_renderer.as_mut().unwrap(); - let lazy_vulkan = self.lazy_vulkan.as_mut().unwrap(); - - let texture_id = self.textures[framebuffer_index as usize].id; - lazy_renderer.render( - lazy_vulkan.context(), - framebuffer_index, - &[DrawCall::new(0, 6, texture_id, lazy_vulkan::Workflow::Main)], - ); - - let semaphore = self.semaphores[framebuffer_index as usize]; - lazy_vulkan.render_end( - framebuffer_index, - &[semaphore, lazy_vulkan.rendering_complete_semaphore], - ); - } - - fn window_event( - &mut self, - event_loop: &winit::event_loop::ActiveEventLoop, - _: winit::window::WindowId, - event: WindowEvent, - ) { - match event { - WindowEvent::CloseRequested - | WindowEvent::KeyboardInput { - event: - KeyEvent { - state: ElementState::Pressed, - physical_key: PhysicalKey::Code(KeyCode::Escape), - .. - }, - .. - } => event_loop.exit(), - - WindowEvent::Resized(size) => { - let lazy_renderer = self.lazy_renderer.as_mut().unwrap(); - let lazy_vulkan = self.lazy_vulkan.as_mut().unwrap(); - let new_render_surface = lazy_vulkan.resized(size.width, size.height); - lazy_renderer.update_surface(new_render_surface, &lazy_vulkan.context().device); - } - _ => (), - } - } -} - -pub fn main() { - env_logger::builder() - .filter_level(log::LevelFilter::Info) - .init(); - - let event_loop = EventLoop::builder().build().unwrap(); - event_loop.set_control_flow(ControlFlow::Poll); - event_loop.run_app(&mut App::new()).unwrap(); -} - -unsafe fn create_semaphores( - context: &lazy_vulkan::vulkan_context::VulkanContext, - image_count: u32, -) -> (Vec, Vec) { - let device = &context.device; - let external_semaphore = - ash::khr::external_semaphore_win32::Device::new(&context.instance, &context.device); - - let handle_type = vk::ExternalSemaphoreHandleTypeFlags::OPAQUE_WIN32_KMT; - (0..image_count) - .map(|_| { - let mut external_semaphore_info = - vk::ExportSemaphoreCreateInfo::default().handle_types(handle_type); - let semaphore = device - .create_semaphore( - &vk::SemaphoreCreateInfo::default().push_next(&mut external_semaphore_info), - None, - ) - .unwrap(); - - let handle = external_semaphore - .get_semaphore_win32_handle( - &vk::SemaphoreGetWin32HandleInfoKHR::default() - .handle_type(handle_type) - .semaphore(semaphore), - ) - .unwrap(); - - (semaphore, handle) - }) - .unzip() -} - -fn create_render_textures( - vulkan_context: &lazy_vulkan::vulkan_context::VulkanContext, - renderer: &mut LazyRenderer, - mut images: Vec, -) -> Vec { - let descriptors = &mut renderer.descriptors; - let address_mode = vk::SamplerAddressMode::REPEAT; - let filter = vk::Filter::LINEAR; - images - .drain(..) - .map(|image| { - let view = unsafe { vulkan_context.create_image_view(image, SWAPCHAIN_FORMAT) }; - let sampler = unsafe { - vulkan_context - .device - .create_sampler( - &vk::SamplerCreateInfo::default() - .address_mode_u(address_mode) - .address_mode_v(address_mode) - .address_mode_w(address_mode) - .mag_filter(filter) - .min_filter(filter), - None, - ) - .unwrap() - }; - - let id = - unsafe { descriptors.update_texture_descriptor_set(view, sampler, vulkan_context) }; - - lazy_vulkan::vulkan_texture::VulkanTexture { - image, - memory: vk::DeviceMemory::null(), // todo - sampler, - view, - id, - } - }) - .collect() -} - -unsafe fn create_render_images( - context: &lazy_vulkan::vulkan_context::VulkanContext, - swapchain_info: &SwapchainInfo, -) -> (Vec, Vec) { - let device = &context.device; - let SwapchainInfo { - resolution, - format, - image_count, - } = swapchain_info; - let handle_type = vk::ExternalMemoryHandleTypeFlags::OPAQUE_WIN32_KMT; - - (0..(*image_count)) - .map(|_| { - let mut handle_info = - vk::ExternalMemoryImageCreateInfo::default().handle_types(handle_type); - let image = device - .create_image( - &vk::ImageCreateInfo { - image_type: vk::ImageType::TYPE_2D, - format: *format, - extent: (*resolution).into(), - mip_levels: 1, - array_layers: 1, - samples: vk::SampleCountFlags::TYPE_1, - tiling: vk::ImageTiling::OPTIMAL, - usage: vk::ImageUsageFlags::TRANSFER_DST | vk::ImageUsageFlags::SAMPLED, - sharing_mode: vk::SharingMode::EXCLUSIVE, - p_next: &mut handle_info as *mut _ as *mut _, - ..Default::default() - }, - None, - ) - .unwrap(); - - let memory_requirements = device.get_image_memory_requirements(image); - let memory_index = find_memorytype_index( - &memory_requirements, - &context.memory_properties, - vk::MemoryPropertyFlags::DEVICE_LOCAL, - ) - .expect("Unable to find suitable memory type for image"); - let mut export_handle_info = - vk::ExportMemoryAllocateInfo::default().handle_types(handle_type); - let memory = context - .device - .allocate_memory( - &vk::MemoryAllocateInfo::default() - .allocation_size(memory_requirements.size) - .memory_type_index(memory_index) - .push_next(&mut export_handle_info), - None, - ) - .unwrap(); - - device.bind_image_memory(image, memory, 0).unwrap(); - - let external_memory = - ash::khr::external_memory_win32::Device::new(&context.instance, &context.device); - let handle = external_memory - .get_memory_win32_handle( - &vk::MemoryGetWin32HandleInfoKHR::default() - .handle_type(handle_type) - .memory(memory), - ) - .unwrap(); - debug!("Created handle {handle:?}"); - - (image, handle) - }) - .unzip() -} - -fn bytes_of_slice(t: &[T]) -> &[u8] { - unsafe { - let ptr = t.as_ptr(); - std::slice::from_raw_parts(ptr.cast(), std::mem::size_of::() * t.len()) - } -} - -fn bytes_of(t: &T) -> &[u8] { - unsafe { - let ptr = t as *const T; - std::slice::from_raw_parts(ptr.cast(), std::mem::size_of::()) - } -} diff --git a/examples/shaders/triangle.frag b/examples/shaders/triangle.frag index d0eaa73..2a710db 100644 --- a/examples/shaders/triangle.frag +++ b/examples/shaders/triangle.frag @@ -1,29 +1,8 @@ #version 450 -#define NO_TEXTURE 4294967295 -#define WORKFLOW_MAIN 0 -#define WORKFLOW_TEXT 1 -layout (location = 0) in vec4 in_color; -layout (location = 1) in vec2 in_uv; -layout (location = 0) out vec4 out_color; - -layout(set = 0, binding = 0) uniform sampler2D textures[16]; -layout(push_constant) uniform push_constants { - uint texture_id; - uint workflow; -}; +layout(location = 0) in vec3 vColor; // from the VS +layout(location = 0) out vec4 fColor; // final output void main() { - if (texture_id == NO_TEXTURE) { - out_color = in_color; - return; - } - - if (workflow == WORKFLOW_TEXT) { - float coverage = texture(textures[texture_id], in_uv).r; - out_color = in_color * coverage; - } else { - vec4 user_texture = texture(textures[texture_id], in_uv); - out_color = in_color * user_texture; - } -} \ No newline at end of file + fColor = vec4(vColor, 1.0); +} diff --git a/examples/shaders/triangle.frag.spv b/examples/shaders/triangle.frag.spv index d0241f6..3f471a2 100644 Binary files a/examples/shaders/triangle.frag.spv and b/examples/shaders/triangle.frag.spv differ diff --git a/examples/shaders/triangle.vert b/examples/shaders/triangle.vert index c2f2858..6f2e62b 100644 --- a/examples/shaders/triangle.vert +++ b/examples/shaders/triangle.vert @@ -1,14 +1,19 @@ #version 450 -layout(location = 0) in vec4 in_position; -layout(location = 1) in vec4 in_colour; -layout(location = 2) in vec2 in_uv; +layout(push_constant) uniform Registers { + vec4 colour; +} registers; +layout(location = 0) out vec3 vColor; -layout(location = 0) out vec4 out_colour; -layout(location = 1) out vec2 out_uv; +vec2 positions[3] = vec2[]( + vec2(0.0, -0.5), // top + vec2(-0.5, 0.5), // bottom-left + vec2(0.5, 0.5) // bottom-right + ); void main() { - gl_Position = in_position; - out_colour = in_colour; - out_uv = in_uv; -} \ No newline at end of file + // Pick the position based on the built-in vertex index + gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); + + vColor = registers.colour.xyz; +} diff --git a/examples/shaders/triangle.vert.spv b/examples/shaders/triangle.vert.spv index 8f067ad..59b2cd8 100644 Binary files a/examples/shaders/triangle.vert.spv and b/examples/shaders/triangle.vert.spv differ diff --git a/examples/triangle.rs b/examples/triangle.rs index 62b9ade..a612ec9 100644 --- a/examples/triangle.rs +++ b/examples/triangle.rs @@ -1,45 +1,110 @@ -use lazy_vulkan::{DrawCall, LazyRenderer, LazyVulkan, Vertex, Workflow, NO_TEXTURE_ID}; +use std::{path::Path, time::Instant}; + +use glam::Vec4; +use lazy_vulkan::{Context, LazyVulkan, SubRenderer}; use winit::{ application::ApplicationHandler, + dpi::PhysicalSize, event::{ElementState, KeyEvent, WindowEvent}, event_loop::{ControlFlow, EventLoop}, keyboard::{KeyCode, PhysicalKey}, + window::WindowAttributes, }; -/// Compile your own damn shaders! LazyVulkan is just as lazy as you are! -static FRAGMENT_SHADER: &'static [u8] = include_bytes!("shaders/triangle.frag.spv"); -static VERTEX_SHADER: &'static [u8] = include_bytes!("shaders/triangle.vert.spv"); -// Fucking winit +pub struct TriangleRenderer { + pipeline: lazy_vulkan::Pipeline, + pub colour: glam::Vec4, +} + +impl TriangleRenderer { + pub fn new(renderer: &lazy_vulkan::Renderer) -> Self { + let pipeline: lazy_vulkan::Pipeline = lazy_vulkan::Pipeline::new::( + renderer.context.clone(), + renderer.swapchain.format, + Path::new("examples/shaders/triangle.vert.spv"), + Path::new("examples/shaders/triangle.frag.spv"), + ); + + Self { + pipeline, + colour: glam::Vec4::ONE, + } + } +} + +impl SubRenderer for TriangleRenderer { + type State = RenderState; + fn draw(&mut self, context: &Context, params: lazy_vulkan::DrawParams) { + self.begin_rendering(params.draw_command_buffer, context, &self.pipeline); + unsafe { + self.pipeline.update_registers( + params.draw_command_buffer, + context, + &Registers { + colour: self.colour, + }, + ); + context + .device + .cmd_draw(params.draw_command_buffer, 3, 1, 0, 0) + } + } + + fn update_state(&mut self, state: &RenderState) { + self.colour = psychedelic_vec4(state.t, state.last_render_time.elapsed().as_secs_f32()) + } + + fn stage_transfers(&mut self, _allocator: &mut lazy_vulkan::Allocator) { + // no-op + } +} + +pub struct RenderState { + last_render_time: Instant, + t: f32, +} + +#[repr(C)] +#[derive(Copy, Clone)] +struct Registers { + colour: glam::Vec4, +} + +unsafe impl bytemuck::Zeroable for Registers {} +unsafe impl bytemuck::Pod for Registers {} + #[derive(Default)] struct App { - lazy_vulkan: Option, - lazy_renderer: Option, + state: Option, +} + +struct State { + lazy_vulkan: LazyVulkan, + sub_renderers: Vec>>, + render_state: RenderState, } -impl ApplicationHandler for App { +impl<'a> ApplicationHandler for App { fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { - // Oh, you thought you could supply your own Vertex type? What is this, a rendergraph?! - // Better make sure those shaders use the right layout! - // **LAUGHS IN VULKAN** - let vertices = [ - Vertex::new([1.0, 1.0, 0.0, 1.0], [1.0, 0.0, 0.0, 0.0], [0.0, 0.0]), - Vertex::new([-1.0, 1.0, 0.0, 1.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0]), - Vertex::new([0.0, -1.0, 0.0, 1.0], [0.0, 0.0, 1.0, 0.0], [0.0, 0.0]), - ]; - - // Your own index type?! What are you going to use, `u16`? - let indices = [0, 1, 2]; - - // Alright, let's build some stuff - let (lazy_vulkan, lazy_renderer) = LazyVulkan::builder() - .initial_vertices(&vertices) - .initial_indices(&indices) - .fragment_shader(FRAGMENT_SHADER) - .vertex_shader(VERTEX_SHADER) - .build(event_loop); - - self.lazy_renderer = Some(lazy_renderer); - self.lazy_vulkan = Some(lazy_vulkan); + let window = event_loop + .create_window( + WindowAttributes::default() + .with_title("Triangle Example") + .with_inner_size(PhysicalSize::new(1024, 768)), + ) + .unwrap(); + + let lazy_vulkan = LazyVulkan::from_window(window); + let sub_renderer = TriangleRenderer::new(&lazy_vulkan.renderer); + + self.state = Some(State { + lazy_vulkan, + sub_renderers: vec![Box::new(sub_renderer)], + render_state: RenderState { + t: 0.0, + last_render_time: Instant::now(), + }, + }); } fn window_event( @@ -61,28 +126,37 @@ impl ApplicationHandler for App { } => event_loop.exit(), WindowEvent::Resized(size) => { - let lazy_renderer = self.lazy_renderer.as_mut().unwrap(); - let lazy_vulkan = self.lazy_vulkan.as_mut().unwrap(); - let new_render_surface = lazy_vulkan.resized(size.width, size.height); - lazy_renderer.update_surface(new_render_surface, &lazy_vulkan.context().device); + let state = self.state.as_mut().unwrap(); + state.lazy_vulkan.resize(size.width, size.height); } _ => (), } } fn about_to_wait(&mut self, _: &winit::event_loop::ActiveEventLoop) { - let lazy_renderer = self.lazy_renderer.as_mut().unwrap(); - let lazy_vulkan = self.lazy_vulkan.as_mut().unwrap(); - let framebuffer_index = lazy_vulkan.render_begin(); - lazy_renderer.render( - &lazy_vulkan.context(), - framebuffer_index, - &[DrawCall::new(0, 3, NO_TEXTURE_ID, Workflow::Main)], - ); - lazy_vulkan.render_end(framebuffer_index, &[lazy_vulkan.present_complete_semaphore]); + let state = self.state.as_mut().unwrap(); + state.render_state.t += state.render_state.last_render_time.elapsed().as_secs_f32(); + let lazy_vulkan = &mut state.lazy_vulkan; + lazy_vulkan.draw(&state.render_state, &mut state.sub_renderers); + state.render_state.last_render_time = Instant::now(); } } +// from chatGPT +pub fn psychedelic_vec4(t: f32, dt: f32) -> Vec4 { + let mut time = t + dt; + + time *= 50.0; + + let r = (time * 1.0 + 0.0).sin() * 0.5 + 0.5; + let g = (time * 1.3 + std::f32::consts::FRAC_PI_2).sin() * 0.5 + 0.5; + let b = (time * 1.6 + std::f32::consts::PI).sin() * 0.5 + 0.5; + + let a = (time * 0.4).cos() * 0.5 + 0.5; + + Vec4::new(r, g, b, a) +} + pub fn main() { env_logger::init(); diff --git a/src/allocator.rs b/src/allocator.rs new file mode 100644 index 0000000..4046b30 --- /dev/null +++ b/src/allocator.rs @@ -0,0 +1,312 @@ +use std::{fmt::Debug, marker::PhantomData, sync::Arc}; + +use ash::vk; + +use super::context::Context; + +const GLOBAL_MEMORY_SIZE: u64 = 2u64 << 30; // 2GB +const STAGING_MEMORY_SIZE: u64 = 100u64 << 20; // 100MB + +pub struct Allocator { + pub context: Arc, + #[allow(unused)] + staging_memory: vk::DeviceMemory, + staging_buffer: vk::Buffer, + staging_buffer_size: vk::DeviceSize, + staging_address: std::ptr::NonNull, + pub global_memory: vk::DeviceMemory, + pub pending_transfers: Vec, + #[allow(unused)] + pub pending_frees: Vec, + offset_allocator: offset_allocator::Allocator, + #[cfg(any(target_os = "macos", target_os = "ios"))] + pub sync2_pfn: ash::khr::synchronization2::Device, +} + +impl Allocator { + pub fn new(context: Arc, sync2_pfn: ash::khr::synchronization2::Device) -> Self { + let device = &context.device; + let memory_properties = &context.memory_properties; + + // Search through the available memory types to find the one we want + let mut memory_type_index = None; + let mut memory_heap_index = None; + for (index, memory_type) in memory_properties.memory_types_as_slice().iter().enumerate() { + if memory_type + .property_flags + .contains(vk::MemoryPropertyFlags::DEVICE_LOCAL) + { + memory_type_index = Some(index as u32); + memory_heap_index = Some(memory_type.heap_index); + break; + } + } + + let memory_type_index = memory_type_index.expect("No device memory? Impossible"); + let memory_heap_index = memory_heap_index.expect("No device memory? Impossible"); + + let device_memory = unsafe { + println!("Allocating {GLOBAL_MEMORY_SIZE} from memory type / heap : {memory_type_index}, {memory_heap_index}"); + device.allocate_memory( + &vk::MemoryAllocateInfo::default() + .memory_type_index(memory_type_index) + .allocation_size(GLOBAL_MEMORY_SIZE).push_next(&mut vk::MemoryAllocateFlagsInfo::default().flags(vk::MemoryAllocateFlags::DEVICE_ADDRESS)), + None, + ) + } + .unwrap(); + + let mut memory_type_index = None; + let mut memory_heap_index = None; + for (index, memory_type) in memory_properties.memory_types_as_slice().iter().enumerate() { + if memory_type.property_flags.contains( + vk::MemoryPropertyFlags::HOST_COHERENT | vk::MemoryPropertyFlags::HOST_VISIBLE, + ) { + memory_type_index = Some(index as u32); + memory_heap_index = Some(memory_type.heap_index); + break; + } + } + + // TODO: We can get reaaaaaally fancy on different devices here. + // Allocate our staging memory + let staging_memory = unsafe { + let memory_type_index = memory_type_index.expect("No host local memory? Impossible"); + let memory_heap_index = memory_heap_index.expect("No host local memory? Impossible"); + + + println!("Allocating {STAGING_MEMORY_SIZE} from memory type / heap : {memory_type_index}, {memory_heap_index}"); + device.allocate_memory( + &vk::MemoryAllocateInfo::default() + .memory_type_index(memory_type_index) + .allocation_size(STAGING_MEMORY_SIZE), + None, + ) + } + .unwrap(); + + // Create a staging buffer + let staging_buffer = unsafe { + device.create_buffer( + &vk::BufferCreateInfo::default() + .size(STAGING_MEMORY_SIZE) + .usage(vk::BufferUsageFlags::TRANSFER_SRC), + None, + ) + } + .unwrap(); + + // Bind its memory + unsafe { device.bind_buffer_memory(staging_buffer, staging_memory, 0) }.unwrap(); + + // Map its memory + let staging_address = unsafe { + std::ptr::NonNull::new_unchecked( + device + .map_memory( + staging_memory, + 0, + vk::WHOLE_SIZE, + vk::MemoryMapFlags::empty(), + ) + .unwrap() as *mut u8, + ) + }; + + let offset_allocator = offset_allocator::Allocator::new(GLOBAL_MEMORY_SIZE as u32); + + Self { + context, + global_memory: device_memory, + staging_memory, + staging_buffer, + staging_buffer_size: 0, + staging_address, + offset_allocator, + pending_frees: Default::default(), + pending_transfers: Default::default(), + #[cfg(any(target_os = "macos", target_os = "ios"))] + sync2_pfn, + } + } + + pub fn allocate_buffer( + &mut self, + max_size: usize, + usage_flags: vk::BufferUsageFlags, + ) -> BufferAllocation { + let device = &self.context.device; + let device_size = (max_size * std::mem::size_of::()) as vk::DeviceSize; + + // Allocate an offset into our device local memory + let offset = self + .offset_allocator + .allocate(device_size as u32) + .expect("Unable to allocate memory. This should be impossible!"); + + // Create the buffer + let handle = unsafe { + device.create_buffer( + &vk::BufferCreateInfo::default().size(device_size).usage( + usage_flags + | vk::BufferUsageFlags::SHADER_DEVICE_ADDRESS + | vk::BufferUsageFlags::TRANSFER_DST, + ), + None, + ) + } + .unwrap(); + + // Bind its memory + unsafe { device.bind_buffer_memory(handle, self.global_memory, offset.offset as _) } + .unwrap(); + + // Get its device address + let device_address = unsafe { + device.get_buffer_device_address(&vk::BufferDeviceAddressInfo::default().buffer(handle)) + }; + + BufferAllocation { + max_device_size: device_size, + device_address, + len: 0, + handle, + global_offset: offset, + _phantom: PhantomData, + } + } + + /// SAFETY: + /// + /// - [`data`] must be POD + pub fn stage_transfer( + &mut self, + data: &[T], + allocation: &mut BufferAllocation, + ) { + // **ACHTUNG**: + // All references to sizes in this function are in terms of *BYTES*, not `[T]`. + + // Step one: copy the data into the staging buffer + let staging_buffer_offset = self.staging_buffer_size; + let transfer_size = std::mem::size_of_val(data) as vk::DeviceSize; + + // We get the staging pointer by taking the base address and adding the current size of + // the buffer. + let staging_ptr = unsafe { + self.staging_address + .add(staging_buffer_offset as usize) + .as_ptr() + }; + + unsafe { + std::ptr::copy_nonoverlapping( + data.as_ptr() as *const u8, + staging_ptr, + transfer_size as usize, // BYTES + ); + }; + + // Step two: record the amount of data transferred + self.staging_buffer_size += transfer_size; + allocation.len += data.len(); + + // DEBUG: Ensure the data was copied correctly. + unsafe { + let staging_data = std::slice::from_raw_parts(staging_ptr as *const T, data.len()); + debug_assert_eq!(staging_data, data); + }; + + // Step three: stage the transfer to device local memory + self.pending_transfers.push(PendingTransfer { + destination: allocation.handle, + staging_buffer_offset, + transfer_size, + }); + } + + pub fn execute_transfers(&mut self) { + let device = &self.context.device; + let command_buffer = self.context.draw_command_buffer; + + // Step one: record transfers and barriers + let mut barriers = vec![]; + + for PendingTransfer { + destination, + staging_buffer_offset, + transfer_size, + } in self.pending_transfers.drain(..) + { + unsafe { + device.cmd_copy_buffer( + command_buffer, + self.staging_buffer, + destination, + &[vk::BufferCopy::default() + .src_offset(staging_buffer_offset) + .dst_offset(0) + .size(transfer_size)], + ); + } + + barriers.push( + vk::BufferMemoryBarrier2::default() + .src_access_mask(vk::AccessFlags2::TRANSFER_WRITE) + .src_stage_mask(vk::PipelineStageFlags2::TRANSFER) + .dst_access_mask(vk::AccessFlags2::SHADER_READ) + .dst_stage_mask(vk::PipelineStageFlags2::VERTEX_SHADER) + .buffer(destination) + .size(transfer_size), + ) + } + + // Step two: set the barriers + unsafe { + #[cfg(not(any(target_os = "macos", target_os = "ios")))] + device.cmd_pipeline_barrier2( + command_buffer, + &vk::DependencyInfo::default().buffer_memory_barriers(&barriers), + ); + #[cfg(any(target_os = "macos", target_os = "ios"))] + self.sync2_pfn.cmd_pipeline_barrier2( + command_buffer, + &vk::DependencyInfo::default().buffer_memory_barriers(&barriers), + ); + } + + // Step three: reset the staging buffer + self.staging_buffer_size = 0; + } + + #[allow(unused)] + pub fn free(&mut self, allocation: BufferAllocation) {} +} + +pub struct PendingTransfer { + destination: vk::Buffer, + staging_buffer_offset: vk::DeviceSize, + transfer_size: vk::DeviceSize, +} + +pub struct PendingFree; +pub struct BufferAllocation { + #[allow(unused)] + pub max_device_size: vk::DeviceSize, + pub device_address: vk::DeviceAddress, + len: usize, // number of `T`s in this buffer + pub handle: vk::Buffer, + #[allow(unused)] + global_offset: offset_allocator::Allocation, // offset into the global memory + _phantom: PhantomData, +} + +impl BufferAllocation { + pub fn len(&self) -> usize { + self.len + } + + pub fn clear(&mut self) { + self.len = 0; + } +} diff --git a/src/buffer.rs b/src/buffer.rs deleted file mode 100644 index c089d7d..0000000 --- a/src/buffer.rs +++ /dev/null @@ -1,95 +0,0 @@ -use std::mem::align_of; - -use ash::{util::Align, vk}; - -use crate::{find_memorytype_index, vulkan_context::VulkanContext}; - -/// Minimum size of a buffer to avoid needless allocations. -const MIN_BUFFER_SIZE: vk::DeviceSize = 1_048_576; // 1MB - -/// A wrapper around a host mapped buffer on the GPU -/// -/// May be slightly slow on desktop, but future work could be done to use DEVICE_LOCAL memory if -/// need be. -pub struct Buffer { - pub handle: vk::Buffer, - pub memory: vk::DeviceMemory, - pub _usage: vk::BufferUsageFlags, - /// The maximum size of the buffer, in bytes - pub size: vk::DeviceSize, - len: usize, - ptr: std::ptr::NonNull, -} - -impl Buffer { - pub fn new( - vulkan_context: &VulkanContext, - usage: vk::BufferUsageFlags, - initial_data: &[T], - ) -> Self { - let device = &vulkan_context.device; - let device_memory_properties = &vulkan_context.memory_properties; - - let buffer_info = vk::BufferCreateInfo::default() - .size(std::mem::size_of_val(initial_data).max(MIN_BUFFER_SIZE as _) as u64) - .usage(usage) - .sharing_mode(vk::SharingMode::EXCLUSIVE); - - let handle = unsafe { device.create_buffer(&buffer_info, None).unwrap() }; - let memory_req = unsafe { device.get_buffer_memory_requirements(handle) }; - let size = MIN_BUFFER_SIZE.max(memory_req.size); - let memory_index = find_memorytype_index( - &memory_req, - device_memory_properties, - vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT, - ) - .expect("Unable to find suitable memorytype for the index buffer."); - - let allocate_info = vk::MemoryAllocateInfo { - allocation_size: size, - memory_type_index: memory_index, - ..Default::default() - }; - let memory = unsafe { device.allocate_memory(&allocate_info, None).unwrap() }; - let ptr = unsafe { - 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 - std::ptr::NonNull::new_unchecked(ptr.cast()) - }; - - Buffer { - handle, - memory, - _usage: usage, - size, - len: initial_data.len(), - ptr, - } - } - - pub unsafe fn overwrite(&mut self, _vulkan_context: &VulkanContext, new_data: &[T]) { - let new_data_size = std::mem::size_of_val(new_data); - if 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() - } - - /// ## Safety - /// Buffer will be unusable after this function has been called. - pub unsafe fn cleanup(&self, device: &ash::Device) { - device.unmap_memory(self.memory); - device.free_memory(self.memory, None); - device.destroy_buffer(self.handle, None); - } -} diff --git a/src/context.rs b/src/context.rs new file mode 100644 index 0000000..a244f17 --- /dev/null +++ b/src/context.rs @@ -0,0 +1,145 @@ +use ash::vk::{self, MemoryRequirements}; + +use super::core::Core; + +pub struct Context { + pub device: ash::Device, + #[allow(unused)] + pub command_pool: vk::CommandPool, + pub draw_command_buffer: vk::CommandBuffer, + pub graphics_queue: vk::Queue, + pub memory_properties: vk::PhysicalDeviceMemoryProperties, +} + +impl Context { + pub(crate) fn new(core: &Core) -> Self { + let instance = &core.instance; + let physical_device = core.physical_device; + + let device = create_device(instance, physical_device); + + let command_pool = unsafe { + device.create_command_pool( + &vk::CommandPoolCreateInfo::default() + .queue_family_index(0) + .flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER), + None, + ) + } + .unwrap(); + + let draw_command_buffer = unsafe { + device.allocate_command_buffers( + &vk::CommandBufferAllocateInfo::default() + .command_pool(command_pool) + .command_buffer_count(1), + ) + } + .unwrap()[0]; + + let graphics_queue = unsafe { device.get_device_queue(0, 0) }; + + let memory_properties = + unsafe { instance.get_physical_device_memory_properties(physical_device) }; + + Self { + device, + command_pool, + draw_command_buffer, + graphics_queue, + memory_properties, + } + } + + pub fn find_memory_type_index( + &self, + requirements: &MemoryRequirements, + required_properties: vk::MemoryPropertyFlags, + ) -> Option { + let mem_props = self.memory_properties; + for i in 0..mem_props.memory_type_count { + if (requirements.memory_type_bits & (1 << i)) != 0 + && mem_props.memory_types[i as usize] + .property_flags + .contains(required_properties) + { + return Some(i); + } + } + None + } +} + +#[cfg(any(target_os = "macos", target_os = "ios"))] +fn create_device(instance: &ash::Instance, physical_device: vk::PhysicalDevice) -> ash::Device { + let enabled_extension_names = [ + ash::khr::swapchain::NAME.as_ptr(), + ash::khr::portability_subset::NAME.as_ptr(), + ash::khr::dynamic_rendering::NAME.as_ptr(), + ash::khr::synchronization2::NAME.as_ptr(), + ]; + + let device = unsafe { + instance.create_device( + physical_device, + &vk::DeviceCreateInfo::default() + .enabled_extension_names(&enabled_extension_names) + .queue_create_infos(&[vk::DeviceQueueCreateInfo::default() + .queue_family_index(0) + .queue_priorities(&[1.0])]) + .enabled_features(&vk::PhysicalDeviceFeatures::default().fill_mode_non_solid(true)) + .push_next( + &mut vk::PhysicalDeviceDynamicRenderingFeatures::default() + .dynamic_rendering(true), + ) + .push_next( + &mut vk::PhysicalDeviceSynchronization2Features::default() + .synchronization2(true), + ) + .push_next( + &mut vk::PhysicalDeviceVulkan12Features::default().buffer_device_address(true), + ) + .push_next( + &mut vk::PhysicalDeviceVulkan11Features::default() + .variable_pointers(true) + .variable_pointers_storage_buffer(true), + ), + None, + ) + } + .unwrap(); + device +} + +#[cfg(not(any(target_os = "macos", target_os = "ios")))] +fn create_device(instance: &ash::Instance, physical_device: vk::PhysicalDevice) -> ash::Device { + let enabled_extension_names = [ash::khr::swapchain::NAME.as_ptr()]; + + let device = unsafe { + instance.create_device( + physical_device, + &vk::DeviceCreateInfo::default() + .enabled_extension_names(&enabled_extension_names) + .queue_create_infos(&[vk::DeviceQueueCreateInfo::default() + .queue_family_index(0) + .queue_priorities(&[1.0])]) + .enabled_features(&vk::PhysicalDeviceFeatures::default().fill_mode_non_solid(true)) + .push_next( + &mut vk::PhysicalDeviceVulkan13Features::default() + .dynamic_rendering(true) + .synchronization2(true), + ) + .push_next( + &mut vk::PhysicalDeviceVulkan12Features::default().buffer_device_address(true), + ) + .push_next( + &mut vk::PhysicalDeviceVulkan11Features::default() + .variable_pointers(true) + .variable_pointers_storage_buffer(true), + ), + None, + ) + } + .unwrap(); + device +} diff --git a/src/core.rs b/src/core.rs new file mode 100644 index 0000000..2e05cc0 --- /dev/null +++ b/src/core.rs @@ -0,0 +1,61 @@ +use ash::vk; +use winit::raw_window_handle::HasDisplayHandle; + +pub struct Core { + pub entry: ash::Entry, + pub instance: ash::Instance, + pub physical_device: vk::PhysicalDevice, +} +impl Core { + pub fn from_window(window: &winit::window::Window) -> Self { + let entry = unsafe { ash::Entry::load().unwrap() }; + + let display_handle = window.display_handle().unwrap().as_raw(); + + #[allow(unused_mut)] + let mut instance_extensions = ash_window::enumerate_required_extensions(display_handle) + .unwrap() + .to_vec(); + + let version; + let instance_create_flags; + + #[cfg(any(target_os = "macos", target_os = "ios"))] + { + instance_extensions.push(ash::khr::portability_enumeration::NAME.as_ptr()); + instance_extensions.push(ash::khr::get_physical_device_properties2::NAME.as_ptr()); + version = vk::API_VERSION_1_2; + instance_create_flags = vk::InstanceCreateFlags::ENUMERATE_PORTABILITY_KHR; + } + + #[cfg(not(any(target_os = "macos", target_os = "ios")))] + { + version = vk::API_VERSION_1_3; + create_flags = vk::InstanceCreateFlags::default(); + } + + let instance = unsafe { + entry + .create_instance( + &vk::InstanceCreateInfo::default() + .flags(instance_create_flags) + .enabled_extension_names(&instance_extensions) + .application_info(&vk::ApplicationInfo::default().api_version(version)), + None, + ) + .unwrap() + }; + + let physical_device = unsafe { instance.enumerate_physical_devices() } + .unwrap() + .first() + .copied() + .unwrap(); + + Self { + entry, + instance, + physical_device, + } + } +} diff --git a/src/depth_buffer.rs b/src/depth_buffer.rs new file mode 100644 index 0000000..40326d5 --- /dev/null +++ b/src/depth_buffer.rs @@ -0,0 +1,86 @@ +use ash::vk; + +use super::{context::Context, swapchain::Swapchain}; + +#[derive(Debug, Copy, Clone)] +pub struct DepthBuffer { + pub image: vk::Image, + pub view: vk::ImageView, + #[allow(unused)] + pub memory: vk::DeviceMemory, +} + +impl DepthBuffer { + pub(crate) fn new(context: &Context, swapchain: &Swapchain) -> Self { + let device = &context.device; + let image = unsafe { + device.create_image( + &vk::ImageCreateInfo::default() + .array_layers(1) + .mip_levels(1) + .image_type(vk::ImageType::TYPE_2D) + .samples(vk::SampleCountFlags::TYPE_1) + .tiling(vk::ImageTiling::OPTIMAL) + .usage(vk::ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT) + .sharing_mode(vk::SharingMode::EXCLUSIVE) + .initial_layout(vk::ImageLayout::UNDEFINED) + .extent(swapchain.extent.into()) + .format(DEPTH_FORMAT), + None, + ) + } + .unwrap(); + + let memory_requirements = unsafe { device.get_image_memory_requirements(image) }; + + let memory_type_index = context + .find_memory_type_index(&memory_requirements, vk::MemoryPropertyFlags::DEVICE_LOCAL) + .expect("No memory type index for depth buffer - impossible"); + + let memory = unsafe { + device.allocate_memory( + &vk::MemoryAllocateInfo::default() + .allocation_size(memory_requirements.size) + .memory_type_index(memory_type_index), + None, + ) + } + .expect("Failed to allocate memory - impossible"); + + unsafe { + device.bind_image_memory2(&[vk::BindImageMemoryInfo::default() + .image(image) + .memory(memory)]) + } + .unwrap(); + + let view = unsafe { + device.create_image_view( + &vk::ImageViewCreateInfo::default() + .image(image) + .view_type(vk::ImageViewType::TYPE_2D) + .format(DEPTH_FORMAT) + .components(vk::ComponentMapping::default()) + .subresource_range(DEPTH_RANGE), + None, + ) + } + .unwrap(); + + Self { + image, + view, + memory, + } + } +} + +pub const DEPTH_FORMAT: vk::Format = vk::Format::D32_SFLOAT; + +pub const DEPTH_RANGE: vk::ImageSubresourceRange = vk::ImageSubresourceRange { + aspect_mask: vk::ImageAspectFlags::DEPTH, + layer_count: 1, + level_count: 1, + base_mip_level: 0, + base_array_layer: 0, +}; diff --git a/src/descriptors.rs b/src/descriptors.rs deleted file mode 100644 index febcb6d..0000000 --- a/src/descriptors.rs +++ /dev/null @@ -1,99 +0,0 @@ -use ash::vk; - -use crate::vulkan_context::VulkanContext; - -pub struct Descriptors { - pub pool: vk::DescriptorPool, - pub set: vk::DescriptorSet, - pub layout: vk::DescriptorSetLayout, - texture_count: u32, -} - -impl Descriptors { - pub fn new(vulkan_context: &VulkanContext) -> Descriptors { - let device = &vulkan_context.device; - - let pool = unsafe { - device.create_descriptor_pool( - &vk::DescriptorPoolCreateInfo::default() - .max_sets(1) - .pool_sizes(&[vk::DescriptorPoolSize { - ty: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, - descriptor_count: 16, - }]), - None, - ) - } - .unwrap(); - - let flags = [vk::DescriptorBindingFlags::PARTIALLY_BOUND]; - let mut binding_flags = - vk::DescriptorSetLayoutBindingFlagsCreateInfo::default().binding_flags(&flags); - - let layout = unsafe { - device.create_descriptor_set_layout( - &vk::DescriptorSetLayoutCreateInfo::default() - .bindings(&[vk::DescriptorSetLayoutBinding { - binding: 0, - descriptor_type: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, - stage_flags: vk::ShaderStageFlags::FRAGMENT, - descriptor_count: 16, - ..Default::default() - }]) - .push_next(&mut binding_flags), - None, - ) - } - .unwrap(); - - let set = unsafe { - device.allocate_descriptor_sets( - &vk::DescriptorSetAllocateInfo::default() - .descriptor_pool(pool) - .set_layouts(std::slice::from_ref(&layout)), - ) - } - .unwrap()[0]; - - Descriptors { - pool, - set, - layout, - texture_count: 0, - } - } - - pub unsafe fn update_texture_descriptor_set( - &mut self, - image_view: vk::ImageView, - sampler: vk::Sampler, - vulkan_context: &VulkanContext, - ) -> u32 { - let texture_id = self.texture_count; - vulkan_context.device.update_descriptor_sets( - std::slice::from_ref( - &vk::WriteDescriptorSet::default() - .image_info(std::slice::from_ref( - &vk::DescriptorImageInfo::default() - .sampler(sampler) - .image_view(image_view) - .image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL), - )) - .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) - .dst_array_element(texture_id as _) - .dst_set(self.set), - ), - &[], - ); - - self.texture_count += 1; - texture_id - } - - /// ## Safety - /// Descriptors will be unusable after this function has been called. - pub unsafe fn cleanup(&self, device: &ash::Device) { - device.destroy_descriptor_set_layout(self.layout, None); - device.destroy_descriptor_pool(self.pool, None); - } -} diff --git a/src/draw_params.rs b/src/draw_params.rs new file mode 100644 index 0000000..e35791f --- /dev/null +++ b/src/draw_params.rs @@ -0,0 +1,26 @@ +use ash::vk; + +use crate::{depth_buffer::DepthBuffer, swapchain::Drawable}; + +#[derive(Clone, Copy)] +pub struct DrawParams { + pub draw_command_buffer: vk::CommandBuffer, + #[allow(unused)] + pub drawable: Drawable, + #[allow(unused)] + pub depth_buffer: DepthBuffer, +} + +impl DrawParams { + pub fn new( + draw_command_buffer: vk::CommandBuffer, + drawable: Drawable, + depth_buffer: DepthBuffer, + ) -> Self { + Self { + draw_command_buffer, + drawable, + depth_buffer, + } + } +} diff --git a/src/lazy_renderer.rs b/src/lazy_renderer.rs deleted file mode 100644 index 7cba514..0000000 --- a/src/lazy_renderer.rs +++ /dev/null @@ -1,567 +0,0 @@ -//! A Vulkan backend for the [`yakui`] crate. Uses [`ash`] to wrap Vulkan related functionality. -//! -//! The main entrypoint is the [`YakuiVulkan`] struct which creates a [`ash::vk::RenderPass`] and [`ash::vk::Pipeline`] -//! to draw yakui GUIs. This is initialised by populating a [`VulkanContext`] helper struct to pass down the relevant hooks -//! into your Vulkan renderer. -//! -//! Like most Vulkan applications, this crate uses unsafe Rust! No checks are made to ensure that Vulkan handles are valid, -//! so take note of the safety warnings on the various methods of [`YakuiVulkan`]. -//! -//! Currently this crate only supports drawing to images in the `VK_IMAGE_LAYOUT_PRESENT_SRC_KHR` layout, but future -//! releases will support drawing to any arbitrary [`vk::ImageView`]. -//! -//! 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. - -use crate::buffer::Buffer; -use crate::descriptors::Descriptors; -use crate::vulkan_context::VulkanContext; -use crate::vulkan_texture::VulkanTexture; -use crate::vulkan_texture::VulkanTextureCreateInfo; -use crate::{LazyVulkanBuilder, Vertex}; -use std::{ffi::CStr, io::Cursor}; - -use ash::{util::read_spv, vk}; -use bytemuck::{Pod, Zeroable}; -use thunderdome::Index; - -/// A struct wrapping everything needed to render yakui on Vulkan. This will be your main entry point. -/// -/// Uses a simple descriptor system that requires at least Vulkan 1.2 and [VkPhysicalDeviceDescriptorIndexingFeatures](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkPhysicalDeviceDescriptorIndexingFeatures.html) -/// `descriptorBindingPartiallyBound` to be enabled. -/// -/// 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` -/// -/// 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 LazyRenderer { - /// 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) - pub render_surface: RenderSurface, - /// The pipeline layout used to draw - pipeline_layout: vk::PipelineLayout, - /// The graphics pipeline used to draw - graphics_pipeline: vk::Pipeline, - /// A single index buffer, shared between all draw calls - pub index_buffer: Buffer, - /// A single vertex buffer, shared between all draw calls - pub vertex_buffer: Buffer, - /// Textures owned by the user - user_textures: thunderdome::Arena, - /// A wrapper around descriptor set functionality - pub descriptors: Descriptors, -} - -#[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, -} - -#[derive(Clone, Copy, Debug)] -/// A single draw call to render a yakui mesh -pub struct DrawCall { - index_offset: u32, - index_count: u32, - texture_id: u32, - workflow: Workflow, -} - -impl DrawCall { - pub fn new(index_offset: u32, index_count: u32, texture_id: u32, workflow: Workflow) -> Self { - Self { - index_offset, - index_count, - texture_id, - workflow, - } - } -} - -#[repr(C)] -#[derive(Clone, Copy, Debug)] -/// Push constant used to determine texture and workflow -struct PushConstant { - texture_id: u32, - workflow: Workflow, -} - -unsafe impl Zeroable for PushConstant {} -unsafe impl Pod for PushConstant {} - -impl PushConstant { - pub fn new(texture_id: u32, workflow: Workflow) -> Self { - Self { - texture_id, - workflow, - } - } -} - -#[repr(u32)] -#[derive(Clone, Copy, Debug)] -/// The workflow to use in the shader -pub enum Workflow { - Main, - Text, -} - -unsafe impl Zeroable for Workflow {} -unsafe impl bytemuck::Pod for Workflow {} - -impl LazyRenderer { - /// Create a new [`LazyRenderer`] instance. Currently only supports rendering directly to the swapchain. - /// - /// ## 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, - builder: &LazyVulkanBuilder, - ) -> Self { - let vertex_shader = builder.vertex_shader.as_ref().unwrap(); - let fragment_shader = builder.fragment_shader.as_ref().unwrap(); - let device = &vulkan_context.device; - let descriptors = Descriptors::new(vulkan_context); - let final_layout = if builder.with_present { - vk::ImageLayout::PRESENT_SRC_KHR - } else { - vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL - }; - - // 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: vk::AttachmentLoadOp::CLEAR, - store_op: vk::AttachmentStoreOp::STORE, - final_layout, - ..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::default() - .color_attachments(&color_attachment_refs) - .pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS); - - let renderpass_create_info = vk::RenderPassCreateInfo::default() - .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, - &builder.initial_indices, - ); - let vertex_buffer = Buffer::new( - vulkan_context, - vk::BufferUsageFlags::VERTEX_BUFFER, - &builder.initial_vertices, - ); - - let mut vertex_spv_file = Cursor::new(vertex_shader); - let mut frag_spv_file = Cursor::new(fragment_shader); - - let vertex_code = - read_spv(&mut vertex_spv_file).expect("Failed to read vertex shader spv file"); - let vertex_shader_info = vk::ShaderModuleCreateInfo::default().code(&vertex_code); - - let frag_code = - read_spv(&mut frag_spv_file).expect("Failed to read fragment shader spv file"); - let frag_shader_info = vk::ShaderModuleCreateInfo::default().code(&frag_code); - - let vertex_shader_module = unsafe { - device - .create_shader_module(&vertex_shader_info, None) - .expect("Vertex shader module error") - }; - - let fragment_shader_module = unsafe { - device - .create_shader_module(&frag_shader_info, None) - .expect("Fragment shader module error") - }; - - let pipeline_layout = unsafe { - device - .create_pipeline_layout( - &vk::PipelineLayoutCreateInfo::default() - .push_constant_ranges(std::slice::from_ref( - &vk::PushConstantRange::default() - .stage_flags(vk::ShaderStageFlags::FRAGMENT) - .size(std::mem::size_of::() as _), - )) - .set_layouts(std::slice::from_ref(&descriptors.layout)), - None, - ) - .unwrap() - }; - - let shader_entry_name = unsafe { CStr::from_bytes_with_nul_unchecked(b"main\0") }; - let shader_stage_create_infos = [ - vk::PipelineShaderStageCreateInfo { - module: vertex_shader_module, - p_name: shader_entry_name.as_ptr(), - stage: vk::ShaderStageFlags::VERTEX, - ..Default::default() - }, - vk::PipelineShaderStageCreateInfo { - s_type: vk::StructureType::PIPELINE_SHADER_STAGE_CREATE_INFO, - module: fragment_shader_module, - p_name: shader_entry_name.as_ptr(), - stage: vk::ShaderStageFlags::FRAGMENT, - ..Default::default() - }, - ]; - let vertex_input_binding_descriptions = [vk::VertexInputBindingDescription { - binding: 0, - stride: std::mem::size_of::() as u32, - input_rate: vk::VertexInputRate::VERTEX, - }]; - - let vertex_input_attribute_descriptions = [ - // position - vk::VertexInputAttributeDescription { - location: 0, - binding: 0, - format: vk::Format::R32G32B32A32_SFLOAT, - offset: bytemuck::offset_of!(Vertex, position) as _, - }, - // color - vk::VertexInputAttributeDescription { - location: 1, - binding: 0, - format: vk::Format::R32G32B32A32_SFLOAT, - offset: bytemuck::offset_of!(Vertex, colour) as _, - }, - // UV / texcoords - vk::VertexInputAttributeDescription { - location: 2, - binding: 0, - format: vk::Format::R32G32_SFLOAT, - offset: bytemuck::offset_of!(Vertex, uv) as _, - }, - ]; - - let vertex_input_state_info = vk::PipelineVertexInputStateCreateInfo::default() - .vertex_attribute_descriptions(&vertex_input_attribute_descriptions) - .vertex_binding_descriptions(&vertex_input_binding_descriptions); - let vertex_input_assembly_state_info = vk::PipelineInputAssemblyStateCreateInfo { - 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::default() - .scissors(&scissors) - .viewports(&viewports); - - let rasterization_info = vk::PipelineRasterizationStateCreateInfo { - front_face: vk::FrontFace::COUNTER_CLOCKWISE, - line_width: 1.0, - polygon_mode: vk::PolygonMode::FILL, - ..Default::default() - }; - let multisample_state_info = vk::PipelineMultisampleStateCreateInfo { - rasterization_samples: vk::SampleCountFlags::TYPE_1, - ..Default::default() - }; - let noop_stencil_state = vk::StencilOpState { - fail_op: vk::StencilOp::KEEP, - pass_op: vk::StencilOp::KEEP, - depth_fail_op: vk::StencilOp::KEEP, - compare_op: vk::CompareOp::ALWAYS, - ..Default::default() - }; - let depth_state_info = vk::PipelineDepthStencilStateCreateInfo { - depth_test_enable: 1, - depth_write_enable: 1, - depth_compare_op: vk::CompareOp::LESS_OR_EQUAL, - front: noop_stencil_state, - back: noop_stencil_state, - max_depth_bounds: 1.0, - ..Default::default() - }; - let color_blend_attachment_states = [vk::PipelineColorBlendAttachmentState { - blend_enable: 0, - src_color_blend_factor: vk::BlendFactor::SRC_COLOR, - dst_color_blend_factor: vk::BlendFactor::ONE_MINUS_DST_COLOR, - color_blend_op: vk::BlendOp::ADD, - src_alpha_blend_factor: vk::BlendFactor::ZERO, - dst_alpha_blend_factor: vk::BlendFactor::ZERO, - alpha_blend_op: vk::BlendOp::ADD, - color_write_mask: vk::ColorComponentFlags::RGBA, - }]; - let color_blend_state = vk::PipelineColorBlendStateCreateInfo::default() - .logic_op(vk::LogicOp::CLEAR) - .attachments(&color_blend_attachment_states); - - let dynamic_state = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR]; - let dynamic_state_info = - vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_state); - - let graphics_pipelines = unsafe { - device - .create_graphics_pipelines( - vk::PipelineCache::null(), - &[vk::GraphicsPipelineCreateInfo::default() - .stages(&shader_stage_create_infos) - .vertex_input_state(&vertex_input_state_info) - .input_assembly_state(&vertex_input_assembly_state_info) - .viewport_state(&viewport_state_info) - .rasterization_state(&rasterization_info) - .multisample_state(&multisample_state_info) - .depth_stencil_state(&depth_state_info) - .color_blend_state(&color_blend_state) - .dynamic_state(&dynamic_state_info) - .layout(pipeline_layout) - .render_pass(render_pass)], - None, - ) - .expect("Unable to create graphics pipeline") - }; - - let graphics_pipeline = graphics_pipelines[0]; - - unsafe { - device.destroy_shader_module(vertex_shader_module, None); - device.destroy_shader_module(fragment_shader_module, None); - } - - Self { - render_pass, - descriptors, - framebuffers, - render_surface, - pipeline_layout, - graphics_pipeline, - index_buffer, - vertex_buffer, - user_textures: Default::default(), - } - } - - /// Render the draw calls we've built up - pub fn render( - &self, - vulkan_context: &VulkanContext, - framebuffer_index: u32, - 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::default() - .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, - }]; - - 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()]; - - // 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, - self.index_buffer.handle, - 0, - vk::IndexType::UINT32, - ); - device.cmd_bind_descriptor_sets( - command_buffer, - vk::PipelineBindPoint::GRAPHICS, - self.pipeline_layout, - 0, - std::slice::from_ref(&self.descriptors.set), - &[], - ); - for draw_call in draw_calls { - // Instead of using different pipelines for text and non-text rendering, we just - // pass the "workflow" down through a push constant and branch in the shader. - device.cmd_push_constants( - command_buffer, - self.pipeline_layout, - vk::ShaderStageFlags::FRAGMENT, - 0, - bytemuck::bytes_of(&PushConstant::new( - draw_call.texture_id, - draw_call.workflow, - )), - ); - - // Draw the mesh with the indexes we were provided - device.cmd_draw_indexed( - command_buffer, - draw_call.index_count, - 1, - draw_call.index_offset, - 0, - 1, - ); - } - device.cmd_end_render_pass(command_buffer); - } - } - - /// Add a "user managed" texture to this [`YakuiVulkan`] instance. Returns a [`yakui::TextureId`] that can be used - /// to refer to the texture in your GUI code. - /// - /// ## Safety - /// - `vulkan_context` must be the same as the one used to create this instance - pub fn add_user_texture( - &mut self, - vulkan_context: &VulkanContext, - texture_create_info: VulkanTextureCreateInfo>, - ) -> Index { - let texture = - VulkanTexture::new(vulkan_context, &mut self.descriptors, texture_create_info); - self.user_textures.insert(texture) - } - - /// Clean up all Vulkan related handles on this instance. You'll probably want to call this when the program ends, but - /// before you've cleaned up your [`ash::Device`], or you'll receive warnings from the Vulkan Validation Layers. - /// - /// ## Safety - /// - After calling this function, this instance will be **unusable**. You **must not** make any further calls on this instance - /// or you will have a terrible time. - /// - `device` must be the same [`ash::Device`] used to create this instance. - pub unsafe fn cleanup(&self, device: &ash::Device) { - device.device_wait_idle().unwrap(); - self.descriptors.cleanup(device); - for (_, texture) in &self.user_textures { - texture.cleanup(device); - } - device.destroy_pipeline_layout(self.pipeline_layout, None); - 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); - } - - /// 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(&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::default() - .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/src/lib.rs b/src/lib.rs index aaec1ec..e98c7c9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,420 +1,68 @@ -mod buffer; -mod descriptors; -pub mod lazy_renderer; -pub mod vulkan_context; -pub mod vulkan_texture; +pub use allocator::Allocator; +pub use ash; +pub use context::Context; +pub use draw_params::DrawParams; +pub use pipeline::Pipeline; +pub use renderer::Renderer; +pub use sub_renderer::SubRenderer; -use ash::vk; -use glam::{Vec2, Vec4}; -pub use lazy_renderer::{DrawCall, LazyRenderer, Workflow}; -use winit::{ - event_loop::ActiveEventLoop, - window::{Window, WindowAttributes}, -}; - -pub use crate::vulkan_texture::NO_TEXTURE_ID; -use crate::{lazy_renderer::RenderSurface, vulkan_context::VulkanContext}; - -#[derive(Default, Debug, Clone, Copy)] -pub struct Vertex { - pub position: Vec4, - pub colour: Vec4, - pub uv: Vec2, -} - -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct SwapchainInfo { - pub image_count: u32, - pub resolution: vk::Extent2D, - pub format: vk::Format, -} - -impl Vertex { - pub fn new, U: Into>(position: T, colour: T, uv: U) -> Self { - Self { - position: position.into(), - colour: colour.into(), - uv: uv.into(), - } - } -} - -pub fn find_memorytype_index( - memory_req: &vk::MemoryRequirements, - memory_prop: &vk::PhysicalDeviceMemoryProperties, - flags: vk::MemoryPropertyFlags, -) -> Option { - memory_prop.memory_types[..memory_prop.memory_type_count as _] - .iter() - .enumerate() - .find(|(index, memory_type)| { - (1 << index) & memory_req.memory_type_bits != 0 - && memory_type.property_flags & flags == flags - }) - .map(|(index, _memory_type)| index as _) -} - -#[derive(Default, Debug, Clone)] -pub struct LazyVulkanBuilder { - pub fragment_shader: Option>, - pub vertex_shader: Option>, - pub initial_indices: Vec, - pub initial_vertices: Vec, - pub with_present: bool, - pub window_size: Option, -} - -impl LazyVulkanBuilder { - pub fn fragment_shader(mut self, shader: &[u8]) -> Self { - self.fragment_shader = Some(shader.to_vec()); - self - } - - pub fn vertex_shader(mut self, shader: &[u8]) -> Self { - self.vertex_shader = Some(shader.to_vec()); - self - } - - pub fn initial_vertices(mut self, vertices: &[Vertex]) -> Self { - self.initial_vertices = vertices.to_vec(); - self - } - - pub fn initial_indices(mut self, indices: &[u32]) -> Self { - self.initial_indices = indices.to_vec(); - self - } - - pub fn with_present(mut self, present: bool) -> Self { - self.with_present = present; - self - } - - pub fn window_size(mut self, extent: vk::Extent2D) -> Self { - self.window_size = Some(extent); - self - } +use core::Core; +use std::sync::Arc; - pub fn build(self, active_event_loop: &ActiveEventLoop) -> (LazyVulkan, LazyRenderer) { - let window_resolution = self.window_size.unwrap_or(vk::Extent2D { - width: 500, - height: 500, - }); - - let window_width = window_resolution.width; - let window_height = window_resolution.height; - - let window = active_event_loop - .create_window( - WindowAttributes::default() - .with_title("Lazy Vulkan") - .with_inner_size(winit::dpi::LogicalSize::new( - f64::from(window_width), - f64::from(window_height), - )), - ) - .unwrap(); - - let (vulkan, render_surface) = LazyVulkan::new(window, window_resolution); - let renderer = LazyRenderer::new(vulkan.context(), render_surface, &self); - - (vulkan, renderer) - } -} +use ash::vk; +use swapchain::Swapchain; + +mod allocator; +mod context; +mod core; +mod depth_buffer; +mod draw_params; +mod pipeline; +mod renderer; +mod sub_renderer; +mod swapchain; pub struct LazyVulkan { - context: VulkanContext, + #[allow(unused)] + core: Core, + #[allow(unused)] + context: Arc, + pub renderer: Renderer, pub window: winit::window::Window, - pub surface: Surface, - pub swapchain: vk::SwapchainKHR, - pub swapchain_images: Vec, - pub swapchain_loader: ash::khr::swapchain::Device, - - 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, -} - -pub struct Surface { - pub surface: vk::SurfaceKHR, - pub surface_loader: ash::khr::surface::Instance, - pub surface_format: vk::SurfaceFormatKHR, - pub surface_resolution: vk::Extent2D, - pub present_mode: vk::PresentModeKHR, - pub desired_image_count: u32, } impl LazyVulkan { - pub fn builder() -> LazyVulkanBuilder { - Default::default() - } - - pub fn context(&self) -> &VulkanContext { - &self.context - } - - /// 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 - fn new(window: Window, window_resolution: vk::Extent2D) -> (Self, RenderSurface) { - let (context, surface) = VulkanContext::new_with_surface(&window, window_resolution); - let device = &context.device; - let instance = &context.instance; - let swapchain_loader = ash::khr::swapchain::Device::new(instance, device); - let (swapchain, swapchain_images, swapchain_image_views) = - create_swapchain(&context, &surface, &swapchain_loader, None); - - let fence_create_info = - vk::FenceCreateInfo::default().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 render_surface = RenderSurface { - resolution: surface.surface_resolution, - format: surface.surface_format.format, - image_views: swapchain_image_views, - }; - - ( - Self { - window, - context, - surface, - swapchain_loader, - swapchain, - swapchain_images, - present_complete_semaphore, - rendering_complete_semaphore, - draw_commands_reuse_fence, - setup_commands_reuse_fence, - }, - render_surface, - ) - } - - pub fn resized(&mut self, window_width: u32, window_height: u32) -> RenderSurface { - log::trace!("Vulkan Resized: {window_width}, {window_height}"); - unsafe { - let device = &self.context.device; - device.device_wait_idle().unwrap(); - self.surface.surface_resolution = vk::Extent2D { - width: window_width, - height: window_height, - }; - let (new_swapchain, _, new_present_image_views) = create_swapchain( - &self.context, - &self.surface, - &self.swapchain_loader, - Some(self.swapchain), - ); - - self.destroy_swapchain(self.swapchain); - self.swapchain = new_swapchain; - - log::trace!("OK! Swapchain recreated"); - - RenderSurface { - resolution: self.surface.surface_resolution, - format: self.surface.surface_format.format, - image_views: new_present_image_views, - } + pub fn from_window(window: winit::window::Window) -> Self { + let core = Core::from_window(&window); + let context = Arc::new(Context::new(&core)); + let swapchain = Swapchain::new(&context.device, &core, &window, vk::SwapchainKHR::null()); + let renderer = Renderer::new(&core, context.clone(), swapchain); + + LazyVulkan { + core, + context, + renderer, + window, } } - unsafe fn destroy_swapchain(&self, swapchain: vk::SwapchainKHR) { - self.swapchain_loader.destroy_swapchain(swapchain, None); - } - - pub fn render_begin(&self) -> u32 { - let (present_index, _) = unsafe { - self.swapchain_loader - .acquire_next_image( - self.swapchain, - std::u64::MAX, - self.present_complete_semaphore, - vk::Fence::null(), - ) - .unwrap() - }; - - let device = &self.context.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.context.draw_command_buffer, - vk::CommandBufferResetFlags::RELEASE_RESOURCES, - ) - .unwrap(); - device - .begin_command_buffer( - self.context.draw_command_buffer, - &vk::CommandBufferBeginInfo::default() - .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT), - ) - .unwrap(); + pub fn draw(&mut self, state: &S, sub_renderers: &mut [Box>]) { + for sub_renderer in &mut *sub_renderers { + sub_renderer.update_state(state); } - present_index + self.renderer.draw(sub_renderers); } - pub fn render_end(&self, present_index: u32, wait_semaphores: &[vk::Semaphore]) { - let device = &self.context.device; - unsafe { - device - .end_command_buffer(self.context.draw_command_buffer) - .unwrap(); - let swapchains = [self.swapchain]; - let image_indices = [present_index]; - let submit_info = vk::SubmitInfo::default() - .wait_semaphores(wait_semaphores) - .wait_dst_stage_mask(&[vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT]) - .command_buffers(std::slice::from_ref(&self.context.draw_command_buffer)) - .signal_semaphores(std::slice::from_ref(&self.rendering_complete_semaphore)); - - device - .queue_submit( - self.context.queue, - std::slice::from_ref(&submit_info), - self.draw_commands_reuse_fence, - ) - .unwrap(); - - match self.swapchain_loader.queue_present( - self.context.queue, - &vk::PresentInfoKHR::default() - .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) => { - log::trace!("Swapchain is suboptimal!") - } - Err(e) => panic!("Error presenting: {e:?}"), - _ => {} - } - }; - } -} - -fn create_swapchain( - context: &VulkanContext, - surface: &Surface, - swapchain_loader: &ash::khr::swapchain::Device, - previous_swapchain: Option, -) -> (vk::SwapchainKHR, Vec, Vec) { - let device = &context.device; - - let mut swapchain_create_info = vk::SwapchainCreateInfoKHR::default() - .surface(surface.surface) - .min_image_count(surface.desired_image_count) - .image_color_space(surface.surface_format.color_space) - .image_format(surface.surface_format.format) - .image_extent(surface.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(surface.present_mode) - .clipped(true) - .image_array_layers(1); - - if let Some(old_swapchain) = previous_swapchain { - swapchain_create_info.old_swapchain = old_swapchain + pub fn resize(&mut self, width: u32, height: u32) { + self.renderer.swapchain.extent = vk::Extent2D { width, height }; + self.renderer.swapchain.needs_update = true; } - - 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 = - create_swapchain_image_views(&present_images, surface.surface_format.format, device); - - (swapchain, present_images, present_image_views) -} - -pub fn create_swapchain_image_views( - present_images: &[vk::Image], - surface_format: vk::Format, - device: &ash::Device, -) -> Vec { - present_images - .iter() - .map(|&image| { - let create_view_info = vk::ImageViewCreateInfo::default() - .view_type(vk::ImageViewType::TYPE_2D) - .format(surface_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() } -#[cfg(test)] -impl Drop for LazyVulkan { - fn drop(&mut self) { - unsafe { - let device = &self.context.device; - device.device_wait_idle().unwrap(); - device.destroy_semaphore(self.present_complete_semaphore, None); - device.destroy_semaphore(self.rendering_complete_semaphore, None); - device.destroy_fence(self.draw_commands_reuse_fence, None); - device.destroy_fence(self.setup_commands_reuse_fence, None); - device.destroy_command_pool(self.context.command_pool, None); - self.destroy_swapchain(self.swapchain); - device.destroy_device(None); - self.surface - .surface_loader - .destroy_surface(self.surface.surface, None); - self.context.instance.destroy_instance(None); - } - } -} +const FULL_IMAGE: vk::ImageSubresourceRange = vk::ImageSubresourceRange { + aspect_mask: vk::ImageAspectFlags::COLOR, + base_mip_level: 0, + level_count: vk::REMAINING_MIP_LEVELS, + base_array_layer: 0, + layer_count: vk::REMAINING_ARRAY_LAYERS, +}; diff --git a/src/pipeline.rs b/src/pipeline.rs new file mode 100644 index 0000000..b015c8d --- /dev/null +++ b/src/pipeline.rs @@ -0,0 +1,139 @@ +use std::{path::Path, sync::Arc}; + +use ash::vk; + +use super::{context::Context, depth_buffer::DEPTH_FORMAT}; + +#[derive(Clone)] +pub struct Pipeline { + pub handle: vk::Pipeline, + pub layout: vk::PipelineLayout, + #[allow(unused)] + pub context: Arc, +} + +impl Pipeline { + pub fn new( + context: Arc, + format: vk::Format, + vertex_shader: impl AsRef, + fragment_shader: impl AsRef, + ) -> Self { + let device = &context.device; + + let layout = unsafe { + device.create_pipeline_layout( + &vk::PipelineLayoutCreateInfo::default().push_constant_ranges(&[ + vk::PushConstantRange::default() + .size(std::mem::size_of::() as u32) + .stage_flags(vk::ShaderStageFlags::ALL_GRAPHICS), + ]), + None, + ) + } + .unwrap(); + + let handle = unsafe { + device.create_graphics_pipelines( + vk::PipelineCache::null(), + &[vk::GraphicsPipelineCreateInfo::default() + .stages(&[ + vk::PipelineShaderStageCreateInfo::default() + .name(c"main") + .module(load_module(vertex_shader, &context)) + .stage(vk::ShaderStageFlags::VERTEX), + vk::PipelineShaderStageCreateInfo::default() + .name(c"main") + .module(load_module(fragment_shader, &context)) + .stage(vk::ShaderStageFlags::FRAGMENT), + ]) + .vertex_input_state(&vk::PipelineVertexInputStateCreateInfo::default()) + .input_assembly_state( + &vk::PipelineInputAssemblyStateCreateInfo::default() + .topology(vk::PrimitiveTopology::TRIANGLE_LIST), + ) + .viewport_state( + &vk::PipelineViewportStateCreateInfo::default() + .scissor_count(1) + .viewport_count(1), + ) + .dynamic_state( + &vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&[ + vk::DynamicState::SCISSOR, + vk::DynamicState::VIEWPORT, + ]), + ) + .rasterization_state( + &vk::PipelineRasterizationStateCreateInfo::default() + .front_face(vk::FrontFace::COUNTER_CLOCKWISE) + .cull_mode(vk::CullModeFlags::BACK) + .polygon_mode(vk::PolygonMode::FILL) + .line_width(1.0), + ) + .depth_stencil_state( + &vk::PipelineDepthStencilStateCreateInfo::default() + .depth_write_enable(true) + .depth_test_enable(true) + .depth_compare_op(vk::CompareOp::GREATER_OR_EQUAL) + .stencil_test_enable(false) + .depth_bounds_test_enable(false) + .max_depth_bounds(1.), + ) + .color_blend_state( + &vk::PipelineColorBlendStateCreateInfo::default().attachments(&[ + vk::PipelineColorBlendAttachmentState::default() + .blend_enable(false) + .color_write_mask(vk::ColorComponentFlags::RGBA), + ]), + ) + .multisample_state( + &vk::PipelineMultisampleStateCreateInfo::default() + .rasterization_samples(vk::SampleCountFlags::TYPE_1), + ) + .layout(layout) + .push_next( + &mut vk::PipelineRenderingCreateInfo::default() + .depth_attachment_format(DEPTH_FORMAT) + .color_attachment_formats(&[format]), + )], + None, + ) + } + .unwrap()[0]; + + Self { + context, + layout, + handle, + } + } + + pub fn update_registers( + &self, + draw_command_buffer: vk::CommandBuffer, + context: &Context, + registers: &Registers, + ) { + unsafe { + context.device.cmd_push_constants( + draw_command_buffer, + self.layout, + vk::ShaderStageFlags::ALL_GRAPHICS, + 0, + bytemuck::bytes_of(registers), + ) + }; + } +} + +fn load_module(path: impl AsRef, context: &Context) -> vk::ShaderModule { + let mut file = std::fs::File::open(path).unwrap(); + let words = ash::util::read_spv(&mut file).unwrap(); + + unsafe { + context + .device + .create_shader_module(&vk::ShaderModuleCreateInfo::default().code(&words), None) + } + .unwrap() +} diff --git a/src/renderer.rs b/src/renderer.rs new file mode 100644 index 0000000..2e6ce7f --- /dev/null +++ b/src/renderer.rs @@ -0,0 +1,323 @@ +use std::{sync::Arc, u64}; + +use ash::vk::{self}; + +use crate::{core::Core, draw_params::DrawParams, sub_renderer::SubRenderer}; + +use super::{ + allocator::Allocator, + context::Context, + depth_buffer::{DepthBuffer, DEPTH_RANGE}, + swapchain::{Drawable, Swapchain}, + FULL_IMAGE, +}; + +pub struct Renderer { + pub context: Arc, + pub fence: vk::Fence, + pub swapchain: Swapchain, + pub depth_buffer: DepthBuffer, + pub allocator: Allocator, + #[cfg(any(target_os = "macos", target_os = "ios"))] + pub dynamic_rendering_pfn: ash::khr::dynamic_rendering::Device, + #[cfg(any(target_os = "macos", target_os = "ios"))] + pub sync2_pfn: ash::khr::synchronization2::Device, +} + +impl Renderer { + pub(crate) fn new(core: &Core, context: Arc, swapchain: Swapchain) -> Self { + let device = &context.device; + + let fence = unsafe { + device.create_fence( + &vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED), + None, + ) + } + .unwrap(); + + #[cfg(any(target_os = "macos", target_os = "ios"))] + let dynamic_rendering_pfn = + ash::khr::dynamic_rendering::Device::new(&core.instance, device); + #[cfg(any(target_os = "macos", target_os = "ios"))] + let sync2_pfn = ash::khr::synchronization2::Device::new(&core.instance, device); + + let allocator = Allocator::new(context.clone(), sync2_pfn.clone()); + let depth_buffer = DepthBuffer::new(&context, &swapchain); + + Self { + context, + fence, + swapchain, + depth_buffer, + allocator, + dynamic_rendering_pfn, + sync2_pfn, + } + } + + pub fn draw(&mut self, sub_renderers: &mut [Box>]) { + // Begin rendering + let drawable = self.begin_rendering(); + + // Stage transfers for the next frame + for subrenderer in &mut *sub_renderers { + subrenderer.stage_transfers(&mut self.allocator); + } + + // Draw with our sub-renderers + for subrenderer in &mut *sub_renderers { + let params = DrawParams::new( + self.context.draw_command_buffer, + drawable, + self.depth_buffer, + ); + subrenderer.draw(&self.context, params); + } + + // End rendering + self.end_rendering(drawable); + + // Present + self.swapchain + .present(drawable, self.context.graphics_queue); + } + + fn begin_rendering(&mut self) -> Drawable { + let device = &self.context.device; + + if self.swapchain.needs_update { + unsafe { device.device_wait_idle().unwrap() }; + self.swapchain.resize(&self.context.device); + } + + let drawable = loop { + if let Some(drawable) = self.swapchain.get_drawable() { + break drawable; + } + + unsafe { device.device_wait_idle().unwrap() }; + self.swapchain.resize(&self.context.device); + }; + + // Get a `Drawable` from the swapchain + let render_area = drawable.extent; + + // Block the CPU until we're done rendering the previous frame + unsafe { + device + .wait_for_fences(&[self.fence], true, u64::MAX) + .unwrap(); + device.reset_fences(&[self.fence]).unwrap(); + } + + // Begin the command buffer + let command_buffer = self.context.draw_command_buffer; + unsafe { + device + .begin_command_buffer(command_buffer, &vk::CommandBufferBeginInfo::default()) + .unwrap() + }; + + // Execute any pending transfers from the previous frame + self.allocator.execute_transfers(); + + unsafe { + // Transition the rendering attachments into their correct state + self.cmd_pipeline_barrier2( + command_buffer, + &vk::DependencyInfo::default().image_memory_barriers(&[ + // Swapchain image + vk::ImageMemoryBarrier2::default() + .subresource_range(FULL_IMAGE) + .image(drawable.image) + .src_access_mask(vk::AccessFlags2::NONE) + .src_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) + .dst_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE) + .dst_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) + .old_layout(vk::ImageLayout::UNDEFINED) + .new_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL), + // Depth buffer + vk::ImageMemoryBarrier2::default() + .subresource_range(DEPTH_RANGE) + .image(self.depth_buffer.image) + .src_access_mask(vk::AccessFlags2::empty()) + .src_stage_mask(vk::PipelineStageFlags2::empty()) + .dst_access_mask( + vk::AccessFlags2::DEPTH_STENCIL_ATTACHMENT_READ + | vk::AccessFlags2::DEPTH_STENCIL_ATTACHMENT_WRITE, + ) + .dst_stage_mask(vk::PipelineStageFlags2::EARLY_FRAGMENT_TESTS) + .old_layout(vk::ImageLayout::UNDEFINED) + .new_layout(vk::ImageLayout::DEPTH_ATTACHMENT_OPTIMAL), + ]), + ); + + // Begin rendering + self.cmd_begin_rendering( + command_buffer, + &vk::RenderingInfo::default() + .render_area(render_area.into()) + .layer_count(1) + .depth_attachment( + &vk::RenderingAttachmentInfo::default() + .image_view(self.depth_buffer.view) + .image_layout(vk::ImageLayout::DEPTH_ATTACHMENT_OPTIMAL) + .load_op(vk::AttachmentLoadOp::CLEAR) + .store_op(vk::AttachmentStoreOp::DONT_CARE) + .clear_value(vk::ClearValue { + depth_stencil: vk::ClearDepthStencilValue { + depth: 0.0, + stencil: 0, + }, + }), + ) + .color_attachments(&[vk::RenderingAttachmentInfo::default() + .image_view(drawable.view) + .image_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .load_op(vk::AttachmentLoadOp::CLEAR) + .store_op(vk::AttachmentStoreOp::STORE) + .clear_value(vk::ClearValue { + color: vk::ClearColorValue { + float32: [0.1, 0.1, 0.1, 1.0], + }, + })]), + ); + + // Set the dynamic state + device.cmd_set_scissor(command_buffer, 0, &[render_area.into()]); + device.cmd_set_viewport( + command_buffer, + 0, + &[vk::Viewport::default() + .width(render_area.width as _) + .height(render_area.height as _) + .max_depth(1.)], + ); + } + + drawable + } + + fn end_rendering(&self, drawable: Drawable) { + let device = &self.context.device; + let queue = self.context.graphics_queue; + let command_buffer = self.context.draw_command_buffer; + let swapchain_image = drawable.image; + + unsafe { + // End rendering + self.cmd_end_rendering(command_buffer); + + // Next, transition the color attachment into the present state + self.cmd_pipeline_barrier2( + command_buffer, + &vk::DependencyInfo::default().image_memory_barriers(&[ + vk::ImageMemoryBarrier2::default() + .subresource_range(FULL_IMAGE) + .image(swapchain_image) + .src_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE) + .src_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) + .dst_access_mask(vk::AccessFlags2::NONE) + .dst_stage_mask(vk::PipelineStageFlags2::BOTTOM_OF_PIPE) + .old_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL) + .new_layout(vk::ImageLayout::PRESENT_SRC_KHR), + ]), + ); + + // End the command buffer + device.end_command_buffer(command_buffer).unwrap(); + + // Submit the work to the queue + self.queue_submit2( + queue, + &[vk::SubmitInfo2::default() + .command_buffer_infos(&[ + vk::CommandBufferSubmitInfo::default().command_buffer(command_buffer) + ]) + .wait_semaphore_infos(&[vk::SemaphoreSubmitInfo::default() + .semaphore(drawable.image_available) + .stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT)]) + .signal_semaphore_infos(&[vk::SemaphoreSubmitInfo::default() + .semaphore(drawable.rendering_complete) + .stage_mask(vk::PipelineStageFlags2::ALL_COMMANDS)])], + self.fence, + ); + } + } + + #[cfg(not(any(target_os = "macos", target_os = "ios")))] + pub unsafe fn cmd_pipeline_barrier2( + &self, + command_buffer: vk::CommandBuffer, + dependency_info: &vk::DependencyInfo, + ) { + self.context + .device + .cmd_pipeline_barrier2(command_buffer, dependency_info); + } + + #[cfg(any(target_os = "macos", target_os = "ios"))] + pub unsafe fn cmd_pipeline_barrier2( + &self, + command_buffer: vk::CommandBuffer, + dependency_info: &vk::DependencyInfo, + ) { + self.sync2_pfn + .cmd_pipeline_barrier2(command_buffer, dependency_info); + } + + #[cfg(not(any(target_os = "macos", target_os = "ios")))] + pub unsafe fn cmd_begin_rendering( + &self, + command_buffer: vk::CommandBuffer, + rendering_info: &vk::RenderingInfo, + ) { + self.context + .device + .cmd_begin_rendering(command_buffer, rendering_info); + } + + #[cfg(any(target_os = "macos", target_os = "ios"))] + pub unsafe fn cmd_begin_rendering( + &self, + command_buffer: vk::CommandBuffer, + rendering_info: &vk::RenderingInfo, + ) { + self.dynamic_rendering_pfn + .cmd_begin_rendering(command_buffer, rendering_info); + } + + #[cfg(not(any(target_os = "macos", target_os = "ios")))] + pub unsafe fn cmd_end_rendering(&self, command_buffer: vk::CommandBuffer) { + self.context.device.cmd_end_rendering(command_buffer); + } + + #[cfg(any(target_os = "macos", target_os = "ios"))] + pub unsafe fn cmd_end_rendering(&self, command_buffer: vk::CommandBuffer) { + self.dynamic_rendering_pfn.cmd_end_rendering(command_buffer); + } + + #[cfg(not(any(target_os = "macos", target_os = "ios")))] + pub unsafe fn queue_submit2( + &self, + queue: vk::Queue, + submits: &[vk::SubmitInfo2KHR], + fence: vk::Fence, + ) { + self.context + .device + .queue_submit2(queue, submits, fence) + .unwrap() + } + + #[cfg(any(target_os = "macos", target_os = "ios"))] + pub unsafe fn queue_submit2( + &self, + queue: vk::Queue, + submits: &[vk::SubmitInfo2KHR], + fence: vk::Fence, + ) { + self.sync2_pfn.queue_submit2(queue, submits, fence).unwrap() + } +} diff --git a/src/sub_renderer.rs b/src/sub_renderer.rs new file mode 100644 index 0000000..62e92e5 --- /dev/null +++ b/src/sub_renderer.rs @@ -0,0 +1,33 @@ +use ash::vk; + +use crate::{allocator::Allocator, context::Context, draw_params::DrawParams, pipeline::Pipeline}; + +pub trait SubRenderer { + type State; + + fn draw(&mut self, context: &Context, params: DrawParams); + fn stage_transfers(&mut self, allocator: &mut Allocator); + fn update_state(&mut self, state: &Self::State); + + /// Convenience function to Generally Do the right thing. Ensure that: + /// + /// - [`draw command buffer`] is in the RECORDING state + /// - no other rendering is in progress + fn begin_rendering( + &self, + draw_command_buffer: vk::CommandBuffer, + context: &Context, + pipeline: &Pipeline, + ) { + let device = &context.device; + + unsafe { + // Bind the pipeline + device.cmd_bind_pipeline( + draw_command_buffer, + vk::PipelineBindPoint::GRAPHICS, + pipeline.handle, + ); + } + } +} diff --git a/src/swapchain.rs b/src/swapchain.rs new file mode 100644 index 0000000..1bcf3d0 --- /dev/null +++ b/src/swapchain.rs @@ -0,0 +1,237 @@ +use ash::vk; +use winit::raw_window_handle::{HasDisplayHandle, HasWindowHandle}; + +pub struct Swapchain { + pub surface_handle: vk::SurfaceKHR, + pub surface_fn: ash::khr::surface::Instance, + pub swapchain_handle: vk::SwapchainKHR, + pub swapchain_fn: ash::khr::swapchain::Device, + pub images: Vec, + pub image_views: Vec, + pub extent: vk::Extent2D, + pub format: vk::Format, + pub needs_update: bool, + image_available: vk::Semaphore, + capabilities: vk::SurfaceCapabilitiesKHR, + rendering_complete_semaphores: [vk::Semaphore; 3], +} + +impl Swapchain { + pub(crate) fn new( + device: &ash::Device, + core: &super::core::Core, + window: &winit::window::Window, + old_swapchain: vk::SwapchainKHR, + ) -> Self { + let entry = &core.entry; + let instance = &core.instance; + let window_handle = window.window_handle().unwrap().as_raw(); + let display_handle = window.display_handle().unwrap().as_raw(); + let extent = vk::Extent2D { + width: window.inner_size().width, + height: window.inner_size().height, + }; + + let surface_handle = unsafe { + ash_window::create_surface(entry, instance, display_handle, window_handle, None) + } + .unwrap(); + + let surface_fn = ash::khr::surface::Instance::new(entry, instance); + let surface_formats = unsafe { + surface_fn.get_physical_device_surface_formats(core.physical_device, surface_handle) + } + .unwrap(); + + let format_preferences = [vk::Format::B8G8R8A8_SRGB, vk::Format::R8G8B8A8_SRGB]; + + let format = *format_preferences + .iter() + .find(|&&f| surface_formats.iter().any(|sf| sf.format == f)) + .expect("Desired swapchain format unavailable"); + + let capabilities = unsafe { + surface_fn + .get_physical_device_surface_capabilities(core.physical_device, surface_handle) + } + .unwrap(); + + let swapchain_fn = ash::khr::swapchain::Device::new(instance, device); + + let (swapchain_handle, images, image_views) = build_swapchain( + device, + old_swapchain, + extent, + surface_handle, + format, + capabilities, + &swapchain_fn, + ); + + let image_available = + unsafe { device.create_semaphore(&vk::SemaphoreCreateInfo::default(), None) }.unwrap(); + + let rendering_complete_semaphores = [unsafe { + device.create_semaphore(&vk::SemaphoreCreateInfo::default(), None) + } + .unwrap(); 3]; + Self { + surface_handle, + surface_fn, + swapchain_handle, + swapchain_fn, + images, + image_views, + extent, + format, + needs_update: false, + image_available, + capabilities, + rendering_complete_semaphores, + } + } + + pub fn get_drawable(&mut self) -> Option { + let (index, suboptimal) = match unsafe { + self.swapchain_fn.acquire_next_image( + self.swapchain_handle, + u64::MAX, + self.image_available, + vk::Fence::null(), + ) + } { + Ok(x) => x, + Err(vk::Result::ERROR_OUT_OF_DATE_KHR) => { + self.needs_update = true; + return None; + } + Err(e) => panic!("{e:?}"), + }; + + if suboptimal { + self.needs_update = true; + } + + Some(Drawable { + image: self.images[index as usize], + view: self.image_views[index as usize], + image_available: self.image_available, + index, + extent: self.extent, + rendering_complete: self.rendering_complete_semaphores[index as usize], + }) + } + + pub fn resize(&mut self, device: &ash::Device) { + // Create a new swapchain + let (swapchain_handle, images, image_views) = build_swapchain( + device, + self.swapchain_handle, + self.extent, + self.surface_handle, + self.format, + self.capabilities, + &self.swapchain_fn, + ); + + // Destroy the old one + unsafe { + self.swapchain_fn + .destroy_swapchain(self.swapchain_handle, None) + }; + + // Destroy its image views + for image_view in self.image_views.drain(..) { + unsafe { device.destroy_image_view(image_view, None) }; + } + + self.swapchain_handle = swapchain_handle; + self.images = images; + self.image_views = image_views; + self.needs_update = false; + } + + pub fn present(&self, drawable: Drawable, queue: vk::Queue) { + unsafe { + self.swapchain_fn + .queue_present( + queue, + &vk::PresentInfoKHR::default() + .wait_semaphores(&[drawable.rendering_complete]) + .image_indices(&[drawable.index]) + .swapchains(&[self.swapchain_handle]), + ) + .unwrap(); + } + } +} + +fn build_swapchain( + device: &ash::Device, + old_swapchain: vk::SwapchainKHR, + extent: vk::Extent2D, + surface_handle: vk::SurfaceKHR, + format: vk::Format, + capabilities: vk::SurfaceCapabilitiesKHR, + swapchain_fn: &ash::khr::swapchain::Device, +) -> (vk::SwapchainKHR, Vec, Vec) { + let swapchain_handle = unsafe { + swapchain_fn.create_swapchain( + &vk::SwapchainCreateInfoKHR::default() + .surface(surface_handle) + .min_image_count(capabilities.min_image_count + 1) + .image_format(format) + .image_extent(extent) + .image_color_space(vk::ColorSpaceKHR::SRGB_NONLINEAR) + .image_array_layers(1) + .image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT) + .image_sharing_mode(vk::SharingMode::EXCLUSIVE) + .queue_family_indices(&[0]) + .clipped(true) + .present_mode(vk::PresentModeKHR::FIFO) + .pre_transform(capabilities.current_transform) + .composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE) + .old_swapchain(old_swapchain), + None, + ) + } + .unwrap(); + + let (images, image_views) = unsafe { swapchain_fn.get_swapchain_images(swapchain_handle) } + .unwrap() + .into_iter() + .map(|image| { + let view = unsafe { + device.create_image_view( + &vk::ImageViewCreateInfo::default() + .view_type(vk::ImageViewType::TYPE_2D) + .image(image) + .format(format) + .subresource_range( + vk::ImageSubresourceRange::default() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .base_mip_level(0) + .level_count(1) + .base_array_layer(0) + .layer_count(1), + ), + None, + ) + } + .unwrap(); + + (image, view) + }) + .unzip(); + (swapchain_handle, images, image_views) +} + +#[derive(Debug, Copy, Clone)] +pub struct Drawable { + pub image: vk::Image, + pub view: vk::ImageView, + pub image_available: vk::Semaphore, + pub rendering_complete: vk::Semaphore, + pub index: u32, + pub extent: vk::Extent2D, +}