f00726267171747763f426ab86685711421d9ee3
[platform/upstream/gstreamer.git] / gst-libs / gst / vulkan / cocoa / gstvkwindow_cocoa.m
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
40 GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
41
42 static void
43 _init_debug (void)
44 {
45   static volatile 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
54 gboolean gst_vulkan_window_cocoa_handle_event (GstVulkanWindowCocoa * window_cocoa);
55
56 enum
57 {
58   PROP_0,
59 };
60
61 struct _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
73 G_DEFINE_TYPE_WITH_CODE (GstVulkanWindowCocoa, gst_vulkan_window_cocoa,
74     GST_TYPE_VULKAN_WINDOW, G_ADD_PRIVATE (GstVulkanWindowCocoa) _init_debug ());
75
76 static VkSurfaceKHR gst_vulkan_window_cocoa_get_surface (GstVulkanWindow * window,
77     GError ** error);
78 static gboolean gst_vulkan_window_cocoa_get_presentation_support (GstVulkanWindow
79     * window, GstVulkanDevice * device, guint32 queue_family_idx);
80 static gboolean gst_vulkan_window_cocoa_open (GstVulkanWindow * window,
81     GError ** error);
82 static void gst_vulkan_window_cocoa_close (GstVulkanWindow * window);
83
84 static void
85 gst_vulkan_window_cocoa_finalize (GObject * object)
86 {
87   G_OBJECT_CLASS (parent_class)->finalize (object);
88 }
89
90 static void
91 gst_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
105 static void
106 gst_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 */
115 GstVulkanWindowCocoa *
116 gst_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
136 static 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
151 static void
152 gst_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
162 static void
163 gst_vulkan_window_cocoa_hide (GstVulkanWindow * window)
164 {
165 //  GstVulkanWindowCocoa *window_cocoa = GST_VULKAN_WINDOW_COCOA (window);
166
167   /* FIXME: implement */
168 }
169
170 static 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
197 gboolean
198 gst_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
208 static VkSurfaceKHR
209 gst_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 NULL;
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 NULL;
237
238   return ret;
239 }
240
241 static gboolean
242 gst_vulkan_window_cocoa_get_presentation_support (GstVulkanWindow * window,
243     GstVulkanDevice * device, guint32 queue_family_idx)
244 {
245   return TRUE;
246 }
247
248 static gboolean
249 gst_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
259 static 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
277 static void
278 gst_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
361 void
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