aboutsummaryrefslogtreecommitdiff
path: root/stockton-skeleton/src/target.rs
diff options
context:
space:
mode:
authortcmal <me@aria.rip>2024-08-25 17:44:23 +0100
committertcmal <me@aria.rip>2024-08-25 17:44:23 +0100
commit0353181306702c40ad0fe482b5c2b159b46794a4 (patch)
tree33acc6a9e8ea4705884cf93b78cf869008f71832 /stockton-skeleton/src/target.rs
parent664f0b0777ba96298b29f0c753d52a81cbb233f1 (diff)
refactor(all): rename some crates
Diffstat (limited to 'stockton-skeleton/src/target.rs')
-rw-r--r--stockton-skeleton/src/target.rs397
1 files changed, 397 insertions, 0 deletions
diff --git a/stockton-skeleton/src/target.rs b/stockton-skeleton/src/target.rs
new file mode 100644
index 0000000..d0d2380
--- /dev/null
+++ b/stockton-skeleton/src/target.rs
@@ -0,0 +1,397 @@
+//! Resources needed for drawing on the screen, including sync objects
+
+use std::{
+ borrow::Borrow,
+ iter::{empty, once},
+ mem::ManuallyDrop,
+};
+
+use hal::{
+ command::CommandBufferFlags,
+ format::{Aspects, ChannelType, Format, ImageFeature},
+ image::{
+ Access, Extent, FramebufferAttachment, Layout, SubresourceRange, Usage as ImgUsage,
+ ViewCapabilities,
+ },
+ memory::{Barrier, Dependencies},
+ pso::{PipelineStage, Viewport},
+ window::{CompositeAlphaMode, Extent2D, PresentMode, SwapchainConfig},
+};
+
+use super::draw_passes::DrawPass;
+use crate::{error::EnvironmentError, types::*};
+use anyhow::{Context, Result};
+use stockton_types::Session;
+
+#[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,
+ pub image_count: u32,
+}
+
+impl SwapchainProperties {
+ pub fn find_best(
+ adapter: &Adapter,
+ surface: &SurfaceT,
+ ) -> Result<SwapchainProperties, EnvironmentError> {
+ 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 = match formats {
+ Some(formats) => formats
+ .iter()
+ .find(|format| format.base_format().1 == ChannelType::Srgb)
+ .copied()
+ .ok_or(EnvironmentError::ColorFormat),
+ None => Ok(Format::Rgba8Srgb),
+ }?;
+
+ let depth_format = *[
+ Format::D32SfloatS8Uint,
+ Format::D24UnormS8Uint,
+ Format::D32Sfloat,
+ ]
+ .iter()
+ .find(|format| {
+ format.is_depth()
+ && adapter
+ .physical_device
+ .format_properties(Some(**format))
+ .optimal_tiling
+ .contains(ImageFeature::DEPTH_STENCIL_ATTACHMENT)
+ })
+ .ok_or(EnvironmentError::DepthFormat)?;
+
+ let present_mode = [
+ PresentMode::MAILBOX,
+ PresentMode::FIFO,
+ PresentMode::RELAXED,
+ PresentMode::IMMEDIATE,
+ ]
+ .iter()
+ .cloned()
+ .find(|pm| caps.present_modes.contains(*pm))
+ .ok_or(EnvironmentError::PresentMode)?;
+
+ let composite_alpha_mode = [
+ CompositeAlphaMode::OPAQUE,
+ CompositeAlphaMode::INHERIT,
+ CompositeAlphaMode::PREMULTIPLIED,
+ CompositeAlphaMode::POSTMULTIPLIED,
+ ]
+ .iter()
+ .cloned()
+ .find(|ca| caps.composite_alpha_modes.contains(*ca))
+ .ok_or(EnvironmentError::CompositeAlphaMode)?;
+
+ 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,
+ 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))
+ },
+ })
+ }
+
+ pub fn framebuffer_attachment(&self) -> FramebufferAttachment {
+ FramebufferAttachment {
+ usage: ImgUsage::COLOR_ATTACHMENT,
+ format: self.format,
+ view_caps: ViewCapabilities::empty(),
+ }
+ }
+}
+
+pub struct TargetChain {
+ /// Surface we're targeting
+ surface: ManuallyDrop<SurfaceT>,
+ properties: SwapchainProperties,
+
+ /// Resources tied to each target frame in the swapchain
+ targets: Box<[TargetResources]>,
+
+ /// Sync objects used in drawing
+ /// These are seperated from the targets because we don't necessarily always match up indexes
+ sync_objects: Box<[SyncObjects]>,
+
+ /// The last set of sync objects used
+ last_syncs: usize,
+
+ /// Last image index of the swapchain drawn to
+ last_image: u32,
+}
+
+impl TargetChain {
+ pub fn new(
+ device: &mut DeviceT,
+ adapter: &Adapter,
+ mut surface: SurfaceT,
+ cmd_pool: &mut CommandPoolT,
+ properties: SwapchainProperties,
+ ) -> Result<TargetChain> {
+ 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,
+ };
+
+ let _fat = swap_config.framebuffer_attachment();
+ let mut targets: Vec<TargetResources> =
+ Vec::with_capacity(swap_config.image_count as usize);
+ let mut sync_objects: Vec<SyncObjects> =
+ Vec::with_capacity(swap_config.image_count as usize);
+
+ for _ in 0..swap_config.image_count {
+ targets.push(
+ TargetResources::new(device, cmd_pool, &properties)
+ .context("Error creating target resources")?,
+ );
+
+ sync_objects.push(SyncObjects::new(device).context("Error creating sync objects")?);
+ }
+
+ // Configure Swapchain
+ unsafe {
+ surface
+ .configure_swapchain(device, swap_config)
+ .context("Error configuring swapchain")?;
+ }
+
+ Ok(TargetChain {
+ surface: ManuallyDrop::new(surface),
+ targets: targets.into_boxed_slice(),
+ sync_objects: sync_objects.into_boxed_slice(),
+ properties,
+ last_syncs: (image_count - 1) as usize, // This means the next one to be used is index 0
+ last_image: 0,
+ })
+ }
+
+ pub fn deactivate(
+ self,
+ instance: &mut InstanceT,
+ device: &mut DeviceT,
+ cmd_pool: &mut CommandPoolT,
+ ) {
+ let surface = self.deactivate_with_recyling(device, cmd_pool);
+
+ unsafe {
+ instance.destroy_surface(surface);
+ }
+ }
+
+ pub fn deactivate_with_recyling(
+ mut self,
+ device: &mut DeviceT,
+ cmd_pool: &mut CommandPoolT,
+ ) -> SurfaceT {
+ use core::ptr::read;
+ unsafe {
+ for i in 0..self.targets.len() {
+ read(&self.targets[i]).deactivate(device, cmd_pool);
+ }
+
+ for i in 0..self.sync_objects.len() {
+ read(&self.sync_objects[i]).deactivate(device);
+ }
+
+ self.surface.unconfigure_swapchain(device);
+ }
+
+ unsafe { ManuallyDrop::into_inner(read(&self.surface)) }
+ }
+
+ pub fn do_draw_with<'a, DP: DrawPass>(
+ &'a mut self,
+ device: &mut DeviceT,
+ command_queue: &mut QueueT,
+ dp: &mut DP,
+ session: &Session,
+ ) -> Result<()> {
+ self.last_syncs = (self.last_syncs + 1) % self.sync_objects.len();
+ self.last_image = (self.last_image + 1) % self.targets.len() as u32;
+
+ let syncs = &mut self.sync_objects[self.last_syncs];
+ let target = &mut self.targets[self.last_image as usize];
+
+ // Get the image
+ let (img, _) = unsafe {
+ self.surface
+ .acquire_image(core::u64::MAX)
+ .context("Error getting image from swapchain")?
+ };
+
+ // Make sure whatever was last using this has finished
+ unsafe {
+ device
+ .wait_for_fence(&syncs.present_complete, core::u64::MAX)
+ .context("Error waiting for present_complete")?;
+ device
+ .reset_fence(&mut syncs.present_complete)
+ .context("Error resetting present_complete fence")?;
+ };
+
+ // Record commands
+ unsafe {
+ target.cmd_buffer.begin_primary(CommandBufferFlags::empty());
+
+ target.cmd_buffer.pipeline_barrier(
+ PipelineStage::TOP_OF_PIPE..PipelineStage::TOP_OF_PIPE,
+ Dependencies::empty(),
+ once(Barrier::Image {
+ states: (Access::empty(), Layout::Undefined)
+ ..(Access::empty(), Layout::ColorAttachmentOptimal),
+ target: img.borrow(),
+ range: SubresourceRange {
+ aspects: Aspects::COLOR,
+ level_start: 0,
+ level_count: Some(1),
+ layer_start: 0,
+ layer_count: Some(1),
+ },
+ families: None,
+ }),
+ );
+
+ dp.queue_draw(session, img.borrow(), &mut target.cmd_buffer)
+ .context("Error in draw pass")?;
+
+ target.cmd_buffer.pipeline_barrier(
+ PipelineStage::BOTTOM_OF_PIPE..PipelineStage::BOTTOM_OF_PIPE,
+ Dependencies::empty(),
+ once(Barrier::Image {
+ states: (Access::empty(), Layout::ColorAttachmentOptimal)
+ ..(Access::empty(), Layout::Present),
+ target: img.borrow(),
+ range: SubresourceRange {
+ aspects: Aspects::COLOR,
+ level_start: 0,
+ level_count: Some(1),
+ layer_start: 0,
+ layer_count: Some(1),
+ },
+ families: None,
+ }),
+ );
+
+ target.cmd_buffer.finish();
+ }
+
+ // Submit it
+ unsafe {
+ command_queue.submit(
+ once(&*target.cmd_buffer),
+ empty(),
+ once(&*syncs.render_complete),
+ Some(&mut syncs.present_complete),
+ );
+ command_queue
+ .present(&mut self.surface, img, Some(&mut *syncs.render_complete))
+ .context("Error presenting to surface")?;
+ };
+
+ Ok(())
+ }
+
+ /// Get a reference to the target chain's properties.
+ pub fn properties(&self) -> &SwapchainProperties {
+ &self.properties
+ }
+}
+
+/// Resources for a single target frame, including sync objects
+pub struct TargetResources {
+ /// Command buffer to use when drawing
+ pub cmd_buffer: ManuallyDrop<CommandBufferT>,
+}
+
+impl TargetResources {
+ pub fn new(
+ _device: &mut DeviceT,
+ cmd_pool: &mut CommandPoolT,
+ _properties: &SwapchainProperties,
+ ) -> Result<TargetResources> {
+ // Command Buffer
+ let cmd_buffer = unsafe { cmd_pool.allocate_one(hal::command::Level::Primary) };
+
+ Ok(TargetResources {
+ cmd_buffer: ManuallyDrop::new(cmd_buffer),
+ })
+ }
+
+ pub fn deactivate(self, _device: &mut DeviceT, cmd_pool: &mut CommandPoolT) {
+ use core::ptr::read;
+ unsafe {
+ cmd_pool.free(once(ManuallyDrop::into_inner(read(&self.cmd_buffer))));
+ }
+ }
+}
+
+pub struct SyncObjects {
+ /// Triggered when rendering is done
+ pub render_complete: ManuallyDrop<SemaphoreT>,
+
+ /// Triggered when the image is on screen
+ pub present_complete: ManuallyDrop<FenceT>,
+}
+
+impl SyncObjects {
+ pub fn new(device: &mut DeviceT) -> Result<Self> {
+ // Sync objects
+ let render_complete = device
+ .create_semaphore()
+ .context("Error creating render_complete semaphore")?;
+ let present_complete = device
+ .create_fence(true)
+ .context("Error creating present_complete fence")?;
+
+ Ok(SyncObjects {
+ render_complete: ManuallyDrop::new(render_complete),
+ present_complete: ManuallyDrop::new(present_complete),
+ })
+ }
+
+ pub fn deactivate(self, device: &mut DeviceT) {
+ use core::ptr::read;
+
+ unsafe {
+ device.destroy_semaphore(ManuallyDrop::into_inner(read(&self.render_complete)));
+ device.destroy_fence(ManuallyDrop::into_inner(read(&self.present_complete)));
+ }
+ }
+}