Files
rust-engine/src/vulkan/mod.rs
2020-12-02 10:21:51 +01:00

581 lines
24 KiB
Rust

use std::sync::Arc;
use std::time::SystemTime;
use cgmath::{Matrix4, SquareMatrix};
use image::{ImageBuffer, Rgb, Rgba};
use image::buffer::ConvertBuffer;
use vulkano::{buffer::{BufferUsage, CpuAccessibleBuffer}, command_buffer::CommandBuffer, image::{ImageLayout, MipmapsCount}};
use vulkano::command_buffer::{AutoCommandBuffer, AutoCommandBufferBuilder, DynamicState};
use vulkano::descriptor::DescriptorSet;
use vulkano::device::{Device, DeviceExtensions, Queue};
use vulkano::format::{ClearValue, Format};
use vulkano::framebuffer::{Framebuffer, FramebufferAbstract, RenderPassAbstract};
use vulkano::image::{AttachmentImage, Dimensions, ImageUsage, ImageViewAccess, ImmutableImage, SwapchainImage};
use vulkano::instance::{ApplicationInfo, Instance, InstanceExtensions, PhysicalDevice, Version};
use vulkano::instance::debug::{DebugCallback, MessageSeverity, MessageType};
use vulkano::pipeline::viewport::Viewport;
use vulkano::sampler::{Filter, MipmapMode, Sampler, SamplerAddressMode};
use vulkano::swapchain::{AcquireError, ColorSpace, FullscreenExclusive, PresentMode, Surface, SurfaceTransform, Swapchain, SwapchainCreationError};
use vulkano::swapchain;
use vulkano::sync::{FlushError, GpuFuture};
use vulkano::sync;
use vulkano_win::VkSurfaceBuild;
use winit::event::{Event, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::{Window, WindowBuilder};
use mesh::CPUMesh;
use pipelines::{Drawcall, LineShader};
use pipelines::{line_vs::ty::LinePushConstants};
use pipelines::DefaultShader;
use pipelines::vs;
use pipelines::vs::ty::PushConstants;
use crate::config::RenderConfig;
use crate::vulkan::{gameobject::{GameObject, GameObjectHandle}};
pub mod pipelines;
pub mod gameobject;
pub mod mesh;
const VALIDATION_LAYERS: &[&str] = &[
"VK_LAYER_KHRONOS_validation"
];
#[derive(Default, Debug, Clone)]
pub struct Vertex {
pub position: [f32; 3],
pub uv: [f32; 2],
pub normal: [f32; 3],
pub tangent: [f32; 4],
pub bone_index: [i32; 4],
pub bone_weight: [f32; 4],
}
vulkano::impl_vertex!(Vertex, position, uv, normal, tangent, bone_index, bone_weight);
#[derive(Default, Debug, Clone)]
pub struct LinePoint {
pub position: [f32; 3],
}
vulkano::impl_vertex!(LinePoint, position);
pub trait Game {
/// Returns true if event should be ignored by the vulkan handler
fn on_window_event(self: &mut Self, event: &Event<()>);
fn update(self: &mut Self, renderer: &mut VulkanRenderer) -> vs::ty::ObjectUniformData;
}
pub struct Mesh {
pub vertex_buffer: Arc<CpuAccessibleBuffer<[Vertex]>>,
pub index_buffer: Arc<CpuAccessibleBuffer<[u32]>>,
pub original_path: String,
}
#[derive(Debug, Clone)]
pub struct MeshHandle {
pub index: usize,
pub diffuse_handle: TextureHandle,
pub normal_handle: TextureHandle,
pub original_path: String
}
pub(crate) type TextureHandle = usize;
pub struct GameData {
pub start_time: SystemTime,
pub line_vertices: Vec<LinePoint>,
pub push_constants: PushConstants,
pub line_push_constants: LinePushConstants,
pub recreate_pipeline: bool,
pub dimensions: [u32; 2],
pub shutdown: bool,
pub game_objects: Vec<GameObject>,
pub meshes: Vec<Mesh>,
pub textures: Vec<Arc<ImmutableImage<Format>>>,
pub use_line_pipeline: bool,
}
pub(crate) type RendererDescriptorSets = dyn DescriptorSet + Send + Sync;
pub struct VulkanRenderer {
pub game_data: GameData,
pub device: Arc<Device>,
pub framebuffers: Vec<Arc<dyn FramebufferAbstract + Send + Sync>>,
pub sampler: Arc<Sampler>,
pub dynamic_state: DynamicState,
pub pipelines: Vec<Box<dyn Drawcall>>,
pub surface: Arc<Surface<Window>>,
pub swapchain: Arc<Swapchain<Window>>,
pub render_pass: Arc<dyn RenderPassAbstract + Send + Sync>,
pub queue: Arc<Queue>,
pub recreate_swapchain: bool,
pub debug_callback: Option<DebugCallback>,
pub previous_frame_end: Option<Box<dyn GpuFuture>>,
pub uniform_buffers: Vec<Arc<CpuAccessibleBuffer<vs::ty::ObjectUniformData>>>,
pub msaa_sample_count: u32,
}
impl VulkanRenderer {
pub fn init(line_vertices: Vec<LinePoint>, enable_validation_layers: bool, render_config: RenderConfig) -> (VulkanRenderer, EventLoop<()>) {
// Create empty game data struct to be filled
let mut data = GameData {
push_constants: PushConstants {
model: Matrix4::identity().into(),
},
line_push_constants: LinePushConstants {
model: Matrix4::identity().into(),
view: Matrix4::identity().into(),
projection: Matrix4::identity().into(),
},
start_time: SystemTime::now(),
recreate_pipeline: false,
shutdown: false,
line_vertices,
dimensions: [0, 0],
meshes: vec![],
game_objects: vec![],
textures: vec![],
use_line_pipeline: true,
};
// Create basic vulkan instance with layers and info
let instance = {
let extensions = InstanceExtensions {
ext_debug_utils: true,
..vulkano_win::required_extensions()
};
let app_info = ApplicationInfo {
application_name: Some("Asuro's Editor".into()),
application_version: Some(Version { major: 0, minor: 1, patch: 0 }),
engine_name: Some("Asuro's Rust Engine".into()),
engine_version: Some(Version { major: 0, minor: 1, patch: 0 })
};
if enable_validation_layers {
println!("Enabling validation layers...");
let available_layers = vulkano::instance::layers_list().unwrap().map(|layer| String::from(layer.name())).collect::<Vec<String>>();
println!("Available layers: {:?}", available_layers);
VALIDATION_LAYERS.iter().for_each(|wanted_layer_name| {
if !available_layers.iter().any(|available_layer_name| available_layer_name == wanted_layer_name) {
panic!("Validation layer not found: {:?}", wanted_layer_name);
}
});
Instance::new(Some(&app_info), &extensions, VALIDATION_LAYERS.iter().cloned()).expect("failed to create Vulkan instance")
} else {
Instance::new(Some(&app_info), &extensions, None).expect("failed to create Vulkan instance")
}
};
// lifetime of this is important, even tho it isn't used!
let mut debug_callback = None;
// Debug stuff
if enable_validation_layers {
let msg_severity = MessageSeverity {
verbose: false,
information: true,
warning: true,
error: true
};
let msg_types = MessageType {
general: true,
performance: true,
validation: true
};
debug_callback = DebugCallback::new(&instance, msg_severity, msg_types, |msg| {
let type_str = match (msg.severity.error, msg.severity.warning, msg.severity.information, msg.severity.verbose) {
(true, _, _, _) => "!!",
(_, true, _, _) => "!",
(_, _, _, true) => "i",
_ => " "
};
let layer_str = msg.layer_prefix;
println!("[{}][{}]: {}", type_str, layer_str, msg.description);
}).ok();
}
// TODO: Just get the first physical device we find, it's fiiiine...
let physical = PhysicalDevice::enumerate(&instance).next().unwrap();
println!("Using device: {} (type: {:?})", physical.name(), physical.ty());
let events_loop = EventLoop::new();
let surface = WindowBuilder::new().build_vk_surface(&events_loop, instance.clone()).unwrap();
let window = surface.window();
// TODO: Tutorial says we need more queues
// 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.
let queue_family = physical.queue_families().find(|&q| {
q.supports_graphics() && surface.is_supported(q).unwrap_or(false)
}).unwrap();
// Queue
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();
let queue = queues.next().unwrap();
// Swapchain
let (swapchain, images) = {
let caps = surface.capabilities(physical).unwrap();
let usage = caps.supported_usage_flags;
let alpha = caps.supported_composite_alpha.iter().next().unwrap();
let format = caps.supported_formats[0].0;
let inner_size = window.inner_size();
data.dimensions = [inner_size.width, inner_size.height];
Swapchain::new(device.clone(), surface.clone(), caps.min_image_count, format,
data.dimensions, 1, usage, &queue, SurfaceTransform::Identity, alpha,
PresentMode::Fifo, FullscreenExclusive::Default, true, ColorSpace::SrgbNonLinear).unwrap()
};
// Render pass
let render_pass = Arc::new(vulkano::single_pass_renderpass!(
device.clone(),
attachments: {
color: {
load: DontCare,
store: Store,
format: swapchain.format(),
samples: 1,
},
intermediary: {
load: Clear,
store: DontCare,
format: swapchain.format(),
samples: render_config.msaa_samples,
},
depth: {
load: Clear,
store: Store,
format: Format::D16Unorm,
samples: render_config.msaa_samples,
initial_layout: ImageLayout::Undefined,
final_layout: ImageLayout::DepthStencilAttachmentOptimal,
}
},
pass: {
color: [intermediary],
depth_stencil: {depth},
resolve: [color]
}
).unwrap());
let sampler = Sampler::new(device.clone(), Filter::Linear, Filter::Linear,
MipmapMode::Nearest, SamplerAddressMode::Repeat, SamplerAddressMode::Repeat,
SamplerAddressMode::Repeat, 0.0, 1.0, 0.0, 0.0).unwrap();
let line_vertex_buffer = CpuAccessibleBuffer::from_iter(device.clone(), BufferUsage::vertex_buffer(), false, data.line_vertices.iter().cloned()).unwrap();
let pipelines: Vec<Box<dyn Drawcall>> = vec![
Box::new(DefaultShader::new(device.clone(), render_pass.clone())),
Box::new(LineShader::new(device.clone(), render_pass.clone(), line_vertex_buffer)),
];
// Dynamic viewports allow us to recreate just the viewport when the window is resized
// Otherwise we would have to recreate the whole pipeline.
let mut dynamic_state = DynamicState { line_width: None, viewports: None, scissors: None, compare_mask: None, write_mask: None, reference: None };
let msaa_attachments = Self::create_msaa_buffers(device.clone(), data.dimensions, &swapchain, render_config.msaa_samples);
// 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.
let framebuffers = window_size_dependent_setup(device.clone(), &images, &msaa_attachments, render_config.msaa_samples, render_pass.clone(), &mut dynamic_state);
let mut uniform_buffers = Vec::new();
let uniform_buffer = vs::ty::ObjectUniformData {
view: Matrix4::identity().into(),
projection: Matrix4::identity().into(),
time: 0.0,
light_position: [0.0, 0.0, 0.0],
camera_position: [0.0, 0.0, 0.0],
_dummy0: [0; 12],
_dummy1: [0; 4],
};
for _ in 0..swapchain.num_images() {
uniform_buffers.push(CpuAccessibleBuffer::from_data(
device.clone(),
BufferUsage::uniform_buffer_transfer_destination(),
false,
uniform_buffer,
).unwrap());
}
// 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 previous_frame_end = Some(Box::new(sync::now(device.clone())) as Box<dyn GpuFuture>);
(VulkanRenderer { game_data: data, device, framebuffers, sampler,
dynamic_state, pipelines, uniform_buffers,
surface, swapchain, render_pass, queue,
recreate_swapchain: false, debug_callback, previous_frame_end,
msaa_sample_count: render_config.msaa_samples
}, events_loop)
}
fn create_command_buffer(self: &mut Self, fb_index: usize, uniform_buffer_data: vs::ty::ObjectUniformData) -> Arc<AutoCommandBuffer> {
// General setup
let mut builder = AutoCommandBufferBuilder::primary_simultaneous_use(self.device.clone(), self.queue.family()).unwrap();
builder.update_buffer(self.uniform_buffers[fb_index].clone(), uniform_buffer_data).unwrap();
builder.begin_render_pass(self.framebuffers[fb_index].clone(), false, vec![ClearValue::None, ClearValue::Float([0.0, 0.0, 0.0, 1.0]), ClearValue::Depth(1.0)]).unwrap();
// Draw meshes etc.
for pipeline in &self.pipelines {
pipeline.draw(&mut builder, fb_index, &self.game_data, &self.dynamic_state);
}
// General cleanup
builder.end_render_pass().unwrap();
Arc::new(builder.build().unwrap())
}
fn create_msaa_buffers(device: Arc<Device>, dimensions: [u32; 2], swapchain: &Arc<Swapchain<Window>>, sample_count: u32) -> Vec<Arc<AttachmentImage>> {
let mut msaa_attachments = vec![];
for _ in 0..swapchain.num_images() {
msaa_attachments.push(AttachmentImage::transient_multisampled(device.clone(), dimensions, sample_count, swapchain.format()).unwrap());
}
msaa_attachments
}
pub fn render_loop(self: &mut Self, new_ubo: vs::ty::ObjectUniformData) {
// cleanup previous frame
self.previous_frame_end.as_mut().unwrap().cleanup_finished();
// recreate swapchain if window size changed
if self.recreate_swapchain {
let window = self.surface.window();
let inner_size = window.inner_size();
self.game_data.dimensions = [inner_size.width, inner_size.height];
let (new_swapchain, new_images) = match self.swapchain.recreate_with_dimensions(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) => {
println!("Swapchain rejected: UnsupportedDimensions");
return;
}
Err(err) => panic!("{:?}", err),
};
let msaa_buffers = Self::create_msaa_buffers(self.device.clone(), self.game_data.dimensions, &new_swapchain, self.msaa_sample_count);
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, &msaa_buffers, self.msaa_sample_count, self.render_pass.clone(), &mut self.dynamic_state);
self.recreate_swapchain = false;
}
// recreate pipeline if requested
if self.game_data.recreate_pipeline {
let device = self.device.clone();
let render_pass = self.render_pass.clone();
self.pipelines.iter_mut().for_each(|pipeline| pipeline.recreate_pipeline(device.clone(), render_pass.clone()));
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 (fb_index, _, acquire_future) = match swapchain::acquire_next_image(self.swapchain.clone(), None) {
Ok(r) => r,
Err(AcquireError::OutOfDate) => {
self.recreate_swapchain = true;
return;
},
Err(err) => panic!("{:?}", err)
};
let command_buffer = self.create_command_buffer(fb_index, new_ubo).clone();
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(), fb_index)
.then_signal_fence_and_flush();
match future {
Ok(future) => {
// we're joining on the previous future but the CPU is running faster than the GPU so
// eventually it stutters, and jumps ahead to the newer frames.
//
// See vulkano issue 1135: https://github.com/vulkano-rs/vulkano/issues/1135
// This makes sure the CPU stays in sync with the GPU in situations when the CPU is
// running "too fast"
#[cfg(target_os = "macos")]
future.wait(None).unwrap();
self.previous_frame_end = Some(Box::new(future) as Box<_>);
},
Err(FlushError::OutOfDate) => {
println!("Swapchain out of date!");
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<_>);
}
};
}
pub fn upload_mesh(self: &mut Self, mesh: CPUMesh, original_path: String) -> usize {
let vertex_buffer = CpuAccessibleBuffer::from_iter(self.device.clone(), BufferUsage::vertex_buffer(), false, mesh.vertices.into_iter()).unwrap();
let index_buffer = CpuAccessibleBuffer::from_iter(self.device.clone(), BufferUsage::index_buffer(), false, mesh.indices.into_iter()).unwrap();
self.game_data.meshes.push(Mesh { vertex_buffer, index_buffer, original_path });
self.game_data.meshes.len() - 1
}
pub fn upload_texture(self: &mut Self, texture_data: &gltf::image::Data) {
// Format buffer on cpu for upload
let buffer: ImageBuffer<Rgb<u8>, Vec<u8>> = image::ImageBuffer::from_raw(texture_data.width, texture_data.height, texture_data.pixels.clone()).unwrap();
let new_buffer: ImageBuffer<Rgba<u8>, Vec<u8>> = buffer.convert();
let dimensions = Dimensions::Dim2d { width: texture_data.width, height: texture_data.height };
let source = CpuAccessibleBuffer::from_iter(
self.device.clone(),
BufferUsage::transfer_source(),
false,
new_buffer.iter().cloned(),
).unwrap();
// Create image
let (image_view, init) = ImmutableImage::uninitialized(
self.device.clone(),
dimensions,
Format::R8G8B8A8Unorm,
MipmapsCount::Log2,
ImageUsage {
transfer_source: true,
transfer_destination: true,
sampled: true,
..ImageUsage::none()
},
ImageLayout::ShaderReadOnlyOptimal,
self.device.active_queue_families()
).unwrap();
// Upload image data
let mut command_buffer_builder = AutoCommandBufferBuilder::new(self.device.clone(), self.queue.family()).unwrap();
command_buffer_builder.copy_buffer_to_image_dimensions(
source,
init,
[0, 0, 0],
dimensions.width_height_depth(),
0,
dimensions.array_layers_with_cube(),
0,
).unwrap();
// Generate mipmaps
// let mut mip_width = image_view.dimensions().width() as i32;
// let mut mip_height = image_view.dimensions().height() as i32;
// for i in 0..image_view.mipmap_levels() {
// command_buffer_builder.blit_image(
// image_view.clone(),
// [0; 3],
// [mip_width, mip_height, 1],
// 0,
// i,
// image_view.clone(),
// [0; 3],
// [mip_width / 2, mip_height / 2, 1],
// 0,
// i + 1,
// dimensions.array_layers_with_cube(),
// Filter::Linear).unwrap();
// if mip_width > 1 { mip_width = mip_width / 2; }
// if mip_height > 1 { mip_height = mip_height / 2; }
// }
let command_buffer = command_buffer_builder.build().unwrap();
let command_buffer_future = command_buffer.execute(self.queue.clone()).unwrap();
command_buffer_future.flush().unwrap();
self.game_data.textures.push(image_view);
}
pub fn add_game_object(self: &mut Self, mut game_object: GameObject, pipeline_index: usize) -> GameObjectHandle {
self.pipelines[pipeline_index].create_descriptor_set(&mut game_object, self);
self.game_data.game_objects.push(game_object);
GameObjectHandle {
object_index: self.game_data.game_objects.len() - 1
}
}
pub fn clear_all(&mut self) {
self.game_data.game_objects.clear();
self.game_data.meshes.clear();
self.game_data.textures.clear();
}
}
pub fn start_event_loop(mut renderer: VulkanRenderer, mut game: Box<dyn Game>, event_loop: EventLoop<()>) {
event_loop.run(move |event, _, control_flow| {
game.on_window_event(&event);
match event {
Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => {
*control_flow = ControlFlow::Exit;
},
Event::RedrawEventsCleared => {
let ubo = game.update(&mut renderer);
renderer.render_loop(ubo);
},
_ => {}
}
});
}
/// This method is called once during initialization, then again whenever the window is resized
fn window_size_dependent_setup(device: Arc<Device>, images: &[Arc<SwapchainImage<Window>>], msaa_buffers: &[Arc<AttachmentImage>], msaa_sample_count: u32, render_pass: Arc<dyn RenderPassAbstract + Send + Sync>, dynamic_state: &mut DynamicState) -> Vec<Arc<dyn FramebufferAbstract + Send + Sync>> {
let dimensions = images[0].dimensions();
let dim_array = [dimensions.width() as f32, dimensions.height() as f32];
let dim_array_u32 = [dimensions.width() as u32, dimensions.height() as u32];
let viewport = Viewport {
origin: [0.0, 0.0],
dimensions: dim_array,
depth_range: 0.0 .. 1.0,
};
dynamic_state.viewports = Some(vec!(viewport));
let depth_image = AttachmentImage::multisampled_with_usage(device.clone(), dim_array_u32, msaa_sample_count, Format::D16Unorm, ImageUsage { depth_stencil_attachment: true, ..ImageUsage::none() }).unwrap();
let mut framebuffers = vec![];
for i in 0..images.len() {
let image_buffer = &images[i];
let msaa_buffer = &msaa_buffers[i];
framebuffers.push(Arc::new(Framebuffer::start(render_pass.clone())
.add(image_buffer.clone()).unwrap()
.add(msaa_buffer.clone()).unwrap()
.add(depth_image.clone()).unwrap()
.build().unwrap()
) as Arc<dyn FramebufferAbstract + Send + Sync>);
}
framebuffers
}