// Copyright (C) 2019 Oscar Shrimpton // This program is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the Free // Software Foundation, either version 3 of the License, or (at your option) // any later version. // This program is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for // more details. // You should have received a copy of the GNU General Public License along // with this program. If not, see . //! Deals with all the Vulkan/HAL details. //! In the end, this takes in a depth-sorted list of faces and a map file and renders them. //! You'll need something else to actually find/sort the faces though. use std::{ mem::{ManuallyDrop, size_of}, ops::Deref, borrow::Borrow, convert::TryInto }; use arrayvec::ArrayVec; use hal::{ prelude::*, pool::CommandPoolCreateFlags }; use log::debug; use winit::window::Window; use stockton_types::{Vector2, Vector3}; use stockton_levels::prelude::*; use stockton_levels::traits::faces::FaceType; use crate::{ types::*, error }; use super::{ target::{TargetChain, SwapchainProperties}, camera::WorkingCamera, texture::TextureStore, buffer::{StagedBuffer, ModifiableBuffer} }; /// Entry point name for shaders const ENTRY_NAME: &str = "main"; /// Initial size of vertex buffer. TODO: Way of overriding this const INITIAL_VERT_SIZE: u64 = 3 * 3000; /// Initial size of index buffer. TODO: Way of overriding this const INITIAL_INDEX_SIZE: u64 = 3000; /// Source for vertex shader. TODO const VERTEX_SOURCE: &str = include_str!("./data/stockton.vert"); /// Source for fragment shader. TODO const FRAGMENT_SOURCE: &str = include_str!("./data/stockton.frag"); /// Represents a point of a triangle, including UV and texture information. #[derive(Debug, Clone, Copy)] pub struct UVPoint (pub Vector3, pub i32, pub Vector2); /// Contains all the hal related stuff. /// In the end, this takes in a depth-sorted list of faces and a map file and renders them. // TODO: Settings for clear colour, buffer sizes, etc pub struct RenderingContext<'a> { // Parents for most of these things /// Vulkan Instance instance: ManuallyDrop, /// Device we're using device: ManuallyDrop, /// Adapter we're using adapter: Adapter, // Render destination /// Surface to draw to surface: ManuallyDrop, /// Swapchain and stuff target_chain: ManuallyDrop, // Pipeline /// Our main render pass renderpass: ManuallyDrop, /// The layout of our main graphics pipeline pipeline_layout: ManuallyDrop, /// Our main graphics pipeline pipeline: ManuallyDrop, // Command pool and buffers /// The command pool used for our buffers cmd_pool: ManuallyDrop, /// The queue group our buffers belong to queue_group: QueueGroup, /// Texture store texture_store: ManuallyDrop, /// (Staged) Vertex Buffer pub vert_buffer: ManuallyDrop>, /// (Staged) Index Buffer pub index_buffer: ManuallyDrop>, /// Our camera settings camera: WorkingCamera, /// The vertex shader module vs_module: ManuallyDrop, /// The fragment shader module fs_module: ManuallyDrop } impl<'a> RenderingContext<'a> { /// Create a new RenderingContext for the given window. pub fn new(window: &Window, file: &T) -> Result { // Create surface let (instance, mut surface, mut adapters) = unsafe { use hal::Instance; let instance = back::Instance::create("stockton", 1).map_err(|_| error::CreationError::WindowError)?; let surface = instance.create_surface(window).map_err(|_| error::CreationError::WindowError)?; let adapters = instance.enumerate_adapters(); (instance, surface, adapters) }; // TODO: Properly figure out which adapter to use let mut adapter = adapters.remove(0); // Device & Queue group let (mut device, mut queue_group) = { let family = adapter .queue_families .iter() .find(|family| { surface.supports_queue_family(family) && family.queue_type().supports_graphics() }) .unwrap(); let mut gpu = unsafe { adapter .physical_device .open(&[(family, &[1.0])], hal::Features::empty()) .unwrap() }; (gpu.device, gpu.queue_groups.pop().unwrap()) }; // Figure out what our swapchain will look like let swapchain_properties = SwapchainProperties::find_best(&adapter, &surface).map_err(|_| error::CreationError::BadSurface)?; debug!("Detected following swapchain properties: {:?}", swapchain_properties); // Command pool let mut cmd_pool = unsafe { device.create_command_pool(queue_group.family, CommandPoolCreateFlags::RESET_INDIVIDUAL) }.map_err(|_| error::CreationError::OutOfMemoryError)?; // Renderpass let renderpass = { use hal::{ pass::*, pso::PipelineStage, image::{Access, Layout}, memory::Dependencies }; let img_attachment = Attachment { format: Some(swapchain_properties.format), samples: 1, ops: AttachmentOps::new(AttachmentLoadOp::Clear, AttachmentStoreOp::Store), stencil_ops: AttachmentOps::new(AttachmentLoadOp::Clear, AttachmentStoreOp::DontCare), layouts: Layout::Undefined..Layout::Present }; let depth_attachment = Attachment { format: Some(swapchain_properties.depth_format), samples: 1, ops: AttachmentOps::new(AttachmentLoadOp::Clear, AttachmentStoreOp::DontCare), stencil_ops: AttachmentOps::new(AttachmentLoadOp::DontCare, AttachmentStoreOp::DontCare), layouts: Layout::Undefined..Layout::DepthStencilAttachmentOptimal }; let subpass = SubpassDesc { colors: &[(0, Layout::ColorAttachmentOptimal)], depth_stencil: Some(&(1, Layout::DepthStencilAttachmentOptimal)), inputs: &[], resolves: &[], preserves: &[] }; let in_dependency = SubpassDependency { flags: Dependencies::empty(), passes: None..Some(0), stages: PipelineStage::COLOR_ATTACHMENT_OUTPUT.. (PipelineStage::COLOR_ATTACHMENT_OUTPUT | PipelineStage::EARLY_FRAGMENT_TESTS), accesses: Access::empty() ..(Access::COLOR_ATTACHMENT_READ | Access::COLOR_ATTACHMENT_WRITE | Access::DEPTH_STENCIL_ATTACHMENT_READ | Access::DEPTH_STENCIL_ATTACHMENT_WRITE) }; let out_dependency = SubpassDependency { flags: Dependencies::empty(), passes: Some(0)..None, stages: PipelineStage::COLOR_ATTACHMENT_OUTPUT | PipelineStage::EARLY_FRAGMENT_TESTS.. PipelineStage::COLOR_ATTACHMENT_OUTPUT, accesses: (Access::COLOR_ATTACHMENT_READ | Access::COLOR_ATTACHMENT_WRITE | Access::DEPTH_STENCIL_ATTACHMENT_READ | Access::DEPTH_STENCIL_ATTACHMENT_WRITE).. Access::empty() }; unsafe { device.create_render_pass(&[img_attachment, depth_attachment], &[subpass], &[in_dependency, out_dependency]) } .map_err(|_| error::CreationError::OutOfMemoryError)? }; // Subpass let subpass = hal::pass::Subpass { index: 0, main_pass: &renderpass }; // Camera // TODO: Settings let ratio = swapchain_properties.extent.width as f32 / swapchain_properties.extent.height as f32; let camera = WorkingCamera::defaults(ratio); // Vertex and index buffers let (vert_buffer, index_buffer) = { use hal::buffer::Usage; let vert = StagedBuffer::new(&mut device, &adapter, Usage::VERTEX, INITIAL_VERT_SIZE)?; let index = StagedBuffer::new(&mut device, &adapter, Usage::INDEX, INITIAL_INDEX_SIZE)?; (vert, index) }; // Texture store let texture_store = TextureStore::new(&mut device, &mut adapter, &mut queue_group.queues[0], &mut cmd_pool, file)?; let mut descriptor_set_layouts: ArrayVec<[_; 2]> = ArrayVec::new(); descriptor_set_layouts.push(texture_store.descriptor_set_layout.deref()); // Graphics pipeline let (pipeline_layout, pipeline, vs_module, fs_module) = Self::create_pipeline(&mut device, swapchain_properties.extent, &subpass, descriptor_set_layouts)?; // Swapchain and associated resources let target_chain = TargetChain::new(&mut device, &adapter, &mut surface, &renderpass, &mut cmd_pool, swapchain_properties, None).map_err(|e| error::CreationError::TargetChainCreationError (e) )?; Ok(RenderingContext { instance: ManuallyDrop::new(instance), surface: ManuallyDrop::new(surface), device: ManuallyDrop::new(device), adapter, queue_group, renderpass: ManuallyDrop::new(renderpass), target_chain: ManuallyDrop::new(target_chain), cmd_pool: ManuallyDrop::new(cmd_pool), pipeline_layout: ManuallyDrop::new(pipeline_layout), pipeline: ManuallyDrop::new(pipeline), texture_store: ManuallyDrop::new(texture_store), vert_buffer: ManuallyDrop::new(vert_buffer), index_buffer: ManuallyDrop::new(index_buffer), vs_module: ManuallyDrop::new(vs_module), fs_module: ManuallyDrop::new(fs_module), camera }) } /// If this function fails the whole context is probably dead /// The context must not be used while this is being called pub unsafe fn handle_surface_change(&mut self) -> Result<(), error::CreationError> { self.device.wait_idle().unwrap(); let properties = SwapchainProperties::find_best(&self.adapter, &self.surface).map_err(|_| error::CreationError::BadSurface)?; // Camera settings (aspect ratio) self.camera.update_aspect_ratio( properties.extent.width as f32 / properties.extent.height as f32); use core::ptr::read; // Graphics pipeline self.device.destroy_graphics_pipeline(ManuallyDrop::into_inner(read(&self.pipeline))); self.device .destroy_pipeline_layout(ManuallyDrop::into_inner(read(&self.pipeline_layout))); self.device.destroy_shader_module(ManuallyDrop::into_inner(read(&self.vs_module))); self.device.destroy_shader_module(ManuallyDrop::into_inner(read(&self.fs_module))); let (pipeline_layout, pipeline, vs_module, fs_module) = { let mut descriptor_set_layouts: ArrayVec<[_; 2]> = ArrayVec::new(); descriptor_set_layouts.push(self.texture_store.descriptor_set_layout.deref()); let subpass = hal::pass::Subpass { index: 0, main_pass: &(*self.renderpass) }; Self::create_pipeline(&mut self.device, properties.extent, &subpass, descriptor_set_layouts)? }; self.pipeline_layout = ManuallyDrop::new(pipeline_layout); self.pipeline = ManuallyDrop::new(pipeline); self.vs_module = ManuallyDrop::new(vs_module); self.fs_module = ManuallyDrop::new(fs_module); let old_swapchain = ManuallyDrop::into_inner(read(&self.target_chain)).deactivate_with_recyling(&mut self.device, &mut self.cmd_pool); self.target_chain = ManuallyDrop::new(TargetChain::new(&mut self.device, &self.adapter, &mut self.surface, &self.renderpass, &mut self.cmd_pool, properties, Some(old_swapchain)) .map_err(|e| error::CreationError::TargetChainCreationError (e) )?); Ok(()) } #[allow(clippy::type_complexity)] fn create_pipeline(device: &mut Device, extent: hal::image::Extent, subpass: &hal::pass::Subpass, set_layouts: T) -> Result< ( PipelineLayout, GraphicsPipeline, ShaderModule, ShaderModule ), error::CreationError> where T: IntoIterator, T::Item: Borrow { use hal::pso::*; use hal::format::Format; // Shader modules let (vs_module, fs_module) = { let mut compiler = shaderc::Compiler::new().ok_or(error::CreationError::NoShaderC)?; let vertex_compile_artifact = compiler .compile_into_spirv(VERTEX_SOURCE, shaderc::ShaderKind::Vertex, "vertex.vert", ENTRY_NAME, None) .map_err(|e| error::CreationError::ShaderCError (e))?; let fragment_compile_artifact = compiler .compile_into_spirv(FRAGMENT_SOURCE, shaderc::ShaderKind::Fragment, "fragment.frag", ENTRY_NAME, None) .map_err(|e| error::CreationError::ShaderCError (e))?; // Make into shader module unsafe { (device .create_shader_module(vertex_compile_artifact.as_binary()) .map_err(|e| error::CreationError::ShaderModuleFailed (e))?, device .create_shader_module(fragment_compile_artifact.as_binary()) .map_err(|e| error::CreationError::ShaderModuleFailed (e))?) } }; // Shader entry points (ShaderStage) let (vs_entry, fs_entry) = ( EntryPoint:: { entry: ENTRY_NAME, module: &vs_module, specialization: Specialization::default() }, EntryPoint:: { entry: ENTRY_NAME, module: &fs_module, specialization: Specialization::default() } ); // Shader set let shaders = GraphicsShaderSet { vertex: vs_entry, fragment: Some(fs_entry), hull: None, domain: None, geometry: None }; // Vertex buffers let vertex_buffers: Vec = vec![VertexBufferDesc { binding: 0, stride: (size_of::() * 6) as u32, rate: VertexInputRate::Vertex, }]; let attributes: Vec = pipeline_vb_attributes!(0, size_of::() * 3; Rgb32Sfloat, size_of::(); R32Sint, size_of::() * 2; Rg32Sfloat ); // Rasterizer let rasterizer = Rasterizer { polygon_mode: PolygonMode::Fill, cull_face: Face::BACK, front_face: FrontFace::CounterClockwise, depth_clamping: false, depth_bias: None, conservative: true, line_width: hal::pso::State::Static(1.0) }; // Depth stencil let depth_stencil = DepthStencilDesc { depth: Some(DepthTest { fun: Comparison::Less, write: true }), depth_bounds: false, stencil: None, }; // Pipeline layout let layout = unsafe { device.create_pipeline_layout( set_layouts, // vp matrix, 4x4 f32 &[(ShaderStageFlags::VERTEX, 0..64)] ) }.map_err(|_| error::CreationError::OutOfMemoryError)?; // Colour blending let blender = { let blend_state = BlendState { color: BlendOp::Add { src: Factor::One, dst: Factor::Zero, }, alpha: BlendOp::Add { src: Factor::One, dst: Factor::Zero, }, }; BlendDesc { logic_op: Some(LogicOp::Copy), targets: vec![ColorBlendDesc { mask: ColorMask::ALL, blend: Some(blend_state) }], } }; // Baked states let baked_states = BakedStates { viewport: Some(Viewport { rect: extent.rect(), depth: (0.0..1.0) }), scissor: Some(extent.rect()), blend_color: None, depth_bounds: None, }; // Input assembler let input_assembler = InputAssemblerDesc::new(Primitive::TriangleList); // Pipeline description let pipeline_desc = GraphicsPipelineDesc { shaders, rasterizer, vertex_buffers, blender, depth_stencil, multisampling: None, baked_states, layout: &layout, subpass: *subpass, flags: PipelineCreationFlags::empty(), parent: BasePipeline::None, input_assembler, attributes }; // Pipeline let pipeline = unsafe { device.create_graphics_pipeline(&pipeline_desc, None) }.map_err(|e| error::CreationError::PipelineError (e))?; Ok((layout, pipeline, vs_module, fs_module)) } /// Draw all vertices in the buffer pub fn draw_vertices>(&mut self, file: &M,faces: &Vec) -> Result<(), &'static str> { // Prepare command buffer let cmd_buffer = self.target_chain.prep_next_target( &mut self.device, self.vert_buffer.get_buffer(), self.index_buffer.get_buffer(), &self.renderpass, &self.pipeline, &self.pipeline_layout, &mut self.camera )?; // Iterate over faces, copying them in and drawing groups that use the same texture chunk all at once. let mut current_chunk = file.get_face(0).texture_idx as usize / 8; let mut chunk_start = 0; let mut curr_vert_idx: usize = 0; let mut curr_idx_idx: usize = 0; for face in faces.into_iter().map(|idx| file.get_face(*idx)) { if current_chunk != face.texture_idx as usize / 8 { // Last index was last of group, so draw it all. let mut descriptor_sets: ArrayVec<[_; 1]> = ArrayVec::new(); descriptor_sets.push(self.texture_store.get_chunk_descriptor_set(current_chunk)); unsafe { cmd_buffer.bind_graphics_descriptor_sets( &self.pipeline_layout, 0, descriptor_sets, &[] ); cmd_buffer.draw_indexed(chunk_start as u32 * 3..(curr_idx_idx as u32 * 3) + 1, 0, 0..1); } // Next group of same-chunked faces starts here. chunk_start = curr_idx_idx; current_chunk = face.texture_idx as usize / 8; } if face.face_type == FaceType::Polygon || face.face_type == FaceType::Mesh { // 2 layers of indirection let base = face.vertices_idx.start; for idx in face.meshverts_idx.clone().step_by(3) { let start_idx: u16 = curr_vert_idx.try_into().unwrap(); for idx2 in idx..idx+3 { let vert = &file.resolve_meshvert(idx2 as u32, base); let uv = Vector2::new(vert.tex.u[0], vert.tex.v[0]); let uvp = UVPoint (vert.position, face.texture_idx.try_into().unwrap(), uv); self.vert_buffer[curr_vert_idx] = uvp; curr_vert_idx += 1; } self.index_buffer[curr_idx_idx] = (start_idx, start_idx + 1, start_idx + 2); curr_idx_idx += 1; if curr_vert_idx >= INITIAL_VERT_SIZE.try_into().unwrap() || curr_idx_idx >= INITIAL_INDEX_SIZE.try_into().unwrap() { println!("out of vertex buffer space!"); break; } } } else { // TODO: Other types of faces } if curr_vert_idx >= INITIAL_VERT_SIZE.try_into().unwrap() || curr_idx_idx >= INITIAL_INDEX_SIZE.try_into().unwrap() { println!("out of vertex buffer space!"); break; } } // Draw the final group of chunks let mut descriptor_sets: ArrayVec<[_; 1]> = ArrayVec::new(); descriptor_sets.push(self.texture_store.get_chunk_descriptor_set(current_chunk)); unsafe { cmd_buffer.bind_graphics_descriptor_sets( &self.pipeline_layout, 0, descriptor_sets, &[] ); cmd_buffer.draw_indexed(chunk_start as u32 * 3..(curr_idx_idx as u32 * 3) + 1, 0, 0..1); } // Update our buffers before we actually start drawing self.vert_buffer.commit( &self.device, &mut self.queue_group.queues[0], &mut self.cmd_pool ); self.index_buffer.commit( &self.device, &mut self.queue_group.queues[0], &mut self.cmd_pool ); // Send commands off to GPU self.target_chain.finish_and_submit_target(&mut self.queue_group.queues[0])?; Ok(()) } /// Get current position of camera pub fn camera_pos(&self) -> Vector3 { self.camera.camera_pos() } /// Move the camera by `delta` relative to its rotation pub fn move_camera_relative(&mut self, delta: Vector3) { self.camera.move_camera_relative(delta) } /// Rotate the camera /// `euler` should be euler angles in radians pub fn rotate(&mut self, euler: Vector3) { self.camera.rotate(euler) } } impl<'a> core::ops::Drop for RenderingContext<'a> { fn drop(&mut self) { self.device.wait_idle().unwrap(); unsafe { use core::ptr::read; ManuallyDrop::into_inner(read(&self.vert_buffer)).deactivate(&mut self.device); ManuallyDrop::into_inner(read(&self.index_buffer)).deactivate(&mut self.device); ManuallyDrop::into_inner(read(&self.texture_store)).deactivate(&mut self.device); ManuallyDrop::into_inner(read(&self.target_chain)).deactivate(&mut self.device, &mut self.cmd_pool); self.device.destroy_command_pool( ManuallyDrop::into_inner(read(&self.cmd_pool)), ); self.device .destroy_render_pass(ManuallyDrop::into_inner(read(&self.renderpass))); self.device.destroy_shader_module(ManuallyDrop::into_inner(read(&self.vs_module))); self.device.destroy_shader_module(ManuallyDrop::into_inner(read(&self.fs_module))); self.device.destroy_graphics_pipeline(ManuallyDrop::into_inner(read(&self.pipeline))); self.device .destroy_pipeline_layout(ManuallyDrop::into_inner(read(&self.pipeline_layout))); self.instance .destroy_surface(ManuallyDrop::into_inner(read(&self.surface))); ManuallyDrop::drop(&mut self.device); } } }