[vlc-devel] [PATCH 4/4] [3.x] vout/caopengllayer: rewrite most of the module

Thomas Guillem thomas at gllm.fr
Thu Feb 20 13:57:00 CET 2020


Hello,

I have few remarks onlined:

On Thu, Feb 20, 2020, at 11:27, Marvin Scholz wrote:
> Rewrites most of the layer vout code to have the same features as the
> view based vout. Additionally fixes laggy resizing, fixes CGL context
> creation bugs, adds support for CI filters and fixes various memory
> management errors.
> 
> The CAOpenGLLayer based API is special and different from all other APIs
> provided on other OSes as it is not a push-model API but a pull one,
> where the OS calls a specific method when a new frame should be rendered.
> This makes integration into VLC relatively tricky and the code a bit
> harder to follow.
> While the API is a pull-model, we can kind of trick it by just forcing
> a re-display of the layer in the vouts display function. With views this
> would be forbidden as views are supposed to be accessed from the main
> thread only, but with layers this is possible if some care is taken.
> When forcing the layer to render from a different thread, the implicitly
> created CATransaction has to be flushed explicitly, as we do not have a
> main loop at the end of which it would be flushed.
> 
> We do not force rendering all the time though, as doing that would break
> resize animations given that VLC can not know the right time when display
> refresh will happen, so resizing would look laggy and have glitches, as
> during a resize both the OS and VLC would drive the rendering of the
> layer, resulting in unexpected result.
> To prevent that, when live resizing starts (the user resizing by dragging
> a windows corner), the layer is set into asynchronous rendering mode
> which makes the OS drive the rendering loop completely not only for
> drawing the resize change. While the layer is in asynchronous mode, we
> ignore all update requests from the core, as the layer is anyway updated
> continuously by the OS and forcing rendering from another thread would
> lead to artifacts. Additionally while in live resize, we do not report
> the size changes to the core, as the event takes too long to reach the
> vout Control() function, resulting in the layer content being displayed
> at the wrong (old) size. Instead we take the current viewport size
> as the size and display using that.
> 
> Another unusual thing compared to other vouts is that the VLC OpenGL
> display functions to update the viewport and aspect ratio are not
> called in the Control event handling callback, thats because before
> the render callback is called, the OS sets the OpenGL viewport to match
> the layer backing store size. So setting it in the Control callback
> is useless as it does not make any difference.
> ---
>  modules/video_output/caopengllayer.m | 896 ++++++++++++++++++---------
>  1 file changed, 604 insertions(+), 292 deletions(-)
> 
> diff --git a/modules/video_output/caopengllayer.m 
> b/modules/video_output/caopengllayer.m
> index c6496ed8f0..fb2b2bc02c 100644
> --- a/modules/video_output/caopengllayer.m
> +++ b/modules/video_output/caopengllayer.m
> @@ -8,6 +8,8 @@
>   *          Felix Paul Kühne <fkuehne at videolan dot org>
>   *          Pierre d'Herbemont <pdherbemont at videolan dot org>
>   *
> + * Some of the code is based on mpv's video_layer.swift by "der 
> richter"
> + *
>   * This program is free software; you can redistribute it and/or 
> modify it
>   * under the terms of the GNU Lesser General Public License as 
> published by
>   * the Free Software Foundation; either version 2.1 of the License, or
> @@ -35,11 +37,12 @@
>  #include <vlc_plugin.h>
>  #include <vlc_vout_display.h>
>  #include <vlc_opengl.h>
> +#include <vlc_atomic.h>
>  
>  #import <QuartzCore/QuartzCore.h>
>  #import <Cocoa/Cocoa.h>
>  #import <OpenGL/OpenGL.h>
> -#import <dlfcn.h>               /* dlsym */
> +#import <dlfcn.h>
>  
>  #include "opengl/vout_helper.h"
>  
> @@ -54,254 +57,401 @@
>  static void PictureDisplay  (vout_display_t *vd, picture_t *pic, 
> subpicture_t *subpicture);
>  static int Control          (vout_display_t *vd, int query, va_list 
> ap);
>  
> -static void *OurGetProcAddress (vlc_gl_t *gl, const char *name);
> -static int OpenglLock         (vlc_gl_t *gl);
> -static void OpenglUnlock       (vlc_gl_t *gl);
> -static void OpenglSwap         (vlc_gl_t *gl);
> -
> - at protocol VLCCoreAnimationVideoLayerEmbedding <NSObject>
> -- (void)addVoutLayer:(CALayer *)aLayer;
> -- (void)removeVoutLayer:(CALayer *)aLayer;
> -- (CGSize)currentOutputSize;
> +/**
> + * Protocol declaration that drawable-nsobject should follow
> + */
> + at protocol VLCOpenGLVideoViewEmbedding <NSObject>
> +- (void)addVoutSubview:(NSView *)view;
> +- (void)removeVoutSubview:(NSView *)view;
>  @end
>  
> +/**
> + * Layer subclass that handles OpenGL video rendering
> + */
>  @interface VLCCAOpenGLLayer : CAOpenGLLayer
> +{
> +    NSLock *_displayLock;
> +    vout_display_t *_voutDisplay; // All accesses to this must be 
> @synchronized(self)
> +                                  // unless you can be sure it won't 
> be called in teardown
> +    CGLContextObj _glContext;
> +}
>  
> - at property (nonatomic, readwrite) vout_display_t* voutDisplay;
> - at property (nonatomic, readwrite) CGLContextObj glContext;
> -
> +- (instancetype)initWithVoutDisplay:(vout_display_t *)vd;
> +- (void)placePictureWithConfig:(const vout_display_cfg_t *)cfg;
> +- (void)displayFromVout;
> +- (void)reportCurrentLayerSize;
> +- (void)vlcClose;
>  @end
>  
> +/**
> + * View subclass which is backed by a VLCCAOpenGLLayer
> + */
> + at interface VLCVideoLayerView : NSView <CALayerDelegate, 
> NSViewLayerContentScaleDelegate>
> +{
> +    vout_display_t *_vlc_vd; // All accesses to this must be 
> @synchronized(self)
> +}
> +
> +- (instancetype)initWithVoutDisplay:(vout_display_t *)vd;
> +- (void)vlcClose;
> + at end
>  
>  struct vout_display_sys_t {
> +    vout_window_t *embed;
> +    id<VLCOpenGLVideoViewEmbedding> container;
>  
>      picture_pool_t *pool;
>      picture_resource_t resource;
>  
> -    CALayer <VLCCoreAnimationVideoLayerEmbedding> *container;
> -    vout_window_t *embed;
> -    VLCCAOpenGLLayer *cgLayer;
> +    VLCVideoLayerView *videoView; // Layer-backed view that creates videoLayer
> +    VLCCAOpenGLLayer *videoLayer; // Backing layer of videoView
>  
>      vlc_gl_t *gl;
>      vout_display_opengl_t *vgl;
>  
>      vout_display_place_t place;
>  
> -    bool  b_frame_available;
> +    atomic_bool is_ready;
>  };
>  
> -struct gl_sys
> +#pragma mark -
> +#pragma mark OpenGL context helpers
> +
> +/**
> + * Create a new CGLContextObj for use by VLC
> + * This function may try various pixel formats until it finds a 
> suitable/compatible
> + * one that works on the given hardware.
> + * \return CGLContextObj or NULL in case of error
> + */
> +CGLContextObj vlc_CreateCGLContext()
> +{
> +    CGLError err;
> +    GLint npix = 0;
> +    CGLPixelFormatObj pix;
> +    CGLContextObj ctx;
> +
> +    CGLPixelFormatAttribute attribs[12] = {
> +        kCGLPFAAllowOfflineRenderers,
> +        kCGLPFADoubleBuffer,
> +        kCGLPFAAccelerated,
> +        kCGLPFANoRecovery,
> +        kCGLPFAColorSize, 24,
> +        kCGLPFAAlphaSize, 8,
> +        kCGLPFADepthSize, 24,
> +        0, // If ever extending this list, adjust the offset below!
> +        0
> +    };
> +
> +    if (@available(macOS 10.8, *)) {
> +        // Enable automatic graphics switching support, important on 
> Macs
> +        // with dedicated GPUs, as it allows to not always use the 
> dedicated
> +        // GPU which has more power consumption
> +        attribs[10] = kCGLPFASupportsAutomaticGraphicsSwitching;
> +    }
> +
> +    err = CGLChoosePixelFormat(attribs, &pix, &npix);
> +    if (err != kCGLNoError || pix == NULL) {
> +        return NULL;
> +    }
> +
> +    err = CGLCreateContext(pix, NULL, &ctx);
> +    if (err != kCGLNoError || ctx == NULL) {
> +        return NULL;
> +    }
> +
> +    CGLDestroyPixelFormat(pix);
> +    return ctx;
> +}
> +
> +struct vlc_gl_sys
>  {
> -    CGLContextObj locked_ctx;
> -    VLCCAOpenGLLayer *cgLayer;
> +    CGLContextObj cgl; // The CGL context managed by us
> +    CGLContextObj cgl_prev; // The previously current CGL context, if 
> any
>  };
>  
> -/*****************************************************************************
> - * Open: This function allocates and initializes the OpenGL vout 
> method.
> - 
> *****************************************************************************/
> -static int Open (vlc_object_t *p_this)
> +/**
> + * Flush the OpenGL context
> + * In case of double-buffering swaps the back buffer with the front 
> buffer.
> + * \note This function implicitly calls \c glFlush() before it returns.
> + */
> +static void gl_cb_Swap(vlc_gl_t *vlc_gl)
>  {
> -    vout_display_t *vd = (vout_display_t *)p_this;
> -    vout_display_sys_t *sys;
> +    struct vlc_gl_sys *sys = vlc_gl->sys;
> +
> +    // Copies a double-buffered contexts back buffer to front buffer, 
> calling
> +    // glFlush before this is not needed and discouraged for 
> performance reasons.
> +    // An implicit glFlush happens before CGLFlushDrawable returns.
> +    CGLFlushDrawable(sys->cgl);
> +}
> +
> +/**
> + * Make the OpenGL context the current one
> + * Makes the CGL context the current context, if it is not already the 
> current one,
> + * and locks it.
> + */
> +static int gl_cb_MakeCurrent(vlc_gl_t *vlc_gl)
> +{
> +    CGLError err;
> +    struct vlc_gl_sys *sys = vlc_gl->sys;
>  
> -    /* Allocate structure */
> -    vd->sys = sys = calloc(1, sizeof(vout_display_sys_t));
> -    if (sys == NULL)
> +    sys->cgl_prev = CGLGetCurrentContext();
> +
> +    if (sys->cgl_prev != sys->cgl) {
> +        err = CGLSetCurrentContext(sys->cgl);
> +        if (err != kCGLNoError) {
> +            msg_Err(vlc_gl, "Failure setting current CGLContext: %s", 
> CGLErrorString(err));
> +            return VLC_EGENERIC;
> +        }
> +    }
> +
> +    err = CGLLockContext(sys->cgl);
> +    if (err != kCGLNoError) {
> +        msg_Err(vlc_gl, "Failure locking CGLContext: %s", 
> CGLErrorString(err));
>          return VLC_EGENERIC;
> +    }
> +
> +    return VLC_SUCCESS;
> +}
> +
> +/**
> + * Make the OpenGL context no longer current one.
> + * Makes the previous context the current one and unlocks the CGL 
> context.
> + */
> +static void gl_cb_ReleaseCurrent(vlc_gl_t *vlc_gl)
> +{
> +    CGLError err;
> +    struct vlc_gl_sys *sys = vlc_gl->sys;
> +
> +    assert(CGLGetCurrentContext() == sys->cgl);
> +
> +    err = CGLUnlockContext(sys->cgl);
> +    if (err != kCGLNoError) {
> +        msg_Err(vlc_gl, "Failure unlocking CGLContext: %s", 
> CGLErrorString(err));
> +        abort();
> +    }
> +
> +    if (sys->cgl_prev != sys->cgl) {
> +        err = CGLSetCurrentContext(sys->cgl_prev);
> +        if (err != kCGLNoError) {
> +            msg_Err(vlc_gl, "Failure restoring previous CGLContext: 
> %s", CGLErrorString(err));
> +            abort();
> +        }
> +    }
> +
> +    sys->cgl_prev = NULL;
> +}
> +
> +/**
> + * Look up OpenGL symbols by name
> + */
> +static void *gl_cb_GetProcAddress(vlc_gl_t *vlc_gl, const char *name)
> +{
> +    VLC_UNUSED(vlc_gl);
>  
> +    return dlsym(RTLD_DEFAULT, name);
> +}
> +
> +
> +#pragma mark -
> +#pragma mark Module functions
> +
> +static int Open(vlc_object_t *this)
> +{
>      @autoreleasepool {
> +        vout_display_t *vd = (vout_display_t *)this;
> +        vout_display_sys_t *sys;
> +
> +        vd->sys = sys = vlc_obj_calloc(this, 1, sizeof(*sys));
> +        if (sys == NULL)
> +            return VLC_ENOMEM;
> +
> +        // Obtain container NSObject
>          id container = var_CreateGetAddress(vd, "drawable-nsobject");
> -        if (container)
> +        if (container) {
>              vout_display_DeleteWindow(vd, NULL);
> -        else {
> +        } else {
>              sys->embed = vout_display_NewWindow(vd, VOUT_WINDOW_TYPE_NSOBJECT);
>              if (sys->embed)
>                  container = sys->embed->handle.nsobject;
>  
>              if (!container) {
>                  msg_Err(vd, "No drawable-nsobject found!");
> -                goto bailout;
> +                goto error;
>              }
>          }
>  
> -        /* store for later, released in Close() */
> +        // Retain container, released in Close
>          sys->container = [container retain];
> +    
> +        // Create the CGL context
> +        CGLContextObj cgl_ctx = vlc_CreateCGLContext();
> +        if (cgl_ctx == NULL) {
> +            msg_Err(vd, "Failure to create CGL context!");
> +            goto error;
> +        }
>  
> -        [CATransaction begin];
> -        sys->cgLayer = [[VLCCAOpenGLLayer alloc] init];
> -        [sys->cgLayer setVoutDisplay:vd];
> -
> -        [sys->cgLayer performSelectorOnMainThread:@selector(display)
> -                                       withObject:nil
> -                                    waitUntilDone:YES];
> -
> -        if ([container respondsToSelector:@selector(addVoutLayer:)]) {
> -            msg_Dbg(vd, "container implements implicit protocol");
> -            [container addVoutLayer:sys->cgLayer];
> -        } else if ([container 
> respondsToSelector:@selector(addSublayer:)] ||
> -                   [container isKindOfClass:[CALayer class]]) {
> -            msg_Dbg(vd, "container doesn't implement implicit 
> protocol, fallback mode used");
> -            [container addSublayer:sys->cgLayer];
> -        } else {
> -            msg_Err(vd, "Provided NSObject container isn't 
> compatible");
> -            [sys->cgLayer release];
> -            sys->cgLayer = nil;
> -            [CATransaction commit];
> -            goto bailout;
> +        // Create a pseudo-context object which provides needed 
> callbacks
> +        // for VLC to deal with the CGL context. Usually this should 
> be done
> +        // by a proper opengl provider module, but we do not have that 
> currently.
> +        sys->gl = vlc_object_create(vd, sizeof(*sys->gl));
> +        if (unlikely(!sys->gl))
> +            goto error;
> +        
> +        struct vlc_gl_sys *glsys = sys->gl->sys =
> +            vlc_obj_malloc(VLC_OBJECT(sys->gl), sizeof(*glsys));
> +        if (unlikely(!glsys)) {
> +            Close(this);
> +            return VLC_ENOMEM;
> +        }
> +        glsys->cgl = cgl_ctx;
> +        glsys->cgl_prev = NULL;
> +
> +        sys->gl->swap = gl_cb_Swap;
> +        sys->gl->makeCurrent = gl_cb_MakeCurrent;
> +        sys->gl->releaseCurrent = gl_cb_ReleaseCurrent;
> +        sys->gl->getProcAddress = gl_cb_GetProcAddress;
> +
> +        // Set the CGL context to the "macosx-glcontext" as the
> +        // CGL context is needed for CIFilters and the CVPX converter
> +        var_Create(vd->obj.parent, "macosx-glcontext", 
> VLC_VAR_ADDRESS);
> +        var_SetAddress(vd->obj.parent, "macosx-glcontext", cgl_ctx);
> +
> +        dispatch_sync(dispatch_get_main_queue(), ^{
> +            // Create video view
> +            sys->videoView = [[VLCVideoLayerView alloc] 
> initWithVoutDisplay:vd];
> +            sys->videoLayer = (VLCCAOpenGLLayer*)[[sys->videoView 
> layer] retain];
> +            // Add video view to container
> +            if ([container 
> respondsToSelector:@selector(addVoutSubview:)]) {
> +                [container addVoutSubview:sys->videoView];
> +            } else if ([container isKindOfClass:[NSView class]]) {
> +                NSView *containerView = container;
> +                [containerView addSubview:sys->videoView];
> +                [sys->videoView setFrame:containerView.bounds];
> +                [sys->videoLayer reportCurrentLayerSize];
> +            } else {
> +                [sys->videoView release];
> +                [sys->videoLayer release];
> +                sys->videoView = nil;
> +                sys->videoLayer = nil;
> +            }
> +        });
> +        

stray spaces

> +        if (sys->videoView == nil) {
> +            msg_Err(vd,
> +                    "Invalid drawable-nsobject object, must either be 
> an NSView "
> +                    "or comply with the VLCOpenGLVideoViewEmbedding 
> protocol");
> +            goto error;
>          }
> -        [CATransaction commit];
>  
> -        if (!sys->cgLayer)
> -            goto bailout;
> +        // Initialize OpenGL video display
> +        const vlc_fourcc_t *spu_chromas;
>  
> -        if (![sys->cgLayer glContext])
> -            msg_Warn(vd, "we might not have an OpenGL context yet");
> +        if (vlc_gl_MakeCurrent(sys->gl))
> +            goto error;
> +        

stray spaces

> +        sys->vgl = vout_display_opengl_New(&vd->fmt, &spu_chromas, sys->gl,
> +                                           &vd->cfg->viewpoint);
> +        vlc_gl_ReleaseCurrent(sys->gl);
>  
> -        /* Initialize common OpenGL video display */
> -        sys->gl = vlc_object_create(vd, sizeof(*sys->gl));
> -        if (unlikely(!sys->gl))
> -            goto bailout;
> -        sys->gl->makeCurrent = OpenglLock;
> -        sys->gl->releaseCurrent = OpenglUnlock;
> -        sys->gl->swap = OpenglSwap;
> -        sys->gl->getProcAddress = OurGetProcAddress;
> -
> -        struct gl_sys *glsys = sys->gl->sys = malloc(sizeof(*glsys));
> -        if (!sys->gl->sys)
> -            goto bailout;
> -        glsys->locked_ctx = NULL;
> -        glsys->cgLayer = sys->cgLayer;
> -
> -        const vlc_fourcc_t *subpicture_chromas;
> -        video_format_t fmt = vd->fmt;
> -        if (!OpenglLock(sys->gl)) {
> -            sys->vgl = vout_display_opengl_New(&vd->fmt, &subpicture_chromas,
> -                                               sys->gl, &vd->cfg->viewpoint);
> -            OpenglUnlock(sys->gl);
> -        } else
> -            sys->vgl = NULL;
> -        if (!sys->vgl) {
> -            msg_Err(vd, "Error while initializing opengl display.");
> -            goto bailout;
> +        if (sys->vgl == NULL) {
> +            msg_Err(vd, "Error while initializing OpenGL display");
> +            goto error;
>          }
>  
> -        /* setup vout display */
> -        vout_display_info_t info = vd->info;
> -        info.subpicture_chromas = subpicture_chromas;
> -        vd->info = info;
> +        vd->info.has_pictures_invalid = false;
> +        vd->info.subpicture_chromas = spu_chromas;
>  
>          vd->pool    = Pool;
>          vd->prepare = PictureRender;
>          vd->display = PictureDisplay;
>          vd->control = Control;
>  
> -        /* setup initial state */
> -        CGSize outputSize;
> -        if ([container 
> respondsToSelector:@selector(currentOutputSize)])
> -            outputSize = [container currentOutputSize];
> -        else
> -            outputSize = [sys->container visibleRect].size;
> -        vout_display_SendEventDisplaySize(vd, (int)outputSize.width, 
> (int)outputSize.height);
> -
> +        atomic_init(&sys->is_ready, false);
>          return VLC_SUCCESS;
>  
> -    bailout:
> -        Close(p_this);
> +    error:
> +        Close(this);
>          return VLC_EGENERIC;
>      }
>  }
>  
> -static void Close (vlc_object_t *p_this)
> +static void Close(vlc_object_t *p_this)
>  {
>      vout_display_t *vd = (vout_display_t *)p_this;
>      vout_display_sys_t *sys = vd->sys;
>  
> -    if (sys->vgl != NULL && !OpenglLock(sys->gl)) {
> +    atomic_store(&sys->is_ready, false);
> +
> +    if (sys->vgl && !vlc_gl_MakeCurrent(sys->gl)) {
>          vout_display_opengl_Delete(sys->vgl);
> -        OpenglUnlock(sys->gl);
> +        vlc_gl_ReleaseCurrent(sys->gl);
>      }
>  
> -    if (sys->cgLayer) {
> -        if ([sys->container 
> respondsToSelector:@selector(removeVoutLayer:)])
> -            [sys->container removeVoutLayer:sys->cgLayer];
> -        else
> -            [sys->cgLayer removeFromSuperlayer];
> -
> -        if ([sys->cgLayer glContext])
> -            CGLReleaseContext([sys->cgLayer glContext]);
> -
> -        [sys->cgLayer release];
> -    }
> +    [sys->videoView vlcClose];

Should vlcCLose be called before vout_display_opengl_Delete(sys->vgl) ?

It seems that you are using sys->vgl from drawInCGLContext() without checking it.
I'm concerted about this scenario:

MainThread: canDrawInCGLContext() returns YES
Vout thread: Close(): sys->vgl is destroyed, _voutDisplay is set to NULL
MainThread: drawInCGLContext(): use destroyed sys->vgl

One way to fix it is to call [sys->videoView vlcClose]; first and check for _voutDisplay in drawInCGLContext().

> +    dispatch_async(dispatch_get_main_queue(), ^{
> +        // Remove vout subview from container
> +        if ([sys->container 
> respondsToSelector:@selector(removeVoutSubview:)]) {
> +            [sys->container removeVoutSubview:sys->videoView];
> +        }
>  
> -    if (sys->container)
> +        [sys->videoView removeFromSuperview];
> +        [sys->videoView release];
>          [sys->container release];
> +        [sys->videoLayer release];
> +    });
>  
>      if (sys->embed)
>          vout_display_DeleteWindow(vd, sys->embed);
>  
> -    if (sys->gl != NULL)
> -    {
> -        if (sys->gl->sys != NULL)
> -        {
> -            assert(((struct gl_sys *)sys->gl->sys)->locked_ctx == NULL);
> -            free(sys->gl->sys);
> -        }
> +    if (sys->gl) {
> +        struct vlc_gl_sys *glsys = sys->gl->sys;
> +
> +        // It should never happen that the context is destroyed and we
> +        // still have a previous context set, as it would mean non-balanced
> +        // calls to MakeCurrent/ReleaseCurrent.
> +        assert(glsys->cgl_prev == NULL);
> +
> +        CGLReleaseContext(glsys->cgl);
>          vlc_object_release(sys->gl);
>      }

Why are you destroying the window and sys->gl after the dispatch_async call ?

Since the async call can be executed before, during and after these 2 destroys. It feel safer to do the async call at the very end.
That way, you have a more reproducible behavior that won't hide weird bugs.

> -
> -    free(sys);
>  }
>  
> -static picture_pool_t *Pool (vout_display_t *vd, unsigned count)
> +static picture_pool_t *Pool(vout_display_t *vd, unsigned count)
>  {
>      vout_display_sys_t *sys = vd->sys;
>  
> -    if (!sys->pool && !OpenglLock(sys->gl)) {
> +    if (!sys->pool && vlc_gl_MakeCurrent(sys->gl) == VLC_SUCCESS)
> +    {
>          sys->pool = vout_display_opengl_GetPool(sys->vgl, count);
> -        OpenglUnlock(sys->gl);
> -        assert(sys->pool);
> +        vlc_gl_ReleaseCurrent(sys->gl);
>      }
>      return sys->pool;
>  }
>  
> -static void PictureRender (vout_display_t *vd, picture_t *pic, 
> subpicture_t *subpicture)
> +static void PictureRender(vout_display_t *vd, picture_t *pic, 
> subpicture_t *subpicture)
>  {
>      vout_display_sys_t *sys = vd->sys;
>  
> -    if (pic == NULL) {
> -        msg_Warn(vd, "invalid pic, skipping frame");
> -        return;
> -    }
> +    if (vlc_gl_MakeCurrent(sys->gl) == VLC_SUCCESS)
> +    {
> +        vout_display_opengl_Prepare(sys->vgl, pic, subpicture);
> +        vlc_gl_ReleaseCurrent(sys->gl);
>  
> -    @synchronized (sys->cgLayer) {
> -        if (!OpenglLock(sys->gl)) {
> -            vout_display_opengl_Prepare(sys->vgl, pic, subpicture);
> -            OpenglUnlock(sys->gl);
> -        }
> +        atomic_store(&sys->is_ready, true);
>      }
>  }
>  
> -static void PictureDisplay (vout_display_t *vd, picture_t *pic, 
> subpicture_t *subpicture)
> +static void PictureDisplay(vout_display_t *vd, picture_t *pic, 
> subpicture_t *subpicture)
>  {
>      vout_display_sys_t *sys = vd->sys;
>  
> -    @synchronized (sys->cgLayer) {
> -        sys->b_frame_available = YES;
> -
> -        /* Calling display on the non-main thread is not officially 
> supported, but
> -         * its suggested at several places and works fine here. Flush 
> is thread-safe
> -         * and makes sure the picture is actually displayed. */
> -        [sys->cgLayer display];
> -        [CATransaction flush];
> -    }
> +    [sys->videoLayer displayFromVout];
>  
>      picture_Release(pic);
> -
>      if (subpicture)
>          subpicture_Delete(subpicture);
>  }
>  
> -static int Control (vout_display_t *vd, int query, va_list ap)
> +static int Control(vout_display_t *vd, int query, va_list ap)
>  {
>      vout_display_sys_t *sys = vd->sys;
>  
> @@ -318,54 +468,31 @@ static int Control (vout_display_t *vd, int 
> query, va_list ap)
>          {
>              const vout_display_cfg_t *cfg;
>  
> -            if (query == VOUT_DISPLAY_CHANGE_SOURCE_ASPECT || query == 
> VOUT_DISPLAY_CHANGE_SOURCE_CROP) {
> +            if (query == VOUT_DISPLAY_CHANGE_SOURCE_ASPECT ||
> +                query == VOUT_DISPLAY_CHANGE_SOURCE_CROP) {
>                  cfg = vd->cfg;
>              } else {
>                  cfg = (const vout_display_cfg_t*)va_arg (ap, const 
> vout_display_cfg_t *);
>              }
>  
> -            /* we always use our current frame here */
> -            vout_display_cfg_t cfg_tmp = *cfg;
> -            [CATransaction lock];
> -            CGRect bounds = [sys->cgLayer visibleRect];
> -            [CATransaction unlock];
> -            cfg_tmp.display.width = bounds.size.width;
> -            cfg_tmp.display.height = bounds.size.height;
> -
> -            /* Reverse vertical alignment as the GL tex are Y inverted 
> */
> -            if (cfg_tmp.align.vertical == VOUT_DISPLAY_ALIGN_TOP)
> -                cfg_tmp.align.vertical = VOUT_DISPLAY_ALIGN_BOTTOM;
> -            else if (cfg_tmp.align.vertical == 
> VOUT_DISPLAY_ALIGN_BOTTOM)
> -                cfg_tmp.align.vertical = VOUT_DISPLAY_ALIGN_TOP;
> -
> -            vout_display_place_t place;
> -            vout_display_PlacePicture (&place, &vd->source, &cfg_tmp, 
> false);
> -            if (OpenglLock(sys->gl))
> -                return VLC_EGENERIC;
> -
> -            vout_display_opengl_SetWindowAspectRatio(sys->vgl, 
> (float)place.width / place.height);
> -            OpenglUnlock(sys->gl);
> -
> -            sys->place = place;
> +            [sys->videoLayer placePictureWithConfig:cfg];

You seem to call placePictureWithConfig() from the vout thread (Control()) and from MT (drawInCGLContext()), sys->place need to be protected then.

>  
> +            // Note!
> +            // No viewport or aspect ratio is set here, as that needs to be set
> +            // when rendering. The viewport is always set to match the layer
> +            // size by the OS right before the OpenGL render callback, so
> +            // setting it here has no effect.
>              return VLC_SUCCESS;
>          }
>  
>          case VOUT_DISPLAY_CHANGE_VIEWPOINT:
>          {
> -            int ret;
> -
> -            if (OpenglLock(sys->gl))
> -                return VLC_EGENERIC;
> -
> -            ret = vout_display_opengl_SetViewpoint(sys->vgl,
> -                &va_arg (ap, const vout_display_cfg_t* )->viewpoint);
> -            OpenglUnlock(sys->gl);
> -            return ret;
> +            return vout_display_opengl_SetViewpoint(sys->vgl,
> +                        &va_arg (ap, const vout_display_cfg_t* )->viewpoint);
>          }
>  
>          case VOUT_DISPLAY_RESET_PICTURES:
> -            vlc_assert_unreachable ();
> +            vlc_assert_unreachable();
>          default:
>              msg_Err (vd, "Unhandled request %d", query);
>              return VLC_EGENERIC;
> @@ -375,188 +502,373 @@ static int Control (vout_display_t *vd, int 
> query, va_list ap)
>  }
>  
>  #pragma mark -
> -#pragma mark OpenGL callbacks
> +#pragma mark VLCVideoLayerView
>  
> -static int OpenglLock (vlc_gl_t *gl)
> + at implementation VLCVideoLayerView
> +
> +- (instancetype)initWithVoutDisplay:(vout_display_t *)vd
>  {
> -    struct gl_sys *sys = gl->sys;
> -    assert(sys->locked_ctx == NULL);
> +    self = [super init];
> +    if (self) {
> +        _vlc_vd = vd;
>  
> -    CGLContextObj ctx = [sys->cgLayer glContext];
> -    if(!ctx) {
> -        return 1;
> +        self.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
> +        self.wantsLayer = YES;
>      }
> +    return self;
> +}
>  
> -    CGLError err = CGLLockContext(ctx);
> -    if (kCGLNoError == err) {
> -        sys->locked_ctx = ctx;
> -        CGLSetCurrentContext(ctx);
> -        return 0;
> +/**
> +* Invalidates VLC objects (notably _vlc_vd)
> +* This method must be called in VLCs module Close (or indirectly by the View)
> +* to ensure all critical VLC resources that might be gone when the module is
> +* closed are properly NULLed. This is necessary as dealloc is only called later
> +* as it has to be done async on the main thread, because NSView must be
> +* dealloc'ed on the main thread and the view own the layer, so the layer
> +* will stay valid until the view is gone, and might still use _vlc_vd
> +* even after the VLC module is gone and the resources would be invalid.
> +*/
> +- (void)vlcClose
> +{
> +    @synchronized (self) {
> +        [(VLCCAOpenGLLayer *)self.layer vlcClose];
> +        _vlc_vd = NULL;
>      }
> -    return 1;
>  }
>  
> -static void OpenglUnlock (vlc_gl_t *gl)
> +- (void)viewWillStartLiveResize
>  {
> -    struct gl_sys *sys = gl->sys;
> -    CGLUnlockContext(sys->locked_ctx);
> -    sys->locked_ctx = NULL;
> +    [(VLCCAOpenGLLayer *)self.layer setAsynchronous:YES];
>  }
>  
> -static void OpenglSwap (vlc_gl_t *gl)
> +- (void)viewDidEndLiveResize
>  {
> -    glFlush();
> +    [(VLCCAOpenGLLayer *)self.layer setAsynchronous:NO];
>  }
>  
> -static void *OurGetProcAddress (vlc_gl_t *gl, const char *name)
> +- (CALayer *)makeBackingLayer
>  {
> -    VLC_UNUSED(gl);
> +    @synchronized(self) {
> +        NSAssert(_vlc_vd != NULL, @"Cannot create backing layer 
> without vout display!");
>  
> -    return dlsym(RTLD_DEFAULT, name);
> +        VLCCAOpenGLLayer *layer = [[VLCCAOpenGLLayer alloc] 
> initWithVoutDisplay:_vlc_vd];
> +        layer.delegate = self;
> +        return [layer autorelease];
> +    }
>  }
>  
> +/* Layer delegate method that ensures the layer always get the
> + * correct contentScale based on whether the view is on a HiDPI
> + * display or not, and when it is moved between displays.
> + */
> +- (BOOL)layer:(CALayer *)layer
> +shouldInheritContentsScale:(CGFloat)newScale
> +   fromWindow:(NSWindow *)window
> +{
> +    return YES;
> +}
> +
> +/*
> + * General properties
> + */
> +
> +- (BOOL)isOpaque
> +{
> +    return YES;
> +}
> +
> +- (BOOL)acceptsFirstResponder
> +{
> +    return YES;
> +}
> +
> +- (BOOL)mouseDownCanMoveWindow
> +{
> +    return NO;
> +}
> +
> +
> +#pragma mark View mouse events
> +
> +/* Left mouse button down */
> +- (void)mouseDown:(NSEvent *)event
> +{
> +    @synchronized(self) {
> +        if (!_vlc_vd) {
> +            [super mouseDown:event];
> +            return;
> +        }
> +
> +        if (event.type == NSLeftMouseDown &&
> +            !(event.modifierFlags & NSControlKeyMask) &&
> +            event.clickCount == 1) {
> +            vout_display_SendEventMousePressed(_vlc_vd, 
> MOUSE_BUTTON_LEFT);
> +        } else {
> +            [super mouseDown:event];
> +        }
> +    }
> +}
> +
> +/* Left mouse button up */
> +- (void)mouseUp:(NSEvent *)event
> +{
> +    @synchronized(self) {
> +        if (!_vlc_vd) {
> +            [super mouseUp:event];
> +            return;
> +        }
> +
> +        if (event.type == NSLeftMouseUp) {
> +            vout_display_SendEventMouseReleased(_vlc_vd, 
> MOUSE_BUTTON_LEFT);
> +        }
> +    }
> +}
> +
> +/* Middle mouse button down */
> +- (void)otherMouseDown:(NSEvent *)event
> +{
> +    @synchronized(self) {
> +        if (_vlc_vd)
> +            vout_display_SendEventMousePressed(_vlc_vd, 
> MOUSE_BUTTON_CENTER);
> +        else
> +            [super otherMouseDown:event];
> +    }
> +}
> +
> +/* Middle mouse button up */
> +- (void)otherMouseUp:(NSEvent *)event
> +{
> +    @synchronized(self) {
> +        if (_vlc_vd)
> +            vout_display_SendEventMouseReleased(_vlc_vd, 
> MOUSE_BUTTON_CENTER);
> +        else
> +            [super otherMouseUp:event];
> +    }
> +}
> +
> +/* Mouse moved */
> +- (void)mouseMoved:(NSEvent *)event
> +{
> +    @synchronized(self) {
> +        if (!_vlc_vd) {
> +            [super mouseMoved:event];
> +            return;
> +        }
> +
> +        vout_display_sys_t *sys = _vlc_vd->sys;
> +
> +        // Convert window-coordinate point to view space
> +        NSPoint pointInView = [self 
> convertPoint:event.locationInWindow fromView:nil];
> +
> +        // Convert to pixels
> +        NSPoint pointInBacking = [self 
> convertPointToBacking:pointInView];
> +
> +        vout_display_SendMouseMovedDisplayCoordinates(_vlc_vd, 
> ORIENT_VFLIPPED,
> +                                                      
> pointInBacking.x, pointInBacking.y,
> +                                                      &sys->place);
> +    }
> +}
> +
> +/* Mouse moved while clicked */
> +- (void)mouseDragged:(NSEvent *)event
> +{
> +    [self mouseMoved:event];
> +}
> +
> +/* Mouse moved while center-clicked */
> +- (void)otherMouseDragged:(NSEvent *)event
> +{
> +    [self mouseMoved:event];
> +}
> +
> +/* Mouse moved while right-clicked */
> +- (void)rightMouseDragged:(NSEvent *)event
> +{
> +    [self mouseMoved:event];
> +}
> +
> + at end
> +
>  #pragma mark -
> -#pragma mark CA layer
> +#pragma mark VLCCAOpenGLLayer
>  
> -/*****************************************************************************
> - * @implementation VLCCAOpenGLLayer
> - *****************************************************************************/
>  @implementation VLCCAOpenGLLayer
>  
> -- (id)init {
> -
> +- (instancetype)initWithVoutDisplay:(vout_display_t *)vd
> +{
>      self = [super init];
>      if (self) {
> +        _displayLock = [[NSLock alloc] init];
> +        _voutDisplay = vd;
> +
> +        struct vlc_gl_sys *glsys = vd->sys->gl->sys;
> +        _glContext = CGLRetainContext(glsys->cgl);
> +
>          [CATransaction lock];
>          self.needsDisplayOnBoundsChange = YES;
>          self.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
>          self.asynchronous = NO;
> +        self.opaque = 1.0;
> +        self.hidden = NO;
>          [CATransaction unlock];
>      }
>  
>      return self;
>  }
>  
> -- (void)setVoutDisplay:(vout_display_t *)aVd
> +/**
> + * Invalidates VLC objects (notably _voutDisplay)
> + * This method must be called in VLCs module Close (or indirectly by the View).
> + */
> +- (void)vlcClose
>  {
> -    _voutDisplay = aVd;
> +    @synchronized (self) {
> +        _voutDisplay = NULL;
> +    }
>  }
>  
> -- (void)resizeWithOldSuperlayerSize:(CGSize)size
> +- (void)dealloc
>  {
> -    [super resizeWithOldSuperlayerSize: size];
> -
> -    CGSize boundsSize = self.visibleRect.size;
> -
> -    if (_voutDisplay)
> -        vout_display_SendEventDisplaySize(_voutDisplay, 
> boundsSize.width, boundsSize.height);
> +    CGLReleaseContext(_glContext);
> +    [_displayLock release];
> +    [super dealloc];
>  }
>  
> -- (BOOL)canDrawInCGLContext:(CGLContextObj)glContext 
> pixelFormat:(CGLPixelFormatObj)pixelFormat 
> forLayerTime:(CFTimeInterval)timeInterval displayTime:(const 
> CVTimeStamp *)timeStamp
> +- (void)placePictureWithConfig:(const vout_display_cfg_t *)cfg
>  {
> -    /* Only draw the frame if we have a frame that was previously 
> rendered */
> -    if (!_voutDisplay)
> -        return false;
> +    vout_display_sys_t *sys = _voutDisplay->sys;
> +    vout_display_cfg_t tmp_cfg = *cfg;
> +
> +    // Reverse vertical alignment as the GL tex are Y inverted
> +    if (tmp_cfg.align.vertical == VOUT_DISPLAY_ALIGN_TOP)
> +        tmp_cfg.align.vertical = VOUT_DISPLAY_ALIGN_BOTTOM;
> +    else if (tmp_cfg.align.vertical == VOUT_DISPLAY_ALIGN_BOTTOM)
> +        tmp_cfg.align.vertical = VOUT_DISPLAY_ALIGN_TOP;
>  
> -    return _voutDisplay->sys->b_frame_available;
> +    vout_display_PlacePicture(&sys->place, &_voutDisplay->source, 
> &tmp_cfg, false);
>  }
>  
> -- (void)drawInCGLContext:(CGLContextObj)glContext 
> pixelFormat:(CGLPixelFormatObj)pixelFormat 
> forLayerTime:(CFTimeInterval)timeInterval displayTime:(const 
> CVTimeStamp *)timeStamp
> +- (void)resizeWithOldSuperlayerSize:(CGSize)size
>  {
> -    if (!_voutDisplay)
> +    [super resizeWithOldSuperlayerSize:size];
> +
> +    if (self.asynchronous) {
> +        // During live resize, the size is updated in the
> +        // OpenGL draw callback, to ensure atomic size changes
> +        // that are in sync with the real layer size.
> +        // This bypasses the core but is needed for resizing
> +        // without glitches or lags.
>          return;
> -    vout_display_sys_t *sys = _voutDisplay->sys;
> +    }
>  
> -    if (!sys->vgl)
> -        return;
> +    [self reportCurrentLayerSize];
> +}
>  
> -    CGRect bounds = [self visibleRect];
> +- (void)reportCurrentLayerSize
> +{
> +    CGSize newSize = self.visibleRect.size;
> +    CGFloat scale = self.contentsScale;
>  
> -    // x / y are top left corner, but we need the lower left one
> -    vout_display_opengl_Viewport(sys->vgl, sys->place.x,
> -                                 bounds.size.height - (sys->place.y + 
> sys->place.height),
> -                                 sys->place.width, sys->place.height);
> +    // Calculate pixel values
> +    newSize.width *= scale;
> +    newSize.height *= scale;
>  
> -    // flush is also done by this method, no need to call super
> -    vout_display_opengl_Display (sys->vgl, &_voutDisplay->source);
> -    sys->b_frame_available = NO;
> -}
> +    @synchronized(self) {
> +        if (!_voutDisplay)
> +            return;
>  
> --(CGLPixelFormatObj)copyCGLPixelFormatForDisplayMask:(uint32_t)mask
> -{
> -    // The default is fine for this demonstration.
> -    return [super copyCGLPixelFormatForDisplayMask:mask];
> +        vout_display_SendEventDisplaySize(_voutDisplay,
> +                                          newSize.width, newSize.height);
> +    }
>  }
>  
> -- (CGLContextObj)copyCGLContextForPixelFormat:(CGLPixelFormatObj)pixelFormat
> +- (void)display
>  {
> -    // Only one opengl context is allowed for the module lifetime
> -    if(_glContext) {
> -        msg_Dbg(_voutDisplay, "Return existing context: %p", _glContext);
> -        return _glContext;
> -    }
> +    [_displayLock lock];
>  
> -    CGLContextObj context = [super copyCGLContextForPixelFormat:pixelFormat];
> +    [super display];
> +    [CATransaction flush];
>  
> -    // Swap buffers only during the vertical retrace of the monitor.
> -    //http://developer.apple.com/documentation/GraphicsImaging/
> -    //Conceptual/OpenGL/chap5/chapter_5_section_44.html /
> -
> -    GLint params = 1;
> -    CGLSetParameter( CGLGetCurrentContext(), kCGLCPSwapInterval,
> -                     &params );
> +    [_displayLock unlock];
> +}
>  
> -    @synchronized (self) {
> -        _glContext = context;
> +- (void)displayFromVout
> +{
> +    if (self.asynchronous) {
> +        // During live resizing we do not take updates
> +        // from the vout, as those would interfere with
> +        // the rendering currently happening on the main
> +        // thread for the resize. Rendering anyway happens
> +        // triggered by the OS every display refresh, so
> +        // forcing an update here would be useless anyway.
> +        return;
>      }
>  
> -    return context;
> +    [self display];
>  }
>  
> -- (void)releaseCGLContext:(CGLContextObj)glContext
> +- (BOOL)canDrawInCGLContext:(CGLContextObj)glContext
> +                pixelFormat:(CGLPixelFormatObj)pixelFormat
> +               forLayerTime:(CFTimeInterval)timeInterval
> +                displayTime:(const CVTimeStamp *)timeStamp
>  {
> -    // do not release anything here, we do that when closing the module
> +    @synchronized(self) {
> +        if (!_voutDisplay)
> +            return NO;
> +
> +        return (atomic_load(&_voutDisplay->sys->is_ready));
> +    }
>  }
>  
> -- (void)mouseButtonDown:(int)buttonNumber
> +- (void)drawInCGLContext:(CGLContextObj)glContext
> +             pixelFormat:(CGLPixelFormatObj)pixelFormat
> +            forLayerTime:(CFTimeInterval)timeInterval
> +             displayTime:(const CVTimeStamp *)timeStamp
>  {
> -    @synchronized (self) {
> -        if (_voutDisplay) {
> -            if (buttonNumber == 0)
> -                vout_display_SendEventMousePressed (_voutDisplay, 
> MOUSE_BUTTON_LEFT);
> -            else if (buttonNumber == 1)
> -                vout_display_SendEventMousePressed (_voutDisplay, 
> MOUSE_BUTTON_RIGHT);
> -            else
> -                vout_display_SendEventMousePressed (_voutDisplay, 
> MOUSE_BUTTON_CENTER);
> +    vout_display_sys_t *sys = _voutDisplay->sys;
> +
> +    if (vlc_gl_MakeCurrent(sys->gl))
> +        return;
> +
> +    if (self.asynchronous) {
> +        GLint dims[4] = { 0, 0, 0, 0 };
> +        glGetIntegerv(GL_VIEWPORT, dims);
> +        NSSize newSize = NSMakeSize(dims[2], dims[3]);
> +
> +        if (NSEqualSizes(newSize, NSZeroSize)) {
> +            newSize = self.bounds.size;
> +            CGFloat scale = self.contentsScale;
> +            newSize.width *= scale;
> +            newSize.height *= scale;
>          }
> +
> +        vout_display_cfg_t cfg = *_voutDisplay->cfg;
> +
> +        cfg.display.width = newSize.width;
> +        cfg.display.height = newSize.height;
> +
> +        [self placePictureWithConfig:&cfg];
>      }
> +
> +    // Ensure viewport and aspect ratio is correct
> +    vout_display_opengl_Viewport(sys->vgl, sys->place.x, sys->place.y,
> +                                 sys->place.width, sys->place.height);
> +    vout_display_opengl_SetWindowAspectRatio(sys->vgl, 
> (float)sys->place.width / sys->place.height);
> +
> +    vout_display_opengl_Display(sys->vgl, &_voutDisplay->source);
> +    vlc_gl_ReleaseCurrent(sys->gl);
>  }
>  
> -- (void)mouseButtonUp:(int)buttonNumber
> +- (CGLPixelFormatObj)copyCGLPixelFormatForDisplayMask:(uint32_t)mask
>  {
> -    @synchronized (self) {
> -        if (_voutDisplay) {
> -            if (buttonNumber == 0)
> -                vout_display_SendEventMouseReleased (_voutDisplay, 
> MOUSE_BUTTON_LEFT);
> -            else if (buttonNumber == 1)
> -                vout_display_SendEventMouseReleased (_voutDisplay, 
> MOUSE_BUTTON_RIGHT);
> -            else
> -                vout_display_SendEventMouseReleased (_voutDisplay, 
> MOUSE_BUTTON_CENTER);
> -        }
> -    }
> +    CGLPixelFormatObj fmt = CGLGetPixelFormat(_glContext);
> +    
> +    return (fmt) ? CGLRetainPixelFormat(fmt) : NULL;
>  }
>  
> -- (void)mouseMovedToX:(double)xValue Y:(double)yValue
> +- 
> (CGLContextObj)copyCGLContextForPixelFormat:(CGLPixelFormatObj)pixelFormat
>  {
> -    @synchronized (self) {
> -        if (_voutDisplay) {
> -            vout_display_SendMouseMovedDisplayCoordinates 
> (_voutDisplay,
> -                                                           
> ORIENT_NORMAL,
> -                                                           xValue,
> -                                                           yValue,
> -                                                           
> &_voutDisplay->sys->place);
> -        }
> -    }
> +    return CGLRetainContext(_glContext);
>  }
>  
>  @end
> -- 
> 2.21.1 (Apple Git-122.3)
> 
> 
> _______________________________________________
> vlc-devel mailing list
> To unsubscribe or modify your subscription options:
> https://mailman.videolan.org/listinfo/vlc-devel



More information about the vlc-devel mailing list