diff options
author | tcmal <me@aria.rip> | 2024-08-25 17:44:24 +0100 |
---|---|---|
committer | tcmal <me@aria.rip> | 2024-08-25 17:44:24 +0100 |
commit | 4f068467c4954fb79e6ce297ae1ac0fdd2bdf16a (patch) | |
tree | 4b3ec44d068f71ab80e8c85a6515a8862407ac56 | |
parent | 5e6396ed225be9a9991705de10174b3cf085f8f0 (diff) |
WIP refactor(skeleton): type state for context
also some api improvements
closes #2
related: #7
-rw-r--r-- | examples/render-quad/src/main.rs | 50 | ||||
-rw-r--r-- | stockton-render/src/camera.rs | 62 | ||||
-rw-r--r-- | stockton-render/src/level.rs | 77 | ||||
-rw-r--r-- | stockton-render/src/lib.rs | 1 | ||||
-rw-r--r-- | stockton-render/src/ui.rs | 9 | ||||
-rw-r--r-- | stockton-render/src/window.rs | 7 | ||||
-rw-r--r-- | stockton-skeleton/src/buffers/image.rs | 16 | ||||
-rw-r--r-- | stockton-skeleton/src/buffers/staged.rs | 4 | ||||
-rw-r--r-- | stockton-skeleton/src/buffers/staging.rs | 4 | ||||
-rw-r--r-- | stockton-skeleton/src/context.rs | 347 | ||||
-rw-r--r-- | stockton-skeleton/src/draw_passes/mod.rs | 6 | ||||
-rw-r--r-- | stockton-skeleton/src/lib.rs | 70 | ||||
-rw-r--r-- | stockton-skeleton/src/mem.rs | 21 | ||||
-rw-r--r-- | stockton-skeleton/src/queue_negotiator.rs | 12 | ||||
-rw-r--r-- | stockton-skeleton/src/texture/loader.rs | 16 | ||||
-rw-r--r-- | stockton-skeleton/src/texture/repo.rs | 8 | ||||
-rw-r--r-- | stockton-types/src/components/mod.rs | 14 |
17 files changed, 407 insertions, 317 deletions
diff --git a/examples/render-quad/src/main.rs b/examples/render-quad/src/main.rs index 618779e..45c469a 100644 --- a/examples/render-quad/src/main.rs +++ b/examples/render-quad/src/main.rs @@ -19,7 +19,6 @@ use stockton_levels::{ types::Rgba, }; use stockton_render::{ - camera::calc_vp_matrix_system, level::{LevelDrawPass, LevelDrawPassConfig}, ui::UiDrawPass, window::{process_window_events_system, UiState, WindowEvent, WindowFlow}, @@ -28,7 +27,7 @@ use stockton_skeleton::{ draw_passes::ConsDrawPass, error::full_error_display, texture::resolver::FsResolver, Renderer, }; use stockton_types::{ - components::{CameraSettings, CameraVPMatrix, Transform}, + components::{CameraSettings, Transform}, Session, Vector2, Vector3, }; @@ -162,15 +161,10 @@ fn try_main() -> Result<()> { let mut session = Session::new(move |schedule| { schedule .add_system(update_deltatime_system()) - .add_system(process_window_events_system::< - MovementInputsManager, - Dp<'static>, - >()) + .add_system(process_window_events_system::<MovementInputsManager>()) .flush() .add_system(hello_world_system()) - .add_system(flycam_move_system::<MovementInputsManager>()) - .flush() - .add_thread_local(calc_vp_matrix_system::<Dp<'static>>()); + .add_system(flycam_move_system::<MovementInputsManager>()); }); session.resources.insert(map.clone()); @@ -190,7 +184,6 @@ fn try_main() -> Result<()> { fov: 90.0, near: 0.1, }, - CameraVPMatrix::default(), FlycamControlled::new(512.0, 400.0), )); @@ -217,7 +210,7 @@ fn try_main() -> Result<()> { ui.populate_initial_state(&renderer); } - session.resources.insert(renderer); + let mut renderer = Some(renderer); // Done loading - This is our main loop. // It just communicates events to the session and continuously ticks @@ -228,15 +221,38 @@ fn try_main() -> Result<()> { } Event::RedrawRequested(_) => { session.do_update(); - let mut renderer = session - .resources - .get_mut::<Renderer<Dp<'static>>>() - .unwrap(); - renderer.render(&session).unwrap(); + let r = renderer.take().unwrap(); + match r.render(&session) { + Ok(r) => { + renderer = Some(r); + } + Err(e) => { + println!("Error drawing: {}", full_error_display(e)); + + // TODO: Not really sound + *(new_control_flow.write().unwrap()) = ControlFlow::Exit; + } + } } _ => { if let Some(we) = WindowEvent::from(&event) { - tx.send(we).unwrap() + tx.send(we).unwrap(); + + println!("{:?}", we); + if let WindowEvent::SizeChanged(_, _) = we { + let r = renderer.take().unwrap(); + match r.recreate_surface(&session) { + Ok(r) => { + renderer = Some(r); + } + Err(e) => { + println!("Error resizing: {}", full_error_display(e)); + + // TODO: Not really sound + *(new_control_flow.write().unwrap()) = ControlFlow::Exit; + } + } + } } } } diff --git a/stockton-render/src/camera.rs b/stockton-render/src/camera.rs deleted file mode 100644 index 6abc183..0000000 --- a/stockton-render/src/camera.rs +++ /dev/null @@ -1,62 +0,0 @@ -//! Things related to converting 3D world space to 2D screen space - -use legion::maybe_changed; -use na::{look_at_lh, perspective_lh_zo}; -use stockton_types::{ - components::{CameraSettings, CameraVPMatrix, Transform}, - Vector3, -}; - -use stockton_skeleton::{ - draw_passes::{DrawPass, Singular}, - Renderer, -}; - -fn euler_to_direction(euler: &Vector3) -> Vector3 { - let pitch = euler.x; - let yaw = euler.y; - let _roll = euler.z; // TODO: Support camera roll - - Vector3::new( - yaw.sin() * pitch.cos(), - pitch.sin(), - yaw.cos() * pitch.cos(), - ) -} - -#[system(for_each)] -#[filter(maybe_changed::<Transform>() | maybe_changed::<CameraSettings>())] -pub fn calc_vp_matrix<DP: DrawPass<Singular> + 'static>( - transform: &Transform, - settings: &CameraSettings, - matrix: &mut CameraVPMatrix, - #[resource] renderer: &Renderer<DP>, -) { - // Get look direction from euler angles - let direction = euler_to_direction(&transform.rotation); - - // Converts world space to camera space - let view_matrix = look_at_lh( - &transform.position, - &(transform.position + direction), - &Vector3::new(0.0, 1.0, 0.0), - ); - - // Converts camera space to screen space - let projection_matrix = { - let mut temp = perspective_lh_zo( - renderer.get_aspect_ratio(), - settings.fov, - settings.near, - settings.far, - ); - - // Vulkan's co-ord system is different from OpenGLs - temp[(1, 1)] *= -1.0; - - temp - }; - - // Chain them together into a single matrix - matrix.vp_matrix = projection_matrix * view_matrix; -} diff --git a/stockton-render/src/level.rs b/stockton-render/src/level.rs index 3851cee..29ab7c5 100644 --- a/stockton-render/src/level.rs +++ b/stockton-render/src/level.rs @@ -22,18 +22,10 @@ use stockton_skeleton::{ types::*, }; use stockton_types::{ - components::{CameraSettings, CameraVPMatrix, Transform}, + components::{CameraSettings, Transform}, *, }; -use std::{ - array::IntoIter, - convert::TryInto, - iter::{empty, once}, - marker::PhantomData, - sync::{Arc, RwLock}, -}; - use anyhow::{Context, Result}; use hal::{ buffer::SubRange, @@ -48,7 +40,15 @@ use hal::{ }, }; use legion::{Entity, IntoQuery}; +use na::{look_at_lh, perspective_lh_zo}; use shaderc::ShaderKind; +use std::{ + array::IntoIter, + convert::TryInto, + iter::{empty, once}, + marker::PhantomData, + sync::{Arc, RwLock}, +}; use thiserror::Error; /// The Vertexes that go to the shader @@ -90,10 +90,43 @@ where .record_commit_cmds(cmd_buffer)?; // Get level & camera - let mut query = <(&Transform, &CameraSettings, &CameraVPMatrix)>::query(); - let (camera_transform, camera_settings, camera_vp) = query + let mut query = <(&Transform, &CameraSettings)>::query(); + let (camera_transform, camera_settings) = query .get(&session.world, self.active_camera) .context("Couldn't find camera components")?; + + let camera_vp = { + let aspect_ratio = + self.pipeline.render_area.w as f32 / self.pipeline.render_area.h as f32; + + // Get look direction from euler angles + let direction = euler_to_direction(&camera_transform.rotation); + + // Converts world space to camera space + let view_matrix = look_at_lh( + &camera_transform.position, + &(camera_transform.position + direction), + &Vector3::new(0.0, 1.0, 0.0), + ); + + // Converts camera space to screen space + let projection_matrix = { + let mut temp = perspective_lh_zo( + aspect_ratio, + camera_settings.fov, + camera_settings.near, + camera_settings.far, + ); + + // Vulkan's co-ord system is different from OpenGLs + temp[(1, 1)] *= -1.0; + + temp + }; + + // Chain them together into a single matrix + projection_matrix * view_matrix + }; let map_lock: Arc<RwLock<M>> = session.resources.get::<Arc<RwLock<M>>>().unwrap().clone(); let map = map_lock.read().map_err(|_| LockPoisoned::Map)?; @@ -131,7 +164,7 @@ where cmd_buffer.bind_graphics_pipeline(&self.pipeline.pipeline); // VP Matrix - let vp = &*(camera_vp.vp_matrix.data.as_slice() as *const [f32] as *const [u32]); + let vp = &*(camera_vp.data.as_slice() as *const [f32] as *const [u32]); cmd_buffer.push_graphics_constants( &self.pipeline.pipeline_layout, @@ -238,7 +271,7 @@ where fn deactivate(self, context: &mut RenderingContext) -> Result<()> { self.draw_buffers.deactivate(context); unsafe { - let mut device = context.device().write().map_err(|_| LockPoisoned::Device)?; + let mut device = context.lock_device()?; self.pipeline.deactivate(&mut device); for fb in self.framebuffers.dissolve() { device.destroy_framebuffer(fb); @@ -402,7 +435,7 @@ where let draw_buffers = DrawBuffers::from_context(context).context("Error creating draw buffers")?; let (pipeline, framebuffers) = { - let mut device = context.device().write().map_err(|_| LockPoisoned::Device)?; + let mut device = context.lock_device()?; let pipeline = spec .build( &mut device, @@ -457,8 +490,8 @@ where }) } - fn find_aux_queues<'c>( - adapter: &'c Adapter, + fn find_aux_queues( + adapter: &Adapter, queue_negotiator: &mut QueueFamilyNegotiator, ) -> Result<()> { queue_negotiator.find(adapter, &TexLoadQueue, 1)?; @@ -473,3 +506,15 @@ pub enum LevelError { #[error("Referential Integrity broken")] BadReference, } + +fn euler_to_direction(euler: &Vector3) -> Vector3 { + let pitch = euler.x; + let yaw = euler.y; + let _roll = euler.z; // TODO: Support camera roll + + Vector3::new( + yaw.sin() * pitch.cos(), + pitch.sin(), + yaw.cos() * pitch.cos(), + ) +} diff --git a/stockton-render/src/lib.rs b/stockton-render/src/lib.rs index e3e5bf8..34f5117 100644 --- a/stockton-render/src/lib.rs +++ b/stockton-render/src/lib.rs @@ -3,7 +3,6 @@ extern crate legion; extern crate gfx_hal as hal; extern crate nalgebra_glm as na; -pub mod camera; pub mod level; pub mod ui; pub mod window; diff --git a/stockton-render/src/ui.rs b/stockton-render/src/ui.rs index f688f42..474c1dd 100644 --- a/stockton-render/src/ui.rs +++ b/stockton-render/src/ui.rs @@ -9,7 +9,6 @@ use stockton_skeleton::{ }, context::RenderingContext, draw_passes::{util::TargetSpecificResources, DrawPass, IntoDrawPass, PassPosition}, - error::LockPoisoned, mem::{DataPool, StagingPool, TexturesPool}, queue_negotiator::QueueFamilyNegotiator, texture::{ @@ -201,7 +200,7 @@ impl<'a, P: PassPosition> DrawPass<P> for UiDrawPass<'a> { self.draw_buffers.deactivate(context); unsafe { - let mut device = context.device().write().map_err(|_| LockPoisoned::Device)?; + let mut device = context.lock_device()?; self.pipeline.deactivate(&mut device); for fb in self.framebuffers.dissolve() { device.destroy_framebuffer(fb); @@ -307,7 +306,7 @@ impl<'a, P: PassPosition> IntoDrawPass<UiDrawPass<'a>, P> for () { DrawBuffers::from_context(context).context("Error creating draw buffers")?; let (pipeline, framebuffers) = { - let mut device = context.device().write().map_err(|_| LockPoisoned::Device)?; + let mut device = context.lock_device()?; let pipeline = spec .build( @@ -339,8 +338,8 @@ impl<'a, P: PassPosition> IntoDrawPass<UiDrawPass<'a>, P> for () { }) } - fn find_aux_queues<'c>( - adapter: &'c Adapter, + fn find_aux_queues( + adapter: &Adapter, queue_negotiator: &mut QueueFamilyNegotiator, ) -> Result<()> { queue_negotiator.find(adapter, &TexLoadQueue, 1)?; diff --git a/stockton-render/src/window.rs b/stockton-render/src/window.rs index e6cf7fb..429a3c2 100644 --- a/stockton-render/src/window.rs +++ b/stockton-render/src/window.rs @@ -216,7 +216,7 @@ impl WindowFlow { #[system] /// A system to process the window events sent to renderer by the winit event loop. -pub fn _process_window_events<T: 'static + InputManager, DP: 'static + DrawPass<Singular>>( +pub fn _process_window_events<T: 'static + InputManager>( #[resource] window_channel: &mut WindowFlow, #[resource] manager: &mut T, #[resource] mouse: &mut Mouse, @@ -273,7 +273,6 @@ pub fn _process_window_events<T: 'static + InputManager, DP: 'static + DrawPass< manager.handle_frame(&actions_buf[0..actions_buf_cursor]); } -pub fn process_window_events_system<T: 'static + InputManager, DP: 'static + DrawPass<Singular>>( -) -> impl Runnable { - _process_window_events_system::<T, DP>(Vec::with_capacity(4)) +pub fn process_window_events_system<T: 'static + InputManager>() -> impl Runnable { + _process_window_events_system::<T>(Vec::with_capacity(4)) } diff --git a/stockton-skeleton/src/buffers/image.rs b/stockton-skeleton/src/buffers/image.rs index 4278585..820561f 100644 --- a/stockton-skeleton/src/buffers/image.rs +++ b/stockton-skeleton/src/buffers/image.rs @@ -73,7 +73,7 @@ impl<P: MemoryPool> BoundImageView<P> { .write() .map_err(|_| LockPoisoned::MemoryPool)?; - let mut device = context.device().write().map_err(|_| LockPoisoned::Device)?; + let mut device = context.lock_device()?; let row_alignment_mask = context .physical_device_properties() .limits @@ -149,11 +149,7 @@ impl<P: MemoryPool> BoundImageView<P> { /// Destroy all vulkan objects. Must be called before dropping. pub fn deactivate_with_context(self, context: &mut RenderingContext) { - let mut device = context - .device() - .write() - .map_err(|_| LockPoisoned::Device) - .unwrap(); + let mut device = context.lock_device().unwrap(); let mut pool = context .existing_memory_pool::<P>() .unwrap() @@ -227,7 +223,7 @@ impl<P: MemoryPool> SampledImage<P> { .write() .map_err(|_| LockPoisoned::MemoryPool)?; - let mut device = context.device().write().map_err(|_| LockPoisoned::Device)?; + let mut device = context.lock_device()?; let row_alignment_mask = context .physical_device_properties() .limits @@ -267,11 +263,7 @@ impl<P: MemoryPool> SampledImage<P> { /// Destroy all vulkan objects. Must be called before dropping. pub fn deactivate_with_context(self, context: &mut RenderingContext) { - let mut device = context - .device() - .write() - .map_err(|_| LockPoisoned::Device) - .unwrap(); + let mut device = context.lock_device().unwrap(); let mut pool = context .existing_memory_pool::<P>() .unwrap() diff --git a/stockton-skeleton/src/buffers/staged.rs b/stockton-skeleton/src/buffers/staged.rs index ec42102..2ece045 100644 --- a/stockton-skeleton/src/buffers/staged.rs +++ b/stockton-skeleton/src/buffers/staged.rs @@ -56,7 +56,7 @@ where context.ensure_memory_pool::<SP>()?; // Lock the device and memory pools - let mut device = context.device().write().map_err(|_| LockPoisoned::Device)?; + let mut device = context.lock_device()?; let mut mempool = context .existing_memory_pool::<P>() .unwrap() @@ -111,7 +111,7 @@ where /// Destroy all Vulkan objects. Should be called before dropping. pub fn deactivate(mut self, context: &mut RenderingContext) { unsafe { - let device = &mut *context.device().write().unwrap(); + let device = &mut *context.lock_device().unwrap(); self.staged_memory.unmap(device).unwrap(); diff --git a/stockton-skeleton/src/buffers/staging.rs b/stockton-skeleton/src/buffers/staging.rs index 44d0c2d..35cac00 100644 --- a/stockton-skeleton/src/buffers/staging.rs +++ b/stockton-skeleton/src/buffers/staging.rs @@ -27,7 +27,7 @@ where pub fn from_context(context: &mut RenderingContext, size: u64) -> Result<Self> { context.ensure_memory_pool::<P>()?; - let mut device = context.device().write().map_err(|_| LockPoisoned::Device)?; + let mut device = context.lock_device()?; let mut mempool = context .existing_memory_pool() .unwrap() @@ -70,7 +70,7 @@ where } pub fn deactivate_context(self, context: &mut RenderingContext) { - let mut device = context.device().write().unwrap(); + let mut device = context.lock_device().unwrap(); let mut mempool = context.existing_memory_pool().unwrap().write().unwrap(); self.deactivate_device_pool(&mut device, &mut mempool) diff --git a/stockton-skeleton/src/context.rs b/stockton-skeleton/src/context.rs index bef9f9d..ea9810f 100644 --- a/stockton-skeleton/src/context.rs +++ b/stockton-skeleton/src/context.rs @@ -4,17 +4,19 @@ use std::{ any::{Any, TypeId}, collections::HashMap, + marker::PhantomData, mem::ManuallyDrop, ptr::read, - sync::{Arc, RwLock}, + sync::{Arc, RwLock, RwLockWriteGuard}, }; -use anyhow::{Context, Result}; +use anyhow::{anyhow, Context, Result}; use hal::{ format::{ChannelType, Format, ImageFeature}, image::{Extent, FramebufferAttachment, Usage, ViewCapabilities}, pool::CommandPoolCreateFlags, pso::Viewport, + queue::QueueFamilyId, window::{CompositeAlphaMode, PresentMode}, PhysicalDeviceProperties, }; @@ -29,17 +31,16 @@ use super::{ }; use crate::{ draw_passes::Singular, - error::{EnvironmentError, LockPoisoned}, + error::{EnvironmentError, LockPoisoned, UsageError}, mem::MemoryPool, - queue_negotiator::QueueFamilyNegotiator, + queue_negotiator::{QueueFamilyNegotiator, QueueFamilySelector, SharedQueue}, types::*, }; use stockton_types::Session; -/// Contains most root vulkan objects, and some precalculated info such as best formats to use. -/// In most cases, this and the DrawPass should contain all vulkan objects present. -pub struct RenderingContext { +/// The actual data behind [`StatefulRenderingContext`] +struct InnerRenderingContext { /// Vulkan Instance instance: ManuallyDrop<back::Instance>, @@ -75,7 +76,43 @@ pub struct RenderingContext { properties: ContextProperties, } -impl RenderingContext { +/// A type enum for different states the `RenderingContext` can be in. +pub trait RenderingContextState: private::Sealed {} + +/// Normal operation. +pub struct Normal; +impl RenderingContextState for Normal {} + +/// The last draw failed, most likely meaning the surface needs re-created, or the entire context is toast. +pub struct LastDrawFailed; +impl RenderingContextState for LastDrawFailed {} + +/// All memory pools have been deactivated. This should only be used when shutting down +pub struct DeactivatedMemoryPools; +impl RenderingContextState for DeactivatedMemoryPools {} + +/// Seal `RenderingContextState` +mod private { + pub trait Sealed {} + impl Sealed for super::Normal {} + impl Sealed for super::LastDrawFailed {} + impl Sealed for super::DeactivatedMemoryPools {} +} + +/// Contains most root vulkan objects, and some precalculated info such as best formats to use. +/// In most cases, this and the DrawPass should contain all Vulkan objects present. +/// [`RenderingContext`] is a convenience type that applies in most situations. +pub struct StatefulRenderingContext<S: RenderingContextState>( + /// The actual data. This is boxed so that there's less overhead when transitioning type state. + Box<InnerRenderingContext>, + PhantomData<S>, +); + +/// Convenience type, since we often want to refer to normal operation +pub type RenderingContext = StatefulRenderingContext<Normal>; + +/// Methods only implemented in normal operation +impl StatefulRenderingContext<Normal> { /// Create a new RenderingContext for the given window. pub fn new<IDP: IntoDrawPass<DP, Singular>, DP: DrawPass<Singular>>( window: &Window, @@ -96,7 +133,7 @@ impl RenderingContext { let adapter = adapters.remove(0); // Queue Negotiator - let (queue_negotiator, surface) = { + let (family_negotiator, surface) = { let dq: DrawQueue = DrawQueue { surface }; let mut qn = QueueFamilyNegotiator::new(); @@ -117,7 +154,7 @@ impl RenderingContext { // TODO: This sucks, but hal is restrictive on how we can pass this specific argument. // Deduplicate families & convert to specific type. - let open_spec = queue_negotiator.get_open_spec(&adapter); + let open_spec = family_negotiator.get_open_spec(&adapter); let gpu = unsafe { adapter @@ -129,143 +166,108 @@ impl RenderingContext { (Arc::new(RwLock::new(gpu.device)), gpu.queue_groups) }; - let mut queue_negotiator = queue_negotiator.finish(queue_groups); + let mut queue_negotiator = family_negotiator.finish(queue_groups); // Context properties let properties = ContextProperties::find_best(&adapter, &surface) .context("Error getting context properties")?; - // Lock device - let mut device = device_lock - .write() - .map_err(|_| LockPoisoned::Device) - .context("Error getting device lock")?; - - debug!("Detected swapchain properties: {:?}", properties); - - // Command pool - let mut cmd_pool = unsafe { - device.create_command_pool( - queue_negotiator - .family::<DrawQueue>() - .ok_or(EnvironmentError::NoSuitableFamilies)?, - CommandPoolCreateFlags::RESET_INDIVIDUAL, - ) - } - .context("Error creating draw command pool")?; - - // Swapchain and associated resources - let target_chain = TargetChain::new(&mut device, surface, &mut cmd_pool, &properties) - .context("Error creating target chain")?; - - // Unlock device - drop(device); + debug!("Detected context properties: {:?}", properties); + + let (cmd_pool, target_chain) = { + // Lock device + let mut device = device_lock + .write() + .map_err(|_| LockPoisoned::Device) + .context("Error getting device lock")?; + + // Command pool + let mut cmd_pool = unsafe { + device.create_command_pool( + queue_negotiator + .family::<DrawQueue>() + .ok_or(EnvironmentError::NoSuitableFamilies)?, + CommandPoolCreateFlags::RESET_INDIVIDUAL, + ) + } + .context("Error creating draw command pool")?; + + // Swapchain and associated resources + let target_chain = TargetChain::new(&mut device, surface, &mut cmd_pool, &properties) + .context("Error creating target chain")?; + + (cmd_pool, target_chain) + }; let queue = queue_negotiator .get_queue::<DrawQueue>() .context("Error getting draw queue")?; - Ok(RenderingContext { - instance: ManuallyDrop::new(instance), + Ok(StatefulRenderingContext( + Box::new(InnerRenderingContext { + instance: ManuallyDrop::new(instance), - device: device_lock, - physical_device_properties: adapter.physical_device.properties(), - adapter, + device: device_lock, + physical_device_properties: adapter.physical_device.properties(), + adapter, - queue_negotiator, - queue, + queue_negotiator, + queue, - target_chain: ManuallyDrop::new(target_chain), - cmd_pool: ManuallyDrop::new(cmd_pool), + target_chain: ManuallyDrop::new(target_chain), + cmd_pool: ManuallyDrop::new(cmd_pool), - pixels_per_point: window.scale_factor() as f32, - memory_pools: HashMap::new(), - properties, - }) + pixels_per_point: window.scale_factor() as f32, + memory_pools: HashMap::new(), + properties, + }), + PhantomData, + )) } - /// If this function fails the whole context is probably dead - /// # Safety - /// The context must not be used while this is being called - pub unsafe fn handle_surface_change(&mut self) -> Result<()> { - let mut device = self - .device - .write() - .map_err(|_| LockPoisoned::Device) - .context("Error getting device lock")?; - - device - .wait_idle() - .context("Error waiting for device to become idle")?; - - let surface = ManuallyDrop::into_inner(read(&self.target_chain)) - .deactivate_with_recyling(&mut device, &mut self.cmd_pool); - - self.properties = ContextProperties::find_best(&self.adapter, &surface) - .context("Error finding best swapchain properties")?; - - self.target_chain = ManuallyDrop::new( - TargetChain::new(&mut device, surface, &mut self.cmd_pool, &self.properties) - .context("Error creating target chain")?, - ); - Ok(()) + /// Draw onto the next frame of the swapchain. + /// This takes ownership so we can transition to `LastDrawFailed` if an error occurs. + /// If it does, you can try to recover with [`StatefulRenderingContext::attempt_recovery`] + pub fn draw_next_frame<DP: DrawPass<Singular>>( + mut self, + session: &Session, + dp: &mut DP, + ) -> Result<RenderingContext, (anyhow::Error, StatefulRenderingContext<LastDrawFailed>)> { + if let Err(e) = self.attempt_draw_next_frame(session, dp) { + Err((e, StatefulRenderingContext(self.0, PhantomData))) + } else { + Ok(self) + } } - /// Draw onto the next frame of the swapchain - pub fn draw_next_frame<DP: DrawPass<Singular>>( + /// The actual drawing attempt + fn attempt_draw_next_frame<DP: DrawPass<Singular>>( &mut self, session: &Session, dp: &mut DP, ) -> Result<()> { + // Lock device & queue. We can't use our nice convenience function, because of borrowing issues let mut device = self + .0 .device .write() .map_err(|_| LockPoisoned::Device) .context("Error getting device lock")?; let mut queue = self + .0 .queue .write() .map_err(|_| LockPoisoned::Queue) .context("Error getting draw queue lock")?; - // Level draw pass - self.target_chain + self.0 + .target_chain .do_draw_with(&mut device, &mut queue, dp, session) .context("Error preparing next target")?; Ok(()) } - /// Get a reference to the rendering context's pixels per point. - pub fn pixels_per_point(&self) -> f32 { - self.pixels_per_point - } - - /// Get a reference to the rendering context's device. - pub fn device(&self) -> &Arc<RwLock<DeviceT>> { - &self.device - } - - /// Get a reference to the rendering context's target chain. - pub fn target_chain(&self) -> &TargetChain { - &self.target_chain - } - - /// Get a reference to the rendering context's adapter. - pub fn adapter(&self) -> &Adapter { - &self.adapter - } - - /// Get a mutable reference to the rendering context's queue negotiator. - pub fn queue_negotiator_mut(&mut self) -> &mut QueueNegotiator { - &mut self.queue_negotiator - } - - /// Get a reference to the physical device's properties. - pub fn physical_device_properties(&self) -> &PhysicalDeviceProperties { - &self.physical_device_properties - } - /// Get the specified memory pool, lazily initialising it if it's not yet present pub fn memory_pool<P: MemoryPool>(&mut self) -> Result<&Arc<RwLock<P>>> { self.ensure_memory_pool::<P>()?; @@ -276,8 +278,9 @@ impl RenderingContext { #[allow(clippy::map_entry)] // We can't follow the suggestion because of a borrowing issue pub fn ensure_memory_pool<P: MemoryPool>(&mut self) -> Result<()> { let tid = TypeId::of::<P>(); - if !self.memory_pools.contains_key(&tid) { - self.memory_pools + if !self.0.memory_pools.contains_key(&tid) { + self.0 + .memory_pools .insert(tid, Box::new(P::from_context(self)?)); } Ok(()) @@ -287,36 +290,130 @@ impl RenderingContext { /// You should only use this when you're certain it exists, such as when freeing memory /// allocated from that pool pub fn existing_memory_pool<P: MemoryPool>(&self) -> Option<&Arc<RwLock<P>>> { - self.memory_pools + self.0 + .memory_pools .get(&TypeId::of::<P>()) .map(|x| x.downcast_ref().unwrap()) } + /// Deactivate all stored memory pools. + pub fn deactivate_memory_pools( + self, + ) -> Result<StatefulRenderingContext<DeactivatedMemoryPools>> { + // TODO: Properly deactivate memory pools + + Ok(StatefulRenderingContext(self.0, PhantomData)) + } +} + +impl StatefulRenderingContext<LastDrawFailed> { + /// If this function fails the whole context is probably dead + pub fn attempt_recovery(self) -> Result<RenderingContext> { + let this = self.recreate_surface()?; + Ok(StatefulRenderingContext(this.0, PhantomData)) + } +} + +// Methods implemented for all states +impl<S: RenderingContextState> StatefulRenderingContext<S> { + /// Get the current pixels per point. + pub fn pixels_per_point(&self) -> f32 { + self.0.pixels_per_point + } + + /// Get a new reference to the lock for the device used by this context. + /// This can be used when instantiating code that runs in another thread. + pub fn clone_device_lock(&self) -> Arc<RwLock<DeviceT>> { + self.0.device.clone() + } + + /// Lock the device used by this rendering context + pub fn lock_device(&self) -> Result<RwLockWriteGuard<'_, DeviceT>> { + Ok(self.0.device.write().map_err(|_| LockPoisoned::Device)?) + } + + /// Get a reference to the rendering context's adapter. + pub fn adapter(&self) -> &Adapter { + &self.0.adapter + } + + /// Get a shared queue from the family that was selected with T. + /// You should already have called [`crate::queue_negotiator::QueueFamilyNegotiator::find`], otherwise this will return an error. + pub fn get_queue<T: QueueFamilySelector>(&mut self) -> Result<SharedQueue> { + self.0.queue_negotiator.get_queue::<T>() + } + + /// Get the family that was selected by T. + /// You should already have called [`crate::queue_negotiator::QueueFamilyNegotiator::find`], otherwise this will return an error. + pub fn get_queue_family<T: QueueFamilySelector>(&self) -> Result<QueueFamilyId> { + self.0 + .queue_negotiator + .family::<T>() + .ok_or(anyhow!(UsageError::QueueNegotiatorMisuse)) + } + + /// Get a reference to the physical device's properties. + pub fn physical_device_properties(&self) -> &PhysicalDeviceProperties { + &self.0.physical_device_properties + } /// Get a reference to the rendering context's properties. pub fn properties(&self) -> &ContextProperties { - &self.properties + &self.0.properties } -} -impl core::ops::Drop for RenderingContext { - fn drop(&mut self) { - { - self.device.write().unwrap().wait_idle().unwrap(); + /// Recreate the surface, swapchain, and other derived components. + pub fn recreate_surface(mut self) -> Result<Self> { + // TODO: Deactivate if this fails + unsafe { + let mut device = self + .0 + .device + .write() + .map_err(|_| LockPoisoned::Device) + .context("Error getting device lock")?; + + device + .wait_idle() + .context("Error waiting for device to become idle")?; + + let surface = ManuallyDrop::into_inner(read(&self.0.target_chain)) + .deactivate_with_recyling(&mut device, &mut self.0.cmd_pool); + + self.0.properties = ContextProperties::find_best(&self.0.adapter, &surface) + .context("Error finding best swapchain properties")?; + + // TODO: This is unsound, if we return an error here `self.0.TargetChain` may be accessed again. + self.0.target_chain = ManuallyDrop::new( + TargetChain::new( + &mut device, + surface, + &mut self.0.cmd_pool, + &self.0.properties, + ) + .context("Error creating target chain")?, + ); } - // TODO: Better deactivation code + Ok(StatefulRenderingContext(self.0, PhantomData)) + } +} + +// Methods only implemented after we start deactivating +impl StatefulRenderingContext<DeactivatedMemoryPools> { + pub fn deactivate(mut self) -> Result<()> { + self.lock_device()?.wait_idle()?; + // TODO: The rest of the deactivation code needs updated. unsafe { - let mut device = self.device.write().unwrap(); + let mut device = self.0.device.write().map_err(|_| LockPoisoned::Device)?; - ManuallyDrop::into_inner(read(&self.target_chain)).deactivate( - &mut self.instance, - &mut device, - &mut self.cmd_pool, - ); + let target_chain = ManuallyDrop::take(&mut self.0.target_chain); + target_chain.deactivate(&mut self.0.instance, &mut device, &mut self.0.cmd_pool); - device.destroy_command_pool(ManuallyDrop::into_inner(read(&self.cmd_pool))); + device.destroy_command_pool(ManuallyDrop::into_inner(self.0.cmd_pool)); } + + Ok(()) } } diff --git a/stockton-skeleton/src/draw_passes/mod.rs b/stockton-skeleton/src/draw_passes/mod.rs index d830fe5..cdc983f 100644 --- a/stockton-skeleton/src/draw_passes/mod.rs +++ b/stockton-skeleton/src/draw_passes/mod.rs @@ -1,7 +1,7 @@ //! Traits and common draw passes. use std::ops::Range; -use crate::{queue_negotiator::QueueFamilyNegotiator, types::*, RenderingContext}; +use crate::{context::RenderingContext, queue_negotiator::QueueFamilyNegotiator, types::*}; use hal::{ image::Layout, pass::{AttachmentLoadOp, AttachmentOps, AttachmentStoreOp}, @@ -44,8 +44,8 @@ pub trait IntoDrawPass<T: DrawPass<P>, P: PassPosition> { /// This function should ask the queue negotatior to find families for any auxilary operations this draw pass needs to perform /// For example, .find(&TexLoadQueue) - fn find_aux_queues<'a>( - adapter: &'a Adapter, + fn find_aux_queues( + adapter: &Adapter, queue_negotiator: &mut QueueFamilyNegotiator, ) -> Result<()>; } diff --git a/stockton-skeleton/src/lib.rs b/stockton-skeleton/src/lib.rs index b514531..6260753 100644 --- a/stockton-skeleton/src/lib.rs +++ b/stockton-skeleton/src/lib.rs @@ -18,6 +18,8 @@ pub mod texture; pub mod types; pub mod utils; +use std::mem::ManuallyDrop; + use context::RenderingContext; use draw_passes::{DrawPass, IntoDrawPass, Singular}; @@ -30,7 +32,7 @@ use winit::window::Window; /// Also takes ownership of the window and channels window events to be processed outside winit's event loop. pub struct Renderer<DP> { /// All the vulkan stuff - context: RenderingContext, + context: ManuallyDrop<RenderingContext>, /// The draw pass we're using draw_pass: DP, @@ -50,25 +52,53 @@ impl<DP: DrawPass<Singular>> Renderer<DP> { .init(session, &mut context) .context("Error initialising draw pass")?; - Ok(Renderer { context, draw_pass }) + Ok(Renderer { + context: ManuallyDrop::new(context), + draw_pass, + }) } /// Render a single frame of the given session. - pub fn render(&mut self, session: &Session) -> Result<()> { - // Try to draw - if self - .context - .draw_next_frame(session, &mut self.draw_pass) - .is_err() - { - // Probably the surface changed - self.handle_surface_change(session)?; - - // If it fails twice, then error - self.context.draw_next_frame(session, &mut self.draw_pass)?; + /// If this returns an error, the whole renderer is dead, hence it takes ownership to ensure it can't be called in that case. + pub fn render(mut self, session: &Session) -> Result<Renderer<DP>> { + // Safety: If this fails at any point, the ManuallyDrop won't be touched again, as Renderer will be dropped. + // Hence, we can always take from the ManuallyDrop + unsafe { + match ManuallyDrop::take(&mut self.context) + .draw_next_frame(session, &mut self.draw_pass) + { + Ok(c) => { + self.context = ManuallyDrop::new(c); + Ok(self) + } + Err((_e, c)) => { + // TODO: Try to detect if the error is actually surface related. + let c = c.attempt_recovery()?; + match c.draw_next_frame(session, &mut self.draw_pass) { + Ok(c) => { + self.context = ManuallyDrop::new(c); + Ok(self) + } + Err((e, _c)) => Err(e), + } + } + } + } + } + + /// Recreate the surface, and other derived components. + /// This should be called when the window is resized. + pub fn recreate_surface(mut self, session: &Session) -> Result<Renderer<DP>> { + // Safety: If this fails at any point, the ManuallyDrop won't be touched again, as Renderer will be dropped. + // Hence, we can always take from the ManuallyDrop + unsafe { + let ctx = ManuallyDrop::take(&mut self.context).recreate_surface()?; + self.context = ManuallyDrop::new(ctx); } + self.draw_pass + .handle_surface_change(session, &mut self.context)?; - Ok(()) + Ok(self) } pub fn get_aspect_ratio(&self) -> f32 { @@ -76,16 +106,6 @@ impl<DP: DrawPass<Singular>> Renderer<DP> { e.width as f32 / e.height as f32 } - pub fn handle_surface_change(&mut self, session: &Session) -> Result<()> { - unsafe { - self.context.handle_surface_change()?; - self.draw_pass - .handle_surface_change(session, &mut self.context)?; - } - - Ok(()) - } - /// Get a reference to the renderer's context. pub fn context(&self) -> &RenderingContext { &self.context diff --git a/stockton-skeleton/src/mem.rs b/stockton-skeleton/src/mem.rs index 03d74ea..7c31712 100644 --- a/stockton-skeleton/src/mem.rs +++ b/stockton-skeleton/src/mem.rs @@ -4,7 +4,10 @@ //! using [`RenderingContext.pool_allocator`] //! Alternatively, some default memory pools are availble when the feature `rendy_pools` is used (on by default). -use crate::{context::RenderingContext, types::*}; +use crate::{ + context::{DeactivatedMemoryPools, RenderingContext, StatefulRenderingContext}, + types::*, +}; use std::{ ops::Range, @@ -36,7 +39,7 @@ pub trait MemoryPool: Send + Sync + 'static { fn free(&mut self, device: &DeviceT, block: Self::Block) -> u64; /// Deactivate this memory pool, freeing any allocated memory objects. - fn deactivate(self, context: &mut RenderingContext); + fn deactivate(self, context: &mut StatefulRenderingContext<DeactivatedMemoryPools>); } /// Block that owns a `Range` of the `Memory`. @@ -78,7 +81,7 @@ mod rendy { use super::*; use crate::{ - error::{EnvironmentError, LockPoisoned, UsageError}, + error::{EnvironmentError, UsageError}, utils::find_memory_type_id, }; @@ -128,7 +131,7 @@ mod rendy { // Size and alignment don't necessarily stay the same, so we're forced to // guess at the alignment for our allocator. - let device = context.device().write().map_err(|_| LockPoisoned::Device)?; + let device = context.lock_device()?; let img = device .create_image( Kind::D2(16, 16, 1, 1), @@ -170,7 +173,7 @@ mod rendy { Ok(Arc::new(RwLock::new(Self(allocator)))) } - fn deactivate(self, _context: &mut RenderingContext) { + fn deactivate(self, _context: &mut StatefulRenderingContext<DeactivatedMemoryPools>) { self.0.dispose(); } } @@ -193,7 +196,7 @@ mod rendy { let type_mask = unsafe { use hal::image::{Kind, Tiling, Usage, ViewCapabilities}; - let device = context.device().write().map_err(|_| LockPoisoned::Device)?; + let device = context.lock_device()?; let img = device .create_image( Kind::D2(16, 16, 1, 1), @@ -235,7 +238,7 @@ mod rendy { Ok(Arc::new(RwLock::new(Self(allocator)))) } - fn deactivate(self, _context: &mut RenderingContext) { + fn deactivate(self, _context: &mut StatefulRenderingContext<DeactivatedMemoryPools>) { self.0.dispose() } } @@ -277,7 +280,7 @@ mod rendy { Ok(Arc::new(RwLock::new(StagingPool(allocator)))) } - fn deactivate(self, _context: &mut RenderingContext) { + fn deactivate(self, _context: &mut StatefulRenderingContext<DeactivatedMemoryPools>) { self.0.dispose() } } @@ -318,7 +321,7 @@ mod rendy { Ok(Arc::new(RwLock::new(DataPool(allocator)))) } - fn deactivate(self, _context: &mut RenderingContext) { + fn deactivate(self, _context: &mut StatefulRenderingContext<DeactivatedMemoryPools>) { self.0.dispose() } } diff --git a/stockton-skeleton/src/queue_negotiator.rs b/stockton-skeleton/src/queue_negotiator.rs index b78fe33..765516b 100644 --- a/stockton-skeleton/src/queue_negotiator.rs +++ b/stockton-skeleton/src/queue_negotiator.rs @@ -70,7 +70,7 @@ impl QueueFamilyNegotiator { mut count: usize, ) -> Result<()> { if let Entry::Occupied(e) = self.family_ids.entry(TypeId::of::<T>()) { - count = count + e.get().0; + count += e.get().0; } let candidates: Vec<&QueueFamilyT> = adapter @@ -123,7 +123,7 @@ impl QueueFamilyNegotiator { } /// Finish selecting our queue families, and turn this into a `QueueNegotiator` - pub fn finish<'a>(self, queue_groups: Vec<QueueGroup>) -> QueueNegotiator { + pub fn finish(self, queue_groups: Vec<QueueGroup>) -> QueueNegotiator { QueueNegotiator { family_ids: self.family_ids, already_allocated: HashMap::new(), @@ -132,6 +132,12 @@ impl QueueFamilyNegotiator { } } +impl Default for QueueFamilyNegotiator { + fn default() -> Self { + Self::new() + } +} + /// Used internally in calls to [`hal::adapter::PhysicalDevice::open`] pub(crate) struct AdapterOpenSpec<'a>(Box<[(&'a QueueFamilyT, Vec<f32>)]>); @@ -164,7 +170,7 @@ impl QueueNegotiator { /// You should already have called [`self::QueueFamilyNegotiator::find`], otherwise this will return an error. /// /// The family of the queue returned is guaranteed to meet the spec of the `QueueFamilySelector` originally used by `find`. - pub fn get_queue<T: QueueFamilySelector>(&mut self) -> Result<Arc<RwLock<QueueT>>> { + pub fn get_queue<T: QueueFamilySelector>(&mut self) -> Result<SharedQueue> { let tid = TypeId::of::<T>(); let (_, family_id) = self .family_ids diff --git a/stockton-skeleton/src/texture/loader.rs b/stockton-skeleton/src/texture/loader.rs index 80d4a61..6de4a4d 100644 --- a/stockton-skeleton/src/texture/loader.rs +++ b/stockton-skeleton/src/texture/loader.rs @@ -12,7 +12,7 @@ use super::{ use crate::{ buffers::image::SampledImage, context::RenderingContext, - error::{EnvironmentError, LockPoisoned}, + error::LockPoisoned, mem::{MappableBlock, MemoryPool}, queue_negotiator::QueueFamilySelector, types::*, @@ -216,22 +216,16 @@ where config: TextureLoadConfig<R>, ) -> Result<Self> { // Queue family & Lock - let family = context - .queue_negotiator_mut() - .family::<Q>() - .ok_or(EnvironmentError::NoSuitableFamilies)?; - let queue_lock = context.queue_negotiator_mut().get_queue::<Q>()?; + let family = context.get_queue_family::<Q>()?; + let queue_lock = context.get_queue::<Q>()?; // Memory pools let tex_mempool = context.memory_pool()?.clone(); let staging_mempool = context.memory_pool()?.clone(); // Lock device - let device_lock = context.device().clone(); - let mut device = device_lock - .write() - .map_err(|_| LockPoisoned::Device) - .context("Error getting device lock")?; + let device_lock = context.clone_device_lock(); + let mut device = context.lock_device().context("Error getting device lock")?; // Physical properties let device_props = context.physical_device_properties(); diff --git a/stockton-skeleton/src/texture/repo.rs b/stockton-skeleton/src/texture/repo.rs index 635eebb..4591f17 100644 --- a/stockton-skeleton/src/texture/repo.rs +++ b/stockton-skeleton/src/texture/repo.rs @@ -68,11 +68,7 @@ where // Create Channels let (req_send, req_recv) = channel(); let (resp_send, resp_recv) = channel(); - let device = context - .device() - .write() - .map_err(|_| LockPoisoned::Device) - .context("Error getting device lock")?; + let device = context.lock_device()?; // Create descriptor set layout let ds_lock = Arc::new(RwLock::new( @@ -193,7 +189,7 @@ where .unwrap(); // Only now can we lock device without deadlocking - let mut device = context.device().write().unwrap(); + let mut device = context.lock_device().unwrap(); // Return all the texture memory and descriptors. for (_, v) in self.blocks.drain() { diff --git a/stockton-types/src/components/mod.rs b/stockton-types/src/components/mod.rs index 421cf9c..a90f5e8 100644 --- a/stockton-types/src/components/mod.rs +++ b/stockton-types/src/components/mod.rs @@ -59,17 +59,3 @@ pub struct CameraSettings { /// Far clipping plane (world units) pub far: f32, } - -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct CameraVPMatrix { - /// The camera's VP Matrix - pub vp_matrix: Mat4, -} - -impl Default for CameraVPMatrix { - fn default() -> Self { - CameraVPMatrix { - vp_matrix: Mat4::identity(), - } - } -} |