#[cfg(feature = "vulkan")]
use ash::vk;
use crate::{
GpuAdapterInfo, GpuBackendType, GpuCaps, GpuContext, GpuDeviceType, GpuError, GpuResult,
TextureFormat,
};
use std::ffi::CString;
pub const REQUIRED_INSTANCE_EXTENSIONS: &[&str] = &[
"VK_KHR_surface",
#[cfg(target_os = "windows")]
"VK_KHR_win32_surface",
#[cfg(target_os = "linux")]
"VK_KHR_xcb_surface",
#[cfg(target_os = "linux")]
"VK_KHR_wayland_surface",
#[cfg(target_os = "macos")]
"VK_EXT_metal_surface",
#[cfg(target_os = "android")]
"VK_KHR_android_surface",
];
pub const REQUIRED_DEVICE_EXTENSIONS: &[&str] = &["VK_KHR_swapchain"];
pub const OPTIONAL_DEVICE_EXTENSIONS: &[&str] = &[
"VK_KHR_maintenance1",
"VK_KHR_maintenance2",
"VK_KHR_maintenance3",
"VK_KHR_bind_memory2",
"VK_KHR_get_memory_requirements2",
"VK_KHR_dedicated_allocation",
"VK_KHR_descriptor_update_template",
"VK_KHR_sampler_ycbcr_conversion",
"VK_EXT_descriptor_indexing",
"VK_KHR_buffer_device_address",
"VK_EXT_memory_budget",
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct VulkanVersion {
pub major: u32,
pub minor: u32,
pub patch: u32,
}
impl VulkanVersion {
pub const V1_0: Self = Self {
major: 1,
minor: 0,
patch: 0,
};
pub const V1_1: Self = Self {
major: 1,
minor: 1,
patch: 0,
};
pub const V1_2: Self = Self {
major: 1,
minor: 2,
patch: 0,
};
pub const V1_3: Self = Self {
major: 1,
minor: 3,
patch: 0,
};
pub const fn new(major: u32, minor: u32, patch: u32) -> Self {
Self {
major,
minor,
patch,
}
}
#[cfg(feature = "vulkan")]
pub fn to_vk_version(&self) -> u32 {
vk::make_api_version(0, self.major, self.minor, self.patch)
}
#[cfg(feature = "vulkan")]
pub fn from_vk_version(version: u32) -> Self {
Self {
major: vk::api_version_major(version),
minor: vk::api_version_minor(version),
patch: vk::api_version_patch(version),
}
}
}
impl std::fmt::Display for VulkanVersion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}
#[derive(Debug, Clone)]
pub struct VulkanContextConfig {
pub app_name: String,
pub app_version: VulkanVersion,
pub min_vulkan_version: VulkanVersion,
pub enable_validation: bool,
pub instance_extensions: Vec<String>,
pub device_extensions: Vec<String>,
pub prefer_discrete: bool,
}
impl Default for VulkanContextConfig {
fn default() -> Self {
Self {
app_name: "skia-rs".to_string(),
app_version: VulkanVersion::new(0, 1, 0),
min_vulkan_version: VulkanVersion::V1_1,
enable_validation: cfg!(debug_assertions),
instance_extensions: Vec::new(),
device_extensions: Vec::new(),
prefer_discrete: true,
}
}
}
#[derive(Debug, Clone)]
pub struct VulkanPhysicalDeviceInfo {
pub name: String,
pub vendor_id: u32,
pub device_id: u32,
pub device_type: GpuDeviceType,
pub api_version: VulkanVersion,
pub driver_version: u32,
}
#[derive(Debug, Clone)]
pub struct VulkanQueueFamilyInfo {
pub index: u32,
pub count: u32,
pub graphics: bool,
pub compute: bool,
pub transfer: bool,
pub sparse_binding: bool,
}
#[derive(Debug, Clone)]
pub struct VulkanMemoryTypeInfo {
pub index: u32,
pub heap_index: u32,
pub device_local: bool,
pub host_visible: bool,
pub host_coherent: bool,
pub host_cached: bool,
pub lazily_allocated: bool,
}
#[derive(Debug, Clone)]
pub struct VulkanMemoryHeapInfo {
pub index: u32,
pub size: u64,
pub device_local: bool,
}
#[derive(Debug, Clone, Default)]
pub struct VulkanCaps {
pub base: GpuCaps,
pub vulkan_version: Option<VulkanVersion>,
pub max_push_constant_size: u32,
pub max_bound_descriptor_sets: u32,
pub max_per_stage_descriptor_samplers: u32,
pub max_per_stage_descriptor_uniform_buffers: u32,
pub max_per_stage_descriptor_storage_buffers: u32,
pub max_per_stage_descriptor_sampled_images: u32,
pub max_per_stage_descriptor_storage_images: u32,
pub max_per_stage_resources: u32,
pub max_descriptor_set_samplers: u32,
pub max_descriptor_set_uniform_buffers: u32,
pub max_descriptor_set_storage_buffers: u32,
pub max_framebuffer_width: u32,
pub max_framebuffer_height: u32,
pub max_framebuffer_layers: u32,
pub max_viewports: u32,
pub geometry_shader: bool,
pub tessellation_shader: bool,
pub multi_draw_indirect: bool,
pub draw_indirect_first_instance: bool,
pub depth_clamp: bool,
pub depth_bias_clamp: bool,
pub fill_mode_non_solid: bool,
pub wide_lines: bool,
pub large_points: bool,
pub alpha_to_one: bool,
pub multi_viewport: bool,
pub sampler_anisotropy: bool,
pub texture_compression_etc2: bool,
pub texture_compression_astc_ldr: bool,
pub texture_compression_bc: bool,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct VulkanFormatFeatures {
pub sampled: bool,
pub storage: bool,
pub color_attachment: bool,
pub color_attachment_blend: bool,
pub depth_stencil_attachment: bool,
pub blit_src: bool,
pub blit_dst: bool,
pub sampled_filter_linear: bool,
pub transfer_src: bool,
pub transfer_dst: bool,
}
#[cfg(feature = "vulkan")]
pub fn texture_format_to_vk(format: TextureFormat) -> vk::Format {
match format {
TextureFormat::Rgba8Unorm => vk::Format::R8G8B8A8_UNORM,
TextureFormat::Rgba8UnormSrgb => vk::Format::R8G8B8A8_SRGB,
TextureFormat::Bgra8Unorm => vk::Format::B8G8R8A8_UNORM,
TextureFormat::Bgra8UnormSrgb => vk::Format::B8G8R8A8_SRGB,
TextureFormat::R8Unorm => vk::Format::R8_UNORM,
TextureFormat::Rg8Unorm => vk::Format::R8G8_UNORM,
TextureFormat::Rgba16Float => vk::Format::R16G16B16A16_SFLOAT,
TextureFormat::Rgba32Float => vk::Format::R32G32B32A32_SFLOAT,
TextureFormat::Depth24Stencil8 => vk::Format::D24_UNORM_S8_UINT,
TextureFormat::Depth32Float => vk::Format::D32_SFLOAT,
}
}
#[cfg(feature = "vulkan")]
pub fn vk_format_to_texture(format: vk::Format) -> Option<TextureFormat> {
match format {
vk::Format::R8G8B8A8_UNORM => Some(TextureFormat::Rgba8Unorm),
vk::Format::R8G8B8A8_SRGB => Some(TextureFormat::Rgba8UnormSrgb),
vk::Format::B8G8R8A8_UNORM => Some(TextureFormat::Bgra8Unorm),
vk::Format::B8G8R8A8_SRGB => Some(TextureFormat::Bgra8UnormSrgb),
vk::Format::R8_UNORM => Some(TextureFormat::R8Unorm),
vk::Format::R8G8_UNORM => Some(TextureFormat::Rg8Unorm),
vk::Format::R16G16B16A16_SFLOAT => Some(TextureFormat::Rgba16Float),
vk::Format::R32G32B32A32_SFLOAT => Some(TextureFormat::Rgba32Float),
vk::Format::D24_UNORM_S8_UINT => Some(TextureFormat::Depth24Stencil8),
vk::Format::D32_SFLOAT => Some(TextureFormat::Depth32Float),
_ => None,
}
}
#[cfg(feature = "vulkan")]
pub struct VulkanContext {
entry: ash::Entry,
instance: ash::Instance,
physical_device: vk::PhysicalDevice,
device: ash::Device,
graphics_queue: vk::Queue,
graphics_queue_family: u32,
transfer_queue: vk::Queue,
transfer_queue_family: u32,
compute_queue: vk::Queue,
compute_queue_family: u32,
graphics_command_pool: vk::CommandPool,
transfer_command_pool: vk::CommandPool,
adapter_info: GpuAdapterInfo,
caps: VulkanCaps,
debug_messenger: Option<vk::DebugUtilsMessengerEXT>,
debug_utils: Option<ash::ext::debug_utils::Instance>,
}
#[cfg(feature = "vulkan")]
impl VulkanContext {
pub fn new() -> GpuResult<Self> {
Self::with_config(VulkanContextConfig::default())
}
pub fn with_config(config: VulkanContextConfig) -> GpuResult<Self> {
unsafe {
let entry = ash::Entry::load()
.map_err(|e| GpuError::DeviceCreation(format!("Failed to load Vulkan: {:?}", e)))?;
let app_name = CString::new(config.app_name.as_str()).unwrap();
let engine_name = CString::new("skia-rs").unwrap();
let app_info = vk::ApplicationInfo::default()
.application_name(&app_name)
.application_version(config.app_version.to_vk_version())
.engine_name(&engine_name)
.engine_version(vk::make_api_version(0, 0, 1, 0))
.api_version(config.min_vulkan_version.to_vk_version());
let mut extensions: Vec<CString> = REQUIRED_INSTANCE_EXTENSIONS
.iter()
.map(|s| CString::new(*s).unwrap())
.collect();
if config.enable_validation {
extensions.push(CString::new("VK_EXT_debug_utils").unwrap());
}
for ext in &config.instance_extensions {
extensions.push(CString::new(ext.as_str()).unwrap());
}
let extension_ptrs: Vec<*const i8> = extensions.iter().map(|s| s.as_ptr()).collect();
let mut layers: Vec<CString> = Vec::new();
if config.enable_validation {
layers.push(CString::new("VK_LAYER_KHRONOS_validation").unwrap());
}
let layer_ptrs: Vec<*const i8> = layers.iter().map(|s| s.as_ptr()).collect();
let create_info = vk::InstanceCreateInfo::default()
.application_info(&app_info)
.enabled_extension_names(&extension_ptrs)
.enabled_layer_names(&layer_ptrs);
let instance = entry.create_instance(&create_info, None).map_err(|e| {
GpuError::DeviceCreation(format!("Failed to create Vulkan instance: {:?}", e))
})?;
let (debug_utils, debug_messenger) = if config.enable_validation {
let debug_utils = ash::ext::debug_utils::Instance::new(&entry, &instance);
let debug_info = vk::DebugUtilsMessengerCreateInfoEXT::default()
.message_severity(
vk::DebugUtilsMessageSeverityFlagsEXT::ERROR
| vk::DebugUtilsMessageSeverityFlagsEXT::WARNING,
)
.message_type(
vk::DebugUtilsMessageTypeFlagsEXT::GENERAL
| vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION
| vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE,
)
.pfn_user_callback(Some(vulkan_debug_callback));
let messenger = debug_utils
.create_debug_utils_messenger(&debug_info, None)
.ok();
(Some(debug_utils), messenger)
} else {
(None, None)
};
let physical_devices = instance.enumerate_physical_devices().map_err(|e| {
GpuError::DeviceCreation(format!("Failed to enumerate devices: {:?}", e))
})?;
if physical_devices.is_empty() {
return Err(GpuError::DeviceCreation("No Vulkan devices found".into()));
}
let mut best_device = None;
let mut best_score = 0i32;
for pdevice in physical_devices {
let props = instance.get_physical_device_properties(pdevice);
let score = Self::score_device(&instance, pdevice, &props, config.prefer_discrete);
if score > best_score {
best_score = score;
best_device = Some(pdevice);
}
}
let physical_device = best_device.ok_or_else(|| {
GpuError::DeviceCreation("No suitable Vulkan device found".into())
})?;
let device_props = instance.get_physical_device_properties(physical_device);
let device_features = instance.get_physical_device_features(physical_device);
let queue_families =
instance.get_physical_device_queue_family_properties(physical_device);
let mut graphics_family = None;
let mut transfer_family = None;
let mut compute_family = None;
for (i, family) in queue_families.iter().enumerate() {
if family.queue_flags.contains(vk::QueueFlags::GRAPHICS) {
graphics_family = Some(i as u32);
}
if family.queue_flags.contains(vk::QueueFlags::TRANSFER)
&& !family.queue_flags.contains(vk::QueueFlags::GRAPHICS)
{
transfer_family = Some(i as u32);
}
if family.queue_flags.contains(vk::QueueFlags::COMPUTE)
&& !family.queue_flags.contains(vk::QueueFlags::GRAPHICS)
{
compute_family = Some(i as u32);
}
}
let graphics_queue_family = graphics_family
.ok_or_else(|| GpuError::DeviceCreation("No graphics queue family found".into()))?;
let transfer_queue_family = transfer_family.unwrap_or(graphics_queue_family);
let compute_queue_family = compute_family.unwrap_or(graphics_queue_family);
let queue_priorities = [1.0f32];
let mut unique_families = vec![graphics_queue_family];
if transfer_queue_family != graphics_queue_family {
unique_families.push(transfer_queue_family);
}
if compute_queue_family != graphics_queue_family
&& compute_queue_family != transfer_queue_family
{
unique_families.push(compute_queue_family);
}
let queue_create_infos: Vec<_> = unique_families
.iter()
.map(|&family| {
vk::DeviceQueueCreateInfo::default()
.queue_family_index(family)
.queue_priorities(&queue_priorities)
})
.collect();
let mut device_extensions: Vec<CString> = REQUIRED_DEVICE_EXTENSIONS
.iter()
.map(|s| CString::new(*s).unwrap())
.collect();
for ext in &config.device_extensions {
device_extensions.push(CString::new(ext.as_str()).unwrap());
}
let device_extension_ptrs: Vec<*const i8> =
device_extensions.iter().map(|s| s.as_ptr()).collect();
let device_create_info = vk::DeviceCreateInfo::default()
.queue_create_infos(&queue_create_infos)
.enabled_extension_names(&device_extension_ptrs)
.enabled_features(&device_features);
let device = instance
.create_device(physical_device, &device_create_info, None)
.map_err(|e| {
GpuError::DeviceCreation(format!("Failed to create device: {:?}", e))
})?;
let graphics_queue = device.get_device_queue(graphics_queue_family, 0);
let transfer_queue = device.get_device_queue(transfer_queue_family, 0);
let compute_queue = device.get_device_queue(compute_queue_family, 0);
let graphics_pool_info = vk::CommandPoolCreateInfo::default()
.queue_family_index(graphics_queue_family)
.flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER);
let graphics_command_pool = device
.create_command_pool(&graphics_pool_info, None)
.map_err(|e| {
GpuError::ResourceCreation(format!("Failed to create command pool: {:?}", e))
})?;
let transfer_pool_info = vk::CommandPoolCreateInfo::default()
.queue_family_index(transfer_queue_family)
.flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER);
let transfer_command_pool = device
.create_command_pool(&transfer_pool_info, None)
.map_err(|e| {
GpuError::ResourceCreation(format!("Failed to create command pool: {:?}", e))
})?;
let device_name = CString::from_raw(device_props.device_name.as_ptr() as *mut i8)
.to_string_lossy()
.into_owned();
let device_name = device_props
.device_name
.iter()
.take_while(|&&c| c != 0)
.map(|&c| c as u8 as char)
.collect::<String>();
let adapter_info = GpuAdapterInfo {
name: device_name,
vendor: format!("{:04x}", device_props.vendor_id),
backend: GpuBackendType::Vulkan,
device_type: match device_props.device_type {
vk::PhysicalDeviceType::INTEGRATED_GPU => GpuDeviceType::Integrated,
vk::PhysicalDeviceType::DISCRETE_GPU => GpuDeviceType::Discrete,
vk::PhysicalDeviceType::VIRTUAL_GPU => GpuDeviceType::Virtual,
vk::PhysicalDeviceType::CPU => GpuDeviceType::Cpu,
_ => GpuDeviceType::Unknown,
},
};
let limits = device_props.limits;
let caps = VulkanCaps {
base: GpuCaps {
max_texture_size: limits.max_image_dimension2_d,
max_render_target_size: limits
.max_framebuffer_width
.min(limits.max_framebuffer_height),
msaa_support: true,
max_msaa_samples: Self::get_max_sample_count(
limits.framebuffer_color_sample_counts,
),
compute_support: true,
instancing_support: true,
},
vulkan_version: Some(VulkanVersion::from_vk_version(device_props.api_version)),
max_push_constant_size: limits.max_push_constants_size,
max_bound_descriptor_sets: limits.max_bound_descriptor_sets,
max_per_stage_descriptor_samplers: limits.max_per_stage_descriptor_samplers,
max_per_stage_descriptor_uniform_buffers: limits
.max_per_stage_descriptor_uniform_buffers,
max_per_stage_descriptor_storage_buffers: limits
.max_per_stage_descriptor_storage_buffers,
max_per_stage_descriptor_sampled_images: limits
.max_per_stage_descriptor_sampled_images,
max_per_stage_descriptor_storage_images: limits
.max_per_stage_descriptor_storage_images,
max_per_stage_resources: limits.max_per_stage_resources,
max_descriptor_set_samplers: limits.max_descriptor_set_samplers,
max_descriptor_set_uniform_buffers: limits.max_descriptor_set_uniform_buffers,
max_descriptor_set_storage_buffers: limits.max_descriptor_set_storage_buffers,
max_framebuffer_width: limits.max_framebuffer_width,
max_framebuffer_height: limits.max_framebuffer_height,
max_framebuffer_layers: limits.max_framebuffer_layers,
max_viewports: limits.max_viewports,
geometry_shader: device_features.geometry_shader != 0,
tessellation_shader: device_features.tessellation_shader != 0,
multi_draw_indirect: device_features.multi_draw_indirect != 0,
draw_indirect_first_instance: device_features.draw_indirect_first_instance != 0,
depth_clamp: device_features.depth_clamp != 0,
depth_bias_clamp: device_features.depth_bias_clamp != 0,
fill_mode_non_solid: device_features.fill_mode_non_solid != 0,
wide_lines: device_features.wide_lines != 0,
large_points: device_features.large_points != 0,
alpha_to_one: device_features.alpha_to_one != 0,
multi_viewport: device_features.multi_viewport != 0,
sampler_anisotropy: device_features.sampler_anisotropy != 0,
texture_compression_etc2: device_features.texture_compression_etc2 != 0,
texture_compression_astc_ldr: device_features.texture_compression_astc_ldr != 0,
texture_compression_bc: device_features.texture_compression_bc != 0,
};
Ok(Self {
entry,
instance,
physical_device,
device,
graphics_queue,
graphics_queue_family,
transfer_queue,
transfer_queue_family,
compute_queue,
compute_queue_family,
graphics_command_pool,
transfer_command_pool,
adapter_info,
caps,
debug_messenger,
debug_utils,
})
}
}
unsafe fn score_device(
instance: &ash::Instance,
device: vk::PhysicalDevice,
props: &vk::PhysicalDeviceProperties,
prefer_discrete: bool,
) -> i32 {
let mut score = 0i32;
match props.device_type {
vk::PhysicalDeviceType::DISCRETE_GPU => {
score += if prefer_discrete { 1000 } else { 500 };
}
vk::PhysicalDeviceType::INTEGRATED_GPU => {
score += if prefer_discrete { 500 } else { 1000 };
}
vk::PhysicalDeviceType::VIRTUAL_GPU => {
score += 100;
}
vk::PhysicalDeviceType::CPU => {
score += 10;
}
_ => {}
}
let queue_families =
unsafe { instance.get_physical_device_queue_family_properties(device) };
let has_graphics = queue_families
.iter()
.any(|f| f.queue_flags.contains(vk::QueueFlags::GRAPHICS));
if !has_graphics {
return -1; }
score += (props.limits.max_image_dimension2_d / 1024) as i32;
score
}
fn get_max_sample_count(flags: vk::SampleCountFlags) -> u32 {
if flags.contains(vk::SampleCountFlags::TYPE_64) {
64
} else if flags.contains(vk::SampleCountFlags::TYPE_32) {
32
} else if flags.contains(vk::SampleCountFlags::TYPE_16) {
16
} else if flags.contains(vk::SampleCountFlags::TYPE_8) {
8
} else if flags.contains(vk::SampleCountFlags::TYPE_4) {
4
} else if flags.contains(vk::SampleCountFlags::TYPE_2) {
2
} else {
1
}
}
pub fn device(&self) -> &ash::Device {
&self.device
}
pub fn physical_device(&self) -> vk::PhysicalDevice {
self.physical_device
}
pub fn instance(&self) -> &ash::Instance {
&self.instance
}
pub fn graphics_queue(&self) -> vk::Queue {
self.graphics_queue
}
pub fn graphics_queue_family(&self) -> u32 {
self.graphics_queue_family
}
pub fn transfer_queue(&self) -> vk::Queue {
self.transfer_queue
}
pub fn compute_queue(&self) -> vk::Queue {
self.compute_queue
}
pub fn graphics_command_pool(&self) -> vk::CommandPool {
self.graphics_command_pool
}
pub fn vulkan_caps(&self) -> &VulkanCaps {
&self.caps
}
pub fn query_format_features(&self, format: TextureFormat) -> VulkanFormatFeatures {
unsafe {
let vk_format = texture_format_to_vk(format);
let props = self
.instance
.get_physical_device_format_properties(self.physical_device, vk_format);
let optimal = props.optimal_tiling_features;
VulkanFormatFeatures {
sampled: optimal.contains(vk::FormatFeatureFlags::SAMPLED_IMAGE),
storage: optimal.contains(vk::FormatFeatureFlags::STORAGE_IMAGE),
color_attachment: optimal.contains(vk::FormatFeatureFlags::COLOR_ATTACHMENT),
color_attachment_blend: optimal
.contains(vk::FormatFeatureFlags::COLOR_ATTACHMENT_BLEND),
depth_stencil_attachment: optimal
.contains(vk::FormatFeatureFlags::DEPTH_STENCIL_ATTACHMENT),
blit_src: optimal.contains(vk::FormatFeatureFlags::BLIT_SRC),
blit_dst: optimal.contains(vk::FormatFeatureFlags::BLIT_DST),
sampled_filter_linear: optimal
.contains(vk::FormatFeatureFlags::SAMPLED_IMAGE_FILTER_LINEAR),
transfer_src: optimal.contains(vk::FormatFeatureFlags::TRANSFER_SRC),
transfer_dst: optimal.contains(vk::FormatFeatureFlags::TRANSFER_DST),
}
}
}
pub fn wait_idle(&self) -> GpuResult<()> {
unsafe {
self.device
.device_wait_idle()
.map_err(|e| GpuError::OperationFailed(format!("Wait idle failed: {:?}", e)))
}
}
}
#[cfg(feature = "vulkan")]
impl GpuContext for VulkanContext {
fn backend_type(&self) -> GpuBackendType {
GpuBackendType::Vulkan
}
fn adapter_info(&self) -> &GpuAdapterInfo {
&self.adapter_info
}
fn flush(&self) {
}
fn submit_and_wait(&self) {
let _ = self.wait_idle();
}
fn is_valid(&self) -> bool {
true
}
}
#[cfg(feature = "vulkan")]
impl Drop for VulkanContext {
fn drop(&mut self) {
unsafe {
let _ = self.device.device_wait_idle();
self.device
.destroy_command_pool(self.graphics_command_pool, None);
self.device
.destroy_command_pool(self.transfer_command_pool, None);
self.device.destroy_device(None);
if let (Some(debug_utils), Some(messenger)) = (&self.debug_utils, self.debug_messenger)
{
debug_utils.destroy_debug_utils_messenger(messenger, None);
}
self.instance.destroy_instance(None);
}
}
}
#[cfg(feature = "vulkan")]
unsafe extern "system" fn vulkan_debug_callback(
message_severity: vk::DebugUtilsMessageSeverityFlagsEXT,
message_type: vk::DebugUtilsMessageTypeFlagsEXT,
p_callback_data: *const vk::DebugUtilsMessengerCallbackDataEXT,
_user_data: *mut std::ffi::c_void,
) -> vk::Bool32 {
let callback_data = unsafe { *p_callback_data };
let message = if callback_data.p_message.is_null() {
std::borrow::Cow::from("")
} else {
unsafe { std::ffi::CStr::from_ptr(callback_data.p_message).to_string_lossy() }
};
let severity = match message_severity {
vk::DebugUtilsMessageSeverityFlagsEXT::ERROR => "ERROR",
vk::DebugUtilsMessageSeverityFlagsEXT::WARNING => "WARNING",
vk::DebugUtilsMessageSeverityFlagsEXT::INFO => "INFO",
_ => "VERBOSE",
};
let msg_type = match message_type {
vk::DebugUtilsMessageTypeFlagsEXT::GENERAL => "GENERAL",
vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION => "VALIDATION",
vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE => "PERFORMANCE",
_ => "UNKNOWN",
};
eprintln!("[Vulkan {} {}] {}", severity, msg_type, message);
vk::FALSE
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_vulkan_version() {
let v = VulkanVersion::new(1, 2, 3);
assert_eq!(v.major, 1);
assert_eq!(v.minor, 2);
assert_eq!(v.patch, 3);
assert_eq!(format!("{}", v), "1.2.3");
}
#[test]
fn test_vulkan_config() {
let config = VulkanContextConfig::default();
assert_eq!(config.app_name, "skia-rs");
assert_eq!(config.min_vulkan_version, VulkanVersion::V1_1);
}
#[test]
fn test_vulkan_caps_default() {
let caps = VulkanCaps::default();
assert_eq!(caps.base.max_texture_size, 0);
}
#[test]
fn test_format_features_default() {
let features = VulkanFormatFeatures::default();
assert!(!features.sampled);
assert!(!features.storage);
}
}