diff options
author | tcmal <me@aria.rip> | 2024-08-25 17:44:19 +0100 |
---|---|---|
committer | tcmal <me@aria.rip> | 2024-08-25 17:44:19 +0100 |
commit | 95cc5ed06b4e251fd560958fe6f53d11d3bf31cd (patch) | |
tree | 29e28be74667d4fc526fe85558e5311212ffaff6 | |
parent | 6770b306fc0b14a96b29281f9b46ea3e912d339b (diff) |
[wip] feat(render): add draw_triangle function
currently broken
-rw-r--r-- | examples/render/src/main.rs | 10 | ||||
-rw-r--r-- | stockton-render/Cargo.toml | 1 | ||||
-rw-r--r-- | stockton-render/src/draw/context.rs | 363 | ||||
-rw-r--r-- | stockton-render/src/draw/mod.rs | 3 | ||||
-rw-r--r-- | stockton-render/src/error.rs | 28 | ||||
-rw-r--r-- | stockton-render/src/lib.rs | 16 |
6 files changed, 397 insertions, 24 deletions
diff --git a/examples/render/src/main.rs b/examples/render/src/main.rs index efb0e41..044da4f 100644 --- a/examples/render/src/main.rs +++ b/examples/render/src/main.rs @@ -45,5 +45,13 @@ fn main() { let window = Window::new(&events).unwrap(); // Create the renderer. - let renderer = Renderer::new(world, &window); + let mut renderer = Renderer::new(world, &window).unwrap(); + + loop { + // TODO: Poll Window events + // TODO: Handle resize + // TODO: Simulate world + + renderer.render_frame().unwrap(); + } } diff --git a/stockton-render/Cargo.toml b/stockton-render/Cargo.toml index 9fbafb8..c608100 100644 --- a/stockton-render/Cargo.toml +++ b/stockton-render/Cargo.toml @@ -10,6 +10,7 @@ winit = "0.19.1" gfx-hal = "0.2.0" arrayvec = "0.4.10" nalgebra-glm = "0.4.0" +shaderc = "0.5.0" [features] default = ["vulkan"] diff --git a/stockton-render/src/draw/context.rs b/stockton-render/src/draw/context.rs index aa116cd..ffac0ec 100644 --- a/stockton-render/src/draw/context.rs +++ b/stockton-render/src/draw/context.rs @@ -14,7 +14,8 @@ // with this program. If not, see <http://www.gnu.org/licenses/>. //! Deals with all the Vulkan/HAL details. -use core::mem::ManuallyDrop; +use core::mem::{ManuallyDrop, size_of}; +use std::borrow::Cow; use crate::error::{CreationError, FrameError}; use super::frame::FrameCell; @@ -27,19 +28,52 @@ use winit::Window; use hal::{Surface as SurfaceTrait, Instance as InstanceTrait, QueueFamily as QFTrait, PhysicalDevice as PDTrait, Device as DeviceTrait, Swapchain as SwapchainTrait}; use hal::{Graphics, Gpu, Features, SwapchainConfig, Submission}; -use hal::pass::{SubpassDesc, AttachmentOps, Attachment, AttachmentStoreOp, AttachmentLoadOp}; +use hal::pso::*; +use hal::pass::{Subpass, SubpassDesc, AttachmentOps, Attachment, AttachmentStoreOp, AttachmentLoadOp}; use hal::image::{Usage, Layout, SubresourceRange, ViewKind, Extent}; +use hal::command::{ClearValue, ClearColor, CommandBuffer}; use hal::format::{ChannelType, Format, Swizzle, Aspects}; use hal::pool::{CommandPoolCreateFlags, CommandPool}; -use hal::command::{ClearValue, ClearColor, CommandBuffer}; -use hal::pso::{Rect, PipelineStage}; -use hal::queue::family::QueueGroup; use hal::window::{PresentMode, Extent2D}; -use hal::adapter::Adapter; +use hal::queue::family::QueueGroup; +use hal::adapter::{Adapter, MemoryTypeId}; use back::{Instance}; use back::{Backend}; +use stockton_types::Vector2; + +const VERTEX_SOURCE: &str = "#version 450 +layout (location = 0) in vec2 position; +out gl_PerVertex { + vec4 gl_Position; +}; +void main() +{ + gl_Position = vec4(position, 0.0, 1.0); +}"; +const FRAGMENT_SOURCE: &str = "#version 450 +layout(location = 0) out vec4 color; +void main() +{ + color = vec4(1.0); +}"; + +/// Represents a triangle in 2D (screen) space. +pub struct Tri2 (pub [Vector2; 3]); + +/// Easy conversion to proper format. +impl From<Tri2> for [f32; 6] { + fn from(tri: Tri2) -> [f32; 6] { + [tri.0[0].x, tri.0[0].y, + tri.0[1].x, tri.0[1].y, + tri.0[2].x, tri.0[2].y] + } +} + +/// Size of Tri2 +const F32_XY_TRIANGLE: u64 = (size_of::<f32>() * 2 * 3) as u64; + /// Contains all the hal related stuff. /// In the end, this takes some 3D points and puts it on the screen. pub struct RenderingContext<'a> { @@ -94,7 +128,17 @@ pub struct RenderingContext<'a> { frames_in_flight: usize, /// Track which framecell is up next. - current_frame: usize + current_frame: usize, + + descriptor_set_layouts: Vec<<Backend as hal::Backend>::DescriptorSetLayout>, + + pipeline_layout: ManuallyDrop<<Backend as hal::Backend>::PipelineLayout>, + + pipeline: ManuallyDrop<<Backend as hal::Backend>::GraphicsPipeline>, + + buffer: ManuallyDrop<<Backend as hal::Backend>::Buffer>, + memory: ManuallyDrop<<Backend as hal::Backend>::Memory>, + requirements: hal::memory::Requirements } impl<'a> RenderingContext<'a> { @@ -116,7 +160,7 @@ impl<'a> RenderingContext<'a> { .ok_or(CreationError::NoAdapter)?; // from that adapter, get the device & queue group - let (device, queue_group) = { + let (mut device, queue_group) = { let queue_family = adapter .queue_families .iter() @@ -248,6 +292,33 @@ impl<'a> RenderingContext<'a> { } }; + // Graphics pipeline + let (descriptor_set_layouts, pipeline_layout, pipeline) = RenderingContext::create_pipeline(&mut device, extent, &render_pass)?; + + // Vertex Buffer + let (mut buffer, memory, requirements) = unsafe { + let buffer = device + .create_buffer(F32_XY_TRIANGLE, hal::buffer::Usage::VERTEX) + .map_err(|e| CreationError::BufferFailed (e))?; + + let requirements = device.get_buffer_requirements(&buffer); + let memory_type_id = adapter.physical_device + .memory_properties().memory_types + .iter().enumerate() + .find(|&(id, memory_type)| { + requirements.type_mask & (1 << id) != 0 && memory_type.properties.contains(hal::memory::Properties::CPU_VISIBLE) + }) + .map(|(id, _)| MemoryTypeId(id)) + .ok_or(CreationError::NoMemory)?; + + let memory = device + .allocate_memory(memory_type_id, requirements.size) + .map_err(|e| CreationError::AllocationFailed (e))?; + + (buffer, memory, requirements) + }; + + // Make the command pool let mut command_pool = unsafe { device @@ -325,10 +396,195 @@ impl<'a> RenderingContext<'a> { command_pool: ManuallyDrop::new(command_pool), - current_frame: 0 + current_frame: 0, + + descriptor_set_layouts, + pipeline_layout: ManuallyDrop::new(pipeline_layout), + pipeline: ManuallyDrop::new(pipeline), + + buffer: ManuallyDrop::new(buffer), + memory: ManuallyDrop::new(memory), + requirements }) } + #[allow(clippy::type_complexity)] + fn create_pipeline(device: &mut back::Device, extent: Extent2D, render_pass: &<Backend as hal::Backend>::RenderPass) + -> Result<( + Vec<<Backend as hal::Backend>::DescriptorSetLayout>, + <Backend as hal::Backend>::PipelineLayout, + <Backend as hal::Backend>::GraphicsPipeline, + ), CreationError> { + + // Compile shaders + let mut compiler = shaderc::Compiler::new().ok_or(CreationError::NoShaderC)?; + + let vertex_compile_artifact = compiler + .compile_into_spirv(VERTEX_SOURCE, shaderc::ShaderKind::Vertex, "vertex.vert", "main", None) + .map_err(|e| CreationError::ShaderCError (e))?; + + let fragment_compile_artifact = compiler + .compile_into_spirv(FRAGMENT_SOURCE, shaderc::ShaderKind::Fragment, "fragment.frag", "main", None) + .map_err(|e| CreationError::ShaderCError (e))?; + + // Make into shader module + let vertex_shader_module = unsafe { + device + .create_shader_module(vertex_compile_artifact.as_binary_u8()) + .map_err(|e| CreationError::ShaderModuleFailed (e))? + }; + let fragment_shader_module = unsafe { + device + .create_shader_module(fragment_compile_artifact.as_binary_u8()) + .map_err(|e| CreationError::ShaderModuleFailed (e))? + }; + + // Specify entrypoints for each shader. + let vs_entry: EntryPoint<Backend> = EntryPoint { + entry: "main", + module: &vertex_shader_module, + specialization: Specialization { + constants: Cow::Borrowed (&[]), + data: Cow::Borrowed (&[]), + } + }; + + let fs_entry: EntryPoint<Backend> = EntryPoint { + entry: "main", + module: &fragment_shader_module, + specialization: Specialization { + constants: Cow::Borrowed (&[]), + data: Cow::Borrowed (&[]), + } + }; + + // Specify input format + let input_assembler = InputAssemblerDesc::new(hal::Primitive::TriangleList); + + // Vertex Shader I/O + let vertex_buffers: Vec<VertexBufferDesc> = vec![VertexBufferDesc { + binding: 0, + stride: (size_of::<f32>() * 2) as u32, + rate: VertexInputRate::Vertex, + }]; + + let attributes: Vec<AttributeDesc> = vec![AttributeDesc { + location: 0, + binding: 0, + element: Element { + format: Format::Rgb32Sfloat, + offset: 0, + }, + }]; + + // Make shader set + let shaders = GraphicsShaderSet { + vertex: vs_entry, + hull: None, + domain: None, + geometry: None, + fragment: Some(fs_entry), + }; + + + // Rasterisation options + let rasterizer = Rasterizer { + depth_clamping: false, + polygon_mode: PolygonMode::Fill, + cull_face: Face::NONE, + front_face: FrontFace::Clockwise, + depth_bias: None, + conservative: false, + }; + + // Depth testing options + let depth_stencil = DepthStencilDesc { + depth: DepthTest::Off, + depth_bounds: false, + stencil: StencilTest::Off, + }; + + // Colour blending options + // Only takes the source value + let blender = { + let blend_state = BlendState::On { + 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(ColorMask::ALL, blend_state)], + } + }; + + // Viewport, scissor, options + let baked_states = BakedStates { + viewport: Some(Viewport { + rect: extent.to_extent().rect(), + depth: (0.0..1.0), + }), + scissor: Some(extent.to_extent().rect()), + blend_color: None, + depth_bounds: None, + }; + + // Non-Buffer data sources (none right now) + let bindings = Vec::<DescriptorSetLayoutBinding>::new(); + let immutable_samplers = Vec::<<Backend as hal::Backend>::Sampler>::new(); + let descriptor_set_layouts: Vec<<Backend as hal::Backend>::DescriptorSetLayout> = vec![unsafe { + device + .create_descriptor_set_layout(bindings, immutable_samplers) + .map_err(|e| CreationError::DescriptorSetLayoutFailed (e))? + }]; + let push_constants = Vec::<(ShaderStageFlags, core::ops::Range<u32>)>::new(); + let layout = unsafe { + device + .create_pipeline_layout(&descriptor_set_layouts, push_constants) + .map_err(|e| CreationError::PipelineLayoutFailed (e))? + }; + + // Create the actual pipeline + let gfx_pipeline = { + let desc = GraphicsPipelineDesc { + shaders, + rasterizer, + vertex_buffers, + attributes, + input_assembler, + blender, + depth_stencil, + multisampling: None, + baked_states, + layout: &layout, + subpass: Subpass { + index: 0, + main_pass: render_pass, + }, + flags: PipelineCreationFlags::empty(), + parent: BasePipeline::None, + }; + + unsafe { + device.create_graphics_pipeline(&desc, None) + .map_err(|e| CreationError::PipelineFailed (e))? + } + }; + + // TODO: Destroy shader modules + unsafe { + device.destroy_shader_module(vertex_shader_module); + device.destroy_shader_module(fragment_shader_module); + } + + Ok((descriptor_set_layouts, layout, gfx_pipeline)) + } + /// Internal function. Gets the index of the next image from the swapchain to use & resets the frame_presented fence. fn get_image(swapchain: &mut <Backend as hal::Backend>::Swapchain, device: &mut <Backend as hal::Backend>::Device, cell: &FrameCell) -> Result<usize, FrameError> { // Get the image of the swapchain to present to. @@ -355,8 +611,8 @@ impl<'a> RenderingContext<'a> { /// Internal function. Prepares a submission for a frame. fn prep_submission(cell: &FrameCell) -> Submission<ArrayVec<[&CommandBuffer<Backend, Graphics>; 1]>, - ArrayVec<[(&<Backend as hal::Backend>::Semaphore, hal::pso::PipelineStage); 1]>, - ArrayVec<[&<Backend as hal::Backend>::Semaphore; 1]>> { + ArrayVec<[(&<Backend as hal::Backend>::Semaphore, hal::pso::PipelineStage); 1]>, + ArrayVec<[&<Backend as hal::Backend>::Semaphore; 1]>> { let command_buffers: ArrayVec<[_; 1]> = [&cell.command_buffer].into(); let wait_semaphores: ArrayVec<[_; 1]> = [(&cell.image_available, PipelineStage::COLOR_ATTACHMENT_OUTPUT)].into(); @@ -410,6 +666,75 @@ impl<'a> RenderingContext<'a> { Ok(()) } + + /// Draw a single triangle as a frame. + pub fn draw_triangle(&mut self, triangle: Tri2) -> Result<(), FrameError> { + // Advance the frame before early outs to prevent fuckery. + self.current_frame = (self.current_frame + 1) % self.frames_in_flight; + + // Get the image + let i = RenderingContext::get_image(&mut self.swapchain, &mut self.device, &self.cells[self.current_frame])?; + + let cell = &mut self.cells[self.current_frame]; + + // Write the vertex data to the buffer. + unsafe { + let mut data_target = self.device + .acquire_mapping_writer(&self.memory, 0..self.requirements.size) + .map_err(|e| FrameError::BufferError (e))?; + + let points: [f32; 6] = triangle.into(); + data_target[..6].copy_from_slice(&points); + + self + .device + .release_mapping_writer(data_target) + .map_err(|e| FrameError::BufferError (hal::mapping::Error::OutOfMemory(e)))?; + } + + // Record commands. + unsafe { + + const TRIANGLE_CLEAR: [ClearValue; 1] = [ClearValue::Color(ClearColor::Float([0.1, 0.2, 0.3, 1.0]))]; + + cell.command_buffer.begin(); + + { + let mut encoder = cell.command_buffer.begin_render_pass_inline( + &self.render_pass, + &self.framebuffers[i], + self.render_area, + TRIANGLE_CLEAR.iter(), + ); + encoder.bind_graphics_pipeline(&self.pipeline); + + let buffer_ref: &<Backend as hal::Backend>::Buffer = &self.buffer; + let buffers: ArrayVec<[_; 1]> = [(buffer_ref, 0)].into(); + + encoder.bind_vertex_buffers(0, buffers); + encoder.draw(0..3, 0..1); + } + cell.command_buffer.finish(); + } + + + // Prepare submission + let submission = RenderingContext::prep_submission(&cell); + + // Submit it for rendering and presentation. + let command_queue = &mut self.queue_group.queues[0]; + + let present_wait_semaphores: ArrayVec<[_; 1]> = [&cell.render_finished].into(); + + unsafe { + command_queue.submit(submission, Some(&cell.frame_presented)); + self.swapchain + .present(command_queue, i as u32, present_wait_semaphores) + .map_err(|e| FrameError::PresentError { 0: e })?; + println!("presented"); + }; + Ok(()) + } } /// Properly destroys all the vulkan objects we have. @@ -439,6 +764,22 @@ impl<'a> std::ops::Drop for RenderingContext<'a> { self.device.destroy_framebuffer(framebuffer); } + for descriptor_set in self.descriptor_set_layouts.drain(..) { + self.device.destroy_descriptor_set_layout(descriptor_set); + } + + // buffer + self.device.destroy_buffer(ManuallyDrop::into_inner(read(&self.buffer))); + self.device.free_memory(ManuallyDrop::into_inner(read(&self.memory))); + + // graphics pipeline + self.device + .destroy_pipeline_layout(ManuallyDrop::into_inner(read(&self.pipeline_layout))); + + self.device + .destroy_graphics_pipeline(ManuallyDrop::into_inner(read(&self.pipeline))); + + // command pool self.device .destroy_command_pool(ManuallyDrop::into_inner(read(&self.command_pool)).into_raw()); diff --git a/stockton-render/src/draw/mod.rs b/stockton-render/src/draw/mod.rs index 0774501..16bfd29 100644 --- a/stockton-render/src/draw/mod.rs +++ b/stockton-render/src/draw/mod.rs @@ -18,4 +18,5 @@ mod context; mod frame; -pub use context::RenderingContext;
\ No newline at end of file +pub use context::RenderingContext; +pub use context::Tri2;
\ No newline at end of file diff --git a/stockton-render/src/error.rs b/stockton-render/src/error.rs index 41fc92d..abd5cd3 100644 --- a/stockton-render/src/error.rs +++ b/stockton-render/src/error.rs @@ -22,13 +22,14 @@ /// These aren't guaranteed sanity issues, but they are weird issues. /// - Runtime - Things caused by runtime conditions, usually resource constraints. /// You can use the associated methods to get the group of one, which may be helpful for error reporting, etc. -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum CreationError { /// # Hardware NoAdapter, NoQueueFamily, NoPhysicalDevice, + NoMemory, /// # Sanity NoQueueGroup, @@ -38,6 +39,8 @@ pub enum CreationError { NoImageFormats, NoColor, NoWindow, + NoShaderC, + ShaderCError (shaderc::Error), /// # Runtime SwapchainFailed (hal::window::CreationError), @@ -46,7 +49,13 @@ pub enum CreationError { SemaphoreFailed (hal::device::OutOfMemory), FenceFailed (hal::device::OutOfMemory), ImageViewFailed (hal::image::ViewError), - FramebufferFailed (hal::device::OutOfMemory) + FramebufferFailed (hal::device::OutOfMemory), + ShaderModuleFailed (hal::device::ShaderError), + DescriptorSetLayoutFailed (hal::device::OutOfMemory), + PipelineLayoutFailed (hal::device::OutOfMemory), + PipelineFailed (hal::pso::CreationError), + BufferFailed (hal::buffer::CreationError), + AllocationFailed (hal::device::AllocationError) } impl CreationError { @@ -54,7 +63,8 @@ impl CreationError { pub fn is_hardware(&self) -> bool { use self::CreationError::*; match &self { - NoAdapter | NoQueueFamily | NoPhysicalDevice => true, + NoAdapter | NoQueueFamily | NoPhysicalDevice | + NoMemory => true, _ => false } } @@ -63,7 +73,8 @@ impl CreationError { use self::CreationError::*; match &self { NoQueueGroup | NoCommandQueues | NoPresentModes | - NoCompositeAlphas | NoImageFormats | NoColor + NoCompositeAlphas | NoImageFormats | NoColor | NoWindow | + ShaderCError(_) | NoShaderC => true, _ => false } @@ -75,7 +86,9 @@ impl CreationError { SwapchainFailed(_) | RenderPassFailed(_) | CommandPoolFailed(_) | SemaphoreFailed(_) | FenceFailed(_) | ImageViewFailed(_) | - FramebufferFailed(_) => true, + FramebufferFailed(_) | ShaderModuleFailed(_) | + DescriptorSetLayoutFailed(_) | PipelineLayoutFailed(_) | + PipelineFailed(_) | BufferFailed(_) | AllocationFailed(_) => true, _ => false } } @@ -96,5 +109,8 @@ pub enum FrameError { FenceResetError (hal::device::OutOfMemory), /// Error presenting the rendered frame. - PresentError (hal::window::PresentError) + PresentError (hal::window::PresentError), + + /// Error writing to buffer + BufferError (hal::mapping::Error), }
\ No newline at end of file diff --git a/stockton-render/src/lib.rs b/stockton-render/src/lib.rs index 79de1ce..f0a88ee 100644 --- a/stockton-render/src/lib.rs +++ b/stockton-render/src/lib.rs @@ -37,6 +37,7 @@ extern crate gfx_backend_vulkan as back; extern crate gfx_hal as hal; extern crate stockton_types; +extern crate shaderc; extern crate winit; extern crate arrayvec; @@ -45,9 +46,9 @@ mod error; mod draw; use error::{CreationError, FrameError}; -use draw::RenderingContext; +use draw::{RenderingContext, Tri2}; -use stockton_types::World; +use stockton_types::{World, Vector2}; use winit::Window; @@ -70,8 +71,13 @@ impl<'a> Renderer<'a> { }) } - /// Draw a frame of solid `color` (RGBA) - pub fn draw_clear(&mut self, color: [f32; 4]) -> Result<(), FrameError> { - self.context.draw_clear(color) + /// Render a single frame of the world + pub fn render_frame(&mut self) -> Result<(), FrameError>{ + // self.context.draw_clear([0.0, 0.5, 0.5, 1.0]) + self.context.draw_triangle(Tri2 ([ + Vector2::new(-0.5, -0.5), + Vector2::new(0.5, -0.5), + Vector2::new(0.0, 0.5) + ])) } } |