diff --git a/src/main.rs b/src/main.rs index c281468..87110ea 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,42 @@ -use crate::vulkan::{Vertex, GameData}; +use crate::vulkan::{Vertex, GameData, Game}; use winit::{Event, WindowEvent, ElementState}; -use std::time::SystemTime; use std::iter::FromIterator; -use cgmath::{Matrix4, SquareMatrix, Rad, Point3, Vector3, Deg}; +use cgmath::{Matrix4, Rad, Point3, Vector3, Deg}; mod vulkan; use vulkan::vs::ty::PushConstants; const PRINT_KEYBOARD_INPUT: bool = false; -impl GameData<'_> { - /// Returns true if event should be ignored by the vulkan handler - fn on_window_event(self: &mut Self, event: &Event) -> bool { +struct TestGame {} + +impl Game for TestGame { + fn update(self: &mut Self, game_data: &mut GameData) { + game_data.push_constants.time = game_data.start_time.elapsed().unwrap().as_millis() as f32 / 1000.0; + + let model = Matrix4::from_angle_z(Rad::from(Deg(game_data.push_constants.time * 100.0))); + + let view = Matrix4::look_at( + Point3::new(2.0, 2.0, 2.0), + Point3::new(0.0, 0.0, 0.0), + Vector3::new(0.0, 0.0, 1.0) + ); + + let mut proj = cgmath::perspective( + Rad::from(Deg(45.0)), + game_data.aspect_ratio, + 0.1, + 10.0 + ); + + proj.y.y *= -1.0; + + game_data.push_constants.model = model.into(); + game_data.push_constants.view = view.into(); + game_data.push_constants.projection = proj.into(); + } + + fn on_window_event(self: &mut Self, game_data: &mut GameData, event: &Event) -> bool { match event { Event::WindowEvent { event: WindowEvent::KeyboardInput { device_id, input }, .. } => { if PRINT_KEYBOARD_INPUT { @@ -28,71 +53,29 @@ impl GameData<'_> { } if input.state == ElementState::Released && input.modifiers.ctrl && input.scancode == 19 { - self.recreate_pipeline = true; + game_data.recreate_pipeline = true; } if input.state == ElementState::Released && input.scancode == 1 { - self.shutdown = true; + game_data.shutdown = true; } } _ => {} } return false; } - - fn update_push_constants(self: &mut Self) { - self.push_constants.time = self.start_time.elapsed().unwrap().as_millis() as f32 / 1000.0; - - let model = Matrix4::from_angle_z(Rad::from(Deg(self.push_constants.time * 100.0))); - - let view = Matrix4::look_at( - Point3::new(2.0, 2.0, 2.0), - Point3::new(0.0, 0.0, 0.0), - Vector3::new(0.0, 0.0, 1.0) - ); - - let mut proj = cgmath::perspective( - Rad::from(Deg(45.0)), - self.aspect_ratio, - 0.1, - 10.0 - ); - - proj.y.y *= -1.0; - - self.push_constants.model = model.into(); - self.push_constants.view = view.into(); - self.push_constants.projection = proj.into(); - } } fn main() { -// let mut whatever = String::new(); -// std::io::stdin().read_line(&mut whatever).unwrap(); - - let mut pc = PushConstants { - time: 0.0, - _dummy0: [0; 12], - model: Matrix4::identity().into(), - view: Matrix4::identity().into(), - projection: Matrix4::identity().into(), - }; - - let data = GameData { - mesh_vertices: vec![ - Vertex { position: [0.1, 0.2, 0.2] }, - Vertex { position: [0.2, 0.4, 0.2] }, - Vertex { position: [0.2, 0.2, 0.3] } - ], - line_vertices: vec![ - Vertex { position: [-0.9, 1., 0.] }, - Vertex { position: [0.9, 0., 0.] }, - ], - push_constants: &mut pc, - start_time: SystemTime::now(), - recreate_pipeline: false, - aspect_ratio: 1.0, - shutdown: false, - }; - - vulkan::init(data); + let mut game = TestGame {}; + vulkan::init(vec![ + Vertex { position: [0.1, 0.2, 0.2] }, + Vertex { position: [0.2, 0.4, 0.2] }, + Vertex { position: [0.2, 0.2, 0.3] } + ], + vec![ + Vertex { position: [-0.9, 1., 0.] }, + Vertex { position: [0.9, 0., 0.] }, + ], + &mut game + ); } \ No newline at end of file diff --git a/src/vulkan.rs b/src/vulkan.rs index b60392c..e1ddbce 100644 --- a/src/vulkan.rs +++ b/src/vulkan.rs @@ -24,6 +24,8 @@ use std::time::SystemTime; use std::path::PathBuf; use std::ffi::{CStr}; +use cgmath::{Matrix4, SquareMatrix}; + use shade_runner; use shade_runner::{CompiledShaders, Entry}; @@ -46,17 +48,40 @@ pub struct Vertex { } vulkano::impl_vertex!(Vertex, position); -pub struct GameData<'a> { +pub trait Game { + fn update(self: &mut Self, game_data: &mut GameData); + + /// Returns true if event should be ignored by the vulkan handler + fn on_window_event(self: &mut Self, game_data: &mut GameData, event: &Event) -> bool; +} + +pub struct GameData { pub start_time: SystemTime, pub mesh_vertices: Vec, pub line_vertices: Vec, - pub push_constants: &'a mut PushConstants, + pub push_constants: PushConstants, pub recreate_pipeline: bool, pub aspect_ratio: f32, pub shutdown: bool, } -pub fn init(mut game: GameData) { +pub fn init(mesh_vertices: Vec, line_vertices: Vec, game: &mut dyn Game) { + let mut data = GameData { + push_constants: PushConstants { + time: 0.0, + _dummy0: [0; 12], + model: Matrix4::identity().into(), + view: Matrix4::identity().into(), + projection: Matrix4::identity().into(), + }, + start_time: SystemTime::now(), + recreate_pipeline: false, + aspect_ratio: 1.0, + shutdown: false, + mesh_vertices, + line_vertices, + }; + if ENABLE_VALIDATION_LAYERS { println!("Enabling validation layers..."); } @@ -122,16 +147,8 @@ pub fn init(mut game: GameData) { let surface = WindowBuilder::new().build_vk_surface(&events_loop, instance.clone()).unwrap(); let window = surface.window(); - // The next step is to choose which GPU queue will execute our draw commands. - // - // Devices can provide multiple queues to run commands in parallel (for example a draw queue - // and a compute queue), similar to CPU threads. This is something you have to have to manage - // manually in Vulkan. - // // In a real-life application, we would probably use at least a graphics queue and a transfers // queue to handle data transfers in parallel. In this example we only use one queue. - // - // We have to choose which queues to use early on, because we will need this info very soon. let queue_family = physical.queue_families().find(|&q| { q.supports_graphics() && surface.is_supported(q).unwrap_or(false) }).unwrap(); @@ -164,22 +181,19 @@ pub fn init(mut game: GameData) { // // Because for both of these cases, the swapchain needs to be the window dimensions, we just use that. let initial_dimensions = if let Some(dimensions) = window.get_inner_size() { - // convert to physical pixels let dimensions: (u32, u32) = dimensions.to_physical(window.get_hidpi_factor()).into(); [dimensions.0, dimensions.1] } else { - // The window no longer exists so exit the application. - panic!("idk"); + panic!("Couldn't get window dimensions!"); }; - // Please take a look at the docs for the meaning of the parameters we didn't mention. Swapchain::new(device.clone(), surface.clone(), caps.min_image_count, format, initial_dimensions, 1, usage, &queue, SurfaceTransform::Identity, alpha, PresentMode::Fifo, true, None).unwrap() }; - let mesh_vertex_buffer = CpuAccessibleBuffer::from_iter(device.clone(), BufferUsage::all(), game.mesh_vertices.iter().cloned()).unwrap(); - let line_vertex_buffer = CpuAccessibleBuffer::from_iter(device.clone(), BufferUsage::all(), game.line_vertices.iter().cloned()).unwrap(); + let mesh_vertex_buffer = CpuAccessibleBuffer::from_iter(device.clone(), BufferUsage::all(), data.mesh_vertices.iter().cloned()).unwrap(); + let line_vertex_buffer = CpuAccessibleBuffer::from_iter(device.clone(), BufferUsage::all(), data.line_vertices.iter().cloned()).unwrap(); let render_pass = Arc::new(vulkano::single_pass_renderpass!( device.clone(), @@ -214,20 +228,8 @@ pub fn init(mut game: GameData) { // The render pass we created above only describes the layout of our framebuffers. Before we // can draw we also need to create the actual framebuffers. - // - // Since we need to draw to multiple images, we are going to create a different framebuffer for - // each image. - let mut framebuffers = window_size_dependent_setup(device.clone(), &images, render_pass.clone(), &mut dynamic_state, &mut game.aspect_ratio); + let mut framebuffers = window_size_dependent_setup(device.clone(), &images, render_pass.clone(), &mut dynamic_state, &mut data.aspect_ratio); - // In some situations, the swapchain will become invalid by it This includes for example - // when the window is resized (as the images of the swapchain will no longer match the - // window's) or, on Android, when the application went to the background and goes back to the - // foreground. - // - // In this situation, acquiring a swapchain image or presenting it will return an error. - // Rendering to an image of that swapchain will not produce any error, but may or may not work. - // To continue rendering, we need to recreate the swapchain by creating a new swapchain. - // Here, we remember that we need to do this for the next loop iteration. let mut recreate_swapchain = false; // In the loop below we are going to submit commands to the GPU. Submitting a command produces @@ -245,10 +247,7 @@ pub fn init(mut game: GameData) { // already processed, and frees the resources that are no longer needed. previous_frame_end.cleanup_finished(); - // Whenever the window resizes we need to recreate everything dependent on the window size. - // In this example that includes the swapchain, the framebuffers and the dynamic state viewport. if recreate_swapchain { - // Get the new dimensions of the window. let 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] @@ -267,19 +266,19 @@ pub fn init(mut game: GameData) { swapchain = new_swapchain; // Because framebuffers contains an Arc on the old swapchain, we need to // recreate framebuffers as well. - framebuffers = window_size_dependent_setup(device.clone(), &new_images, render_pass.clone(), &mut dynamic_state, &mut game.aspect_ratio); + framebuffers = window_size_dependent_setup(device.clone(), &new_images, render_pass.clone(), &mut dynamic_state, &mut data.aspect_ratio); recreate_swapchain = false; } - if game.recreate_pipeline { + if data.recreate_pipeline { if let Some(pipeline_ok) = create_pipeline(device.clone(), sub_pass.clone(), "shaders/triangle.vert", "shaders/triangle.frag", false) { pipeline = pipeline_ok; println!("Updated pipeline."); } else { println!("Failed to update pipeline."); } - game.recreate_pipeline = false; + data.recreate_pipeline = false; } // Before we can draw on the output, we have to *acquire* an image from the swapchain. If @@ -298,20 +297,11 @@ pub fn init(mut game: GameData) { Err(err) => panic!("{:?}", err) }; - game.update_push_constants(); + game.update(&mut data); // Specify the color to clear the framebuffer with i.e. blue let clear_values = vec!([0.0, 0.0, 1.0, 1.0].into(), 1f32.into()); - // In order to draw, we have to build a *command buffer*. The command buffer object holds - // the list of commands that are going to be executed. - // - // Building a command buffer is an expensive operation (usually a few hundred - // microseconds), but it is known to be a hot path in the driver and is expected to be - // optimized. - // - // Note that we have to pass a queue family when we create the command buffer. The command - // buffer will only be executable on that given queue family. let command_buffer = AutoCommandBufferBuilder::primary_one_time_submit(device.clone(), 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 @@ -323,10 +313,7 @@ pub fn init(mut game: GameData) { .begin_render_pass(framebuffers[image_num].clone(), false, clear_values).unwrap() // We are now inside the first subpass of the render pass. We add a draw command. - // - // The last two parameters contain the list of resources to pass to the shaders. - // Since we used an `EmptyPipeline` object, the objects have to be `()`. - .draw(pipeline.clone(), &dynamic_state, mesh_vertex_buffer.clone(), (), game.push_constants.clone()).unwrap() + .draw(pipeline.clone(), &dynamic_state, mesh_vertex_buffer.clone(), (), data.push_constants.clone()).unwrap() .draw(line_pipeline.clone(), &dynamic_state, line_vertex_buffer.clone(), (), ()).unwrap() // We leave the render pass by calling `draw_end`. Note that if we had multiple @@ -339,13 +326,6 @@ pub fn init(mut game: GameData) { let future = previous_frame_end.join(acquire_future) .then_execute(queue.clone(), command_buffer).unwrap() - - // The color output is now expected to contain our triangle. But in order to show it on - // the screen, we have to *present* the image by calling `present`. - // - // This function does not actually present the image immediately. Instead it submits a - // present command at the end of the queue. This means that it will only be presented once - // the GPU has finished executing the command buffer that draws the triangle. .then_swapchain_present(queue.clone(), swapchain.clone(), image_num) .then_signal_fence_and_flush(); @@ -370,19 +350,16 @@ pub fn init(mut game: GameData) { // 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. - - // Handling the window events in order to close the program when the user wants to close - // it. events_loop.poll_events(|ev| { - if !game.on_window_event(&ev) { + if !game.on_window_event(&mut data, &ev) { match ev { - Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => game.shutdown = true, + Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => data.shutdown = true, Event::WindowEvent { event: WindowEvent::Resized(_), .. } => recreate_swapchain = true, _ => {} } } }); - if game.shutdown { return; } + if data.shutdown { return; } } }