use std::sync::Arc;
use crate::device::Device;
use crate::error::{VkResult, check};
use crate::loader::Loader;
use crate::vk;
use vk::Handle;
pub struct Instance {
handle: vk::Instance,
commands: Box<vk::commands::InstanceCommands>,
get_device_proc_addr: vk::commands::PFN_vkGetDeviceProcAddr,
_loader: Option<Arc<dyn Loader>>,
}
impl Instance {
pub(crate) unsafe fn load(
handle: vk::Instance,
get_instance_proc_addr: vk::commands::PFN_vkGetInstanceProcAddr,
get_device_proc_addr: vk::commands::PFN_vkGetDeviceProcAddr,
loader: Option<Arc<dyn Loader>>,
) -> Self {
let get_instance_proc_addr_fn =
get_instance_proc_addr.expect("vkGetInstanceProcAddr not loaded");
let commands = Box::new(unsafe {
vk::commands::InstanceCommands::load(|name| {
std::mem::transmute(get_instance_proc_addr_fn(handle, name.as_ptr()))
})
});
Self {
handle,
commands,
get_device_proc_addr,
_loader: loader,
}
}
pub unsafe fn from_raw_parts(
handle: vk::Instance,
get_instance_proc_addr: vk::commands::PFN_vkGetInstanceProcAddr,
) -> Self {
let get_instance_proc_addr_fn =
get_instance_proc_addr.expect("vkGetInstanceProcAddr not loaded");
let get_device_proc_addr: vk::commands::PFN_vkGetDeviceProcAddr = unsafe {
std::mem::transmute(get_instance_proc_addr_fn(
handle,
c"vkGetDeviceProcAddr".as_ptr(),
))
};
unsafe { Self::load(handle, get_instance_proc_addr, get_device_proc_addr, None) }
}
pub fn handle(&self) -> vk::Instance {
self.handle
}
pub fn commands(&self) -> &vk::commands::InstanceCommands {
&self.commands
}
pub unsafe fn create_device(
&self,
physical_device: vk::PhysicalDevice,
create_info: &vk::DeviceCreateInfo,
allocator: Option<&vk::AllocationCallbacks>,
) -> VkResult<Device> {
let fp = self
.commands
.create_device
.expect("vkCreateDevice not loaded");
let mut raw = vk::Device::null();
let result = unsafe {
fp(
physical_device,
create_info,
allocator.map_or(std::ptr::null(), |a| a),
&mut raw,
)
};
check(result)?;
let device = unsafe { Device::load(raw, self.get_device_proc_addr, self._loader.clone()) };
Ok(device)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::c_char;
use vk::Handle;
fn fake_handle() -> vk::Instance {
vk::Instance::from_raw(0xDEAD)
}
unsafe extern "system" fn mock_get_instance_proc_addr(
_instance: vk::Instance,
_name: *const c_char,
) -> vk::PFN_vkVoidFunction {
None
}
#[test]
fn from_raw_parts_stores_handle() {
let instance =
unsafe { Instance::from_raw_parts(fake_handle(), Some(mock_get_instance_proc_addr)) };
assert_eq!(instance.handle().as_raw(), fake_handle().as_raw());
}
#[test]
fn handle_returns_value_from_construction() {
let instance =
unsafe { Instance::load(fake_handle(), Some(mock_get_instance_proc_addr), None, None) };
assert_eq!(instance.handle().as_raw(), fake_handle().as_raw());
}
#[test]
fn commands_returns_reference() {
let instance =
unsafe { Instance::load(fake_handle(), Some(mock_get_instance_proc_addr), None, None) };
let _ = instance.commands();
}
unsafe extern "system" fn rich_instance_proc_addr(
_instance: vk::Instance,
name: *const c_char,
) -> vk::PFN_vkVoidFunction {
let name = unsafe { std::ffi::CStr::from_ptr(name) };
match name.to_bytes() {
b"vkGetDeviceProcAddr" => Some(unsafe {
std::mem::transmute::<
unsafe extern "system" fn(vk::Device, *const c_char) -> vk::PFN_vkVoidFunction,
unsafe extern "system" fn(),
>(mock_device_proc_addr)
}),
b"vkCreateDevice" => Some(unsafe {
std::mem::transmute::<
unsafe extern "system" fn(
vk::PhysicalDevice,
*const vk::DeviceCreateInfo,
*const vk::AllocationCallbacks,
*mut vk::Device,
) -> vk::Result,
unsafe extern "system" fn(),
>(mock_create_device)
}),
_ => None,
}
}
unsafe extern "system" fn mock_device_proc_addr(
_device: vk::Device,
_name: *const c_char,
) -> vk::PFN_vkVoidFunction {
None
}
unsafe extern "system" fn mock_create_device(
_physical_device: vk::PhysicalDevice,
_p_create_info: *const vk::DeviceCreateInfo,
_p_allocator: *const vk::AllocationCallbacks,
p_device: *mut vk::Device,
) -> vk::Result {
unsafe {
*p_device = std::mem::transmute::<usize, vk::Device>(0xBEEF_usize);
}
vk::Result::SUCCESS
}
fn mock_instance() -> Instance {
unsafe {
Instance::load(
fake_handle(),
Some(rich_instance_proc_addr),
Some(mock_device_proc_addr),
None,
)
}
}
#[test]
fn create_device_succeeds_with_mock() {
let instance = mock_instance();
let physical_device = vk::PhysicalDevice::from_raw(0xCAFE);
let create_info: vk::DeviceCreateInfo = unsafe { std::mem::zeroed() };
let device = unsafe { instance.create_device(physical_device, &create_info, None) }
.expect("create_device should succeed");
assert_eq!(device.handle().as_raw(), 0xBEEF);
}
#[test]
fn create_device_with_allocator() {
let instance = mock_instance();
let physical_device = vk::PhysicalDevice::from_raw(0xCAFE);
let create_info: vk::DeviceCreateInfo = unsafe { std::mem::zeroed() };
let allocator: vk::AllocationCallbacks = unsafe { std::mem::zeroed() };
let device =
unsafe { instance.create_device(physical_device, &create_info, Some(&allocator)) }
.expect("create_device should succeed");
assert_eq!(device.handle().as_raw(), 0xBEEF);
}
#[test]
fn from_raw_parts_resolves_device_proc_addr() {
let instance =
unsafe { Instance::from_raw_parts(fake_handle(), Some(rich_instance_proc_addr)) };
assert_eq!(instance.handle().as_raw(), fake_handle().as_raw());
let physical_device = vk::PhysicalDevice::from_raw(0xCAFE);
let create_info: vk::DeviceCreateInfo = unsafe { std::mem::zeroed() };
let device = unsafe { instance.create_device(physical_device, &create_info, None) }
.expect("create_device should succeed via from_raw_parts path");
assert_eq!(device.handle().as_raw(), 0xBEEF);
}
#[test]
fn load_with_loader_reference() {
use std::ffi::{CStr, c_void};
struct DummyLoader;
unsafe impl Loader for DummyLoader {
unsafe fn load(&self, _name: &CStr) -> *const c_void {
std::ptr::null()
}
}
let loader: Arc<dyn Loader> = Arc::new(DummyLoader);
let instance = unsafe {
Instance::load(
fake_handle(),
Some(mock_get_instance_proc_addr),
None,
Some(loader.clone()),
)
};
assert_eq!(Arc::strong_count(&loader), 2);
assert_eq!(instance.handle().as_raw(), fake_handle().as_raw());
}
unsafe extern "system" fn failing_instance_proc_addr(
_instance: vk::Instance,
name: *const c_char,
) -> vk::PFN_vkVoidFunction {
let name = unsafe { std::ffi::CStr::from_ptr(name) };
match name.to_bytes() {
b"vkGetDeviceProcAddr" => Some(unsafe {
std::mem::transmute::<
unsafe extern "system" fn(vk::Device, *const c_char) -> vk::PFN_vkVoidFunction,
unsafe extern "system" fn(),
>(mock_device_proc_addr)
}),
b"vkCreateDevice" => Some(unsafe {
std::mem::transmute::<
unsafe extern "system" fn(
vk::PhysicalDevice,
*const vk::DeviceCreateInfo,
*const vk::AllocationCallbacks,
*mut vk::Device,
) -> vk::Result,
unsafe extern "system" fn(),
>(failing_create_device)
}),
_ => None,
}
}
unsafe extern "system" fn failing_create_device(
_physical_device: vk::PhysicalDevice,
_p_create_info: *const vk::DeviceCreateInfo,
_p_allocator: *const vk::AllocationCallbacks,
_p_device: *mut vk::Device,
) -> vk::Result {
vk::Result::ERROR_INITIALIZATION_FAILED
}
#[test]
fn from_raw_parts_stores_no_loader() {
let instance =
unsafe { Instance::from_raw_parts(fake_handle(), Some(mock_get_instance_proc_addr)) };
assert_eq!(instance.handle().as_raw(), fake_handle().as_raw());
}
#[test]
fn load_with_loader_keeps_arc_alive() {
use std::ffi::{CStr, c_void};
struct DummyLoader;
unsafe impl Loader for DummyLoader {
unsafe fn load(&self, _name: &CStr) -> *const c_void {
std::ptr::null()
}
}
let loader: Arc<dyn Loader> = Arc::new(DummyLoader);
let weak = Arc::downgrade(&loader);
let instance = unsafe {
Instance::load(
fake_handle(),
Some(mock_get_instance_proc_addr),
None,
Some(loader),
)
};
assert!(weak.upgrade().is_some(), "loader should still be alive");
drop(instance);
assert!(weak.upgrade().is_none(), "loader should be dropped");
}
#[test]
fn commands_all_none_from_null_mock() {
let instance =
unsafe { Instance::load(fake_handle(), Some(mock_get_instance_proc_addr), None, None) };
assert!(instance.commands().enumerate_physical_devices.is_none());
assert!(instance.commands().destroy_instance.is_none());
assert!(instance.commands().create_device.is_none());
}
unsafe extern "system" fn mock_enumerate_physical_devices(
_instance: vk::Instance,
p_count: *mut u32,
p_devices: *mut vk::PhysicalDevice,
) -> vk::Result {
unsafe { *p_count = 2 };
if !p_devices.is_null() {
unsafe {
*p_devices = vk::PhysicalDevice::from_raw(0xAA);
*p_devices.add(1) = vk::PhysicalDevice::from_raw(0xBB);
}
}
vk::Result::SUCCESS
}
unsafe extern "system" fn mock_destroy_instance(
_instance: vk::Instance,
_p_allocator: *const vk::AllocationCallbacks,
) {
}
unsafe extern "system" fn rich_instance_proc_addr_v2(
_instance: vk::Instance,
name: *const c_char,
) -> vk::PFN_vkVoidFunction {
let name = unsafe { std::ffi::CStr::from_ptr(name) };
match name.to_bytes() {
b"vkGetDeviceProcAddr" => Some(unsafe {
std::mem::transmute::<
unsafe extern "system" fn(vk::Device, *const c_char) -> vk::PFN_vkVoidFunction,
unsafe extern "system" fn(),
>(mock_device_proc_addr)
}),
b"vkCreateDevice" => Some(unsafe {
std::mem::transmute::<
unsafe extern "system" fn(
vk::PhysicalDevice,
*const vk::DeviceCreateInfo,
*const vk::AllocationCallbacks,
*mut vk::Device,
) -> vk::Result,
unsafe extern "system" fn(),
>(mock_create_device)
}),
b"vkEnumeratePhysicalDevices" => Some(unsafe {
std::mem::transmute::<
unsafe extern "system" fn(
vk::Instance,
*mut u32,
*mut vk::PhysicalDevice,
) -> vk::Result,
unsafe extern "system" fn(),
>(mock_enumerate_physical_devices)
}),
b"vkDestroyInstance" => Some(unsafe {
std::mem::transmute::<
unsafe extern "system" fn(vk::Instance, *const vk::AllocationCallbacks),
unsafe extern "system" fn(),
>(mock_destroy_instance)
}),
_ => None,
}
}
#[test]
fn from_raw_parts_populates_commands_from_rich_mock() {
let instance =
unsafe { Instance::from_raw_parts(fake_handle(), Some(rich_instance_proc_addr_v2)) };
assert!(instance.commands().enumerate_physical_devices.is_some());
assert!(instance.commands().destroy_instance.is_some());
assert!(instance.commands().create_device.is_some());
assert!(instance.commands().get_physical_device_properties.is_none());
}
#[test]
fn enumerate_physical_devices_with_mock() {
let instance =
unsafe { Instance::from_raw_parts(fake_handle(), Some(rich_instance_proc_addr_v2)) };
let devices =
unsafe { instance.enumerate_physical_devices() }.expect("enumerate should succeed");
assert_eq!(devices.len(), 2);
assert_eq!(devices[0].as_raw(), 0xAA);
assert_eq!(devices[1].as_raw(), 0xBB);
}
#[test]
fn destroy_instance_with_mock() {
let instance =
unsafe { Instance::from_raw_parts(fake_handle(), Some(rich_instance_proc_addr_v2)) };
unsafe { instance.destroy_instance(None) };
}
#[test]
fn create_device_from_raw_parts_instance() {
let instance =
unsafe { Instance::from_raw_parts(fake_handle(), Some(rich_instance_proc_addr_v2)) };
let create_info: vk::DeviceCreateInfo = unsafe { std::mem::zeroed() };
let device = unsafe {
instance.create_device(vk::PhysicalDevice::from_raw(0xAA), &create_info, None)
}
.expect("create_device should succeed");
assert_eq!(device.handle().as_raw(), 0xBEEF);
}
#[test]
fn create_device_propagates_error() {
let instance = unsafe {
Instance::load(
fake_handle(),
Some(failing_instance_proc_addr),
Some(mock_device_proc_addr),
None,
)
};
let physical_device = vk::PhysicalDevice::from_raw(0xCAFE);
let create_info: vk::DeviceCreateInfo = unsafe { std::mem::zeroed() };
let result = unsafe { instance.create_device(physical_device, &create_info, None) };
match result {
Err(e) => assert_eq!(e, vk::Result::ERROR_INITIALIZATION_FAILED),
Ok(_) => panic!("expected error, got Ok"),
}
}
#[test]
#[ignore] fn enumerate_physical_devices_returns_at_least_one() {
let _vk = crate::VK_TEST_MUTEX.lock().expect("VK_TEST_MUTEX poisoned");
let instance = create_real_instance();
let devices = unsafe { instance.enumerate_physical_devices() }
.expect("enumerate_physical_devices failed");
assert!(!devices.is_empty(), "expected at least one physical device");
}
#[test]
#[ignore] fn get_physical_device_properties_succeeds() {
let _vk = crate::VK_TEST_MUTEX.lock().expect("VK_TEST_MUTEX poisoned");
let instance = create_real_instance();
let devices = unsafe { instance.enumerate_physical_devices() }
.expect("enumerate_physical_devices failed");
let props = unsafe { instance.get_physical_device_properties(devices[0]) };
let name_bytes: Vec<u8> = props
.device_name
.iter()
.take_while(|&&c| c != 0)
.map(|&c| c as u8)
.collect();
let name = String::from_utf8_lossy(&name_bytes);
println!("GPU: {name}");
assert!(!name.is_empty());
}
#[test]
#[ignore] fn get_physical_device_queue_family_properties_returns_at_least_one() {
let _vk = crate::VK_TEST_MUTEX.lock().expect("VK_TEST_MUTEX poisoned");
let instance = create_real_instance();
let devices = unsafe { instance.enumerate_physical_devices() }
.expect("enumerate_physical_devices failed");
let families = unsafe { instance.get_physical_device_queue_family_properties(devices[0]) };
assert!(!families.is_empty(), "expected at least one queue family");
}
#[test]
#[ignore] fn get_physical_device_memory_properties_succeeds() {
let _vk = crate::VK_TEST_MUTEX.lock().expect("VK_TEST_MUTEX poisoned");
let instance = create_real_instance();
let devices = unsafe { instance.enumerate_physical_devices() }
.expect("enumerate_physical_devices failed");
let mem_props = unsafe { instance.get_physical_device_memory_properties(devices[0]) };
assert!(
mem_props.memory_type_count > 0,
"expected at least one memory type"
);
assert!(
mem_props.memory_heap_count > 0,
"expected at least one memory heap"
);
}
#[test]
#[ignore] fn get_physical_device_features_succeeds() {
let _vk = crate::VK_TEST_MUTEX.lock().expect("VK_TEST_MUTEX poisoned");
let instance = create_real_instance();
let devices = unsafe { instance.enumerate_physical_devices() }
.expect("enumerate_physical_devices failed");
let _features = unsafe { instance.get_physical_device_features(devices[0]) };
}
fn create_real_instance() -> Instance {
use crate::entry::Entry;
use crate::loader::LibloadingLoader;
let loader = LibloadingLoader::new().expect("failed to load Vulkan");
let entry = unsafe { Entry::new(loader) }.expect("failed to create Entry");
let api_version_1_0 = crate::Version::new(1, 0, 0).to_raw();
let app_info = vk::ApplicationInfo {
s_type: vk::StructureType::APPLICATION_INFO,
p_next: std::ptr::null(),
p_application_name: std::ptr::null(),
application_version: 0,
p_engine_name: std::ptr::null(),
engine_version: 0,
api_version: api_version_1_0,
};
let create_info = vk::InstanceCreateInfo {
s_type: vk::StructureType::INSTANCE_CREATE_INFO,
p_next: std::ptr::null(),
flags: vk::InstanceCreateFlagBits::empty(),
p_application_info: &app_info,
enabled_layer_count: 0,
pp_enabled_layer_names: std::ptr::null(),
enabled_extension_count: 0,
pp_enabled_extension_names: std::ptr::null(),
};
unsafe { entry.create_instance(&create_info, None) }.expect("failed to create instance")
}
}