1/*
2 * GStreamer
3 * Copyright (C) 2015 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
27#include "gstglcaopengllayer.h"
28#include "gstgl_cocoa_private.h"
29
30#define GST_CAT_DEFAULT gst_gl_ca_opengl_layer_debug
31GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
32
33static void
34_init_debug (void)
35{
36  static volatile gsize _init = 0;
37
38  if (g_once_init_enter (&_init)) {
39    GST_DEBUG_CATEGORY_INIT(gst_gl_ca_opengl_layer_debug, "glcaopengllayer",
40        0, "CAOpenGLLayer");
41
42    g_once_init_leave (&_init, 1);
43  }
44}
45
46@implementation GstGLCAOpenGLLayer
47- (void)dealloc {
48  if (self->draw_notify)
49    self->draw_notify (self->draw_data);
50
51  if (self->draw_context)
52    gst_object_unref (self->draw_context);
53
54  GST_TRACE ("dealloc GstGLCAOpenGLLayer %p context %p", self, self->gst_gl_context);
55}
56
57static void
58_context_ready (gpointer data)
59{
60  GstGLCAOpenGLLayer *ca_layer = (__bridge GstGLCAOpenGLLayer *) data;
61
62  g_atomic_int_set (&ca_layer->can_draw, 1);
63}
64
65- (id)initWithGstGLContext:(GstGLContext *)parent_gl_context {
66  g_return_val_if_fail (GST_IS_GL_CONTEXT_COCOA (parent_gl_context), nil);
67
68  self = [super init];
69
70  _init_debug();
71
72  GST_LOG ("init CAOpenGLLayer");
73
74  self->gst_gl_context = parent_gl_context;
75  self.needsDisplayOnBoundsChange = YES;
76
77  gst_gl_window_send_message_async (parent_gl_context->window,
78      (GstGLWindowCB) _context_ready, (__bridge_retained gpointer)self, (GDestroyNotify)CFRelease);
79
80  return self;
81}
82
83- (CGLPixelFormatObj)copyCGLPixelFormatForDisplayMask:(uint32_t)mask {
84  CGLPixelFormatObj fmt = NULL;
85
86  if (self->gst_gl_context)
87    fmt = gst_gl_context_cocoa_get_pixel_format (GST_GL_CONTEXT_COCOA (self->gst_gl_context));
88
89  if (!fmt) {
90    CGLPixelFormatAttribute attribs[] = {
91      kCGLPFADoubleBuffer,
92      kCGLPFAAccumSize, 32,
93      0
94    };
95    CGLError ret;
96    gint npix = 0;
97
98    GST_DEBUG ("creating new pixel format for CAOpenGLLayer %p", self);
99
100    ret = CGLChoosePixelFormat (attribs, &fmt, &npix);
101    if (ret != kCGLNoError) {
102      GST_ERROR ("CAOpenGLLayer cannot choose a pixel format: %s", CGLErrorString (ret));
103    }
104  }
105
106  return fmt;
107}
108
109- (CGLContextObj)copyCGLContextForPixelFormat:(CGLPixelFormatObj)pixelFormat {
110  GstGLDisplay *display;
111  CGLContextObj external_context = NULL;
112  CGLError ret;
113  GError *error = NULL;
114
115  if (self->gst_gl_context)
116    external_context = (CGLContextObj) gst_gl_context_get_gl_context (self->gst_gl_context);
117
118  GST_INFO ("attempting to create CGLContext for CAOpenGLLayer with "
119      "share context %p", external_context);
120
121  ret = CGLCreateContext (pixelFormat, external_context, &self->gl_context);
122  if (ret != kCGLNoError) {
123    GST_ERROR ("failed to create CGL context in CAOpenGLLayer with share context %p: %s", external_context, CGLErrorString(ret));
124    return NULL;
125  }
126
127  if (self->draw_context)
128    gst_object_unref (self->draw_context);
129
130  if (kCGLNoError != CGLSetCurrentContext (self->gl_context)) {
131    GST_ERROR ("failed set cgl context %p current", self->gl_context);
132    return NULL;
133  }
134
135  display = gst_gl_context_get_display (self->gst_gl_context);
136  self->draw_context = gst_gl_context_new_wrapped (display,
137      (guintptr) self->gl_context, GST_GL_PLATFORM_CGL,
138      gst_gl_context_get_current_gl_api (GST_GL_PLATFORM_CGL, NULL, NULL));
139  gst_object_unref (display);
140
141  if (!self->draw_context) {
142    GST_ERROR ("failed to create wrapped context");
143    return NULL;
144  }
145
146  gst_gl_context_activate (self->draw_context, TRUE);
147  gst_gl_context_set_shared_with (self->draw_context, self->gst_gl_context);
148  if (!gst_gl_context_fill_info (self->draw_context, &error)) {
149    GST_ERROR ("failed to fill wrapped context information: %s", error->message);
150    return NULL;
151  }
152  gst_gl_context_activate (self->draw_context, FALSE);
153
154  return self->gl_context;
155}
156
157- (void)queueResize {
158  self->queue_resize = TRUE;
159}
160
161- (void)releaseCGLContext:(CGLContextObj)glContext {
162  CGLReleaseContext (glContext);
163}
164
165- (void)setDrawCallback:(GstGLWindowCB)cb data:(gpointer)data
166      notify:(GDestroyNotify)notify {
167  g_return_if_fail (cb);
168
169  if (self->draw_notify)
170    self->draw_notify (self->draw_data);
171
172  self->draw_cb = cb;
173  self->draw_data = data;
174  self->draw_notify = notify;
175}
176
177- (void)setResizeCallback:(GstGLWindowResizeCB)cb data:(gpointer)data
178      notify:(GDestroyNotify)notify {
179  if (self->resize_notify)
180    self->resize_notify (self->resize_data);
181
182  self->resize_cb = cb;
183  self->resize_data = data;
184  self->resize_notify = notify;
185}
186
187- (BOOL)canDrawInCGLContext:(CGLContextObj)glContext
188               pixelFormat:(CGLPixelFormatObj)pixelFormat
189            forLayerTime:(CFTimeInterval)interval
190             displayTime:(const CVTimeStamp *)timeStamp {
191  return g_atomic_int_get (&self->can_draw);
192}
193
194- (void)drawInCGLContext:(CGLContextObj)glContext
195               pixelFormat:(CGLPixelFormatObj)pixelFormat
196            forLayerTime:(CFTimeInterval)interval
197             displayTime:(const CVTimeStamp *)timeStamp {
198  const GstGLFuncs *gl = ((GstGLContext *)self->gst_gl_context)->gl_vtable;
199  GstVideoRectangle src, dst, result;
200  gint ca_viewport[4];
201
202  GST_LOG ("CAOpenGLLayer drawing with cgl context %p", glContext);
203
204  /* attempt to get the correct viewport back due to CA being too smart
205   * and messing around with it so center the expected viewport into
206   * the CA viewport set up on entry to this function */
207  gl->GetIntegerv (GL_VIEWPORT, ca_viewport);
208
209  GST_TRACE ("retrieved viewport from CA %u,%u %ux%u", self->expected_dims[0],
210      self->expected_dims[1], self->expected_dims[2], self->expected_dims[3]);
211  gst_gl_context_activate (self->draw_context, TRUE);
212  if (self->queue_resize || self->last_bounds.size.width != self.bounds.size.width
213      || self->last_bounds.size.height != self.bounds.size.height) {
214    if (self->resize_cb) {
215      self->resize_cb (self->resize_data,
216          self.bounds.size.width*self.contentsScale,
217          self.bounds.size.height*self.contentsScale);
218
219      gl->GetIntegerv (GL_VIEWPORT, self->expected_dims);
220
221      GST_LOG ("resize callback wants viewport %u,%u %ux%u",
222          self->expected_dims[0], self->expected_dims[1],
223          self->expected_dims[2], self->expected_dims[3]);
224    } else {
225      /* default to whatever ca gives us */
226      self->expected_dims[0] = ca_viewport[0];
227      self->expected_dims[1] = ca_viewport[1];
228      self->expected_dims[2] = ca_viewport[2];
229      self->expected_dims[3] = ca_viewport[3];
230    }
231
232    self->last_bounds = self.bounds;
233    self->queue_resize = FALSE;
234  }
235
236  src.x = self->expected_dims[0];
237  src.y = self->expected_dims[1];
238  src.w = self->expected_dims[2];
239  src.h = self->expected_dims[3];
240
241  dst.x = ca_viewport[0];
242  dst.y = ca_viewport[1];
243  dst.w = ca_viewport[2];
244  dst.h = ca_viewport[3];
245
246  gst_video_sink_center_rect (src, dst, &result, TRUE);
247
248  GST_TRACE ("Using viewport %u,%u %ux%u", result.x, result.y, result.w,
249      result.h);
250  gl->Viewport (result.x, result.y, result.w, result.h);
251
252  if (self->draw_cb)
253    self->draw_cb (self->draw_data);
254  gst_gl_context_activate (self->draw_context, FALSE);
255
256  /* flushes the buffer */
257  [super drawInCGLContext:glContext pixelFormat:pixelFormat forLayerTime:interval displayTime:timeStamp];
258}
259
260@end
261