Skip to content

Commit

Permalink
Hides graphics implementation from outside (#1170)
Browse files Browse the repository at this point in the history
  • Loading branch information
ultimaweapon authored Dec 8, 2024
1 parent 2c40a3c commit 2561b39
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 222 deletions.
111 changes: 0 additions & 111 deletions gui/main.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
#include "main_window.hpp"
#ifndef __APPLE__
#include "vulkan.hpp"
#endif

#include <QApplication>
#include <QCommandLineParser>
Expand Down Expand Up @@ -37,114 +34,6 @@ int main(int argc, char *argv[])
args.addOption(Args::kernel);
args.process(app);

// Initialize Vulkan.
#ifndef __APPLE__
QVulkanInstance vulkan;

vulkan.setApiVersion(QVersionNumber(1, 3));

#if !defined(NDEBUG)
vulkan.setLayers({"VK_LAYER_KHRONOS_validation"});
#endif

if (!vulkan.create()) {
QMessageBox::critical(
nullptr,
"Error",
QString("Failed to initialize Vulkan (%1).").arg(vulkan.errorCode()));
return 1;
}

vkFunctions = vulkan.functions();

// List available devices.
QList<VkPhysicalDevice> vkDevices;

for (;;) {
// Get device count.
uint32_t count;
auto result = vkFunctions->vkEnumeratePhysicalDevices(vulkan.vkInstance(), &count, nullptr);

if (result != VK_SUCCESS) {
QMessageBox::critical(
nullptr,
"Error",
QString("Failed to get a number of Vulkan physical device (%1).").arg(result));
return 1;
} else if (!count) {
QMessageBox::critical(
nullptr,
"Error",
"No any Vulkan physical device available.");
return 1;
}

// Get devices.
vkDevices.resize(count);

result = vkFunctions->vkEnumeratePhysicalDevices(
vulkan.vkInstance(),
&count,
vkDevices.data());

if (result == VK_INCOMPLETE) {
continue;
} else if (result != VK_SUCCESS) {
QMessageBox::critical(
nullptr,
"Error",
QString("Failed to list Vulkan physical devices (%1).").arg(result));
return 1;
}

break;
}

// Filter out devices without Vulkan 1.3.
erase_if(vkDevices, [](VkPhysicalDevice dev) {
VkPhysicalDeviceProperties props;
vkFunctions->vkGetPhysicalDeviceProperties(dev, &props);
return props.apiVersion < VK_API_VERSION_1_3;
});

if (vkDevices.isEmpty()) {
QMessageBox::critical(
nullptr,
"Error",
"No Vulkan device supports Vulkan 1.3.");
return 1;
}

// Filter out devices that does not support graphics operations.
erase_if(vkDevices, [](VkPhysicalDevice dev) {
// Get number of queue family.
uint32_t count;

vkFunctions->vkGetPhysicalDeviceQueueFamilyProperties(dev, &count, nullptr);

// Get queue family.
QList<VkQueueFamilyProperties> families(count);

vkFunctions->vkGetPhysicalDeviceQueueFamilyProperties(dev, &count, families.data());

for (auto &f : families) {
if (f.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
return false;
}
}

return true;
});

if (vkDevices.isEmpty()) {
QMessageBox::critical(
nullptr,
"Error",
"No any Vulkan device supports graphics operations.");
return 1;
}
#endif

// Setup main window.
MainWindow win(args);

Expand Down
23 changes: 12 additions & 11 deletions gui/src/graphics/metal/mod.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,34 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use self::screen::MetalScreen;
use super::Graphics;
use crate::profile::Profile;
use metal::Device;
use std::ops::Deref;
use thiserror::Error;

mod buffer;
mod screen;

pub struct Metal {
pub fn new() -> Result<impl Graphics, GraphicsError> {
Ok(Metal {
devices: Device::all(),
})
}

/// Implementation of [`Graphics`] using Metal.
struct Metal {
devices: Vec<metal::Device>,
}

impl Graphics for Metal {
type Err = MetalError;
type PhysicalDevice = metal::Device;
type Screen = MetalScreen;

fn new() -> Result<Self, Self::Err> {
Ok(Self {
devices: Device::all(),
})
}

fn physical_devices(&self) -> &[Self::PhysicalDevice] {
&self.devices
}

fn create_screen(&mut self) -> Result<Self::Screen, Self::Err> {
fn create_screen(&mut self, profile: &Profile) -> Result<Self::Screen, GraphicsError> {
todo!()
}
}
Expand All @@ -38,6 +39,6 @@ impl super::PhysicalDevice for metal::Device {
}
}

/// Implementation of [`Graphics::Err`] for Metal.
/// Represents an error when operation on Metal fails.
#[derive(Debug, Error)]
pub enum MetalError {}
pub enum GraphicsError {}
13 changes: 4 additions & 9 deletions gui/src/graphics/mod.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,21 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pub use self::engine::{new, GraphicsError};

use crate::profile::Profile;
use std::error::Error;
use std::sync::Arc;

#[cfg_attr(target_os = "macos", path = "metal/mod.rs")]
#[cfg_attr(not(target_os = "macos"), path = "vulkan/mod.rs")]
mod engine;

#[cfg(not(target_os = "macos"))]
pub type DefaultApi = self::engine::Vulkan;

#[cfg(target_os = "macos")]
pub type DefaultApi = self::engine::Metal;

/// The underlying graphics engine (e.g. Vulkan).
pub trait Graphics: Sized + 'static {
type Err: Error;
type PhysicalDevice: PhysicalDevice;
type Screen: Screen;

fn new() -> Result<Self, Self::Err>;
fn physical_devices(&self) -> &[Self::PhysicalDevice];
fn create_screen(&mut self) -> Result<Self::Screen, Self::Err>;
fn create_screen(&mut self, profile: &Profile) -> Result<Self::Screen, GraphicsError>;
}

pub trait PhysicalDevice: Sized {
Expand Down
144 changes: 88 additions & 56 deletions gui/src/graphics/vulkan/mod.rs
Original file line number Diff line number Diff line change
@@ -1,67 +1,99 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use self::screen::VulkanScreen;
use super::Graphics;
use ash::vk::{ApplicationInfo, InstanceCreateInfo};
use std::ffi::CStr;
use crate::profile::Profile;
use ash::vk::{ApplicationInfo, InstanceCreateInfo, QueueFlags, API_VERSION_1_3};
use thiserror::Error;

mod buffer;
mod screen;

pub struct Vulkan {
entry: ash::Entry,
instance: ash::Instance,
devices: Vec<VulkanPhysicalDevice>,
}

impl Graphics for Vulkan {
type Err = VulkanError;
type PhysicalDevice = VulkanPhysicalDevice;
type Screen = VulkanScreen;

fn new() -> Result<Self, Self::Err> {
let entry = ash::Entry::linked();

let app_info = ApplicationInfo::default().application_name(c"Obliteration");

let create_info = InstanceCreateInfo::default().application_info(&app_info);
pub fn new() -> Result<impl Graphics, GraphicsError> {
// Setup application info.
let info = ApplicationInfo::default()
.application_name(c"Obliteration")
.api_version(API_VERSION_1_3);

// Setup validation layers.
let layers = [
#[cfg(debug_assertions)]
c"VK_LAYER_KHRONOS_validation".as_ptr(),
];

// Setup VkInstanceCreateInfo.
let info = InstanceCreateInfo::default()
.application_info(&info)
.enabled_layer_names(&layers);

// Create Vulkan instance.
let api = ash::Entry::linked();
let mut vk = match unsafe { api.create_instance(&info, None) } {
Ok(instance) => Vulkan {
instance,
devices: Vec::new(),
},
Err(e) => return Err(GraphicsError::CreateInstance(e)),
};

// List available devices.
let all = match unsafe { vk.instance.enumerate_physical_devices() } {
Ok(v) => v,
Err(e) => return Err(GraphicsError::EnumeratePhysicalDevices(e)),
};

if all.is_empty() {
return Err(GraphicsError::NoPhysicalDevice);
}

let instance = unsafe { entry.create_instance(&create_info, None) }
.map_err(VulkanError::CreateInstanceFailed)?;
for dev in all {
// Filter out devices without Vulkan 1.3.
let p = unsafe { vk.instance.get_physical_device_properties(dev) };

if p.api_version < API_VERSION_1_3 {
continue;
}

// Skip if device does not support graphics operations.
if !unsafe { vk.instance.get_physical_device_queue_family_properties(dev) }
.iter()
.any(|p| p.queue_flags.contains(QueueFlags::GRAPHICS))
{
continue;
}

// Add to list.
let name = p
.device_name_as_c_str()
.unwrap()
.to_str()
.unwrap()
.to_owned();

vk.devices.push(PhysicalDevice { device: dev, name });
}

let devices = unsafe { instance.enumerate_physical_devices() }
.map_err(VulkanError::EnumeratePhysicalDevicesFailed)?
.into_iter()
.map(|device| -> Result<VulkanPhysicalDevice, VulkanError> {
let properties = unsafe { instance.get_physical_device_properties(device) };
if vk.devices.is_empty() {
return Err(GraphicsError::NoSuitableDevice);
}

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(|_| VulkanError::DeviceNameInvalid)?
.to_str()
.map_err(VulkanError::DeviceNameInvalidUtf8)?
.to_owned();
Ok(vk)
}

Ok(VulkanPhysicalDevice { device, name })
})
.collect::<Result<_, VulkanError>>()?;
/// Implementation of [`Graphics`] using Vulkan.
struct Vulkan {
instance: ash::Instance,
devices: Vec<PhysicalDevice>,
}

Ok(Self {
entry,
instance,
devices,
})
}
impl Graphics for Vulkan {
type PhysicalDevice = PhysicalDevice;
type Screen = VulkanScreen;

fn physical_devices(&self) -> &[Self::PhysicalDevice] {
&self.devices
}

fn create_screen(&mut self) -> Result<Self::Screen, Self::Err> {
fn create_screen(&mut self, profile: &Profile) -> Result<Self::Screen, GraphicsError> {
todo!()
}
}
Expand All @@ -72,29 +104,29 @@ impl Drop for Vulkan {
}
}

pub struct VulkanPhysicalDevice {
pub struct PhysicalDevice {
device: ash::vk::PhysicalDevice,
name: String,
}

impl super::PhysicalDevice for VulkanPhysicalDevice {
impl super::PhysicalDevice for PhysicalDevice {
fn name(&self) -> &str {
&self.name
}
}

/// Implementation of [`Graphics::Err`] for Vulkan.
/// Represents an error when operation on Vulkan fails.
#[derive(Debug, Error)]
pub enum VulkanError {
pub enum GraphicsError {
#[error("couldn't create Vulkan instance")]
CreateInstanceFailed(#[source] ash::vk::Result),
CreateInstance(#[source] ash::vk::Result),

#[error("couldn't enumerate physical devices")]
EnumeratePhysicalDevicesFailed(#[source] ash::vk::Result),
EnumeratePhysicalDevices(#[source] ash::vk::Result),

#[error("no null byte in device name")]
DeviceNameInvalid,
#[error("no any Vulkan physical device available")]
NoPhysicalDevice,

#[error("device name is not valid UTF-8")]
DeviceNameInvalidUtf8(#[source] std::str::Utf8Error),
#[error("no Vulkan device supports graphics operations with Vulkan 1.3")]
NoSuitableDevice,
}
Loading

0 comments on commit 2561b39

Please sign in to comment.