<html><head></head><body>Hi,<br><br>First, we need to decide how higher video than display frame rate should be handled in general. Current display plugins seem to assume that the core will drop frames automatically and aggressively, but that's probably not quite what actually happens.<br><br><div class="gmail_quote">Le 20 janvier 2020 09:18:25 GMT+02:00, Alexandre Janniaux <ajanni@videolabs.io> a écrit :<blockquote class="gmail_quote" style="margin: 0pt 0pt 0pt 0.8ex; border-left: 1px solid rgb(204, 204, 204); padding-left: 1ex;">
<pre class="k9mail">Hi,<br><br>Upping this answer, should the OpenGL display execute the<br>callbacks in its own thread instead then?<br><br>Regards,<br>--<br>Alexandre Janniaux<br>Videolabs<br><br>On Wed, Dec 11, 2019 at 01:12:46PM +0200, Rémi Denis-Courmont wrote:<br><blockquote class="gmail_quote" style="margin: 0pt 0pt 1ex 0.8ex; border-left: 1px solid #729fcf; padding-left: 1ex;"> If you want thread barriers, use thread barriers, not semaphore. That's super inefficient, ironically for a performance-focused patch.<br><br> It's also highly questionable idea anyway. This looks like a bug in the GL display, as this can't work anyway with high frame rate. Plus making multiple threads contend for internal resources of the display backend is likely to make performance worse rather than better.<br><br> Le 11 décembre 2019 12:32:10 GMT+02:00, Alexandre Janniaux <ajanni@videolabs.io> a écrit :<br><blockquote class="gmail_quote" style="margin: 0pt 0pt 1ex 0.8ex; border-left: 1px solid #ad7fa8; padding-left: 1ex;">Display modules are expected to present the image to the user from the<br>display callback. Some display can achieve it asynchronously but others<br>having VSYNC mechanisms like OpenGL are forcing the display to sleep<br>until the next VSYNC. In a general way, similar display with a limited<br>number of render buffer will wait until a new buffer gets released and<br>available for processing.<br><br>While it is correct for one display, the behaviour will make splitter<br>managed display miss the screen frame deadline and generate stuttering<br>with more than one output. It stems from display operations being<br>executed sequentially for each output.<br><br>Instead, parallelize and synchronize each display's prepare+display so<br>that display taking VSYNC into account don't accumulate sleeping time.<hr> modules/video_output/splitter.c | 132 +++++++++++++++++++++++++++++---<br> 1 file changed, 122 insertions(+), 10 deletions(-)<br><br>diff --git a/modules/video_output/splitter.c<br>b/modules/video_output/splitter.c<br>index 8d6532ceb4..195bf2c50c 100644<br>--- a/modules/video_output/splitter.c<br>+++ b/modules/video_output/splitter.c<br>@@ -34,23 +34,77 @@<br> #include <vlc_codec.h><br> #include <vlc_vout_display.h><br> #include <vlc_video_splitter.h><br>+#include <vlc_threads.h><br><br> struct vlc_vidsplit_part {<br>     vout_window_t *window;<br>     vout_display_t *display;<br>+    picture_t *picture;<br>+    vlc_tick_t date;<br>     vlc_sem_t lock;<br>     unsigned width;<br>     unsigned height;<br> };<br><br>+struct vlc_vidsplit_thread<br>+{<br>+    vout_display_t *vd;<br>+    struct vlc_vidsplit_part *part;<br>+    vlc_thread_t thread;<br>+};<br>+<br> struct vout_display_sys_t {<br>     video_splitter_t splitter;<br>     vlc_mutex_t lock;<br><br>     picture_t **pictures;<br>     struct vlc_vidsplit_part *parts;<br>+    struct vlc_vidsplit_thread *threads;<br>+<br>+    vlc_sem_t prepare_wait;<br>+    vlc_sem_t prepare_done;<br>+    vlc_sem_t display_wait;<br>+    vlc_sem_t display_done;<br> };<br><br>+static void* vlc_vidsplit_ThreadDisplay(void *data)<br>+{<br>+    struct vlc_vidsplit_thread *vd_thread = data;<br>+    vout_display_t *vd = vd_thread->vd;<br>+    vout_display_sys_t *sys = vd->sys;<br>+    struct vlc_vidsplit_part *part = vd_thread->part;<br>+<br>+    for (;;)<br>+    {<br>+        vlc_sem_wait(&sys->prepare_wait);<br>+<br>+        /* We might need to stop the prepare just after the barrier<br>+         * and we were not supposed to take the semaphore token. */<br>+        if (part->display == NULL)<br>+        {<br>+            /* If we don't have a display, we can early exist. */<br>+            vlc_sem_post(&sys->prepare_done);<br>+            continue;<br>+        }<br>+<br>+        part->picture = vout_display_Prepare(part->display,<br>+                                             part->picture, NULL,<br>+                                             part->date);<br>+<br>+        /* notify readiness */<br>+        vlc_sem_post(&sys->prepare_done);<br>+<br>+        vlc_sem_wait(&sys->display_wait);<br>+        if (part->picture)<br>+            vout_display_Display(part->display, part->picture);<br>+<br>+        /* notify that image has been displayed */<br>+        vlc_sem_post(&sys->display_done);<br>+    }<br>+<br>+    return NULL;<br>+}<br>+<br> static void vlc_vidsplit_Prepare(vout_display_t *vd, picture_t *pic,<br>                                 subpicture_t *subpic, vlc_tick_t date)<br> {<br>@@ -69,26 +123,61 @@ static void vlc_vidsplit_Prepare(vout_display_t<br>*vd, picture_t *pic,<br>     }<br>     vlc_mutex_unlock(&sys->lock);<br><br>+    /* After here, part are locked until all vout display has finished<br>+     * displaying the picture. See vlc_vidsplit_Display. */<br>+    for (int i = 0; i < sys->splitter.i_output; i++)<br>+        vlc_sem_wait(&sys->parts[i].lock);<br>+<br>+    /* Now that all display are waiting, prepare their state and<br>remove unused<br>+     * pictures from killed display. */<br>     for (int i = 0; i < sys->splitter.i_output; i++) {<br>         struct vlc_vidsplit_part *part = &sys->parts[i];<br>-<br>-        vlc_sem_wait(&part->lock);<br>-        sys->pictures[i] = vout_display_Prepare(part->display,<br>-                                                sys->pictures[i],<br>NULL, date);<br>+        part->picture = sys->pictures[i];<br>+        part->date = date;<br>+<br>+        if (part->display == NULL)<br>+        {<br>+            /* The display died, cleanup. */<br>+            part->picture = NULL;<br>+            if (sys->pictures[i] != NULL)<br>+            {<br>+                picture_Release(sys->pictures[i]);<br>+                sys->pictures[i] = NULL;<br>+            }<br>+        }<br>     }<br>+<br>+    /* Start preparing each vout display.  */<br>+    for (int i = 0; i < sys->splitter.i_output; ++i)<br>+        if (sys->parts[i].display != NULL)<br>+            vlc_sem_post(&sys->prepare_wait);<br>+<br>+    /* Wait for each vout display to have finished. */<br>+    for (int i = 0; i < sys->splitter.i_output; ++i)<br>+        if (sys->parts[i].display != NULL)<br>+            vlc_sem_wait(&sys->prepare_done);<br>+<br>+    /* The time to prepare all vout display is the preparation time<br>for the<br>+     * slower of all display, which matches the core requirement. */<br> }<br><br>static void vlc_vidsplit_Display(vout_display_t *vd, picture_t<br>*picture)<br> {<br>     vout_display_sys_t *sys = vd->sys;<br><br>-    for (int i = 0; i < sys->splitter.i_output; i++) {<br>-        struct vlc_vidsplit_part *part = &sys->parts[i];<br>+    /* Request each output to display the picture. */<br>+    for (int i = 0; i < sys->splitter.i_output; i++)<br>+        if (sys->parts[i].display != NULL)<br>+            vlc_sem_post(&sys->display_wait);<br><br>-        if (sys->pictures[i] != NULL)<br>-            vout_display_Display(part->display, sys->pictures[i]);<br>-        vlc_sem_post(&part->lock);<br>-    }<br>+    /* Wait until every output has displayed a picture. */<br>+    for (int i = 0; i < sys->splitter.i_output; i++)<br>+        if (sys->parts[i].display != NULL)<br>+            vlc_sem_wait(&sys->display_done);<br>+<br>+    /* Release parts lock, we can't read sys->parts[i] after that. */<br>+    for (int i = 0; i < sys->splitter.i_output; i++)<br>+        vlc_sem_post(&sys->parts[i].lock);<br><br>     (void) picture;<br> }<br>@@ -115,6 +204,7 @@ static void vlc_vidsplit_Close(vout_display_t *vd)<br><br>     for (int i = 0; i < n; i++) {<br>         struct vlc_vidsplit_part *part = &sys->parts[i];<br>+        struct vlc_vidsplit_thread *thread = &sys->threads[i];<br>         vout_display_t *display;<br><br>         vlc_sem_wait(&part->lock);<br>@@ -122,6 +212,9 @@ static void vlc_vidsplit_Close(vout_display_t *vd)<br>         part->display = NULL;<br>         vlc_sem_post(&part->lock);<br><br>+        vlc_cancel(thread->thread);<br>+        vlc_join(thread->thread, NULL);<br>+<br>         if (display != NULL)<br>             vout_display_Delete(display);<br><br>@@ -259,12 +352,19 @@ static int vlc_vidsplit_Open(vout_display_t *vd,<br>                                         * sizeof (*sys->pictures));<br>     sys->parts = vlc_obj_malloc(obj,<br>                            splitter->i_output * sizeof (*sys->parts));<br>+    sys->threads = vlc_obj_malloc(obj,<br>+                                  splitter->i_output * sizeof<br>(*sys->threads));<br>     if (unlikely(sys->pictures == NULL || sys->parts == NULL)) {<br>         splitter->i_output = 0;<br>         vlc_vidsplit_Close(vd);<br>         return VLC_ENOMEM;<br>     }<br><br>+    vlc_sem_init(&sys->prepare_wait, 0);<br>+    vlc_sem_init(&sys->prepare_done, 0);<br>+    vlc_sem_init(&sys->display_wait, 0);<br>+    vlc_sem_init(&sys->display_done, 0);<br>+<br>     for (int i = 0; i < splitter->i_output; i++) {<br>        const video_splitter_output_t *output = &splitter->p_output[i];<br>         vout_display_cfg_t vdcfg = {<br>@@ -305,6 +405,18 @@ static int vlc_vidsplit_Open(vout_display_t *vd,<br>         part->display = display;<br>         vout_display_SetSize(display, part->width, part->height);<br>         vlc_sem_post(&part->lock);<br>+<br>+        sys->threads[i].vd = vd;<br>+        sys->threads[i].part = part;<br>+<br>+        int ret_clone = vlc_clone(&sys->threads[i].thread,<br>+                                  vlc_vidsplit_ThreadDisplay,<br>+                                  &sys->threads[i],<br>+                                  VLC_THREAD_PRIORITY_VIDEO);<br>+<br>+        if (ret_clone != VLC_SUCCESS) {<br>+            /* TODO: Abort and cleanup */<br>+        }<br>     }<br><br>     vd->prepare = vlc_vidsplit_Prepare;<br>--<br>2.24.1<hr>vlc-devel mailing list<br>To unsubscribe or modify your subscription options:<br><a href="https://mailman.videolan.org/listinfo/vlc-devel">https://mailman.videolan.org/listinfo/vlc-devel</a><br></blockquote>--<br>Envoyé de mon appareil Android avec Courriel K-9 Mail. Veuillez excuser ma brièveté.<br></blockquote><br><blockquote class="gmail_quote" style="margin: 0pt 0pt 1ex 0.8ex; border-left: 1px solid #729fcf; padding-left: 1ex;"><hr>vlc-devel mailing list<br>To unsubscribe or modify your subscription options:<br><a href="https://mailman.videolan.org/listinfo/vlc-devel">https://mailman.videolan.org/listinfo/vlc-devel</a><br></blockquote><hr>vlc-devel mailing list<br>To unsubscribe or modify your subscription options:<br><a href="https://mailman.videolan.org/listinfo/vlc-devel">https://mailman.videolan.org/listinfo/vlc-devel</a></pre></blockquote></div><br>-- <br>Envoyé de mon appareil Android avec Courriel K-9 Mail. Veuillez excuser ma brièveté.</body></html>