1/* 2 * GStreamer 3 * Copyright (C) 2019 Matthew Waters <matthew@centricular.com> 4 * 5 * This library is free software; you can redistribute it and/or 6 * modify it under the terms of the GNU Library General Public 7 * License as published by the Free Software Foundation; either 8 * version 2 of the License, or (at your option) any later version. 9 * 10 * This library is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 * Library General Public License for more details. 14 * 15 * You should have received a copy of the GNU Library General Public 16 * License along with this library; if not, write to the 17 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, 18 * Boston, MA 02110-1301, USA. 19 */ 20 21#ifdef HAVE_CONFIG_H 22#include "config.h" 23#endif 24 25#include <Cocoa/Cocoa.h> 26#include <QuartzCore/QuartzCore.h> 27 28#include <gst/gst.h> 29 30#include <gst/vulkan/vulkan.h> 31 32#include "gstvkwindow_cocoa.h" 33#include "gstvkdisplay_cocoa.h" 34 35#include "gstvkcocoa_utils.h" 36 37#define GET_PRIV(o) gst_vulkan_window_cocoa_get_instance_private (o) 38 39#define GST_CAT_DEFAULT gst_vulkan_window_cocoa_debug 40GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); 41 42static void 43_init_debug (void) 44{ 45 static gsize _init = 0; 46 47 if (g_once_init_enter (&_init)) { 48 GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "vulkanwindowmacos", 0, 49 "Vulkan MacOS Window"); 50 g_once_init_leave (&_init, 1); 51 } 52} 53 54gboolean gst_vulkan_window_cocoa_handle_event (GstVulkanWindowCocoa * window_cocoa); 55 56enum 57{ 58 PROP_0, 59}; 60 61struct _GstVulkanWindowCocoaPrivate 62{ 63 gpointer internal_win_id; 64 gpointer internal_view; 65 66 gint preferred_width; 67 gint preferred_height; 68 69 gboolean visible; 70}; 71 72#define gst_vulkan_window_cocoa_parent_class parent_class 73G_DEFINE_TYPE_WITH_CODE (GstVulkanWindowCocoa, gst_vulkan_window_cocoa, 74 GST_TYPE_VULKAN_WINDOW, G_ADD_PRIVATE (GstVulkanWindowCocoa) _init_debug ()); 75 76static VkSurfaceKHR gst_vulkan_window_cocoa_get_surface (GstVulkanWindow * window, 77 GError ** error); 78static gboolean gst_vulkan_window_cocoa_get_presentation_support (GstVulkanWindow 79 * window, GstVulkanDevice * device, guint32 queue_family_idx); 80static gboolean gst_vulkan_window_cocoa_open (GstVulkanWindow * window, 81 GError ** error); 82static void gst_vulkan_window_cocoa_close (GstVulkanWindow * window); 83 84static void 85gst_vulkan_window_cocoa_finalize (GObject * object) 86{ 87 G_OBJECT_CLASS (parent_class)->finalize (object); 88} 89 90static void 91gst_vulkan_window_cocoa_class_init (GstVulkanWindowCocoaClass * klass) 92{ 93 GObjectClass *obj_class = G_OBJECT_CLASS (klass); 94 GstVulkanWindowClass *window_class = (GstVulkanWindowClass *) klass; 95 96 obj_class->finalize = gst_vulkan_window_cocoa_finalize; 97 98 window_class->open = GST_DEBUG_FUNCPTR (gst_vulkan_window_cocoa_open); 99 window_class->close = GST_DEBUG_FUNCPTR (gst_vulkan_window_cocoa_close); 100 window_class->get_surface = gst_vulkan_window_cocoa_get_surface; 101 window_class->get_presentation_support = 102 gst_vulkan_window_cocoa_get_presentation_support; 103} 104 105static void 106gst_vulkan_window_cocoa_init (GstVulkanWindowCocoa * window) 107{ 108 GstVulkanWindowCocoaPrivate *priv = GET_PRIV (window); 109 110 priv->preferred_width = 320; 111 priv->preferred_height = 240; 112} 113 114/* Must be called in the gl thread */ 115GstVulkanWindowCocoa * 116gst_vulkan_window_cocoa_new (GstVulkanDisplay * display) 117{ 118 GstVulkanWindowCocoa *window; 119 120 _init_debug (); 121 122 if ((gst_vulkan_display_get_handle_type (display) & 123 GST_VULKAN_DISPLAY_TYPE_COCOA) 124 == GST_VULKAN_DISPLAY_TYPE_NONE) { 125 GST_INFO ("Wrong display type %u for this window type %u", display->type, 126 GST_VULKAN_DISPLAY_TYPE_COCOA); 127 return NULL; 128 } 129 130 window = g_object_new (GST_TYPE_VULKAN_WINDOW_COCOA, NULL); 131 gst_object_ref_sink (window); 132 133 return window; 134} 135 136static void 137_show_window (gpointer data) 138{ 139 GstVulkanWindowCocoa *window_cocoa = GST_VULKAN_WINDOW_COCOA (data); 140 GstVulkanWindowCocoaPrivate *priv = GET_PRIV (window_cocoa); 141 GstVulkanNSWindow *internal_win_id = (__bridge GstVulkanNSWindow *)priv->internal_win_id; 142 143 GST_DEBUG_OBJECT (window_cocoa, "showing"); 144 [internal_win_id makeMainWindow]; 145 [internal_win_id orderFrontRegardless]; 146 [internal_win_id setViewsNeedDisplay:YES]; 147 148 priv->visible = TRUE; 149} 150 151static void 152gst_vulkan_window_cocoa_show (GstVulkanWindow * window) 153{ 154 GstVulkanWindowCocoa *window_cocoa = GST_VULKAN_WINDOW_COCOA (window); 155 GstVulkanWindowCocoaPrivate *priv = GET_PRIV (window_cocoa); 156 157 if (!priv->visible) 158 _invoke_on_main ((GstVulkanWindowFunc) _show_window, gst_object_ref (window), 159 (GDestroyNotify) gst_object_unref); 160} 161 162static void 163gst_vulkan_window_cocoa_hide (GstVulkanWindow * window) 164{ 165// GstVulkanWindowCocoa *window_cocoa = GST_VULKAN_WINDOW_COCOA (window); 166 167 /* FIXME: implement */ 168} 169 170static void 171_create_window (GstVulkanWindowCocoa * window_cocoa) 172{ 173 GstVulkanWindowCocoaPrivate *priv = GET_PRIV (window_cocoa); 174 NSRect mainRect = [[NSScreen mainScreen] visibleFrame]; 175 gint h = priv->preferred_height; 176 gint y = mainRect.size.height > h ? (mainRect.size.height - h) * 0.5 : 0; 177 NSRect rect = NSMakeRect (0, y, priv->preferred_width, priv->preferred_height); 178 GstVulkanNSWindow *internal_win_id; 179 GstVulkanNSView *view; 180 181 view = [[GstVulkanNSView alloc] initWithFrame:rect]; 182 view.wantsLayer = YES; 183 184 internal_win_id = [[GstVulkanNSWindow alloc] initWithContentRect:rect styleMask: 185 (NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | 186 NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable) 187 backing: NSBackingStoreBuffered defer: NO screen: nil gstWin: window_cocoa]; 188 189 [internal_win_id setContentView:view]; 190 191 priv->internal_win_id = (__bridge_retained gpointer)internal_win_id; 192 priv->internal_view = (__bridge_retained gpointer)view; 193 194 gst_vulkan_window_cocoa_show (GST_VULKAN_WINDOW (window_cocoa)); 195} 196 197gboolean 198gst_vulkan_window_cocoa_create_window (GstVulkanWindowCocoa * window_cocoa) 199{ 200 _invoke_on_main ((GstVulkanWindowFunc) _create_window, 201 gst_object_ref (window_cocoa), gst_object_unref); 202 203 g_usleep(1000000); 204 205 return TRUE; 206} 207 208static VkSurfaceKHR 209gst_vulkan_window_cocoa_get_surface (GstVulkanWindow * window, GError ** error) 210{ 211 GstVulkanWindowCocoa *window_cocoa = GST_VULKAN_WINDOW_COCOA (window); 212 GstVulkanWindowCocoaPrivate *priv = GET_PRIV (window_cocoa); 213 VkMacOSSurfaceCreateInfoMVK info = { 0, }; 214 VkSurfaceKHR ret; 215 VkResult err; 216 217 info.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK; 218 info.pNext = NULL; 219 info.flags = 0; 220 info.pView = priv->internal_view; 221 222 if (!window_cocoa->CreateMacOSSurface) 223 window_cocoa->CreateMacOSSurface = 224 gst_vulkan_instance_get_proc_address (window->display->instance, 225 "vkCreateMacOSSurfaceMVK"); 226 if (!window_cocoa->CreateMacOSSurface) { 227 g_set_error_literal (error, GST_VULKAN_ERROR, VK_ERROR_FEATURE_NOT_PRESENT, 228 "Could not retrieve \"vkCreateMacOSSurfaceMVK\" function pointer"); 229 return VK_NULL_HANDLE; 230 } 231 232 err = 233 window_cocoa->CreateMacOSSurface (window->display->instance->instance, &info, 234 NULL, &ret); 235 if (gst_vulkan_error_to_g_error (err, error, "vkCreateMacOSSurfaceMVK") < 0) 236 return VK_NULL_HANDLE; 237 238 return ret; 239} 240 241static gboolean 242gst_vulkan_window_cocoa_get_presentation_support (GstVulkanWindow * window, 243 GstVulkanDevice * device, guint32 queue_family_idx) 244{ 245 return TRUE; 246} 247 248static gboolean 249gst_vulkan_window_cocoa_open (GstVulkanWindow * window, GError ** error) 250{ 251 GstVulkanWindowCocoa *window_cocoa = GST_VULKAN_WINDOW_COCOA (window); 252 253 if (!GST_VULKAN_WINDOW_CLASS (parent_class)->open (window, error)) 254 return FALSE; 255 256 return gst_vulkan_window_cocoa_create_window (window_cocoa); 257} 258 259static void 260_close_window (gpointer * data) 261{ 262 GstVulkanWindowCocoa *window_cocoa = GST_VULKAN_WINDOW_COCOA (data); 263 GstVulkanWindow *window = GST_VULKAN_WINDOW (window_cocoa); 264 GstVulkanWindowCocoaPrivate *priv = GET_PRIV (window_cocoa); 265 GstVulkanNSWindow *internal_win_id = 266 (__bridge GstVulkanNSWindow *) priv->internal_win_id; 267 268 gst_vulkan_window_cocoa_hide (window); 269 270 [[internal_win_id contentView] removeFromSuperview]; 271 CFBridgingRelease (priv->internal_win_id); 272 priv->internal_win_id = NULL; 273 CFBridgingRelease (priv->internal_view); 274 priv->internal_view = NULL; 275} 276 277static void 278gst_vulkan_window_cocoa_close (GstVulkanWindow * window) 279{ 280 _invoke_on_main ((GstVulkanWindowFunc) _close_window, gst_object_ref (window), 281 (GDestroyNotify) gst_object_unref); 282 283 GST_VULKAN_WINDOW_CLASS (parent_class)->close (window); 284} 285 286@implementation GstVulkanNSWindow 287 288- (id) initWithContentRect: (NSRect) contentRect 289 styleMask: (unsigned int) styleMask 290 backing: (NSBackingStoreType) bufferingType 291 defer: (BOOL) flag screen: (NSScreen *) aScreen 292 gstWin: (GstVulkanWindowCocoa *) cocoa { 293 294 m_isClosed = NO; 295 window_cocoa = cocoa; 296 297 self = [super initWithContentRect: contentRect 298 styleMask: styleMask backing: bufferingType 299 defer: flag screen:aScreen]; 300 301 GST_DEBUG ("initializing GstVulkanNSWindow"); 302 303 [self setReleasedWhenClosed:NO]; 304 [self setTitle:@"Vulkan renderer"]; 305 [self setBackgroundColor:[NSColor blackColor]]; 306 [self orderOut:self]; 307 308 return self; 309} 310 311- (void) setClosed { 312 m_isClosed = YES; 313} 314 315- (BOOL) isClosed { 316 return m_isClosed; 317} 318 319- (BOOL) canBecomeMainWindow { 320 return YES; 321} 322 323- (BOOL) canBecomeKeyWindow { 324 return YES; 325} 326 327- (BOOL) windowShouldClose:(id)sender { 328 329 GstVulkanWindowCocoaPrivate *priv = GET_PRIV (window_cocoa); 330 GstVulkanNSWindow *internal_win_id = (__bridge GstVulkanNSWindow *)priv->internal_win_id; 331 GST_DEBUG ("user clicked the close button"); 332 [internal_win_id setClosed]; 333 return YES; 334} 335 336@end 337 338 339@implementation GstVulkanNSView 340 341-(BOOL) wantsUpdateLayer 342{ 343 return YES; 344} 345 346+(Class) layerClass 347{ 348 return [CAMetalLayer class]; 349} 350 351-(CALayer*) makeBackingLayer 352{ 353 CALayer* layer = [self.class.layerClass layer]; 354 CGSize viewScale = [self convertSizeToBacking: CGSizeMake(1.0, 1.0)]; 355 layer.contentsScale = MIN(viewScale.width, viewScale.height); 356 return layer; 357} 358 359@end 360 361void 362_invoke_on_main (GstVulkanWindowFunc func, gpointer data, GDestroyNotify notify) 363{ 364 if ([NSThread isMainThread]) { 365 func (data); 366 if (notify) 367 notify (data); 368 } else { 369 dispatch_async (dispatch_get_main_queue (), ^{ 370 func (data); 371 if (notify) 372 notify (data); 373 }); 374 } 375} 376 377