Passes device list to Slint UI (#1093)

Co-authored-by: tompro <tomas.prochazka@apertia.cz>
This commit is contained in:
SuchAFuriousDeath 2024-11-15 06:22:51 +01:00 committed by GitHub
parent f9c14d68e5
commit 7f7c5429bf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 194 additions and 19 deletions

40
gui/src/graphics/metal.rs Normal file
View File

@ -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<metal::Device>,
}
impl super::GraphicsApi for Metal {
type PhysicalDevice = metal::Device;
type CreateError = MetalCreateError;
fn new() -> Result<Self, Self::CreateError> {
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 {}

25
gui/src/graphics/mod.rs Normal file
View File

@ -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<Self, Self::CreateError>;
fn physical_devices(&self) -> &[Self::PhysicalDevice];
}
pub trait PhysicalDevice: Sized {
fn name(&self) -> &str;
}

View File

@ -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<VulkanPhysicalDevice>,
}
impl super::GraphicsApi for Vulkan {
type PhysicalDevice = VulkanPhysicalDevice;
type CreateError = VulkanCreateError;
fn new() -> Result<Self, Self::CreateError> {
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<VulkanPhysicalDevice, VulkanCreateError> {
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::<Result<_, VulkanCreateError>>()?;
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),
}

View File

@ -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::<String>() {

View File

@ -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<Self, ApplicationError> {
let main_window = ui::MainWindow::new().map_err(ApplicationError::CreateMainWindow)?;
let graphics_api =
graphics::DefaultApi::new().map_err(ApplicationError::InitGraphicsApi)?;
let devices: Vec<SharedString> = 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] <graphics::DefaultApi as GraphicsApi>::CreateError),
#[error("failed to run main window")]
RunMainWindow(#[source] slint::PlatformError),
}

View File

@ -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<MetalBuffer>,
layer: MetalLayer,
device: Device,
}
impl Metal {
impl MetalScreen {
pub fn from_screen(screen: &VmmScreen) -> Result<Self, MetalError> {
// 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")]

View File

@ -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;

View File

@ -10,13 +10,13 @@ use thiserror::Error;
mod buffer;
/// Implementation of [`Screen`] using Vulkan.
pub struct Vulkan {
pub struct VulkanScreen {
buffer: Arc<VulkanBuffer>,
device: Device,
}
impl Vulkan {
pub fn from_screen(screen: &VmmScreen) -> Result<Self, VulkanError> {
impl VulkanScreen {
pub fn from_screen(screen: &VmmScreen) -> Result<Self, VulkanScreenError> {
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,

View File

@ -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