This commit is contained in:
2019-07-25 16:38:42 +02:00
parent 007e158410
commit 767efab3d4
4 changed files with 111 additions and 145 deletions

View File

@@ -11,6 +11,4 @@ vulkano-win = "0.13"
shade_runner = "0.1.2"
shaderc = "0.5.0"
cgmath = "0.17"
image = "0.21"
winit = "0.19"
time = "0.1.37"

View File

@@ -6,9 +6,12 @@ layout(location = 0) out vec3 pos;
layout(push_constant) uniform push_constants {
float time;
mat4 model;
mat4 view;
mat4 projection;
} push;
void main() {
gl_Position = vec4(position + vec3(sin(push.time / 10.), 0., 0.), 1.0);
gl_Position = push.projection * push.view * push.model * vec4(position + vec3(sin(push.time / 10.), 0., 0.), 1.0);
pos = gl_Position.xyz;
}

View File

@@ -2,28 +2,36 @@ use crate::vulkan::{Vertex, GameData};
use winit::{Event, WindowEvent, ElementState};
use std::time::SystemTime;
use std::iter::FromIterator;
use cgmath::{Matrix4, SquareMatrix, Rad, Point3, Vector3};
mod vulkan;
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 {
match event {
Event::WindowEvent { event: WindowEvent::KeyboardInput { device_id, input }, .. } => {
let mods = String::from_iter(
vec!["shift", "ctrl", "alt", "logo"].iter()
.zip(vec![input.modifiers.shift, input.modifiers.ctrl, input.modifiers.alt, input.modifiers.logo])
.filter(|(&_name, state)| *state)
.map(|(&name, _state)| name));
if mods.len() > 0 {
println!("Keyboard {:?} input {:?} {:?} + {:?}", device_id, input.state, &mods, input.scancode)
} else {
println!("Keyboard {:?} input {:?} {:?}", device_id, input.state, input.scancode)
if PRINT_KEYBOARD_INPUT {
let mods = String::from_iter(
vec!["shift", "ctrl", "alt", "logo"].iter()
.zip(vec![input.modifiers.shift, input.modifiers.ctrl, input.modifiers.alt, input.modifiers.logo])
.filter(|(&_name, state)| *state)
.map(|(&name, _state)| name));
if mods.len() > 0 {
println!("Keyboard {:?} input {:?} {:?} + {:?}", device_id, input.state, &mods, input.scancode)
} else {
println!("Keyboard {:?} input {:?} {:?}", device_id, input.state, input.scancode)
}
}
if input.state == ElementState::Released && input.modifiers.ctrl && input.scancode == 19 {
self.recreate_pipeline = true;
}
if input.state == ElementState::Released && input.scancode == 1 {
self.shutdown = true;
}
}
_ => {}
}
@@ -32,17 +40,29 @@ impl GameData<'_> {
fn update_push_constants(self: &mut Self) {
self.push_constants.time = self.start_time.elapsed().unwrap().as_millis() as f32 / 1000.0;
self.push_constants.model = Matrix4::identity();
self.push_constants.view = Matrix4::look_at(Point3::new(f32::sin(self.push_constants.time), 0.0, f32::cos(self.push_constants.time)), Point3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 1.0, 0.0));
self.push_constants.projection = cgmath::perspective(Rad(std::f32::consts::FRAC_PI_2), self.aspect_ratio, 0.01, 100.0);
}
}
#[derive(Debug, Clone, Copy)]
pub struct PushConstants {
pub time: f32
pub time: f32,
pub model: Matrix4<f32>,
pub view: Matrix4<f32>,
pub projection: Matrix4<f32>,
}
fn main() {
let mut whatever = String::new();
std::io::stdin().read_line(&mut whatever).unwrap();
let mut pc = PushConstants {
time: 0.0
time: 0.0,
model: Matrix4::identity(),
view: Matrix4::identity(),
projection: Matrix4::identity()
};
let data = GameData {
@@ -57,7 +77,9 @@ fn main() {
],
push_constants: &mut pc,
start_time: SystemTime::now(),
recreate_pipeline: false
recreate_pipeline: false,
aspect_ratio: 1.0,
shutdown: false,
};
vulkan::init(data);

View File

@@ -2,7 +2,7 @@ use vulkano::buffer::{BufferUsage, CpuAccessibleBuffer};
use vulkano::command_buffer::{AutoCommandBufferBuilder, DynamicState};
use vulkano::device::{Device, DeviceExtensions};
use vulkano::framebuffer::{Framebuffer, FramebufferAbstract, Subpass, RenderPassAbstract};
use vulkano::image::SwapchainImage;
use vulkano::image::{SwapchainImage, AttachmentImage};
use vulkano::instance::{Instance, PhysicalDevice, ApplicationInfo, Version, InstanceExtensions};
use vulkano::pipeline::{GraphicsPipeline};
use vulkano::pipeline::shader::{GraphicsShaderType, ShaderModule};
@@ -13,6 +13,7 @@ use vulkano::sync::{GpuFuture, FlushError};
use vulkano::sync;
use vulkano::pipeline::vertex::SingleBufferDefinition;
use vulkano::descriptor::PipelineLayoutAbstract;
use vulkano::format::Format;
use vulkano_win::VkSurfaceBuild;
@@ -51,6 +52,8 @@ pub struct GameData<'a> {
pub line_vertices: Vec<Vertex>,
pub push_constants: &'a mut PushConstants,
pub recreate_pipeline: bool,
pub aspect_ratio: f32,
pub shutdown: bool,
}
pub fn init(mut game: GameData) {
@@ -130,44 +133,15 @@ pub fn init(mut game: GameData) {
//
// 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| {
// We take the first queue that supports drawing to our window.
q.supports_graphics() && surface.is_supported(q).unwrap_or(false)
}).unwrap();
// Now initializing the device. This is probably the most important object of Vulkan.
//
// We have to pass five parameters when creating a device:
//
// - Which physical device to connect to.
//
// - A list of optional features and extensions that our program needs to work correctly.
// Some parts of the Vulkan specs are optional and must be enabled manually at device
// creation. In this example the only thing we are going to need is the `khr_swapchain`
// extension that allows us to draw to a window.
//
// - A list of layers to enable. This is very niche, and you will usually pass `None`.
//
// - The list of queues that we are going to use. The exact parameter is an iterator whose
// items are `(Queue, f32)` where the floating-point represents the priority of the queue
// between 0.0 and 1.0. The priority of the queue is a hint to the implementation about how
// much it should prioritize queues between one another.
//
// The list of created queues is returned by the function alongside with the device.
let device_ext = DeviceExtensions { khr_swapchain: true, .. DeviceExtensions::none() };
let (device, mut queues) = Device::new(physical, physical.supported_features(), &device_ext,
[(queue_family, 0.5)].iter().cloned()).unwrap();
// Since we can request multiple queues, the `queues` variable is in fact an iterator. In this
// example we use only one queue, so we just retrieve the first and only element of the
// iterator and throw it away.
let queue = queues.next().unwrap();
// Before we can draw on the surface, we have to create what is called a swapchain. Creating
// a swapchain allocates the color buffers that will contain the image that will ultimately
// be visible on the screen. These images are returned alongside with the swapchain.
let (mut swapchain, images) = {
// Querying the capabilities of the surface. When we create the swapchain we can only
// pass values that are allowed by the capabilities.
let caps = surface.capabilities(physical).unwrap();
let usage = caps.supported_usage_flags;
@@ -202,7 +176,6 @@ pub fn init(mut game: GameData) {
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();
@@ -211,66 +184,29 @@ pub fn init(mut game: GameData) {
let render_pass = Arc::new(vulkano::single_pass_renderpass!(
device.clone(),
attachments: {
// `color` is a custom name we give to the first and only attachment.
color: {
// `load: Clear` means that we ask the GPU to clear the content of this
// attachment at the start of the drawing.
load: Clear,
// `store: Store` means that we ask the GPU to store the output of the draw
// in the actual image. We could also ask it to discard the result.
store: Store,
// `format: <ty>` indicates the type of the format of the image. This has to
// be one of the types of the `vulkano::format` module (or alternatively one
// of your structs that implements the `FormatDesc` trait). Here we use the
// same format as the swapchain.
format: swapchain.format(),
// TODO:
samples: 1,
},
depth: {
load: Clear,
store: DontCare,
format: Format::D16Unorm,
samples: 1,
}
},
pass: {
// We use the attachment named `color` as the one and only color attachment.
color: [color],
// No depth-stencil attachment is indicated with empty brackets.
depth_stencil: {}
depth_stencil: {depth}
}
).unwrap());
let sub_pass = Subpass::from(render_pass.clone(), 0).unwrap();
let mut pipeline = create_pipeline(device.clone(), sub_pass.clone()).unwrap();
let line_shader_vertex_entry;
let line_shader_fragment_entry;
let line_shader_module_vertex;
let line_shader_module_fragment;
let (line_shader, line_shader_data) = read_shader("shaders/line.vert", "shaders/line.frag").unwrap();
unsafe {
line_shader_module_vertex = ShaderModule::from_words(device.clone(), &line_shader.vertex).expect("Failed to load");
line_shader_vertex_entry = line_shader_module_vertex.graphics_entry_point(
CStr::from_bytes_with_nul_unchecked(b"main\0"),
line_shader_data.vert_input,
line_shader_data.vert_output,
line_shader_data.vert_layout,
GraphicsShaderType::Vertex);
line_shader_module_fragment = ShaderModule::from_words(device.clone(), &line_shader.fragment).expect("Failed to load");
line_shader_fragment_entry = line_shader_module_fragment.graphics_entry_point(
CStr::from_bytes_with_nul_unchecked(b"main\0"),
line_shader_data.frag_input,
line_shader_data.frag_output,
line_shader_data.frag_layout,
GraphicsShaderType::Fragment);
};
let line_pipeline = Arc::new(GraphicsPipeline::start()
.vertex_input_single_buffer::<Vertex>()
.vertex_shader(line_shader_vertex_entry.clone(), ())
.line_list()
.viewports_dynamic_scissors_irrelevant(1)
.fragment_shader(line_shader_fragment_entry.clone(), ())
.render_pass(sub_pass.clone())
.build(device.clone())
.unwrap());
let mut pipeline = create_pipeline(device.clone(), sub_pass.clone(), "shaders/triangle.vert", "shaders/triangle.frag", false).unwrap();
let line_pipeline = create_pipeline(device.clone(), sub_pass.clone(), "shaders/line.vert", "shaders/line.frag", true).unwrap();
// Dynamic viewports allow us to recreate just the viewport when the window is resized
// Otherwise we would have to recreate the whole pipeline.
@@ -281,7 +217,7 @@ pub fn init(mut game: GameData) {
//
// 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(&images, render_pass.clone(), &mut dynamic_state);
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
@@ -331,13 +267,13 @@ 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(&new_images, render_pass.clone(), &mut dynamic_state);
framebuffers = window_size_dependent_setup(device.clone(), &new_images, render_pass.clone(), &mut dynamic_state, &mut game.aspect_ratio);
recreate_swapchain = false;
}
if game.recreate_pipeline {
if let Some(pipeline_ok) = create_pipeline(device.clone(), sub_pass.clone()) {
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 {
@@ -363,7 +299,7 @@ pub fn init(mut game: GameData) {
};
// Specify the color to clear the framebuffer with i.e. blue
let clear_values = vec!([0.0, 0.0, 1.0, 1.0].into());
let clear_values = vec!([0.0, 0.0, 1.0, 1.0].into(), 1f32.into());
game.update_push_constants();
@@ -437,27 +373,23 @@ pub fn init(mut game: GameData) {
// Handling the window events in order to close the program when the user wants to close
// it.
let mut done = false;
events_loop.poll_events(|ev| {
if !game.on_window_event(&ev) {
match ev {
Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => done = true,
Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => game.shutdown = true,
Event::WindowEvent { event: WindowEvent::Resized(_), .. } => recreate_swapchain = true,
_ => {}
}
}
});
if done { return; }
if game.shutdown { return; }
}
}
/// This method is called once during initialization, then again whenever the window is resized
fn window_size_dependent_setup(
images: &[Arc<SwapchainImage<Window>>],
render_pass: Arc<dyn RenderPassAbstract + Send + Sync>,
dynamic_state: &mut DynamicState
) -> Vec<Arc<dyn FramebufferAbstract + Send + Sync>> {
fn window_size_dependent_setup(device: Arc<Device>, images: &[Arc<SwapchainImage<Window>>], render_pass: Arc<dyn RenderPassAbstract + Send + Sync>, dynamic_state: &mut DynamicState, aspect_ratio: &mut f32) -> Vec<Arc<dyn FramebufferAbstract + Send + Sync>> {
let dimensions = images[0].dimensions();
*aspect_ratio = dimensions[0] as f32 / dimensions[1] as f32;
let viewport = Viewport {
origin: [0.0, 0.0],
@@ -466,60 +398,71 @@ fn window_size_dependent_setup(
};
dynamic_state.viewports = Some(vec!(viewport));
let depth_buffer = AttachmentImage::transient(device.clone(), dimensions, Format::D16Unorm).unwrap();
images.iter().map(|image| {
Arc::new(
Framebuffer::start(render_pass.clone())
.add(image.clone()).unwrap()
.build().unwrap()
Arc::new(Framebuffer::start(render_pass.clone())
.add(image.clone()).unwrap()
.add(depth_buffer.clone()).unwrap()
.build().unwrap()
) as Arc<dyn FramebufferAbstract + Send + Sync>
}).collect::<Vec<_>>()
}
fn create_pipeline<T: RenderPassAbstract>(device: Arc<Device>, sub_pass: Subpass<Arc<T>>) -> Option<Arc<GraphicsPipeline<SingleBufferDefinition<Vertex>, Box<dyn PipelineLayoutAbstract + Send + Sync>, Arc<T>>>> {
if let Some((mesh_shader, mesh_shader_data)) = read_shader("shaders/triangle.vert", "shaders/triangle.frag") {
let mesh_shader_vertex_entry;
let mesh_shader_fragment_entry;
let mesh_shader_module_vertex;
let mesh_shader_module_fragment;
fn create_pipeline<T: RenderPassAbstract>(device: Arc<Device>, sub_pass: Subpass<Arc<T>>, vertex_shader_path: &str, fragment_shader_path: &str, is_line: bool) -> Option<Arc<GraphicsPipeline<SingleBufferDefinition<Vertex>, Box<dyn PipelineLayoutAbstract + Send + Sync>, Arc<T>>>> {
if let Some((shader, shader_data)) = read_shader(vertex_shader_path, fragment_shader_path) {
let vertex_shader_entry;
let fragment_shader_entry;
let vertex_shader_module;
let fragment_shader_module;
unsafe {
mesh_shader_module_vertex = ShaderModule::from_words(device.clone(), &mesh_shader.vertex).expect("Failed to load");
mesh_shader_vertex_entry = mesh_shader_module_vertex.graphics_entry_point(
vertex_shader_module = ShaderModule::from_words(device.clone(), &shader.vertex).expect("Failed to load vertex shader.");
vertex_shader_entry = vertex_shader_module.graphics_entry_point(
CStr::from_bytes_with_nul_unchecked(b"main\0"),
mesh_shader_data.vert_input,
mesh_shader_data.vert_output,
mesh_shader_data.vert_layout,
shader_data.vert_input,
shader_data.vert_output,
shader_data.vert_layout,
GraphicsShaderType::Vertex);
mesh_shader_module_fragment = ShaderModule::from_words(device.clone(), &mesh_shader.fragment).expect("Failed to load");
mesh_shader_fragment_entry = mesh_shader_module_fragment.graphics_entry_point(
fragment_shader_module = ShaderModule::from_words(device.clone(), &shader.fragment).expect("Failed to load fragment shader.");
fragment_shader_entry = fragment_shader_module.graphics_entry_point(
CStr::from_bytes_with_nul_unchecked(b"main\0"),
mesh_shader_data.frag_input,
mesh_shader_data.frag_output,
mesh_shader_data.frag_layout,
shader_data.frag_input,
shader_data.frag_output,
shader_data.frag_layout,
GraphicsShaderType::Fragment);
};
// Before we draw we have to create what is called a pipeline. This is similar to an OpenGL
// program, but much more specific.
let pipeline = Arc::new(GraphicsPipeline::start()
// We need to indicate the layout of the vertices.
.vertex_input_single_buffer::<Vertex>()
// A Vulkan shader can in theory contain multiple entry points, so we have to specify
// which one. The `main` word of `main_entry_point` actually corresponds to the name of
// the entry point.
.vertex_shader(mesh_shader_vertex_entry.clone(), ())
// The content of the vertex buffer describes a list of triangles.
.triangle_list()
// Use a resizable viewport set to draw over the entire window
.viewports_dynamic_scissors_irrelevant(1)
// See `vertex_shader`.
.fragment_shader(mesh_shader_fragment_entry.clone(), ())
// We have to indicate which subpass of which render pass this pipeline is going to be used
// in. The pipeline will only be usable from this particular subpass.
.render_pass(sub_pass.clone())
// Now that our builder is filled, we call `build()` to obtain an actual pipeline.
.build(device.clone())
.unwrap());
let pipeline;
if is_line {
pipeline = Arc::new(GraphicsPipeline::start()
.vertex_input_single_buffer::<Vertex>()
.vertex_shader(vertex_shader_entry.clone(), ())
.line_list()
.viewports_dynamic_scissors_irrelevant(1)
.fragment_shader(fragment_shader_entry.clone(), ())
.render_pass(sub_pass.clone())
.build(device.clone())
.unwrap());
} else {
// Before we draw we have to create what is called a pipeline. This is similar to an OpenGL
// program, but much more specific.
pipeline = Arc::new(GraphicsPipeline::start()
.vertex_input_single_buffer::<Vertex>()
// A Vulkan shader can in theory contain multiple entry points, so we have to specify
// which one. The `main` word of `main_entry_point` actually corresponds to the name of
// the entry point.
.vertex_shader(vertex_shader_entry.clone(), ())
.triangle_list()
// Use a resizable viewport set to draw over the entire window
.viewports_dynamic_scissors_irrelevant(1)
.fragment_shader(fragment_shader_entry.clone(), ())
// We have to indicate which subpass of which render pass this pipeline is going to be used
// in. The pipeline will only be usable from this particular subpass.
.render_pass(sub_pass.clone())
.build(device.clone())
.unwrap());
}
return Some(pipeline);
} else {