diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 9749726..caa545e 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -6,14 +6,11 @@ "command": "run", "args": [ "--example", - "triangle" + "textured_cube" ], "problemMatcher": [ "$rustc" ], - "env": { - "RUST_LOG": "debug", - }, "group": { "kind": "test", "isDefault": true diff --git a/Cargo.toml b/Cargo.toml index 79b5dcf..0867d9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,9 +14,14 @@ bytemuck = "1.13.0" glam = "0.22.0" log = "0.4.17" raw-window-handle = "0.5.0" +simple-easing = "1.0.1" thunderdome = "0.6.0" uds_windows = "1.0.2" +vk-shader-macros = "0.2.8" winit = "0.28.1" [dev-dependencies] env_logger = "0.10.0" + +[profile.dev.package."*"] +opt-level = 3 diff --git a/examples/remote_client.rs b/examples/remote_client.rs index a35323e..1321356 100644 --- a/examples/remote_client.rs +++ b/examples/remote_client.rs @@ -1,7 +1,5 @@ use lazy_vulkan::vulkan_context::VulkanContext; -use lazy_vulkan::{ - create_swapchain_image_views, DrawCall, SwapchainInfo, Vertex, Workflow, NO_TEXTURE_ID, -}; +use lazy_vulkan::{create_swapchain_image_views, DrawCall, SwapchainInfo, Vertex, NO_TEXTURE_ID}; use std::io::{Read, Write}; #[cfg(not(target_os = "windows"))] use std::os::unix::net::UnixStream; @@ -12,10 +10,6 @@ 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, @@ -53,8 +47,6 @@ pub fn main() -> std::io::Result<()> { // 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); @@ -80,16 +72,17 @@ pub fn main() -> std::io::Result<()> { &vulkan_context.device, ); - let render_surface = lazy_vulkan::lazy_renderer::RenderSurface { - resolution: swapchain_info.resolution, - format: swapchain_info.format, + let render_surface = lazy_vulkan::lazy_renderer::RenderSurface::new( + &vulkan_context, + swapchain_info.resolution, + 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 draw_calls = [DrawCall::new(0, 3, NO_TEXTURE_ID, Default::default())]; let fences = create_fences(&vulkan_context, swapchain_info.image_count); let command_buffers = create_command_buffers(&vulkan_context, swapchain_info.image_count); diff --git a/examples/remote_host.rs b/examples/remote_host.rs index bd28adb..62eb076 100644 --- a/examples/remote_host.rs +++ b/examples/remote_host.rs @@ -15,9 +15,6 @@ use winit::{ platform::run_return::EventLoopExtRunReturn, }; -/// 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"; @@ -43,8 +40,6 @@ pub fn main() { let (mut lazy_vulkan, mut lazy_renderer, mut event_loop) = LazyVulkan::builder() .initial_vertices(&vertices) .initial_indices(&indices) - .fragment_shader(FRAGMENT_SHADER) - .vertex_shader(VERTEX_SHADER) .with_present(true) .build(); @@ -111,7 +106,7 @@ pub fn main() { 0, indices.len() as _, texture_id, - lazy_vulkan::Workflow::Main, + Default::default(), )], ); diff --git a/examples/shaders/triangle.frag.spv b/examples/shaders/triangle.frag.spv deleted file mode 100644 index d0241f6..0000000 Binary files a/examples/shaders/triangle.frag.spv and /dev/null differ diff --git a/examples/shaders/triangle.vert.spv b/examples/shaders/triangle.vert.spv deleted file mode 100644 index 8f067ad..0000000 Binary files a/examples/shaders/triangle.vert.spv and /dev/null differ diff --git a/examples/textured_cube.rs b/examples/textured_cube.rs new file mode 100644 index 0000000..c68d539 --- /dev/null +++ b/examples/textured_cube.rs @@ -0,0 +1,237 @@ +use std::{f32::consts::TAU, time::Instant}; + +use glam::Affine3A; +use lazy_vulkan::{DrawCall, LazyVulkan, Vertex, NO_TEXTURE_ID}; +use winit::{ + event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, + event_loop::ControlFlow, + platform::run_return::EventLoopExtRunReturn, +}; + +pub fn main() { + env_logger::init(); + + // SQUUUUUUUUUUUUUUUARRRRRRRRRE + 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([-1.0, -1.0, 0.0, 1.0], [0.0, 0.0, 1.0, 0.0], [0.0, 0.0]), + Vertex::new([1.0, -1.0, 0.0, 1.0], [0.0, 0.0, 1.0, 0.0], [0.0, 0.0]), + ]; + + let indices = [0, 1, 2, 2, 3, 0]; + + // Alright, let's build some stuff + let (mut lazy_vulkan, mut lazy_renderer, mut event_loop) = LazyVulkan::builder() + .initial_vertices(&vertices) + .initial_indices(&indices) + .with_present(true) + .build(); + + lazy_renderer.camera.position.y = 2.; + lazy_renderer.camera.position.z = 10.; + lazy_renderer.camera.pitch = -15_f32.to_radians(); + + let mut last_sim_update = Instant::now(); + let mut carousel = Carousel::new(); + + // Off we go! + // TODO: How do we share this between examples? + let mut winit_initializing = true; + event_loop.run_return(|event, _, control_flow| { + *control_flow = ControlFlow::Poll; + match event { + Event::WindowEvent { + event: + WindowEvent::CloseRequested + | WindowEvent::KeyboardInput { + input: + KeyboardInput { + state: ElementState::Pressed, + virtual_keycode: Some(VirtualKeyCode::Escape), + .. + }, + .. + }, + .. + } => *control_flow = ControlFlow::Exit, + + Event::NewEvents(cause) => { + if cause == winit::event::StartCause::Init { + winit_initializing = true; + } else { + winit_initializing = false; + } + } + + Event::MainEventsCleared => { + let framebuffer_index = lazy_vulkan.render_begin(); + let dt = Instant::now() - last_sim_update; + if dt.as_secs_f32() > (1. / 120.) { + carousel.update(dt.as_secs_f32()); + last_sim_update = Instant::now(); + } + lazy_renderer.render( + &lazy_vulkan.context(), + framebuffer_index, + &carousel.draw_calls(), + ); + lazy_vulkan + .render_end(framebuffer_index, &[lazy_vulkan.present_complete_semaphore]); + } + Event::WindowEvent { + event: WindowEvent::Resized(size), + .. + } => { + if winit_initializing { + return; + } else { + let new_render_surface = lazy_vulkan.resized(size.width, size.height); + lazy_renderer.update_surface(new_render_surface, &lazy_vulkan.context().device); + } + } + + Event::WindowEvent { + event: + WindowEvent::KeyboardInput { + input: + KeyboardInput { + state: ElementState::Pressed, + virtual_keycode: Some(VirtualKeyCode::A), + .. + }, + .. + }, + .. + } => carousel.rotate(), + _ => (), + } + }); + + // I guess we better do this or else the Dreaded Validation Layers will complain + unsafe { + lazy_renderer.cleanup(&lazy_vulkan.context().device); + } +} + +pub struct Carousel { + quads: Vec, + state: CarouselState, + theta: f32, + animation_state: AnimationState, +} + +impl Carousel { + pub fn new() -> Self { + Self { + quads: create_quads(), + state: CarouselState::Idle, + theta: 0., + animation_state: AnimationState::new(0., 0.), + } + } + + pub fn update(&mut self, dt: f32) { + let rotation_time = self.animation_state.target_time; + + if self.animation_state.elapsed >= rotation_time && self.theta == self.animation_state.end { + self.theta = self.animation_state.end; + self.animation_state.start = self.animation_state.end; + self.animation_state.elapsed = 0.; + self.animation_state.target_time = 1.; + let slice_theta = std::f32::consts::TAU / self.quads.len() as f32; + + for (n, quad) in self.quads.iter_mut().enumerate() { + quad.theta = (n as f32 * slice_theta) + self.theta; + } + + return; + } + + self.animation_state.elapsed += dt; + let progress = self.animation_state.elapsed / rotation_time; + let target = lerp( + self.animation_state.start, + self.animation_state.end, + simple_easing::expo_in(progress), + ); + let delta = target - self.theta; + self.theta += delta; + println!("{progress} - {delta}"); + for quad in &mut self.quads { + quad.theta += delta; + } + } + + pub fn rotate(&mut self) { + let slice_theta = std::f32::consts::TAU / self.quads.len() as f32; + self.animation_state.end = self.animation_state.end + slice_theta; + self.animation_state.start = self.theta; + self.animation_state.elapsed -= 0.5; + if self.animation_state.elapsed <= 0. { + self.animation_state.elapsed = 0.; + } + } + + pub fn draw_calls(&self) -> Vec { + let radius = 3.; + + self.quads + .iter() + .map(|quad| { + let theta = quad.theta; + let x = radius * theta.sin(); + let z = radius * theta.cos(); + let transform = Affine3A::from_translation([x, 0., z].into()); + DrawCall::new(0, 6, NO_TEXTURE_ID, transform) + }) + .collect() + } +} + +fn get_increment(progress: f32, theta: f32) -> f32 { + todo!() +} + +#[derive(PartialEq, PartialOrd)] +enum CarouselState { + Idle, + Rotating(usize), +} + +#[derive(PartialEq, PartialOrd)] +pub struct AnimationState { + start: f32, + end: f32, + elapsed: f32, + target_time: f32, +} + +impl AnimationState { + pub fn new(start: f32, end: f32) -> Self { + Self { + start, + end, + elapsed: 0., + target_time: 1., + } + } +} + +pub struct Quad { + pub theta: f32, +} + +fn lerp(start: f32, end: f32, amount: f32) -> f32 { + start + ((end - start) * amount) +} + +fn create_quads() -> Vec { + let number_of_quads = 10; + let slice_theta = std::f32::consts::TAU / (number_of_quads as f32); + (0..number_of_quads) + .map(|n| Quad { + theta: n as f32 * slice_theta, + }) + .collect() +} diff --git a/examples/triangle.rs b/examples/triangle.rs index b083628..ebe5e88 100644 --- a/examples/triangle.rs +++ b/examples/triangle.rs @@ -1,12 +1,9 @@ -use lazy_vulkan::{DrawCall, LazyVulkan, Vertex, Workflow, NO_TEXTURE_ID}; +use lazy_vulkan::{DrawCall, LazyVulkan, Vertex, NO_TEXTURE_ID}; use winit::{ event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, event_loop::ControlFlow, platform::run_return::EventLoopExtRunReturn, }; -/// 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"); pub fn main() { env_logger::init(); @@ -27,8 +24,7 @@ pub fn main() { let (mut lazy_vulkan, mut lazy_renderer, mut event_loop) = LazyVulkan::builder() .initial_vertices(&vertices) .initial_indices(&indices) - .fragment_shader(FRAGMENT_SHADER) - .vertex_shader(VERTEX_SHADER) + .with_present(true) .build(); // Off we go! @@ -64,7 +60,12 @@ pub fn main() { lazy_renderer.render( &lazy_vulkan.context(), framebuffer_index, - &[DrawCall::new(0, 3, NO_TEXTURE_ID, Workflow::Main)], + &[DrawCall::new( + 0, + indices.len() as _, + NO_TEXTURE_ID, + Default::default(), + )], ); lazy_vulkan .render_end(framebuffer_index, &[lazy_vulkan.present_complete_semaphore]); diff --git a/src/lazy_renderer.rs b/src/lazy_renderer.rs index 05fa67d..b185ab4 100644 --- a/src/lazy_renderer.rs +++ b/src/lazy_renderer.rs @@ -1,44 +1,23 @@ -//! 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 crate::{ + buffer::Buffer, + descriptors::Descriptors, + vulkan_context::VulkanContext, + vulkan_texture::{VulkanTexture, VulkanTextureCreateInfo}, + LazyVulkanBuilder, Vertex, +}; +use std::ffi::CStr; + +use ash::vk; +use ash::vk::PushConstantRange; use bytemuck::{Pod, Zeroable}; use thunderdome::Index; +use vk_shader_macros::include_glsl; -/// 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()`]. +const VERTEX_SHADER: &[u32] = include_glsl!("src/shaders/shader.vert"); +const FRAGMENT_SHADER: &[u32] = include_glsl!("src/shaders/shader.frag"); +pub const DEPTH_FORMAT: vk::Format = vk::Format::D32_SFLOAT; + +/// HELLO WOULD YOU LIKE TO RENDER SOME THINGS???? pub struct LazyRenderer { /// The render pass used to draw render_pass: vk::RenderPass, @@ -58,6 +37,8 @@ pub struct LazyRenderer { user_textures: thunderdome::Arena, /// A wrapper around descriptor set functionality pub descriptors: Descriptors, + /// You know. A camera. + pub camera: Camera, } #[derive(Clone)] @@ -69,6 +50,65 @@ pub struct RenderSurface { pub format: vk::Format, /// The image views to render to. One framebuffer will be created per view pub image_views: Vec, + /// The depth buffers; one per view + pub depth_buffers: Vec, +} + +impl RenderSurface { + pub fn new( + vulkan_context: &VulkanContext, + resolution: vk::Extent2D, + format: vk::Format, + image_views: Vec, + ) -> Self { + let depth_buffers = create_depth_buffers(vulkan_context, resolution, image_views.len()); + Self { + resolution, + format, + image_views, + depth_buffers, + } + } + + /// Safety: + /// + /// After you call this method.. like.. don't use this struct again, basically. + unsafe fn destroy(&mut self, device: &ash::Device) { + self.image_views + .drain(..) + .for_each(|v| device.destroy_image_view(v, None)); + self.depth_buffers.drain(..).for_each(|d| { + d.destory(device); + }); + } +} + +#[derive(Clone, Default)] +pub struct Camera { + pub position: glam::Vec3, + pub pitch: f32, + pub yaw: f32, +} + +impl Camera { + pub fn matrix(&self) -> glam::Affine3A { + let rotation = glam::Quat::from_euler(glam::EulerRot::YXZ, self.yaw, self.pitch, 0.); + glam::Affine3A::from_rotation_translation(rotation, self.position).inverse() + } +} + +#[derive(Clone)] +pub struct DepthBuffer { + pub image: vk::Image, + pub view: vk::ImageView, + pub memory: vk::DeviceMemory, +} +impl DepthBuffer { + unsafe fn destory(&self, device: &ash::Device) { + device.destroy_image_view(self.view, None); + device.destroy_image(self.image, None); + device.free_memory(self.memory, None); + } } #[derive(Clone, Copy, Debug)] @@ -77,16 +117,21 @@ pub struct DrawCall { index_offset: u32, index_count: u32, texture_id: u32, - workflow: Workflow, + transform: glam::Affine3A, } impl DrawCall { - pub fn new(index_offset: u32, index_count: u32, texture_id: u32, workflow: Workflow) -> Self { + pub fn new( + index_offset: u32, + index_count: u32, + texture_id: u32, + transform: glam::Affine3A, + ) -> Self { Self { index_offset, index_count, texture_id, - workflow, + transform, } } } @@ -95,33 +140,19 @@ impl DrawCall { #[derive(Clone, Copy, Debug)] /// Push constant used to determine texture and workflow struct PushConstant { + mvp: glam::Mat4, 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, - } + pub fn new(texture_id: u32, mvp: glam::Mat4) -> Self { + Self { texture_id, mvp } } } -#[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. /// @@ -133,8 +164,6 @@ impl LazyRenderer { 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 { @@ -143,30 +172,57 @@ impl LazyRenderer { 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 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() + }, + vk::AttachmentDescription { + format: DEPTH_FORMAT, + samples: vk::SampleCountFlags::TYPE_1, + load_op: vk::AttachmentLoadOp::CLEAR, + store_op: vk::AttachmentStoreOp::DONT_CARE, + final_layout: vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + ..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 depth_attachment_ref = vk::AttachmentReference { + attachment: 1, + layout: vk::ImageLayout::DEPTH_STENCIL_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() + }, + vk::SubpassDependency { + src_subpass: vk::SUBPASS_EXTERNAL, + src_stage_mask: vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS, + dst_access_mask: vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_READ + | vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE, + dst_stage_mask: vk::PipelineStageFlags::LATE_FRAGMENT_TESTS, + ..Default::default() + }, + ]; let subpass = vk::SubpassDescription::builder() .color_attachments(&color_attachment_refs) + .depth_stencil_attachment(&depth_attachment_ref) .pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS); let renderpass_create_info = vk::RenderPassCreateInfo::builder() @@ -193,16 +249,8 @@ impl LazyRenderer { &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::builder().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::builder().code(&frag_code); + let vertex_shader_info = vk::ShaderModuleCreateInfo::builder().code(VERTEX_SHADER); + let frag_shader_info = vk::ShaderModuleCreateInfo::builder().code(FRAGMENT_SHADER); let vertex_shader_module = unsafe { device @@ -220,11 +268,12 @@ impl LazyRenderer { device .create_pipeline_layout( &vk::PipelineLayoutCreateInfo::builder() - .push_constant_ranges(std::slice::from_ref( - &vk::PushConstantRange::builder() - .stage_flags(vk::ShaderStageFlags::FRAGMENT) - .size(std::mem::size_of::() as _), - )) + .push_constant_ranges(&[PushConstantRange { + size: std::mem::size_of::() as _, + stage_flags: vk::ShaderStageFlags::VERTEX + | vk::ShaderStageFlags::FRAGMENT, + ..Default::default() + }]) .set_layouts(std::slice::from_ref(&descriptors.layout)), None, ) @@ -381,6 +430,7 @@ impl LazyRenderer { index_buffer, vertex_buffer, user_textures: Default::default(), + camera: Default::default(), } } @@ -456,18 +506,21 @@ impl LazyRenderer { std::slice::from_ref(&self.descriptors.set), &[], ); + + let aspect_ratio = self.render_surface.resolution.width as f32 + / self.render_surface.resolution.height as f32; + let mut perspective = + glam::Mat4::perspective_rh(60_f32.to_radians(), aspect_ratio, 0.01, 1000.); + perspective.y_axis[1] *= -1.; + 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. + let mvp: glam::Mat4 = perspective * self.camera.matrix() * draw_call.transform; device.cmd_push_constants( command_buffer, self.pipeline_layout, - vk::ShaderStageFlags::FRAGMENT, + vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT, 0, - bytemuck::bytes_of(&PushConstant::new( - draw_call.texture_id, - draw_call.workflow, - )), + bytemuck::bytes_of(&PushConstant::new(draw_call.texture_id, mvp.into())), ); // Draw the mesh with the indexes we were provided @@ -528,6 +581,7 @@ impl LazyRenderer { /// - 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.render_surface.destroy(device); self.destroy_framebuffers(device); } self.framebuffers = create_framebuffers(&render_surface, self.render_pass, device); @@ -549,8 +603,9 @@ fn create_framebuffers( let framebuffers: Vec = render_surface .image_views .iter() - .map(|&present_image_view| { - let framebuffer_attachments = [present_image_view]; + .zip(&render_surface.depth_buffers) + .map(|(&present_image_view, depth_buffer)| { + let framebuffer_attachments = [present_image_view, depth_buffer.view]; let frame_buffer_create_info = vk::FramebufferCreateInfo::builder() .render_pass(render_pass) .attachments(&framebuffer_attachments) @@ -567,3 +622,23 @@ fn create_framebuffers( .collect(); framebuffers } + +fn create_depth_buffers( + vulkan_context: &VulkanContext, + resolution: vk::Extent2D, + len: usize, +) -> Vec { + (0..len) + .map(|_| { + let (image, memory) = + unsafe { vulkan_context.create_image(&[], resolution, DEPTH_FORMAT) }; + let view = unsafe { vulkan_context.create_image_view(image, DEPTH_FORMAT) }; + + DepthBuffer { + image, + view, + memory, + } + }) + .collect::>() +} diff --git a/src/lib.rs b/src/lib.rs index 3340398..b6f30b4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,7 @@ pub mod vulkan_texture; use ash::vk; use glam::{Vec2, Vec4}; -pub use lazy_renderer::{DrawCall, LazyRenderer, Workflow}; +pub use lazy_renderer::{DrawCall, LazyRenderer}; use winit::{event_loop::EventLoop, window::Window}; pub use crate::vulkan_texture::NO_TEXTURE_ID; @@ -54,8 +54,6 @@ pub fn find_memorytype_index( #[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, @@ -63,16 +61,6 @@ pub struct LazyVulkanBuilder { } 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 @@ -177,11 +165,12 @@ impl LazyVulkan { .unwrap() }; - let render_surface = RenderSurface { - resolution: surface.surface_resolution, - format: surface.surface_format.format, - image_views: swapchain_image_views, - }; + let render_surface = RenderSurface::new( + &context, + surface.surface_resolution, + surface.surface_format.format, + swapchain_image_views, + ); ( Self { @@ -219,13 +208,12 @@ impl LazyVulkan { self.destroy_swapchain(self.swapchain); self.swapchain = new_swapchain; - println!("OK! Swapchain recreated"); - - RenderSurface { - resolution: self.surface.surface_resolution, - format: self.surface.surface_format.format, - image_views: new_present_image_views, - } + RenderSurface::new( + &self.context, + self.surface.surface_resolution, + self.surface.surface_format.format, + new_present_image_views, + ) } } diff --git a/src/shaders/push_constant.glsl b/src/shaders/push_constant.glsl new file mode 100644 index 0000000..f153836 --- /dev/null +++ b/src/shaders/push_constant.glsl @@ -0,0 +1,4 @@ +layout(push_constant) uniform push_constants { + mat4 mvp; + uint texture_id; +}; \ No newline at end of file diff --git a/examples/shaders/triangle.frag b/src/shaders/shader.frag similarity index 51% rename from examples/shaders/triangle.frag rename to src/shaders/shader.frag index d0eaa73..bf84288 100644 --- a/examples/shaders/triangle.frag +++ b/src/shaders/shader.frag @@ -8,10 +8,8 @@ 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; -}; + +#include "push_constant.glsl" void main() { if (texture_id == NO_TEXTURE) { @@ -19,11 +17,6 @@ void main() { 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; - } + vec4 user_texture = texture(textures[texture_id], in_uv); + out_color = in_color * user_texture; } \ No newline at end of file diff --git a/examples/shaders/triangle.vert b/src/shaders/shader.vert similarity index 80% rename from examples/shaders/triangle.vert rename to src/shaders/shader.vert index c2f2858..df1152a 100644 --- a/examples/shaders/triangle.vert +++ b/src/shaders/shader.vert @@ -7,8 +7,10 @@ layout(location = 2) in vec2 in_uv; layout(location = 0) out vec4 out_colour; layout(location = 1) out vec2 out_uv; +#include "push_constant.glsl" + void main() { - gl_Position = in_position; + gl_Position = mvp * in_position; out_colour = in_colour; out_uv = in_uv; } \ No newline at end of file diff --git a/src/vulkan_context.rs b/src/vulkan_context.rs index 7df6530..5bbb87a 100644 --- a/src/vulkan_context.rs +++ b/src/vulkan_context.rs @@ -2,7 +2,7 @@ use ash::vk; use log::info; use winit::window::Window; -use crate::{buffer::Buffer, find_memorytype_index, Surface}; +use crate::{buffer::Buffer, find_memorytype_index, lazy_renderer::DEPTH_FORMAT, Surface}; #[derive(Clone)] /// A wrapper around handles into your Vulkan renderer to call various methods on [`crate::YakuiVulkan`] @@ -195,6 +195,11 @@ impl VulkanContext { let scratch_buffer = Buffer::new(self, vk::BufferUsageFlags::TRANSFER_SRC, image_data); let device = &self.device; + let usage = match format { + DEPTH_FORMAT => vk::ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT, + _ => vk::ImageUsageFlags::TRANSFER_DST | vk::ImageUsageFlags::SAMPLED, + }; + let image = device .create_image( &vk::ImageCreateInfo { @@ -205,7 +210,7 @@ impl VulkanContext { array_layers: 1, samples: vk::SampleCountFlags::TYPE_1, tiling: vk::ImageTiling::OPTIMAL, - usage: vk::ImageUsageFlags::TRANSFER_DST | vk::ImageUsageFlags::SAMPLED, + usage, sharing_mode: vk::SharingMode::EXCLUSIVE, ..Default::default() }, @@ -223,6 +228,11 @@ impl VulkanContext { let image_memory = self.allocate_memory(memory_requirements.size, memory_index); device.bind_image_memory(image, image_memory, 0).unwrap(); + // If we weren't given any image data, we can just return now + if image_data.is_empty() { + return (image, image_memory); + } + self.one_time_command(|command_buffer| { let transfer_barrier = vk::ImageMemoryBarrier { dst_access_mask: vk::AccessFlags::TRANSFER_WRITE, @@ -351,13 +361,17 @@ impl VulkanContext { } pub unsafe fn create_image_view(&self, image: vk::Image, format: vk::Format) -> vk::ImageView { + let aspect_mask = match format { + DEPTH_FORMAT => vk::ImageAspectFlags::DEPTH, + _ => vk::ImageAspectFlags::COLOR, + }; self.device .create_image_view( &vk::ImageViewCreateInfo { image, format, subresource_range: vk::ImageSubresourceRange { - aspect_mask: vk::ImageAspectFlags::COLOR, + aspect_mask, level_count: 1, layer_count: 1, ..Default::default()