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