1/*****************************************************************************
2 * caopengllayer.m: CAOpenGLLayer (Mac OS X) video output
3 *****************************************************************************
4 * Copyright (C) 2014-2017 VLC authors and VideoLAN
5 * $Id: aef7b538f77fab601998bc0aaeda247cf000a04e $
6 *
7 * Authors: David Fuhrmann <david dot fuhrmann at googlemail dot com>
8 *          Felix Paul Kühne <fkuehne at videolan dot org>
9 *          Pierre d'Herbemont <pdherbemont at videolan dot org>
10 *
11 * Some of the code is based on mpv's video_layer.swift by "der richter"
12 *
13 * This program is free software; you can redistribute it and/or modify it
14 * under the terms of the GNU Lesser General Public License as published by
15 * the Free Software Foundation; either version 2.1 of the License, or
16 * (at your option) any later version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU Lesser General Public License for more details.
22 *
23 * You should have received a copy of the GNU Lesser General Public License
24 * along with this program; if not, write to the Free Software Foundation,
25 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
26 *****************************************************************************/
27
28/*****************************************************************************
29 * Preamble
30 *****************************************************************************/
31
32#ifdef HAVE_CONFIG_H
33# include "config.h"
34#endif
35
36#include <vlc_common.h>
37#include <vlc_plugin.h>
38#include <vlc_vout_display.h>
39#include <vlc_opengl.h>
40#include <vlc_atomic.h>
41
42#import <QuartzCore/QuartzCore.h>
43#import <Cocoa/Cocoa.h>
44#import <OpenGL/OpenGL.h>
45#import <dlfcn.h>
46
47#include "opengl/vout_helper.h"
48
49/*****************************************************************************
50 * Vout interface
51 *****************************************************************************/
52static int  Open   (vlc_object_t *);
53static void Close  (vlc_object_t *);
54
55static picture_pool_t *Pool (vout_display_t *vd, unsigned requested_count);
56static void PictureRender   (vout_display_t *vd, picture_t *pic, subpicture_t *subpicture);
57static void PictureDisplay  (vout_display_t *vd, picture_t *pic, subpicture_t *subpicture);
58static int Control          (vout_display_t *vd, int query, va_list ap);
59
60/**
61 * Protocol declaration that drawable-nsobject should follow
62 */
63@protocol VLCOpenGLVideoViewEmbedding <NSObject>
64- (void)addVoutSubview:(NSView *)view;
65- (void)removeVoutSubview:(NSView *)view;
66@end
67
68/**
69 * Layer subclass that handles OpenGL video rendering
70 */
71@interface VLCCAOpenGLLayer : CAOpenGLLayer
72{
73    NSLock *_displayLock;
74    vout_display_t *_voutDisplay; // All accesses to this must be @synchronized(self)
75                                  // unless you can be sure it won't be called in teardown
76    CGLContextObj _glContext;
77}
78
79- (instancetype)initWithVoutDisplay:(vout_display_t *)vd;
80- (void)placePictureWithConfig:(const vout_display_cfg_t *)cfg;
81- (void)displayFromVout;
82- (void)reportCurrentLayerSize;
83- (void)reportCurrentLayerSizeWithScale:(CGFloat)scale;
84- (void)vlcClose;
85@end
86
87/**
88 * View subclass which is backed by a VLCCAOpenGLLayer
89 */
90#if __MAC_OS_X_VERSION_MAX_ALLOWED < 101400
91// macOS SDKs lower than 10.14 did not have a NSViewLayerContentScaleDelegate
92// protocol definition, but its not needed, it will work fine without it as the
93// delegate method even existed before, just not the protocol.
94@interface VLCVideoLayerView : NSView <CALayerDelegate>
95#else
96@interface VLCVideoLayerView : NSView <CALayerDelegate, NSViewLayerContentScaleDelegate>
97#endif
98{
99    vout_display_t *_vlc_vd; // All accesses to this must be @synchronized(self)
100}
101
102- (instancetype)initWithVoutDisplay:(vout_display_t *)vd;
103- (void)vlcClose;
104@end
105
106struct vout_display_sys_t {
107    vout_window_t *embed;
108    id<VLCOpenGLVideoViewEmbedding> container;
109
110    picture_pool_t *pool;
111    picture_resource_t resource;
112
113    VLCVideoLayerView *videoView; // Layer-backed view that creates videoLayer
114    VLCCAOpenGLLayer *videoLayer; // Backing layer of videoView
115
116    vlc_gl_t *gl;
117    vout_display_opengl_t *vgl;
118
119    vout_display_place_t place;
120
121    atomic_bool is_ready;
122};
123
124#pragma mark -
125#pragma mark OpenGL context helpers
126
127/**
128 * Create a new CGLContextObj for use by VLC
129 * This function may try various pixel formats until it finds a suitable/compatible
130 * one that works on the given hardware.
131 * \return CGLContextObj or NULL in case of error
132 */
133CGLContextObj vlc_CreateCGLContext()
134{
135    CGLError err;
136    GLint npix = 0;
137    CGLPixelFormatObj pix;
138    CGLContextObj ctx;
139
140    CGLPixelFormatAttribute attribs[12] = {
141        kCGLPFAAllowOfflineRenderers,
142        kCGLPFADoubleBuffer,
143        kCGLPFAAccelerated,
144        kCGLPFANoRecovery,
145        kCGLPFAColorSize, 24,
146        kCGLPFAAlphaSize, 8,
147        kCGLPFADepthSize, 24,
148        0, // If ever extending this list, adjust the offset below!
149        0
150    };
151
152    if (@available(macOS 10.8, *)) {
153        // Enable automatic graphics switching support, important on Macs
154        // with dedicated GPUs, as it allows to not always use the dedicated
155        // GPU which has more power consumption
156        attribs[10] = kCGLPFASupportsAutomaticGraphicsSwitching;
157    }
158
159    err = CGLChoosePixelFormat(attribs, &pix, &npix);
160    if (err != kCGLNoError || pix == NULL) {
161        return NULL;
162    }
163
164    err = CGLCreateContext(pix, NULL, &ctx);
165    if (err != kCGLNoError || ctx == NULL) {
166        return NULL;
167    }
168
169    CGLDestroyPixelFormat(pix);
170    return ctx;
171}
172
173struct vlc_gl_sys
174{
175    CGLContextObj cgl; // The CGL context managed by us
176    CGLContextObj cgl_prev; // The previously current CGL context, if any
177};
178
179/**
180 * Flush the OpenGL context
181 * In case of double-buffering swaps the back buffer with the front buffer.
182 * \note This function implicitly calls \c glFlush() before it returns.
183 */
184static void gl_cb_Swap(vlc_gl_t *vlc_gl)
185{
186    struct vlc_gl_sys *sys = vlc_gl->sys;
187
188    // Copies a double-buffered contexts back buffer to front buffer, calling
189    // glFlush before this is not needed and discouraged for performance reasons.
190    // An implicit glFlush happens before CGLFlushDrawable returns.
191    CGLFlushDrawable(sys->cgl);
192}
193
194/**
195 * Make the OpenGL context the current one
196 * Makes the CGL context the current context, if it is not already the current one,
197 * and locks it.
198 */
199static int gl_cb_MakeCurrent(vlc_gl_t *vlc_gl)
200{
201    CGLError err;
202    struct vlc_gl_sys *sys = vlc_gl->sys;
203
204    sys->cgl_prev = CGLGetCurrentContext();
205
206    if (sys->cgl_prev != sys->cgl) {
207        err = CGLSetCurrentContext(sys->cgl);
208        if (err != kCGLNoError) {
209            msg_Err(vlc_gl, "Failure setting current CGLContext: %s", CGLErrorString(err));
210            return VLC_EGENERIC;
211        }
212    }
213
214    err = CGLLockContext(sys->cgl);
215    if (err != kCGLNoError) {
216        msg_Err(vlc_gl, "Failure locking CGLContext: %s", CGLErrorString(err));
217        return VLC_EGENERIC;
218    }
219
220    return VLC_SUCCESS;
221}
222
223/**
224 * Make the OpenGL context no longer current one.
225 * Makes the previous context the current one and unlocks the CGL context.
226 */
227static void gl_cb_ReleaseCurrent(vlc_gl_t *vlc_gl)
228{
229    CGLError err;
230    struct vlc_gl_sys *sys = vlc_gl->sys;
231
232    assert(CGLGetCurrentContext() == sys->cgl);
233
234    err = CGLUnlockContext(sys->cgl);
235    if (err != kCGLNoError) {
236        msg_Err(vlc_gl, "Failure unlocking CGLContext: %s", CGLErrorString(err));
237        abort();
238    }
239
240    if (sys->cgl_prev != sys->cgl) {
241        err = CGLSetCurrentContext(sys->cgl_prev);
242        if (err != kCGLNoError) {
243            msg_Err(vlc_gl, "Failure restoring previous CGLContext: %s", CGLErrorString(err));
244            abort();
245        }
246    }
247
248    sys->cgl_prev = NULL;
249}
250
251/**
252 * Look up OpenGL symbols by name
253 */
254static void *gl_cb_GetProcAddress(vlc_gl_t *vlc_gl, const char *name)
255{
256    VLC_UNUSED(vlc_gl);
257
258    return dlsym(RTLD_DEFAULT, name);
259}
260
261
262#pragma mark -
263#pragma mark Module functions
264
265static int Open(vlc_object_t *this)
266{
267    @autoreleasepool {
268        vout_display_t *vd = (vout_display_t *)this;
269        vout_display_sys_t *sys;
270
271        vd->sys = sys = vlc_obj_calloc(this, 1, sizeof(*sys));
272        if (sys == NULL)
273            return VLC_ENOMEM;
274
275        // Only use this video output on macOS 10.14 or higher
276        // currently, as it has some issues on at least macOS 10.7
277        // and the old NSView based output still works fine on old
278        // macOS versions.
279        if (@available(macOS 10.14, *)) {
280            // This is intentionally left empty, as the check
281            // can not be negated or combined with other conditions!
282        } else {
283            if (!vd->obj.force)
284                return VLC_EGENERIC;
285        }
286
287        // Obtain container NSObject
288        id container = var_CreateGetAddress(vd, "drawable-nsobject");
289        if (container) {
290            vout_display_DeleteWindow(vd, NULL);
291        } else {
292            sys->embed = vout_display_NewWindow(vd, VOUT_WINDOW_TYPE_NSOBJECT);
293            if (sys->embed)
294                container = sys->embed->handle.nsobject;
295
296            if (!container) {
297                msg_Err(vd, "No drawable-nsobject found!");
298                goto error;
299            }
300        }
301
302        // Retain container, released in Close
303        sys->container = [container retain];
304
305        // Create the CGL context
306        CGLContextObj cgl_ctx = vlc_CreateCGLContext();
307        if (cgl_ctx == NULL) {
308            msg_Err(vd, "Failure to create CGL context!");
309            goto error;
310        }
311
312        // Create a pseudo-context object which provides needed callbacks
313        // for VLC to deal with the CGL context. Usually this should be done
314        // by a proper opengl provider module, but we do not have that currently.
315        sys->gl = vlc_object_create(vd, sizeof(*sys->gl));
316        if (unlikely(!sys->gl))
317            goto error;
318
319        struct vlc_gl_sys *glsys = sys->gl->sys = malloc(sizeof(*glsys));
320        if (unlikely(!glsys)) {
321            Close(this);
322            return VLC_ENOMEM;
323        }
324        glsys->cgl = cgl_ctx;
325        glsys->cgl_prev = NULL;
326
327        sys->gl->swap = gl_cb_Swap;
328        sys->gl->makeCurrent = gl_cb_MakeCurrent;
329        sys->gl->releaseCurrent = gl_cb_ReleaseCurrent;
330        sys->gl->getProcAddress = gl_cb_GetProcAddress;
331
332        // Set the CGL context to the "macosx-glcontext" as the
333        // CGL context is needed for CIFilters and the CVPX converter
334        var_Create(vd->obj.parent, "macosx-glcontext", VLC_VAR_ADDRESS);
335        var_SetAddress(vd->obj.parent, "macosx-glcontext", cgl_ctx);
336
337        dispatch_sync(dispatch_get_main_queue(), ^{
338            // Create video view
339            sys->videoView = [[VLCVideoLayerView alloc] initWithVoutDisplay:vd];
340            sys->videoLayer = (VLCCAOpenGLLayer*)[[sys->videoView layer] retain];
341            // Add video view to container
342            if ([container respondsToSelector:@selector(addVoutSubview:)]) {
343                [container addVoutSubview:sys->videoView];
344            } else if ([container isKindOfClass:[NSView class]]) {
345                NSView *containerView = container;
346                [containerView addSubview:sys->videoView];
347                [sys->videoView setFrame:containerView.bounds];
348                [sys->videoLayer reportCurrentLayerSize];
349            } else {
350                [sys->videoView release];
351                [sys->videoLayer release];
352                sys->videoView = nil;
353                sys->videoLayer = nil;
354            }
355        });
356
357        if (sys->videoView == nil) {
358            msg_Err(vd,
359                    "Invalid drawable-nsobject object, must either be an NSView "
360                    "or comply with the VLCOpenGLVideoViewEmbedding protocol");
361            goto error;
362        }
363
364        // Initialize OpenGL video display
365        const vlc_fourcc_t *spu_chromas;
366
367        if (vlc_gl_MakeCurrent(sys->gl))
368            goto error;
369
370        sys->vgl = vout_display_opengl_New(&vd->fmt, &spu_chromas, sys->gl,
371                                           &vd->cfg->viewpoint);
372        vlc_gl_ReleaseCurrent(sys->gl);
373
374        if (sys->vgl == NULL) {
375            msg_Err(vd, "Error while initializing OpenGL display");
376            goto error;
377        }
378
379        vd->info.has_pictures_invalid = false;
380        vd->info.subpicture_chromas = spu_chromas;
381
382        vd->pool    = Pool;
383        vd->prepare = PictureRender;
384        vd->display = PictureDisplay;
385        vd->control = Control;
386
387        atomic_init(&sys->is_ready, false);
388        return VLC_SUCCESS;
389
390    error:
391        Close(this);
392        return VLC_EGENERIC;
393    }
394}
395
396static void Close(vlc_object_t *p_this)
397{
398    vout_display_t *vd = (vout_display_t *)p_this;
399    vout_display_sys_t *sys = vd->sys;
400
401    atomic_store(&sys->is_ready, false);
402    [sys->videoView vlcClose];
403
404    if (sys->vgl && !vlc_gl_MakeCurrent(sys->gl)) {
405        vout_display_opengl_Delete(sys->vgl);
406        vlc_gl_ReleaseCurrent(sys->gl);
407    }
408
409    if (sys->embed)
410        vout_display_DeleteWindow(vd, sys->embed);
411
412    if (sys->gl) {
413        struct vlc_gl_sys *glsys = sys->gl->sys;
414
415        // It should never happen that the context is destroyed and we
416        // still have a previous context set, as it would mean non-balanced
417        // calls to MakeCurrent/ReleaseCurrent.
418        assert(glsys->cgl_prev == NULL);
419
420        CGLReleaseContext(glsys->cgl);
421        vlc_object_release(sys->gl);
422        free(glsys);
423    }
424
425    // Copy pointers out of sys, as sys can be gone already
426    // when the dispatch_async block is run!
427    id container = sys->container;
428    VLCVideoLayerView *videoView = sys->videoView;
429    VLCCAOpenGLLayer *videoLayer = sys->videoLayer;
430
431    dispatch_async(dispatch_get_main_queue(), ^{
432        // Remove vout subview from container
433        if ([container respondsToSelector:@selector(removeVoutSubview:)]) {
434            [container removeVoutSubview:videoView];
435        }
436        [videoView removeFromSuperview];
437
438        [videoView release];
439        [container release];
440        [videoLayer release];
441    });
442}
443
444static picture_pool_t *Pool(vout_display_t *vd, unsigned count)
445{
446    vout_display_sys_t *sys = vd->sys;
447
448    if (!sys->pool && vlc_gl_MakeCurrent(sys->gl) == VLC_SUCCESS)
449    {
450        sys->pool = vout_display_opengl_GetPool(sys->vgl, count);
451        vlc_gl_ReleaseCurrent(sys->gl);
452    }
453    return sys->pool;
454}
455
456static void PictureRender(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
457{
458    vout_display_sys_t *sys = vd->sys;
459
460    if (vlc_gl_MakeCurrent(sys->gl) == VLC_SUCCESS)
461    {
462        vout_display_opengl_Prepare(sys->vgl, pic, subpicture);
463        vlc_gl_ReleaseCurrent(sys->gl);
464
465        atomic_store(&sys->is_ready, true);
466    }
467}
468
469static void PictureDisplay(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
470{
471    vout_display_sys_t *sys = vd->sys;
472
473    [sys->videoLayer displayFromVout];
474
475    picture_Release(pic);
476    if (subpicture)
477        subpicture_Delete(subpicture);
478}
479
480static int Control(vout_display_t *vd, int query, va_list ap)
481{
482    vout_display_sys_t *sys = vd->sys;
483
484    if (!vd->sys)
485        return VLC_EGENERIC;
486
487    switch (query)
488    {
489        case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
490        case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
491        case VOUT_DISPLAY_CHANGE_ZOOM:
492        case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
493        case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
494        {
495            const vout_display_cfg_t *cfg;
496
497            if (query == VOUT_DISPLAY_CHANGE_SOURCE_ASPECT ||
498                query == VOUT_DISPLAY_CHANGE_SOURCE_CROP) {
499                cfg = vd->cfg;
500            } else {
501                cfg = (const vout_display_cfg_t*)va_arg (ap, const vout_display_cfg_t *);
502            }
503
504            [sys->videoLayer placePictureWithConfig:cfg];
505
506            // Note!
507            // No viewport or aspect ratio is set here, as that needs to be set
508            // when rendering. The viewport is always set to match the layer
509            // size by the OS right before the OpenGL render callback, so
510            // setting it here has no effect.
511            return VLC_SUCCESS;
512        }
513
514        case VOUT_DISPLAY_CHANGE_VIEWPOINT:
515        {
516            return vout_display_opengl_SetViewpoint(sys->vgl,
517                        &va_arg (ap, const vout_display_cfg_t* )->viewpoint);
518        }
519
520        case VOUT_DISPLAY_RESET_PICTURES:
521            vlc_assert_unreachable();
522        default:
523            msg_Err (vd, "Unhandled request %d", query);
524            return VLC_EGENERIC;
525    }
526
527    return VLC_SUCCESS;
528}
529
530#pragma mark -
531#pragma mark VLCVideoLayerView
532
533@implementation VLCVideoLayerView
534
535- (instancetype)initWithVoutDisplay:(vout_display_t *)vd
536{
537    self = [super init];
538    if (self) {
539        _vlc_vd = vd;
540
541        self.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
542        self.wantsLayer = YES;
543    }
544    return self;
545}
546
547/**
548 * Invalidates VLC objects (notably _vlc_vd)
549 * This method must be called in VLCs module Close (or indirectly by the View)
550 * to ensure all critical VLC resources that might be gone when the module is
551 * closed are properly NULLed. This is necessary as dealloc is only called later
552 * as it has to be done async on the main thread, because NSView must be
553 * dealloc'ed on the main thread and the view own the layer, so the layer
554 * will stay valid until the view is gone, and might still use _vlc_vd
555 * even after the VLC module is gone and the resources would be invalid.
556 */
557- (void)vlcClose
558{
559    @synchronized (self) {
560        [(VLCCAOpenGLLayer *)self.layer vlcClose];
561        _vlc_vd = NULL;
562    }
563}
564
565- (void)viewWillStartLiveResize
566{
567    [(VLCCAOpenGLLayer *)self.layer setAsynchronous:YES];
568}
569
570- (void)viewDidEndLiveResize
571{
572    [(VLCCAOpenGLLayer *)self.layer setAsynchronous:NO];
573
574    // After a live resize we need to tell the core about our new size
575    [(VLCCAOpenGLLayer *)self.layer reportCurrentLayerSize];
576}
577
578- (CALayer *)makeBackingLayer
579{
580    @synchronized(self) {
581        NSAssert(_vlc_vd != NULL, @"Cannot create backing layer without vout display!");
582
583        VLCCAOpenGLLayer *layer = [[VLCCAOpenGLLayer alloc] initWithVoutDisplay:_vlc_vd];
584        layer.delegate = self;
585        return [layer autorelease];
586    }
587}
588
589/* Layer delegate method that ensures the layer always get the
590 * correct contentScale based on whether the view is on a HiDPI
591 * display or not, and when it is moved between displays.
592 */
593- (BOOL)layer:(CALayer *)layer
594shouldInheritContentsScale:(CGFloat)newScale
595   fromWindow:(NSWindow *)window
596{
597    // If the scale changes, from the OpenGL point of view
598    // the size changes, so we need to indicate a resize
599    if (layer == self.layer) {
600        [(VLCCAOpenGLLayer *)self.layer
601            reportCurrentLayerSizeWithScale:newScale];
602        // FIXME
603        // For a brief moment the old image with a wrong scale
604        // is still visible, thats because the resize event is not
605        // processed immediately. Ideally this would be handled similar
606        // to how the live resize is done, to avoid this.
607    }
608    return YES;
609}
610
611/*
612 * General properties
613 */
614
615- (BOOL)isOpaque
616{
617    return YES;
618}
619
620- (BOOL)acceptsFirstResponder
621{
622    return YES;
623}
624
625- (BOOL)mouseDownCanMoveWindow
626{
627    return YES;
628}
629
630
631#pragma mark View mouse events
632
633/* Left mouse button down */
634- (void)mouseDown:(NSEvent *)event
635{
636    @synchronized(self) {
637        if (!_vlc_vd) {
638            [super mouseDown:event];
639            return;
640        }
641
642        if (event.type == NSLeftMouseDown &&
643            !(event.modifierFlags & NSControlKeyMask) &&
644            event.clickCount == 1) {
645            vout_display_SendEventMousePressed(_vlc_vd, MOUSE_BUTTON_LEFT);
646        }
647    }
648
649    [super mouseDown:event];
650}
651
652/* Left mouse button up */
653- (void)mouseUp:(NSEvent *)event
654{
655    @synchronized(self) {
656        if (!_vlc_vd) {
657            [super mouseUp:event];
658            return;
659        }
660
661        if (event.type == NSLeftMouseUp) {
662            vout_display_SendEventMouseReleased(_vlc_vd, MOUSE_BUTTON_LEFT);
663        }
664    }
665
666    [super mouseUp:event];
667}
668
669/* Middle mouse button down */
670- (void)otherMouseDown:(NSEvent *)event
671{
672    @synchronized(self) {
673        if (_vlc_vd)
674            vout_display_SendEventMousePressed(_vlc_vd, MOUSE_BUTTON_CENTER);
675    }
676
677    [super otherMouseDown:event];
678}
679
680/* Middle mouse button up */
681- (void)otherMouseUp:(NSEvent *)event
682{
683    @synchronized(self) {
684        if (_vlc_vd)
685            vout_display_SendEventMouseReleased(_vlc_vd, MOUSE_BUTTON_CENTER);
686    }
687
688    [super otherMouseUp:event];
689}
690
691- (void)mouseMovedInternal:(NSEvent *)event
692{
693    @synchronized(self) {
694        if (!_vlc_vd) {
695            return;
696        }
697
698        vout_display_sys_t *sys = _vlc_vd->sys;
699
700        // Convert window-coordinate point to view space
701        NSPoint pointInView = [self convertPoint:event.locationInWindow fromView:nil];
702
703        // Convert to pixels
704        NSPoint pointInBacking = [self convertPointToBacking:pointInView];
705
706        vout_display_SendMouseMovedDisplayCoordinates(_vlc_vd, ORIENT_VFLIPPED,
707                                                      pointInBacking.x, pointInBacking.y,
708                                                      &sys->place);
709    }
710}
711
712/* Mouse moved */
713- (void)mouseMoved:(NSEvent *)event
714{
715    [self mouseMovedInternal:event];
716    [super mouseMoved:event];
717}
718
719/* Mouse moved while clicked */
720- (void)mouseDragged:(NSEvent *)event
721{
722    [self mouseMovedInternal:event];
723    [super mouseDragged:event];
724}
725
726/* Mouse moved while center-clicked */
727- (void)otherMouseDragged:(NSEvent *)event
728{
729    [self mouseMovedInternal:event];
730    [super otherMouseDragged:event];
731}
732
733/* Mouse moved while right-clicked */
734- (void)rightMouseDragged:(NSEvent *)event
735{
736    [self mouseMovedInternal:event];
737    [super rightMouseDragged:event];
738}
739
740@end
741
742#pragma mark -
743#pragma mark VLCCAOpenGLLayer
744
745@implementation VLCCAOpenGLLayer
746
747- (instancetype)initWithVoutDisplay:(vout_display_t *)vd
748{
749    self = [super init];
750    if (self) {
751        _displayLock = [[NSLock alloc] init];
752        _voutDisplay = vd;
753
754        struct vlc_gl_sys *glsys = vd->sys->gl->sys;
755        _glContext = CGLRetainContext(glsys->cgl);
756
757        [CATransaction lock];
758        self.needsDisplayOnBoundsChange = YES;
759        self.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
760        self.asynchronous = NO;
761        self.opaque = 1.0;
762        self.hidden = NO;
763        [CATransaction unlock];
764    }
765
766    return self;
767}
768
769/**
770 * Invalidates VLC objects (notably _voutDisplay)
771 * This method must be called in VLCs module Close (or indirectly by the View).
772 */
773- (void)vlcClose
774{
775    @synchronized (self) {
776        _voutDisplay = NULL;
777    }
778}
779
780- (void)dealloc
781{
782    CGLReleaseContext(_glContext);
783    [_displayLock release];
784    [super dealloc];
785}
786
787- (void)placePictureWithConfig:(const vout_display_cfg_t *)cfg
788{
789    vout_display_sys_t *sys = _voutDisplay->sys;
790    vout_display_cfg_t tmp_cfg = *cfg;
791
792    // Reverse vertical alignment as the GL tex are Y inverted
793    if (tmp_cfg.align.vertical == VOUT_DISPLAY_ALIGN_TOP)
794        tmp_cfg.align.vertical = VOUT_DISPLAY_ALIGN_BOTTOM;
795    else if (tmp_cfg.align.vertical == VOUT_DISPLAY_ALIGN_BOTTOM)
796        tmp_cfg.align.vertical = VOUT_DISPLAY_ALIGN_TOP;
797
798    // Synchronization not for _voutDisplay but for sys->place
799    // as this method can be called either from the Control()
800    // function or the layer draw method.
801    @synchronized(self) {
802        vout_display_PlacePicture(&sys->place, &_voutDisplay->source, &tmp_cfg, false);
803    }
804}
805
806- (void)layoutSublayers
807{
808    [super layoutSublayers];
809
810    if (self.asynchronous) {
811        // During live resize, the size is updated in the
812        // OpenGL draw callback, to ensure atomic size changes
813        // that are in sync with the real layer size.
814        // This bypasses the core but is needed for resizing
815        // without glitches or lags.
816        return;
817    }
818
819    [self reportCurrentLayerSize];
820}
821
822- (void)reportCurrentLayerSizeWithScale:(CGFloat)scale
823{
824    CGSize newSize = self.visibleRect.size;
825
826    // Calculate pixel values
827    newSize.width *= scale;
828    newSize.height *= scale;
829
830    @synchronized(self) {
831        if (!_voutDisplay)
832            return;
833
834        vout_display_SendEventDisplaySize(_voutDisplay,
835                                          newSize.width, newSize.height);
836    }
837}
838
839- (void)reportCurrentLayerSize
840{
841    CGFloat scale = self.contentsScale;
842    [self reportCurrentLayerSizeWithScale:scale];
843}
844
845- (void)display
846{
847    [_displayLock lock];
848
849    [super display];
850    [CATransaction flush];
851
852    [_displayLock unlock];
853}
854
855- (void)displayFromVout
856{
857    if (self.asynchronous) {
858        // During live resizing we do not take updates
859        // from the vout, as those would interfere with
860        // the rendering currently happening on the main
861        // thread for the resize. Rendering anyway happens
862        // triggered by the OS every display refresh, so
863        // forcing an update here would be useless anyway.
864        return;
865    }
866
867    [self display];
868}
869
870- (BOOL)canDrawInCGLContext:(CGLContextObj)glContext
871                pixelFormat:(CGLPixelFormatObj)pixelFormat
872               forLayerTime:(CFTimeInterval)timeInterval
873                displayTime:(const CVTimeStamp *)timeStamp
874{
875    @synchronized(self) {
876        if (!_voutDisplay)
877            return NO;
878
879        return (atomic_load(&_voutDisplay->sys->is_ready));
880    }
881}
882
883- (void)drawInCGLContext:(CGLContextObj)glContext
884             pixelFormat:(CGLPixelFormatObj)pixelFormat
885            forLayerTime:(CFTimeInterval)timeInterval
886             displayTime:(const CVTimeStamp *)timeStamp
887{
888    @synchronized(self) {
889        if (!_voutDisplay)
890            return;
891
892        vout_display_sys_t *sys = _voutDisplay->sys;
893
894        if (vlc_gl_MakeCurrent(sys->gl))
895            return;
896
897        if (self.asynchronous) {
898            GLint dims[4] = { 0, 0, 0, 0 };
899            glGetIntegerv(GL_VIEWPORT, dims);
900            NSSize newSize = NSMakeSize(dims[2], dims[3]);
901
902            if (NSEqualSizes(newSize, NSZeroSize)) {
903                newSize = self.bounds.size;
904                CGFloat scale = self.contentsScale;
905                newSize.width *= scale;
906                newSize.height *= scale;
907            }
908
909            vout_display_cfg_t cfg = *_voutDisplay->cfg;
910
911            cfg.display.width = newSize.width;
912            cfg.display.height = newSize.height;
913
914            [self placePictureWithConfig:&cfg];
915        }
916
917        // Ensure viewport and aspect ratio is correct
918        vout_display_opengl_Viewport(sys->vgl, sys->place.x, sys->place.y,
919                                     sys->place.width, sys->place.height);
920        vout_display_opengl_SetWindowAspectRatio(sys->vgl, (float)sys->place.width / sys->place.height);
921
922        vout_display_opengl_Display(sys->vgl, &_voutDisplay->source);
923        vlc_gl_ReleaseCurrent(sys->gl);
924    }
925}
926
927- (CGLPixelFormatObj)copyCGLPixelFormatForDisplayMask:(uint32_t)mask
928{
929    CGLPixelFormatObj fmt = CGLGetPixelFormat(_glContext);
930
931    return (fmt) ? CGLRetainPixelFormat(fmt) : NULL;
932}
933
934- (CGLContextObj)copyCGLContextForPixelFormat:(CGLPixelFormatObj)pixelFormat
935{
936    return CGLRetainContext(_glContext);
937}
938
939@end
940
941/*
942 * Module descriptor
943 */
944
945vlc_module_begin()
946    set_description(N_("Core Animation OpenGL Layer (Mac OS X)"))
947    set_capability("vout display", 300)
948    set_category(CAT_VIDEO)
949    set_subcategory(SUBCAT_VIDEO_VOUT)
950    set_callbacks(Open, Close)
951vlc_module_end()
952