use std::ffi::CStr;
use std::fmt;
use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle};
use crate::error::check;
use crate::instance::Instance;
use crate::vk;
use vk::Handle;
#[derive(Debug)]
pub enum SurfaceError {
UnsupportedPlatform,
HandleError(raw_window_handle::HandleError),
Vulkan(vk::Result),
}
impl fmt::Display for SurfaceError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnsupportedPlatform => {
f.write_str("unsupported display/window handle combination for Vulkan surface")
}
Self::HandleError(e) => write!(f, "raw-window-handle error: {e}"),
Self::Vulkan(e) => write!(f, "Vulkan surface creation failed: {e:?}"),
}
}
}
impl std::error::Error for SurfaceError {}
impl From<raw_window_handle::HandleError> for SurfaceError {
fn from(e: raw_window_handle::HandleError) -> Self {
Self::HandleError(e)
}
}
impl From<vk::Result> for SurfaceError {
fn from(e: vk::Result) -> Self {
Self::Vulkan(e)
}
}
pub fn required_extensions() -> &'static [&'static CStr] {
use vk::extension_names::*;
#[cfg(target_os = "windows")]
{
&[KHR_SURFACE_EXTENSION_NAME, KHR_WIN32_SURFACE_EXTENSION_NAME]
}
#[cfg(all(
unix,
not(target_os = "android"),
not(target_os = "macos"),
not(target_os = "ios"),
))]
{
&[
KHR_SURFACE_EXTENSION_NAME,
KHR_XLIB_SURFACE_EXTENSION_NAME,
KHR_WAYLAND_SURFACE_EXTENSION_NAME,
]
}
#[cfg(target_os = "macos")]
{
&[KHR_SURFACE_EXTENSION_NAME, EXT_METAL_SURFACE_EXTENSION_NAME]
}
#[cfg(target_os = "android")]
{
&[
KHR_SURFACE_EXTENSION_NAME,
KHR_ANDROID_SURFACE_EXTENSION_NAME,
]
}
#[cfg(not(any(
target_os = "windows",
target_os = "android",
target_os = "macos",
target_os = "ios",
all(
unix,
not(target_os = "android"),
not(target_os = "macos"),
not(target_os = "ios"),
),
)))]
{
compile_error!(
"vulkan-rust surface support: unsupported platform. \
Disable the `surface` feature or open an issue."
);
}
}
impl Instance {
pub unsafe fn create_surface(
&self,
display: &dyn HasDisplayHandle,
window: &dyn HasWindowHandle,
allocator: Option<&vk::AllocationCallbacks>,
) -> Result<vk::SurfaceKHR, SurfaceError> {
let raw_display = display.display_handle()?.as_raw();
let raw_window = window.window_handle()?.as_raw();
let alloc_ptr = allocator.map_or(std::ptr::null(), |a| a as *const _);
match (raw_display, raw_window) {
#[cfg(target_os = "windows")]
(RawDisplayHandle::Windows(_), RawWindowHandle::Win32(h)) => {
let info = vk::Win32SurfaceCreateInfoKHR {
hinstance: h.hinstance.map_or(0, |v| v.get()),
hwnd: h.hwnd.get(),
..Default::default()
};
let fp = self
.commands()
.create_win32_surface_khr
.expect("VK_KHR_win32_surface not loaded");
let mut surface = vk::SurfaceKHR::null();
check(unsafe { fp(self.handle(), &info, alloc_ptr, &mut surface) })?;
Ok(surface)
}
#[cfg(all(
unix,
not(target_os = "android"),
not(target_os = "macos"),
not(target_os = "ios"),
))]
(RawDisplayHandle::Xlib(d), RawWindowHandle::Xlib(w)) => {
let info = vk::XlibSurfaceCreateInfoKHR {
dpy: d.display.map_or(std::ptr::null_mut(), |p| p.as_ptr()),
window: w.window,
..Default::default()
};
let fp = self
.commands()
.create_xlib_surface_khr
.expect("VK_KHR_xlib_surface not loaded");
let mut surface = vk::SurfaceKHR::null();
check(unsafe { fp(self.handle(), &info, alloc_ptr, &mut surface) })?;
Ok(surface)
}
#[cfg(all(
unix,
not(target_os = "android"),
not(target_os = "macos"),
not(target_os = "ios"),
))]
(RawDisplayHandle::Xcb(d), RawWindowHandle::Xcb(w)) => {
let info = vk::XcbSurfaceCreateInfoKHR {
connection: d.connection.map_or(std::ptr::null_mut(), |p| p.as_ptr()),
window: w.window.get(),
..Default::default()
};
let fp = self
.commands()
.create_xcb_surface_khr
.expect("VK_KHR_xcb_surface not loaded");
let mut surface = vk::SurfaceKHR::null();
check(unsafe { fp(self.handle(), &info, alloc_ptr, &mut surface) })?;
Ok(surface)
}
#[cfg(all(
unix,
not(target_os = "android"),
not(target_os = "macos"),
not(target_os = "ios"),
))]
(RawDisplayHandle::Wayland(d), RawWindowHandle::Wayland(w)) => {
let info = vk::WaylandSurfaceCreateInfoKHR {
display: d.display.as_ptr(),
surface: w.surface.as_ptr(),
..Default::default()
};
let fp = self
.commands()
.create_wayland_surface_khr
.expect("VK_KHR_wayland_surface not loaded");
let mut surface = vk::SurfaceKHR::null();
check(unsafe { fp(self.handle(), &info, alloc_ptr, &mut surface) })?;
Ok(surface)
}
#[cfg(target_os = "macos")]
(RawDisplayHandle::AppKit(_), RawWindowHandle::AppKit(w)) => {
let info = vk::MetalSurfaceCreateInfoEXT {
p_layer: w.ns_view.as_ptr() as *const _,
..Default::default()
};
let fp = self
.commands()
.create_metal_surface_ext
.expect("VK_EXT_metal_surface not loaded");
let mut surface = vk::SurfaceKHR::null();
check(unsafe { fp(self.handle(), &info, alloc_ptr, &mut surface) })?;
Ok(surface)
}
#[cfg(target_os = "android")]
(RawDisplayHandle::Android(_), RawWindowHandle::AndroidNdk(w)) => {
let info = vk::AndroidSurfaceCreateInfoKHR {
window: w.a_native_window.as_ptr(),
..Default::default()
};
let fp = self
.commands()
.create_android_surface_khr
.expect("VK_KHR_android_surface not loaded");
let mut surface = vk::SurfaceKHR::null();
check(unsafe { fp(self.handle(), &info, alloc_ptr, &mut surface) })?;
Ok(surface)
}
_ => Err(SurfaceError::UnsupportedPlatform),
}
}
pub unsafe fn destroy_surface(
&self,
surface: vk::SurfaceKHR,
allocator: Option<&vk::AllocationCallbacks>,
) {
let fp = self
.commands()
.destroy_surface_khr
.expect("VK_KHR_surface not loaded");
let alloc_ptr = allocator.map_or(std::ptr::null(), |a| a as *const _);
unsafe { fp(self.handle(), surface, alloc_ptr) };
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn required_extensions_is_non_empty() {
assert!(!required_extensions().is_empty());
}
#[test]
fn first_extension_is_khr_surface() {
assert_eq!(
required_extensions()[0],
vk::extension_names::KHR_SURFACE_EXTENSION_NAME
);
}
#[test]
fn unsupported_handle_returns_error() {
use raw_window_handle::{
DisplayHandle, RawDisplayHandle, RawWindowHandle, WebDisplayHandle, WebWindowHandle,
WindowHandle,
};
let raw_display = RawDisplayHandle::Web(WebDisplayHandle::new());
let display = unsafe { DisplayHandle::borrow_raw(raw_display) };
let raw_window = RawWindowHandle::Web(WebWindowHandle::new(1));
let window = unsafe { WindowHandle::borrow_raw(raw_window) };
let instance = mock_instance();
let result = unsafe { instance.create_surface(&display, &window, None) };
assert!(matches!(result, Err(SurfaceError::UnsupportedPlatform)));
}
#[test]
#[should_panic(expected = "VK_KHR_surface not loaded")]
fn destroy_surface_panics_when_extension_not_loaded() {
let instance = mock_instance();
unsafe {
instance.destroy_surface(vk::SurfaceKHR::null(), None);
}
}
#[test]
fn surface_error_display_unsupported() {
let err = SurfaceError::UnsupportedPlatform;
assert_eq!(
err.to_string(),
"unsupported display/window handle combination for Vulkan surface"
);
}
#[test]
fn surface_error_display_vulkan() {
let err = SurfaceError::Vulkan(vk::Result::ERROR_SURFACE_LOST);
let msg = err.to_string();
assert!(msg.contains("Vulkan surface creation failed"));
}
#[test]
fn surface_error_display_handle_error() {
let err = SurfaceError::HandleError(raw_window_handle::HandleError::Unavailable);
let msg = err.to_string();
assert!(msg.contains("raw-window-handle error"));
}
#[test]
fn surface_error_from_handle_error() {
let he = raw_window_handle::HandleError::Unavailable;
let se: SurfaceError = he.into();
assert!(matches!(se, SurfaceError::HandleError(_)));
}
#[test]
fn surface_error_from_vk_result() {
let vk_err = vk::Result::ERROR_SURFACE_LOST;
let se: SurfaceError = vk_err.into();
assert!(matches!(se, SurfaceError::Vulkan(_)));
}
fn mock_instance() -> Instance {
use std::ffi::c_char;
unsafe extern "system" fn mock_get_instance_proc_addr(
_instance: vk::Instance,
_name: *const c_char,
) -> vk::PFN_vkVoidFunction {
None
}
unsafe {
Instance::from_raw_parts(
vk::Instance::from_raw(0xDEAD),
Some(mock_get_instance_proc_addr),
)
}
}
#[test]
fn surface_error_is_std_error() {
let err: &dyn std::error::Error = &SurfaceError::UnsupportedPlatform;
assert!(err.source().is_none());
}
#[cfg(target_os = "windows")]
mod win32_tests {
use super::*;
use std::ffi::c_char;
use std::num::NonZeroIsize;
use raw_window_handle::{
DisplayHandle, RawDisplayHandle, RawWindowHandle, Win32WindowHandle, WindowHandle,
WindowsDisplayHandle,
};
unsafe extern "system" fn mock_create_win32_surface(
_instance: vk::Instance,
_p_create_info: *const vk::Win32SurfaceCreateInfoKHR,
_p_allocator: *const vk::AllocationCallbacks,
p_surface: *mut vk::SurfaceKHR,
) -> vk::Result {
unsafe { *p_surface = vk::SurfaceKHR::from_raw(0xEF01) };
vk::Result::SUCCESS
}
unsafe extern "system" fn failing_create_win32_surface(
_instance: vk::Instance,
_p_create_info: *const vk::Win32SurfaceCreateInfoKHR,
_p_allocator: *const vk::AllocationCallbacks,
_p_surface: *mut vk::SurfaceKHR,
) -> vk::Result {
vk::Result::ERROR_INITIALIZATION_FAILED
}
unsafe extern "system" fn mock_destroy_surface(
instance: vk::Instance,
surface: vk::SurfaceKHR,
_p_allocator: *const vk::AllocationCallbacks,
) {
assert_eq!(instance.as_raw(), 0xDEAD, "wrong instance handle");
assert_eq!(surface.as_raw(), 0xABCD, "wrong surface handle");
}
unsafe extern "system" fn surface_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"vkCreateWin32SurfaceKHR" => Some(unsafe {
std::mem::transmute::<
unsafe extern "system" fn(
vk::Instance,
*const vk::Win32SurfaceCreateInfoKHR,
*const vk::AllocationCallbacks,
*mut vk::SurfaceKHR,
) -> vk::Result,
unsafe extern "system" fn(),
>(mock_create_win32_surface)
}),
b"vkDestroySurfaceKHR" => Some(unsafe {
std::mem::transmute::<
unsafe extern "system" fn(
vk::Instance,
vk::SurfaceKHR,
*const vk::AllocationCallbacks,
),
unsafe extern "system" fn(),
>(mock_destroy_surface)
}),
_ => None,
}
}
unsafe extern "system" fn failing_surface_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"vkCreateWin32SurfaceKHR" => Some(unsafe {
std::mem::transmute::<
unsafe extern "system" fn(
vk::Instance,
*const vk::Win32SurfaceCreateInfoKHR,
*const vk::AllocationCallbacks,
*mut vk::SurfaceKHR,
) -> vk::Result,
unsafe extern "system" fn(),
>(failing_create_win32_surface)
}),
_ => None,
}
}
fn win32_display() -> DisplayHandle<'static> {
let raw = RawDisplayHandle::Windows(WindowsDisplayHandle::new());
unsafe { DisplayHandle::borrow_raw(raw) }
}
fn win32_window() -> WindowHandle<'static> {
let hwnd = NonZeroIsize::new(0x1234).unwrap();
let raw = RawWindowHandle::Win32(Win32WindowHandle::new(hwnd));
unsafe { WindowHandle::borrow_raw(raw) }
}
fn surface_instance() -> Instance {
unsafe {
Instance::from_raw_parts(
vk::Instance::from_raw(0xDEAD),
Some(surface_instance_proc_addr),
)
}
}
#[test]
fn create_surface_win32_succeeds() {
let instance = surface_instance();
let display = win32_display();
let window = win32_window();
let surface = unsafe { instance.create_surface(&display, &window, None) }
.expect("create_surface should succeed");
assert!(!surface.is_null());
}
#[test]
fn create_surface_win32_propagates_vulkan_error() {
let instance = unsafe {
Instance::from_raw_parts(
vk::Instance::from_raw(0xDEAD),
Some(failing_surface_instance_proc_addr),
)
};
let display = win32_display();
let window = win32_window();
let result = unsafe { instance.create_surface(&display, &window, None) };
match result {
Err(SurfaceError::Vulkan(e)) => {
assert_eq!(e, vk::Result::ERROR_INITIALIZATION_FAILED);
}
other => panic!("expected SurfaceError::Vulkan, got {other:?}"),
}
}
#[test]
fn destroy_surface_calls_fp_with_correct_args() {
let instance = surface_instance();
let surface = vk::SurfaceKHR::from_raw(0xABCD);
unsafe { instance.destroy_surface(surface, None) };
}
}
}