* Fix iOS, use similar approach for metal layer as wgpu

* Use correct return type

* Handle ios resizing

* Add changelog notes

* Fix typo

* Update raw-window-handle

* Ensure mac changes correspond master version
This commit is contained in:
Okko Hakola 2022-08-11 10:49:29 +03:00 committed by GitHub
parent d4e760caa7
commit 062b5e2f96
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 227 additions and 56 deletions

View File

@ -8,6 +8,7 @@
-->
- **Breaking** Public dependency updates:
- Winit 0.27
- raw-window-handle 0.5
- **Breaking** Changes to `Instance` and Vulkan initialization:
- `FunctionPointers` is renamed to `VulkanLibrary`, and now resides in a separate `library` module. It is re-exported from the crate root.
- The `Loader` trait is now in the `library` module.
@ -36,6 +37,12 @@
- **Breaking** Changes to memory pools:
- Renamed `StdMemoryPool[Alloc]`, `StdHostVisibleMemoryTypePool[Alloc]`, `StdNonHostVisibleMemoryTypePool[Alloc]` to `Standard{...}`.
- Removed `Device::standard_pool` in favor of `Device::standard_memory_pool`, which returns `&Arc<StandardMemoryPool>`.
- **Potentially Breaking** Fix iOS compilation:
- Removed dependency to `cocoa` and `metal`
- Fixed iOS compilation errors
- Added `winit_to_surface` method for iOS, ensuring we can draw to a sub `CAMetalLayer` layer
- Added `Surface::update_ios_sublayer_on_resize` to ensure iOS sublayer is fullscreen if initial window size was not the same as device's
- Ensure both iOS and MacOS have `CAMetalLayer` when using `create_surface_from_handle`
- Bugs fixed:
- [#1896](https://github.com/vulkano-rs/vulkano/issues/1896): Vulkano-shaders generates invalid struct definitions when struct field names are stripped out by the compiler.

View File

@ -339,6 +339,10 @@ impl VulkanoWindowRenderer {
self.remove_additional_image_view(i);
self.add_additional_image_view(i, format, usage);
}
#[cfg(target_os = "ios")]
unsafe {
self.surface.update_ios_sublayer_on_resize();
}
self.recreate_swapchain = false;
}
}

View File

@ -13,15 +13,14 @@ categories = ["rendering::graphics-api"]
[features]
default = ["winit_", "raw-window-handle_"]
winit_ = ["winit", "metal", "cocoa", "objc"]
winit_ = ["winit", "objc", "core-graphics-types"]
raw-window-handle_ = ["raw-window-handle"]
[dependencies]
raw-window-handle = { version = "0.4", optional = true }
raw-window-handle = { version = "0.5", optional = true }
vulkano = { version = "0.30.0", path = "../vulkano" }
winit = { version = "0.27", optional = true }
[target.'cfg(target_os = "macos")'.dependencies]
cocoa = { version = "0.24", optional = true }
metal = { version = "0.23", optional = true }
objc = { version = "0.2", optional = true }
[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies]
objc = { version = "0.2.5", optional = true }
core-graphics-types = { version = "0.1", optional = true }

View File

@ -1,4 +1,10 @@
use raw_window_handle::{HasRawWindowHandle, RawWindowHandle};
#[cfg(target_os = "ios")]
use crate::get_metal_layer_ios;
#[cfg(target_os = "macos")]
use crate::get_metal_layer_macos;
use raw_window_handle::{
HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle,
};
use std::sync::Arc;
use vulkano::instance::Instance;
use vulkano::swapchain::Surface;
@ -6,29 +12,64 @@ use vulkano::swapchain::SurfaceCreationError;
/// Creates a vulkan surface from a generic window
/// which implements HasRawWindowHandle and thus can reveal the os-dependent handle.
/// - Note that if you wish to use this function with MacOS, you will need to ensure that the
/// `CAMetalLayer` is set to the ns_view. An example of how one might do that can be found in
/// `vulkano_win::set_ca_metal_layer_to_winit`
pub fn create_surface_from_handle<W>(
window: W,
instance: Arc<Instance>,
) -> Result<Arc<Surface<W>>, SurfaceCreationError>
where
W: HasRawWindowHandle,
W: HasRawWindowHandle + HasRawDisplayHandle,
{
unsafe {
match window.raw_window_handle() {
RawWindowHandle::AndroidNdk(h) => {
Surface::from_android(instance, h.a_native_window, window)
}
RawWindowHandle::UiKit(h) => Surface::from_ios(instance, h.ui_view, window),
RawWindowHandle::AppKit(h) => Surface::from_mac_os(instance, h.ns_view, window),
RawWindowHandle::UiKit(_h) => {
#[cfg(target_os = "ios")]
{
// Ensure the layer is CAMetalLayer
let layer = get_metal_layer_ios(_h.ui_view);
Surface::from_ios(instance, layer, window)
}
#[cfg(not(target_os = "ios"))]
{
panic!("UiKit handle should only be used when target_os == 'ios'");
}
}
RawWindowHandle::AppKit(_h) => {
#[cfg(target_os = "macos")]
{
// Ensure the layer is CAMetalLayer
let layer = get_metal_layer_macos(_h.ns_view);
Surface::from_mac_os(instance, layer as *const (), window)
}
#[cfg(not(target_os = "macos"))]
{
panic!("AppKit handle should only be used when target_os == 'macos'");
}
}
RawWindowHandle::Wayland(h) => {
Surface::from_wayland(instance, h.display, h.surface, window)
let d = match window.raw_display_handle() {
RawDisplayHandle::Wayland(d) => d,
_ => panic!("Invalid RawDisplayHandle"),
};
Surface::from_wayland(instance, d.display, h.surface, window)
}
RawWindowHandle::Win32(h) => Surface::from_win32(instance, h.hinstance, h.hwnd, window),
RawWindowHandle::Xcb(h) => Surface::from_xcb(instance, h.connection, h.window, window),
RawWindowHandle::Xlib(h) => Surface::from_xlib(instance, h.display, h.window, window),
RawWindowHandle::Xcb(h) => {
let d = match window.raw_display_handle() {
RawDisplayHandle::Xcb(d) => d,
_ => panic!("Invalid RawDisplayHandle"),
};
Surface::from_xcb(instance, d.connection, h.window, window)
}
RawWindowHandle::Xlib(h) => {
let d = match window.raw_display_handle() {
RawDisplayHandle::Xlib(d) => d,
_ => panic!("Invalid RawDisplayHandle"),
};
Surface::from_xlib(instance, d.display, h.window, window)
}
RawWindowHandle::Web(_) => unimplemented!(),
_ => unimplemented!(),
}

View File

@ -123,7 +123,12 @@ unsafe fn winit_to_surface<W: SafeBorrow<Window>>(
}
}
#[cfg(all(unix, not(target_os = "android"), not(target_os = "macos")))]
#[cfg(all(
unix,
not(target_os = "android"),
not(target_os = "macos"),
not(target_os = "ios")
))]
unsafe fn winit_to_surface<W: SafeBorrow<Window>>(
instance: Arc<Instance>,
win: W,
@ -157,36 +162,37 @@ unsafe fn winit_to_surface<W: SafeBorrow<Window>>(
}
}
#[cfg(target_os = "macos")]
use cocoa::{
appkit::{NSView, NSWindow},
base::id as cocoa_id,
};
#[cfg(target_os = "macos")]
use metal::MetalLayer;
#[cfg(target_os = "macos")]
use objc::runtime::YES;
#[cfg(target_os = "macos")]
use std::mem;
#[cfg(any(target_os = "macos", target_os = "ios"))]
use objc::{class, msg_send, runtime::Object, sel, sel_impl};
/// Ensure `CAMetalLayer` (native rendering surface on MacOs) is used by the ns_view.
/// Get (and set) `CAMetalLayer` to ns_view.
/// This is necessary to be able to render on Mac.
#[cfg(target_os = "macos")]
unsafe fn set_ca_metal_layer_to_winit<W: SafeBorrow<Window>>(win: W) {
use winit::platform::macos::WindowExtMacOS;
pub(crate) unsafe fn get_metal_layer_macos(view: *mut std::ffi::c_void) -> *mut Object {
use core_graphics_types::base::CGFloat;
use objc::runtime::YES;
use objc::runtime::{BOOL, NO};
let wnd: cocoa_id = mem::transmute(win.borrow().ns_window());
let layer = MetalLayer::new();
layer.set_edge_antialiasing_mask(0);
layer.set_presents_with_transaction(false);
layer.remove_all_animations();
let view = wnd.contentView();
layer.set_contents_scale(view.backingScaleFactor());
view.setLayer(mem::transmute(layer.as_ref())); // Bombs here with out of memory
view.setWantsLayer(YES);
let view: *mut Object = std::mem::transmute(view);
let main_layer: *mut Object = msg_send![view, layer];
let class = class!(CAMetalLayer);
let is_valid_layer: BOOL = msg_send![main_layer, isKindOfClass: class];
if is_valid_layer == NO {
let new_layer: *mut Object = msg_send![class, new];
let () = msg_send![new_layer, setEdgeAntialiasingMask: 0];
let () = msg_send![new_layer, setPresentsWithTransaction: false];
let () = msg_send![new_layer, removeAllAnimations];
let () = msg_send![view, setLayer: new_layer];
let () = msg_send![view, setWantsLayer: YES];
let window: *mut Object = msg_send![view, window];
if !window.is_null() {
let scale_factor: CGFloat = msg_send![window, backingScaleFactor];
let () = msg_send![new_layer, setContentsScale: scale_factor];
}
new_layer
} else {
main_layer
}
}
#[cfg(target_os = "macos")]
@ -195,9 +201,39 @@ unsafe fn winit_to_surface<W: SafeBorrow<Window>>(
win: W,
) -> Result<Arc<Surface<W>>, SurfaceCreationError> {
use winit::platform::macos::WindowExtMacOS;
let layer = get_metal_layer_macos(win.borrow().ns_view());
Surface::from_mac_os(instance, layer as *const (), win)
}
set_ca_metal_layer_to_winit(win.borrow());
Surface::from_mac_os(instance, win.borrow().ns_view() as *const (), win)
#[cfg(target_os = "ios")]
use vulkano::swapchain::IOSMetalLayer;
/// Get sublayer from iOS main view (ui_view). The sublayer is created as CAMetalLayer
#[cfg(target_os = "ios")]
pub(crate) unsafe fn get_metal_layer_ios(view: *mut std::ffi::c_void) -> IOSMetalLayer {
use core_graphics_types::{base::CGFloat, geometry::CGRect};
let view: *mut Object = std::mem::transmute(view);
let main_layer: *mut Object = msg_send![view, layer];
let class = class!(CAMetalLayer);
let new_layer: *mut Object = msg_send![class, new];
let frame: CGRect = msg_send![main_layer, bounds];
let () = msg_send![new_layer, setFrame: frame];
let () = msg_send![main_layer, addSublayer: new_layer];
let screen: *mut Object = msg_send![class!(UIScreen), mainScreen];
let scale_factor: CGFloat = msg_send![screen, nativeScale];
let () = msg_send![view, setContentScaleFactor: scale_factor];
IOSMetalLayer::new(view, new_layer)
}
#[cfg(target_os = "ios")]
unsafe fn winit_to_surface<W: SafeBorrow<Window>>(
instance: Arc<Instance>,
win: W,
) -> Result<Arc<Surface<W>>, SurfaceCreationError> {
use winit::platform::ios::WindowExtIOS;
let layer = get_metal_layer_ios(win.borrow().ui_view());
Surface::from_ios(instance, layer, win)
}
#[cfg(target_os = "windows")]

View File

@ -26,6 +26,10 @@ once_cell = { version = "1.13", features = ["parking_lot"] }
parking_lot = { version = "0.12", features = ["send_guard"] }
smallvec = "1.8"
[target.'cfg(target_os = "ios")'.dependencies]
objc = "0.2.5"
core-graphics-types = "0.1"
[build-dependencies]
heck = "0.4"
indexmap = "1.8"

View File

@ -42,8 +42,8 @@ impl VulkanLibrary {
pub fn new() -> Result<Arc<Self>, LoadingError> {
#[cfg(target_os = "ios")]
#[allow(non_snake_case)]
fn def_loader_impl() -> Result<Box<Loader>, LoadingError> {
let loader = statically_linked_vulkan_loader!();
fn def_loader_impl() -> Result<Box<dyn Loader>, LoadingError> {
let loader = crate::statically_linked_vulkan_loader!();
Ok(Box::new(loader))
}
@ -84,7 +84,6 @@ impl VulkanLibrary {
.get_instance_proc_addr(ash::vk::Instance::null(), name.as_ptr())
.map_or(ptr::null(), |func| func as _)
});
// Per the Vulkan spec:
// If the vkGetInstanceProcAddr returns NULL for vkEnumerateInstanceVersion, it is a
// Vulkan 1.0 implementation. Otherwise, the application can call vkEnumerateInstanceVersion
@ -325,12 +324,12 @@ macro_rules! statically_linked_vulkan_loader {
struct StaticallyLinkedVulkanLoader;
unsafe impl Loader for StaticallyLinkedVulkanLoader {
fn get_instance_proc_addr(
unsafe fn get_instance_proc_addr(
&self,
instance: ash::vk::Instance,
name: *const c_char,
) -> extern "system" fn() -> () {
unsafe { vkGetInstanceProcAddr(instance, name) }
) -> ash::vk::PFN_vkVoidFunction {
vkGetInstanceProcAddr(instance, name)
}
}

View File

@ -330,6 +330,9 @@ pub use self::{
SwapchainAcquireFuture, SwapchainCreateInfo, SwapchainCreationError, Win32Monitor,
},
};
#[cfg(target_os = "ios")]
pub use surface::IOSMetalLayer;
use std::sync::atomic::AtomicBool;
pub mod display;

View File

@ -18,6 +18,10 @@ use crate::{
},
Error, OomError, VulkanObject,
};
#[cfg(target_os = "ios")]
use objc::{class, msg_send, runtime::Object, sel, sel_impl};
use std::{
error, fmt,
hash::{Hash, Hasher},
@ -35,10 +39,11 @@ pub struct Surface<W> {
instance: Arc<Instance>,
api: SurfaceApi,
window: W,
// If true, a swapchain has been associated to this surface, and that any new swapchain
// creation should be forbidden.
has_swapchain: AtomicBool,
#[cfg(target_os = "ios")]
metal_layer: IOSMetalLayer,
}
impl<W> Surface<W> {
@ -63,6 +68,8 @@ impl<W> Surface<W> {
window: win,
has_swapchain: AtomicBool::new(false),
#[cfg(target_os = "ios")]
metal_layer: IOSMetalLayer::new(std::ptr::null_mut(), std::ptr::null_mut()),
}
}
@ -131,6 +138,8 @@ impl<W> Surface<W> {
window: (),
has_swapchain: AtomicBool::new(false),
#[cfg(target_os = "ios")]
metal_layer: IOSMetalLayer::new(std::ptr::null_mut(), std::ptr::null_mut()),
}))
}
@ -177,6 +186,8 @@ impl<W> Surface<W> {
window: win,
has_swapchain: AtomicBool::new(false),
#[cfg(target_os = "ios")]
metal_layer: IOSMetalLayer::new(std::ptr::null_mut(), std::ptr::null_mut()),
}))
}
@ -188,9 +199,10 @@ impl<W> Surface<W> {
/// - The object referred to by `view` must outlive the created `Surface`.
/// The `win` parameter can be used to ensure this.
/// - The `UIView` must be backed by a `CALayer` instance of type `CAMetalLayer`.
pub unsafe fn from_ios<T>(
#[cfg(target_os = "ios")]
pub unsafe fn from_ios(
instance: Arc<Instance>,
view: *const T,
metal_layer: IOSMetalLayer,
win: W,
) -> Result<Arc<Surface<W>>, SurfaceCreationError> {
if !instance.enabled_extensions().mvk_ios_surface {
@ -201,7 +213,7 @@ impl<W> Surface<W> {
let create_info = ash::vk::IOSSurfaceCreateInfoMVK {
flags: ash::vk::IOSSurfaceCreateFlagsMVK::empty(),
p_view: view as *const _,
p_view: metal_layer.render_layer.0 as *const _,
..Default::default()
};
@ -224,6 +236,7 @@ impl<W> Surface<W> {
window: win,
has_swapchain: AtomicBool::new(false),
metal_layer,
}))
}
@ -235,6 +248,7 @@ impl<W> Surface<W> {
/// - The object referred to by `view` must outlive the created `Surface`.
/// The `win` parameter can be used to ensure this.
/// - The `NSView` must be backed by a `CALayer` instance of type `CAMetalLayer`.
#[cfg(target_os = "macos")]
pub unsafe fn from_mac_os<T>(
instance: Arc<Instance>,
view: *const T,
@ -271,6 +285,8 @@ impl<W> Surface<W> {
window: win,
has_swapchain: AtomicBool::new(false),
#[cfg(target_os = "ios")]
metal_layer: IOSMetalLayer::new(std::ptr::null_mut(), std::ptr::null_mut()),
}))
}
@ -317,6 +333,8 @@ impl<W> Surface<W> {
window: win,
has_swapchain: AtomicBool::new(false),
#[cfg(target_os = "ios")]
metal_layer: IOSMetalLayer::new(std::ptr::null_mut(), std::ptr::null_mut()),
}))
}
@ -363,6 +381,8 @@ impl<W> Surface<W> {
window: win,
has_swapchain: AtomicBool::new(false),
#[cfg(target_os = "ios")]
metal_layer: IOSMetalLayer::new(std::ptr::null_mut(), std::ptr::null_mut()),
}))
}
@ -413,6 +433,8 @@ impl<W> Surface<W> {
window: win,
has_swapchain: AtomicBool::new(false),
#[cfg(target_os = "ios")]
metal_layer: IOSMetalLayer::new(std::ptr::null_mut(), std::ptr::null_mut()),
}))
}
@ -463,6 +485,8 @@ impl<W> Surface<W> {
window: win,
has_swapchain: AtomicBool::new(false),
#[cfg(target_os = "ios")]
metal_layer: IOSMetalLayer::new(std::ptr::null_mut(), std::ptr::null_mut()),
}))
}
@ -513,6 +537,8 @@ impl<W> Surface<W> {
window: win,
has_swapchain: AtomicBool::new(false),
#[cfg(target_os = "ios")]
metal_layer: IOSMetalLayer::new(std::ptr::null_mut(), std::ptr::null_mut()),
}))
}
@ -563,6 +589,8 @@ impl<W> Surface<W> {
window: win,
has_swapchain: AtomicBool::new(false),
#[cfg(target_os = "ios")]
metal_layer: IOSMetalLayer::new(std::ptr::null_mut(), std::ptr::null_mut()),
}))
}
@ -583,6 +611,24 @@ impl<W> Surface<W> {
pub fn window(&self) -> &W {
&self.window
}
/// Resizes the sublayer bounds on iOS.
/// It may not be necessary if original window size matches device's, but often it does not.
/// Thus this should be called after a resize has occurred abd swapchain has been recreated.
///
/// On iOS, we've created CAMetalLayer as a sublayer. However, when the view changes size,
/// its sublayers are not automatically resized, and we must resize
/// it here.
#[cfg(target_os = "ios")]
#[inline]
pub unsafe fn update_ios_sublayer_on_resize(&self) {
use core_graphics_types::geometry::CGRect;
let class = class!(CAMetalLayer);
let main_layer: *mut Object = self.metal_layer.main_layer.0;
let bounds: CGRect = msg_send![main_layer, bounds];
let render_layer: *mut Object = self.metal_layer.render_layer.0;
let () = msg_send![render_layer, setFrame: bounds];
}
}
impl<W> Drop for Surface<W> {
@ -616,8 +662,8 @@ impl<W> fmt::Debug for Surface<W> {
instance,
api,
window: _,
has_swapchain,
..
} = self;
fmt.debug_struct("Surface")
@ -1226,6 +1272,38 @@ impl Default for SurfaceInfo {
}
}
#[cfg(target_os = "ios")]
struct LayerHandle(*mut Object);
#[cfg(target_os = "ios")]
unsafe impl Send for LayerHandle {}
#[cfg(target_os = "ios")]
unsafe impl Sync for LayerHandle {}
/// Represents the metal layer for IOS
#[cfg(target_os = "ios")]
pub struct IOSMetalLayer {
main_layer: LayerHandle,
render_layer: LayerHandle,
}
#[cfg(target_os = "ios")]
impl IOSMetalLayer {
pub fn new(main_layer: *mut Object, render_layer: *mut Object) -> Self {
Self {
main_layer: LayerHandle(main_layer),
render_layer: LayerHandle(render_layer),
}
}
}
#[cfg(target_os = "ios")]
unsafe impl Send for IOSMetalLayer {}
#[cfg(target_os = "ios")]
unsafe impl Sync for IOSMetalLayer {}
/// The capabilities of a surface when used by a physical device.
///
/// You have to match these capabilities when you create a swapchain.