use crate::render::PipelineCache;
use crate::validation::{self, ValidationLog, ValidationMessage, VALIDATION_LAYER};
use ash::vk;
use impeller_hal::{Capabilities, DmaBufSupport, Error, Result, SampleCounts, SyncSupport};
use std::collections::HashSet;
use std::ffi::{c_char, CStr, CString};
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum DevicePreference {
#[default]
Auto,
Software,
Index(usize),
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ContextConfig {
pub device: DevicePreference,
pub validation: bool,
}
pub fn surface_extensions() -> &'static [&'static str] {
&[
"VK_KHR_surface",
"VK_KHR_wayland_surface",
"VK_KHR_xcb_surface",
"VK_KHR_xlib_surface",
"VK_EXT_headless_surface",
]
}
#[derive(Debug, Clone, Copy, Default)]
pub struct FrameSync<'a> {
pub wait: &'a [vk::Semaphore],
pub signal: &'a [vk::Semaphore],
pub presents: bool,
}
mod ext {
pub const EXTERNAL_MEMORY_DMA_BUF: &str = "VK_EXT_external_memory_dma_buf";
pub const EXTERNAL_MEMORY_FD: &str = "VK_KHR_external_memory_fd";
pub const IMAGE_DRM_FORMAT_MODIFIER: &str = "VK_EXT_image_drm_format_modifier";
pub const EXTERNAL_FENCE_FD: &str = "VK_KHR_external_fence_fd";
pub const EXTERNAL_SEMAPHORE_FD: &str = "VK_KHR_external_semaphore_fd";
pub const PHYSICAL_DEVICE_DRM: &str = "VK_EXT_physical_device_drm";
pub const IMAGE_FORMAT_LIST: &str = "VK_KHR_image_format_list";
pub const BLEND_OPERATION_ADVANCED: &str = "VK_EXT_blend_operation_advanced";
pub const SWAPCHAIN: &str = "VK_KHR_swapchain";
}
fn required_dependencies(name: &str) -> &'static [&'static str] {
match name {
ext::IMAGE_DRM_FORMAT_MODIFIER => &[ext::IMAGE_FORMAT_LIST],
ext::EXTERNAL_MEMORY_DMA_BUF => &[ext::EXTERNAL_MEMORY_FD],
_ => &[],
}
}
fn probe_advanced_blend(
instance: &ash::Instance,
physical_device: vk::PhysicalDevice,
enabled: &HashSet<String>,
) -> bool {
if !enabled.contains(ext::BLEND_OPERATION_ADVANCED) {
return false;
}
let mut properties = vk::PhysicalDeviceBlendOperationAdvancedPropertiesEXT::default();
let mut properties2 = vk::PhysicalDeviceProperties2::default().push_next(&mut properties);
unsafe { instance.get_physical_device_properties2(physical_device, &mut properties2) };
let mut features = vk::PhysicalDeviceBlendOperationAdvancedFeaturesEXT::default();
let mut features2 = vk::PhysicalDeviceFeatures2::default().push_next(&mut features);
unsafe { instance.get_physical_device_features2(physical_device, &mut features2) };
properties.advanced_blend_all_operations == vk::TRUE
&& features.advanced_blend_coherent_operations == vk::TRUE
&& properties.advanced_blend_max_color_attachments >= 1
}
fn resolve_extensions(wanted: &[&str], available: &HashSet<String>) -> HashSet<String> {
let mut enabled = HashSet::new();
for name in wanted {
if !available.contains(*name) {
continue;
}
let deps = required_dependencies(name);
if deps.iter().any(|d| !available.contains(*d)) {
continue;
}
enabled.insert((*name).to_string());
enabled.extend(deps.iter().map(|d| (*d).to_string()));
}
enabled
}
pub struct VulkanContext {
debug_messenger: Option<(ash::ext::debug_utils::Instance, vk::DebugUtilsMessengerEXT)>,
validation_log: Arc<ValidationLog>,
sync_validation: bool,
allocator: Option<gpu_allocator::vulkan::Allocator>,
pipelines: PipelineCache,
command_pool: vk::CommandPool,
device: ash::Device,
physical_device: vk::PhysicalDevice,
queue: vk::Queue,
queue_family_index: u32,
capabilities: Capabilities,
descriptor_layout: Option<vk::DescriptorSetLayout>,
material_layout: Option<vk::DescriptorSetLayout>,
runtime_programs: Vec<Vec<u32>>,
uniform_alignment: u64,
sampler: Option<vk::Sampler>,
placeholder: Option<crate::resource::VulkanTexture>,
placeholder_binding: Option<(vk::DescriptorPool, vk::ImageView, vk::DescriptorSet)>,
enabled_extensions: HashSet<String>,
instance: ash::Instance,
_entry: ash::Entry,
}
impl VulkanContext {
pub fn new(preference: DevicePreference) -> Result<Self> {
Self::with_config(ContextConfig {
device: preference,
validation: false,
})
}
pub fn with_config(config: ContextConfig) -> Result<Self> {
let preference = config.device;
let entry = unsafe { ash::Entry::load() }.map_err(|e| Error::Backend {
backend: "vulkan",
detail: format!("loader: {e}"),
})?;
let app_name =
CString::new("impeller-rs").expect("no interior NUL in an extension or layer name");
let app_info = vk::ApplicationInfo::default()
.application_name(&app_name)
.engine_name(&app_name)
.api_version(vk::make_api_version(0, 1, 1, 0));
let want_validation = config.validation
&& layer_available(&entry, VALIDATION_LAYER)
&& instance_extension_available(&entry, "VK_EXT_debug_utils");
let layer_name =
CString::new(VALIDATION_LAYER).expect("no interior NUL in an extension or layer name");
let layer_ptrs: Vec<*const c_char> = if want_validation {
vec![layer_name.as_ptr()]
} else {
Vec::new()
};
let mut instance_extensions: Vec<CString> = Vec::new();
if want_validation {
instance_extensions.push(
CString::new("VK_EXT_debug_utils")
.expect("no interior NUL in an extension or layer name"),
);
if layer_extension_available(&entry, VALIDATION_LAYER, "VK_EXT_validation_features") {
instance_extensions.push(
CString::new("VK_EXT_validation_features")
.expect("no interior NUL in an extension or layer name"),
);
}
}
for &name in surface_extensions() {
if instance_extension_available(&entry, name) {
instance_extensions.push(
CString::new(name).expect("no interior NUL in an extension or layer name"),
);
}
}
let ext_ptrs: Vec<*const c_char> = instance_extensions.iter().map(|s| s.as_ptr()).collect();
let sync_validation_available = want_validation
&& layer_extension_available(&entry, VALIDATION_LAYER, "VK_EXT_validation_features");
let sync_validation = [vk::ValidationFeatureEnableEXT::SYNCHRONIZATION_VALIDATION];
let mut validation_features =
vk::ValidationFeaturesEXT::default().enabled_validation_features(&sync_validation);
let mut create_info = vk::InstanceCreateInfo::default()
.application_info(&app_info)
.enabled_layer_names(&layer_ptrs)
.enabled_extension_names(&ext_ptrs);
if sync_validation_available {
create_info = create_info.push_next(&mut validation_features);
}
let instance = unsafe { entry.create_instance(&create_info, None) }
.map_err(|e| backend_err("create_instance", e))?;
let validation_log = Arc::new(ValidationLog::default());
let debug_messenger = if want_validation {
let loader = ash::ext::debug_utils::Instance::new(&entry, &instance);
let info = validation::messenger_create_info()
.user_data(Arc::as_ptr(&validation_log) as *mut std::ffi::c_void);
match unsafe { loader.create_debug_utils_messenger(&info, None) } {
Ok(m) => Some((loader, m)),
Err(e) => {
unsafe { instance.destroy_instance(None) };
return Err(backend_err("create_debug_utils_messenger", e));
}
}
} else {
None
};
let physical_device = match select_physical_device(&instance, preference) {
Ok(pd) => pd,
Err(e) => {
unsafe { instance.destroy_instance(None) };
return Err(e);
}
};
Self::finish(
entry,
instance,
physical_device,
debug_messenger,
validation_log,
sync_validation_available,
)
}
#[allow(clippy::too_many_arguments)]
fn finish(
entry: ash::Entry,
instance: ash::Instance,
physical_device: vk::PhysicalDevice,
debug_messenger: Option<(ash::ext::debug_utils::Instance, vk::DebugUtilsMessengerEXT)>,
validation_log: Arc<ValidationLog>,
sync_validation: bool,
) -> Result<Self> {
let available = match device_extensions(&instance, physical_device) {
Ok(set) => set,
Err(e) => {
unsafe { instance.destroy_instance(None) };
return Err(e);
}
};
let wanted = [
ext::EXTERNAL_MEMORY_DMA_BUF,
ext::EXTERNAL_MEMORY_FD,
ext::IMAGE_DRM_FORMAT_MODIFIER,
ext::EXTERNAL_FENCE_FD,
ext::EXTERNAL_SEMAPHORE_FD,
ext::PHYSICAL_DEVICE_DRM,
ext::BLEND_OPERATION_ADVANCED,
ext::SWAPCHAIN,
];
let enabled = resolve_extensions(&wanted, &available);
let advanced_blend = probe_advanced_blend(&instance, physical_device, &enabled);
let queue_family_index = match select_queue_family(&instance, physical_device) {
Ok(i) => i,
Err(e) => {
unsafe { instance.destroy_instance(None) };
return Err(e);
}
};
let enabled_cstrings: Vec<CString> = enabled
.iter()
.map(|s| CString::new(s.as_str()).expect("no interior NUL in an extension name"))
.collect();
let enabled_ptrs: Vec<*const c_char> =
enabled_cstrings.iter().map(|s| s.as_ptr()).collect();
let priorities = [1.0f32];
let queue_info = vk::DeviceQueueCreateInfo::default()
.queue_family_index(queue_family_index)
.queue_priorities(&priorities);
let queue_infos = [queue_info];
let mut device_info = vk::DeviceCreateInfo::default()
.queue_create_infos(&queue_infos)
.enabled_extension_names(&enabled_ptrs);
let mut advanced_features = vk::PhysicalDeviceBlendOperationAdvancedFeaturesEXT::default()
.advanced_blend_coherent_operations(true);
if advanced_blend {
device_info = device_info.push_next(&mut advanced_features);
}
let device = match unsafe { instance.create_device(physical_device, &device_info, None) } {
Ok(d) => d,
Err(e) => {
unsafe { instance.destroy_instance(None) };
return Err(backend_err("create_device", e));
}
};
let queue = unsafe { device.get_device_queue(queue_family_index, 0) };
let capabilities =
detect_capabilities(&instance, physical_device, &enabled, advanced_blend);
let allocator =
gpu_allocator::vulkan::Allocator::new(&gpu_allocator::vulkan::AllocatorCreateDesc {
instance: instance.clone(),
device: device.clone(),
physical_device,
debug_settings: Default::default(),
buffer_device_address: false,
allocation_sizes: Default::default(),
})
.map_err(|e| Error::Backend {
backend: "vulkan",
detail: format!("allocator: {e}"),
})?;
let pool_info = vk::CommandPoolCreateInfo::default()
.queue_family_index(queue_family_index)
.flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER);
let command_pool = unsafe { device.create_command_pool(&pool_info, None) }
.map_err(|e| backend_err("create_command_pool", e))?;
let uniform_alignment = unsafe { instance.get_physical_device_properties(physical_device) }
.limits
.min_uniform_buffer_offset_alignment;
Ok(Self {
debug_messenger,
validation_log,
sync_validation,
allocator: Some(allocator),
pipelines: PipelineCache::default(),
command_pool,
device,
physical_device,
queue,
queue_family_index,
capabilities,
descriptor_layout: None,
material_layout: None,
runtime_programs: Vec::new(),
uniform_alignment,
sampler: None,
placeholder: None,
placeholder_binding: None,
enabled_extensions: enabled,
instance,
_entry: entry,
})
}
pub fn capabilities(&self) -> &Capabilities {
&self.capabilities
}
pub fn register_program(&mut self, program: &impeller_hal::RuntimeProgram) -> Result<u32> {
if program.spirv.is_empty() {
return Err(impeller_hal::Error::Unsupported(
"a runtime program needs a SPIR-V fragment module for this backend",
));
}
if let Some(existing) = self
.runtime_programs
.iter()
.position(|held| held.as_slice() == program.spirv.as_slice())
{
return Ok(existing as u32);
}
self.runtime_programs.push(program.spirv.clone());
Ok((self.runtime_programs.len() - 1) as u32)
}
pub(crate) fn runtime_program(&self, id: u32) -> Result<&[u32]> {
self.runtime_programs
.get(id as usize)
.map(Vec::as_slice)
.ok_or(impeller_hal::Error::Unsupported(
"a draw names a runtime program that was never registered",
))
}
pub(crate) fn material_layout(&mut self) -> Result<vk::DescriptorSetLayout> {
if self.material_layout.is_none() {
self.material_layout = Some(crate::materials::create_layout(&self.device)?);
}
Ok(self.material_layout.expect("just created"))
}
pub(crate) fn uniform_alignment(&self) -> u64 {
self.uniform_alignment
}
pub(crate) fn descriptor_layout(&mut self) -> Result<vk::DescriptorSetLayout> {
if self.descriptor_layout.is_none() {
self.descriptor_layout = Some(crate::sampling::create_descriptor_layout(&self.device)?);
}
Ok(self.descriptor_layout.expect("just created"))
}
pub(crate) fn sampler(&mut self) -> Result<vk::Sampler> {
if self.sampler.is_none() {
self.sampler = Some(crate::sampling::create_sampler(&self.device)?);
}
Ok(self.sampler.expect("just created"))
}
pub(crate) fn placeholder_texture(&mut self) -> Result<vk::Image> {
if self.placeholder.is_none() {
let mut texture = self.create_texture(&impeller_hal::TextureDescriptor::offscreen(
impeller_hal::Extent2D::new(1, 1),
impeller_hal::PixelFormat::Rgba8Unorm,
))?;
self.write_texture(&mut texture, &[255, 255, 255, 255])?;
let device = self.device.clone();
let cmd = self.begin_one_shot()?;
crate::resource::transition(
&device,
cmd,
texture.raw_image(),
texture.layout(),
vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
);
if let Err(e) = self.submit_one_shot(cmd) {
self.destroy_texture(texture);
return Err(e);
}
texture.set_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL);
self.placeholder = Some(texture);
}
Ok(self.placeholder.as_ref().expect("just created").raw_image())
}
pub(crate) fn placeholder_view(&mut self) -> Result<vk::ImageView> {
self.placeholder_set()?;
Ok(self
.placeholder_binding
.expect("placeholder_set just made it")
.1)
}
pub(crate) fn placeholder_set(&mut self) -> Result<vk::DescriptorSet> {
if let Some((_, _, set)) = self.placeholder_binding {
return Ok(set);
}
let image = self.placeholder_texture()?;
let layout = self.descriptor_layout()?;
let sampler = self.sampler()?;
let binding = crate::sampling::create_placeholder_binding(
&self.device.clone(),
image,
layout,
sampler,
)?;
self.placeholder_binding = Some(binding);
Ok(binding.2)
}
pub fn sync_validation_active(&self) -> bool {
self.sync_validation
}
pub fn validation_active(&self) -> bool {
self.debug_messenger.is_some()
}
pub fn validation_messages(&self) -> Vec<ValidationMessage> {
self.validation_log.messages()
}
pub fn validation_clean(&self) -> bool {
self.validation_log.is_clean()
}
pub fn validation_log(&self) -> Arc<ValidationLog> {
Arc::clone(&self.validation_log)
}
pub fn queue_family_index(&self) -> u32 {
self.queue_family_index
}
pub fn has_extension(&self, name: &str) -> bool {
self.enabled_extensions.contains(name)
}
pub fn raw_device(&self) -> &ash::Device {
&self.device
}
pub fn raw_entry(&self) -> &ash::Entry {
&self._entry
}
pub fn raw_instance(&self) -> &ash::Instance {
&self.instance
}
pub fn raw_physical_device(&self) -> vk::PhysicalDevice {
self.physical_device
}
pub fn raw_queue(&self) -> vk::Queue {
self.queue
}
pub(crate) fn pipeline_cache(&self) -> &PipelineCache {
&self.pipelines
}
pub(crate) fn pipeline_cache_mut(&mut self) -> &mut PipelineCache {
&mut self.pipelines
}
pub(crate) fn allocator_mut(&mut self) -> &mut gpu_allocator::vulkan::Allocator {
self.allocator
.as_mut()
.expect("allocator is taken only in Drop")
}
pub(crate) fn submit_exportable(
&self,
cmd: vk::CommandBuffer,
framebuffer: vk::Framebuffer,
view: vk::ImageView,
sync: FrameSync<'_>,
) -> Result<crate::fence::VulkanFence> {
unsafe { self.device.end_command_buffer(cmd) }
.map_err(|e| backend_err("end_command_buffer", e))?;
let info = vk::FenceCreateInfo::default();
let fence = unsafe { self.device.create_fence(&info, None) }
.map_err(|e| backend_err("create_fence", e))?;
let can_export = self.has_extension(ext::EXTERNAL_SEMAPHORE_FD);
let semaphore = if can_export {
let mut export_info = vk::ExportSemaphoreCreateInfo::default()
.handle_types(vk::ExternalSemaphoreHandleTypeFlags::SYNC_FD);
let info = vk::SemaphoreCreateInfo::default().push_next(&mut export_info);
match unsafe { self.device.create_semaphore(&info, None) } {
Ok(semaphore) => Some(semaphore),
Err(e) => {
unsafe { self.device.destroy_fence(fence, None) };
return Err(backend_err("create_semaphore", e));
}
}
} else {
None
};
let cmds = [cmd];
let signals: Vec<vk::Semaphore> = semaphore
.into_iter()
.chain(sync.signal.iter().copied())
.collect();
let wait_stages: Vec<vk::PipelineStageFlags> =
vec![vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT; sync.wait.len()];
let submit = vk::SubmitInfo::default()
.command_buffers(&cmds)
.wait_semaphores(sync.wait)
.wait_dst_stage_mask(&wait_stages)
.signal_semaphores(&signals);
if let Err(e) = unsafe { self.device.queue_submit(self.queue, &[submit], fence) } {
unsafe {
if let Some(semaphore) = semaphore {
self.device.destroy_semaphore(semaphore, None);
}
self.device.destroy_fence(fence, None);
}
return Err(backend_err("queue_submit", e));
}
let loader = can_export
.then(|| ash::khr::external_semaphore_fd::Device::new(&self.instance, &self.device));
Ok(crate::fence::VulkanFence::new(
self.device.clone(),
fence,
self.command_pool,
cmd,
framebuffer,
view,
semaphore,
loader,
))
}
pub(crate) fn begin_one_shot(&self) -> Result<vk::CommandBuffer> {
let info = vk::CommandBufferAllocateInfo::default()
.command_pool(self.command_pool)
.level(vk::CommandBufferLevel::PRIMARY)
.command_buffer_count(1);
let buffers = unsafe { self.device.allocate_command_buffers(&info) }
.map_err(|e| backend_err("allocate_command_buffers", e))?;
let cmd = buffers[0];
let begin = vk::CommandBufferBeginInfo::default()
.flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT);
unsafe { self.device.begin_command_buffer(cmd, &begin) }
.map_err(|e| backend_err("begin_command_buffer", e))?;
Ok(cmd)
}
pub(crate) fn submit_one_shot(&self, cmd: vk::CommandBuffer) -> Result<()> {
let result = self.submit_and_wait(cmd);
unsafe { self.device.free_command_buffers(self.command_pool, &[cmd]) };
result
}
fn submit_and_wait(&self, cmd: vk::CommandBuffer) -> Result<()> {
unsafe { self.device.end_command_buffer(cmd) }
.map_err(|e| backend_err("end_command_buffer", e))?;
let fence_info = vk::FenceCreateInfo::default();
let fence = unsafe { self.device.create_fence(&fence_info, None) }
.map_err(|e| backend_err("create_fence", e))?;
let cmds = [cmd];
let submit = vk::SubmitInfo::default().command_buffers(&cmds);
let submitted = unsafe { self.device.queue_submit(self.queue, &[submit], fence) };
let result = match submitted {
Ok(()) => unsafe {
self.device
.wait_for_fences(&[fence], true, u64::MAX)
.map_err(|e| match e {
vk::Result::TIMEOUT => Error::Timeout {
what: "a fence to signal",
},
vk::Result::ERROR_DEVICE_LOST => Error::DeviceLost,
other => backend_err("wait_for_fences", other),
})
},
Err(e) => Err(backend_err("queue_submit", e)),
};
unsafe { self.device.destroy_fence(fence, None) };
result
}
}
impl Drop for VulkanContext {
fn drop(&mut self) {
self.pipelines.destroy(&self.device);
if let Some(texture) = self.placeholder.take() {
self.destroy_texture(texture);
}
unsafe {
if let Some((pool, view, _)) = self.placeholder_binding.take() {
self.device.destroy_descriptor_pool(pool, None);
self.device.destroy_image_view(view, None);
}
if let Some(sampler) = self.sampler.take() {
self.device.destroy_sampler(sampler, None);
}
for layout in [self.descriptor_layout.take(), self.material_layout.take()]
.into_iter()
.flatten()
{
self.device.destroy_descriptor_set_layout(layout, None);
}
}
drop(self.allocator.take());
unsafe {
self.device.destroy_command_pool(self.command_pool, None);
self.device.destroy_device(None);
if let Some((loader, messenger)) = self.debug_messenger.take() {
loader.destroy_debug_utils_messenger(messenger, None);
}
self.instance.destroy_instance(None);
}
}
}
impl std::fmt::Debug for VulkanContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("VulkanContext")
.field("device", &self.capabilities.device_name)
.field("driver", &self.capabilities.driver_name)
.finish_non_exhaustive()
}
}
fn layer_available(entry: &ash::Entry, name: &str) -> bool {
let Ok(layers) = (unsafe { entry.enumerate_instance_layer_properties() }) else {
return false;
};
layers.iter().any(|l| {
unsafe { CStr::from_ptr(l.layer_name.as_ptr()) }
.to_str()
.map(|s| s == name)
.unwrap_or(false)
})
}
fn layer_extension_available(entry: &ash::Entry, layer: &str, name: &str) -> bool {
let Ok(layer_name) = CString::new(layer) else {
return false;
};
let Ok(exts) = (unsafe { entry.enumerate_instance_extension_properties(Some(&layer_name)) })
else {
return false;
};
exts.iter().any(|e| {
unsafe { CStr::from_ptr(e.extension_name.as_ptr()) }
.to_str()
.map(|s| s == name)
.unwrap_or(false)
})
}
fn instance_extension_available(entry: &ash::Entry, name: &str) -> bool {
let Ok(exts) = (unsafe { entry.enumerate_instance_extension_properties(None) }) else {
return false;
};
exts.iter().any(|e| {
unsafe { CStr::from_ptr(e.extension_name.as_ptr()) }
.to_str()
.map(|s| s == name)
.unwrap_or(false)
})
}
fn backend_err(what: &str, e: vk::Result) -> Error {
Error::Backend {
backend: "vulkan",
detail: format!("{what}: {e:?}"),
}
}
fn device_extensions(instance: &ash::Instance, pd: vk::PhysicalDevice) -> Result<HashSet<String>> {
let props = unsafe { instance.enumerate_device_extension_properties(pd) }
.map_err(|e| backend_err("enumerate_device_extension_properties", e))?;
Ok(props
.iter()
.filter_map(|p| {
let raw = unsafe { CStr::from_ptr(p.extension_name.as_ptr()) };
raw.to_str().ok().map(|s| s.to_owned())
})
.collect())
}
fn select_physical_device(
instance: &ash::Instance,
preference: DevicePreference,
) -> Result<vk::PhysicalDevice> {
let devices = unsafe { instance.enumerate_physical_devices() }
.map_err(|e| backend_err("enumerate_physical_devices", e))?;
if devices.is_empty() {
return Err(Error::Backend {
backend: "vulkan",
detail: "no physical devices".into(),
});
}
match preference {
DevicePreference::Index(i) => devices.get(i).copied().ok_or(Error::Backend {
backend: "vulkan",
detail: format!("device index {i} out of range ({} present)", devices.len()),
}),
DevicePreference::Auto => Ok(*devices
.iter()
.max_by_key(|pd| {
let props = unsafe { instance.get_physical_device_properties(**pd) };
match props.device_type {
vk::PhysicalDeviceType::DISCRETE_GPU => 3,
vk::PhysicalDeviceType::INTEGRATED_GPU => 2,
vk::PhysicalDeviceType::VIRTUAL_GPU => 1,
_ => 0,
}
})
.expect("non-empty")),
DevicePreference::Software => devices
.iter()
.find(|pd| {
let props = unsafe { instance.get_physical_device_properties(**pd) };
props.device_type == vk::PhysicalDeviceType::CPU
})
.copied()
.ok_or(Error::Unsupported("no software rasterizer present")),
}
}
fn select_queue_family(instance: &ash::Instance, pd: vk::PhysicalDevice) -> Result<u32> {
let families = unsafe { instance.get_physical_device_queue_family_properties(pd) };
families
.iter()
.position(|f| f.queue_flags.contains(vk::QueueFlags::GRAPHICS))
.map(|i| i as u32)
.ok_or(Error::Unsupported("no graphics queue family"))
}
fn detect_capabilities(
instance: &ash::Instance,
pd: vk::PhysicalDevice,
enabled: &HashSet<String>,
advanced_blend: bool,
) -> Capabilities {
let modifiers_known = enabled.contains(ext::IMAGE_DRM_FORMAT_MODIFIER);
let props = unsafe { instance.get_physical_device_properties(pd) };
let limits = props.limits;
let sample_mask =
limits.framebuffer_color_sample_counts & limits.framebuffer_depth_sample_counts;
let fd = enabled.contains(ext::EXTERNAL_MEMORY_FD);
let dma_buf = enabled.contains(ext::EXTERNAL_MEMORY_DMA_BUF);
let modifiers = enabled.contains(ext::IMAGE_DRM_FORMAT_MODIFIER);
let wanted = vk::FormatFeatureFlags::COLOR_ATTACHMENT
| vk::FormatFeatureFlags::COLOR_ATTACHMENT_BLEND
| vk::FormatFeatureFlags::SAMPLED_IMAGE
| vk::FormatFeatureFlags::SAMPLED_IMAGE_FILTER_LINEAR;
let float_render_targets = unsafe {
instance
.get_physical_device_format_properties(pd, vk::Format::R16G16B16A16_SFLOAT)
.optimal_tiling_features
.contains(wanted)
};
Capabilities {
advanced_blend,
float_render_targets,
max_texture_size: limits.max_image_dimension2_d,
sample_counts: SampleCounts::from_mask(sample_mask.as_raw()),
dma_buf: DmaBufSupport {
import: fd && dma_buf,
export: fd && dma_buf,
modifiers,
},
sync: SyncSupport {
export_sync_file: enabled.contains(ext::EXTERNAL_SEMAPHORE_FD),
import_sync_file: enabled.contains(ext::EXTERNAL_FENCE_FD),
},
render_formats: if modifiers_known {
crate::external::render_formats(instance, pd)
} else {
Vec::new()
},
device_name: device_name(&props),
driver_name: format!("vulkan {}", api_version_string(props.api_version)),
software: props.device_type == vk::PhysicalDeviceType::CPU,
}
}
fn device_name(props: &vk::PhysicalDeviceProperties) -> String {
unsafe { CStr::from_ptr(props.device_name.as_ptr()) }
.to_string_lossy()
.into_owned()
}
fn api_version_string(v: u32) -> String {
format!(
"{}.{}.{}",
vk::api_version_major(v),
vk::api_version_minor(v),
vk::api_version_patch(v)
)
}