// 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 vertices and renders them to a window. //! You'll need something else to actually generate the vertices though. use std::{ mem::{ManuallyDrop, size_of}, iter::once, ops::Deref }; use winit::window::Window; use arrayvec::ArrayVec; use image::RgbaImage; use hal::{ prelude::*, queue::{Submission}, window::SwapchainConfig }; use stockton_types::{Vector2, Vector3}; use crate::types::*; use crate::error; use super::texture::TextureStore; use super::buffer::{StagedBuffer, ModifiableBuffer}; /// Entry point name for shaders const ENTRY_NAME: &str = "main"; /// Defines the colour range we use. const COLOR_RANGE: hal::image::SubresourceRange = hal::image::SubresourceRange { aspects: hal::format::Aspects::COLOR, levels: 0..1, layers: 0..1, }; /// Size of texturestore. This needs to sync up with the array size in the fragment shader const INITIAL_TEX_SIZE: usize = 2; /// Initial size of vertex buffer. TODO: Way of overriding this const INITIAL_VERT_SIZE: u64 = 32; /// Initial size of index buffer. TODO: Way of overriding this const INITIAL_INDEX_SIZE: u64 = 16; /// 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 vertices, including RGB, UV and texture information. #[derive(Debug, Clone, Copy)] pub struct UVPoint (pub Vector2, pub Vector3, pub Vector2, pub i32); /// Contains all the hal related stuff. /// In the end, this takes some 3D points and puts it on the screen. // TODO: Settings for clear colour, buffer sizes, etc pub struct RenderingContext<'a> { // Parents for most of these things instance: ManuallyDrop, device: ManuallyDrop, adapter: Adapter, // Render destination surface: ManuallyDrop, swapchain: ManuallyDrop, viewport: hal::pso::Viewport, imageviews: Vec, framebuffers: Vec, current_frame: usize, frames_in_flight: usize, // Sync objects // TODO: Collect these together? get_image: Vec, render_complete: Vec, present_complete: Vec, // Pipeline renderpass: ManuallyDrop, pipeline_layout: ManuallyDrop, pipeline: ManuallyDrop, // Command pool and buffers cmd_pool: ManuallyDrop, cmd_buffers: Vec, queue_group: QueueGroup, // Texture store texture_store: ManuallyDrop, // Vertex and index buffers // These are both staged pub vert_buffer: ManuallyDrop>, pub index_buffer: ManuallyDrop>, } impl<'a> RenderingContext<'a> { /// Create a new RenderingContext for the given window. pub fn new(window: &Window) -> 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 adapter = adapters.remove(0); // Device & Queue group let (mut device, 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()) }; // Swapchain let (format, viewport, extent, swapchain, backbuffer) = { use hal::{ window::{PresentMode, CompositeAlphaMode}, format::{Format, ChannelType}, image::Usage, pso::Viewport }; // Figure out what the surface supports let caps = surface.capabilities(&adapter.physical_device); let formats = surface.supported_formats(&adapter.physical_device); // Find which settings we'll actually use based on preset preferences let format = formats.map_or(Format::Rgba8Srgb, |formats| { formats.iter() .find(|format| format.base_format().1 == ChannelType::Srgb) .map(|format| *format) .unwrap_or(formats[0]) }); let present_mode = { [PresentMode::MAILBOX, PresentMode::FIFO, PresentMode::RELAXED, PresentMode::IMMEDIATE] .iter() .cloned() .find(|pm| caps.present_modes.contains(*pm)) .ok_or(error::CreationError::BadSurface)? }; let composite_alpha = { [CompositeAlphaMode::OPAQUE, CompositeAlphaMode::INHERIT, CompositeAlphaMode::PREMULTIPLIED, CompositeAlphaMode::POSTMULTIPLIED] .iter() .cloned() .find(|ca| caps.composite_alpha_modes.contains(*ca)) .ok_or(error::CreationError::BadSurface)? }; // Figure out properties for our swapchain let extent = caps.extents.end(); // Size // Number of frames to pre-render let image_count = if present_mode == PresentMode::MAILBOX { ((*caps.image_count.end()) - 1).min((*caps.image_count.start()).max(3)) } else { ((*caps.image_count.end()) - 1).min((*caps.image_count.start()).max(2)) }; let image_layers = 1; // Don't support 3D let image_usage = if caps.usage.contains(Usage::COLOR_ATTACHMENT) { Usage::COLOR_ATTACHMENT } else { Err(error::CreationError::BadSurface)? }; // Swap config let swap_config = SwapchainConfig { present_mode, composite_alpha_mode: composite_alpha, format, extent: *extent, image_count, image_layers, image_usage, }; // Viewport let extent = extent.to_extent(); let viewport = Viewport { rect: extent.rect(), depth: 0.0..1.0 }; // Swapchain let (swapchain, backbuffer) = unsafe { device.create_swapchain(&mut surface, swap_config, None) .map_err(|e| error::CreationError::SwapchainError (e))? }; (format, viewport, extent, swapchain, backbuffer) }; // Renderpass let renderpass = { use hal::{ pass::*, pso::PipelineStage, image::{Access, Layout}, memory::Dependencies }; let attachment = Attachment { format: Some(format), samples: 1, ops: AttachmentOps::new(AttachmentLoadOp::Clear, AttachmentStoreOp::Store), stencil_ops: AttachmentOps::new(AttachmentLoadOp::DontCare, AttachmentStoreOp::DontCare), layouts: Layout::Undefined..Layout::Present }; let subpass = SubpassDesc { colors: &[(0, Layout::ColorAttachmentOptimal)], depth_stencil: None, inputs: &[], resolves: &[], preserves: &[] }; let dependency = SubpassDependency { flags: Dependencies::empty(), passes: None..Some(0), stages: PipelineStage::COLOR_ATTACHMENT_OUTPUT..PipelineStage::COLOR_ATTACHMENT_OUTPUT, accesses: Access::empty() ..(Access::COLOR_ATTACHMENT_READ | Access::COLOR_ATTACHMENT_WRITE) }; unsafe { device.create_render_pass(&[attachment], &[subpass], &[dependency]) } .map_err(|_| error::CreationError::OutOfMemoryError)? }; // Subpass let subpass = hal::pass::Subpass { index: 0, main_pass: &renderpass }; // 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) }; // Command Pool, Buffers, imageviews, framebuffers & Sync objects let frames_in_flight = backbuffer.len(); let (cmd_pool, cmd_buffers, get_image, render_complete, present_complete, imageviews, framebuffers) = { use hal::pool::CommandPoolCreateFlags; use hal::command::Level; let mut cmd_pool = ManuallyDrop::new(unsafe { device.create_command_pool(queue_group.family, CommandPoolCreateFlags::empty()) }.map_err(|_| error::CreationError::OutOfMemoryError)?); let mut cmd_buffers = Vec::with_capacity(frames_in_flight); let mut get_image = Vec::with_capacity(frames_in_flight); let mut render_complete = Vec::with_capacity(frames_in_flight); let mut present_complete = Vec::with_capacity(frames_in_flight); let mut imageviews = Vec::with_capacity(frames_in_flight); let mut framebuffers = Vec::with_capacity(frames_in_flight); for i in 0..frames_in_flight { unsafe { cmd_buffers.push(cmd_pool.allocate_one(Level::Primary)); // TODO: We can do this all at once outside the loop } get_image.push(device.create_semaphore().map_err(|_| error::CreationError::SyncObjectError)?); render_complete.push(device.create_semaphore().map_err(|_| error::CreationError::SyncObjectError)?); present_complete.push(device.create_fence(true).map_err(|_| error::CreationError::SyncObjectError)?); unsafe { use hal::image::ViewKind; use hal::format::Swizzle; imageviews.push(device.create_image_view( &backbuffer[i], ViewKind::D2, format, Swizzle::NO, COLOR_RANGE.clone(), ).map_err(|e| error::CreationError::ImageViewError (e))?); framebuffers.push(device.create_framebuffer( &renderpass, Some(&imageviews[i]), extent ).map_err(|_| error::CreationError::OutOfMemoryError)?); } } (cmd_pool, cmd_buffers, get_image, render_complete, present_complete, imageviews, framebuffers) }; // Texture store let texture_store = TextureStore::new(&mut device, INITIAL_TEX_SIZE)?; // Graphics pipeline let (pipeline_layout, pipeline) = Self::create_pipeline(&mut device, extent, &subpass, &texture_store.descriptor_set_layout)?; Ok(RenderingContext { instance: ManuallyDrop::new(instance), surface: ManuallyDrop::new(surface), device: ManuallyDrop::new(device), adapter, queue_group, swapchain: ManuallyDrop::new(swapchain), viewport, imageviews, framebuffers, renderpass: ManuallyDrop::new(renderpass), current_frame: 0, get_image, render_complete, present_complete, frames_in_flight, cmd_pool, cmd_buffers, 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), }) } /// Load the given image into the texturestore, returning the index or an error. pub fn add_texture(&mut self, image: RgbaImage) -> Result { self.texture_store.add_texture(image, &mut self.device, &mut self.adapter, &mut self.queue_group.queues[0], &mut self.cmd_pool) } #[allow(clippy::type_complexity)] pub fn create_pipeline(device: &mut Device, extent: hal::image::Extent, subpass: &hal::pass::Subpass, set_layout: &DescriptorSetLayout) -> Result< ( PipelineLayout, GraphicsPipeline, ), error::CreationError> { 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::() * 8) as u32, rate: VertexInputRate::Vertex, }]; let attributes: Vec = vec![AttributeDesc { // XY Attribute location: 0, binding: 0, element: Element { format: Format::Rg32Sfloat, offset: 0, }, }, AttributeDesc { // RGB Attribute location: 1, binding: 0, element: Element { format: Format::Rgb32Sfloat, offset: (size_of::() * 2) as ElemOffset, } }, AttributeDesc { // UV Attribute location: 2, binding: 0, element: Element { format: Format::Rg32Sfloat, offset: (size_of::() * 5) as ElemOffset, } }, AttributeDesc { // Tex Attribute location: 3, binding: 0, element: Element { format: Format::R32Sint, offset: (size_of::() * 7) as ElemOffset } }]; // Rasterizer let rasterizer = Rasterizer { polygon_mode: PolygonMode::Fill, cull_face: Face::NONE, front_face: FrontFace::Clockwise, depth_clamping: false, depth_bias: None, conservative: true, line_width: hal::pso::State::Static(1.0) }; // Depth stencil let depth_stencil = DepthStencilDesc { depth: None, depth_bounds: false, stencil: None, }; // Pipeline layout let layout = unsafe { device.create_pipeline_layout(once(set_layout), &[]) }.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)) } /// Draw a frame that's just cleared to the color specified. pub fn draw_clear(&mut self, color: [f32; 4]) -> Result<(), error::FrameError> { let get_image = &self.get_image[self.current_frame]; let render_complete = &self.render_complete[self.current_frame]; // Advance the frame _before_ we start using the `?` operator self.current_frame = (self.current_frame + 1) % self.frames_in_flight; // Get the image let (image_index, _) = unsafe { self .swapchain .acquire_image(core::u64::MAX, Some(get_image), None) .map_err(|e| error::FrameError::AcquireError (e))? }; let image_index = image_index as usize; // Make sure whatever was last using this has finished let present_complete = &self.present_complete[image_index]; unsafe { self.device .wait_for_fence(present_complete, core::u64::MAX) .map_err(|_| error::FrameError::SyncObjectError)?; self.device .reset_fence(present_complete) .map_err(|_| error::FrameError::SyncObjectError)?; }; // Record commands unsafe { use hal::command::{ClearValue, ClearColor, SubpassContents, CommandBufferFlags}; let buffer = &mut self.cmd_buffers[image_index]; let clear_values = [ClearValue { color: ClearColor { float32: color } }]; buffer.begin_primary(CommandBufferFlags::EMPTY); buffer.begin_render_pass( &self.renderpass, &self.framebuffers[image_index], self.viewport.rect, clear_values.iter(), SubpassContents::Inline ); buffer.end_render_pass(); buffer.finish(); }; // Make submission object let command_buffers = &self.cmd_buffers[image_index..=image_index]; let wait_semaphores: ArrayVec<[_; 1]> = [(get_image, hal::pso::PipelineStage::COLOR_ATTACHMENT_OUTPUT)].into(); let signal_semaphores: ArrayVec<[_; 1]> = [render_complete].into(); let present_wait_semaphores: ArrayVec<[_; 1]> = [render_complete].into(); let submission = Submission { command_buffers, wait_semaphores, signal_semaphores, }; // Submit it let command_queue = &mut self.queue_group.queues[0]; unsafe { command_queue.submit(submission, Some(present_complete)); self.swapchain .present(command_queue, image_index as u32, present_wait_semaphores) .map_err(|_| error::FrameError::PresentError)? }; Ok(()) } pub fn draw_vertices(&mut self) -> Result<(), &'static str> { let get_image = &self.get_image[self.current_frame]; let render_complete = &self.render_complete[self.current_frame]; // Advance the frame _before_ we start using the `?` operator self.current_frame = (self.current_frame + 1) % self.frames_in_flight; // Get the image let (image_index, _) = unsafe { self .swapchain .acquire_image(core::u64::MAX, Some(get_image), None) .map_err(|_| "FrameError::AcquireError")? }; let image_index = image_index as usize; // Make sure whatever was last using this has finished let present_complete = &self.present_complete[image_index]; unsafe { self.device .wait_for_fence(present_complete, core::u64::MAX) .map_err(|_| "FrameError::SyncObjectError")?; self.device .reset_fence(present_complete) .map_err(|_| "FrameError::SyncObjectError")?; }; // Record commands unsafe { use hal::buffer::{IndexBufferView, SubRange}; use hal::command::{SubpassContents, CommandBufferFlags, ClearValue, ClearColor}; let buffer = &mut self.cmd_buffers[image_index]; let clear_values = [ClearValue { color: ClearColor { float32: [0.0, 0.0, 0.0, 1.0] } }]; // Commit from staging buffers let (vbufs, ibuf) = { let vbufref: &::Buffer = self.vert_buffer.commit( &self.device, &mut self.queue_group.queues[0], &mut self.cmd_pool ); let vbufs: ArrayVec<[_; 1]> = [(vbufref, SubRange::WHOLE)].into(); let ibuf = self.index_buffer.commit( &self.device, &mut self.queue_group.queues[0], &mut self.cmd_pool ); (vbufs, ibuf) }; buffer.begin_primary(CommandBufferFlags::EMPTY); { buffer.begin_render_pass( &self.renderpass, &self.framebuffers[image_index], self.viewport.rect, clear_values.iter(), SubpassContents::Inline ); buffer.bind_graphics_pipeline(&self.pipeline); buffer.bind_graphics_descriptor_sets( &self.pipeline_layout, 0, once(self.texture_store.descriptor_set.deref()), &[] ); buffer.bind_vertex_buffers(0, vbufs); buffer.bind_index_buffer(IndexBufferView { buffer: ibuf, range: SubRange::WHOLE, index_type: hal::IndexType::U16 }); buffer.draw_indexed(0..((self.index_buffer.highest_used as u32 + 1) * 3), 0, 0..1); buffer.end_render_pass(); } buffer.finish(); }; // Make submission object let command_buffers = &self.cmd_buffers[image_index..=image_index]; let wait_semaphores: ArrayVec<[_; 1]> = [(get_image, hal::pso::PipelineStage::COLOR_ATTACHMENT_OUTPUT)].into(); let signal_semaphores: ArrayVec<[_; 1]> = [render_complete].into(); let present_wait_semaphores: ArrayVec<[_; 1]> = [render_complete].into(); let submission = Submission { command_buffers, wait_semaphores, signal_semaphores, }; // Submit it let command_queue = &mut self.queue_group.queues[0]; unsafe { command_queue.submit(submission, Some(present_complete)); self.swapchain .present(command_queue, image_index as u32, present_wait_semaphores) .map_err(|_| "FrameError::PresentError")? }; Ok(()) } } impl<'a> core::ops::Drop for RenderingContext<'a> { fn drop(&mut self) { // TODO: Probably missing some destroy stuff self.device.wait_idle().unwrap(); unsafe { for fence in self.present_complete.drain(..) { self.device.destroy_fence(fence) } for semaphore in self.render_complete.drain(..) { self.device.destroy_semaphore(semaphore) } for semaphore in self.get_image.drain(..) { self.device.destroy_semaphore(semaphore) } for framebuffer in self.framebuffers.drain(..) { self.device.destroy_framebuffer(framebuffer); } for image_view in self.imageviews.drain(..) { self.device.destroy_image_view(image_view); } 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); 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_swapchain(ManuallyDrop::into_inner(read(&self.swapchain))); 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); } } }