restructure

This commit is contained in:
2019-07-26 22:37:02 +02:00
parent 8c14653cfc
commit 3bb9c40749
2 changed files with 85 additions and 125 deletions

View File

@@ -1,17 +1,42 @@
use crate::vulkan::{Vertex, GameData}; use crate::vulkan::{Vertex, GameData, Game};
use winit::{Event, WindowEvent, ElementState}; use winit::{Event, WindowEvent, ElementState};
use std::time::SystemTime;
use std::iter::FromIterator; use std::iter::FromIterator;
use cgmath::{Matrix4, SquareMatrix, Rad, Point3, Vector3, Deg}; use cgmath::{Matrix4, Rad, Point3, Vector3, Deg};
mod vulkan; mod vulkan;
use vulkan::vs::ty::PushConstants; use vulkan::vs::ty::PushConstants;
const PRINT_KEYBOARD_INPUT: bool = false; const PRINT_KEYBOARD_INPUT: bool = false;
impl GameData<'_> { struct TestGame {}
/// Returns true if event should be ignored by the vulkan handler
fn on_window_event(self: &mut Self, event: &Event) -> bool { 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 { match event {
Event::WindowEvent { event: WindowEvent::KeyboardInput { device_id, input }, .. } => { Event::WindowEvent { event: WindowEvent::KeyboardInput { device_id, input }, .. } => {
if PRINT_KEYBOARD_INPUT { if PRINT_KEYBOARD_INPUT {
@@ -28,71 +53,29 @@ impl GameData<'_> {
} }
if input.state == ElementState::Released && input.modifiers.ctrl && input.scancode == 19 { 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 { if input.state == ElementState::Released && input.scancode == 1 {
self.shutdown = true; game_data.shutdown = true;
} }
} }
_ => {} _ => {}
} }
return false; 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() { fn main() {
// let mut whatever = String::new(); let mut game = TestGame {};
// std::io::stdin().read_line(&mut whatever).unwrap(); vulkan::init(vec![
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.1, 0.2, 0.2] },
Vertex { position: [0.2, 0.4, 0.2] }, Vertex { position: [0.2, 0.4, 0.2] },
Vertex { position: [0.2, 0.2, 0.3] } Vertex { position: [0.2, 0.2, 0.3] }
], ],
line_vertices: vec![ vec![
Vertex { position: [-0.9, 1., 0.] }, Vertex { position: [-0.9, 1., 0.] },
Vertex { position: [0.9, 0., 0.] }, Vertex { position: [0.9, 0., 0.] },
], ],
push_constants: &mut pc, &mut game
start_time: SystemTime::now(), );
recreate_pipeline: false,
aspect_ratio: 1.0,
shutdown: false,
};
vulkan::init(data);
} }

View File

@@ -24,6 +24,8 @@ use std::time::SystemTime;
use std::path::PathBuf; use std::path::PathBuf;
use std::ffi::{CStr}; use std::ffi::{CStr};
use cgmath::{Matrix4, SquareMatrix};
use shade_runner; use shade_runner;
use shade_runner::{CompiledShaders, Entry}; use shade_runner::{CompiledShaders, Entry};
@@ -46,17 +48,40 @@ pub struct Vertex {
} }
vulkano::impl_vertex!(Vertex, position); 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 start_time: SystemTime,
pub mesh_vertices: Vec<Vertex>, pub mesh_vertices: Vec<Vertex>,
pub line_vertices: Vec<Vertex>, pub line_vertices: Vec<Vertex>,
pub push_constants: &'a mut PushConstants, pub push_constants: PushConstants,
pub recreate_pipeline: bool, pub recreate_pipeline: bool,
pub aspect_ratio: f32, pub aspect_ratio: f32,
pub shutdown: bool, pub shutdown: bool,
} }
pub fn init(mut game: GameData) { pub fn init(mesh_vertices: Vec<Vertex>, line_vertices: Vec<Vertex>, 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 { if ENABLE_VALIDATION_LAYERS {
println!("Enabling 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 surface = WindowBuilder::new().build_vk_surface(&events_loop, instance.clone()).unwrap();
let window = surface.window(); 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 // 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. // 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| { let queue_family = physical.queue_families().find(|&q| {
q.supports_graphics() && surface.is_supported(q).unwrap_or(false) q.supports_graphics() && surface.is_supported(q).unwrap_or(false)
}).unwrap(); }).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. // 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() { 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(); let dimensions: (u32, u32) = dimensions.to_physical(window.get_hidpi_factor()).into();
[dimensions.0, dimensions.1] [dimensions.0, dimensions.1]
} else { } else {
// The window no longer exists so exit the application. panic!("Couldn't get window dimensions!");
panic!("idk");
}; };
// 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, Swapchain::new(device.clone(), surface.clone(), caps.min_image_count, format,
initial_dimensions, 1, usage, &queue, SurfaceTransform::Identity, alpha, initial_dimensions, 1, usage, &queue, SurfaceTransform::Identity, alpha,
PresentMode::Fifo, true, None).unwrap() PresentMode::Fifo, true, None).unwrap()
}; };
let mesh_vertex_buffer = CpuAccessibleBuffer::from_iter(device.clone(), BufferUsage::all(), game.mesh_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(), game.line_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!( let render_pass = Arc::new(vulkano::single_pass_renderpass!(
device.clone(), 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 // 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. // can draw we also need to create the actual framebuffers.
// let mut framebuffers = window_size_dependent_setup(device.clone(), &images, render_pass.clone(), &mut dynamic_state, &mut data.aspect_ratio);
// 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);
// 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; let mut recreate_swapchain = false;
// In the loop below we are going to submit commands to the GPU. Submitting a command produces // 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. // already processed, and frees the resources that are no longer needed.
previous_frame_end.cleanup_finished(); 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 { if recreate_swapchain {
// Get the new dimensions of the window.
let dimensions = if let Some(dimensions) = window.get_inner_size() { let dimensions = if let Some(dimensions) = window.get_inner_size() {
let dimensions: (u32, u32) = dimensions.to_physical(window.get_hidpi_factor()).into(); let dimensions: (u32, u32) = dimensions.to_physical(window.get_hidpi_factor()).into();
[dimensions.0, dimensions.1] [dimensions.0, dimensions.1]
@@ -267,19 +266,19 @@ pub fn init(mut game: GameData) {
swapchain = new_swapchain; swapchain = new_swapchain;
// Because framebuffers contains an Arc on the old swapchain, we need to // Because framebuffers contains an Arc on the old swapchain, we need to
// recreate framebuffers as well. // 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; 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) { if let Some(pipeline_ok) = create_pipeline(device.clone(), sub_pass.clone(), "shaders/triangle.vert", "shaders/triangle.frag", false) {
pipeline = pipeline_ok; pipeline = pipeline_ok;
println!("Updated pipeline."); println!("Updated pipeline.");
} else { } else {
println!("Failed to update pipeline."); 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 // 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) Err(err) => panic!("{:?}", err)
}; };
game.update_push_constants(); game.update(&mut data);
// Specify the color to clear the framebuffer with i.e. blue // 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()); 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() 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 // 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 // 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() .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. // We are now inside the first subpass of the render pass. We add a draw command.
// .draw(pipeline.clone(), &dynamic_state, mesh_vertex_buffer.clone(), (), data.push_constants.clone()).unwrap()
// 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(line_pipeline.clone(), &dynamic_state, line_vertex_buffer.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 // 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) let future = previous_frame_end.join(acquire_future)
.then_execute(queue.clone(), command_buffer).unwrap() .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_swapchain_present(queue.clone(), swapchain.clone(), image_num)
.then_signal_fence_and_flush(); .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 // 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 // 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. // 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| { events_loop.poll_events(|ev| {
if !game.on_window_event(&ev) { if !game.on_window_event(&mut data, &ev) {
match 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, Event::WindowEvent { event: WindowEvent::Resized(_), .. } => recreate_swapchain = true,
_ => {} _ => {}
} }
} }
}); });
if game.shutdown { return; } if data.shutdown { return; }
} }
} }