gl/cocoa: avoid deadlock when creating context on the main thread.
[platform/upstream/gstreamer.git] / gst-libs / gst / gl / cocoa / gstglwindow_cocoa.m
1 /*
2  * GStreamer
3  * Copyright (C) 2008 Julien Isorce <julien.isorce@gmail.com>
4  * Copyright (C) 2014 Sebastian Dröge <sebastian@centricular.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it un der the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25
26 #include <Cocoa/Cocoa.h>
27 #include <QuartzCore/QuartzCore.h>
28
29 #include "gstgl_cocoa_private.h"
30
31 /* =============================================================*/
32 /*                                                              */
33 /*               GstGLNSWindow declaration                      */
34 /*                                                              */
35 /* =============================================================*/
36
37 @interface GstGLNSWindow: NSWindow {
38   BOOL m_isClosed;
39   GstGLWindowCocoa *window_cocoa;
40 }
41 - (id)initWithContentRect:(NSRect)contentRect
42     styleMask: (unsigned int) styleMask
43     backing: (NSBackingStoreType) bufferingType
44     defer: (BOOL) flag screen: (NSScreen *) aScreen
45     gstWin: (GstGLWindowCocoa *) window;
46 - (void) setClosed;
47 - (BOOL) isClosed;
48 - (BOOL) canBecomeMainWindow;
49 - (BOOL) canBecomeKeyWindow;
50 @end
51
52 /* =============================================================*/
53 /*                                                              */
54 /*                      GstGLWindow                             */
55 /*                                                              */
56 /* =============================================================*/
57
58 #define GST_GL_WINDOW_COCOA_GET_PRIVATE(o)  \
59   (G_TYPE_INSTANCE_GET_PRIVATE((o), GST_GL_TYPE_WINDOW_COCOA, GstGLWindowCocoaPrivate))
60
61 #define GST_CAT_DEFAULT gst_gl_window_cocoa_debug
62 GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
63
64 #define DEBUG_INIT \
65   GST_DEBUG_CATEGORY_GET (GST_CAT_DEFAULT, "glwindow");
66 #define gst_gl_window_cocoa_parent_class parent_class
67 G_DEFINE_TYPE_WITH_CODE (GstGLWindowCocoa, gst_gl_window_cocoa, GST_GL_TYPE_WINDOW, DEBUG_INIT);
68 static void gst_gl_window_cocoa_finalize (GObject * object);
69
70 static gboolean gst_gl_window_cocoa_open (GstGLWindow *window, GError **err);
71 static void gst_gl_window_cocoa_close (GstGLWindow *window);
72 static guintptr gst_gl_window_cocoa_get_window_handle (GstGLWindow * window);
73 static void gst_gl_window_cocoa_set_window_handle (GstGLWindow * window,
74     guintptr handle);
75 static void gst_gl_window_cocoa_draw (GstGLWindow * window);
76 static void gst_gl_window_cocoa_run (GstGLWindow * window);
77 static void gst_gl_window_cocoa_quit (GstGLWindow * window);
78 static void gst_gl_window_cocoa_send_message_async (GstGLWindow * window,
79     GstGLWindowCB callback, gpointer data, GDestroyNotify destroy);
80 static void gst_gl_window_cocoa_set_preferred_size (GstGLWindow * window,
81     gint width, gint height);
82 static void gst_gl_window_cocoa_show (GstGLWindow * window);
83
84 struct _GstGLWindowCocoaPrivate
85 {
86   GstGLNSWindow *internal_win_id;
87   NSView *external_view;
88   gboolean visible;
89   GMainContext *main_context;
90   GMainLoop *loop;
91   gint preferred_width;
92   gint preferred_height;
93
94   GLint viewport_dim[4];
95
96   /* atomic set when the internal NSView has been created */
97   int view_ready;
98 };
99
100 static void
101 gst_gl_window_cocoa_class_init (GstGLWindowCocoaClass * klass)
102 {
103   GstGLWindowClass *window_class = (GstGLWindowClass *) klass;
104   GObjectClass *gobject_class = (GObjectClass *) klass;
105
106   g_type_class_add_private (klass, sizeof (GstGLWindowCocoaPrivate));
107
108   window_class->open = GST_DEBUG_FUNCPTR (gst_gl_window_cocoa_open);
109   window_class->close = GST_DEBUG_FUNCPTR (gst_gl_window_cocoa_close);
110   window_class->get_window_handle =
111       GST_DEBUG_FUNCPTR (gst_gl_window_cocoa_get_window_handle);
112   window_class->set_window_handle =
113       GST_DEBUG_FUNCPTR (gst_gl_window_cocoa_set_window_handle);
114   window_class->draw_unlocked = GST_DEBUG_FUNCPTR (gst_gl_window_cocoa_draw);
115   window_class->draw = GST_DEBUG_FUNCPTR (gst_gl_window_cocoa_draw);
116   window_class->run = GST_DEBUG_FUNCPTR (gst_gl_window_cocoa_run);
117   window_class->quit = GST_DEBUG_FUNCPTR (gst_gl_window_cocoa_quit);
118   window_class->send_message_async =
119       GST_DEBUG_FUNCPTR (gst_gl_window_cocoa_send_message_async);
120   window_class->set_preferred_size =
121       GST_DEBUG_FUNCPTR (gst_gl_window_cocoa_set_preferred_size);
122   window_class->show = GST_DEBUG_FUNCPTR (gst_gl_window_cocoa_show);
123
124   gobject_class->finalize = gst_gl_window_cocoa_finalize;
125 }
126
127 static void
128 gst_gl_window_cocoa_init (GstGLWindowCocoa * window)
129 {
130   window->priv = GST_GL_WINDOW_COCOA_GET_PRIVATE (window);
131
132   window->priv->preferred_width = 320;
133   window->priv->preferred_height = 240;
134
135   window->priv->main_context = g_main_context_new ();
136   window->priv->loop =g_main_loop_new (window->priv->main_context, FALSE);
137 }
138
139 static void
140 gst_gl_window_cocoa_finalize (GObject * object)
141 {
142   GstGLWindowCocoa *window_cocoa = GST_GL_WINDOW_COCOA (object);
143
144   g_main_loop_unref (window_cocoa->priv->loop);
145   g_main_context_unref (window_cocoa->priv->main_context);
146
147   G_OBJECT_CLASS (parent_class)->finalize (object);
148 }
149
150 /* Must be called in the gl thread */
151 GstGLWindowCocoa *
152 gst_gl_window_cocoa_new (void)
153 {
154   GstGLWindowCocoa *window = g_object_new (GST_GL_TYPE_WINDOW_COCOA, NULL);
155
156   return window;
157 }
158
159 /* Must be called from the main thread */
160 gboolean
161 gst_gl_window_cocoa_create_window (GstGLWindowCocoa *window_cocoa)
162 {
163   GstGLWindowCocoaPrivate *priv = window_cocoa->priv;
164   GstGLWindow *window = GST_GL_WINDOW (window_cocoa);
165   NSRect mainRect = [[NSScreen mainScreen] visibleFrame];
166   gint h = priv->preferred_height;
167   gint y = mainRect.size.height > h ? (mainRect.size.height - h) * 0.5 : 0;
168   NSRect rect = NSMakeRect (0, y, priv->preferred_width, priv->preferred_height);
169   NSRect windowRect = NSMakeRect (0, y, priv->preferred_width, priv->preferred_height);
170   GstGLContext *context = gst_gl_window_get_context (window);
171   GstGLContextCocoa *context_cocoa = GST_GL_CONTEXT_COCOA (context);
172   GstGLCAOpenGLLayer *layer = [[GstGLCAOpenGLLayer alloc] initWithGstGLContext:context_cocoa];
173   GstGLNSView *glView = [[GstGLNSView alloc] initWithFrameLayer:window_cocoa rect:windowRect layer:layer];
174
175   gst_object_unref (context);
176
177   priv->internal_win_id = [[GstGLNSWindow alloc] initWithContentRect:rect styleMask: 
178       (NSTitledWindowMask | NSClosableWindowMask |
179       NSResizableWindowMask | NSMiniaturizableWindowMask)
180       backing: NSBackingStoreBuffered defer: NO screen: nil gstWin: window_cocoa];
181
182       GST_DEBUG ("NSWindow id: %"G_GUINTPTR_FORMAT, (guintptr) priv->internal_win_id);
183
184   [priv->internal_win_id setContentView:glView];
185
186   g_atomic_int_set (&window_cocoa->priv->view_ready, 1);
187
188   return TRUE;
189 }
190
191 static gboolean
192 gst_gl_window_cocoa_open (GstGLWindow *window, GError **err)
193 {
194   GstGLWindowCocoa *window_cocoa;
195
196   window_cocoa = GST_GL_WINDOW_COCOA (window);
197
198   return TRUE;
199 }
200
201 static void
202 gst_gl_window_cocoa_close (GstGLWindow *window)
203 {
204   GstGLWindowCocoa *window_cocoa;
205
206   window_cocoa = GST_GL_WINDOW_COCOA (window);
207
208   g_main_loop_unref (window_cocoa->priv->loop);
209   g_main_context_unref (window_cocoa->priv->main_context);
210
211   [window_cocoa->priv->internal_win_id release];
212   window_cocoa->priv->internal_win_id = nil;
213 }
214
215 static guintptr
216 gst_gl_window_cocoa_get_window_handle (GstGLWindow *window)
217 {
218   return (guintptr) GST_GL_WINDOW_COCOA (window)->priv->internal_win_id;
219 }
220
221 static void
222 gst_gl_window_cocoa_set_window_handle (GstGLWindow * window, guintptr handle)
223 {
224   GstGLWindowCocoa *window_cocoa;
225   GstGLWindowCocoaPrivate *priv;
226
227   window_cocoa = GST_GL_WINDOW_COCOA (window);
228   priv = window_cocoa->priv;
229
230   if (priv->internal_win_id) {
231     if (handle) {
232       priv->external_view = (NSView *) handle;
233       priv->visible = TRUE;
234     } else {
235       /* bring back our internal window */
236       priv->external_view = 0;
237       priv->visible = FALSE;
238     }
239
240
241     dispatch_async (dispatch_get_main_queue (), ^{
242       NSView *view = [window_cocoa->priv->internal_win_id contentView];
243       [window_cocoa->priv->internal_win_id orderOut:window_cocoa->priv->internal_win_id];
244
245       [window_cocoa->priv->external_view addSubview: view];
246
247       [view setFrame: [window_cocoa->priv->external_view bounds]];
248       [view setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable];
249     });
250   } else {
251     /* no internal window yet so delay it to the next drawing */
252     priv->external_view = (NSView*) handle;
253     priv->visible = FALSE;
254   }
255 }
256
257 static void
258 _show_window (gpointer data)
259 {
260   GstGLWindowCocoa *window_cocoa = GST_GL_WINDOW_COCOA (data);
261   GstGLWindowCocoaPrivate *priv = window_cocoa->priv;
262
263   GST_DEBUG_OBJECT (window_cocoa, "make the window available\n");
264   [priv->internal_win_id makeMainWindow];
265   [priv->internal_win_id orderFrontRegardless];
266   [priv->internal_win_id setViewsNeedDisplay:YES];
267
268   priv->visible = TRUE;
269 }
270
271 static void
272 gst_gl_window_cocoa_show (GstGLWindow * window)
273 {
274   GstGLWindowCocoa *window_cocoa = GST_GL_WINDOW_COCOA (window);
275   GstGLWindowCocoaPrivate *priv = window_cocoa->priv;
276
277   if (!priv->visible) {
278     /* useful when set_window_handle is called before
279      * the internal NSWindow */
280     if (priv->external_view) {
281       gst_gl_window_cocoa_set_window_handle (window, (guintptr) priv->external_view);
282       priv->visible = TRUE;
283       return;
284     }
285
286     if (!priv->external_view && !priv->visible)
287       _invoke_on_main ((GstGLWindowCB) _show_window, window);
288   }
289 }
290
291 static void
292 gst_gl_window_cocoa_draw (GstGLWindow * window)
293 {
294   GstGLWindowCocoa *window_cocoa = GST_GL_WINDOW_COCOA (window);
295   GstGLNSView *view;
296
297   /* As the view is created asynchronously in the main thread we cannot know
298    * exactly when it will be ready to draw to */
299   if (!g_atomic_int_get (&window_cocoa->priv->view_ready))
300     return;
301
302   view = (GstGLNSView *)[window_cocoa->priv->internal_win_id contentView];
303
304   /* this redraws the GstGLCAOpenGLLayer which calls
305    * gst_gl_window_cocoa_draw_thread(). Use an explicit CATransaction since we
306    * don't know how often the main runloop is running.
307    */
308   [CATransaction begin];
309   [view setNeedsDisplay:YES];
310   [CATransaction commit];
311 }
312
313 static void
314 gst_gl_window_cocoa_set_preferred_size (GstGLWindow * window, gint width,
315     gint height)
316 {
317   GstGLWindowCocoa *window_cocoa = GST_GL_WINDOW_COCOA (window);
318
319   window_cocoa->priv->preferred_width = width;
320   window_cocoa->priv->preferred_height = height;
321 }
322
323 static void
324 gst_gl_window_cocoa_run (GstGLWindow * window)
325 {
326   GstGLWindowCocoa *window_cocoa;
327
328   window_cocoa = GST_GL_WINDOW_COCOA (window);
329
330   GST_LOG ("starting main loop");
331   g_main_loop_run (window_cocoa->priv->loop);
332   GST_LOG ("exiting main loop");
333 }
334
335 /* Thread safe */
336 static void
337 gst_gl_window_cocoa_quit (GstGLWindow * window)
338 {
339   GstGLWindowCocoa *window_cocoa;
340
341   window_cocoa = GST_GL_WINDOW_COCOA (window);
342
343   g_main_loop_quit (window_cocoa->priv->loop);
344 }
345
346 /* Thread safe */
347 typedef struct _GstGLMessage
348 {
349   GstGLWindowCB callback;
350   gpointer data;
351   GDestroyNotify destroy;
352 } GstGLMessage;
353
354 static gboolean
355 _run_message (GstGLMessage * message)
356 {
357   if (message->callback)
358     message->callback (message->data);
359
360   if (message->destroy)
361     message->destroy (message->data);
362
363   g_slice_free (GstGLMessage, message);
364
365   return FALSE;
366 }
367
368 static void
369 gst_gl_window_cocoa_send_message_async (GstGLWindow * window,
370     GstGLWindowCB callback, gpointer data, GDestroyNotify destroy)
371 {
372   GstGLWindowCocoa *window_cocoa;
373   GstGLMessage *message;
374
375   window_cocoa = GST_GL_WINDOW_COCOA (window);
376   message = g_slice_new (GstGLMessage);
377
378   message->callback = callback;
379   message->data = data;
380   message->destroy = destroy;
381
382   g_main_context_invoke (window_cocoa->priv->main_context,
383       (GSourceFunc) _run_message, message);
384 }
385
386 static void
387 gst_gl_cocoa_draw_cb (GstGLWindowCocoa *window_cocoa)
388 {
389   GstGLWindowCocoaPrivate *priv = window_cocoa->priv;
390
391   if (g_main_loop_is_running (priv->loop)) {
392     if (![priv->internal_win_id isClosed]) {
393      GstGLWindow *window = GST_GL_WINDOW (window_cocoa);
394
395       /* draw opengl scene in the back buffer */
396       if (window->draw)
397         window->draw (window->draw_data);
398     }
399   }
400 }
401
402 static void
403 gst_gl_cocoa_resize_cb (GstGLNSView * view, guint width, guint height)
404 {
405   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
406   GstGLWindowCocoa *window_cocoa = view->window_cocoa;
407   GstGLWindow *window = GST_GL_WINDOW (window_cocoa);
408   GstGLContext *context = gst_gl_window_get_context (window);
409
410   if (g_main_loop_is_running (window_cocoa->priv->loop) && ![window_cocoa->priv->internal_win_id isClosed]) {
411     const GstGLFuncs *gl;
412     NSRect bounds = [view bounds];
413     NSRect visibleRect = [view visibleRect];
414     gint viewport_dim[4];
415
416     gl = context->gl_vtable;
417
418 #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070
419     bounds = [view convertRectToBacking:bounds];
420     visibleRect = [view convertRectToBacking:visibleRect];
421 #endif
422
423     GST_DEBUG_OBJECT (window, "Window resized: bounds %lf %lf %lf %lf "
424                       "visibleRect %lf %lf %lf %lf",
425                       bounds.origin.x, bounds.origin.y,
426                       bounds.size.width, bounds.size.height,
427                       visibleRect.origin.x, visibleRect.origin.y,
428                       visibleRect.size.width, visibleRect.size.height);
429
430     if (window->resize) {
431       window->resize (window->resize_data, width, height);
432       gl->GetIntegerv (GL_VIEWPORT, viewport_dim);
433     }
434
435     gl->Viewport (viewport_dim[0] - visibleRect.origin.x,
436                   viewport_dim[1] - visibleRect.origin.y,
437                   viewport_dim[2], viewport_dim[3]);
438   }
439
440   gst_object_unref (context);
441   [pool release];
442 }
443
444 /* =============================================================*/
445 /*                                                              */
446 /*                    GstGLNSWindow implementation              */
447 /*                                                              */
448 /* =============================================================*/
449
450 /* Must be called from the main thread */
451 @implementation GstGLNSWindow
452
453 - (id) initWithContentRect: (NSRect) contentRect
454         styleMask: (unsigned int) styleMask
455     backing: (NSBackingStoreType) bufferingType
456     defer: (BOOL) flag screen: (NSScreen *) aScreen
457     gstWin: (GstGLWindowCocoa *) cocoa {
458
459   m_isClosed = NO;
460   window_cocoa = cocoa;
461
462   self = [super initWithContentRect: contentRect
463         styleMask: styleMask backing: bufferingType
464         defer: flag screen:aScreen];
465
466   [self setReleasedWhenClosed:NO];
467
468   GST_DEBUG ("initializing GstGLNSWindow\n");
469
470   [self setTitle:@"OpenGL renderer"];
471
472   [self setBackgroundColor:[NSColor blackColor]];
473
474   [self orderOut:window_cocoa->priv->internal_win_id];
475
476   if (window_cocoa->priv->external_view) {
477     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
478     NSView *view = [window_cocoa->priv->internal_win_id contentView];
479
480     [window_cocoa->priv->external_view addSubview: view];
481     [view setFrame: [window_cocoa->priv->external_view bounds]];
482     [view setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable];
483
484     [pool release];
485   }
486
487   return self;
488 }
489
490 - (void) setClosed {
491   m_isClosed = YES;
492 }
493
494 - (BOOL) isClosed {
495   return m_isClosed;
496 }
497
498 - (BOOL) canBecomeMainWindow {
499   return YES;
500 }
501
502 - (BOOL) canBecomeKeyWindow {
503   return YES;
504 }
505
506 static void
507 close_window_cb (gpointer data)
508 {
509   GstGLWindowCocoa *window_cocoa = data;
510   GstGLWindow *window;
511
512   window = GST_GL_WINDOW (window_cocoa);
513
514   if (window->close) {
515     window->close (window->close_data);
516   }
517 }
518
519 /* Called in the main thread which is never the gl thread */
520 - (BOOL) windowShouldClose:(id)sender {
521
522   GST_DEBUG ("user clicked the close button\n");
523   [window_cocoa->priv->internal_win_id setClosed];
524   gst_gl_window_send_message_async (GST_GL_WINDOW (window_cocoa),
525       (GstGLWindowCB) close_window_cb, gst_object_ref (window_cocoa),
526       (GDestroyNotify) gst_object_unref);
527   return YES;
528 }
529
530 @end
531
532 /* =============================================================*/
533 /*                                                              */
534 /*                GstGLNSView implementation              */
535 /*                                                              */
536 /* =============================================================*/
537
538 @implementation GstGLNSView
539
540 /* Must be called from the application main thread */
541 - (id)initWithFrameLayer:(GstGLWindowCocoa *)window rect:(NSRect)contentRect layer:(CALayer *)layerContent {
542
543   self = [super initWithFrame: contentRect];
544
545   window_cocoa = window;
546
547   /* The order of the next two calls matters.  This creates a layer-hosted
548    * NSView.  Calling setWantsLayer before setLayer will create a
549    * layer-backed NSView.  See the apple developer documentation on the
550    * difference.
551    */
552   [self setLayer:layerContent];
553   [self setWantsLayer:YES];
554   self->layer = (GstGLCAOpenGLLayer *)layerContent;
555   [self->layer setDrawCallback:(GstGLWindowCB)gst_gl_cocoa_draw_cb
556       data:window notify:NULL];
557   [self->layer setResizeCallback:(GstGLWindowResizeCB)gst_gl_cocoa_resize_cb
558       data:self notify:NULL];
559
560   [self setLayerContentsRedrawPolicy:NSViewLayerContentsRedrawOnSetNeedsDisplay];
561
562   [self setWantsBestResolutionOpenGLSurface:YES];
563
564   return self;
565 }
566
567 - (void) dealloc {
568   [self->layer release];
569
570   [super dealloc];
571 }
572
573 - (void)renewGState {
574   /* Don't update the screen until we redraw, this
575    * prevents flickering during scrolling, clipping,
576    * resizing, etc
577    */
578   [[self window] disableScreenUpdatesUntilFlush];
579
580   [super renewGState];
581 }
582
583 - (BOOL) isOpaque {
584     return YES;
585 }
586
587 - (BOOL) isFlipped {
588     return NO;
589 }
590
591 @end
592
593 void
594 _invoke_on_main (GstGLWindowCB func, gpointer data)
595 {
596   if ([NSThread isMainThread]) {
597     func (data);
598   } else {
599     dispatch_async (dispatch_get_main_queue (), ^{
600       func (data);
601     });
602   }
603 }