1use std::ffi::CStr;
7use std::fmt;
8
9use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle};
10
11use crate::error::check;
12use crate::instance::Instance;
13use crate::vk;
14use vk::handles::Handle;
15
16#[derive(Debug)]
27pub enum SurfaceError {
28 UnsupportedPlatform,
30 HandleError(raw_window_handle::HandleError),
32 Vulkan(vk::enums::Result),
34}
35
36impl fmt::Display for SurfaceError {
37 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38 match self {
39 Self::UnsupportedPlatform => {
40 f.write_str("unsupported display/window handle combination for Vulkan surface")
41 }
42 Self::HandleError(e) => write!(f, "raw-window-handle error: {e}"),
43 Self::Vulkan(e) => write!(f, "Vulkan surface creation failed: {e:?}"),
44 }
45 }
46}
47
48impl std::error::Error for SurfaceError {}
49
50impl From<raw_window_handle::HandleError> for SurfaceError {
51 fn from(e: raw_window_handle::HandleError) -> Self {
52 Self::HandleError(e)
53 }
54}
55
56impl From<vk::enums::Result> for SurfaceError {
57 fn from(e: vk::enums::Result) -> Self {
58 Self::Vulkan(e)
59 }
60}
61
62pub fn required_extensions() -> &'static [&'static CStr] {
77 use vk::extension_names::*;
78 #[cfg(target_os = "windows")]
79 {
80 &[KHR_SURFACE_EXTENSION_NAME, KHR_WIN32_SURFACE_EXTENSION_NAME]
81 }
82 #[cfg(all(
83 unix,
84 not(target_os = "android"),
85 not(target_os = "macos"),
86 not(target_os = "ios"),
87 ))]
88 {
89 &[
92 KHR_SURFACE_EXTENSION_NAME,
93 KHR_XLIB_SURFACE_EXTENSION_NAME,
94 KHR_WAYLAND_SURFACE_EXTENSION_NAME,
95 ]
96 }
97 #[cfg(target_os = "macos")]
98 {
99 &[KHR_SURFACE_EXTENSION_NAME, EXT_METAL_SURFACE_EXTENSION_NAME]
100 }
101 #[cfg(target_os = "android")]
102 {
103 &[
104 KHR_SURFACE_EXTENSION_NAME,
105 KHR_ANDROID_SURFACE_EXTENSION_NAME,
106 ]
107 }
108 #[cfg(not(any(
109 target_os = "windows",
110 target_os = "android",
111 target_os = "macos",
112 target_os = "ios",
113 all(
114 unix,
115 not(target_os = "android"),
116 not(target_os = "macos"),
117 not(target_os = "ios"),
118 ),
119 )))]
120 {
121 compile_error!(
122 "vulkan-rust surface support: unsupported platform. \
123 Disable the `surface` feature or open an issue."
124 );
125 }
126}
127
128impl Instance {
129 pub unsafe fn create_surface(
141 &self,
142 display: &dyn HasDisplayHandle,
143 window: &dyn HasWindowHandle,
144 allocator: Option<&vk::structs::AllocationCallbacks>,
145 ) -> Result<vk::handles::SurfaceKHR, SurfaceError> {
146 let raw_display = display.display_handle()?.as_raw();
147 let raw_window = window.window_handle()?.as_raw();
148 let alloc_ptr = allocator.map_or(std::ptr::null(), |a| a as *const _);
149
150 match (raw_display, raw_window) {
154 #[cfg(target_os = "windows")]
155 (RawDisplayHandle::Windows(_), RawWindowHandle::Win32(h)) => {
156 let info = vk::structs::Win32SurfaceCreateInfoKHR {
157 hinstance: h.hinstance.map_or(0, |v| v.get()),
158 hwnd: h.hwnd.get(),
159 ..Default::default()
160 };
161 let fp = self
162 .commands()
163 .create_win32_surface_khr
164 .expect("VK_KHR_win32_surface not loaded");
165 let mut surface = vk::handles::SurfaceKHR::null();
166 check(unsafe { fp(self.handle(), &info, alloc_ptr, &mut surface) })?;
167 Ok(surface)
168 }
169
170 #[cfg(all(
171 unix,
172 not(target_os = "android"),
173 not(target_os = "macos"),
174 not(target_os = "ios"),
175 ))]
176 (RawDisplayHandle::Xlib(d), RawWindowHandle::Xlib(w)) => {
177 let info = vk::structs::XlibSurfaceCreateInfoKHR {
178 dpy: d.display.map_or(std::ptr::null_mut(), |p| p.as_ptr()),
179 window: w.window,
180 ..Default::default()
181 };
182 let fp = self
183 .commands()
184 .create_xlib_surface_khr
185 .expect("VK_KHR_xlib_surface not loaded");
186 let mut surface = vk::handles::SurfaceKHR::null();
187 check(unsafe { fp(self.handle(), &info, alloc_ptr, &mut surface) })?;
188 Ok(surface)
189 }
190
191 #[cfg(all(
192 unix,
193 not(target_os = "android"),
194 not(target_os = "macos"),
195 not(target_os = "ios"),
196 ))]
197 (RawDisplayHandle::Xcb(d), RawWindowHandle::Xcb(w)) => {
198 let info = vk::structs::XcbSurfaceCreateInfoKHR {
199 connection: d.connection.map_or(std::ptr::null_mut(), |p| p.as_ptr()),
200 window: w.window.get(),
201 ..Default::default()
202 };
203 let fp = self
204 .commands()
205 .create_xcb_surface_khr
206 .expect("VK_KHR_xcb_surface not loaded");
207 let mut surface = vk::handles::SurfaceKHR::null();
208 check(unsafe { fp(self.handle(), &info, alloc_ptr, &mut surface) })?;
209 Ok(surface)
210 }
211
212 #[cfg(all(
213 unix,
214 not(target_os = "android"),
215 not(target_os = "macos"),
216 not(target_os = "ios"),
217 ))]
218 (RawDisplayHandle::Wayland(d), RawWindowHandle::Wayland(w)) => {
219 let info = vk::structs::WaylandSurfaceCreateInfoKHR {
220 display: d.display.as_ptr(),
221 surface: w.surface.as_ptr(),
222 ..Default::default()
223 };
224 let fp = self
225 .commands()
226 .create_wayland_surface_khr
227 .expect("VK_KHR_wayland_surface not loaded");
228 let mut surface = vk::handles::SurfaceKHR::null();
229 check(unsafe { fp(self.handle(), &info, alloc_ptr, &mut surface) })?;
230 Ok(surface)
231 }
232
233 #[cfg(target_os = "macos")]
234 (RawDisplayHandle::AppKit(_), RawWindowHandle::AppKit(w)) => {
235 let info = vk::structs::MetalSurfaceCreateInfoEXT {
238 p_layer: w.ns_view.as_ptr() as *const _,
239 ..Default::default()
240 };
241 let fp = self
242 .commands()
243 .create_metal_surface_ext
244 .expect("VK_EXT_metal_surface not loaded");
245 let mut surface = vk::handles::SurfaceKHR::null();
246 check(unsafe { fp(self.handle(), &info, alloc_ptr, &mut surface) })?;
247 Ok(surface)
248 }
249
250 #[cfg(target_os = "android")]
251 (RawDisplayHandle::Android(_), RawWindowHandle::AndroidNdk(w)) => {
252 let info = vk::structs::AndroidSurfaceCreateInfoKHR {
253 window: w.a_native_window.as_ptr(),
254 ..Default::default()
255 };
256 let fp = self
257 .commands()
258 .create_android_surface_khr
259 .expect("VK_KHR_android_surface not loaded");
260 let mut surface = vk::handles::SurfaceKHR::null();
261 check(unsafe { fp(self.handle(), &info, alloc_ptr, &mut surface) })?;
262 Ok(surface)
263 }
264
265 _ => Err(SurfaceError::UnsupportedPlatform),
266 }
267 }
268
269 pub unsafe fn destroy_surface(
276 &self,
277 surface: vk::handles::SurfaceKHR,
278 allocator: Option<&vk::structs::AllocationCallbacks>,
279 ) {
280 let fp = self
281 .commands()
282 .destroy_surface_khr
283 .expect("VK_KHR_surface not loaded");
284 let alloc_ptr = allocator.map_or(std::ptr::null(), |a| a as *const _);
285 unsafe { fp(self.handle(), surface, alloc_ptr) };
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293
294 #[test]
295 fn required_extensions_is_non_empty() {
296 assert!(!required_extensions().is_empty());
297 }
298
299 #[test]
300 fn first_extension_is_khr_surface() {
301 assert_eq!(
302 required_extensions()[0],
303 vk::extension_names::KHR_SURFACE_EXTENSION_NAME
304 );
305 }
306
307 #[test]
308 fn unsupported_handle_returns_error() {
309 use raw_window_handle::{
310 DisplayHandle, RawDisplayHandle, RawWindowHandle, WebDisplayHandle, WebWindowHandle,
311 WindowHandle,
312 };
313
314 let raw_display = RawDisplayHandle::Web(WebDisplayHandle::new());
315 let display = unsafe { DisplayHandle::borrow_raw(raw_display) };
316
317 let raw_window = RawWindowHandle::Web(WebWindowHandle::new(1));
318 let window = unsafe { WindowHandle::borrow_raw(raw_window) };
319
320 let instance = mock_instance();
321 let result = unsafe { instance.create_surface(&display, &window, None) };
322 assert!(matches!(result, Err(SurfaceError::UnsupportedPlatform)));
323 }
324
325 #[test]
326 #[should_panic(expected = "VK_KHR_surface not loaded")]
327 fn destroy_surface_panics_when_extension_not_loaded() {
328 let instance = mock_instance();
329 unsafe {
330 instance.destroy_surface(vk::handles::SurfaceKHR::null(), None);
331 }
332 }
333
334 #[test]
335 fn surface_error_display_unsupported() {
336 let err = SurfaceError::UnsupportedPlatform;
337 assert_eq!(
338 err.to_string(),
339 "unsupported display/window handle combination for Vulkan surface"
340 );
341 }
342
343 #[test]
344 fn surface_error_display_vulkan() {
345 let err = SurfaceError::Vulkan(vk::enums::Result::ERROR_SURFACE_LOST);
346 let msg = err.to_string();
347 assert!(msg.contains("Vulkan surface creation failed"));
348 }
349
350 #[test]
351 fn surface_error_display_handle_error() {
352 let err = SurfaceError::HandleError(raw_window_handle::HandleError::Unavailable);
353 let msg = err.to_string();
354 assert!(msg.contains("raw-window-handle error"));
355 }
356
357 #[test]
358 fn surface_error_from_handle_error() {
359 let he = raw_window_handle::HandleError::Unavailable;
360 let se: SurfaceError = he.into();
361 assert!(matches!(se, SurfaceError::HandleError(_)));
362 }
363
364 #[test]
365 fn surface_error_from_vk_result() {
366 let vk_err = vk::enums::Result::ERROR_SURFACE_LOST;
367 let se: SurfaceError = vk_err.into();
368 assert!(matches!(se, SurfaceError::Vulkan(_)));
369 }
370
371 fn mock_instance() -> Instance {
372 use std::ffi::c_char;
373
374 unsafe extern "system" fn mock_get_instance_proc_addr(
375 _instance: vk::handles::Instance,
376 _name: *const c_char,
377 ) -> vk::structs::PFN_vkVoidFunction {
378 None
379 }
380
381 unsafe {
382 Instance::from_raw_parts(
383 vk::handles::Instance::from_raw(0xDEAD),
384 Some(mock_get_instance_proc_addr),
385 )
386 }
387 }
388
389 #[test]
390 fn surface_error_is_std_error() {
391 let err: &dyn std::error::Error = &SurfaceError::UnsupportedPlatform;
392 assert!(err.source().is_none());
393 }
394
395 #[cfg(target_os = "windows")]
398 mod win32_tests {
399 use super::*;
400 use std::ffi::c_char;
401 use std::num::NonZeroIsize;
402
403 use raw_window_handle::{
404 DisplayHandle, RawDisplayHandle, RawWindowHandle, Win32WindowHandle, WindowHandle,
405 WindowsDisplayHandle,
406 };
407
408 unsafe extern "system" fn mock_create_win32_surface(
409 _instance: vk::handles::Instance,
410 _p_create_info: *const vk::structs::Win32SurfaceCreateInfoKHR,
411 _p_allocator: *const vk::structs::AllocationCallbacks,
412 p_surface: *mut vk::handles::SurfaceKHR,
413 ) -> vk::enums::Result {
414 unsafe { *p_surface = vk::handles::SurfaceKHR::from_raw(0xEF01) };
415 vk::enums::Result::SUCCESS
416 }
417
418 unsafe extern "system" fn failing_create_win32_surface(
419 _instance: vk::handles::Instance,
420 _p_create_info: *const vk::structs::Win32SurfaceCreateInfoKHR,
421 _p_allocator: *const vk::structs::AllocationCallbacks,
422 _p_surface: *mut vk::handles::SurfaceKHR,
423 ) -> vk::enums::Result {
424 vk::enums::Result::ERROR_INITIALIZATION_FAILED
425 }
426
427 unsafe extern "system" fn mock_destroy_surface(
428 instance: vk::handles::Instance,
429 surface: vk::handles::SurfaceKHR,
430 _p_allocator: *const vk::structs::AllocationCallbacks,
431 ) {
432 assert_eq!(instance.as_raw(), 0xDEAD, "wrong instance handle");
433 assert_eq!(surface.as_raw(), 0xABCD, "wrong surface handle");
434 }
435
436 unsafe extern "system" fn surface_instance_proc_addr(
437 _instance: vk::handles::Instance,
438 name: *const c_char,
439 ) -> vk::structs::PFN_vkVoidFunction {
440 let name = unsafe { CStr::from_ptr(name) };
441 match name.to_bytes() {
442 b"vkCreateWin32SurfaceKHR" => Some(unsafe {
443 std::mem::transmute::<
444 unsafe extern "system" fn(
445 vk::handles::Instance,
446 *const vk::structs::Win32SurfaceCreateInfoKHR,
447 *const vk::structs::AllocationCallbacks,
448 *mut vk::handles::SurfaceKHR,
449 ) -> vk::enums::Result,
450 unsafe extern "system" fn(),
451 >(mock_create_win32_surface)
452 }),
453 b"vkDestroySurfaceKHR" => Some(unsafe {
454 std::mem::transmute::<
455 unsafe extern "system" fn(
456 vk::handles::Instance,
457 vk::handles::SurfaceKHR,
458 *const vk::structs::AllocationCallbacks,
459 ),
460 unsafe extern "system" fn(),
461 >(mock_destroy_surface)
462 }),
463 _ => None,
464 }
465 }
466
467 unsafe extern "system" fn failing_surface_instance_proc_addr(
468 _instance: vk::handles::Instance,
469 name: *const c_char,
470 ) -> vk::structs::PFN_vkVoidFunction {
471 let name = unsafe { CStr::from_ptr(name) };
472 match name.to_bytes() {
473 b"vkCreateWin32SurfaceKHR" => Some(unsafe {
474 std::mem::transmute::<
475 unsafe extern "system" fn(
476 vk::handles::Instance,
477 *const vk::structs::Win32SurfaceCreateInfoKHR,
478 *const vk::structs::AllocationCallbacks,
479 *mut vk::handles::SurfaceKHR,
480 ) -> vk::enums::Result,
481 unsafe extern "system" fn(),
482 >(failing_create_win32_surface)
483 }),
484 _ => None,
485 }
486 }
487
488 fn win32_display() -> DisplayHandle<'static> {
489 let raw = RawDisplayHandle::Windows(WindowsDisplayHandle::new());
490 unsafe { DisplayHandle::borrow_raw(raw) }
491 }
492
493 fn win32_window() -> WindowHandle<'static> {
494 let hwnd = NonZeroIsize::new(0x1234).unwrap();
495 let raw = RawWindowHandle::Win32(Win32WindowHandle::new(hwnd));
496 unsafe { WindowHandle::borrow_raw(raw) }
497 }
498
499 fn surface_instance() -> Instance {
500 unsafe {
501 Instance::from_raw_parts(
502 vk::handles::Instance::from_raw(0xDEAD),
503 Some(surface_instance_proc_addr),
504 )
505 }
506 }
507
508 #[test]
509 fn create_surface_win32_succeeds() {
510 let instance = surface_instance();
511 let display = win32_display();
512 let window = win32_window();
513
514 let surface = unsafe { instance.create_surface(&display, &window, None) }
515 .expect("create_surface should succeed");
516 assert!(!surface.is_null());
517 }
518
519 #[test]
520 fn create_surface_win32_propagates_vulkan_error() {
521 let instance = unsafe {
522 Instance::from_raw_parts(
523 vk::handles::Instance::from_raw(0xDEAD),
524 Some(failing_surface_instance_proc_addr),
525 )
526 };
527 let display = win32_display();
528 let window = win32_window();
529
530 let result = unsafe { instance.create_surface(&display, &window, None) };
531 match result {
532 Err(SurfaceError::Vulkan(e)) => {
533 assert_eq!(e, vk::enums::Result::ERROR_INITIALIZATION_FAILED);
534 }
535 other => panic!("expected SurfaceError::Vulkan, got {other:?}"),
536 }
537 }
538
539 #[test]
540 fn destroy_surface_calls_fp_with_correct_args() {
541 let instance = surface_instance();
542 let surface = vk::handles::SurfaceKHR::from_raw(0xABCD);
543 unsafe { instance.destroy_surface(surface, None) };
545 }
546 }
547}