aboutsummaryrefslogtreecommitdiff
path: root/stockton-render/src/draw/target.rs
diff options
context:
space:
mode:
authortcmal <me@aria.rip>2024-08-25 17:44:21 +0100
committertcmal <me@aria.rip>2024-08-25 17:44:21 +0100
commit2f112ab34ac1458b038598f4d7ef6638df463dc6 (patch)
treec0100341b31276297fc9d2992797c5ca18cd2d99 /stockton-render/src/draw/target.rs
parent27760ec1ca7a93877b2b015a0a2e9db87de4204c (diff)
feat(render): depth buffer and refactors
Diffstat (limited to 'stockton-render/src/draw/target.rs')
-rw-r--r--stockton-render/src/draw/target.rs407
1 files changed, 407 insertions, 0 deletions
diff --git a/stockton-render/src/draw/target.rs b/stockton-render/src/draw/target.rs
new file mode 100644
index 0000000..3d0ecdf
--- /dev/null
+++ b/stockton-render/src/draw/target.rs
@@ -0,0 +1,407 @@
+// Copyright (C) Oscar Shrimpton 2019
+
+// 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 <http://www.gnu.org/licenses/>.
+
+//! Resources needed for drawing on the screen, including sync objects
+use crate::types::*;
+use super::{
+ camera::WorkingCamera,
+ texture::image::LoadedImage
+};
+
+use core::{
+ iter::once,
+ mem::ManuallyDrop
+};
+
+use arrayvec::ArrayVec;
+use hal::{
+ prelude::*,
+ queue::Submission,
+ pso::Viewport,
+ format::{ChannelType, Format, Swizzle},
+ image::{Extent, ViewKind, Usage as ImgUsage},
+ window::{CompositeAlphaMode, PresentMode, SwapchainConfig, Extent2D}
+};
+
+/// 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,
+};
+
+#[derive(Debug, Clone)]
+pub struct SwapchainProperties {
+ pub format: Format,
+ pub depth_format: Format,
+ pub present_mode: PresentMode,
+ pub composite_alpha_mode: CompositeAlphaMode,
+ pub viewport: Viewport,
+ pub extent: Extent
+}
+
+impl SwapchainProperties {
+ pub fn find_best(adapter: &Adapter, surface: &Surface) -> Result<SwapchainProperties, ()> {
+ 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.clone().map_or(Format::Rgba8Srgb, |formats| {
+ formats.iter()
+ .find(|format| format.base_format().1 == ChannelType::Srgb)
+ .map(|format| *format)
+ .unwrap_or(formats[0])
+ });
+
+ let depth_format = *[Format::D32SfloatS8Uint, Format::D24UnormS8Uint, Format::D32Sfloat].iter().find(|format| {
+ use hal::format::ImageFeature;
+
+ format.is_depth() && adapter.physical_device.format_properties(Some(**format)).optimal_tiling.contains(ImageFeature::DEPTH_STENCIL_ATTACHMENT)
+ }).ok_or_else(|| ())?;
+
+ let present_mode = {
+ [PresentMode::MAILBOX, PresentMode::FIFO, PresentMode::RELAXED, PresentMode::IMMEDIATE]
+ .iter()
+ .cloned()
+ .find(|pm| caps.present_modes.contains(*pm))
+ .ok_or(())?
+ };
+ let composite_alpha_mode = {
+ [CompositeAlphaMode::OPAQUE, CompositeAlphaMode::INHERIT, CompositeAlphaMode::PREMULTIPLIED, CompositeAlphaMode::POSTMULTIPLIED]
+ .iter()
+ .cloned()
+ .find(|ca| caps.composite_alpha_modes.contains(*ca))
+ .ok_or(())?
+ };
+
+ let extent = caps.extents.end().to_extent(); // Size
+ let viewport = Viewport {
+ rect: extent.rect(),
+ depth: 0.0..1.0
+ };
+
+ Ok(SwapchainProperties {format, depth_format, present_mode, composite_alpha_mode, extent, viewport})
+ }
+}
+
+pub struct TargetChain {
+ /// Swapchain we're targeting
+ pub swapchain: ManuallyDrop<Swapchain>,
+
+ pub properties: SwapchainProperties,
+
+ /// The depth buffer/image used for drawing
+ pub depth_buffer: ManuallyDrop<LoadedImage>,
+
+ /// Resources tied to each target frame in the swapchain
+ pub targets: Box<[TargetResources]>,
+
+ /// The last target drawn to
+ last_drawn: usize,
+
+ /// Last image index of the swapchain drawn to
+ last_image_index: u32,
+}
+
+impl TargetChain {
+ pub fn new(device: &mut Device, adapter: &Adapter, surface: &mut Surface, renderpass: &RenderPass, cmd_pool: &mut CommandPool, properties: SwapchainProperties, old_swapchain: Option<Swapchain>) -> Result<TargetChain, TargetChainCreationError> {
+ let caps = surface.capabilities(&adapter.physical_device);
+
+ // Number of frames to pre-render
+ let image_count = if properties.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))
+ };
+
+ // Swap config
+ let swap_config = SwapchainConfig {
+ present_mode: properties.present_mode,
+ composite_alpha_mode: properties.composite_alpha_mode,
+ format: properties.format,
+ extent: Extent2D { width: properties.extent.width, height: properties.extent.height },
+ image_count,
+ image_layers: 1,
+ image_usage: ImgUsage::COLOR_ATTACHMENT,
+ };
+
+ // Swapchain
+ let (swapchain, mut backbuffer) = unsafe {
+ device.create_swapchain(surface, swap_config, old_swapchain)
+ .map_err(|_| TargetChainCreationError::Todo)?
+ };
+
+ let depth_buffer: LoadedImage = {
+ use hal::image::SubresourceRange;
+ use hal::format::Aspects;
+
+ LoadedImage::new(device, adapter, properties.depth_format, ImgUsage::DEPTH_STENCIL_ATTACHMENT, SubresourceRange {
+ aspects: Aspects::DEPTH,
+ levels: 0..1,
+ layers: 0..1,
+ },properties.extent.width as usize, properties.extent.height as usize).map_err(|_| TargetChainCreationError::Todo)
+ }?;
+
+ let mut targets: Vec<TargetResources> = Vec::with_capacity(backbuffer.len());
+ for image in backbuffer.drain(..) {
+ targets.push(TargetResources::new(device, cmd_pool, renderpass, image, &(*depth_buffer.image_view), properties.extent, properties.format)
+ .map_err(|_| TargetChainCreationError::Todo)?);
+ }
+
+ Ok(TargetChain {
+ swapchain: ManuallyDrop::new(swapchain),
+ targets: targets.into_boxed_slice(),
+ depth_buffer: ManuallyDrop::new(depth_buffer),
+ properties,
+ last_drawn: (image_count - 1) as usize, // This means the next one to be used is index 0
+ last_image_index: 0
+ })
+ }
+
+ pub fn deactivate(self, device: &mut Device, cmd_pool: &mut CommandPool) -> () {
+ use core::ptr::read;
+ unsafe {
+ ManuallyDrop::into_inner(read(&self.depth_buffer)).deactivate(device);
+
+ for i in 0..self.targets.len() {
+ read(&self.targets[i]).deactivate(device, cmd_pool);
+ }
+
+ device.destroy_swapchain(ManuallyDrop::into_inner(read(&self.swapchain)));
+ }
+ }
+
+ pub fn deactivate_with_recyling(self, device: &mut Device, cmd_pool: &mut CommandPool) -> Swapchain {
+ use core::ptr::read;
+ unsafe {
+ ManuallyDrop::into_inner(read(&self.depth_buffer)).deactivate(device);
+
+ for i in 0..self.targets.len() {
+ read(&self.targets[i]).deactivate(device, cmd_pool);
+ }
+ }
+
+ return unsafe { ManuallyDrop::into_inner(read(&self.swapchain)) };
+ }
+
+ pub fn prep_next_target<'a>(&'a mut self, device: &mut Device, vert_buffer: &Buffer, index_buffer: &Buffer, renderpass: &RenderPass, pipeline: &GraphicsPipeline, pipeline_layout: &PipelineLayout, camera: &mut WorkingCamera) -> Result<&'a mut crate::types::CommandBuffer, &'static str> {
+ self.last_drawn = (self.last_drawn + 1) % self.targets.len();
+
+ let target = &mut self.targets[self.last_drawn];
+
+ // Get the image
+ let (image_index, _) = unsafe {
+ self.swapchain
+ .acquire_image(core::u64::MAX, Some(&target.get_image), None)
+ .map_err(|_| "FrameError::AcquireError")?
+ };
+
+ self.last_image_index = image_index;
+
+ // Make sure whatever was last using this has finished
+ unsafe {
+ device
+ .wait_for_fence(&target.present_complete, core::u64::MAX)
+ .map_err(|_| "FrameError::SyncObjectError")?;
+ device
+ .reset_fence(&target.present_complete)
+ .map_err(|_| "FrameError::SyncObjectError")?;
+ };
+
+ // Record commands
+ unsafe {
+ use hal::buffer::{IndexBufferView, SubRange};
+ use hal::command::{SubpassContents, CommandBufferFlags, ClearValue, ClearColor, ClearDepthStencil};
+ use hal::pso::ShaderStageFlags;
+
+ // Colour to clear window to
+ let clear_values = [ClearValue {
+ color: ClearColor {
+ float32: [0.0, 0.0, 0.0, 1.0]
+ }
+ }, ClearValue {
+ depth_stencil: ClearDepthStencil {
+ depth: 1.0,
+ stencil: 0
+ }
+ }];
+
+ // Get references to our buffers
+ let (vbufs, ibuf) = {
+ let vbufref: &<back::Backend as hal::Backend>::Buffer = vert_buffer;
+
+ let vbufs: ArrayVec<[_; 1]> = [(vbufref, SubRange::WHOLE)].into();
+ let ibuf = index_buffer;
+
+ (vbufs, ibuf)
+ };
+
+ target.cmd_buffer.begin_primary(CommandBufferFlags::EMPTY);
+ // Main render pass / pipeline
+ target.cmd_buffer.begin_render_pass(
+ renderpass,
+ &target.framebuffer,
+ self.properties.viewport.rect,
+ clear_values.iter(),
+ SubpassContents::Inline
+ );
+ target.cmd_buffer.bind_graphics_pipeline(&pipeline);
+
+ // VP Matrix
+ let vp = camera.get_matrix().as_slice();
+ let vp = std::mem::transmute::<&[f32], &[u32]>(vp);
+
+ target.cmd_buffer.push_graphics_constants(
+ &pipeline_layout,
+ ShaderStageFlags::VERTEX,
+ 0,
+ vp);
+
+ // Bind buffers
+ target.cmd_buffer.bind_vertex_buffers(0, vbufs);
+ target.cmd_buffer.bind_index_buffer(IndexBufferView {
+ buffer: ibuf,
+ range: SubRange::WHOLE,
+ index_type: hal::IndexType::U16
+ });
+ };
+
+ Ok(&mut target.cmd_buffer)
+ }
+
+ pub fn finish_and_submit_target(&mut self, command_queue: &mut CommandQueue) -> Result<(), &'static str> {
+ let target = &mut self.targets[self.last_drawn];
+
+ unsafe {
+ target.cmd_buffer.end_render_pass();
+ target.cmd_buffer.finish();
+ }
+
+ // Make submission object
+ let command_buffers: std::iter::Once<&CommandBuffer> = once(&target.cmd_buffer);
+ let wait_semaphores: std::iter::Once<(&Semaphore, hal::pso::PipelineStage)> = once((&target.get_image, hal::pso::PipelineStage::COLOR_ATTACHMENT_OUTPUT));
+ let signal_semaphores: std::iter::Once<&Semaphore> = once(&target.render_complete);
+
+ let present_wait_semaphores: std::iter::Once<&Semaphore> = once(&target.render_complete);
+
+ let submission = Submission {
+ command_buffers,
+ wait_semaphores,
+ signal_semaphores,
+ };
+
+ // Submit it
+ unsafe {
+ command_queue.submit(submission, Some(&target.present_complete));
+ self.swapchain
+ .present(command_queue, self.last_image_index as u32, present_wait_semaphores)
+ .map_err(|_| "FrameError::PresentError")?;
+ };
+
+ // TODO
+ Ok(())
+ }
+}
+
+/// Resources for a single target frame, including sync objects
+pub struct TargetResources {
+ /// Command buffer to use when drawing
+ pub cmd_buffer: ManuallyDrop<CommandBuffer>,
+
+ /// The image for this frame
+ pub image: ManuallyDrop<Image>,
+
+ /// Imageviews for this frame
+ pub imageview: ManuallyDrop<ImageView>,
+
+ /// Framebuffer for this frame
+ pub framebuffer: ManuallyDrop<Framebuffer>,
+
+ // Sync objects
+
+ /// Triggered when the image is ready to draw to
+ pub get_image: ManuallyDrop<Semaphore>,
+
+ /// Triggered when rendering is done
+ pub render_complete: ManuallyDrop<Semaphore>,
+
+ /// Triggered when the image is on screen
+ pub present_complete: ManuallyDrop<Fence>
+}
+
+
+impl TargetResources {
+ pub fn new(device: &mut Device, cmd_pool: &mut CommandPool, renderpass: &RenderPass, image: Image, depth_pass: &ImageView, extent: Extent, format: Format) -> Result<TargetResources, TargetResourcesCreationError> {
+ // Command Buffer
+ let cmd_buffer = unsafe { cmd_pool.allocate_one(hal::command::Level::Primary) };
+
+ // ImageView
+ let imageview = unsafe { device.create_image_view(
+ &image,
+ ViewKind::D2,
+ format,
+ Swizzle::NO,
+ COLOR_RANGE.clone(),
+ ).map_err(|e| TargetResourcesCreationError::ImageViewError (e))? };
+
+ // Framebuffer
+ let framebuffer = unsafe { device.create_framebuffer(
+ &renderpass,
+ once(&imageview).chain(once(depth_pass)),
+ extent
+ ).map_err(|_| TargetResourcesCreationError::FrameBufferNoMemory)? };
+
+ // Sync objects
+ let get_image = device.create_semaphore().map_err(|_| TargetResourcesCreationError::SyncObjectsNoMemory)?;
+ let render_complete = device.create_semaphore().map_err(|_| TargetResourcesCreationError::SyncObjectsNoMemory)?;
+ let present_complete = device.create_fence(true).map_err(|_| TargetResourcesCreationError::SyncObjectsNoMemory)?;
+
+ Ok(TargetResources {
+ cmd_buffer: ManuallyDrop::new(cmd_buffer),
+ image: ManuallyDrop::new(image),
+ imageview: ManuallyDrop::new(imageview),
+ framebuffer: ManuallyDrop::new(framebuffer),
+ get_image: ManuallyDrop::new(get_image),
+ render_complete: ManuallyDrop::new(render_complete),
+ present_complete: ManuallyDrop::new(present_complete)
+ })
+ }
+
+ pub fn deactivate(self, device: &mut Device, cmd_pool: &mut CommandPool) -> () {
+ use core::ptr::read;
+ unsafe {
+ cmd_pool.free(once(ManuallyDrop::into_inner(read(&self.cmd_buffer))));
+
+ device.destroy_framebuffer(ManuallyDrop::into_inner(read(&self.framebuffer)));
+ device.destroy_image_view(ManuallyDrop::into_inner(read(&self.imageview)));
+
+ device.destroy_semaphore(ManuallyDrop::into_inner(read(&self.get_image)));
+ device.destroy_semaphore(ManuallyDrop::into_inner(read(&self.render_complete)));
+ device.destroy_fence(ManuallyDrop::into_inner(read(&self.present_complete)));
+ }
+ }
+}
+
+#[derive(Debug)]
+pub enum TargetChainCreationError {
+ Todo
+}
+
+#[derive(Debug)]
+pub enum TargetResourcesCreationError {
+ ImageViewError (hal::image::ViewCreationError),
+ FrameBufferNoMemory,
+ SyncObjectsNoMemory
+}