use std::sync::Arc;
use std::ffi::CStr;
use crate::error::{LoadError, VkResult, check, enumerate_two_call};
use crate::instance::Instance;
use crate::loader::Loader;
use crate::version::Version;
use crate::vk;
use vk::Handle;
pub struct Entry {
_loader: Arc<dyn Loader>,
get_instance_proc_addr: vk::commands::PFN_vkGetInstanceProcAddr,
get_device_proc_addr: vk::commands::PFN_vkGetDeviceProcAddr,
commands: vk::commands::EntryCommands,
}
impl Entry {
pub unsafe fn new(loader: impl Loader + 'static) -> Result<Self, LoadError> {
let loader: Arc<dyn Loader> = Arc::new(loader);
let get_instance_proc_addr: vk::commands::PFN_vkGetInstanceProcAddr = unsafe {
let ptr = loader.load(c"vkGetInstanceProcAddr");
if ptr.is_null() {
return Err(LoadError::MissingEntryPoint);
}
std::mem::transmute(ptr)
};
let get_instance_proc_addr_fn =
get_instance_proc_addr.expect("vkGetInstanceProcAddr not loaded");
let null_instance = vk::Instance::null();
let get_device_proc_addr: vk::commands::PFN_vkGetDeviceProcAddr =
unsafe { std::mem::transmute(loader.load(c"vkGetDeviceProcAddr")) };
let commands = unsafe {
vk::commands::EntryCommands::load(|name| {
std::mem::transmute(get_instance_proc_addr_fn(null_instance, name.as_ptr()))
})
};
Ok(Self {
_loader: loader,
get_instance_proc_addr,
get_device_proc_addr,
commands,
})
}
pub fn get_instance_proc_addr(&self) -> vk::commands::PFN_vkGetInstanceProcAddr {
self.get_instance_proc_addr
}
pub fn get_device_proc_addr(&self) -> vk::commands::PFN_vkGetDeviceProcAddr {
self.get_device_proc_addr
}
pub(crate) fn commands(&self) -> &vk::commands::EntryCommands {
&self.commands
}
pub fn version(&self) -> VkResult<Version> {
let fp = match self.commands.enumerate_instance_version {
Some(fp) => fp,
None => {
return Ok(Version {
major: 1,
minor: 0,
patch: 0,
});
}
};
let mut raw = 0u32;
check(unsafe { fp(&mut raw) })?;
Ok(Version::from_raw(raw))
}
pub unsafe fn create_instance(
&self,
create_info: &vk::InstanceCreateInfo,
allocator: Option<&vk::AllocationCallbacks>,
) -> VkResult<Instance> {
let raw = unsafe { self.create_instance_raw(create_info, allocator) }?;
let instance = unsafe {
Instance::load(
raw,
self.get_instance_proc_addr,
self.get_device_proc_addr,
Some(self._loader.clone()),
)
};
Ok(instance)
}
pub unsafe fn create_instance_raw(
&self,
create_info: &vk::InstanceCreateInfo,
allocator: Option<&vk::AllocationCallbacks>,
) -> VkResult<vk::Instance> {
let fp = self
.commands
.create_instance
.expect("vkCreateInstance not loaded");
let mut instance = vk::Instance::null();
let result = unsafe {
fp(
create_info,
allocator.map_or(std::ptr::null(), |a| a),
&mut instance,
)
};
check(result)?;
Ok(instance)
}
pub unsafe fn enumerate_instance_layer_properties(&self) -> VkResult<Vec<vk::LayerProperties>> {
let fp = self
.commands
.enumerate_instance_layer_properties
.expect("vkEnumerateInstanceLayerProperties not loaded");
enumerate_two_call(|count, data| unsafe { fp(count, data) })
}
pub unsafe fn enumerate_instance_extension_properties(
&self,
layer_name: Option<&CStr>,
) -> VkResult<Vec<vk::ExtensionProperties>> {
let fp = self
.commands
.enumerate_instance_extension_properties
.expect("vkEnumerateInstanceExtensionProperties not loaded");
let layer_ptr = layer_name.map_or(std::ptr::null(), |n| n.as_ptr());
enumerate_two_call(|count, data| unsafe { fp(layer_ptr, count, data) })
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::{CStr, c_char, c_void};
struct NullLoader;
unsafe impl Loader for NullLoader {
unsafe fn load(&self, _name: &CStr) -> *const c_void {
std::ptr::null()
}
}
struct FakeEntryLoader;
unsafe extern "system" fn mock_get_instance_proc_addr(
_instance: vk::Instance,
_name: *const c_char,
) -> vk::PFN_vkVoidFunction {
None
}
unsafe impl Loader for FakeEntryLoader {
unsafe fn load(&self, name: &CStr) -> *const c_void {
if name == c"vkGetInstanceProcAddr" {
mock_get_instance_proc_addr as *const c_void
} else {
std::ptr::null()
}
}
}
#[test]
fn new_returns_missing_entry_point_when_loader_returns_null() {
let result = unsafe { Entry::new(NullLoader) };
match result {
Err(LoadError::MissingEntryPoint) => {}
Err(other) => panic!("expected MissingEntryPoint, got {other}"),
Ok(_) => panic!("expected error, got Ok"),
}
}
#[test]
fn new_succeeds_with_fake_loader() {
let entry = unsafe { Entry::new(FakeEntryLoader) }.expect("should create Entry");
assert!(entry.get_instance_proc_addr().is_some());
}
#[test]
fn version_returns_1_0_when_enumerate_instance_version_is_none() {
let entry = unsafe { Entry::new(FakeEntryLoader) }.expect("should create Entry");
let version = entry.version().expect("version should succeed");
assert_eq!(version.major, 1);
assert_eq!(version.minor, 0);
assert_eq!(version.patch, 0);
}
#[test]
fn commands_returns_reference() {
let entry = unsafe { Entry::new(FakeEntryLoader) }.expect("should create Entry");
let _ = entry.commands();
}
#[test]
fn get_instance_proc_addr_returns_some() {
let entry = unsafe { Entry::new(FakeEntryLoader) }.expect("should create Entry");
assert!(entry.get_instance_proc_addr().is_some());
}
#[test]
fn get_device_proc_addr_returns_none_from_fake_loader() {
let entry = unsafe { Entry::new(FakeEntryLoader) }.expect("should create Entry");
assert!(entry.get_device_proc_addr().is_none());
}
struct RichEntryLoader;
unsafe extern "system" fn rich_get_instance_proc_addr(
_instance: vk::Instance,
name: *const c_char,
) -> vk::PFN_vkVoidFunction {
let name = unsafe { CStr::from_ptr(name) };
match name.to_bytes() {
b"vkEnumerateInstanceVersion" => Some(unsafe {
std::mem::transmute::<
unsafe extern "system" fn(*mut u32) -> vk::Result,
unsafe extern "system" fn(),
>(mock_enumerate_instance_version)
}),
b"vkEnumerateInstanceLayerProperties" => Some(unsafe {
std::mem::transmute::<
unsafe extern "system" fn(*mut u32, *mut vk::LayerProperties) -> vk::Result,
unsafe extern "system" fn(),
>(mock_enumerate_instance_layer_properties)
}),
b"vkEnumerateInstanceExtensionProperties" => Some(unsafe {
std::mem::transmute::<
unsafe extern "system" fn(
*const c_char,
*mut u32,
*mut vk::ExtensionProperties,
) -> vk::Result,
unsafe extern "system" fn(),
>(mock_enumerate_instance_extension_properties)
}),
b"vkCreateInstance" => Some(unsafe {
std::mem::transmute::<
unsafe extern "system" fn(
*const vk::InstanceCreateInfo,
*const vk::AllocationCallbacks,
*mut vk::Instance,
) -> vk::Result,
unsafe extern "system" fn(),
>(mock_create_instance)
}),
_ => None,
}
}
unsafe extern "system" fn mock_enumerate_instance_version(
p_api_version: *mut u32,
) -> vk::Result {
unsafe { *p_api_version = crate::Version::new(1, 3, 290).to_raw() };
vk::Result::SUCCESS
}
unsafe extern "system" fn mock_enumerate_instance_layer_properties(
p_count: *mut u32,
_p_properties: *mut vk::LayerProperties,
) -> vk::Result {
unsafe { *p_count = 0 };
vk::Result::SUCCESS
}
unsafe extern "system" fn mock_enumerate_instance_extension_properties(
_p_layer_name: *const c_char,
p_count: *mut u32,
_p_properties: *mut vk::ExtensionProperties,
) -> vk::Result {
unsafe { *p_count = 0 };
vk::Result::SUCCESS
}
unsafe extern "system" fn mock_create_instance(
_p_create_info: *const vk::InstanceCreateInfo,
_p_allocator: *const vk::AllocationCallbacks,
p_instance: *mut vk::Instance,
) -> vk::Result {
unsafe { *p_instance = std::mem::transmute::<usize, vk::Instance>(0x1234_usize) };
vk::Result::SUCCESS
}
unsafe impl Loader for RichEntryLoader {
unsafe fn load(&self, name: &CStr) -> *const c_void {
match name.to_bytes() {
b"vkGetInstanceProcAddr" => rich_get_instance_proc_addr as *const c_void,
b"vkGetDeviceProcAddr" => std::ptr::null(),
_ => std::ptr::null(),
}
}
}
#[test]
fn version_returns_parsed_version_when_fp_available() {
let entry = unsafe { Entry::new(RichEntryLoader) }.expect("should create Entry");
let version = entry.version().expect("version should succeed");
assert_eq!(version.major, 1);
assert_eq!(version.minor, 3);
assert_eq!(version.patch, 290);
}
#[test]
fn enumerate_layer_properties_with_mock() {
let entry = unsafe { Entry::new(RichEntryLoader) }.expect("should create Entry");
let layers =
unsafe { entry.enumerate_instance_layer_properties() }.expect("should succeed");
assert!(layers.is_empty());
}
#[test]
fn enumerate_extension_properties_with_mock() {
let entry = unsafe { Entry::new(RichEntryLoader) }.expect("should create Entry");
let extensions =
unsafe { entry.enumerate_instance_extension_properties(None) }.expect("should succeed");
assert!(extensions.is_empty());
}
#[test]
fn enumerate_extension_properties_with_layer_name() {
let entry = unsafe { Entry::new(RichEntryLoader) }.expect("should create Entry");
let extensions =
unsafe { entry.enumerate_instance_extension_properties(Some(c"VK_LAYER_test")) }
.expect("should succeed");
assert!(extensions.is_empty());
}
#[test]
fn create_instance_raw_with_mock() {
let entry = unsafe { Entry::new(RichEntryLoader) }.expect("should create Entry");
let create_info: vk::InstanceCreateInfo = unsafe { std::mem::zeroed() };
let raw = unsafe { entry.create_instance_raw(&create_info, None) }.expect("should succeed");
assert!(!raw.is_null());
}
struct FailingEntryLoader;
unsafe extern "system" fn failing_get_instance_proc_addr(
_instance: vk::Instance,
name: *const c_char,
) -> vk::PFN_vkVoidFunction {
let name = unsafe { CStr::from_ptr(name) };
match name.to_bytes() {
b"vkEnumerateInstanceVersion" => Some(unsafe {
std::mem::transmute::<
unsafe extern "system" fn(*mut u32) -> vk::Result,
unsafe extern "system" fn(),
>(failing_enumerate_instance_version)
}),
b"vkEnumerateInstanceLayerProperties" => Some(unsafe {
std::mem::transmute::<
unsafe extern "system" fn(*mut u32, *mut vk::LayerProperties) -> vk::Result,
unsafe extern "system" fn(),
>(failing_enumerate_instance_layer_properties)
}),
b"vkEnumerateInstanceExtensionProperties" => Some(unsafe {
std::mem::transmute::<
unsafe extern "system" fn(
*const c_char,
*mut u32,
*mut vk::ExtensionProperties,
) -> vk::Result,
unsafe extern "system" fn(),
>(failing_enumerate_instance_extension_properties)
}),
b"vkCreateInstance" => Some(unsafe {
std::mem::transmute::<
unsafe extern "system" fn(
*const vk::InstanceCreateInfo,
*const vk::AllocationCallbacks,
*mut vk::Instance,
) -> vk::Result,
unsafe extern "system" fn(),
>(failing_create_instance)
}),
_ => None,
}
}
unsafe extern "system" fn failing_enumerate_instance_version(
_p_api_version: *mut u32,
) -> vk::Result {
vk::Result::ERROR_OUT_OF_HOST_MEMORY
}
unsafe extern "system" fn failing_enumerate_instance_layer_properties(
_p_count: *mut u32,
_p_properties: *mut vk::LayerProperties,
) -> vk::Result {
vk::Result::ERROR_OUT_OF_HOST_MEMORY
}
unsafe extern "system" fn failing_enumerate_instance_extension_properties(
_p_layer_name: *const c_char,
_p_count: *mut u32,
_p_properties: *mut vk::ExtensionProperties,
) -> vk::Result {
vk::Result::ERROR_OUT_OF_HOST_MEMORY
}
unsafe extern "system" fn failing_create_instance(
_p_create_info: *const vk::InstanceCreateInfo,
_p_allocator: *const vk::AllocationCallbacks,
_p_instance: *mut vk::Instance,
) -> vk::Result {
vk::Result::ERROR_INITIALIZATION_FAILED
}
unsafe impl Loader for FailingEntryLoader {
unsafe fn load(&self, name: &CStr) -> *const c_void {
match name.to_bytes() {
b"vkGetInstanceProcAddr" => failing_get_instance_proc_addr as *const c_void,
_ => std::ptr::null(),
}
}
}
#[test]
fn version_propagates_error() {
let entry = unsafe { Entry::new(FailingEntryLoader) }.expect("should create Entry");
let result = entry.version();
assert_eq!(result.unwrap_err(), vk::Result::ERROR_OUT_OF_HOST_MEMORY);
}
#[test]
fn create_instance_raw_propagates_error() {
let entry = unsafe { Entry::new(FailingEntryLoader) }.expect("should create Entry");
let create_info: vk::InstanceCreateInfo = unsafe { std::mem::zeroed() };
let result = unsafe { entry.create_instance_raw(&create_info, None) };
assert_eq!(result.unwrap_err(), vk::Result::ERROR_INITIALIZATION_FAILED);
}
#[test]
fn enumerate_layer_properties_propagates_error() {
let entry = unsafe { Entry::new(FailingEntryLoader) }.expect("should create Entry");
let result = unsafe { entry.enumerate_instance_layer_properties() };
assert_eq!(result.unwrap_err(), vk::Result::ERROR_OUT_OF_HOST_MEMORY);
}
#[test]
fn enumerate_extension_properties_propagates_error() {
let entry = unsafe { Entry::new(FailingEntryLoader) }.expect("should create Entry");
let result = unsafe { entry.enumerate_instance_extension_properties(None) };
assert_eq!(result.unwrap_err(), vk::Result::ERROR_OUT_OF_HOST_MEMORY);
}
#[test]
#[ignore] fn new_succeeds_with_real_loader() {
let _vk = crate::VK_TEST_MUTEX.lock().expect("VK_TEST_MUTEX poisoned");
let loader = crate::loader::LibloadingLoader::new().expect("failed to load Vulkan library");
let entry = unsafe { Entry::new(loader) }.expect("failed to create Entry");
assert!(entry.get_instance_proc_addr().is_some());
assert!(entry.get_device_proc_addr().is_some());
}
#[test]
#[ignore] fn version_returns_at_least_1_0() {
let _vk = crate::VK_TEST_MUTEX.lock().expect("VK_TEST_MUTEX poisoned");
let entry = create_entry();
let version = entry.version().expect("failed to query version");
assert!(version.major >= 1);
println!("Vulkan {version}");
}
#[test]
#[ignore] fn enumerate_layer_properties_succeeds() {
let _vk = crate::VK_TEST_MUTEX.lock().expect("VK_TEST_MUTEX poisoned");
let entry = create_entry();
let layers = unsafe { entry.enumerate_instance_layer_properties() }
.expect("failed to enumerate layers");
println!("found {} layers", layers.len());
}
#[test]
#[ignore] fn enumerate_extension_properties_succeeds() {
let _vk = crate::VK_TEST_MUTEX.lock().expect("VK_TEST_MUTEX poisoned");
let entry = create_entry();
let extensions = unsafe { entry.enumerate_instance_extension_properties(None) }
.expect("failed to enumerate extensions");
assert!(!extensions.is_empty(), "expected at least one extension");
println!("found {} extensions", extensions.len());
}
fn create_entry() -> Entry {
let loader = crate::loader::LibloadingLoader::new().expect("failed to load Vulkan library");
unsafe { Entry::new(loader) }.expect("failed to create Entry")
}
}