From 14fb61a4d508f728a3b0c0ed46ecc913f6058b85 Mon Sep 17 00:00:00 2001 From: Lucas Vas Date: Thu, 14 May 2026 15:32:14 -0400 Subject: [PATCH] feat: render with depth checking --- src/app.rs | 204 ++++++++++++++++----------------- src/common/camera.rs | 2 +- src/gpu/camera.rs | 6 +- src/shaders/triangle_frag.glsl | 43 ++++++- src/shaders/triangle_vert.glsl | 12 +- 5 files changed, 156 insertions(+), 111 deletions(-) diff --git a/src/app.rs b/src/app.rs index dbd91a2..7f11707 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,10 +1,9 @@ -use std::collections::HashMap; +use std::alloc::Layout; use crate::common::mesh::MeshRegistry; use crate::gpu::vertex3::Vertex3; use crate::triangle; use std::error::Error; use std::sync::Arc; -use vulkano::buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer}; use vulkano::command_buffer::allocator::{ StandardCommandBufferAllocator, StandardCommandBufferAllocatorCreateInfo, }; @@ -17,12 +16,10 @@ use vulkano::device::physical::{PhysicalDevice, PhysicalDeviceType}; use vulkano::device::{ Device, DeviceCreateInfo, DeviceExtensions, Queue, QueueCreateInfo, QueueFlags, }; -use vulkano::image::view::ImageView; -use vulkano::image::{Image, ImageUsage}; +use vulkano::image::view::{ImageView, ImageViewCreateInfo}; +use vulkano::image::{Image, ImageAspects, ImageCreateInfo, ImageLayout, ImageSubresourceRange, ImageType, ImageUsage}; use vulkano::instance::{Instance, InstanceCreateFlags, InstanceCreateInfo, InstanceExtensions}; -use vulkano::memory::allocator::{ - AllocationCreateInfo, MemoryAllocator, MemoryTypeFilter, StandardMemoryAllocator, -}; +use vulkano::memory::allocator::{AllocationCreateInfo, MemoryAllocator, MemoryTypeFilter, StandardMemoryAllocator}; use vulkano::pipeline::graphics::GraphicsPipelineCreateInfo; use vulkano::pipeline::graphics::color_blend::{ColorBlendAttachmentState, ColorBlendState}; use vulkano::pipeline::graphics::input_assembly::InputAssemblyState; @@ -37,8 +34,9 @@ use vulkano::shader::ShaderModule; use vulkano::swapchain::{Surface, Swapchain, SwapchainCreateInfo, SwapchainPresentInfo}; use vulkano::sync::GpuFuture; use vulkano::{Validated, VulkanError, VulkanLibrary, swapchain, sync}; -use vulkano::descriptor_set::DescriptorSet; -use vulkano::descriptor_set::layout::DescriptorSetLayout; +use vulkano::format::{ClearValue, Format}; +use vulkano::memory::MemoryType; +use vulkano::pipeline::graphics::depth_stencil::{DepthState, DepthStencilState}; use winit::application::ApplicationHandler; use winit::event::WindowEvent; use winit::event_loop::ActiveEventLoop; @@ -63,7 +61,6 @@ struct VulkanContext { render_pass: Arc, framebuffers: Vec>, pipeline: Arc, - cmd_buffers: Vec>, mesh_registry: Arc, instance_registry: Arc, camera: Camera, @@ -150,12 +147,18 @@ impl VulkanContext { format: swapchain.image_format(), samples: 1, load_op: Clear, - store_op: Store, + store_op: Store + }, + depth: { + format: Format::D16_UNORM, + samples: 1, + load_op: Clear, + store_op: DontCare, } }, pass: { color: [color], - depth_stencil: {}, + depth_stencil: {depth}, } )?; @@ -164,7 +167,11 @@ impl VulkanContext { let frag_shader = triangle::load_fragment(device.clone()) .map_err(|e| format!("Failed to load fragment shader: {e}"))?; - let framebuffers = Self::create_framebuffers(&images, &render_pass)?; + let framebuffers = Self::create_framebuffers( + mem_allocator.clone(), + &images, + &render_pass + )?; let image_extents = swapchain.image_extent(); @@ -206,17 +213,6 @@ impl VulkanContext { instance_registry.clone() ); - let cmd_buffers = Self::get_command_buffers( - &cmd_allocator, - &queue, - &pipeline, - &framebuffers, - &camera, - &camera_resources, - mesh_registry.clone(), - batches, - )?; - Ok(Self { window, device, @@ -231,7 +227,6 @@ impl VulkanContext { frag_shader, framebuffers, pipeline, - cmd_buffers, mesh_registry, instance_registry, camera, @@ -264,11 +259,11 @@ impl VulkanContext { } // Rebuild command buffers - let cmd_buffers = Self::get_command_buffers( + let cmd_buffer = Self::get_command_buffer( &self.cmd_allocator, &self.queue, &self.pipeline, - &self.framebuffers, + self.framebuffers[image_idx as usize].clone(), &self.camera, &self.camera_resources, self.mesh_registry.clone(), @@ -283,7 +278,7 @@ impl VulkanContext { .join(acquire_future) .then_execute( self.queue.clone(), - cmd_buffers[image_idx as usize].clone(), + cmd_buffer.clone(), )? .then_swapchain_present( self.queue.clone(), @@ -350,9 +345,24 @@ impl VulkanContext { /// Creates a new [`Framebuffer`] for each swapchain image. fn create_framebuffers( + alloc: Arc, images: &Vec>, render_pass: &Arc, ) -> Result>, Box> { + let depth_buffer = ImageView::new_default( + Image::new( + alloc.clone(), + ImageCreateInfo { + image_type: ImageType::Dim2d, + format: Format::D16_UNORM, + extent: images[0].extent(), + usage: ImageUsage::DEPTH_STENCIL_ATTACHMENT | ImageUsage::TRANSIENT_ATTACHMENT, + ..Default::default() + }, + AllocationCreateInfo::default() + )? + )?; + let framebuffers = images .iter() .map(|image| { @@ -361,7 +371,7 @@ impl VulkanContext { Framebuffer::new( render_pass.clone(), FramebufferCreateInfo { - attachments: vec![view], + attachments: vec![view, depth_buffer.clone()], ..Default::default() }, ) @@ -413,7 +423,6 @@ impl VulkanContext { device.clone(), None, GraphicsPipelineCreateInfo { - flags: Default::default(), stages: stages.into_iter().collect(), vertex_input_state: Some(vertex_input_state), input_assembly_state: Some(InputAssemblyState::default()), @@ -429,86 +438,80 @@ impl VulkanContext { )), dynamic_state: [DynamicState::Viewport].into_iter().collect(), subpass: Some(subpass.into()), + depth_stencil_state: Some(DepthStencilState { + depth: Some(DepthState::simple()), + ..Default::default() + }), ..GraphicsPipelineCreateInfo::layout(layout) }, )?) } /// Creates a [`PrimaryAutoCommandBuffer`] for each swapchain image. - fn get_command_buffers( + fn get_command_buffer( cmd_allocator: &Arc, queue: &Arc, pipeline: &Arc, - framebuffers: &Vec>, + framebuffer: Arc, camera: &Camera, camera_resources: &CameraResources, mesh_registry: Arc, render_batches: Vec, - ) -> Result>, Box> { - let buffers = framebuffers - .iter() - .filter_map(|framebuffer| { - let mut builder = AutoCommandBufferBuilder::primary( - cmd_allocator.clone(), - queue.queue_family_index(), - CommandBufferUsage::OneTimeSubmit, - ) - .expect("Couldn't create command buffer builder"); + ) -> Result, Box> { + let mut builder = AutoCommandBufferBuilder::primary( + cmd_allocator.clone(), + queue.queue_family_index(), + CommandBufferUsage::OneTimeSubmit, + ) + .expect("Couldn't create command buffer builder"); - unsafe { - builder - .begin_render_pass( - RenderPassBeginInfo { - clear_values: vec![Some([0.1, 0.1, 0.1, 1.0].into())], - ..RenderPassBeginInfo::framebuffer(framebuffer.clone()) - }, - SubpassBeginInfo { - contents: SubpassContents::Inline, - ..Default::default() - }, - ) - .unwrap() - .set_viewport( - 0, - [camera.viewport()].into_iter().collect() - ) - .unwrap() - .bind_descriptor_sets( - PipelineBindPoint::Graphics, - pipeline.layout().clone(), - 0, - camera_resources.descriptor_set().clone(), - ) - .unwrap() - .bind_pipeline_graphics(pipeline.clone()) - .unwrap() - .bind_vertex_buffers(0, mesh_registry.vertex_buffer.as_ref().clone()) - .unwrap() - .bind_index_buffer(mesh_registry.index_buffer.as_ref().clone()) - .unwrap(); + builder + .begin_render_pass( + RenderPassBeginInfo { + clear_values: vec![ + Some([0.1, 0.1, 0.1, 0.5].into()), + Some(1f32.into()), + ], + ..RenderPassBeginInfo::framebuffer(framebuffer.clone()) + }, + SubpassBeginInfo { + contents: SubpassContents::Inline, + ..Default::default() + }, + )? + .set_viewport( + 0, + [camera.viewport()].into_iter().collect() + )? + .bind_descriptor_sets( + PipelineBindPoint::Graphics, + pipeline.layout().clone(), + 0, + camera_resources.descriptor_set().clone(), + )? + .bind_pipeline_graphics(pipeline.clone())? + .bind_vertex_buffers(0, mesh_registry.vertex_buffer.as_ref().clone())? + .bind_index_buffer(mesh_registry.index_buffer.as_ref().clone())?; - render_batches.iter().for_each(|batch| { - // Grab mesh information - let mesh = mesh_registry.get(&batch.mesh_id) - .expect("Mesh does not exist in registry"); + unsafe { + render_batches.iter().for_each(|batch| { + // Grab mesh information + let mesh = mesh_registry.get(&batch.mesh_id) + .expect("Mesh does not exist in registry"); - builder - .bind_vertex_buffers(1, batch.instance_buffer.clone()) - .unwrap() - .draw_indexed(mesh.index_count as u32, batch.instance_count, mesh.index_loc as u32, mesh.vertex_loc as i32, 0) - .unwrap(); - }); + builder + .bind_vertex_buffers(1, batch.instance_buffer.clone()) + .unwrap() + .draw_indexed(mesh.index_count as u32, batch.instance_count, mesh.index_loc as u32, mesh.vertex_loc as i32, 0) + .unwrap(); + }); + } - builder - .end_render_pass(SubpassEndInfo::default()) - .unwrap(); - } + builder.end_render_pass(SubpassEndInfo::default())?; - builder.build().ok() - }) - .collect(); + let buffer = builder.build()?; - Ok(buffers) + Ok(buffer) } /// Handles recreation logic for the [`Swapchain`] by using the current information and any new @@ -532,7 +535,11 @@ impl VulkanContext { // Recreate the framebuffers, which depend on the swapchain. let new_framebuffers = - Self::create_framebuffers(&self.swapchain_images, &self.render_pass)?; + Self::create_framebuffers( + self.mem_allocator.clone(), + &self.swapchain_images, + &self.render_pass + )?; self.framebuffers = new_framebuffers; @@ -553,19 +560,7 @@ impl VulkanContext { self.instance_registry.clone() ); - let command_buffers = Self::get_command_buffers( - &self.cmd_allocator, - &self.queue, - &new_pipeline, - &self.framebuffers, - &self.camera, - &self.camera_resources, - self.mesh_registry.clone(), - batches - )?; - self.pipeline = new_pipeline; - self.cmd_buffers = command_buffers; } Ok(()) @@ -604,6 +599,7 @@ impl MainApplication { library, InstanceCreateInfo { flags: InstanceCreateFlags::ENUMERATE_PORTABILITY, + enabled_layers: vec!["VK_LAYER_KHRONOS_validation".to_string()], enabled_extensions: required_extensions, ..Default::default() }, diff --git a/src/common/camera.rs b/src/common/camera.rs index 749385e..f71863b 100644 --- a/src/common/camera.rs +++ b/src/common/camera.rs @@ -32,7 +32,7 @@ impl Camera { extents: impl Into> ) -> Self { let extents = extents.into(); - let position = Vector3::new(-10.0, 10.0, -10.0); + let position = Vector3::new(-7.0, 7.0, -7.0); let target = Vector3::zeros(); // Traveling along this vector moves us toward the target. diff --git a/src/gpu/camera.rs b/src/gpu/camera.rs index b3ec80a..e8d8edd 100644 --- a/src/gpu/camera.rs +++ b/src/gpu/camera.rs @@ -1,6 +1,4 @@ -use std::sync::Arc; -use vulkano::buffer::{Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer}; -use vulkano::memory::allocator::{AllocationCreateInfo, MemoryAllocator, MemoryTypeFilter}; +use vulkano::buffer::BufferContents; use crate::common::camera::Camera; #[derive(BufferContents)] @@ -8,6 +6,7 @@ use crate::common::camera::Camera; pub struct CameraUniform { pub view: [[f32; 4]; 4], pub proj: [[f32; 4]; 4], + pub position: [f32; 3], } impl From for CameraUniform { @@ -24,6 +23,7 @@ impl From<&Camera> for CameraUniform { Self { view: view.into(), proj: proj.into(), + position: camera.position.into(), } } } diff --git a/src/shaders/triangle_frag.glsl b/src/shaders/triangle_frag.glsl index fa8ab62..8e66835 100644 --- a/src/shaders/triangle_frag.glsl +++ b/src/shaders/triangle_frag.glsl @@ -1,7 +1,48 @@ #version 460 +// Per-fragment - mesh dependent information. +layout(location = 0) in vec3 inFragPosition; +layout(location = 1) in vec3 inNormal; + +// Per-frame information - CameraUniform struct +layout(set = 0, binding = 0) uniform CameraUniform { + mat4 view; + mat4 projection; + vec3 position; +} camera; + layout(location = 0) out vec4 f_color; void main() { - f_color = vec4(1.0, 0.0, 0.0, 1.0); + // Default mesh color - no textures for now. + vec3 objectColor = vec3(0.1, 0.1, 0.1); + // Default light information - we aren't going to edit light information until we know more about it. + vec3 lightPosition = vec3(0.0, 10000.0, 0.0); + vec3 lightColor = vec3(1.0, 1.0, 1.0); + + // Ambient lighting - literally just a base color percentage. + vec3 ambient = 0.1 * lightColor; + + // Diffuse lighting - brighter on faces that catch more light. + // This is the dot product of the frag to the light, and the frag normal. + vec3 norm = normalize(inNormal); + vec3 lightDir = normalize(lightPosition - inFragPosition); + float diff = max(dot(norm, lightDir), 0.0); + vec3 diffuse = diff * lightColor; + + // Specular lighting - shiny spots corresponding to reflected light toward the camera. + // The direction from the fragment to the camera. + vec3 viewDirection = normalize(camera.position - inFragPosition); + // The direction of reflection of light - the inverse of the light to the surface. + vec3 reflectDirection = reflect(-lightDir, norm); + // The specular is basically like diffuse, plus an extra "specular strength". There's also a coarseness, which + // determines the size of the "dot" created from the shading itself. + float specStrength = 0.5; + int specCoarseness = 32; + float spec = pow(max(dot(viewDirection, reflectDirection), 0.0), specCoarseness); + vec3 specular = specStrength * spec * lightColor; + + vec3 result = (ambient + diffuse + specular) * objectColor; + + f_color = vec4(result, 1.0); } diff --git a/src/shaders/triangle_vert.glsl b/src/shaders/triangle_vert.glsl index 4a9b67c..81d83f7 100644 --- a/src/shaders/triangle_vert.glsl +++ b/src/shaders/triangle_vert.glsl @@ -14,13 +14,21 @@ layout(location = 5) in vec4 transform_col4; layout(set = 0, binding = 0) uniform CameraUniform { mat4 view; mat4 projection; + vec3 position; } camera; + +// Outputs +layout(location = 0) out vec3 outFragPosition; +layout(location = 1) out vec3 outNormal; + void main() { // We then multiply each vertex by its transform to find the correct location in the scene. - mat4 transform = mat4(transform_col1, transform_col2, transform_col3, transform_col4); + mat4 model = mat4(transform_col1, transform_col2, transform_col3, transform_col4); - mat4 mvp = camera.projection * camera.view * transform; + mat4 mvp = camera.projection * camera.view * model; gl_Position = mvp * vec4(position, 1.0f); + outFragPosition = vec3(model * vec4(position, 1.0)); + outNormal = normal; }