3 * Copyright (C) 2008 Julien Isorce <julien.isorce@gmail.com>
4 * Copyright (C) 2014 Sebastian Dröge <sebastian@centricular.com>
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.
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.
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.
26 #include <Cocoa/Cocoa.h>
27 #include <QuartzCore/QuartzCore.h>
29 #include "gstgl_cocoa_private.h"
31 /* =============================================================*/
33 /* GstGLNSWindow declaration */
35 /* =============================================================*/
37 @interface GstGLNSWindow: NSWindow {
39 GstGLWindowCocoa *window_cocoa;
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;
48 - (BOOL) canBecomeMainWindow;
49 - (BOOL) canBecomeKeyWindow;
52 /* =============================================================*/
56 /* =============================================================*/
58 #define GST_GL_WINDOW_COCOA_GET_PRIVATE(o) \
59 (G_TYPE_INSTANCE_GET_PRIVATE((o), GST_GL_TYPE_WINDOW_COCOA, GstGLWindowCocoaPrivate))
61 #define GST_CAT_DEFAULT gst_gl_window_cocoa_debug
62 GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
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);
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,
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);
84 struct _GstGLWindowCocoaPrivate
86 GstGLNSWindow *internal_win_id;
87 NSView *external_view;
89 GMainContext *main_context;
92 gint preferred_height;
94 GLint viewport_dim[4];
96 /* atomic set when the internal NSView has been created */
101 gst_gl_window_cocoa_class_init (GstGLWindowCocoaClass * klass)
103 GstGLWindowClass *window_class = (GstGLWindowClass *) klass;
104 GObjectClass *gobject_class = (GObjectClass *) klass;
106 g_type_class_add_private (klass, sizeof (GstGLWindowCocoaPrivate));
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);
124 gobject_class->finalize = gst_gl_window_cocoa_finalize;
128 gst_gl_window_cocoa_init (GstGLWindowCocoa * window)
130 window->priv = GST_GL_WINDOW_COCOA_GET_PRIVATE (window);
132 window->priv->preferred_width = 320;
133 window->priv->preferred_height = 240;
135 window->priv->main_context = g_main_context_new ();
136 window->priv->loop =g_main_loop_new (window->priv->main_context, FALSE);
140 gst_gl_window_cocoa_finalize (GObject * object)
142 GstGLWindowCocoa *window_cocoa = GST_GL_WINDOW_COCOA (object);
144 g_main_loop_unref (window_cocoa->priv->loop);
145 g_main_context_unref (window_cocoa->priv->main_context);
147 G_OBJECT_CLASS (parent_class)->finalize (object);
150 /* Must be called in the gl thread */
152 gst_gl_window_cocoa_new (void)
154 GstGLWindowCocoa *window = g_object_new (GST_GL_TYPE_WINDOW_COCOA, NULL);
159 /* Must be called from the main thread */
161 gst_gl_window_cocoa_create_window (GstGLWindowCocoa *window_cocoa)
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];
175 gst_object_unref (context);
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];
182 GST_DEBUG ("NSWindow id: %"G_GUINTPTR_FORMAT, (guintptr) priv->internal_win_id);
184 [priv->internal_win_id setContentView:glView];
186 g_atomic_int_set (&window_cocoa->priv->view_ready, 1);
192 gst_gl_window_cocoa_open (GstGLWindow *window, GError **err)
194 GstGLWindowCocoa *window_cocoa;
196 window_cocoa = GST_GL_WINDOW_COCOA (window);
202 gst_gl_window_cocoa_close (GstGLWindow *window)
204 GstGLWindowCocoa *window_cocoa;
206 window_cocoa = GST_GL_WINDOW_COCOA (window);
208 g_main_loop_unref (window_cocoa->priv->loop);
209 g_main_context_unref (window_cocoa->priv->main_context);
211 [window_cocoa->priv->internal_win_id release];
212 window_cocoa->priv->internal_win_id = nil;
216 gst_gl_window_cocoa_get_window_handle (GstGLWindow *window)
218 return (guintptr) GST_GL_WINDOW_COCOA (window)->priv->internal_win_id;
222 gst_gl_window_cocoa_set_window_handle (GstGLWindow * window, guintptr handle)
224 GstGLWindowCocoa *window_cocoa;
225 GstGLWindowCocoaPrivate *priv;
227 window_cocoa = GST_GL_WINDOW_COCOA (window);
228 priv = window_cocoa->priv;
230 if (priv->internal_win_id) {
232 priv->external_view = (NSView *) handle;
233 priv->visible = TRUE;
235 /* bring back our internal window */
236 priv->external_view = 0;
237 priv->visible = FALSE;
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];
245 [window_cocoa->priv->external_view addSubview: view];
247 [view setFrame: [window_cocoa->priv->external_view bounds]];
248 [view setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable];
251 /* no internal window yet so delay it to the next drawing */
252 priv->external_view = (NSView*) handle;
253 priv->visible = FALSE;
258 _show_window (gpointer data)
260 GstGLWindowCocoa *window_cocoa = GST_GL_WINDOW_COCOA (data);
261 GstGLWindowCocoaPrivate *priv = window_cocoa->priv;
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];
268 priv->visible = TRUE;
272 gst_gl_window_cocoa_show (GstGLWindow * window)
274 GstGLWindowCocoa *window_cocoa = GST_GL_WINDOW_COCOA (window);
275 GstGLWindowCocoaPrivate *priv = window_cocoa->priv;
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;
286 if (!priv->external_view && !priv->visible)
287 _invoke_on_main ((GstGLWindowCB) _show_window, window);
292 gst_gl_window_cocoa_draw (GstGLWindow * window)
294 GstGLWindowCocoa *window_cocoa = GST_GL_WINDOW_COCOA (window);
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))
302 view = (GstGLNSView *)[window_cocoa->priv->internal_win_id contentView];
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.
308 [CATransaction begin];
309 [view setNeedsDisplay:YES];
310 [CATransaction commit];
314 gst_gl_window_cocoa_set_preferred_size (GstGLWindow * window, gint width,
317 GstGLWindowCocoa *window_cocoa = GST_GL_WINDOW_COCOA (window);
319 window_cocoa->priv->preferred_width = width;
320 window_cocoa->priv->preferred_height = height;
324 gst_gl_window_cocoa_run (GstGLWindow * window)
326 GstGLWindowCocoa *window_cocoa;
328 window_cocoa = GST_GL_WINDOW_COCOA (window);
330 GST_LOG ("starting main loop");
331 g_main_loop_run (window_cocoa->priv->loop);
332 GST_LOG ("exiting main loop");
337 gst_gl_window_cocoa_quit (GstGLWindow * window)
339 GstGLWindowCocoa *window_cocoa;
341 window_cocoa = GST_GL_WINDOW_COCOA (window);
343 g_main_loop_quit (window_cocoa->priv->loop);
347 typedef struct _GstGLMessage
349 GstGLWindowCB callback;
351 GDestroyNotify destroy;
355 _run_message (GstGLMessage * message)
357 if (message->callback)
358 message->callback (message->data);
360 if (message->destroy)
361 message->destroy (message->data);
363 g_slice_free (GstGLMessage, message);
369 gst_gl_window_cocoa_send_message_async (GstGLWindow * window,
370 GstGLWindowCB callback, gpointer data, GDestroyNotify destroy)
372 GstGLWindowCocoa *window_cocoa;
373 GstGLMessage *message;
375 window_cocoa = GST_GL_WINDOW_COCOA (window);
376 message = g_slice_new (GstGLMessage);
378 message->callback = callback;
379 message->data = data;
380 message->destroy = destroy;
382 g_main_context_invoke (window_cocoa->priv->main_context,
383 (GSourceFunc) _run_message, message);
387 gst_gl_cocoa_draw_cb (GstGLWindowCocoa *window_cocoa)
389 GstGLWindowCocoaPrivate *priv = window_cocoa->priv;
391 if (g_main_loop_is_running (priv->loop)) {
392 if (![priv->internal_win_id isClosed]) {
393 GstGLWindow *window = GST_GL_WINDOW (window_cocoa);
395 /* draw opengl scene in the back buffer */
397 window->draw (window->draw_data);
403 gst_gl_cocoa_resize_cb (GstGLNSView * view, guint width, guint height)
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);
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];
416 gl = context->gl_vtable;
418 #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070
419 bounds = [view convertRectToBacking:bounds];
420 visibleRect = [view convertRectToBacking:visibleRect];
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);
430 if (window->resize) {
431 window->resize (window->resize_data, width, height);
432 gl->GetIntegerv (GL_VIEWPORT, viewport_dim);
435 gl->Viewport (viewport_dim[0] - visibleRect.origin.x,
436 viewport_dim[1] - visibleRect.origin.y,
437 viewport_dim[2], viewport_dim[3]);
440 gst_object_unref (context);
444 /* =============================================================*/
446 /* GstGLNSWindow implementation */
448 /* =============================================================*/
450 /* Must be called from the main thread */
451 @implementation GstGLNSWindow
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 {
460 window_cocoa = cocoa;
462 self = [super initWithContentRect: contentRect
463 styleMask: styleMask backing: bufferingType
464 defer: flag screen:aScreen];
466 [self setReleasedWhenClosed:NO];
468 GST_DEBUG ("initializing GstGLNSWindow\n");
470 [self setTitle:@"OpenGL renderer"];
472 [self setBackgroundColor:[NSColor blackColor]];
474 [self orderOut:window_cocoa->priv->internal_win_id];
476 if (window_cocoa->priv->external_view) {
477 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
478 NSView *view = [window_cocoa->priv->internal_win_id contentView];
480 [window_cocoa->priv->external_view addSubview: view];
481 [view setFrame: [window_cocoa->priv->external_view bounds]];
482 [view setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable];
498 - (BOOL) canBecomeMainWindow {
502 - (BOOL) canBecomeKeyWindow {
507 close_window_cb (gpointer data)
509 GstGLWindowCocoa *window_cocoa = data;
512 window = GST_GL_WINDOW (window_cocoa);
515 window->close (window->close_data);
519 /* Called in the main thread which is never the gl thread */
520 - (BOOL) windowShouldClose:(id)sender {
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);
532 /* =============================================================*/
534 /* GstGLNSView implementation */
536 /* =============================================================*/
538 @implementation GstGLNSView
540 /* Must be called from the application main thread */
541 - (id)initWithFrameLayer:(GstGLWindowCocoa *)window rect:(NSRect)contentRect layer:(CALayer *)layerContent {
543 self = [super initWithFrame: contentRect];
545 window_cocoa = window;
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
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];
560 [self setLayerContentsRedrawPolicy:NSViewLayerContentsRedrawOnSetNeedsDisplay];
562 [self setWantsBestResolutionOpenGLSurface:YES];
568 [self->layer release];
573 - (void)renewGState {
574 /* Don't update the screen until we redraw, this
575 * prevents flickering during scrolling, clipping,
578 [[self window] disableScreenUpdatesUntilFlush];
594 _invoke_on_main (GstGLWindowCB func, gpointer data)
596 if ([NSThread isMainThread]) {
599 dispatch_async (dispatch_get_main_queue (), ^{