[vlc-devel] [PATCH 4/4] [3.x] vout/caopengllayer: rewrite most of the module
Marvin Scholz
epirat07 at gmail.com
Thu Feb 20 16:13:35 CET 2020
Thanks for the review, will send a new patchset
that should fix all your remarks.
On 20 Feb 2020, at 13:57, Thomas Guillem wrote:
> 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,
>> - ¶ms );
>> + [_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
>
> _______________________________________________
> 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