From 0f837ce65ec7920663a6b2dca84292701f3e0792 Mon Sep 17 00:00:00 2001 From: Till Date: Tue, 30 Jul 2019 15:17:52 +0200 Subject: [PATCH] refactor 2 --- src/main.rs | 12 ++- src/vulkan.rs | 255 ++++++++++++++++++++++++++------------------------ 2 files changed, 142 insertions(+), 125 deletions(-) diff --git a/src/main.rs b/src/main.rs index 7c7c533..7e6aac8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ use winit::{Event}; use cgmath::{Matrix4, Rad, Vector3, Deg, Quaternion, Rotation3, One, Rotation, SquareMatrix}; mod vulkan; -use crate::vulkan::{GameData, Game, LinePoint, GameObject, VulkanRenderer}; +use crate::vulkan::{GameData, Game, LinePoint, GameObject, VulkanRenderer, RenderLoopResult}; mod input; use crate::input::{InputState}; @@ -100,5 +100,13 @@ fn main() { log_config.vulkan_validation_layers, ); renderer.upload_mesh(mesh::load_mesh("models/cube.dae", true).into_iter().nth(0).unwrap()); - renderer.render_loop(&mut game); + + let mut continue_rendering = true; + + while continue_rendering { + match renderer.render_loop(&mut game) { + RenderLoopResult::Ok => {}, + RenderLoopResult::Quit => continue_rendering = false, + } + } } \ No newline at end of file diff --git a/src/vulkan.rs b/src/vulkan.rs index 5f061ed..87b591e 100644 --- a/src/vulkan.rs +++ b/src/vulkan.rs @@ -34,6 +34,7 @@ use vs::ty::PushConstants; use line_vs::ty::LinePushConstants; use crate::mesh::CPUMesh; +use crate::vulkan::RenderLoopResult::Quit; const VALIDATION_LAYERS: &[&str] = &[ "VK_LAYER_LUNARG_standard_validation" @@ -97,7 +98,14 @@ pub struct VulkanRenderer { pub render_pass: Arc, pub queue: Arc, pub events_loop: EventsLoop, + pub recreate_swapchain: bool, pub debug_callback: Option, + pub previous_frame_end: Option>, +} + +pub enum RenderLoopResult { + Ok, + Quit } impl VulkanRenderer { @@ -273,145 +281,146 @@ impl VulkanRenderer { // can draw we also need to create the actual framebuffers. let framebuffers = window_size_dependent_setup(device.clone(), &images, render_pass.clone(), &mut dynamic_state); - VulkanRenderer { game_data: data, device, framebuffers, dynamic_state, pipeline, line_pipeline, - surface, swapchain, render_pass, queue, line_vertex_buffer, events_loop, debug_callback } - } - - pub fn render_loop(self: &mut Self, game: &mut dyn Game) { - let mut recreate_swapchain = false; - // In the loop below we are going to submit commands to the GPU. Submitting a command produces // an object that implements the `GpuFuture` trait, which holds the resources for as long as // they are in use by the GPU. // // Destroying the `GpuFuture` blocks until the GPU is finished executing it. In order to avoid // that, we store the submission of the previous frame here. - let mut previous_frame_end = Box::new(sync::now(self.device.clone())) as Box; + let previous_frame_end = Some(Box::new(sync::now(device.clone())) as Box); - loop { - // It is important to call this function from time to time, otherwise resources will keep - // accumulating and you will eventually reach an out of memory error. - // Calling this function polls various fences in order to determine what the GPU has - // already processed, and frees the resources that are no longer needed. - previous_frame_end.cleanup_finished(); + VulkanRenderer { game_data: data, device, framebuffers, dynamic_state, pipeline, line_pipeline, + surface, swapchain, render_pass, queue, line_vertex_buffer, events_loop, + recreate_swapchain: false, debug_callback, previous_frame_end } + } - if recreate_swapchain { - let window = self.surface.window(); - self.game_data.dimensions = if let Some(dimensions) = window.get_inner_size() { - let dimensions: (u32, u32) = dimensions.to_physical(window.get_hidpi_factor()).into(); - [dimensions.0, dimensions.1] - } else { - return; - }; + pub fn render_loop(self: &mut Self, game: &mut dyn Game) -> RenderLoopResult { + // It is important to call this function from time to time, otherwise resources will keep + // accumulating and you will eventually reach an out of memory error. + // Calling this function polls various fences in order to determine what the GPU has + // already processed, and frees the resources that are no longer needed. + self.previous_frame_end.as_mut().unwrap().cleanup_finished(); - let (new_swapchain, new_images) = match self.swapchain.recreate_with_dimension(self.game_data.dimensions) { - Ok(r) => r, - // This error tends to happen when the user is manually resizing the window. - // Simply restarting the loop is the easiest way to fix this issue. - Err(SwapchainCreationError::UnsupportedDimensions) => continue, - Err(err) => panic!("{:?}", err) - }; + if self.recreate_swapchain { + let window = self.surface.window(); + self.game_data.dimensions = if let Some(dimensions) = window.get_inner_size() { + let dimensions: (u32, u32) = dimensions.to_physical(window.get_hidpi_factor()).into(); + [dimensions.0, dimensions.1] + } else { + return RenderLoopResult::Ok; + }; - self.swapchain = new_swapchain; - // Because framebuffers contains an Arc on the old swapchain, we need to - // recreate framebuffers as well. - self.framebuffers = window_size_dependent_setup(self.device.clone(), &new_images, self.render_pass.clone(), &mut self.dynamic_state); - - recreate_swapchain = false; - } - - if self.game_data.recreate_pipeline { - if let Some(pipeline_ok) = create_pipeline::(self.device.clone(), self.render_pass.clone(), "shaders/triangle.vert", "shaders/triangle.frag", false) { - self.pipeline = pipeline_ok; - println!("Updated pipeline."); - } else { - println!("Failed to update pipeline."); - } - self.game_data.recreate_pipeline = false; - } - - // Before we can draw on the output, we have to *acquire* an image from the swapchain. If - // no image is available (which happens if you submit draw commands too quickly), then the - // function will block. - // This operation returns the index of the image that we are allowed to draw upon. - // - // This function can block if no image is available. The parameter is an optional timeout - // after which the function call will return an error. - let (image_num, acquire_future) = match swapchain::acquire_next_image(self.swapchain.clone(), None) { + let (new_swapchain, new_images) = match self.swapchain.recreate_with_dimension(self.game_data.dimensions) { Ok(r) => r, - Err(AcquireError::OutOfDate) => { - recreate_swapchain = true; - continue; - }, + // This error tends to happen when the user is manually resizing the window. + // Simply restarting the loop is the easiest way to fix this issue. + Err(SwapchainCreationError::UnsupportedDimensions) => return RenderLoopResult::Ok, Err(err) => panic!("{:?}", err) }; - game.update(&mut self.game_data); + self.swapchain = new_swapchain; + // Because framebuffers contains an Arc on the old swapchain, we need to + // recreate framebuffers as well. + self.framebuffers = window_size_dependent_setup(self.device.clone(), &new_images, self.render_pass.clone(), &mut self.dynamic_state); - let mut cbb = AutoCommandBufferBuilder::primary_one_time_submit(self.device.clone(), self.queue.family()).unwrap() - // Before we can draw, we have to *enter a render pass*. There are two methods to do - // this: `draw_inline` and `draw_secondary`. The latter is a bit more advanced and is - // not covered here. - .begin_render_pass(self.framebuffers[image_num].clone(), false, vec![[0.0, 0.0, 0.0, 1.0].into(), ClearValue::Depth(1.0)]).unwrap(); - - - // We are now inside the first subpass of the render pass - for i in 0..self.game_data.game_objects.len() { - let game_object = &self.game_data.game_objects[i]; - let mesh = &self.game_data.meshes[game_object.mesh_index]; - self.game_data.push_constants.model = game_object.model_matrix.into(); - cbb = cbb.draw_indexed(self.pipeline.clone(), &self.dynamic_state, - vec![mesh.vertex_buffer.clone()], - mesh.index_buffer.clone(), - (), self.game_data.push_constants.clone()).unwrap(); - } - - cbb = cbb.draw(self.line_pipeline.clone(), &self.dynamic_state, vec![self.line_vertex_buffer.clone()], (), self.game_data.line_push_constants.clone()).unwrap() - - // We leave the render pass by calling `draw_end`. Note that if we had multiple - // subpasses we could have called `next_inline` (or `next_secondary`) to jump to the - // next subpass. - .end_render_pass().unwrap(); - - let command_buffer = cbb.build().unwrap(); - - let future = previous_frame_end.join(acquire_future) - .then_execute(self.queue.clone(), command_buffer).unwrap() - .then_swapchain_present(self.queue.clone(), self.swapchain.clone(), image_num) - .then_signal_fence_and_flush(); - - match future { - Ok(future) => { - previous_frame_end = Box::new(future) as Box<_>; - } - Err(FlushError::OutOfDate) => { - recreate_swapchain = true; - previous_frame_end = Box::new(sync::now(self.device.clone())) as Box<_>; - } - Err(e) => { - println!("{:?}", e); - previous_frame_end = Box::new(sync::now(self.device.clone())) as Box<_>; - } - } - - // Note that in more complex programs it is likely that one of `acquire_next_image`, - // `command_buffer::submit`, or `present` will block for some time. This happens when the - // GPU's queue is full and the driver has to wait until the GPU finished some work. - // - // Unfortunately the Vulkan API doesn't provide any way to not wait or to detect when a - // wait would happen. Blocking may be the desired behavior, but if you don't want to - // block you should spawn a separate thread dedicated to submissions. - let mut window_closed = false; - self.events_loop.poll_events(|ev| { - game.on_window_event(&ev); - match ev { - Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => window_closed = true, - Event::WindowEvent { event: WindowEvent::Resized(_), .. } => recreate_swapchain = true, - _ => {} - } - }); - if self.game_data.shutdown || window_closed { return; } + self.recreate_swapchain = false; } + + if self.game_data.recreate_pipeline { + if let Some(pipeline_ok) = create_pipeline::(self.device.clone(), self.render_pass.clone(), "shaders/triangle.vert", "shaders/triangle.frag", false) { + self.pipeline = pipeline_ok; + println!("Updated pipeline."); + } else { + println!("Failed to update pipeline."); + } + self.game_data.recreate_pipeline = false; + } + + // Before we can draw on the output, we have to *acquire* an image from the swapchain. If + // no image is available (which happens if you submit draw commands too quickly), then the + // function will block. + // This operation returns the index of the image that we are allowed to draw upon. + // + // This function can block if no image is available. The parameter is an optional timeout + // after which the function call will return an error. + let (image_num, acquire_future) = match swapchain::acquire_next_image(self.swapchain.clone(), None) { + Ok(r) => r, + Err(AcquireError::OutOfDate) => { + self.recreate_swapchain = true; + return RenderLoopResult::Ok; + }, + Err(err) => panic!("{:?}", err) + }; + + game.update(&mut self.game_data); + + let mut cbb = AutoCommandBufferBuilder::primary_one_time_submit(self.device.clone(), self.queue.family()).unwrap() + // Before we can draw, we have to *enter a render pass*. There are two methods to do + // this: `draw_inline` and `draw_secondary`. The latter is a bit more advanced and is + // not covered here. + .begin_render_pass(self.framebuffers[image_num].clone(), false, vec![[0.0, 0.0, 0.0, 1.0].into(), ClearValue::Depth(1.0)]).unwrap(); + + + // We are now inside the first subpass of the render pass + for i in 0..self.game_data.game_objects.len() { + let game_object = &self.game_data.game_objects[i]; + let mesh = &self.game_data.meshes[game_object.mesh_index]; + self.game_data.push_constants.model = game_object.model_matrix.into(); + cbb = cbb.draw_indexed(self.pipeline.clone(), &self.dynamic_state, + vec![mesh.vertex_buffer.clone()], + mesh.index_buffer.clone(), + (), self.game_data.push_constants.clone()).unwrap(); + } + + cbb = cbb.draw(self.line_pipeline.clone(), &self.dynamic_state, vec![self.line_vertex_buffer.clone()], (), self.game_data.line_push_constants.clone()).unwrap() + + // We leave the render pass by calling `draw_end`. Note that if we had multiple + // subpasses we could have called `next_inline` (or `next_secondary`) to jump to the + // next subpass. + .end_render_pass().unwrap(); + + let command_buffer = cbb.build().unwrap(); + + let future = self.previous_frame_end.take().unwrap().join(acquire_future) + .then_execute(self.queue.clone(), command_buffer).unwrap() + .then_swapchain_present(self.queue.clone(), self.swapchain.clone(), image_num) + .then_signal_fence_and_flush(); + + match future { + Ok(future) => { + self.previous_frame_end = Some(Box::new(future) as Box<_>); + }, + Err(FlushError::OutOfDate) => { + self.recreate_swapchain = true; + self.previous_frame_end = Some(Box::new(sync::now(self.device.clone())) as Box<_>); + } + Err(e) => { + println!("{:?}", e); + self.previous_frame_end = Some(Box::new(sync::now(self.device.clone())) as Box<_>); + } + }; + + // Note that in more complex programs it is likely that one of `acquire_next_image`, + // `command_buffer::submit`, or `present` will block for some time. This happens when the + // GPU's queue is full and the driver has to wait until the GPU finished some work. + // + // Unfortunately the Vulkan API doesn't provide any way to not wait or to detect when a + // wait would happen. Blocking may be the desired behavior, but if you don't want to + // block you should spawn a separate thread dedicated to submissions. + let mut window_closed = false; + let mut resized = false; + self.events_loop.poll_events(|ev| { + game.on_window_event(&ev); + match ev { + Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => window_closed = true, + Event::WindowEvent { event: WindowEvent::Resized(_), .. } => resized = true, + _ => {} + } + }); + if resized { self.recreate_swapchain = true } + if self.game_data.shutdown || window_closed { return Quit; } + + RenderLoopResult::Ok } pub fn upload_mesh(self: &mut Self, mesh: CPUMesh) -> usize {