Skip to main content

vulkan_rust/
surface.rs

1//! Platform-aware Vulkan surface creation and extension helpers.
2//!
3//! **Guide:** [Hello Triangle, Part 2](https://hiddentale.github.io/vulkan_rust/getting-started/hello-triangle-2.html)
4//! covers surface creation and swapchain setup.
5
6use 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/// Error returned by surface creation.
17///
18/// # Examples
19///
20/// ```
21/// use vulkan_rust::SurfaceError;
22///
23/// let err = SurfaceError::UnsupportedPlatform;
24/// assert!(err.to_string().contains("unsupported"));
25/// ```
26#[derive(Debug)]
27pub enum SurfaceError {
28    /// The display/window handle combination is not supported.
29    UnsupportedPlatform,
30    /// `raw-window-handle` returned an error.
31    HandleError(raw_window_handle::HandleError),
32    /// Vulkan error from the surface creation call.
33    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
62/// Instance extensions required for surface creation on this platform.
63///
64/// Always includes `VK_KHR_surface`. Adds the platform-specific
65/// surface extension based on `#[cfg(target_os)]`.
66///
67/// # Examples
68///
69/// ```
70/// use vulkan_rust::required_extensions;
71/// use vulkan_rust::vk::extension_names::KHR_SURFACE_EXTENSION_NAME;
72///
73/// let exts = required_extensions();
74/// assert!(exts.iter().any(|e| *e == KHR_SURFACE_EXTENSION_NAME));
75/// ```
76pub 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        // Wayland and X11, return both; the loader ignores missing ones
90        // at enumerate time, and the user can filter to what's available.
91        &[
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    /// Create a Vulkan surface from platform window handles.
130    ///
131    /// Supports Win32, X11, Wayland, Metal, and Android. The instance must
132    /// have been created with the extensions returned by [`required_extensions()`].
133    ///
134    /// # Safety
135    ///
136    /// - `display` and `window` must be valid and outlive the returned surface.
137    /// - The instance must have enabled the required surface extensions.
138    /// - The returned surface must be destroyed with `destroy_surface_khr`
139    ///   before the instance is destroyed.
140    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        // SAFETY (all arms): caller guarantees display/window handles are valid and outlive
151        // the surface. The instance has the required surface extensions enabled. The create
152        // info structs are built from the raw handles and are valid for the duration of the call.
153        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                // The caller's NSView must be backed by a CAMetalLayer
236                // (typically set up by the windowing library).
237                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    /// Destroy a Vulkan surface.
270    ///
271    /// # Safety
272    ///
273    /// - The surface must not be in use by any swapchain or other object.
274    /// - The surface must have been created from this instance.
275    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        // SAFETY: caller guarantees the surface is not in use and belongs to this instance.
286        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    // -- Platform-specific surface creation tests (Windows) -------------------
396
397    #[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            // The mock asserts handle values internally.
544            unsafe { instance.destroy_surface(surface, None) };
545        }
546    }
547}