use super::{ ambient_lighting_system::AmbientLightingSystem, directional_lighting_system::DirectionalLightingSystem, point_lighting_system::PointLightingSystem, }; use glam::f32::{Mat4, Vec3}; use std::sync::Arc; use vulkano::{ command_buffer::{ allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, PrimaryAutoCommandBuffer, RenderPassBeginInfo, SecondaryAutoCommandBuffer, SubpassBeginInfo, SubpassContents, }, descriptor_set::allocator::StandardDescriptorSetAllocator, device::Queue, format::Format, image::{view::ImageView, Image, ImageCreateInfo, ImageType, ImageUsage}, memory::allocator::{AllocationCreateInfo, StandardMemoryAllocator}, render_pass::{Framebuffer, FramebufferCreateInfo, RenderPass, Subpass}, sync::GpuFuture, }; /// System that contains the necessary facilities for rendering a single frame. pub struct FrameSystem { // Queue to use to render everything. gfx_queue: Arc, // Render pass used for the drawing. See the `new` method for the actual render pass content. // We need to keep it in `FrameSystem` because we may want to recreate the intermediate buffers // in of a change in the dimensions. render_pass: Arc, memory_allocator: Arc, command_buffer_allocator: Arc, // Intermediate render target that will contain the albedo of each pixel of the scene. diffuse_buffer: Arc, // Intermediate render target that will contain the normal vector in world coordinates of each // pixel of the scene. // The normal vector is the vector perpendicular to the surface of the object at this point. normals_buffer: Arc, // Intermediate render target that will contain the depth of each pixel of the scene. // This is a traditional depth buffer. `0.0` means "near", and `1.0` means "far". depth_buffer: Arc, // Will allow us to add an ambient lighting to a scene during the second subpass. ambient_lighting_system: AmbientLightingSystem, // Will allow us to add a directional light to a scene during the second subpass. directional_lighting_system: DirectionalLightingSystem, // Will allow us to add a spot light source to a scene during the second subpass. point_lighting_system: PointLightingSystem, } impl FrameSystem { /// Initializes the frame system. /// /// Should be called at initialization, as it can take some time to build. /// /// - `gfx_queue` is the queue that will be used to perform the main rendering. /// - `final_output_format` is the format of the image that will later be passed to the /// `frame()` method. We need to know that in advance. If that format ever changes, we have /// to create a new `FrameSystem`. pub fn new( gfx_queue: Arc, final_output_format: Format, memory_allocator: Arc, command_buffer_allocator: Arc, ) -> FrameSystem { // Creating the render pass. // // The render pass has two subpasses. In the first subpass, we draw all the objects of the // scene. Note that it is not the `FrameSystem` that is responsible for the drawing, // instead it only provides an API that allows the user to do so. // // The drawing of the objects will write to the `diffuse`, `normals` and `depth` // attachments. // // Then in the second subpass, we read these three attachments as input attachments and // draw to `final_color`. Each draw operation performed in this second subpass has its // value added to `final_color` and not replaced, thanks to blending. // // > **Warning**: If the red, green or blue component of the final image goes over `1.0` // > then it will be clamped. For example a pixel of `[2.0, 1.0, 1.0]` (which is red) will // > be clamped to `[1.0, 1.0, 1.0]` (which is white) instead of being converted to // > `[1.0, 0.5, 0.5]` as desired. In a real-life application you want to use an additional // > intermediate image with a floating-point format, then perform additional passes to // > convert all the colors in the correct range. These techniques are known as HDR and // > tone mapping. // // Input attachments are a special kind of way to read images. You can only read from them // from a fragment shader, and you can only read the pixel corresponding to the pixel // currently being processed by the fragment shader. If you want to read from attachments // but can't deal with these restrictions, then you should create multiple render passes // instead. let render_pass = vulkano::ordered_passes_renderpass!( gfx_queue.device().clone(), attachments: { // The image that will contain the final rendering (in this example the swapchain // image, but it could be another image). final_color: { format: final_output_format, samples: 1, load_op: Clear, store_op: Store, }, // Will be bound to `self.diffuse_buffer`. diffuse: { format: Format::A2B10G10R10_UNORM_PACK32, samples: 1, load_op: Clear, store_op: DontCare, }, // Will be bound to `self.normals_buffer`. normals: { format: Format::R16G16B16A16_SFLOAT, samples: 1, load_op: Clear, store_op: DontCare, }, // Will be bound to `self.depth_buffer`. depth_stencil: { format: Format::D16_UNORM, samples: 1, load_op: Clear, store_op: DontCare, }, }, passes: [ // Write to the diffuse, normals and depth attachments. { color: [diffuse, normals], depth_stencil: {depth_stencil}, input: [], }, // Apply lighting by reading these three attachments and writing to `final_color`. { color: [final_color], depth_stencil: {}, input: [diffuse, normals, depth_stencil], }, ], ) .unwrap(); // For now we create three temporary images with a dimension of 1 by 1 pixel. These images // will be replaced the first time we call `frame()`. let diffuse_buffer = ImageView::new_default( Image::new( memory_allocator.clone(), ImageCreateInfo { image_type: ImageType::Dim2d, format: Format::A2B10G10R10_UNORM_PACK32, extent: [1, 1, 1], usage: ImageUsage::COLOR_ATTACHMENT | ImageUsage::TRANSIENT_ATTACHMENT | ImageUsage::INPUT_ATTACHMENT, ..Default::default() }, AllocationCreateInfo::default(), ) .unwrap(), ) .unwrap(); let normals_buffer = ImageView::new_default( Image::new( memory_allocator.clone(), ImageCreateInfo { image_type: ImageType::Dim2d, format: Format::R16G16B16A16_SFLOAT, extent: [1, 1, 1], usage: ImageUsage::TRANSIENT_ATTACHMENT | ImageUsage::INPUT_ATTACHMENT, ..Default::default() }, AllocationCreateInfo::default(), ) .unwrap(), ) .unwrap(); let depth_buffer = ImageView::new_default( Image::new( memory_allocator.clone(), ImageCreateInfo { image_type: ImageType::Dim2d, format: Format::D16_UNORM, extent: [1, 1, 1], usage: ImageUsage::TRANSIENT_ATTACHMENT | ImageUsage::INPUT_ATTACHMENT, ..Default::default() }, AllocationCreateInfo::default(), ) .unwrap(), ) .unwrap(); let descriptor_set_allocator = Arc::new(StandardDescriptorSetAllocator::new( gfx_queue.device().clone(), Default::default(), )); // Initialize the three lighting systems. Note that we need to pass to them the subpass // where they will be executed. let lighting_subpass = Subpass::from(render_pass.clone(), 1).unwrap(); let ambient_lighting_system = AmbientLightingSystem::new( gfx_queue.clone(), lighting_subpass.clone(), memory_allocator.clone(), command_buffer_allocator.clone(), descriptor_set_allocator.clone(), ); let directional_lighting_system = DirectionalLightingSystem::new( gfx_queue.clone(), lighting_subpass.clone(), memory_allocator.clone(), command_buffer_allocator.clone(), descriptor_set_allocator.clone(), ); let point_lighting_system = PointLightingSystem::new( gfx_queue.clone(), lighting_subpass, memory_allocator.clone(), command_buffer_allocator.clone(), descriptor_set_allocator, ); FrameSystem { gfx_queue, render_pass, memory_allocator, command_buffer_allocator, diffuse_buffer, normals_buffer, depth_buffer, ambient_lighting_system, directional_lighting_system, point_lighting_system, } } /// Returns the subpass of the render pass where the rendering should write info to gbuffers. /// /// Has two outputs: the diffuse color (3 components) and the normals in world coordinates /// (3 components). Also has a depth attachment. /// /// This method is necessary in order to initialize the pipelines that will draw the objects /// of the scene. #[inline] pub fn deferred_subpass(&self) -> Subpass { Subpass::from(self.render_pass.clone(), 0).unwrap() } /// Starts drawing a new frame. /// /// - `before_future` is the future after which the main rendering should be executed. /// - `final_image` is the image we are going to draw to. /// - `world_to_framebuffer` is the matrix that will be used to convert from 3D coordinates in /// the world into 2D coordinates on the framebuffer. pub fn frame( &mut self, before_future: F, final_image_view: Arc, world_to_framebuffer: Mat4, ) -> Frame where F: GpuFuture + 'static, { // First of all we recreate `self.diffuse_buffer`, `self.normals_buffer` and // `self.depth_buffer` if their extent doesn't match the extent of the final image. let extent = final_image_view.image().extent(); if self.diffuse_buffer.image().extent() != extent { // Note that we create "transient" images here. This means that the content of the // image is only defined when within a render pass. In other words you can draw to // them in a subpass then read them in another subpass, but as soon as you leave the // render pass their content becomes undefined. self.diffuse_buffer = ImageView::new_default( Image::new( self.memory_allocator.clone(), ImageCreateInfo { extent, format: Format::A2B10G10R10_UNORM_PACK32, usage: ImageUsage::COLOR_ATTACHMENT | ImageUsage::TRANSIENT_ATTACHMENT | ImageUsage::INPUT_ATTACHMENT, ..Default::default() }, AllocationCreateInfo::default(), ) .unwrap(), ) .unwrap(); self.normals_buffer = ImageView::new_default( Image::new( self.memory_allocator.clone(), ImageCreateInfo { extent, format: Format::R16G16B16A16_SFLOAT, usage: ImageUsage::COLOR_ATTACHMENT | ImageUsage::TRANSIENT_ATTACHMENT | ImageUsage::INPUT_ATTACHMENT, ..Default::default() }, AllocationCreateInfo::default(), ) .unwrap(), ) .unwrap(); self.depth_buffer = ImageView::new_default( Image::new( self.memory_allocator.clone(), ImageCreateInfo { extent, format: Format::D16_UNORM, usage: ImageUsage::DEPTH_STENCIL_ATTACHMENT | ImageUsage::TRANSIENT_ATTACHMENT | ImageUsage::INPUT_ATTACHMENT, ..Default::default() }, AllocationCreateInfo::default(), ) .unwrap(), ) .unwrap(); } // Build the framebuffer. The image must be attached in the same order as they were defined // with the `ordered_passes_renderpass!` macro. let framebuffer = Framebuffer::new( self.render_pass.clone(), FramebufferCreateInfo { attachments: vec![ final_image_view, self.diffuse_buffer.clone(), self.normals_buffer.clone(), self.depth_buffer.clone(), ], ..Default::default() }, ) .unwrap(); // Start the command buffer builder that will be filled throughout the frame handling. let mut command_buffer_builder = AutoCommandBufferBuilder::primary( self.command_buffer_allocator.clone(), self.gfx_queue.queue_family_index(), CommandBufferUsage::OneTimeSubmit, ) .unwrap(); command_buffer_builder .begin_render_pass( RenderPassBeginInfo { clear_values: vec![ Some([0.0, 0.0, 0.0, 0.0].into()), Some([0.0, 0.0, 0.0, 0.0].into()), Some([0.0, 0.0, 0.0, 0.0].into()), Some(1.0f32.into()), ], ..RenderPassBeginInfo::framebuffer(framebuffer.clone()) }, SubpassBeginInfo { contents: SubpassContents::SecondaryCommandBuffers, ..Default::default() }, ) .unwrap(); Frame { system: self, before_main_cb_future: Some(Box::new(before_future)), framebuffer, num_pass: 0, command_buffer_builder: Some(command_buffer_builder), world_to_framebuffer, } } } /// Represents the active process of rendering a frame. /// /// This struct mutably borrows the `FrameSystem`. pub struct Frame<'a> { // The `FrameSystem`. system: &'a mut FrameSystem, // The active pass we are in. This keeps track of the step we are in. // - If `num_pass` is 0, then we haven't start anything yet. // - If `num_pass` is 1, then we have finished drawing all the objects of the scene. // - If `num_pass` is 2, then we have finished applying lighting. // - Otherwise the frame is finished. // In a more complex application you can have dozens of passes, in which case you probably // don't want to document them all here. num_pass: u8, // Future to wait upon before the main rendering. before_main_cb_future: Option>, // Framebuffer that was used when starting the render pass. framebuffer: Arc, // The command buffer builder that will be built during the lifetime of this object. command_buffer_builder: Option>, // Matrix that was passed to `frame()`. world_to_framebuffer: Mat4, } impl<'a> Frame<'a> { /// Returns an enumeration containing the next pass of the rendering. pub fn next_pass<'f>(&'f mut self) -> Option> { // This function reads `num_pass` increments its value, and returns a struct corresponding // to that pass that the user will be able to manipulate in order to customize the pass. let current_pass = self.num_pass; self.num_pass += 1; match current_pass { 0 => { // If we are in the pass 0 then we haven't start anything yet. // We already called `begin_render_pass` (in the `frame()` method), and that's the // state we are in. // We return an object that will allow the user to draw objects on the scene. Some(Pass::Deferred(DrawPass { frame: self })) } 1 => { // If we are in pass 1 then we have finished drawing the objects on the scene. // Going to the next subpass. self.command_buffer_builder .as_mut() .unwrap() .next_subpass( Default::default(), SubpassBeginInfo { contents: SubpassContents::SecondaryCommandBuffers, ..Default::default() }, ) .unwrap(); // And returning an object that will allow the user to apply lighting to the scene. Some(Pass::Lighting(LightingPass { frame: self })) } 2 => { // If we are in pass 2 then we have finished applying lighting. // We take the builder, call `end_render_pass()`, and then `build()` it to obtain // an actual command buffer. self.command_buffer_builder .as_mut() .unwrap() .end_render_pass(Default::default()) .unwrap(); let command_buffer = self.command_buffer_builder.take().unwrap().build().unwrap(); // Extract `before_main_cb_future` and append the command buffer execution to it. let after_main_cb = self .before_main_cb_future .take() .unwrap() .then_execute(self.system.gfx_queue.clone(), command_buffer) .unwrap(); // We obtain `after_main_cb`, which we give to the user. Some(Pass::Finished(Box::new(after_main_cb))) } // If the pass is over 2 then the frame is in the finished state and can't do anything // more. _ => None, } } } /// Struct provided to the user that allows them to customize or handle the pass. pub enum Pass<'f, 's: 'f> { /// We are in the pass where we draw objects on the scene. The `DrawPass` allows the user to /// draw the objects. Deferred(DrawPass<'f, 's>), /// We are in the pass where we add lighting to the scene. The `LightingPass` allows the user /// to add light sources. Lighting(LightingPass<'f, 's>), /// The frame has been fully prepared, and here is the future that will perform the drawing /// on the image. Finished(Box), } /// Allows the user to draw objects on the scene. pub struct DrawPass<'f, 's: 'f> { frame: &'f mut Frame<'s>, } impl<'f, 's: 'f> DrawPass<'f, 's> { /// Appends a command that executes a secondary command buffer that performs drawing. pub fn execute(&mut self, command_buffer: Arc) { self.frame .command_buffer_builder .as_mut() .unwrap() .execute_commands(command_buffer) .unwrap(); } /// Returns the dimensions in pixels of the viewport. pub fn viewport_dimensions(&self) -> [u32; 2] { self.frame.framebuffer.extent() } /// Returns the 4x4 matrix that turns world coordinates into 2D coordinates on the framebuffer. #[allow(dead_code)] pub fn world_to_framebuffer_matrix(&self) -> Mat4 { self.frame.world_to_framebuffer } } /// Allows the user to apply lighting on the scene. pub struct LightingPass<'f, 's: 'f> { frame: &'f mut Frame<'s>, } impl<'f, 's: 'f> LightingPass<'f, 's> { /// Applies an ambient lighting to the scene. /// /// All the objects will be colored with an intensity of `color`. pub fn ambient_light(&mut self, color: [f32; 3]) { let command_buffer = self.frame.system.ambient_lighting_system.draw( self.frame.framebuffer.extent(), self.frame.system.diffuse_buffer.clone(), color, ); self.frame .command_buffer_builder .as_mut() .unwrap() .execute_commands(command_buffer) .unwrap(); } /// Applies an directional lighting to the scene. /// /// All the objects will be colored with an intensity varying between `[0, 0, 0]` and `color`, /// depending on the dot product of their normal and `direction`. pub fn directional_light(&mut self, direction: Vec3, color: [f32; 3]) { let command_buffer = self.frame.system.directional_lighting_system.draw( self.frame.framebuffer.extent(), self.frame.system.diffuse_buffer.clone(), self.frame.system.normals_buffer.clone(), direction, color, ); self.frame .command_buffer_builder .as_mut() .unwrap() .execute_commands(command_buffer) .unwrap(); } /// Applies a spot lighting to the scene. /// /// All the objects will be colored with an intensity varying between `[0, 0, 0]` and `color`, /// depending on their distance with `position`. Objects that aren't facing `position` won't /// receive any light. pub fn point_light(&mut self, position: Vec3, color: [f32; 3]) { let command_buffer = { self.frame.system.point_lighting_system.draw( self.frame.framebuffer.extent(), self.frame.system.diffuse_buffer.clone(), self.frame.system.normals_buffer.clone(), self.frame.system.depth_buffer.clone(), self.frame.world_to_framebuffer.inverse(), position, color, ) }; self.frame .command_buffer_builder .as_mut() .unwrap() .execute_commands(command_buffer) .unwrap(); } }