diff --git a/src/correlation/gpu/metal.rs b/src/correlation/gpu/metal.rs index af6eaff..82bb276 100644 --- a/src/correlation/gpu/metal.rs +++ b/src/correlation/gpu/metal.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, error, ffi::c_void, slice}; +use std::{collections::HashMap, error, ffi::c_void, fmt, slice}; use metal::objc::rc::autoreleasepool; use rayon::iter::ParallelIterator; @@ -8,7 +8,7 @@ use crate::{ data::{Grid, Point2D}, }; -use super::{CorrelationDirection, GpuError, HardwareMode}; +use super::{CorrelationDirection, HardwareMode}; // This is optimized to work with built-in Apple Silicon GPUs. // AMD or built-in Intel GPUs will likely underperform. @@ -54,10 +54,10 @@ pub struct DeviceContext { } impl DeviceContext { - pub fn new(hardware_mode: HardwareMode) -> Result> { + pub fn new(hardware_mode: HardwareMode) -> Result { autoreleasepool(|| { if !matches!(hardware_mode, HardwareMode::Gpu | HardwareMode::GpuLowPower) { - return Err(GpuError::new("GPU mode is not enabled").into()); + return Err("GPU mode is not enabled".into()); }; let low_power = matches!(hardware_mode, HardwareMode::GpuLowPower); let device = Device::new()?; @@ -86,7 +86,7 @@ impl super::DeviceContext for DeviceContext { &mut self, img1_dimensions: (usize, usize), img2_dimensions: (usize, usize), - ) -> Result<(), Box> { + ) -> Result<(), GpuError> { let img1_pixels = img1_dimensions.0 * img1_dimensions.1; let img2_pixels = img2_dimensions.0 * img2_dimensions.1; @@ -103,24 +103,24 @@ impl super::DeviceContext for DeviceContext { fn device(&self) -> Result<&Device, GpuError> { match self.device.as_ref() { Some(device) => Ok(device), - None => Err(GpuError::new("Device not initialized")), + None => Err("Device not initialized".into()), } } fn device_mut(&mut self) -> Result<&mut Device, GpuError> { match self.device.as_mut() { Some(device) => Ok(device), - None => Err(GpuError::new("Device not initialized")), + None => Err("Device not initialized".into()), } } } impl Device { - fn new() -> Result> { + fn new() -> Result { autoreleasepool(|| { let device = match metal::Device::system_default() { Some(device) => device, - None => return Err(GpuError::new("GPU mode is not enabled").into()), + None => return Err("GPU mode is not enabled".into()), }; let direction = CorrelationDirection::Forward; let unified_memory = device.has_unified_memory(); @@ -141,7 +141,7 @@ impl Device { &self, img1_pixels: usize, img2_pixels: usize, - ) -> Result> { + ) -> Result { let max_pixels = img1_pixels.max(img2_pixels); let buffer_img = self.create_buffer( (img1_pixels + img2_pixels) * std::mem::size_of::(), @@ -192,7 +192,7 @@ impl Device { fn buffers(&self) -> Result<&DeviceBuffers, GpuError> { match self.buffers.as_ref() { Some(buffers) => Ok(buffers), - None => Err(GpuError::new("Buffers not initialized")), + None => Err("Buffers not initialized".into()), } } @@ -234,7 +234,7 @@ impl Device { fn create_pipelines( device: &metal::Device, - ) -> Result, Box> { + ) -> Result, GpuError> { autoreleasepool(|| { let mut result = HashMap::new(); let source = include_bytes!("shaders/correlation.metallib"); @@ -297,7 +297,7 @@ impl super::Device for Device { dimensions: (usize, usize), shader_type: ShaderModuleType, shader_params: ShaderParams, - ) -> Result<(), Box> { + ) -> Result<(), GpuError> { let workgroup_size = ((dimensions.0 + 15) / 16, ((dimensions.1 + 15) / 16)); autoreleasepool(|| { let pipeline = self.pipelines.get(&shader_type).unwrap(); @@ -338,11 +338,7 @@ impl super::Device for Device { }) } - unsafe fn transfer_in_images( - &self, - img1: &Grid, - img2: &Grid, - ) -> Result<(), Box> { + unsafe fn transfer_in_images(&self, img1: &Grid, img2: &Grid) -> Result<(), GpuError> { autoreleasepool(|| { let buffers = self.buffers()?; let buffer = &buffers.buffer_img; @@ -365,7 +361,7 @@ impl super::Device for Device { &self, correlation_values: &mut Grid>, correlation_threshold: f32, - ) -> Result<(), Box> { + ) -> Result<(), GpuError> { autoreleasepool(|| { let buffers = self.buffers()?; let buffer = &buffers.buffer_out_corr; @@ -391,7 +387,7 @@ impl super::Device for Device { &self, out_image: &mut Grid>, correlation_values: &Grid>, - ) -> Result<(), Box> { + ) -> Result<(), GpuError> { autoreleasepool(|| { let buffers = self.buffers()?; let buffer = &buffers.buffer_out; @@ -448,7 +444,7 @@ impl ShaderModuleType { Self::CrossCheckFilter, ]; - fn load(&self, library: &metal::Library) -> Result> { + fn load(&self, library: &metal::Library) -> Result { let function_name = match self { ShaderModuleType::InitOutData => "init_out_data", ShaderModuleType::PrepareInitialdataSearchdata => "prepare_initialdata_searchdata", @@ -462,3 +458,39 @@ impl ShaderModuleType { Ok(function) } } + +#[derive(Debug)] +pub enum GpuError { + Internal(&'static str), + Metal(String), +} + +impl fmt::Display for GpuError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + GpuError::Internal(msg) => f.write_str(msg), + GpuError::Metal(ref msg) => f.write_str(msg), + } + } +} + +impl std::error::Error for GpuError { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match *self { + GpuError::Internal(_msg) => None, + GpuError::Metal(ref _msg) => None, + } + } +} + +impl From for GpuError { + fn from(e: String) -> GpuError { + GpuError::Metal(e) + } +} + +impl From<&'static str> for GpuError { + fn from(msg: &'static str) -> GpuError { + GpuError::Internal(msg) + } +} diff --git a/src/correlation/gpu/mod.rs b/src/correlation/gpu/mod.rs index d849302..75cf9a9 100644 --- a/src/correlation/gpu/mod.rs +++ b/src/correlation/gpu/mod.rs @@ -13,9 +13,13 @@ pub type DefaultDeviceContext = metal::DeviceContext; #[cfg(not(target_os = "macos"))] pub type DefaultDeviceContext = vulkan::DeviceContext; +#[cfg(target_os = "macos")] +pub type GpuError = metal::GpuError; +#[cfg(not(target_os = "macos"))] +pub type GpuError = vulkan::GpuError; + use crate::data::Grid; use nalgebra::Matrix3; -use std::{error, fmt}; use crate::correlation::{ CorrelationDirection, CorrelationParameters, HardwareMode, ProjectionMode, CORRIDOR_MIN_RANGE, @@ -38,25 +42,21 @@ trait Device { dimensions: (usize, usize), shader_type: ShaderModuleType, shader_params: ShaderParams, - ) -> Result<(), Box>; + ) -> Result<(), GpuError>; - unsafe fn transfer_in_images( - &self, - img1: &Grid, - img2: &Grid, - ) -> Result<(), Box>; + unsafe fn transfer_in_images(&self, img1: &Grid, img2: &Grid) -> Result<(), GpuError>; unsafe fn save_corr( &self, correlation_values: &mut Grid>, correlation_threshold: f32, - ) -> Result<(), Box>; + ) -> Result<(), GpuError>; unsafe fn save_result( &self, out_image: &mut Grid>, correlation_values: &Grid>, - ) -> Result<(), Box>; + ) -> Result<(), GpuError>; unsafe fn destroy_buffers(&mut self); } @@ -73,7 +73,7 @@ where &mut self, img1_dimensions: (usize, usize), img2_dimensions: (usize, usize), - ) -> Result<(), Box>; + ) -> Result<(), GpuError>; fn device(&self) -> Result<&D, GpuError>; @@ -127,7 +127,7 @@ impl GpuContext<'_> { img2_dimensions: (usize, usize), projection_mode: ProjectionMode, fundamental_matrix: Matrix3, - ) -> Result> { + ) -> Result { let (search_area_segment_length, corridor_segment_length) = if device_context.is_low_power() { ( @@ -171,7 +171,7 @@ impl GpuContext<'_> { &mut self, scale: f32, dir: CorrelationDirection, - ) -> Result<(), Box> { + ) -> Result<(), GpuError> { let device = self.device_context.device_mut()?; device.set_buffer_direction(&dir)?; let (out_dimensions, out_dimensions_reverse) = match dir { @@ -208,9 +208,7 @@ impl GpuContext<'_> { Ok(()) } - pub fn complete_process( - &mut self, - ) -> Result>, Box> { + pub fn complete_process(&mut self) -> Result>, GpuError> { let device = self.device_context.device_mut()?; let mut out_image = Grid::new(self.img1_dimensions.0, self.img1_dimensions.1, None); unsafe { @@ -228,7 +226,7 @@ impl GpuContext<'_> { first_pass: bool, progress_listener: Option<&PL>, dir: CorrelationDirection, - ) -> Result<(), Box> { + ) -> Result<(), GpuError> { { let device = self.device_context.device_mut()?; device.set_buffer_direction(&dir)?; @@ -406,22 +404,3 @@ impl GpuContext<'_> { f } } - -#[derive(Debug)] -pub struct GpuError { - msg: &'static str, -} - -impl GpuError { - fn new(msg: &'static str) -> GpuError { - GpuError { msg } - } -} - -impl std::error::Error for GpuError {} - -impl fmt::Display for GpuError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.msg) - } -} diff --git a/src/correlation/gpu/vulkan.rs b/src/correlation/gpu/vulkan.rs index 3d74877..6ae28c9 100644 --- a/src/correlation/gpu/vulkan.rs +++ b/src/correlation/gpu/vulkan.rs @@ -1,4 +1,10 @@ -use std::{cmp::Ordering, collections::HashMap, error, slice}; +use std::{ + cmp::Ordering, + collections::HashMap, + error, fmt, io, + ops::{Deref, DerefMut}, + slice, +}; use super::Device as GpuDevice; use ash::{prelude::VkResult, vk}; @@ -9,7 +15,7 @@ use crate::{ data::{Grid, Point2D}, }; -use super::{CorrelationDirection, GpuError, HardwareMode}; +use super::{CorrelationDirection, HardwareMode}; // This should be supported by most modern GPUs, even old/budget ones like Celeron N3350. // Based on https://www.vulkan.gpuinfo.org, only old integrated GPUs like 4th gen Core i5 have a @@ -99,9 +105,9 @@ pub struct DeviceContext { } impl DeviceContext { - pub fn new(hardware_mode: HardwareMode) -> Result> { + pub fn new(hardware_mode: HardwareMode) -> Result { if !matches!(hardware_mode, HardwareMode::Gpu | HardwareMode::GpuLowPower) { - return Err(GpuError::new("GPU mode is not enabled").into()); + return Err("GPU mode is not enabled".into()); }; let low_power = matches!(hardware_mode, HardwareMode::GpuLowPower); let entry = unsafe { ash::Entry::load()? }; @@ -126,7 +132,7 @@ impl super::DeviceContext for DeviceContext { &mut self, img1_dimensions: (usize, usize), img2_dimensions: (usize, usize), - ) -> Result<(), Box> { + ) -> Result<(), GpuError> { let img1_pixels = img1_dimensions.0 * img1_dimensions.1; let img2_pixels = img2_dimensions.0 * img2_dimensions.1; @@ -165,14 +171,14 @@ impl super::DeviceContext for DeviceContext { fn device(&self) -> Result<&Device, GpuError> { match self.device.as_ref() { Some(device) => Ok(device), - None => Err(GpuError::new("Device not initialized")), + None => Err("Device not initialized".into()), } } fn device_mut(&mut self) -> Result<&mut Device, GpuError> { match self.device.as_mut() { Some(device) => Ok(device), - None => Err(GpuError::new("Device not initialized")), + None => Err("Device not initialized".into()), } } } @@ -184,59 +190,40 @@ impl Drop for DeviceContext { } impl Device { - fn new(entry: &ash::Entry, max_buffer_size: usize) -> Result> { + fn new(entry: &ash::Entry, max_buffer_size: usize) -> Result { // Init adapter. let instance = unsafe { Device::init_vk(entry)? }; - let cleanup_err = |err| unsafe { - // TODO: look at refactoring this to follow RAII patterns (like ashpan/scopeguard). - instance.destroy_instance(None); - err - }; + let instance = ScopeRollback::new(instance, |instance| unsafe { + instance.destroy_instance(None) + }); let (physical_device, name, compute_queue_index) = - unsafe { Device::find_device(&instance, max_buffer_size).map_err(cleanup_err)? }; + unsafe { Device::find_device(&instance, max_buffer_size)? }; let name = name.to_string(); - let device = unsafe { - match Device::create_device(&instance, physical_device, compute_queue_index) { - Ok(dev) => dev, - Err(err) => { - instance.destroy_instance(None); - return Err(err); - } - } - }; + let device = + unsafe { Device::create_device(&instance, physical_device, compute_queue_index)? }; + let device = ScopeRollback::new(device, |device| unsafe { device.destroy_device(None) }); let memory_properties = unsafe { instance.get_physical_device_memory_properties(physical_device) }; - let cleanup_err = |err| unsafe { - device.destroy_device(None); - instance.destroy_instance(None); - err - }; // Init pipelines and shaders. - let descriptor_sets = - unsafe { Device::create_descriptor_sets(&device).map_err(cleanup_err)? }; - let cleanup_err = |err| unsafe { - descriptor_sets.destroy(&device); - device.destroy_device(None); - instance.destroy_instance(None); - err - }; - let pipelines = - unsafe { Device::create_pipelines(&device, &descriptor_sets).map_err(cleanup_err)? }; - let cleanup_err = |err| unsafe { - destroy_pipelines(&device, &pipelines); - descriptor_sets.destroy(&device); - device.destroy_device(None); - instance.destroy_instance(None); - err - }; + let descriptor_sets = unsafe { Device::create_descriptor_sets(&device)? }; + let descriptor_sets = ScopeRollback::new(descriptor_sets, |descriptor_sets| unsafe { + descriptor_sets.destroy(&device) + }); + let pipelines = unsafe { Device::create_pipelines(&device, &descriptor_sets)? }; + let pipelines = ScopeRollback::new(pipelines, |pipelines| unsafe { + destroy_pipelines(&device, &pipelines) + }); let direction = CorrelationDirection::Forward; // Init control struct - queues, fences, command buffer. - let control = - unsafe { Device::create_control(&device, compute_queue_index).map_err(cleanup_err)? }; + let control = unsafe { Device::create_control(&device, compute_queue_index)? }; + + // All is good - consume instances and defuse the scope rollback. + let pipelines = pipelines.consume(); + let descriptor_sets = descriptor_sets.consume(); let result = Device { - instance, + instance: instance.consume(), name, - device, + device: device.consume(), memory_properties, buffers: None, direction, @@ -253,7 +240,7 @@ impl Device { dst_buffer: &Buffer, size: usize, f: F, - ) -> Result<(), Box> + ) -> Result<(), GpuError> where F: FnOnce(&mut [T]), { @@ -296,17 +283,11 @@ impl Device { size_bytes, BufferType::HostSource, )?; - let cleanup_err = |err| { - temp_buffer.destroy(&self.device); - err - }; - - handle_buffer(&temp_buffer).map_err(cleanup_err)?; - - self.copy_buffer_to_buffer(&temp_buffer, dst_buffer, size_bytes) - .map_err(cleanup_err)?; + let temp_buffer = + ScopeRollback::new(temp_buffer, |temp_buffer| temp_buffer.destroy(&self.device)); - temp_buffer.destroy(&self.device); + handle_buffer(&temp_buffer)?; + self.copy_buffer_to_buffer(&temp_buffer, dst_buffer, size_bytes)?; Ok(()) } @@ -344,7 +325,7 @@ impl Device { buffer: &Buffer, size: usize, f: F, - ) -> Result<(), Box> + ) -> Result<(), GpuError> where F: FnOnce(&[T]), { @@ -387,16 +368,11 @@ impl Device { size_bytes, BufferType::HostDestination, )?; - let cleanup_err = |err| { - temp_buffer.destroy(&self.device); - err - }; - - self.copy_buffer_to_buffer(buffer, &temp_buffer, size_bytes) - .map_err(cleanup_err)?; + let temp_buffer = + ScopeRollback::new(temp_buffer, |temp_buffer| temp_buffer.destroy(&self.device)); - handle_buffer(&temp_buffer).map_err(cleanup_err)?; - temp_buffer.destroy(&self.device); + self.copy_buffer_to_buffer(buffer, &temp_buffer, size_bytes)?; + handle_buffer(&temp_buffer)?; Ok(()) } @@ -421,7 +397,7 @@ impl Device { unsafe fn find_device( instance: &ash::Instance, max_buffer_size: usize, - ) -> Result<(vk::PhysicalDevice, String, u32), Box> { + ) -> Result<(vk::PhysicalDevice, String, u32), GpuError> { let devices = instance.enumerate_physical_devices()?; let device = devices .iter() @@ -468,7 +444,7 @@ impl Device { { (device, name, queue_index) } else { - return Err(GpuError::new("Device not found").into()); + return Err("Device not found".into()); }; Ok((device, name, queue_index)) } @@ -499,7 +475,7 @@ impl Device { instance: &ash::Instance, physical_device: vk::PhysicalDevice, compute_queue_index: u32, - ) -> Result> { + ) -> Result { let queue_info = vk::DeviceQueueCreateInfo::default() .queue_family_index(compute_queue_index) .queue_priorities(&[1.0f32]); @@ -516,23 +492,20 @@ impl Device { memory_properties: &vk::PhysicalDeviceMemoryProperties, img1_pixels: usize, img2_pixels: usize, - ) -> Result> { + ) -> Result { let max_pixels = img1_pixels.max(img2_pixels); - let mut buffers: Vec = vec![]; - let cleanup_err = |buffers: &[Buffer], err| { + let mut buffers = ScopeRollback::new(vec![], |buffers: Vec| { buffers.iter().for_each(|buffer| { device.free_memory(buffer.buffer_memory, None); device.destroy_buffer(buffer.buffer, None) }); - err - }; + }); let buffer_img = Device::create_buffer( device, memory_properties, (img1_pixels + img2_pixels) * std::mem::size_of::(), BufferType::GpuDestination, - ) - .map_err(|err| cleanup_err(buffers.as_slice(), err))?; + )?; buffers.push(buffer_img); let buffer_internal_img1 = Device::create_buffer( @@ -540,8 +513,7 @@ impl Device { memory_properties, (img1_pixels * 2) * std::mem::size_of::(), BufferType::GpuOnly, - ) - .map_err(|err| cleanup_err(buffers.as_slice(), err))?; + )?; buffers.push(buffer_internal_img1); let buffer_internal_img2 = Device::create_buffer( @@ -549,8 +521,7 @@ impl Device { memory_properties, (img2_pixels * 2) * std::mem::size_of::(), BufferType::GpuOnly, - ) - .map_err(|err| cleanup_err(buffers.as_slice(), err))?; + )?; buffers.push(buffer_internal_img2); let buffer_internal_int = Device::create_buffer( @@ -558,8 +529,7 @@ impl Device { memory_properties, max_pixels * 4 * std::mem::size_of::(), BufferType::GpuOnly, - ) - .map_err(|err| cleanup_err(buffers.as_slice(), err))?; + )?; buffers.push(buffer_internal_int); let buffer_out = Device::create_buffer( @@ -567,8 +537,7 @@ impl Device { memory_properties, img1_pixels * 2 * std::mem::size_of::(), BufferType::GpuSource, - ) - .map_err(|err| cleanup_err(buffers.as_slice(), err))?; + )?; buffers.push(buffer_out); let buffer_out_reverse = Device::create_buffer( @@ -576,8 +545,7 @@ impl Device { memory_properties, img2_pixels * 2 * std::mem::size_of::(), BufferType::GpuOnly, - ) - .map_err(|err| cleanup_err(buffers.as_slice(), err))?; + )?; buffers.push(buffer_out_reverse); let buffer_out_corr = Device::create_buffer( @@ -585,8 +553,7 @@ impl Device { memory_properties, max_pixels * std::mem::size_of::(), BufferType::GpuSource, - ) - .map_err(|err| cleanup_err(buffers.as_slice(), err))?; + )?; Ok(DeviceBuffers { buffer_img, @@ -602,7 +569,7 @@ impl Device { fn buffers(&self) -> Result<&DeviceBuffers, GpuError> { match self.buffers.as_ref() { Some(buffers) => Ok(buffers), - None => Err(GpuError::new("Buffers not initialized")), + None => Err("Buffers not initialized".into()), } } @@ -611,7 +578,7 @@ impl Device { memory_properties: &vk::PhysicalDeviceMemoryProperties, size: usize, buffer_type: BufferType, - ) -> Result> { + ) -> Result { let size = size as u64; let required_memory_properties = match buffer_type { BufferType::GpuOnly | BufferType::GpuDestination | BufferType::GpuSource => { @@ -632,12 +599,10 @@ impl Device { vk::BufferUsageFlags::TRANSFER_DST | vk::BufferUsageFlags::STORAGE_BUFFER } }; - let buffer_create_info = vk::BufferCreateInfo { - size, - usage: extra_usage_flags, - sharing_mode: vk::SharingMode::EXCLUSIVE, - ..Default::default() - }; + let buffer_create_info = vk::BufferCreateInfo::default() + .size(size) + .usage(extra_usage_flags) + .sharing_mode(vk::SharingMode::EXCLUSIVE); let buffer = device.create_buffer(&buffer_create_info, None)?; let memory_requirements = device.get_buffer_memory_requirements(buffer); // Most vendors provide a sorted list - with less features going first. @@ -662,11 +627,9 @@ impl Device { } let host_visible = property_flags.contains(vk::MemoryPropertyFlags::HOST_VISIBLE); let host_coherent = property_flags.contains(vk::MemoryPropertyFlags::HOST_COHERENT); - let allocate_info = vk::MemoryAllocateInfo { - allocation_size: memory_requirements.size, - memory_type_index: memory_type_index as u32, - ..Default::default() - }; + let allocate_info = vk::MemoryAllocateInfo::default() + .allocation_size(memory_requirements.size) + .memory_type_index(memory_type_index as u32); // Some buffers may fill up, in this case allocating memory can fail. let mem = device.allocate_memory(&allocate_info, None).ok()?; @@ -678,7 +641,7 @@ impl Device { mem } else { device.destroy_buffer(buffer, None); - return Err(GpuError::new("Cannot find suitable memory").into()); + return Err("Cannot find suitable memory".into()); }; let result = Buffer { buffer, @@ -690,9 +653,7 @@ impl Device { Ok(result) } - unsafe fn create_descriptor_sets( - device: &ash::Device, - ) -> Result> { + unsafe fn create_descriptor_sets(device: &ash::Device) -> Result { let create_layout_bindings = |count| { let bindings = (0..count) .map(|i| { @@ -714,73 +675,63 @@ impl Device { .max_sets(1) .pool_sizes(&descriptor_pool_size); let descriptor_pool = device.create_descriptor_pool(&descriptor_pool_info, None)?; - let cleanup_err = |err| { - device.destroy_descriptor_pool(descriptor_pool, None); - err - }; - let layout = create_layout_bindings(6).map_err(cleanup_err)?; - let cleanup_err = |err| { - device.destroy_descriptor_set_layout(layout, None); - device.destroy_descriptor_pool(descriptor_pool, None); - err - }; - let layouts = [layout]; + let descriptor_pool = ScopeRollback::new(descriptor_pool, |descriptor_pool| { + device.destroy_descriptor_pool(descriptor_pool, None) + }); + let layout = create_layout_bindings(6)?; + let layout = ScopeRollback::new(layout, |layout| { + device.destroy_descriptor_set_layout(layout, None) + }); + let layouts = [*layout.deref()]; let push_constant_ranges = vk::PushConstantRange::default() .offset(0) .size(std::mem::size_of::() as u32) .stage_flags(vk::ShaderStageFlags::COMPUTE); - let pipeline_layout = device - .create_pipeline_layout( - &vk::PipelineLayoutCreateInfo::default() - .set_layouts(&layouts) - .push_constant_ranges(&[push_constant_ranges]), - None, - ) - .map_err(cleanup_err)?; - let cleanup_err = |err| { - device.destroy_pipeline_layout(pipeline_layout, None); - device.destroy_descriptor_set_layout(layout, None); - device.destroy_descriptor_pool(descriptor_pool, None); - err - }; + let pipeline_layout = device.create_pipeline_layout( + &vk::PipelineLayoutCreateInfo::default() + .set_layouts(&layouts) + .push_constant_ranges(&[push_constant_ranges]), + None, + )?; + let pipeline_layout = ScopeRollback::new(pipeline_layout, |pipeline_layout| { + device.destroy_pipeline_layout(pipeline_layout, None) + }); let descriptor_set_allocate_info = vk::DescriptorSetAllocateInfo::default() - .descriptor_pool(descriptor_pool) + .descriptor_pool(*descriptor_pool.deref()) .set_layouts(&layouts); - let descriptor_sets = device - .allocate_descriptor_sets(&descriptor_set_allocate_info) - .map_err(cleanup_err)?; + let descriptor_sets = device.allocate_descriptor_sets(&descriptor_set_allocate_info)?; Ok(DescriptorSets { - descriptor_pool, - layout, - pipeline_layout, + descriptor_pool: descriptor_pool.consume(), + layout: layout.consume(), + pipeline_layout: pipeline_layout.consume(), descriptor_sets, }) } unsafe fn load_shaders( device: &ash::Device, - ) -> Result, Box> { - let mut result = vec![]; - let cleanup_err = |result: &mut Vec<(ShaderModuleType, vk::ShaderModule)>, err| { - result - .iter() - .for_each(|(_type, shader)| device.destroy_shader_module(*shader, None)); - err - }; - for module_type in ShaderModuleType::VALUES { - let shader = module_type - .load(device) - .map_err(|err| cleanup_err(&mut result, err))?; - result.push((module_type, shader)); - } - Ok(result) + ) -> Result, GpuError> { + let mut shader_modules = ShaderModuleType::VALUES + .iter() + .map(|module_type| { + let shader = module_type.load(device)?; + let shader = + ScopeRollback::new(shader, |shader| device.destroy_shader_module(shader, None)); + Ok((module_type, shader)) + }) + .collect::, GpuError>>()?; + let shader_modules = shader_modules + .drain(..) + .map(|(module_type, shader)| (module_type.to_owned(), shader.consume())) + .collect::>(); + Ok(shader_modules) } unsafe fn create_pipelines( device: &ash::Device, descriptor_sets: &DescriptorSets, - ) -> Result, Box> { + ) -> Result, GpuError> { let shader_modules = Device::load_shaders(device)?; let main_module_name = c"main"; @@ -883,36 +834,27 @@ impl Device { unsafe fn create_control( device: &ash::Device, queue_family_index: u32, - ) -> Result> { + ) -> Result { let queue = device.get_device_queue(queue_family_index, 0); let command_pool_info = vk::CommandPoolCreateInfo::default() .queue_family_index(queue_family_index) .flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER); let command_pool = device.create_command_pool(&command_pool_info, None)?; - let cleanup_err = |err| { - device.destroy_command_pool(command_pool, None); - err - }; + let command_pool = ScopeRollback::new(command_pool, |command_pool| { + device.destroy_command_pool(command_pool, None) + }); let fence_create_info = vk::FenceCreateInfo::default(); - let fence = device - .create_fence(&fence_create_info, None) - .map_err(cleanup_err)?; - let cleanup_err = |err| { - device.destroy_command_pool(command_pool, None); - device.destroy_fence(fence, None); - err - }; + let fence = device.create_fence(&fence_create_info, None)?; + let fence = ScopeRollback::new(fence, |fence| device.destroy_fence(fence, None)); let command_buffers_info = vk::CommandBufferAllocateInfo::default() .command_buffer_count(1) - .command_pool(command_pool) + .command_pool(*command_pool.deref()) .level(vk::CommandBufferLevel::PRIMARY); - let command_buffer = device - .allocate_command_buffers(&command_buffers_info) - .map_err(cleanup_err)?[0]; + let command_buffer = device.allocate_command_buffers(&command_buffers_info)?[0]; Ok(Control { queue, - command_pool, - fence, + command_pool: command_pool.consume(), + fence: fence.consume(), command_buffer, }) } @@ -920,7 +862,7 @@ impl Device { impl super::Device for Device { fn set_buffer_direction(&mut self, direction: &CorrelationDirection) -> Result<(), GpuError> { - self.direction = direction.to_owned(); + direction.clone_into(&mut self.direction); Ok(()) } @@ -929,7 +871,7 @@ impl super::Device for Device { dimensions: (usize, usize), shader_type: ShaderModuleType, shader_params: ShaderParams, - ) -> Result<(), Box> { + ) -> Result<(), GpuError> { let pipeline_config = self.pipelines.get(&shader_type).unwrap(); let command_buffer = self.control.command_buffer; @@ -990,11 +932,7 @@ impl super::Device for Device { Ok(()) } - unsafe fn transfer_in_images( - &self, - img1: &Grid, - img2: &Grid, - ) -> Result<(), Box> { + unsafe fn transfer_in_images(&self, img1: &Grid, img2: &Grid) -> Result<(), GpuError> { let buffers = self.buffers()?; let img2_offset = img1.width() * img1.height(); let size = img1.width() * img1.height() + img2.width() * img2.height(); @@ -1015,7 +953,7 @@ impl super::Device for Device { &self, correlation_values: &mut Grid>, correlation_threshold: f32, - ) -> Result<(), Box> { + ) -> Result<(), GpuError> { let buffers = self.buffers()?; let size = correlation_values.width() * correlation_values.height(); let width = correlation_values.width(); @@ -1039,7 +977,7 @@ impl super::Device for Device { &self, out_image: &mut Grid>, correlation_values: &Grid>, - ) -> Result<(), Box> { + ) -> Result<(), GpuError> { let buffers = self.buffers()?; let size = out_image.width() * out_image.height() * 2; let width = out_image.width(); @@ -1127,7 +1065,7 @@ impl ShaderModuleType { Self::CrossCheckFilter, ]; - unsafe fn load(&self, device: &ash::Device) -> Result> { + unsafe fn load(&self, device: &ash::Device) -> Result { const SHADER_INIT_OUT_DATA: &[u8] = include_bytes!("shaders/init_out_data.spv"); const SHADER_PREPARE_INITIALDATA_SEARCHDATA: &[u8] = include_bytes!("shaders/prepare_initialdata_searchdata.spv"); @@ -1181,3 +1119,131 @@ impl Drop for Device { } } } + +struct ScopeRollback +where + F: FnOnce(T), +{ + val: Option, + rollback: Option, +} + +impl ScopeRollback +where + F: FnOnce(T), +{ + fn new(val: T, rollback: F) -> ScopeRollback { + ScopeRollback { + val: Some(val), + rollback: Some(rollback), + } + } + + fn consume(mut self) -> T { + self.rollback = None; + self.val.take().unwrap() + } +} + +impl Deref for ScopeRollback +where + F: FnOnce(T), +{ + type Target = T; + + fn deref(&self) -> &Self::Target { + self.val.as_ref().unwrap() + } +} + +impl DerefMut for ScopeRollback +where + F: FnOnce(T), +{ + fn deref_mut(&mut self) -> &mut Self::Target { + self.val.as_mut().unwrap() + } +} + +impl Drop for ScopeRollback +where + F: FnOnce(T), +{ + fn drop(&mut self) { + if let Some(val) = self.val.take() { + if let Some(rb) = self.rollback.take() { + rb(val) + } + } + } +} + +#[derive(Debug)] +pub enum GpuError { + Internal(&'static str), + Vk(&'static str, vk::Result), + Loading(ash::LoadingError), + Io(io::Error), +} + +impl fmt::Display for GpuError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + GpuError::Internal(msg) => f.write_str(msg), + GpuError::Vk(msg, ref e) => { + if !msg.is_empty() { + write!(f, "Vulkan error: {} ({})", msg, e) + } else { + write!(f, "Vulkan error: {}", e) + } + } + GpuError::Loading(ref e) => { + write!(f, "Failed to init GPU: {}", e) + } + GpuError::Io(ref e) => { + write!(f, "IO error: {}", e) + } + } + } +} + +impl std::error::Error for GpuError { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match *self { + GpuError::Internal(_msg) => None, + GpuError::Vk(_msg, ref e) => Some(e), + GpuError::Loading(ref e) => Some(e), + GpuError::Io(ref e) => Some(e), + } + } +} + +impl From<&'static str> for GpuError { + fn from(msg: &'static str) -> GpuError { + GpuError::Internal(msg) + } +} + +impl From<(&'static str, vk::Result)> for GpuError { + fn from(e: (&'static str, vk::Result)) -> GpuError { + GpuError::Vk(e.0, e.1) + } +} + +impl From for GpuError { + fn from(err: vk::Result) -> GpuError { + GpuError::Vk("", err) + } +} + +impl From for GpuError { + fn from(err: ash::LoadingError) -> GpuError { + GpuError::Loading(err) + } +} + +impl From for GpuError { + fn from(err: io::Error) -> GpuError { + GpuError::Io(err) + } +} diff --git a/src/correlation/mod.rs b/src/correlation/mod.rs index a1ba7fd..b7dcdd2 100644 --- a/src/correlation/mod.rs +++ b/src/correlation/mod.rs @@ -1,11 +1,16 @@ mod gpu; -use self::gpu::{DefaultDeviceContext, GpuContext}; +use self::gpu::{DefaultDeviceContext, GpuContext, GpuError}; use crate::data::{Grid, Point2D}; use nalgebra::{Matrix3, Vector3}; use rayon::iter::ParallelIterator; -use std::{cell::RefCell, error, ops::Range, sync::atomic::AtomicUsize, sync::atomic::Ordering}; +use std::{ + cell::RefCell, + error, fmt, + ops::Range, + sync::atomic::{AtomicUsize, Ordering}, +}; const SCALE_MIN_SIZE: usize = 64; const KERNEL_SIZE: usize = 5; @@ -126,7 +131,7 @@ impl CorrelationParameters { } } -pub fn create_gpu_context(hardware_mode: HardwareMode) -> Result> { +pub fn create_gpu_context(hardware_mode: HardwareMode) -> Result { GpuDevice::new(hardware_mode) } @@ -188,13 +193,10 @@ impl PointCorrelations<'_> { &self.selected_hardware } - pub fn complete(&mut self) -> Result<(), Box> { + pub fn complete(&mut self) -> Result<(), CorrelationError> { self.correlated_points_reverse = Grid::new(0, 0, None); if let Some(gpu_context) = &mut self.gpu_context { - match gpu_context.complete_process() { - Ok(correlated_points) => self.correlated_points = correlated_points, - Err(err) => return Err(err), - }; + self.correlated_points = gpu_context.complete_process()?; self.gpu_context = None; } Ok(()) @@ -206,7 +208,7 @@ impl PointCorrelations<'_> { img2: Grid, scale: f32, progress_listener: Option<&PL>, - ) -> Result<(), Box> { + ) -> Result<(), CorrelationError> { self.correlate_images_step( &img1, &img2, @@ -237,16 +239,11 @@ impl PointCorrelations<'_> { scale: f32, progress_listener: Option<&PL>, dir: CorrelationDirection, - ) -> Result<(), Box> { + ) -> Result<(), CorrelationError> { if let Some(gpu_context) = &mut self.gpu_context { - return gpu_context.correlate_images( - img1, - img2, - scale, - self.first_pass, - progress_listener, - dir, - ); + return gpu_context + .correlate_images(img1, img2, scale, self.first_pass, progress_listener, dir) + .map_err(|err| err.into()); }; let img2_data = compute_image_point_data(img2); let mut out_data = Grid::>::new(img1.width(), img1.height(), None); @@ -545,7 +542,7 @@ impl PointCorrelations<'_> { &mut self, scale: f32, dir: CorrelationDirection, - ) -> Result<(), Box> { + ) -> Result<(), CorrelationError> { if let Some(gpu_context) = &mut self.gpu_context { gpu_context.cross_check_filter(scale, dir)?; return Ok(()); @@ -725,3 +722,30 @@ pub fn compute_point_data( Some(result) } + +#[derive(Debug)] +pub enum CorrelationError { + Gpu(GpuError), +} + +impl fmt::Display for CorrelationError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + CorrelationError::Gpu(ref err) => err.fmt(f), + } + } +} + +impl std::error::Error for CorrelationError { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match *self { + CorrelationError::Gpu(ref err) => err.source(), + } + } +} + +impl From for CorrelationError { + fn from(e: GpuError) -> CorrelationError { + CorrelationError::Gpu(e) + } +} diff --git a/src/fundamentalmatrix.rs b/src/fundamentalmatrix.rs index 0403b00..95d52a8 100644 --- a/src/fundamentalmatrix.rs +++ b/src/fundamentalmatrix.rs @@ -110,7 +110,7 @@ impl FundamentalMatrix { progress_listener: Option<&PL>, ) -> Result { if point_matches.len() < RANSAC_D + self.ransac_n { - return Err(RansacError::new("Not enough matches")); + return Err("Not enough matches".into()); } let ransac_outer = self.ransac_k / RANSAC_CHECK_INTERVAL; @@ -146,7 +146,7 @@ impl FundamentalMatrix { } match best_result { Some(res) => Ok(self.optimize_result(res, point_matches)), - None => Err(RansacError::new("No reliable matches found")), + None => Err("No reliable matches found".into()), } } @@ -571,7 +571,7 @@ where let delta = if let Some(delta) = delta { delta } else { - return Err(RansacError::new("Failed to compute delta vector")); + return Err("Failed to compute delta vector".into()); }; if delta.norm() <= DELTA_EPSILON * (params.norm() + DELTA_EPSILON) { @@ -614,7 +614,7 @@ where } if !found { - return Err(RansacError::new("Levenberg-Marquardt failed to converge")); + return Err("Levenberg-Marquardt failed to converge".into()); } Ok(params) @@ -667,16 +667,16 @@ pub struct RansacError { msg: &'static str, } -impl RansacError { - fn new(msg: &'static str) -> RansacError { - RansacError { msg } +impl fmt::Display for RansacError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.msg) } } impl std::error::Error for RansacError {} -impl fmt::Display for RansacError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.msg) +impl From<&'static str> for RansacError { + fn from(msg: &'static str) -> RansacError { + RansacError { msg } } } diff --git a/src/output.rs b/src/output.rs index 49c4344..a6b2ce6 100644 --- a/src/output.rs +++ b/src/output.rs @@ -5,7 +5,6 @@ use crate::{ use core::fmt; use std::{ collections::HashMap, - error, fs::File, io::{BufWriter, Write}, path::Path, @@ -209,7 +208,7 @@ impl Mesh { surface: triangulation::Surface, interpolation: InterpolationMode, progress_listener: Option<&PL>, - ) -> Result> { + ) -> Result { let point_normals = vec![Vector3::zeros(); surface.tracks_len()]; let mut surface = Mesh { points: surface, @@ -415,7 +414,7 @@ impl Mesh { camera_i: usize, interpolation: InterpolationMode, progress_listener: Option<&PL>, - ) -> Result<(), Box> { + ) -> Result<(), OutputError> { if interpolation != InterpolationMode::Delaunay { return Ok(()); } @@ -546,7 +545,7 @@ impl Mesh { &self, mut writer: Box, progress_listener: Option<&PL>, - ) -> Result<(), Box> { + ) -> Result<(), OutputError> { writer.output_header(self.points.tracks_len(), self.polygons.len())?; let nvertices = self.points.tracks_len() as f32; self.points @@ -601,7 +600,7 @@ pub fn output( interpolation: InterpolationMode, vertex_mode: VertexMode, progress_listener: Option<&PL>, -) -> Result<(), Box> { +) -> Result<(), OutputError> { let output_normals = interpolation != InterpolationMode::None; let writer: Box = if path.to_lowercase().ends_with(".obj") { Box::new(ObjWriter::new( @@ -643,33 +642,23 @@ trait MeshWriter { &mut self, _point: &triangulation::Track, _normal: &Vector3, - ) -> Result<(), Box> { + ) -> Result<(), OutputError> { Ok(()) } - fn output_vertex_normal( - &mut self, - _normal: &Vector3, - ) -> Result<(), Box> { + fn output_vertex_normal(&mut self, _normal: &Vector3) -> Result<(), OutputError> { Ok(()) } - fn output_vertex_uv( - &mut self, - _point: &triangulation::Track, - ) -> Result<(), Box> { + fn output_vertex_uv(&mut self, _point: &triangulation::Track) -> Result<(), OutputError> { Ok(()) } - fn output_face( - &mut self, - _p: &Polygon, - _tracks: [&Track; 3], - ) -> Result<(), Box> { + fn output_face(&mut self, _p: &Polygon, _tracks: [&Track; 3]) -> Result<(), OutputError> { Ok(()) } - fn complete(&mut self) -> Result<(), Box>; + fn complete(&mut self) -> Result<(), OutputError>; } const WRITE_BUFFER_SIZE: usize = 1024 * 1024; @@ -690,7 +679,7 @@ impl PlyWriter { output_normals: bool, vertex_mode: VertexMode, out_scale: (f64, f64, f64), - ) -> Result> { + ) -> Result { let writer = BufWriter::new(File::create(path)?); let buffer = Vec::with_capacity(WRITE_BUFFER_SIZE); @@ -750,7 +739,7 @@ impl MeshWriter for PlyWriter { &mut self, track: &triangulation::Track, normal: &Vector3, - ) -> Result<(), Box> { + ) -> Result<(), OutputError> { let color = match self.vertex_mode { VertexMode::Plain | VertexMode::Texture => None, VertexMode::Color => { @@ -764,7 +753,7 @@ impl MeshWriter for PlyWriter { img.get_pixel_checked(point2d.x, point2d.y) .map(|pixel| pixel.0) } else { - return Err(OutputError::new("Track has no images").into()); + return Err("Track has no images".into()); } } }; @@ -774,7 +763,7 @@ impl MeshWriter for PlyWriter { let p = if let Some(point3d) = track.get_point3d() { point3d } else { - return Err(OutputError::new("Point has no 3D coordinates").into()); + return Err("Point has no 3D coordinates".into()); }; let (x, y, z) = ( p.x * self.out_scale.0, @@ -798,11 +787,7 @@ impl MeshWriter for PlyWriter { Ok(()) } - fn output_face( - &mut self, - polygon: &Polygon, - _tracks: [&Track; 3], - ) -> Result<(), Box> { + fn output_face(&mut self, polygon: &Polygon, _tracks: [&Track; 3]) -> Result<(), OutputError> { let indices = polygon.vertices; self.check_flush_buffer()?; @@ -815,7 +800,7 @@ impl MeshWriter for PlyWriter { Ok(()) } - fn complete(&mut self) -> Result<(), Box> { + fn complete(&mut self) -> Result<(), OutputError> { let buffer = &mut self.buffer; let w = &mut self.writer; w.write_all(buffer)?; @@ -843,7 +828,7 @@ impl ObjWriter { output_normals: bool, vertex_mode: VertexMode, out_scale: (f64, f64, f64), - ) -> Result> { + ) -> Result { let writer = BufWriter::new(File::create(path)?); let buffer = Vec::with_capacity(WRITE_BUFFER_SIZE); Ok(ObjWriter { @@ -898,7 +883,7 @@ impl ObjWriter { Ok(()) } - fn write_materials(&mut self) -> Result<(), Box> { + fn write_materials(&mut self) -> Result<(), OutputError> { let out_filename = match self.vertex_mode { VertexMode::Plain | VertexMode::Color => return Ok(()), VertexMode::Texture => self.get_output_filename().unwrap(), @@ -948,7 +933,7 @@ impl MeshWriter for ObjWriter { &mut self, track: &triangulation::Track, _normal: &Vector3, - ) -> Result<(), Box> { + ) -> Result<(), OutputError> { self.check_flush_buffer()?; let w = &mut self.buffer; @@ -965,7 +950,7 @@ impl MeshWriter for ObjWriter { img.get_pixel_checked(point2d.x, point2d.y) .map(|pixel| pixel.0) } else { - return Err(OutputError::new("Track has no images").into()); + return Err("Track has no images".into()); } } }; @@ -973,7 +958,7 @@ impl MeshWriter for ObjWriter { let p = if let Some(point3d) = track.get_point3d() { point3d } else { - return Err(OutputError::new("Point has no 3D coordinates").into()); + return Err("Point has no 3D coordinates".into()); }; let (x, y, z) = ( p.x * self.out_scale.0, @@ -995,7 +980,7 @@ impl MeshWriter for ObjWriter { Ok(()) } - fn output_vertex_normal(&mut self, normal: &Vector3) -> Result<(), Box> { + fn output_vertex_normal(&mut self, normal: &Vector3) -> Result<(), OutputError> { self.check_flush_buffer()?; let w = &mut self.buffer; @@ -1006,10 +991,7 @@ impl MeshWriter for ObjWriter { Ok(()) } - fn output_vertex_uv( - &mut self, - track: &triangulation::Track, - ) -> Result<(), Box> { + fn output_vertex_uv(&mut self, track: &triangulation::Track) -> Result<(), OutputError> { self.check_flush_buffer()?; match self.vertex_mode { VertexMode::Plain | VertexMode::Color => {} @@ -1032,7 +1014,7 @@ impl MeshWriter for ObjWriter { )? } if projections_count == 0 { - return Err(OutputError::new("Track has no images").into()); + return Err("Track has no images".into()); } // Tracks are output in an ordered way, so uv_index will use the same ordering as tracks. let last_index = self.uv_index.last().map_or(0, |last_index| *last_index); @@ -1042,11 +1024,7 @@ impl MeshWriter for ObjWriter { Ok(()) } - fn output_face( - &mut self, - polygon: &Polygon, - tracks: [&Track; 3], - ) -> Result<(), Box> { + fn output_face(&mut self, polygon: &Polygon, tracks: [&Track; 3]) -> Result<(), OutputError> { let indices = polygon.vertices; // Polygons are sorted by their image IDs, so if the index is increased, this is a new image. @@ -1082,7 +1060,7 @@ impl MeshWriter for ObjWriter { Ok(()) } - fn complete(&mut self) -> Result<(), Box> { + fn complete(&mut self) -> Result<(), OutputError> { let buffer = &mut self.buffer; let w = &mut self.writer; w.write_all(buffer)?; @@ -1162,17 +1140,17 @@ impl MeshWriter for ImageWriter { &mut self, track: &triangulation::Track, _normal: &Vector3, - ) -> Result<(), Box> { + ) -> Result<(), OutputError> { // TODO: project all polygons into first image let point2d = if let Some(point2d) = track.get(0) { point2d } else { - return Err(OutputError::new("Track is absent from first image").into()); + return Err("Track is absent from first image".into()); }; let point3d = if let Some(point3d) = track.get_point3d() { point3d } else { - return Err(OutputError::new("Point has no 3D coordinates").into()); + return Err("Point has no 3D coordinates".into()); }; let (x, y) = (point2d.x as usize, point2d.y as usize); if x < self.output_map.width() && y < self.output_map.height() { @@ -1181,11 +1159,7 @@ impl MeshWriter for ImageWriter { Ok(()) } - fn output_face( - &mut self, - polygon: &Polygon, - _tracks: [&Track; 3], - ) -> Result<(), Box> { + fn output_face(&mut self, polygon: &Polygon, _tracks: [&Track; 3]) -> Result<(), OutputError> { let vertices = polygon.vertices; let (min_x, max_x, min_y, max_y) = vertices.iter().fold( (self.output_map.width(), 0, self.output_map.height(), 0), @@ -1225,7 +1199,7 @@ impl MeshWriter for ImageWriter { Ok(()) } - fn complete(&mut self) -> Result<(), Box> { + fn complete(&mut self) -> Result<(), OutputError> { let (min_depth, max_depth) = self.output_map.iter().fold((f64::MAX, f64::MIN), |acc, v| { if let Some(v) = v.2 { (acc.0.min(*v), acc.1.max(*v)) @@ -1376,20 +1350,55 @@ impl HasPosition for Point { } #[derive(Debug)] -pub struct OutputError { - msg: &'static str, +pub enum OutputError { + Internal(&'static str), + Triangulation(spade::InsertionError), + Io(std::io::Error), + Image(image::ImageError), +} + +impl fmt::Display for OutputError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + OutputError::Internal(msg) => f.write_str(msg), + OutputError::Triangulation(ref err) => err.fmt(f), + OutputError::Io(ref err) => err.fmt(f), + OutputError::Image(ref err) => err.fmt(f), + } + } } -impl OutputError { - fn new(msg: &'static str) -> OutputError { - OutputError { msg } +impl std::error::Error for OutputError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match *self { + OutputError::Internal(_msg) => None, + OutputError::Triangulation(ref err) => err.source(), + OutputError::Io(ref err) => err.source(), + OutputError::Image(ref err) => err.source(), + } } } -impl std::error::Error for OutputError {} +impl From<&'static str> for OutputError { + fn from(msg: &'static str) -> OutputError { + OutputError::Internal(msg) + } +} -impl fmt::Display for OutputError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.msg) +impl From for OutputError { + fn from(e: spade::InsertionError) -> OutputError { + OutputError::Triangulation(e) + } +} + +impl From for OutputError { + fn from(e: std::io::Error) -> OutputError { + OutputError::Io(e) + } +} + +impl From for OutputError { + fn from(e: image::ImageError) -> OutputError { + OutputError::Image(e) } } diff --git a/src/reconstruction.rs b/src/reconstruction.rs index 7162cf5..f1ba4c3 100644 --- a/src/reconstruction.rs +++ b/src/reconstruction.rs @@ -12,12 +12,12 @@ use crate::Args; use image::{imageops::FilterType, GenericImageView, GrayImage, RgbImage}; use indicatif::{ProgressBar, ProgressState, ProgressStyle}; use nalgebra::Matrix3; -use std::error; use std::fmt::Write; use std::fs::File; use std::io::BufReader; use std::str::FromStr; use std::time::SystemTime; +use std::{error, fmt}; const TIFFTAG_META_PHENOM: exif::Tag = exif::Tag(exif::Context::Tiff, 34683); const TIFFTAG_META_QUANTA: exif::Tag = exif::Tag(exif::Context::Tiff, 34682); @@ -190,7 +190,7 @@ struct ImageReconstruction { img_filenames: Vec, } -pub fn reconstruct(args: &Args) -> Result<(), Box> { +pub fn reconstruct(args: &Args) -> Result<(), ReconstructionError> { let start_time = SystemTime::now(); let projection_mode = match args.projection { @@ -305,7 +305,7 @@ impl ImageReconstruction { gpu_device: Option<&mut correlation::GpuDevice>, img1_index: usize, img2_index: usize, - ) -> Result<(), Box> { + ) -> Result<(), ReconstructionError> { let img1_filename = &self.img_filenames[img1_index]; let img2_filename = &self.img_filenames[img2_index]; println!("Processing images {} and {}", img1_filename, img2_filename); @@ -523,7 +523,7 @@ impl ImageReconstruction { img1: &SourceImage, img2: &SourceImage, f: Matrix3, - ) -> Result> { + ) -> Result { let mut point_correlations; let start_time = SystemTime::now(); @@ -629,7 +629,9 @@ impl ImageReconstruction { result } - fn complete_triangulation(&mut self) -> Result> { + fn complete_triangulation( + &mut self, + ) -> Result { let start_time = SystemTime::now(); let pb = new_progress_bar(false); @@ -663,7 +665,7 @@ impl ImageReconstruction { out_scale: (f64, f64, f64), texture_filenames: &[String], output_filename: &str, - ) -> Result<(), Box> { + ) -> Result<(), output::OutputError> { let start_time = SystemTime::now(); let pb = new_progress_bar(false); let images = texture_filenames @@ -763,3 +765,66 @@ impl output::ProgressListener for ProgressBar { self.set_position((pos * 10000.0) as u64); } } + +#[derive(Debug)] +pub enum ReconstructionError { + Image(image::ImageError), + Ransac(fundamentalmatrix::RansacError), + Correlation(correlation::CorrelationError), + Triangulation(triangulation::TriangulationError), + Output(output::OutputError), +} + +impl fmt::Display for ReconstructionError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + ReconstructionError::Image(ref err) => err.fmt(f), + ReconstructionError::Ransac(ref err) => err.fmt(f), + ReconstructionError::Correlation(ref err) => err.fmt(f), + ReconstructionError::Triangulation(ref err) => err.fmt(f), + ReconstructionError::Output(ref err) => err.fmt(f), + } + } +} + +impl std::error::Error for ReconstructionError { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match *self { + ReconstructionError::Image(ref err) => err.source(), + ReconstructionError::Ransac(ref err) => err.source(), + ReconstructionError::Correlation(ref err) => err.source(), + ReconstructionError::Triangulation(ref err) => err.source(), + ReconstructionError::Output(ref err) => err.source(), + } + } +} + +impl From for ReconstructionError { + fn from(e: image::ImageError) -> ReconstructionError { + ReconstructionError::Image(e) + } +} + +impl From for ReconstructionError { + fn from(e: fundamentalmatrix::RansacError) -> ReconstructionError { + ReconstructionError::Ransac(e) + } +} + +impl From for ReconstructionError { + fn from(e: correlation::CorrelationError) -> ReconstructionError { + ReconstructionError::Correlation(e) + } +} + +impl From for ReconstructionError { + fn from(e: triangulation::TriangulationError) -> ReconstructionError { + ReconstructionError::Triangulation(e) + } +} + +impl From for ReconstructionError { + fn from(e: output::OutputError) -> ReconstructionError { + ReconstructionError::Output(e) + } +} diff --git a/src/triangulation.rs b/src/triangulation.rs index 8ba3cc8..c1f30df 100644 --- a/src/triangulation.rs +++ b/src/triangulation.rs @@ -168,7 +168,7 @@ impl Triangulation { progress_listener, ) } else { - Err(TriangulationError::new("Triangulation not initialized")) + Err("Triangulation not initialized".into()) } } @@ -181,7 +181,7 @@ impl Triangulation { } else if let Some(perspective) = &mut self.perspective { perspective.recover_next_camera(progress_listener) } else { - Err(TriangulationError::new("Triangulation not initialized")) + Err("Triangulation not initialized".into()) } } @@ -194,7 +194,7 @@ impl Triangulation { } else if let Some(perspective) = &mut self.perspective { perspective.triangulate_all(progress_listener) } else { - Err(TriangulationError::new("Triangulation not initialized")) + Err("Triangulation not initialized".into()) } } @@ -204,7 +204,7 @@ impl Triangulation { } else if let Some(perspective) = &self.perspective { Ok(perspective.retained_images()) } else { - Err(TriangulationError::new("Triangulation not initialized")) + Err("Triangulation not initialized".into()) } } @@ -224,9 +224,7 @@ impl AffineTriangulation { correlated_points: &CorrelatedPoints, ) -> Result<(), TriangulationError> { if !self.surface.tracks.is_empty() { - return Err(TriangulationError::new( - "Triangulation of multiple affine image is not supported", - )); + return Err("Triangulation of multiple affine image is not supported".into()); } let points3d = correlated_points @@ -488,12 +486,12 @@ impl PerspectiveTriangulation { let k1 = if let Some(calibration) = self.calibration[image1_index] { calibration } else { - return Err(TriangulationError::new("Missing calibration matrix")); + return Err("Missing calibration matrix".into()); }; let k2 = if let Some(calibration) = self.calibration[image2_index] { calibration } else { - return Err(TriangulationError::new("Missing calibration matrix")); + return Err("Missing calibration matrix".into()); }; let short_tracks = self .tracks @@ -514,7 +512,7 @@ impl PerspectiveTriangulation { short_tracks.as_slice(), ) { Some(res) => res, - None => return Err(TriangulationError::new("Unable to find projection matrix")), + None => return Err("Unable to find projection matrix".into()), }; if self @@ -543,12 +541,12 @@ impl PerspectiveTriangulation { let k1 = if let Some(calibration) = self.calibration[initial_images.0] { calibration } else { - return Err(TriangulationError::new("Missing calibration matrix")); + return Err("Missing calibration matrix".into()); }; let k2 = if let Some(calibration) = self.calibration[initial_images.1] { calibration } else { - return Err(TriangulationError::new("Missing calibration matrix")); + return Err("Missing calibration matrix".into()); }; let p1 = k1 * Matrix3x4::identity(); let camera1 = Camera::from_matrix(&k1, &Matrix3::identity(), &Vector3::zeros()); @@ -557,9 +555,7 @@ impl PerspectiveTriangulation { let p2 = if let Some(p2) = self.best_initial_p2 { p2 } else { - return Err(TriangulationError::new( - "Missing projection matrix for initial image pair", - )); + return Err("Missing projection matrix for initial image pair".into()); }; let camera2_r = p2.fixed_view::<3, 3>(0, 0); let camera2_t = p2.column(3); @@ -623,19 +619,15 @@ impl PerspectiveTriangulation { let k2 = if let Some(calibration) = self.calibration[best_candidate] { calibration } else { - return Err(TriangulationError::new("Missing calibration matrix")); + return Err("Missing calibration matrix".into()); }; let k2_inv = match k2.pseudo_inverse(f64::EPSILON) { Ok(k_inverse) => k_inverse, - Err(_) => { - return Err(TriangulationError::new( - "Unable to invert calibration matrix", - )) - } + Err(_) => return Err("Unable to invert calibration matrix".into()), }; let camera2 = match self.recover_pose(best_candidate, &k2, &k2_inv, progress_listener) { Some(camera2) => camera2, - None => return Err(TriangulationError::new("Unable to find projection matrix")), + None => return Err("Unable to find projection matrix".into()), }; let projection2 = camera2.projection(); self.cameras[best_candidate] = Some(camera2); @@ -1881,7 +1873,7 @@ impl BundleAdjustment<'_> { let delta = if let Some(delta) = self.calculate_delta_step() { delta } else { - return Err(TriangulationError::new("Failed to compute delta vector")); + return Err("Failed to compute delta vector".into()); }; let params_norm; @@ -1959,9 +1951,7 @@ impl BundleAdjustment<'_> { } if !found { - return Err(TriangulationError::new( - "Levenberg-Marquardt failed to converge", - )); + return Err("Levenberg-Marquardt failed to converge".into()); } Ok(self.cameras.to_vec()) @@ -1973,16 +1963,16 @@ pub struct TriangulationError { msg: &'static str, } -impl TriangulationError { - fn new(msg: &'static str) -> TriangulationError { - TriangulationError { msg } +impl fmt::Display for TriangulationError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.msg) } } impl std::error::Error for TriangulationError {} -impl fmt::Display for TriangulationError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.msg) +impl From<&'static str> for TriangulationError { + fn from(msg: &'static str) -> TriangulationError { + TriangulationError { msg } } }