diff --git a/gui/src/graphics/metal.rs b/gui/src/graphics/metal.rs new file mode 100644 index 00000000..701a66b6 --- /dev/null +++ b/gui/src/graphics/metal.rs @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +use self::buffer::MetalBuffer; +use super::{Screen, ScreenBuffer}; +use crate::vmm::VmmScreen; +use metal::{CAMetalLayer, Device, MetalLayer}; +use objc::runtime::{Object, NO, YES}; +use objc::{msg_send, sel, sel_impl}; +use std::ptr::null_mut; +use std::sync::Arc; +use thiserror::Error; + +pub struct Metal { + devices: Vec, +} + +impl super::GraphicsApi for Metal { + type PhysicalDevice = metal::Device; + + type CreateError = MetalCreateError; + + fn new() -> Result { + Ok(Self { + devices: Device::all(), + }) + } + + fn physical_devices(&self) -> &[Self::PhysicalDevice] { + &self.devices + } +} + +impl super::PhysicalDevice for metal::Device { + fn name(&self) -> &str { + self.name() + } +} + +/// Represents an error when [`Metal::new()`] fails. +#[derive(Debug, Error)] +pub enum MetalCreateError {} diff --git a/gui/src/graphics/mod.rs b/gui/src/graphics/mod.rs new file mode 100644 index 00000000..4416d993 --- /dev/null +++ b/gui/src/graphics/mod.rs @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +#[cfg_attr(target_os = "macos", path = "metal.rs")] +#[cfg_attr(not(target_os = "macos"), path = "vulkan.rs")] +mod api; + +#[cfg(not(target_os = "macos"))] +pub type DefaultApi = self::api::Vulkan; + +#[cfg(target_os = "macos")] +pub type DefaultApi = self::api::Metal; + +pub trait GraphicsApi: Sized + 'static { + type PhysicalDevice: PhysicalDevice; + + type CreateError: core::error::Error; + + fn new() -> Result; + + fn physical_devices(&self) -> &[Self::PhysicalDevice]; +} + +pub trait PhysicalDevice: Sized { + fn name(&self) -> &str; +} diff --git a/gui/src/graphics/vulkan.rs b/gui/src/graphics/vulkan.rs new file mode 100644 index 00000000..5f5aacbe --- /dev/null +++ b/gui/src/graphics/vulkan.rs @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +use ash::vk::{ApplicationInfo, InstanceCreateInfo}; +use std::ffi::CStr; +use thiserror::Error; + +pub struct Vulkan { + entry: ash::Entry, + instance: ash::Instance, + devices: Vec, +} + +impl super::GraphicsApi for Vulkan { + type PhysicalDevice = VulkanPhysicalDevice; + + type CreateError = VulkanCreateError; + + fn new() -> Result { + let entry = ash::Entry::linked(); + + let app_info = ApplicationInfo::default().application_name(c"Obliteration"); + + let create_info = InstanceCreateInfo::default().application_info(&app_info); + + let instance = unsafe { entry.create_instance(&create_info, None) } + .map_err(VulkanCreateError::CreateInstanceFailed)?; + + let devices = unsafe { instance.enumerate_physical_devices() } + .map_err(VulkanCreateError::EnumeratePhysicalDevicesFailed)? + .into_iter() + .map( + |device| -> Result { + let properties = unsafe { instance.get_physical_device_properties(device) }; + + let name = CStr::from_bytes_until_nul(unsafe { + std::slice::from_raw_parts( + properties.device_name.as_ptr().cast(), + properties.device_name.len(), + ) + }) + .map_err(|_| VulkanCreateError::DeviceNameInvalid)? + .to_str() + .map_err(VulkanCreateError::DeviceNameInvalidUtf8)? + .to_owned(); + + Ok(VulkanPhysicalDevice { device, name }) + }, + ) + .collect::>()?; + + Ok(Self { + entry, + instance, + devices, + }) + } + + fn physical_devices(&self) -> &[Self::PhysicalDevice] { + &self.devices + } +} + +impl Drop for Vulkan { + fn drop(&mut self) { + unsafe { self.instance.destroy_instance(None) }; + } +} + +pub struct VulkanPhysicalDevice { + device: ash::vk::PhysicalDevice, + name: String, +} + +impl super::PhysicalDevice for VulkanPhysicalDevice { + fn name(&self) -> &str { + &self.name + } +} + +/// Represents an error when [`Vulkan::new()`] fails. +#[derive(Debug, Error)] +pub enum VulkanCreateError { + #[error("couldn't create Vulkan instance")] + CreateInstanceFailed(#[source] ash::vk::Result), + + #[error("couldn't enumerate physical devices")] + EnumeratePhysicalDevicesFailed(#[source] ash::vk::Result), + + #[error("no null byte in device name")] + DeviceNameInvalid, + + #[error("device name is not valid UTF-8")] + DeviceNameInvalidUtf8(#[source] std::str::Utf8Error), +} diff --git a/gui/src/lib.rs b/gui/src/lib.rs index 8220cba5..c7d7fca5 100644 --- a/gui/src/lib.rs +++ b/gui/src/lib.rs @@ -26,6 +26,7 @@ pub unsafe extern "C-unwind" fn set_panic_hook( let line = loc.line(); // Get message. + //TODO: use payload_as_str() when https://github.com/rust-lang/rust/issues/125175 is stable. let msg = if let Some(&p) = info.payload().downcast_ref::<&str>() { p } else if let Some(p) = info.payload().downcast_ref::() { diff --git a/gui/src/main.rs b/gui/src/main.rs index 1f3f65bd..922d4b83 100644 --- a/gui/src/main.rs +++ b/gui/src/main.rs @@ -1,6 +1,7 @@ use args::CliArgs; use clap::Parser; use debug::DebugServer; +use graphics::{GraphicsApi, PhysicalDevice}; use slint::{ComponentHandle, ModelExt, ModelRc, SharedString, VecModel}; use std::process::{ExitCode, Termination}; use thiserror::Error; @@ -8,6 +9,7 @@ use thiserror::Error; mod args; mod debug; mod error; +mod graphics; mod param; mod pkg; mod profile; @@ -70,6 +72,17 @@ impl App { fn new() -> Result { let main_window = ui::MainWindow::new().map_err(ApplicationError::CreateMainWindow)?; + let graphics_api = + graphics::DefaultApi::new().map_err(ApplicationError::InitGraphicsApi)?; + + let devices: Vec = graphics_api + .physical_devices() + .into_iter() + .map(|d| SharedString::from(d.name())) + .collect(); + + main_window.set_devices(ModelRc::new(VecModel::from(devices))); + let games = ModelRc::new(VecModel::from(Vec::new())); main_window.set_games(games.clone()); @@ -172,6 +185,9 @@ pub enum ApplicationError { #[error("failed to create main window")] CreateMainWindow(#[source] slint::PlatformError), + #[error("failed to initialize graphics API")] + InitGraphicsApi(#[source] ::CreateError), + #[error("failed to run main window")] RunMainWindow(#[source] slint::PlatformError), } diff --git a/gui/src/screen/metal/mod.rs b/gui/src/screen/metal/mod.rs index a6a32ef9..883ae3e3 100644 --- a/gui/src/screen/metal/mod.rs +++ b/gui/src/screen/metal/mod.rs @@ -14,14 +14,14 @@ mod buffer; /// Implementation of [`Screen`] using Metal. /// /// Fields in this struct need to be dropped in a correct order. -pub struct Metal { +pub struct MetalScreen { view: *mut Object, buffer: Arc, layer: MetalLayer, device: Device, } -impl Metal { +impl MetalScreen { pub fn from_screen(screen: &VmmScreen) -> Result { // Get Metal device. let device = match Device::system_default() { @@ -49,7 +49,7 @@ impl Metal { } } -impl Drop for Metal { +impl Drop for MetalScreen { fn drop(&mut self) { let l: *mut CAMetalLayer = null_mut(); let _: () = unsafe { msg_send![self.view, setWantsLayer:NO] }; @@ -57,7 +57,7 @@ impl Drop for Metal { } } -impl Screen for Metal { +impl Screen for MetalScreen { type Buffer = MetalBuffer; type UpdateErr = UpdateError; @@ -70,7 +70,7 @@ impl Screen for Metal { } } -/// Represents an error when [`Metal::new()`] fails. +/// Represents an error when [`MetalScreen::new()`] fails. #[derive(Debug, Error)] pub enum MetalError { #[error("couldn't get default MTLDevice")] diff --git a/gui/src/screen/mod.rs b/gui/src/screen/mod.rs index dfe69738..e535f5ea 100644 --- a/gui/src/screen/mod.rs +++ b/gui/src/screen/mod.rs @@ -7,13 +7,13 @@ use std::sync::Arc; mod engine; #[cfg(not(target_os = "macos"))] -pub type Default = self::engine::Vulkan; +pub type Default = self::engine::VulkanScreen; #[cfg(target_os = "macos")] -pub type Default = self::engine::Metal; +pub type Default = self::engine::MetalScreen; #[cfg(not(target_os = "macos"))] -pub type ScreenError = self::engine::VulkanError; +pub type ScreenError = self::engine::VulkanScreenError; #[cfg(target_os = "macos")] pub type ScreenError = self::engine::MetalError; diff --git a/gui/src/screen/vulkan/mod.rs b/gui/src/screen/vulkan/mod.rs index e1e30cc1..30777575 100644 --- a/gui/src/screen/vulkan/mod.rs +++ b/gui/src/screen/vulkan/mod.rs @@ -10,13 +10,13 @@ use thiserror::Error; mod buffer; /// Implementation of [`Screen`] using Vulkan. -pub struct Vulkan { +pub struct VulkanScreen { buffer: Arc, device: Device, } -impl Vulkan { - pub fn from_screen(screen: &VmmScreen) -> Result { +impl VulkanScreen { + pub fn from_screen(screen: &VmmScreen) -> Result { let entry = ash::Entry::linked(); let instance = unsafe { @@ -34,11 +34,11 @@ impl Vulkan { let queue = unsafe { instance.get_physical_device_queue_family_properties(physical) } .into_iter() .position(|p| p.queue_flags.contains(QueueFlags::GRAPHICS)) - .ok_or(VulkanError::NoQueue)?; + .ok_or(VulkanScreenError::NoQueue)?; let queue = queue .try_into() - .map_err(|_| VulkanError::QueueOutOfBounds(queue))?; + .map_err(|_| VulkanScreenError::QueueOutOfBounds(queue))?; let queues = DeviceQueueCreateInfo::default() .queue_family_index(queue) @@ -47,7 +47,7 @@ impl Vulkan { // Create logical device. let device = DeviceCreateInfo::default().queue_create_infos(std::slice::from_ref(&queues)); let device = unsafe { instance.create_device(physical, &device, None) } - .map_err(VulkanError::CreateDeviceFailed)?; + .map_err(VulkanScreenError::CreateDeviceFailed)?; Ok(Self { buffer: Arc::new(VulkanBuffer::new()), @@ -56,14 +56,14 @@ impl Vulkan { } } -impl Drop for Vulkan { +impl Drop for VulkanScreen { fn drop(&mut self) { unsafe { self.device.device_wait_idle().unwrap() }; unsafe { self.device.destroy_device(None) }; } } -impl Screen for Vulkan { +impl Screen for VulkanScreen { type Buffer = VulkanBuffer; type UpdateErr = UpdateError; @@ -76,9 +76,9 @@ impl Screen for Vulkan { } } -/// Represents an error when [`Vulkan::new()`] fails. +/// Represents an error when [`VulkanScreen::new()`] fails. #[derive(Debug, Error)] -pub enum VulkanError { +pub enum VulkanScreenError { #[error("couldn't find suitable queue")] NoQueue, diff --git a/src/llt/src/lib.rs b/src/llt/src/lib.rs index 4a524da6..30beaca1 100644 --- a/src/llt/src/lib.rs +++ b/src/llt/src/lib.rs @@ -17,7 +17,7 @@ mod unix; /// /// The reason this function accept an [`FnMut`] instead of [`FnOnce`] to support exiting the /// thread without returning from the `entry` (e.g. using `pthread_exit`). [`FnOnce`] requires the -/// function to live on the stack while [`FnMut`] is not. The caller still need to make sure no +/// function to live on the stack while [`FnMut`] does not. The caller still need to make sure no /// other variables need to be dropped before exiting the thread. /// /// # Safety