refactor
This commit is contained in:
21
src/main.rs
21
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};
|
||||
use crate::vulkan::{GameData, Game, LinePoint, GameObject, VulkanRenderer};
|
||||
|
||||
mod input;
|
||||
use crate::input::{InputState};
|
||||
@@ -10,16 +10,18 @@ use crate::input::{InputState};
|
||||
mod config;
|
||||
use crate::config::LogConfig;
|
||||
|
||||
mod mesh;
|
||||
|
||||
struct TestGame<'a> {
|
||||
input: InputState<'a>,
|
||||
cam_position: Vector3<f32>,
|
||||
cam_rotation: Quaternion<f32>,
|
||||
log_config: &'a LogConfig,
|
||||
}
|
||||
|
||||
impl Game for TestGame<'_> {
|
||||
fn validation_layers_enabled(self: &Self) -> bool {
|
||||
self.log_config.vulkan_validation_layers
|
||||
fn game_start(self: &mut Self, _game_data: &mut GameData) {
|
||||
println!("Game started.");
|
||||
// let player_mesh = init_data.upload_mesh(mesh::load_mesh("models/iski51.dae").into_iter().nth(0).unwrap());
|
||||
}
|
||||
|
||||
fn update(self: &mut Self, game_data: &mut GameData) {
|
||||
@@ -53,8 +55,6 @@ impl Game for TestGame<'_> {
|
||||
self.input.get_axis("move_forward") * 0.05));
|
||||
|
||||
// Move game objects
|
||||
let model = Matrix4::from_angle_z(Rad::from(Deg(game_data.push_constants.time * 100.0)));
|
||||
|
||||
let view = Matrix4::from(self.cam_rotation) * Matrix4::from_translation(self.cam_position);
|
||||
|
||||
let mut proj = cgmath::perspective(
|
||||
@@ -66,7 +66,6 @@ impl Game for TestGame<'_> {
|
||||
|
||||
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();
|
||||
game_data.line_push_constants.view = view.into();
|
||||
@@ -87,12 +86,10 @@ fn main() {
|
||||
input: InputState::new("config/input.toml", &log_config),
|
||||
cam_rotation: Quaternion::one(),
|
||||
cam_position: Vector3::new(0.0, 0.0, -10.0),
|
||||
log_config: &log_config,
|
||||
};
|
||||
|
||||
let line_count = 30;
|
||||
vulkan::init(
|
||||
vec!["models/cube.dae"],
|
||||
let mut renderer = VulkanRenderer::init(
|
||||
(-line_count..=line_count)
|
||||
.flat_map(|it| vec![
|
||||
LinePoint { position: [it as f32, -line_count as f32, 0.] },
|
||||
@@ -100,6 +97,8 @@ fn main() {
|
||||
LinePoint { position: [-line_count as f32, it as f32, 0.] },
|
||||
LinePoint { position: [line_count as f32, it as f32, 0.] },
|
||||
]).collect(),
|
||||
&mut game
|
||||
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);
|
||||
}
|
||||
62
src/mesh.rs
Normal file
62
src/mesh.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
use crate::vulkan::Vertex;
|
||||
use std::path::Path;
|
||||
|
||||
pub struct CPUMesh {
|
||||
pub(crate) vertices: Vec<Vertex>,
|
||||
pub(crate) indices: Vec<u32>,
|
||||
}
|
||||
|
||||
pub fn load_mesh(mesh_path: &str, print_status: bool) -> Vec<CPUMesh> {
|
||||
struct TempVertex {
|
||||
pos: Option<[f32; 3]>,
|
||||
uv: Option<[f32; 2]>,
|
||||
normal: Option<[f32; 3]>,
|
||||
}
|
||||
|
||||
if print_status {
|
||||
println!("Start loading file {}", mesh_path);
|
||||
}
|
||||
let file = collada::document::ColladaDocument::from_path(Path::new(mesh_path)).unwrap();
|
||||
file.get_obj_set().unwrap().objects.iter().map(|model| {
|
||||
if print_status {
|
||||
println!("Loading mesh {}: {} Vertices", model.name, model.vertices.len());
|
||||
}
|
||||
let mut vertices: Vec<TempVertex> = model.vertices.iter().map(|v| {
|
||||
TempVertex {
|
||||
pos: Some([v.x as f32, v.y as f32, v.z as f32]),
|
||||
uv: None,
|
||||
normal: None
|
||||
}
|
||||
}).collect();
|
||||
|
||||
let mut indices = Vec::new();
|
||||
|
||||
model.geometry.iter().for_each(|geometry| geometry.mesh.iter().for_each(|primitive| {
|
||||
if let collada::PrimitiveElement::Triangles(tris) = primitive {
|
||||
tris.vertices.iter().for_each(|tri| {
|
||||
[tri.0, tri.1, tri.2].iter().for_each(|(vertex_index, texture_position_index, normal_index)| {
|
||||
indices.push(*vertex_index as u32);
|
||||
vertices[*vertex_index].uv = texture_position_index.or(None).map(|tpi| {
|
||||
let tex_vertex = model.tex_vertices[tpi];
|
||||
[tex_vertex.x as f32, 1.0 - tex_vertex.y as f32]
|
||||
});
|
||||
vertices[*vertex_index].normal = normal_index.or(None).map(|ni| {
|
||||
let normal = model.normals[ni];
|
||||
[normal.x as f32, normal.y as f32, normal.z as f32]
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
panic!("Mesh format Polylist not supported!");
|
||||
}
|
||||
}));
|
||||
|
||||
let finished_vertices = vertices.iter().map(|v| Vertex {
|
||||
position: v.pos.unwrap(),
|
||||
uv: v.uv.unwrap(),
|
||||
normal: v.normal.unwrap(),
|
||||
}).collect();
|
||||
|
||||
CPUMesh { vertices: finished_vertices, indices }
|
||||
}).collect()
|
||||
}
|
||||
633
src/vulkan.rs
633
src/vulkan.rs
@@ -1,20 +1,19 @@
|
||||
use vulkano::buffer::{BufferUsage, CpuAccessibleBuffer};
|
||||
use vulkano::command_buffer::{AutoCommandBufferBuilder, DynamicState};
|
||||
use vulkano::device::{Device, DeviceExtensions};
|
||||
use vulkano::device::{Device, DeviceExtensions, Queue};
|
||||
use vulkano::framebuffer::{Framebuffer, FramebufferAbstract, Subpass, RenderPassAbstract};
|
||||
use vulkano::image::{SwapchainImage, AttachmentImage, ImageUsage};
|
||||
use vulkano::instance::{Instance, PhysicalDevice, ApplicationInfo, Version, InstanceExtensions};
|
||||
use vulkano::pipeline::{GraphicsPipeline};
|
||||
use vulkano::pipeline::{GraphicsPipeline, GraphicsPipelineAbstract};
|
||||
use vulkano::pipeline::shader::{GraphicsShaderType, ShaderModule};
|
||||
use vulkano::pipeline::viewport::Viewport;
|
||||
use vulkano::swapchain::{AcquireError, PresentMode, SurfaceTransform, Swapchain, SwapchainCreationError};
|
||||
use vulkano::swapchain::{AcquireError, PresentMode, SurfaceTransform, Swapchain, SwapchainCreationError, Surface};
|
||||
use vulkano::swapchain;
|
||||
use vulkano::sync::{GpuFuture, FlushError};
|
||||
use vulkano::sync;
|
||||
use vulkano::pipeline::vertex::{SingleBufferDefinition};
|
||||
use vulkano::descriptor::PipelineLayoutAbstract;
|
||||
use vulkano::format::{Format, ClearValue};
|
||||
use vulkano::instance::debug::{DebugCallback, MessageTypes};
|
||||
use vulkano::memory::pool::{PotentialDedicatedAllocation, StdMemoryPoolAlloc};
|
||||
|
||||
use vulkano_win::VkSurfaceBuild;
|
||||
|
||||
@@ -22,7 +21,7 @@ use winit::{EventsLoop, Window, WindowBuilder, Event, WindowEvent};
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::time::SystemTime;
|
||||
use std::path::{PathBuf, Path};
|
||||
use std::path::{PathBuf};
|
||||
use std::ffi::{CStr};
|
||||
|
||||
use cgmath::{Matrix4, SquareMatrix};
|
||||
@@ -34,7 +33,7 @@ use shaderc;
|
||||
use vs::ty::PushConstants;
|
||||
use line_vs::ty::LinePushConstants;
|
||||
|
||||
use collada;
|
||||
use crate::mesh::CPUMesh;
|
||||
|
||||
const VALIDATION_LAYERS: &[&str] = &[
|
||||
"VK_LAYER_LUNARG_standard_validation"
|
||||
@@ -55,7 +54,7 @@ pub struct LinePoint {
|
||||
vulkano::impl_vertex!(LinePoint, position);
|
||||
|
||||
pub trait Game {
|
||||
fn validation_layers_enabled(self: &Self) -> bool;
|
||||
fn game_start(self: &mut Self, game_data: &mut GameData);
|
||||
|
||||
fn update(self: &mut Self, game_data: &mut GameData);
|
||||
|
||||
@@ -85,308 +84,341 @@ pub struct GameData {
|
||||
pub meshes: Vec<Mesh>,
|
||||
}
|
||||
|
||||
pub fn init(mesh_paths: Vec<&str>, line_vertices: Vec<LinePoint>, 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(),
|
||||
},
|
||||
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![],
|
||||
};
|
||||
pub struct VulkanRenderer {
|
||||
pub game_data: GameData,
|
||||
pub device: Arc<Device>,
|
||||
pub framebuffers: Vec<Arc<FramebufferAbstract + Send + Sync>>,
|
||||
pub dynamic_state: DynamicState,
|
||||
pub pipeline: Arc<GraphicsPipelineAbstract + Send + Sync>,
|
||||
pub line_pipeline: Arc<GraphicsPipelineAbstract + Send + Sync>,
|
||||
pub line_vertex_buffer: Arc<CpuAccessibleBuffer<[LinePoint], PotentialDedicatedAllocation<StdMemoryPoolAlloc>>>,
|
||||
pub surface: Arc<Surface<Window>>,
|
||||
pub swapchain: Arc<Swapchain<Window>>,
|
||||
pub render_pass: Arc<RenderPassAbstract + Send + Sync>,
|
||||
pub queue: Arc<Queue>,
|
||||
pub events_loop: EventsLoop,
|
||||
pub debug_callback: Option<DebugCallback>,
|
||||
}
|
||||
|
||||
if game.validation_layers_enabled() {
|
||||
println!("Enabling validation layers...");
|
||||
}
|
||||
|
||||
let instance = {
|
||||
let extensions = InstanceExtensions {
|
||||
ext_debug_report: true,
|
||||
..vulkano_win::required_extensions()
|
||||
impl VulkanRenderer {
|
||||
pub fn init(line_vertices: Vec<LinePoint>, enable_validation_layers: bool) -> VulkanRenderer {
|
||||
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(),
|
||||
},
|
||||
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![],
|
||||
};
|
||||
|
||||
let app_info = ApplicationInfo {
|
||||
application_name: Some("Asuro Editor".into()),
|
||||
application_version: Some(Version { major: 0, minor: 1, patch: 0 }),
|
||||
engine_name: Some("Asuro Rust Engine".into()),
|
||||
engine_version: Some(Version { major: 0, minor: 1, patch: 0 })
|
||||
};
|
||||
|
||||
if game.validation_layers_enabled() {
|
||||
let available_layers = vulkano::instance::layers_list().unwrap().map(|layer| String::from(layer.name())).collect::<Vec<String>>();
|
||||
|
||||
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: {:?}. Available layers: {:?}", wanted_layer_name, &available_layers.join(", "));
|
||||
}
|
||||
});
|
||||
|
||||
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")
|
||||
if enable_validation_layers {
|
||||
println!("Enabling validation layers...");
|
||||
}
|
||||
};
|
||||
|
||||
// lifetime of this is important, even tho it isn't used!
|
||||
let mut _debug_callback = None;
|
||||
if game.validation_layers_enabled() {
|
||||
let msg_types = MessageTypes {
|
||||
error: true,
|
||||
warning: true,
|
||||
performance_warning: true,
|
||||
information: false,
|
||||
debug: true,
|
||||
};
|
||||
|
||||
_debug_callback = DebugCallback::new(&instance, msg_types, |msg| {
|
||||
let type_str = match (msg.ty.error, msg.ty.warning, msg.ty.performance_warning, msg.ty.information, msg.ty.debug) {
|
||||
(true, _, _, _, _) => "!!",
|
||||
(_, true, _, _, _) => "!",
|
||||
(_, _, true, _, _) => "p",
|
||||
(_, _, _, true, _) => "i",
|
||||
_ => " "
|
||||
let instance = {
|
||||
let extensions = InstanceExtensions {
|
||||
ext_debug_report: true,
|
||||
..vulkano_win::required_extensions()
|
||||
};
|
||||
|
||||
let layer_str = msg.layer_prefix;
|
||||
let app_info = ApplicationInfo {
|
||||
application_name: Some("Asuro Editor".into()),
|
||||
application_version: Some(Version { major: 0, minor: 1, patch: 0 }),
|
||||
engine_name: Some("Asuro Rust Engine".into()),
|
||||
engine_version: Some(Version { major: 0, minor: 1, patch: 0 })
|
||||
};
|
||||
|
||||
println!("[{}][{}]: {}", type_str, layer_str, msg.description);
|
||||
}).ok();
|
||||
}
|
||||
if enable_validation_layers {
|
||||
let available_layers = vulkano::instance::layers_list().unwrap().map(|layer| String::from(layer.name())).collect::<Vec<String>>();
|
||||
|
||||
let physical = PhysicalDevice::enumerate(&instance).next().unwrap();
|
||||
println!("Using device: {} (type: {:?})", physical.name(), physical.ty());
|
||||
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: {:?}. Available layers: {:?}", wanted_layer_name, &available_layers.join(", "));
|
||||
}
|
||||
});
|
||||
|
||||
let mut events_loop = EventsLoop::new();
|
||||
let surface = WindowBuilder::new().build_vk_surface(&events_loop, instance.clone()).unwrap();
|
||||
let window = surface.window();
|
||||
|
||||
// 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();
|
||||
|
||||
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();
|
||||
|
||||
let (mut swapchain, images) = {
|
||||
let caps = surface.capabilities(physical).unwrap();
|
||||
|
||||
let usage = caps.supported_usage_flags;
|
||||
|
||||
// The alpha mode indicates how the alpha value of the final image will behave. For example
|
||||
// you can choose whether the window will be opaque or transparent.
|
||||
let alpha = caps.supported_composite_alpha.iter().next().unwrap();
|
||||
|
||||
// Choosing the internal format that the images will have.
|
||||
let format = caps.supported_formats[0].0;
|
||||
|
||||
// The dimensions of the window, only used to initially setup the swapchain.
|
||||
// NOTE:
|
||||
// On some drivers the swapchain dimensions are specified by `caps.current_extent` and the
|
||||
// swapchain size must use these dimensions.
|
||||
// These dimensions are always the same as the window dimensions
|
||||
//
|
||||
// However other drivers dont specify a value i.e. `caps.current_extent` is `None`
|
||||
// These drivers will allow anything but the only sensible value is the window dimensions.
|
||||
//
|
||||
// Because for both of these cases, the swapchain needs to be the window dimensions, we just use that.
|
||||
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 {
|
||||
panic!("Couldn't get window dimensions!");
|
||||
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")
|
||||
}
|
||||
};
|
||||
|
||||
Swapchain::new(device.clone(), surface.clone(), caps.min_image_count, format,
|
||||
data.dimensions, 1, usage, &queue, SurfaceTransform::Identity, alpha,
|
||||
PresentMode::Fifo, true, None).unwrap()
|
||||
};
|
||||
// lifetime of this is important, even tho it isn't used!
|
||||
let mut debug_callback = None;
|
||||
if enable_validation_layers {
|
||||
let msg_types = MessageTypes {
|
||||
error: true,
|
||||
warning: true,
|
||||
performance_warning: true,
|
||||
information: false,
|
||||
debug: true,
|
||||
};
|
||||
|
||||
mesh_paths.iter().flat_map(|path| load_mesh(device.clone(), path)).for_each(|mesh| data.meshes.push(mesh));
|
||||
debug_callback = DebugCallback::new(&instance, msg_types, |msg| {
|
||||
let type_str = match (msg.ty.error, msg.ty.warning, msg.ty.performance_warning, msg.ty.information, msg.ty.debug) {
|
||||
(true, _, _, _, _) => "!!",
|
||||
(_, true, _, _, _) => "!",
|
||||
(_, _, true, _, _) => "p",
|
||||
(_, _, _, true, _) => "i",
|
||||
_ => " "
|
||||
};
|
||||
|
||||
let line_vertex_buffer = CpuAccessibleBuffer::from_iter(device.clone(), BufferUsage::vertex_buffer(), data.line_vertices.iter().cloned()).unwrap();
|
||||
let layer_str = msg.layer_prefix;
|
||||
|
||||
let render_pass = Arc::new(vulkano::single_pass_renderpass!(
|
||||
device.clone(),
|
||||
attachments: {
|
||||
color: {
|
||||
load: Clear,
|
||||
store: Store,
|
||||
format: swapchain.format(),
|
||||
samples: 1,
|
||||
},
|
||||
depth: {
|
||||
load: Clear,
|
||||
store: DontCare,
|
||||
format: Format::D16Unorm,
|
||||
samples: 1,
|
||||
initial_layout: ImageLayout::Undefined,
|
||||
final_layout: ImageLayout::DepthStencilAttachmentOptimal,
|
||||
}
|
||||
},
|
||||
pass: {
|
||||
color: [color],
|
||||
depth_stencil: {depth}
|
||||
println!("[{}][{}]: {}", type_str, layer_str, msg.description);
|
||||
}).ok();
|
||||
}
|
||||
).unwrap());
|
||||
|
||||
let sub_pass = Subpass::from(render_pass.clone(), 0).unwrap();
|
||||
let physical = PhysicalDevice::enumerate(&instance).next().unwrap();
|
||||
println!("Using device: {} (type: {:?})", physical.name(), physical.ty());
|
||||
|
||||
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();
|
||||
let events_loop = EventsLoop::new();
|
||||
let surface = WindowBuilder::new().build_vk_surface(&events_loop, instance.clone()).unwrap();
|
||||
let window = surface.window();
|
||||
|
||||
// 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 };
|
||||
// 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();
|
||||
|
||||
// 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 mut framebuffers = window_size_dependent_setup(device.clone(), &images, render_pass.clone(), &mut dynamic_state);
|
||||
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();
|
||||
|
||||
let mut recreate_swapchain = false;
|
||||
let (swapchain, images) = {
|
||||
let caps = surface.capabilities(physical).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 mut previous_frame_end = Box::new(sync::now(device.clone())) as Box<dyn GpuFuture>;
|
||||
let usage = caps.supported_usage_flags;
|
||||
|
||||
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();
|
||||
// The alpha mode indicates how the alpha value of the final image will behave. For example
|
||||
// you can choose whether the window will be opaque or transparent.
|
||||
let alpha = caps.supported_composite_alpha.iter().next().unwrap();
|
||||
|
||||
if recreate_swapchain {
|
||||
// Choosing the internal format that the images will have.
|
||||
let format = caps.supported_formats[0].0;
|
||||
|
||||
// The dimensions of the window, only used to initially setup the swapchain.
|
||||
// NOTE:
|
||||
// On some drivers the swapchain dimensions are specified by `caps.current_extent` and the
|
||||
// swapchain size must use these dimensions.
|
||||
// These dimensions are always the same as the window dimensions
|
||||
//
|
||||
// However other drivers dont specify a value i.e. `caps.current_extent` is `None`
|
||||
// These drivers will allow anything but the only sensible value is the window dimensions.
|
||||
//
|
||||
// Because for both of these cases, the swapchain needs to be the window dimensions, we just use that.
|
||||
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;
|
||||
panic!("Couldn't get window dimensions!");
|
||||
};
|
||||
|
||||
let (new_swapchain, new_images) = match swapchain.recreate_with_dimension(data.dimensions) {
|
||||
Swapchain::new(device.clone(), surface.clone(), caps.min_image_count, format,
|
||||
data.dimensions, 1, usage, &queue, SurfaceTransform::Identity, alpha,
|
||||
PresentMode::Fifo, true, None).unwrap()
|
||||
};
|
||||
|
||||
let line_vertex_buffer = CpuAccessibleBuffer::from_iter(device.clone(), BufferUsage::vertex_buffer(), data.line_vertices.iter().cloned()).unwrap();
|
||||
|
||||
let render_pass = Arc::new(vulkano::single_pass_renderpass!(
|
||||
device.clone(),
|
||||
attachments: {
|
||||
color: {
|
||||
load: Clear,
|
||||
store: Store,
|
||||
format: swapchain.format(),
|
||||
samples: 1,
|
||||
},
|
||||
depth: {
|
||||
load: Clear,
|
||||
store: DontCare,
|
||||
format: Format::D16Unorm,
|
||||
samples: 1,
|
||||
initial_layout: ImageLayout::Undefined,
|
||||
final_layout: ImageLayout::DepthStencilAttachmentOptimal,
|
||||
}
|
||||
},
|
||||
pass: {
|
||||
color: [color],
|
||||
depth_stencil: {depth}
|
||||
}
|
||||
).unwrap());
|
||||
|
||||
let pipeline: Arc<GraphicsPipelineAbstract + Send + Sync> =
|
||||
create_pipeline::<Vertex>(device.clone(), render_pass.clone(), "shaders/triangle.vert", "shaders/triangle.frag", false).unwrap();
|
||||
let line_pipeline: Arc<GraphicsPipelineAbstract + Send + Sync> =
|
||||
create_pipeline::<LinePoint>(device.clone(), render_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.
|
||||
let mut dynamic_state = DynamicState { line_width: None, viewports: None, scissors: None };
|
||||
|
||||
// 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, 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<dyn GpuFuture>;
|
||||
|
||||
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();
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
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)
|
||||
};
|
||||
|
||||
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::<Vertex>(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,
|
||||
// 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(AcquireError::OutOfDate) => {
|
||||
recreate_swapchain = true;
|
||||
continue;
|
||||
},
|
||||
Err(err) => panic!("{:?}", err)
|
||||
};
|
||||
|
||||
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);
|
||||
game.update(&mut self.game_data);
|
||||
|
||||
recreate_swapchain = false;
|
||||
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; }
|
||||
}
|
||||
}
|
||||
|
||||
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.");
|
||||
}
|
||||
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(swapchain.clone(), None) {
|
||||
Ok(r) => r,
|
||||
Err(AcquireError::OutOfDate) => {
|
||||
recreate_swapchain = true;
|
||||
continue;
|
||||
},
|
||||
Err(err) => panic!("{:?}", err)
|
||||
};
|
||||
|
||||
game.update(&mut data);
|
||||
|
||||
let mut cbb = 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
|
||||
// not covered here.
|
||||
.begin_render_pass(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..data.game_objects.len() {
|
||||
cbb = cbb.draw_indexed(pipeline.clone(), &dynamic_state,
|
||||
data.meshes[data.game_objects[i].mesh_index].vertex_buffer.clone(),
|
||||
data.meshes[data.game_objects[i].mesh_index].index_buffer.clone(),
|
||||
(), data.push_constants.clone()).unwrap();
|
||||
}
|
||||
|
||||
cbb = cbb.draw(line_pipeline.clone(), &dynamic_state, line_vertex_buffer.clone(), (), 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(queue.clone(), command_buffer).unwrap()
|
||||
.then_swapchain_present(queue.clone(), 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(device.clone())) as Box<_>;
|
||||
}
|
||||
Err(e) => {
|
||||
println!("{:?}", e);
|
||||
previous_frame_end = Box::new(sync::now(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.
|
||||
events_loop.poll_events(|ev| {
|
||||
game.on_window_event(&ev);
|
||||
match ev {
|
||||
Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => data.shutdown = true,
|
||||
Event::WindowEvent { event: WindowEvent::Resized(_), .. } => recreate_swapchain = true,
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
if data.shutdown { return; }
|
||||
pub fn upload_mesh(self: &mut Self, mesh: CPUMesh) -> usize {
|
||||
let vertex_buffer = CpuAccessibleBuffer::from_iter(self.device.clone(), BufferUsage::vertex_buffer(), mesh.vertices.into_iter()).unwrap();
|
||||
let index_buffer = CpuAccessibleBuffer::from_iter(self.device.clone(), BufferUsage::index_buffer(), mesh.indices.into_iter()).unwrap();
|
||||
self.game_data.meshes.push(Mesh { vertex_buffer, index_buffer });
|
||||
self.game_data.meshes.len() - 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,8 +472,10 @@ pub mod line_fs {
|
||||
}
|
||||
}
|
||||
|
||||
fn create_pipeline<T: RenderPassAbstract, V: vulkano::pipeline::vertex::Vertex>(device: Arc<Device>, sub_pass: Subpass<Arc<T>>, vertex_shader_path: &str, fragment_shader_path: &str, is_line: bool) -> Option<Arc<GraphicsPipeline<SingleBufferDefinition<V>, Box<dyn PipelineLayoutAbstract + Send + Sync>, Arc<T>>>> {
|
||||
fn create_pipeline<V: vulkano::pipeline::vertex::Vertex>(device: Arc<Device>, render_pass: Arc<RenderPassAbstract + Send + Sync>, vertex_shader_path: &str, fragment_shader_path: &str, is_line: bool) -> Option<Arc<GraphicsPipelineAbstract + Send + Sync>> {
|
||||
if let Some((shader, shader_data)) = read_shader(vertex_shader_path, fragment_shader_path) {
|
||||
let sub_pass = Subpass::from(render_pass.clone(), 0).unwrap();
|
||||
|
||||
let vertex_shader_entry;
|
||||
let fragment_shader_entry;
|
||||
let vertex_shader_module;
|
||||
@@ -520,55 +554,4 @@ fn read_shader(vert_path_relative: &str, frag_path_relative: &str) -> Option<(Co
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn load_mesh(device: Arc<Device>, mesh_path: &str) -> Vec<Mesh> {
|
||||
struct TempVertex {
|
||||
pos: Option<[f32; 3]>,
|
||||
uv: Option<[f32; 2]>,
|
||||
normal: Option<[f32; 3]>,
|
||||
}
|
||||
|
||||
let file = collada::document::ColladaDocument::from_path(Path::new(mesh_path)).unwrap();
|
||||
file.get_obj_set().unwrap().objects.iter().map(|model| {
|
||||
let mut vertices: Vec<TempVertex> = model.vertices.iter().map(|v| {
|
||||
TempVertex {
|
||||
pos: Some([v.x as f32, v.y as f32, v.z as f32]),
|
||||
uv: None,
|
||||
normal: None
|
||||
}
|
||||
}).collect();
|
||||
|
||||
let mut indices = Vec::new();
|
||||
|
||||
model.geometry.iter().for_each(|geometry| geometry.mesh.iter().for_each(|primitive| {
|
||||
if let collada::PrimitiveElement::Triangles(tris) = primitive {
|
||||
tris.vertices.iter().for_each(|tri| {
|
||||
[tri.0, tri.1, tri.2].iter().for_each(|(vertex_index, texture_position_index, normal_index)| {
|
||||
indices.push(*vertex_index as u32);
|
||||
vertices[*vertex_index].uv = texture_position_index.or(None).map(|tpi| {
|
||||
let tex_vertex = model.tex_vertices[tpi];
|
||||
[tex_vertex.x as f32, 1.0 - tex_vertex.y as f32]
|
||||
});
|
||||
vertices[*vertex_index].normal = normal_index.or(None).map(|ni| {
|
||||
let normal = model.normals[ni];
|
||||
[normal.x as f32, normal.y as f32, normal.z as f32]
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
panic!("Mesh format Polylist not supported!");
|
||||
}
|
||||
}));
|
||||
|
||||
let finished_vertices = vertices.iter().map(|v| Vertex {
|
||||
position: v.pos.unwrap(),
|
||||
uv: v.uv.unwrap(),
|
||||
normal: v.normal.unwrap(),
|
||||
});
|
||||
|
||||
let vertex_buffer = CpuAccessibleBuffer::from_iter(device.clone(), BufferUsage::vertex_buffer(), finished_vertices.into_iter()).unwrap();
|
||||
let index_buffer = CpuAccessibleBuffer::from_iter(device.clone(), BufferUsage::index_buffer(), indices.into_iter()).unwrap();
|
||||
Mesh { vertex_buffer, index_buffer }
|
||||
}).collect()
|
||||
}
|
||||
Reference in New Issue
Block a user