From 32e92991d7ec282c89b5414c561b44e80451d3b5 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Wed, 3 Oct 2018 21:43:29 -0700 Subject: [PATCH] add first part of xcb swapchain creation; memory bug somewhere --- vulkan-driver/Cargo.toml | 4 +- vulkan-driver/src/api_impl.rs | 212 ++++++++++++--- vulkan-driver/src/handle.rs | 105 ++++++-- vulkan-driver/src/image.rs | 15 ++ vulkan-driver/src/lib.rs | 10 + vulkan-driver/src/shm.rs | 98 +++++++ vulkan-driver/src/swapchain.rs | 153 +++++++++++ vulkan-driver/src/xcb_swapchain.rs | 413 +++++++++++++++++++++++++++++ vulkan-driver/vulkan-wrapper.h | 12 +- 9 files changed, 963 insertions(+), 59 deletions(-) create mode 100644 vulkan-driver/src/image.rs create mode 100644 vulkan-driver/src/shm.rs create mode 100644 vulkan-driver/src/swapchain.rs create mode 100644 vulkan-driver/src/xcb_swapchain.rs diff --git a/vulkan-driver/Cargo.toml b/vulkan-driver/Cargo.toml index 8fc0efa..5bd3d44 100644 --- a/vulkan-driver/Cargo.toml +++ b/vulkan-driver/Cargo.toml @@ -16,7 +16,9 @@ uuid = {version = "0.7", features = ["v5"]} sys-info = "0.5" [target.'cfg(unix)'.dependencies] -xcb = "0.8" +xcb = {version = "0.8", features = ["shm"]} +libc = "0.2" +errno = "0.2" [build-dependencies] bindgen = "0.40" diff --git a/vulkan-driver/src/api_impl.rs b/vulkan-driver/src/api_impl.rs index f71d191..aedd754 100644 --- a/vulkan-driver/src/api_impl.rs +++ b/vulkan-driver/src/api_impl.rs @@ -13,8 +13,10 @@ use std::ops::*; use std::os::raw::c_char; use std::ptr::null; use std::ptr::null_mut; +use std::ptr::NonNull; use std::slice; use std::str::FromStr; +use swapchain::{SurfaceImplementation, SurfacePlatform, Swapchain}; use sys_info; use uuid; #[cfg(unix)] @@ -117,6 +119,7 @@ fn is_supported_structure_type(v: api::VkStructureType) -> bool { | api::VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETER_FEATURES | api::VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SPARSE_IMAGE_FORMAT_INFO_2 | api::VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES + | api::VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR | api::VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTER_FEATURES | api::VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO | api::VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO @@ -148,6 +151,7 @@ fn is_supported_structure_type(v: api::VkStructureType) -> bool { | api::VK_STRUCTURE_TYPE_SPARSE_IMAGE_FORMAT_PROPERTIES_2 | api::VK_STRUCTURE_TYPE_SPARSE_IMAGE_MEMORY_REQUIREMENTS_2 | api::VK_STRUCTURE_TYPE_SUBMIT_INFO + | api::VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR | api::VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR | api::VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET => true, _ => false, @@ -3365,7 +3369,9 @@ pub unsafe extern "system" fn vkDestroyInstance( instance: api::VkInstance, _allocator: *const api::VkAllocationCallbacks, ) { - OwnedHandle::from(instance); + if !instance.is_null() { + OwnedHandle::from(instance); + } } #[allow(non_snake_case)] @@ -3531,7 +3537,9 @@ pub unsafe extern "system" fn vkDestroyDevice( device: api::VkDevice, _allocator: *const api::VkAllocationCallbacks, ) { - OwnedHandle::from(device); + if !device.is_null() { + OwnedHandle::from(device); + } } unsafe fn enumerate_extension_properties( @@ -5142,10 +5150,42 @@ pub unsafe extern "system" fn vkGetDescriptorSetLayoutSupport( #[allow(non_snake_case)] pub unsafe extern "system" fn vkDestroySurfaceKHR( _instance: api::VkInstance, - _surface: api::VkSurfaceKHR, - _pAllocator: *const api::VkAllocationCallbacks, + surface: api::VkSurfaceKHR, + _allocator: *const api::VkAllocationCallbacks, ) { - unimplemented!() + if !surface.is_null() { + let surface = SharedHandle::from(surface); + match surface.platform { + api::VK_ICD_WSI_PLATFORM_MIR => { + panic!("unimplemented platform: VK_ICD_WSI_PLATFORM_MIR") + } + api::VK_ICD_WSI_PLATFORM_WAYLAND => { + panic!("unimplemented platform: VK_ICD_WSI_PLATFORM_WAYLAND") + } + api::VK_ICD_WSI_PLATFORM_WIN32 => { + panic!("unimplemented platform: VK_ICD_WSI_PLATFORM_WIN32") + } + api::VK_ICD_WSI_PLATFORM_XCB => { + Box::from_raw(surface.take().get().unwrap().as_ptr() as *mut api::VkIcdSurfaceXcb); + } + api::VK_ICD_WSI_PLATFORM_XLIB => { + panic!("unimplemented platform: VK_ICD_WSI_PLATFORM_XLIB") + } + api::VK_ICD_WSI_PLATFORM_ANDROID => { + panic!("unimplemented platform: VK_ICD_WSI_PLATFORM_ANDROID") + } + api::VK_ICD_WSI_PLATFORM_MACOS => { + panic!("unimplemented platform: VK_ICD_WSI_PLATFORM_MACOS") + } + api::VK_ICD_WSI_PLATFORM_IOS => { + panic!("unimplemented platform: VK_ICD_WSI_PLATFORM_IOS") + } + api::VK_ICD_WSI_PLATFORM_DISPLAY => { + panic!("unimplemented platform: VK_ICD_WSI_PLATFORM_DISPLAY") + } + platform => panic!("unknown VkSurfaceKHR platform: {:?}", platform), + } + } } #[allow(non_snake_case)] @@ -5160,50 +5200,117 @@ pub unsafe extern "system" fn vkGetPhysicalDeviceSurfaceSupportKHR( #[allow(non_snake_case)] pub unsafe extern "system" fn vkGetPhysicalDeviceSurfaceCapabilitiesKHR( - _physicalDevice: api::VkPhysicalDevice, - _surface: api::VkSurfaceKHR, - _pSurfaceCapabilities: *mut api::VkSurfaceCapabilitiesKHR, + physical_device: api::VkPhysicalDevice, + surface: api::VkSurfaceKHR, + surface_capabilities: *mut api::VkSurfaceCapabilitiesKHR, ) -> api::VkResult { - unimplemented!() + let mut surface_capabilities_2 = api::VkSurfaceCapabilities2KHR { + sType: api::VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR, + pNext: null_mut(), + surfaceCapabilities: mem::zeroed(), + }; + match vkGetPhysicalDeviceSurfaceCapabilities2KHR( + physical_device, + &api::VkPhysicalDeviceSurfaceInfo2KHR { + sType: api::VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR, + pNext: null(), + surface: surface, + }, + &mut surface_capabilities_2, + ) { + api::VK_SUCCESS => { + *surface_capabilities = surface_capabilities_2.surfaceCapabilities; + api::VK_SUCCESS + } + error => error, + } } #[allow(non_snake_case)] pub unsafe extern "system" fn vkGetPhysicalDeviceSurfaceFormatsKHR( - _physicalDevice: api::VkPhysicalDevice, - _surface: api::VkSurfaceKHR, - _pSurfaceFormatCount: *mut u32, - _pSurfaceFormats: *mut api::VkSurfaceFormatKHR, + _physical_device: api::VkPhysicalDevice, + surface: api::VkSurfaceKHR, + surface_format_count: *mut u32, + surface_formats: *mut api::VkSurfaceFormatKHR, ) -> api::VkResult { - unimplemented!() + let surface_implementation = SurfacePlatform::from(SharedHandle::from(surface).platform) + .unwrap() + .get_surface_implementation(); + let returned_surface_formats = match surface_implementation.get_surface_formats(surface) { + Ok(returned_surface_formats) => returned_surface_formats, + Err(result) => return result, + }; + enumerate_helper( + surface_format_count, + surface_formats, + returned_surface_formats.iter(), + |a, b| *a = *b, + ) } #[allow(non_snake_case)] pub unsafe extern "system" fn vkGetPhysicalDeviceSurfacePresentModesKHR( - _physicalDevice: api::VkPhysicalDevice, - _surface: api::VkSurfaceKHR, - _pPresentModeCount: *mut u32, - _pPresentModes: *mut api::VkPresentModeKHR, + _physical_device: api::VkPhysicalDevice, + surface: api::VkSurfaceKHR, + present_mode_count: *mut u32, + present_modes: *mut api::VkPresentModeKHR, ) -> api::VkResult { - unimplemented!() + let surface_implementation = SurfacePlatform::from(SharedHandle::from(surface).platform) + .unwrap() + .get_surface_implementation(); + let returned_present_modes = match surface_implementation.get_present_modes(surface) { + Ok(returned_present_modes) => returned_present_modes, + Err(result) => return result, + }; + enumerate_helper( + present_mode_count, + present_modes, + returned_present_modes.iter(), + |a, b| *a = *b, + ) } #[allow(non_snake_case)] pub unsafe extern "system" fn vkCreateSwapchainKHR( - _device: api::VkDevice, - _pCreateInfo: *const api::VkSwapchainCreateInfoKHR, - _pAllocator: *const api::VkAllocationCallbacks, - _pSwapchain: *mut api::VkSwapchainKHR, + device: api::VkDevice, + create_info: *const api::VkSwapchainCreateInfoKHR, + _allocator: *const api::VkAllocationCallbacks, + swapchain: *mut api::VkSwapchainKHR, ) -> api::VkResult { - unimplemented!() + parse_next_chain_const!{ + create_info, + root = api::VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, + device_group_swapchain_create_info: api::VkDeviceGroupSwapchainCreateInfoKHR = api::VK_STRUCTURE_TYPE_DEVICE_GROUP_SWAPCHAIN_CREATE_INFO_KHR, + } + let ref create_info = *create_info; + let device_group_swapchain_create_info = if device_group_swapchain_create_info.is_null() { + None + } else { + Some(&*device_group_swapchain_create_info) + }; + *swapchain = Handle::null(); + let platform = SurfacePlatform::from(SharedHandle::from(create_info.surface).platform).unwrap(); + match platform + .get_surface_implementation() + .build(create_info, device_group_swapchain_create_info) + { + Ok(new_swapchain) => { + *swapchain = OwnedHandle::::new(new_swapchain).take(); + api::VK_SUCCESS + } + Err(error) => error, + } } #[allow(non_snake_case)] pub unsafe extern "system" fn vkDestroySwapchainKHR( _device: api::VkDevice, - _swapchain: api::VkSwapchainKHR, - _pAllocator: *const api::VkAllocationCallbacks, + swapchain: api::VkSwapchainKHR, + _allocator: *const api::VkAllocationCallbacks, ) { - unimplemented!() + if !swapchain.is_null() { + OwnedHandle::from(swapchain); + } } #[allow(non_snake_case)] @@ -5474,11 +5581,31 @@ pub unsafe extern "system" fn vkGetFenceFdKHR( #[allow(non_snake_case)] pub unsafe extern "system" fn vkGetPhysicalDeviceSurfaceCapabilities2KHR( - _physicalDevice: api::VkPhysicalDevice, - _pSurfaceInfo: *const api::VkPhysicalDeviceSurfaceInfo2KHR, - _pSurfaceCapabilities: *mut api::VkSurfaceCapabilities2KHR, + _physical_device: api::VkPhysicalDevice, + surface_info: *const api::VkPhysicalDeviceSurfaceInfo2KHR, + surface_capabilities: *mut api::VkSurfaceCapabilities2KHR, ) -> api::VkResult { - unimplemented!() + parse_next_chain_const!{ + surface_info, + root = api::VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR, + } + let ref surface_info = *surface_info; + parse_next_chain_mut!{ + surface_capabilities, + root = api::VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR, + } + let ref mut surface_capabilities = *surface_capabilities; + let surface_implementation = + SurfacePlatform::from(SharedHandle::from(surface_info.surface).platform) + .unwrap() + .get_surface_implementation(); + match surface_implementation.get_capabilities(surface_info.surface) { + Ok(capabilities) => { + surface_capabilities.surfaceCapabilities = capabilities; + api::VK_SUCCESS + } + Err(result) => result, + } } #[allow(non_snake_case)] @@ -6054,11 +6181,26 @@ pub unsafe extern "system" fn vkGetQueueCheckpointDataNV( #[allow(non_snake_case)] pub unsafe extern "system" fn vkCreateXcbSurfaceKHR( _instance: api::VkInstance, - _pCreateInfo: *const api::VkXcbSurfaceCreateInfoKHR, - _pAllocator: *const api::VkAllocationCallbacks, - _pSurface: *mut api::VkSurfaceKHR, + create_info: *const api::VkXcbSurfaceCreateInfoKHR, + _allocator: *const api::VkAllocationCallbacks, + surface: *mut api::VkSurfaceKHR, ) -> api::VkResult { - unimplemented!() + parse_next_chain_const!{ + create_info, + root = api::VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR, + } + let ref create_info = *create_info; + let new_surface = Box::new(api::VkIcdSurfaceXcb { + base: api::VkIcdSurfaceBase { + platform: api::VK_ICD_WSI_PLATFORM_XCB, + }, + connection: create_info.connection, + window: create_info.window, + }); + *surface = api::VkSurfaceKHR::new(NonNull::new( + Box::into_raw(new_surface) as *mut api::VkIcdSurfaceBase + )); + api::VK_SUCCESS } #[cfg(unix)] diff --git a/vulkan-driver/src/handle.rs b/vulkan-driver/src/handle.rs index 559aa58..9db1077 100644 --- a/vulkan-driver/src/handle.rs +++ b/vulkan-driver/src/handle.rs @@ -9,6 +9,7 @@ use std::ops::Deref; use std::ops::DerefMut; use std::ptr::null_mut; use std::ptr::NonNull; +use swapchain::Swapchain; #[repr(C)] pub struct DispatchableType { @@ -16,8 +17,6 @@ pub struct DispatchableType { value: T, } -impl DispatchableType {} - impl From for DispatchableType { fn from(v: T) -> Self { Self { @@ -40,10 +39,7 @@ impl DerefMut for DispatchableType { } } -pub trait Handle: Copy + Eq + fmt::Debug { - type Value; - fn get(&self) -> Option>; - fn new(v: Option>) -> Self; +pub trait HandleAllocFree: Handle { unsafe fn allocate>(v: T) -> Self { Self::new(Some(NonNull::new_unchecked(Box::into_raw(Box::new( v.into(), @@ -52,6 +48,12 @@ pub trait Handle: Copy + Eq + fmt::Debug { unsafe fn free(self) { Box::from_raw(self.get().unwrap().as_ptr()); } +} + +pub trait Handle: Copy + Eq + fmt::Debug { + type Value; + fn get(&self) -> Option>; + fn new(v: Option>) -> Self; fn null() -> Self { Self::new(None) } @@ -87,7 +89,12 @@ impl PartialEq for DispatchableHandle { impl fmt::Debug for DispatchableHandle { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_tuple("DispatchableHandle") - .field(&self.get().map(|v| v.as_ptr()).unwrap_or(null_mut())) + .field( + &self + .get() + .map(|v| v.as_ptr()) + .unwrap_or(null_mut::<*mut ()>() as *mut _), + ) .finish() } } @@ -143,9 +150,9 @@ impl Handle for NondispatchableHandle { } #[repr(transparent)] -pub struct OwnedHandle(T); +pub struct OwnedHandle(T); -impl OwnedHandle { +impl OwnedHandle { pub fn new>(v: I) -> Self { unsafe { OwnedHandle(T::allocate(v)) } } @@ -160,20 +167,20 @@ impl OwnedHandle { } } -impl Deref for OwnedHandle { +impl Deref for OwnedHandle { type Target = T::Value; fn deref(&self) -> &T::Value { unsafe { &*self.0.get().unwrap().as_ptr() } } } -impl DerefMut for OwnedHandle { +impl DerefMut for OwnedHandle { fn deref_mut(&mut self) -> &mut T::Value { unsafe { &mut *self.0.get().unwrap().as_ptr() } } } -impl Drop for OwnedHandle { +impl Drop for OwnedHandle { fn drop(&mut self) { if !self.0.is_null() { unsafe { @@ -183,7 +190,7 @@ impl Drop for OwnedHandle { } } -impl fmt::Debug for OwnedHandle +impl fmt::Debug for OwnedHandle where T::Value: fmt::Debug, { @@ -227,128 +234,192 @@ where pub type VkInstance = DispatchableHandle; +impl HandleAllocFree for VkInstance {} + pub type VkPhysicalDevice = DispatchableHandle; +impl HandleAllocFree for VkPhysicalDevice {} + pub type VkDevice = DispatchableHandle; +impl HandleAllocFree for VkDevice {} + pub type VkQueue = DispatchableHandle; +impl HandleAllocFree for VkQueue {} + pub struct CommandBuffer {} pub type VkCommandBuffer = DispatchableHandle; +impl HandleAllocFree for VkCommandBuffer {} + pub struct Semaphore {} pub type VkSemaphore = NondispatchableHandle; +impl HandleAllocFree for VkSemaphore {} + pub struct Fence {} pub type VkFence = NondispatchableHandle; +impl HandleAllocFree for VkFence {} + pub struct DeviceMemory {} pub type VkDeviceMemory = NondispatchableHandle; +impl HandleAllocFree for VkDeviceMemory {} + pub struct Buffer {} pub type VkBuffer = NondispatchableHandle; +impl HandleAllocFree for VkBuffer {} + pub struct Image {} pub type VkImage = NondispatchableHandle; +impl HandleAllocFree for VkImage {} + pub struct Event {} pub type VkEvent = NondispatchableHandle; +impl HandleAllocFree for VkEvent {} + pub struct QueryPool {} pub type VkQueryPool = NondispatchableHandle; +impl HandleAllocFree for VkQueryPool {} + pub struct BufferView {} pub type VkBufferView = NondispatchableHandle; +impl HandleAllocFree for VkBufferView {} + pub struct ImageView {} pub type VkImageView = NondispatchableHandle; +impl HandleAllocFree for VkImageView {} + pub struct ShaderModule {} pub type VkShaderModule = NondispatchableHandle; +impl HandleAllocFree for VkShaderModule {} + pub struct PipelineCache {} pub type VkPipelineCache = NondispatchableHandle; +impl HandleAllocFree for VkPipelineCache {} + pub struct PipelineLayout {} pub type VkPipelineLayout = NondispatchableHandle; +impl HandleAllocFree for VkPipelineLayout {} + pub struct RenderPass {} pub type VkRenderPass = NondispatchableHandle; +impl HandleAllocFree for VkRenderPass {} + pub struct Pipeline {} pub type VkPipeline = NondispatchableHandle; +impl HandleAllocFree for VkPipeline {} + pub struct DescriptorSetLayout {} pub type VkDescriptorSetLayout = NondispatchableHandle; +impl HandleAllocFree for VkDescriptorSetLayout {} + pub struct Sampler {} pub type VkSampler = NondispatchableHandle; +impl HandleAllocFree for VkSampler {} + pub struct DescriptorPool {} pub type VkDescriptorPool = NondispatchableHandle; +impl HandleAllocFree for VkDescriptorPool {} + pub struct DescriptorSet {} pub type VkDescriptorSet = NondispatchableHandle; +impl HandleAllocFree for VkDescriptorSet {} + pub struct Framebuffer {} pub type VkFramebuffer = NondispatchableHandle; +impl HandleAllocFree for VkFramebuffer {} + pub struct CommandPool {} pub type VkCommandPool = NondispatchableHandle; +impl HandleAllocFree for VkCommandPool {} + pub struct SamplerYcbcrConversion {} pub type VkSamplerYcbcrConversion = NondispatchableHandle; +impl HandleAllocFree for VkSamplerYcbcrConversion {} + pub struct DescriptorUpdateTemplate {} pub type VkDescriptorUpdateTemplate = NondispatchableHandle; -pub struct SurfaceKHR {} +impl HandleAllocFree for VkDescriptorUpdateTemplate {} + +pub type VkSurfaceKHR = NondispatchableHandle; -pub type VkSurfaceKHR = NondispatchableHandle; +// HandleAllocFree specifically not implemented for VkSurfaceKHR -pub struct SwapchainKHR {} +pub type VkSwapchainKHR = NondispatchableHandle>; -pub type VkSwapchainKHR = NondispatchableHandle; +impl HandleAllocFree for VkSwapchainKHR {} pub struct DisplayKHR {} pub type VkDisplayKHR = NondispatchableHandle; +impl HandleAllocFree for VkDisplayKHR {} + pub struct DisplayModeKHR {} pub type VkDisplayModeKHR = NondispatchableHandle; +impl HandleAllocFree for VkDisplayModeKHR {} + pub struct DebugReportCallbackEXT {} pub type VkDebugReportCallbackEXT = NondispatchableHandle; +impl HandleAllocFree for VkDebugReportCallbackEXT {} + pub struct DebugUtilsMessengerEXT {} pub type VkDebugUtilsMessengerEXT = NondispatchableHandle; +impl HandleAllocFree for VkDebugUtilsMessengerEXT {} + pub struct ValidationCacheEXT {} pub type VkValidationCacheEXT = NondispatchableHandle; + +impl HandleAllocFree for VkValidationCacheEXT {} diff --git a/vulkan-driver/src/image.rs b/vulkan-driver/src/image.rs new file mode 100644 index 0000000..21c31ae --- /dev/null +++ b/vulkan-driver/src/image.rs @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright 2018 Jacob Lifshay +use api; + +pub enum SupportedTilings { + Any, + LinearOnly, +} + +pub struct ImageDescriptor { + pub supported_tilings: SupportedTilings, + pub format: api::VkFormat, + pub extents: api::VkExtent3D, + pub mip_levels: u32, +} diff --git a/vulkan-driver/src/lib.rs b/vulkan-driver/src/lib.rs index fa00c49..91068b6 100644 --- a/vulkan-driver/src/lib.rs +++ b/vulkan-driver/src/lib.rs @@ -2,6 +2,10 @@ // Copyright 2018 Jacob Lifshay #[macro_use] extern crate enum_map; +#[cfg(unix)] +extern crate errno; +#[cfg(unix)] +extern crate libc; extern crate sys_info; extern crate uuid; #[cfg(unix)] @@ -9,6 +13,12 @@ extern crate xcb; mod api; mod api_impl; mod handle; +mod image; +#[cfg(unix)] +mod shm; +mod swapchain; +#[cfg(unix)] +mod xcb_swapchain; use std::ffi::CStr; use std::os::raw::c_char; diff --git a/vulkan-driver/src/shm.rs b/vulkan-driver/src/shm.rs new file mode 100644 index 0000000..3aeb070 --- /dev/null +++ b/vulkan-driver/src/shm.rs @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright 2018 Jacob Lifshay +use errno; +use libc; +use std::marker::PhantomData; +use std::ops::Deref; +use std::ops::DerefMut; +use std::os::raw::c_int; +use std::ptr::null_mut; +use std::slice; + +#[derive(Debug)] +pub struct SharedMemorySegment { + id: c_int, + size: usize, + _phantom_data: PhantomData<*mut u8>, +} + +unsafe impl Send for SharedMemorySegment {} +unsafe impl Sync for SharedMemorySegment {} + +impl SharedMemorySegment { + pub unsafe fn new(id: c_int, size: usize) -> Self { + assert_ne!(size, 0); + assert_ne!(id, -1); + SharedMemorySegment { + id, + size, + _phantom_data: PhantomData, + } + } + pub unsafe fn create_with_flags(size: usize, flags: c_int) -> Result { + match libc::shmget(libc::IPC_PRIVATE, size, flags) { + -1 => Err(errno::errno()), + id => Ok(Self::new(id, size)), + } + } + pub fn create(size: usize) -> Result { + unsafe { Self::create_with_flags(size, libc::IPC_CREAT | libc::IPC_EXCL | 0o666) } + } + pub fn map(&self) -> Result { + unsafe { + let memory = libc::shmat(self.id, null_mut(), 0); + if memory == !0usize as *mut _ { + Err(errno::errno()) + } else { + Ok(MappedSharedMemorySegment { + memory: memory as *mut u8, + size: self.size, + }) + } + } + } +} + +impl Drop for SharedMemorySegment { + fn drop(&mut self) { + unsafe { + libc::shmctl(self.id, libc::IPC_RMID, null_mut()); + } + } +} + +#[derive(Debug)] +pub struct MappedSharedMemorySegment { + memory: *mut u8, + size: usize, +} + +impl MappedSharedMemorySegment { + unsafe fn get(&self) -> *mut [u8] { + slice::from_raw_parts_mut(self.memory, self.size) + } +} + +unsafe impl Send for MappedSharedMemorySegment {} +unsafe impl Sync for MappedSharedMemorySegment {} + +impl Deref for MappedSharedMemorySegment { + type Target = [u8]; + fn deref(&self) -> &[u8] { + unsafe { &*self.get() } + } +} + +impl DerefMut for MappedSharedMemorySegment { + fn deref_mut(&mut self) -> &mut [u8] { + unsafe { &mut *self.get() } + } +} + +impl Drop for MappedSharedMemorySegment { + fn drop(&mut self) { + unsafe { + libc::shmdt(self.memory as *const _); + } + } +} diff --git a/vulkan-driver/src/swapchain.rs b/vulkan-driver/src/swapchain.rs new file mode 100644 index 0000000..c86647b --- /dev/null +++ b/vulkan-driver/src/swapchain.rs @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright 2018 Jacob Lifshay +use api; +use std::any::Any; +use std::borrow::Cow; +use std::error::Error; +use std::fmt::{self, Debug}; +#[cfg(unix)] +use xcb_swapchain::XcbSurfaceImplementation; + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Enum)] +#[allow(non_camel_case_types)] +pub enum SurfacePlatform { + VK_ICD_WSI_PLATFORM_MIR, + VK_ICD_WSI_PLATFORM_WAYLAND, + VK_ICD_WSI_PLATFORM_WIN32, + VK_ICD_WSI_PLATFORM_XCB, + VK_ICD_WSI_PLATFORM_XLIB, + VK_ICD_WSI_PLATFORM_ANDROID, + VK_ICD_WSI_PLATFORM_MACOS, + VK_ICD_WSI_PLATFORM_IOS, + VK_ICD_WSI_PLATFORM_DISPLAY, +} + +#[derive(Debug)] +pub struct UnknownSurfacePlatform(pub api::VkIcdWsiPlatform); + +impl fmt::Display for UnknownSurfacePlatform { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "unknown surface platform {:?}", self.0) + } +} + +impl Error for UnknownSurfacePlatform {} + +impl SurfacePlatform { + pub fn from(platform: api::VkIcdWsiPlatform) -> Result { + match platform { + api::VK_ICD_WSI_PLATFORM_MIR => Ok(SurfacePlatform::VK_ICD_WSI_PLATFORM_MIR), + api::VK_ICD_WSI_PLATFORM_WAYLAND => Ok(SurfacePlatform::VK_ICD_WSI_PLATFORM_WAYLAND), + api::VK_ICD_WSI_PLATFORM_WIN32 => Ok(SurfacePlatform::VK_ICD_WSI_PLATFORM_WIN32), + api::VK_ICD_WSI_PLATFORM_XCB => Ok(SurfacePlatform::VK_ICD_WSI_PLATFORM_XCB), + api::VK_ICD_WSI_PLATFORM_XLIB => Ok(SurfacePlatform::VK_ICD_WSI_PLATFORM_XLIB), + api::VK_ICD_WSI_PLATFORM_ANDROID => Ok(SurfacePlatform::VK_ICD_WSI_PLATFORM_ANDROID), + api::VK_ICD_WSI_PLATFORM_MACOS => Ok(SurfacePlatform::VK_ICD_WSI_PLATFORM_MACOS), + api::VK_ICD_WSI_PLATFORM_IOS => Ok(SurfacePlatform::VK_ICD_WSI_PLATFORM_IOS), + api::VK_ICD_WSI_PLATFORM_DISPLAY => Ok(SurfacePlatform::VK_ICD_WSI_PLATFORM_DISPLAY), + platform => Err(UnknownSurfacePlatform(platform)), + } + } + pub fn get_surface_implementation(self) -> Cow<'static, dyn SurfaceImplementation> { + #[cfg(unix)] + const XCB_SURFACE_IMPLEMENTATION: XcbSurfaceImplementation = XcbSurfaceImplementation; + match self { + #[cfg(unix)] + SurfacePlatform::VK_ICD_WSI_PLATFORM_XCB => Cow::Borrowed(&XCB_SURFACE_IMPLEMENTATION), + _ => Cow::Owned(FallbackSurfaceImplementation(self).duplicate()), + } + } +} + +impl From for api::VkIcdWsiPlatform { + fn from(platform: SurfacePlatform) -> api::VkIcdWsiPlatform { + match platform { + SurfacePlatform::VK_ICD_WSI_PLATFORM_MIR => api::VK_ICD_WSI_PLATFORM_MIR, + SurfacePlatform::VK_ICD_WSI_PLATFORM_WAYLAND => api::VK_ICD_WSI_PLATFORM_WAYLAND, + SurfacePlatform::VK_ICD_WSI_PLATFORM_WIN32 => api::VK_ICD_WSI_PLATFORM_WIN32, + SurfacePlatform::VK_ICD_WSI_PLATFORM_XCB => api::VK_ICD_WSI_PLATFORM_XCB, + SurfacePlatform::VK_ICD_WSI_PLATFORM_XLIB => api::VK_ICD_WSI_PLATFORM_XLIB, + SurfacePlatform::VK_ICD_WSI_PLATFORM_ANDROID => api::VK_ICD_WSI_PLATFORM_ANDROID, + SurfacePlatform::VK_ICD_WSI_PLATFORM_MACOS => api::VK_ICD_WSI_PLATFORM_MACOS, + SurfacePlatform::VK_ICD_WSI_PLATFORM_IOS => api::VK_ICD_WSI_PLATFORM_IOS, + SurfacePlatform::VK_ICD_WSI_PLATFORM_DISPLAY => api::VK_ICD_WSI_PLATFORM_DISPLAY, + } + } +} + +pub trait Swapchain: Any + Sync + Send + Debug {} + +pub trait SurfaceImplementation: Any + Sync + Send + Debug { + fn get_platform(&self) -> SurfacePlatform; + unsafe fn get_surface_formats( + &self, + surface: api::VkSurfaceKHR, + ) -> Result, api::VkResult>; + unsafe fn get_present_modes( + &self, + surface: api::VkSurfaceKHR, + ) -> Result, api::VkResult>; + unsafe fn get_capabilities( + &self, + surface: api::VkSurfaceKHR, + ) -> Result; + unsafe fn build( + &self, + create_info: &api::VkSwapchainCreateInfoKHR, + device_group_create_info: Option<&api::VkDeviceGroupSwapchainCreateInfoKHR>, + ) -> Result, api::VkResult>; + fn duplicate(&self) -> Box; +} + +impl ToOwned for dyn SurfaceImplementation { + type Owned = Box; + fn to_owned(&self) -> Box { + self.duplicate() + } +} + +#[derive(Debug)] +pub struct FallbackSurfaceImplementation(SurfacePlatform); + +impl FallbackSurfaceImplementation { + pub fn report_error(&self) -> ! { + panic!( + "there is no surface implementation for {:?}", + self.get_platform() + ) + } +} + +impl SurfaceImplementation for FallbackSurfaceImplementation { + fn get_platform(&self) -> SurfacePlatform { + self.0 + } + unsafe fn get_surface_formats( + &self, + _surface: api::VkSurfaceKHR, + ) -> Result, api::VkResult> { + self.report_error() + } + unsafe fn get_present_modes( + &self, + surface: api::VkSurfaceKHR, + ) -> Result, api::VkResult> { + self.report_error() + } + unsafe fn get_capabilities( + &self, + surface: api::VkSurfaceKHR, + ) -> Result { + self.report_error() + } + unsafe fn build( + &self, + _create_info: &api::VkSwapchainCreateInfoKHR, + _device_group_create_info: Option<&api::VkDeviceGroupSwapchainCreateInfoKHR>, + ) -> Result, api::VkResult> { + self.report_error() + } + fn duplicate(&self) -> Box { + Box::new(FallbackSurfaceImplementation(self.0)) + } +} diff --git a/vulkan-driver/src/xcb_swapchain.rs b/vulkan-driver/src/xcb_swapchain.rs new file mode 100644 index 0000000..8fdff80 --- /dev/null +++ b/vulkan-driver/src/xcb_swapchain.rs @@ -0,0 +1,413 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright 2018 Jacob Lifshay +use api; +use handle::Handle; +use image::{ImageDescriptor, SupportedTilings}; +use libc; +use std::borrow::Cow; +use std::mem; +use std::ops::{Deref, DerefMut}; +use std::os::raw::c_char; +use std::ptr::null_mut; +use std::ptr::NonNull; +use swapchain::{SurfaceImplementation, SurfacePlatform, Swapchain}; +use xcb; + +#[derive(Debug)] +pub struct XcbSwapchain {} + +impl Swapchain for XcbSwapchain {} + +struct ReplyObject(NonNull); + +impl ReplyObject { + unsafe fn from(v: *mut T) -> Option { + NonNull::new(v).map(ReplyObject) + } +} + +impl Deref for ReplyObject { + type Target = T; + fn deref(&self) -> &T { + unsafe { self.0.as_ref() } + } +} + +impl DerefMut for ReplyObject { + fn deref_mut(&mut self) -> &mut T { + unsafe { self.0.as_mut() } + } +} + +impl Drop for ReplyObject { + fn drop(&mut self) { + unsafe { + libc::free(self.0.as_ptr() as *mut libc::c_void); + } + } +} + +struct ServerObject { + id: Id, + connection: *mut xcb::ffi::xcb_connection_t, + free_fn: unsafe extern "C" fn(connection: *mut xcb::ffi::xcb_connection_t, id: Id) + -> xcb::ffi::xcb_void_cookie_t, +} + +impl ServerObject { + fn get(&self) -> Id { + self.id + } +} + +impl Drop for ServerObject { + fn drop(&mut self) { + unsafe { + (self.free_fn)(self.connection, self.id); + } + } +} + +type Gc = ServerObject; + +unsafe fn create_gc( + id: xcb::ffi::xcb_gcontext_t, + connection: *mut xcb::ffi::xcb_connection_t, +) -> Gc { + ServerObject { + id, + connection, + free_fn: xcb::ffi::xcb_free_gc, + } +} + +type Pixmap = ServerObject; + +unsafe fn create_pixmap( + id: xcb::ffi::xcb_pixmap_t, + connection: *mut xcb::ffi::xcb_connection_t, +) -> Pixmap { + ServerObject { + id, + connection, + free_fn: xcb::ffi::xcb_free_pixmap, + } +} + +type ShmSeg = ServerObject; + +unsafe fn create_shm_seg( + id: xcb::ffi::shm::xcb_shm_seg_t, + connection: *mut xcb::ffi::xcb_connection_t, +) -> ShmSeg { + ServerObject { + id, + connection, + free_fn: xcb::ffi::shm::xcb_shm_detach, + } +} + +enum SurfaceFormatGroup { + B8G8R8A8, +} + +enum SwapchainSetupError { + BadSurface, + NoSupport, +} + +unsafe fn query_extension( + connection: *mut xcb::ffi::xcb_connection_t, + extension_name: &str, +) -> xcb::ffi::xcb_query_extension_cookie_t { + let len = extension_name.len() as u16; + assert_eq!(len as usize, extension_name.len()); + xcb::ffi::xcb_query_extension(connection, len, extension_name.as_ptr() as *const c_char) +} + +pub const MAX_SWAPCHAIN_IMAGE_COUNT: u32 = 16; + +struct SwapchainSetupFirstStage { + gc: Gc, + shm_supported: bool, + window_depth: u8, + surface_format_group: SurfaceFormatGroup, + present_modes: &'static [api::VkPresentModeKHR], + capabilities: api::VkSurfaceCapabilitiesKHR, + shared_present_capabilities: Option, + image_pixel_size: usize, + scanline_alignment: usize, + shm_version: Option, + image_descriptor: ImageDescriptor, +} + +impl SwapchainSetupFirstStage { + unsafe fn new( + connection: *mut xcb::ffi::xcb_connection_t, + window: xcb::ffi::xcb_window_t, + is_full_setup: bool, + ) -> Result { + let has_mit_shm = query_extension(connection, "MIT-SHM"); + let geometry = xcb::ffi::xcb_get_geometry(connection, window); + let window_attributes = xcb::ffi::xcb_get_window_attributes(connection, window); + let tree = xcb::ffi::xcb_query_tree(connection, window); + let gc = xcb::ffi::xcb_generate_id(connection); + xcb::ffi::xcb_create_gc( + connection, + gc, + window, + xcb::ffi::XCB_GC_GRAPHICS_EXPOSURES, + [0].as_ptr(), + ); + let gc = create_gc(gc, connection); + let has_mit_shm = ReplyObject::from(xcb::ffi::xcb_query_extension_reply( + connection, + has_mit_shm, + null_mut(), + )); + let shm_supported = has_mit_shm.map(|v| v.present != 0).unwrap_or(false); + let shm_version = if is_full_setup && shm_supported { + Some(xcb::ffi::shm::xcb_shm_query_version(connection)) + } else { + None + }; + let geometry = ReplyObject::from(xcb::ffi::xcb_get_geometry_reply( + connection, + geometry, + null_mut(), + )) + .ok_or(SwapchainSetupError::BadSurface)?; + let image_extent = api::VkExtent2D { + width: geometry.width as u32, + height: geometry.height as u32, + }; + mem::drop(geometry); + let window_attributes = ReplyObject::from(xcb::ffi::xcb_get_window_attributes_reply( + connection, + window_attributes, + null_mut(), + )) + .ok_or(SwapchainSetupError::BadSurface)?; + let window_visual_id = window_attributes.visual; + mem::drop(window_attributes); + let tree = ReplyObject::from(xcb::ffi::xcb_query_tree_reply(connection, tree, null_mut())) + .ok_or(SwapchainSetupError::BadSurface)?; + let root_window = tree.root; + mem::drop(tree); + let mut screen = None; + let mut roots_iter = + xcb::ffi::xcb_setup_roots_iterator(xcb::ffi::xcb_get_setup(connection)); + while roots_iter.rem != 0 { + if (*roots_iter.data).root == root_window { + screen = Some(roots_iter.data); + break; + } + xcb::ffi::xcb_screen_next(&mut roots_iter); + } + let screen = screen.ok_or(SwapchainSetupError::BadSurface)?; + let mut window_visual_type_and_depth = None; + let mut depth_iter = xcb::ffi::xcb_screen_allowed_depths_iterator(screen); + while depth_iter.rem != 0 { + let mut visual_iter = xcb::ffi::xcb_depth_visuals_iterator(depth_iter.data); + while visual_iter.rem != 0 { + if (*visual_iter.data).visual_id == window_visual_id { + window_visual_type_and_depth = + Some((visual_iter.data, (*depth_iter.data).depth)); + break; + } + xcb::ffi::xcb_visualtype_next(&mut visual_iter); + } + if window_visual_type_and_depth.is_some() { + break; + } + xcb::ffi::xcb_depth_next(&mut depth_iter); + } + let (window_visual_type, window_depth) = + window_visual_type_and_depth.ok_or(SwapchainSetupError::BadSurface)?; + let ref window_visual_type = *window_visual_type; + let red_mask = window_visual_type.red_mask; + let green_mask = window_visual_type.green_mask; + let blue_mask = window_visual_type.blue_mask; + let alpha_mask = match window_depth { + 24 => 0, + 32 => !(red_mask | green_mask | blue_mask), + _ => return Err(SwapchainSetupError::NoSupport), + }; + let mut window_pixmap_format = None; + let mut formats_iter = + xcb::ffi::xcb_setup_pixmap_formats_iterator(xcb::ffi::xcb_get_setup(connection)); + while formats_iter.rem != 0 { + if (*formats_iter.data).depth == window_depth { + window_pixmap_format = Some(formats_iter.data); + break; + } + xcb::ffi::xcb_format_next(&mut formats_iter); + } + let ref window_pixmap_format = + *(window_pixmap_format.ok_or(SwapchainSetupError::BadSurface)?); + let image_pixel_size = match window_pixmap_format.bits_per_pixel { + 24 => 3, + 32 => 4, + _ => return Err(SwapchainSetupError::NoSupport), + }; + fn u32_from_bytes(v: [u8; 4]) -> u32 { + unsafe { mem::transmute(v) } + } + let surface_format_group = match ( + u32_from_bytes([0xFF, 0, 0, 0]), + u32_from_bytes([0, 0xFF, 0, 0]), + u32_from_bytes([0, 0, 0xFF, 0]), + u32_from_bytes([0, 0, 0, 0xFF]), + ) { + (r, g, b, a) + if r == red_mask + && g == green_mask + && b == blue_mask + && (alpha_mask == 0 || a == alpha_mask) => + { + SurfaceFormatGroup::B8G8R8A8 + } + _ => return Err(SwapchainSetupError::NoSupport), + }; + let scanline_alignment = match window_pixmap_format.scanline_pad { + 8 => 1, + 16 => 2, + 32 => 4, + _ => unreachable!("invalid pixmap format scanline_pad"), + }; + const PRESENT_MODES: &'static [api::VkPresentModeKHR] = &[ + api::VK_PRESENT_MODE_FIFO_KHR, // FIXME: properly implement FIFO present mode using X11 Present extension + api::VK_PRESENT_MODE_IMMEDIATE_KHR, + ]; + Ok(Self { + gc, + shm_supported, + window_depth, + surface_format_group, + present_modes: PRESENT_MODES, + capabilities: api::VkSurfaceCapabilitiesKHR { + minImageCount: 2, + maxImageCount: MAX_SWAPCHAIN_IMAGE_COUNT, + currentExtent: image_extent, + minImageExtent: image_extent, + maxImageExtent: image_extent, + maxImageArrayLayers: 1, + supportedTransforms: api::VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR, + currentTransform: api::VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR, + supportedCompositeAlpha: api::VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + supportedUsageFlags: api::VK_IMAGE_USAGE_TRANSFER_SRC_BIT + | api::VK_IMAGE_USAGE_TRANSFER_DST_BIT + | api::VK_IMAGE_USAGE_SAMPLED_BIT + | api::VK_IMAGE_USAGE_STORAGE_BIT + | api::VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT + | api::VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT, + }, + shared_present_capabilities: None, + image_pixel_size, + scanline_alignment, + shm_version, + image_descriptor: ImageDescriptor { + supported_tilings: SupportedTilings::Any, + format: api::VK_FORMAT_UNDEFINED, + extents: api::VkExtent3D { + width: image_extent.width, + height: image_extent.height, + depth: 1, + }, + mip_levels: 1, + }, + }) + } +} + +impl XcbSwapchain { + unsafe fn new( + create_info: &api::VkSwapchainCreateInfoKHR, + device_group_create_info: Option<&api::VkDeviceGroupSwapchainCreateInfoKHR>, + ) -> Result { + unimplemented!() + } +} + +#[derive(Debug)] +pub struct XcbSurfaceImplementation; + +impl XcbSurfaceImplementation { + unsafe fn get_surface(&self, surface: api::VkSurfaceKHR) -> &api::VkIcdSurfaceXcb { + let surface = surface.get().unwrap().as_ptr(); + assert_eq!((*surface).platform, api::VK_ICD_WSI_PLATFORM_XCB); + &*(surface as *const api::VkIcdSurfaceXcb) + } +} + +impl SurfaceImplementation for XcbSurfaceImplementation { + fn get_platform(&self) -> SurfacePlatform { + SurfacePlatform::VK_ICD_WSI_PLATFORM_XCB + } + unsafe fn get_surface_formats( + &self, + surface: api::VkSurfaceKHR, + ) -> Result, api::VkResult> { + let surface = &self.get_surface(surface); + let first_stage = SwapchainSetupFirstStage::new(surface.connection, surface.window, false) + .map_err(|v| match v { + SwapchainSetupError::BadSurface | SwapchainSetupError::NoSupport => { + api::VK_ERROR_SURFACE_LOST_KHR + } + })?; + match first_stage.surface_format_group { + SurfaceFormatGroup::B8G8R8A8 => { + const SURFACE_FORMATS: &'static [api::VkSurfaceFormatKHR] = &[ + api::VkSurfaceFormatKHR { + format: api::VK_FORMAT_B8G8R8A8_SRGB, + colorSpace: api::VK_COLOR_SPACE_SRGB_NONLINEAR_KHR, + }, + api::VkSurfaceFormatKHR { + format: api::VK_FORMAT_B8G8R8A8_UNORM, + colorSpace: api::VK_COLOR_SPACE_SRGB_NONLINEAR_KHR, + }, + ]; + Ok(Cow::Borrowed(SURFACE_FORMATS)) + } + } + } + unsafe fn get_present_modes( + &self, + surface: api::VkSurfaceKHR, + ) -> Result, api::VkResult> { + let surface = &self.get_surface(surface); + let first_stage = SwapchainSetupFirstStage::new(surface.connection, surface.window, false) + .map_err(|v| match v { + SwapchainSetupError::BadSurface | SwapchainSetupError::NoSupport => { + api::VK_ERROR_SURFACE_LOST_KHR + } + })?; + Ok(Cow::Borrowed(first_stage.present_modes)) + } + unsafe fn get_capabilities( + &self, + surface: api::VkSurfaceKHR, + ) -> Result { + let surface = &self.get_surface(surface); + let first_stage = SwapchainSetupFirstStage::new(surface.connection, surface.window, false) + .map_err(|v| match v { + SwapchainSetupError::BadSurface | SwapchainSetupError::NoSupport => { + api::VK_ERROR_SURFACE_LOST_KHR + } + })?; + Ok(first_stage.capabilities) + } + unsafe fn build( + &self, + create_info: &api::VkSwapchainCreateInfoKHR, + device_group_create_info: Option<&api::VkDeviceGroupSwapchainCreateInfoKHR>, + ) -> Result, api::VkResult> { + Ok(Box::new(XcbSwapchain::new( + create_info, + device_group_create_info, + )?)) + } + fn duplicate(&self) -> Box { + Box::new(Self {}) + } +} diff --git a/vulkan-driver/vulkan-wrapper.h b/vulkan-driver/vulkan-wrapper.h index 956139e..ddc15bf 100644 --- a/vulkan-driver/vulkan-wrapper.h +++ b/vulkan-driver/vulkan-wrapper.h @@ -4,13 +4,13 @@ #ifdef __ANDROID__ #error not supported on Android; need to fix ABI #endif +#ifdef __unix +#define VK_USE_PLATFORM_XCB_KHR +#endif #define VK_NO_PROTOTYPES #include #include -#ifdef __unix -typedef struct xcb_connection_t xcb_connection_t; -typedef uint32_t xcb_visualid_t; -typedef uint32_t xcb_window_t; -#include -#endif #undef VK_NO_PROTOTYPES +#ifdef VK_USE_PLATFORM_XCB_KHR +#undef VK_USE_PLATFORM_XCB_KHR +#endif -- 2.30.2