diff options
author | tcmal <me@aria.rip> | 2024-08-25 17:44:23 +0100 |
---|---|---|
committer | tcmal <me@aria.rip> | 2024-08-25 17:44:23 +0100 |
commit | 47a0c0317cc774c19b78582bec9b5b09d56f569a (patch) | |
tree | d03471ea4e084ace9b95a2c5b7febb780b45bb63 /stockton-render/src/draw/builders | |
parent | fb996488aa651cb2e7f46abc083c4318b47e77cd (diff) |
feat(render): draw passes
Diffstat (limited to 'stockton-render/src/draw/builders')
-rw-r--r-- | stockton-render/src/draw/builders/mod.rs | 3 | ||||
-rw-r--r-- | stockton-render/src/draw/builders/pipeline.rs | 276 | ||||
-rw-r--r-- | stockton-render/src/draw/builders/renderpass.rs | 75 | ||||
-rw-r--r-- | stockton-render/src/draw/builders/shader.rs | 35 |
4 files changed, 389 insertions, 0 deletions
diff --git a/stockton-render/src/draw/builders/mod.rs b/stockton-render/src/draw/builders/mod.rs new file mode 100644 index 0000000..002c09f --- /dev/null +++ b/stockton-render/src/draw/builders/mod.rs @@ -0,0 +1,3 @@ +pub mod pipeline; +pub mod renderpass; +pub mod shader; diff --git a/stockton-render/src/draw/builders/pipeline.rs b/stockton-render/src/draw/builders/pipeline.rs new file mode 100644 index 0000000..cdd9736 --- /dev/null +++ b/stockton-render/src/draw/builders/pipeline.rs @@ -0,0 +1,276 @@ +use super::{renderpass::RenderpassSpec, shader::ShaderDesc}; +use crate::{draw::target::SwapchainProperties, error::EnvironmentError, types::*}; + +use std::{mem::ManuallyDrop, ops::Range}; + +use anyhow::{Context, Result}; +use hal::{ + format::Format, + pso::{ + AttributeDesc, BakedStates, BasePipeline, BlendDesc, BufferIndex, DepthStencilDesc, + ElemStride, Element, GraphicsPipelineDesc, InputAssemblerDesc, PipelineCreationFlags, + PrimitiveAssemblerDesc, Rasterizer, Rect, ShaderStageFlags, VertexBufferDesc, + VertexInputRate, Viewport, + }, +}; +use shaderc::Compiler; + +pub struct VertexBufferSpec { + pub attributes: Vec<Format>, + pub rate: VertexInputRate, +} + +impl VertexBufferSpec { + pub fn as_attribute_desc(&self, binding: BufferIndex) -> Vec<AttributeDesc> { + let mut v = Vec::with_capacity(self.attributes.len()); + let mut offset = 0; + for (idx, format) in self.attributes.iter().enumerate() { + v.push(AttributeDesc { + location: idx as u32, + binding, + element: Element { + offset, + format: *format, + }, + }); + offset += get_size(*format); + } + + v + } + pub fn stride(&self) -> ElemStride { + self.attributes.iter().fold(0, |x, f| x + get_size(*f)) + } +} + +fn get_size(f: Format) -> u32 { + match f { + Format::Rgb32Sfloat => 4 * 3, + Format::R32Sint => 4, + Format::Rg32Sfloat => 4 * 2, + _ => unimplemented!("dont know size of format {:?}", f), + } +} + +#[derive(Debug, Clone)] +pub struct VertexPrimitiveAssemblerSpec { + buffers: Vec<VertexBufferDesc>, + attributes: Vec<AttributeDesc>, + input_assembler: InputAssemblerDesc, +} + +impl VertexPrimitiveAssemblerSpec { + pub fn with_buffer(&mut self, bd: VertexBufferSpec) -> &mut Self { + let idx = self.buffers.len() as u32; + self.buffers.push(VertexBufferDesc { + binding: idx, + stride: bd.stride(), + rate: bd.rate, + }); + + self.attributes.extend(bd.as_attribute_desc(idx)); + + self + } + + pub fn with_buffers(iad: InputAssemblerDesc, mut bds: Vec<VertexBufferSpec>) -> Self { + let mut this = VertexPrimitiveAssemblerSpec { + buffers: vec![], + attributes: vec![], + input_assembler: iad, + }; + + for bd in bds.drain(..) { + this.with_buffer(bd); + } + + this + } +} + +#[derive(Builder, Debug)] +#[builder(public)] +pub struct PipelineSpec { + rasterizer: Rasterizer, + depth_stencil: DepthStencilDesc, + blender: BlendDesc, + primitive_assembler: VertexPrimitiveAssemblerSpec, + + shader_vertex: ShaderDesc, + #[builder(setter(strip_option))] + shader_fragment: Option<ShaderDesc>, + #[builder(setter(strip_option), default)] + shader_geom: Option<ShaderDesc>, + #[builder(setter(strip_option), default)] + shader_tesselation: Option<(ShaderDesc, ShaderDesc)>, + + push_constants: Vec<(ShaderStageFlags, Range<u32>)>, + + renderpass: RenderpassSpec, +} + +impl PipelineSpec { + pub fn build<'b, T: Iterator<Item = &'b DescriptorSetLayoutT> + std::fmt::Debug>( + self, + device: &mut DeviceT, + extent: hal::image::Extent, + _swapchain_properties: &SwapchainProperties, + set_layouts: T, + ) -> Result<CompletePipeline> { + // Renderpass + let renderpass = self.renderpass.build_renderpass(device)?; + + // Subpass + let subpass = hal::pass::Subpass { + index: 0, + main_pass: &renderpass, + }; + + let mut compiler = Compiler::new().ok_or(EnvironmentError::NoShaderC)?; + let (vs_module, fs_module, gm_module, ts_module) = { + ( + self.shader_vertex.compile(&mut compiler, device)?, + self.shader_fragment + .as_ref() + .map(|x| x.compile(&mut compiler, device)) + .transpose()?, + self.shader_geom + .as_ref() + .map(|x| x.compile(&mut compiler, device)) + .transpose()?, + self.shader_tesselation + .as_ref() + .map::<Result<_>, _>(|(a, b)| { + Ok(( + a.compile(&mut compiler, device)?, + b.compile(&mut compiler, device)?, + )) + }) + .transpose()?, + ) + }; + + // Safety: *_module is always populated when shader_* is, so this is safe + let (vs_entry, fs_entry, gm_entry, ts_entry) = ( + self.shader_vertex.as_entry(&vs_module), + self.shader_fragment + .as_ref() + .map(|x| x.as_entry(fs_module.as_ref().unwrap())), + self.shader_geom + .as_ref() + .map(|x| x.as_entry(gm_module.as_ref().unwrap())), + self.shader_tesselation.as_ref().map(|(a, b)| { + ( + a.as_entry(&ts_module.as_ref().unwrap().0), + b.as_entry(&ts_module.as_ref().unwrap().1), + ) + }), + ); + + // Pipeline layout + let layout = unsafe { + device.create_pipeline_layout(set_layouts.into_iter(), self.push_constants.into_iter()) + } + .context("Error creating pipeline layout")?; + + // Baked states + let baked_states = BakedStates { + viewport: Some(Viewport { + rect: extent.rect(), + depth: (0.0..1.0), + }), + scissor: Some(extent.rect()), + blend_constants: None, + depth_bounds: None, + }; + + // Primitive assembler + let primitive_assembler = PrimitiveAssemblerDesc::Vertex { + buffers: self.primitive_assembler.buffers.as_slice(), + attributes: self.primitive_assembler.attributes.as_slice(), + input_assembler: self.primitive_assembler.input_assembler, + vertex: vs_entry, + tessellation: ts_entry, + geometry: gm_entry, + }; + + // Pipeline description + let pipeline_desc = GraphicsPipelineDesc { + label: Some("stockton"), + rasterizer: self.rasterizer, + fragment: fs_entry, + blender: self.blender, + depth_stencil: self.depth_stencil, + multisampling: None, + baked_states, + layout: &layout, + subpass, + flags: PipelineCreationFlags::empty(), + parent: BasePipeline::None, + primitive_assembler, + }; + + // Pipeline + let pipeline = unsafe { device.create_graphics_pipeline(&pipeline_desc, None) } + .context("Error creating graphics pipeline")?; + + Ok(CompletePipeline { + renderpass: ManuallyDrop::new(renderpass), + pipeline_layout: ManuallyDrop::new(layout), + pipeline: ManuallyDrop::new(pipeline), + vs_module: ManuallyDrop::new(vs_module), + fs_module, + gm_module, + ts_module, + render_area: extent.rect(), + }) + } +} + +pub struct CompletePipeline { + /// Our main render pass + pub(crate) renderpass: ManuallyDrop<RenderPassT>, + + /// The layout of our main graphics pipeline + pub(crate) pipeline_layout: ManuallyDrop<PipelineLayoutT>, + + /// Our main graphics pipeline + pub(crate) pipeline: ManuallyDrop<GraphicsPipelineT>, + + /// The vertex shader module + pub(crate) vs_module: ManuallyDrop<ShaderModuleT>, + + /// The fragment shader module + pub(crate) fs_module: Option<ShaderModuleT>, + pub(crate) gm_module: Option<ShaderModuleT>, + pub(crate) ts_module: Option<(ShaderModuleT, ShaderModuleT)>, + + pub(crate) render_area: Rect, +} + +impl CompletePipeline { + /// Deactivate vulkan resources. Use before dropping + pub fn deactivate(mut self, device: &mut DeviceT) { + unsafe { + use core::ptr::read; + + device.destroy_render_pass(ManuallyDrop::into_inner(read(&self.renderpass))); + + device.destroy_shader_module(ManuallyDrop::into_inner(read(&self.vs_module))); + if let Some(x) = self.fs_module.take() { + device.destroy_shader_module(x) + } + if let Some(x) = self.gm_module.take() { + device.destroy_shader_module(x) + } + self.ts_module.take().map(|(a, b)| { + device.destroy_shader_module(a); + device.destroy_shader_module(b); + }); + + device.destroy_graphics_pipeline(ManuallyDrop::into_inner(read(&self.pipeline))); + + device.destroy_pipeline_layout(ManuallyDrop::into_inner(read(&self.pipeline_layout))); + } + } +} diff --git a/stockton-render/src/draw/builders/renderpass.rs b/stockton-render/src/draw/builders/renderpass.rs new file mode 100644 index 0000000..43f0eb2 --- /dev/null +++ b/stockton-render/src/draw/builders/renderpass.rs @@ -0,0 +1,75 @@ +use crate::types::*; + +use std::iter::{empty, once}; + +use anyhow::Result; +use hal::pass::{Attachment, AttachmentRef, SubpassDesc}; + +#[derive(Debug, Clone)] +pub struct RenderpassSpec { + pub colors: Vec<Attachment>, + pub depth: Option<Attachment>, + pub inputs: Vec<Attachment>, + pub resolves: Vec<Attachment>, + pub preserves: Vec<Attachment>, +} + +impl RenderpassSpec { + pub fn build_renderpass(self, device: &mut DeviceT) -> Result<RenderPassT> { + let mut next_offset = 0; + + let colors: Vec<AttachmentRef> = self + .colors + .iter() + .enumerate() + .map(|(i, a)| (next_offset + i, a.layouts.end)) + .collect(); + next_offset = colors.len(); + + let depth_stencil = self.depth.as_ref().map(|x| (next_offset, x.layouts.end)); + if depth_stencil.is_some() { + next_offset += 1; + } + + let inputs: Vec<AttachmentRef> = self + .inputs + .iter() + .enumerate() + .map(|(i, a)| (next_offset + i, a.layouts.end)) + .collect(); + next_offset += inputs.len(); + + let resolves: Vec<AttachmentRef> = self + .resolves + .iter() + .enumerate() + .map(|(i, a)| (next_offset + i, a.layouts.end)) + .collect(); + next_offset += resolves.len(); + + let preserves: Vec<usize> = self + .preserves + .iter() + .enumerate() + .map(|(i, _a)| next_offset + i) + .collect(); + + let sp_desc = SubpassDesc { + colors: colors.as_slice(), + depth_stencil: depth_stencil.as_ref(), + inputs: inputs.as_slice(), + resolves: resolves.as_slice(), + preserves: preserves.as_slice(), + }; + + let all_attachments = self + .colors + .into_iter() + .chain(self.depth.into_iter()) + .chain(self.inputs.into_iter()) + .chain(self.resolves.into_iter()) + .chain(self.preserves.into_iter()); + + Ok(unsafe { device.create_render_pass(all_attachments, once(sp_desc), empty())? }) + } +} diff --git a/stockton-render/src/draw/builders/shader.rs b/stockton-render/src/draw/builders/shader.rs new file mode 100644 index 0000000..fde185d --- /dev/null +++ b/stockton-render/src/draw/builders/shader.rs @@ -0,0 +1,35 @@ +use crate::types::*; + +use anyhow::{Context, Result}; +use hal::pso::Specialization; +use shaderc::{Compiler, ShaderKind}; + +#[derive(Debug, Clone)] +pub struct ShaderDesc { + pub source: String, + pub entry: String, + pub kind: ShaderKind, +} + +impl ShaderDesc { + pub fn compile(&self, compiler: &mut Compiler, device: &mut DeviceT) -> Result<ShaderModuleT> { + let artifact = compiler + .compile_into_spirv(&self.source, self.kind, "shader", &self.entry, None) + .context("Shader compilation failed")?; + + // Make into shader module + Ok(unsafe { + device + .create_shader_module(artifact.as_binary()) + .context("Shader module creation failed")? + }) + } + + pub fn as_entry<'a>(&'a self, module: &'a ShaderModuleT) -> EntryPoint<'a> { + EntryPoint { + entry: &self.entry, + module, + specialization: Specialization::default(), + } + } +} |