aboutsummaryrefslogtreecommitdiff
path: root/stockton-render/src
diff options
context:
space:
mode:
authortcmal <me@aria.rip>2024-08-25 17:44:19 +0100
committertcmal <me@aria.rip>2024-08-25 17:44:19 +0100
commit95cc5ed06b4e251fd560958fe6f53d11d3bf31cd (patch)
tree29e28be74667d4fc526fe85558e5311212ffaff6 /stockton-render/src
parent6770b306fc0b14a96b29281f9b46ea3e912d339b (diff)
[wip] feat(render): add draw_triangle function
currently broken
Diffstat (limited to 'stockton-render/src')
-rw-r--r--stockton-render/src/draw/context.rs363
-rw-r--r--stockton-render/src/draw/mod.rs3
-rw-r--r--stockton-render/src/error.rs28
-rw-r--r--stockton-render/src/lib.rs16
4 files changed, 387 insertions, 23 deletions
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)
+ ]))
}
}