ash-tray 0.5.0

A Tray to host Ash with Winit
Documentation
//    Copyright 2019 Michael Mestnik

//    Licensed under the Apache License, Version 2.0 (the "License");
//    you may not use this file except in compliance with the License.
//    You may obtain a copy of the License at

//        http://www.apache.org/licenses/LICENSE-2.0

//    Unless required by applicable law or agreed to in writing, software
//    distributed under the License is distributed on an "AS IS" BASIS,
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//    See the License for the specific language governing permissions and
//    limitations under the License.

pub use winit;
pub mod ash;
pub use imgui;
pub use imgui_sys;
pub use vk_mem;
pub mod imguiash;
pub use glsl_to_spirv_macros::*;
pub use glsl_to_spirv_macros_impl::GLSLEmbedImpl;

use std::os::raw::c_char;
use winit::window::Window;

use crate::ash::{extensions::khr, prelude::VkResult, vk};

pub mod vk_helper;

/// Extension trait for Vulkan surface creation.
pub trait WindowExt {
    /// Test whether presentation is supported on a physical device.
    ///
    /// This function first determines the correct Vulkan WSI extension for this window and then calls one of the
    /// `get_*_presentation_support_*` family of functions on the `PhysicalDevice`.
    fn is_presentation_supported(
        &self,
        entry: &ash::Entry,
        instance: &ash::Instance,
        physical_device: vk::PhysicalDevice,
        queue_family_indices: u32,
    ) -> VkResult<bool>;

    /// Determine required Vulkan instance extensions.
    ///
    /// This will always include [`VK_KHR_surface`]. One of the platform-dependent WSI extensions,
    /// that corresponds to this window, will also be added.
    ///
    /// Please note, that the device extension [`VK_KHR_swapchain`] is also required for
    /// presentation.
    ///
    /// [`VK_KHR_surface`]: https://www.khronos.org/registry/vulkan/specs/1.0-extensions/html/vkspec.html#VK_KHR_surface
    /// [`VK_KHR_swapchain`]: https://www.khronos.org/registry/vulkan/specs/1.0-extensions/html/vkspec.html#VK_KHR_swapchain
    fn get_required_extensions(&self) -> VkResult<Vec<*const c_char>>;

    /// Create a surface for this window.
    ///
    /// `Instance` must have been created with required extensions, as determined by
    /// `get_required_extensions()`. The `flags` parameter is currently just a place holder. You
    /// should specify `SurfaceCreateFlags::empty()` here.
    fn create_surface(
        &self,
        entry: &ash::Entry,
        instance: &ash::Instance,
        flags: vk::Flags,
        allocator: Option<&vk::AllocationCallbacks>,
    ) -> VkResult<vk::SurfaceKHR>;
}

impl WindowExt for Window {
    fn is_presentation_supported(
        &self,
        entry: &ash::Entry,
        instance: &ash::Instance,
        physical_device: vk::PhysicalDevice,
        queue_family_indices: u32,
    ) -> VkResult<bool> {
        match get_backend(self)? {
            Backend::Xlib { .. } => Ok(true), // FIXME: This needs a VisualID, which winit does not expose
            Backend::Wayland { display, .. } => {
                let surface = ash::extensions::khr::WaylandSurface::new(entry, instance);
                Ok(unsafe {
                    surface.get_physical_device_wayland_presentation_support(
                        physical_device,
                        queue_family_indices,
                        display,
                    )
                })
            }
            Backend::Win32 { .. } => {
                let surface = ash::extensions::khr::Win32Surface::new(entry, instance);
                Ok(unsafe {
                    surface.get_physical_device_win32_presentation_support(
                        physical_device,
                        queue_family_indices,
                    )
                })
            }
        }
    }

    fn get_required_extensions(&self) -> VkResult<Vec<*const c_char>> {
        match get_backend(self)? {
            Backend::Xlib { .. } => Ok(vec![
                khr::Surface::name().as_ptr(),
                khr::XlibSurface::name().as_ptr(),
            ]),
            Backend::Wayland { .. } => Ok(vec![
                khr::Surface::name().as_ptr(),
                khr::WaylandSurface::name().as_ptr(),
            ]),
            Backend::Win32 { .. } => Ok(vec![
                khr::Surface::name().as_ptr(),
                khr::Win32Surface::name().as_ptr(),
            ]),
        }
    }

    fn create_surface(
        &self,
        entry: &ash::Entry,
        instance: &ash::Instance,
        flags: vk::Flags,
        allocator: Option<&vk::AllocationCallbacks>,
    ) -> VkResult<vk::SurfaceKHR> {
        match get_backend(self)? {
            Backend::Xlib { display, window } => {
                let create_info = vk::XlibSurfaceCreateInfoKHR {
                    flags: vk::XlibSurfaceCreateFlagsKHR::from_raw(flags),
                    dpy: display,
                    window,
                    ..Default::default()
                };

                unsafe {
                    khr::XlibSurface::new(entry, instance)
                        .create_xlib_surface(&create_info, allocator)
                }
            }

            Backend::Wayland { display, surface } => {
                let create_info = vk::WaylandSurfaceCreateInfoKHR {
                    flags: vk::WaylandSurfaceCreateFlagsKHR::from_raw(flags),
                    display,
                    surface,
                    ..Default::default()
                };

                unsafe {
                    khr::WaylandSurface::new(entry, instance)
                        .create_wayland_surface(&create_info, allocator)
                }
            }

            Backend::Win32 { hinstance, hwnd } => {
                let create_info = vk::Win32SurfaceCreateInfoKHR {
                    flags: vk::Win32SurfaceCreateFlagsKHR::from_raw(flags),
                    hinstance,
                    hwnd,
                    ..Default::default()
                };

                unsafe {
                    khr::Win32Surface::new(entry, instance)
                        .create_win32_surface(&create_info, allocator)
                }
            }
        }
    }
}

#[allow(dead_code)]
enum Backend<'a> {
    Xlib {
        display: &'a mut vk::Display,
        window: vk::Window,
    },

    Wayland {
        display: &'a mut vk::wl_display,
        surface: &'a mut vk::wl_surface,
    },

    Win32 {
        hinstance: vk::HINSTANCE,
        hwnd: vk::HWND,
    },
}

#[allow(unused_variables)]
#[allow(unreachable_code)]
fn get_backend(window: &Window) -> VkResult<Backend> {
    #[cfg(any(
        target_os = "linux",
        target_os = "dragonfly",
        target_os = "freebsd",
        target_os = "openbsd"
    ))]
    {
        use winit::platform::unix::WindowExtUnix;

        if let (Some(display), Some(window)) = (window.xlib_display(), window.xlib_window()) {
            // Do we know it can't be NULL?
            return if !display.is_null() {
                Ok(Backend::Xlib {
                    display: unsafe { &mut *(display as *mut vk::Display) },
                    window: window as _,
                })
            } else {
                Err(vk::Result::ERROR_OUT_OF_HOST_MEMORY)
            };
        }

        if let (Some(display), Some(surface)) = (window.wayland_display(), window.wayland_surface())
        {
            // Do we know they can't be NULL?
            return if !(display.is_null() || surface.is_null()) {
                Ok(Backend::Wayland {
                    display: unsafe { &mut *(display as *mut vk::wl_display) },
                    surface: unsafe { &mut *(surface as *mut vk::wl_surface) },
                })
            } else {
                Err(vk::Result::ERROR_OUT_OF_HOST_MEMORY)
            };
        }
    }

    #[cfg(target_os = "windows")]
    {
        use winit::platform::windows::WindowExtWindows;

        return Ok(Backend::Win32 {
            hinstance: ::std::ptr::null_mut(), // FIXME: Need HINSTANCE of the correct module
            hwnd: window.hwnd() as _,
        });
    }

    Err(vk::Result::ERROR_INITIALIZATION_FAILED)
}