wl/shm: initial unaccelerated RGB video output
authorRémi Denis-Courmont <remi@remlab.net>
Sun, 31 Aug 2014 11:40:09 +0000 (14:40 +0300)
committerRémi Denis-Courmont <remi@remlab.net>
Sun, 31 Aug 2014 20:18:10 +0000 (23:18 +0300)
modules/MODULES_LIST
modules/video_output/Modules.am
modules/video_output/wl/shm.c [new file with mode: 0644]
po/POTFILES.in

index 5440b0e..06a2303 100644 (file)
@@ -420,6 +420,7 @@ $Id$
  * wingdi: WIN 32 / WIN CE GDI video output
  * winstore: Windows Store App audio output
  * wl_shell_surface: Wayland shell surface window provider
+ * wl_shm: Wayland shared memory video output
  * wma_fixed: wma decoder using integer decoder from Rockbox
  * x262: MPEG-2 video encoder using x262
  * x26410b: H264 10 bit video encoder using x264
index 2031a3b..b610edc 100644 (file)
@@ -129,6 +129,13 @@ endif
 
 
 ### Wayland ###
+libwl_shm_plugin_la_SOURCES = wl/shm.c
+libwl_shm_plugin_la_CFLAGS = $(WAYLAND_CLIENT_CFLAGS)
+libwl_shm_plugin_la_LIBADD = $(WAYLAND_CLIENT_LIBS)
+if HAVE_WAYLAND
+vout_LTLIBRARIES += libwl_shm_plugin.la
+endif
+
 libwl_shell_surface_plugin_la_SOURCES = wl/shell_surface.c
 libwl_shell_surface_plugin_la_CFLAGS = $(WAYLAND_CLIENT_CFLAGS)
 libwl_shell_surface_plugin_la_LIBADD = $(WAYLAND_CLIENT_LIBS) $(LIBPTHREAD)
diff --git a/modules/video_output/wl/shm.c b/modules/video_output/wl/shm.c
new file mode 100644 (file)
index 0000000..c17ecd1
--- /dev/null
@@ -0,0 +1,445 @@
+/**
+ * @file shm.c
+ * @brief Wayland shared memory video output module for VLC media player
+ */
+/*****************************************************************************
+ * Copyright © 2014 Rémi Denis-Courmont
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include <wayland-client.h>
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_vout_display.h>
+#include <vlc_picture_pool.h>
+
+#define MAX_PICTURES 4
+
+struct vout_display_sys_t
+{
+    vout_window_t *embed; /* VLC window */
+    struct wl_shm *shm;
+    struct wl_shm_pool *shm_pool;
+
+    picture_pool_t *pool; /* picture pool */
+    unsigned char *base;
+    size_t length;
+    int fd;
+
+    int x;
+    int y;
+};
+
+static void PictureDestroy(picture_t *pic)
+{
+    struct wl_buffer *buf = (struct wl_buffer *)pic->p_sys;
+
+    wl_buffer_destroy(buf);
+}
+
+static void buffer_release_cb(void *data, struct wl_buffer *buffer)
+{
+    picture_t *pic = data;
+
+    picture_Release(pic);
+    (void) buffer;
+}
+
+static const struct wl_buffer_listener buffer_cbs =
+{
+    buffer_release_cb,
+};
+
+static picture_pool_t *Pool(vout_display_t *vd, unsigned req)
+{
+    vout_display_sys_t *sys = vd->sys;
+
+    if (sys->pool != NULL)
+        return sys->pool;
+
+    if (req > MAX_PICTURES)
+        req = MAX_PICTURES;
+
+    vout_display_place_t place;
+
+    vout_display_PlacePicture(&place, &vd->source, vd->cfg, false);
+
+    /* We need one extra line to cover for horizontal crop offset */
+    unsigned stride = 4 * ((vd->fmt.i_width + 31) & ~31);
+    unsigned lines = (vd->fmt.i_height + 31 + 1) & ~31;
+    const long pagemask = sysconf(_SC_PAGE_SIZE) - 1;
+    size_t picsize = ((stride * lines) + pagemask) & ~pagemask;
+
+    sys->length = picsize * req;
+
+    if (ftruncate(sys->fd, sys->length))
+    {
+        msg_Err(vd, "cannot allocate buffers: %s", vlc_strerror_c(errno));
+        return NULL;
+    }
+
+    sys->base = mmap(NULL, sys->length, PROT_READ|PROT_WRITE, MAP_SHARED,
+                     sys->fd, 0);
+    if (sys->base == MAP_FAILED)
+    {
+        msg_Err(vd, "cannot map buffers: %s", vlc_strerror_c(errno));
+        goto error;
+    }
+
+    sys->shm_pool = wl_shm_create_pool(sys->shm, sys->fd, sys->length);
+    if (sys->shm_pool == NULL)
+        goto error;
+
+    picture_t *pics[MAX_PICTURES];
+    picture_resource_t res = {
+        .pf_destroy = PictureDestroy,
+        .p = {
+            [0] = {
+                .i_lines = lines,
+                .i_pitch = stride,
+            },
+        },
+    };
+    size_t offset = 4 * vd->fmt.i_x_offset + stride * vd->fmt.i_y_offset;
+    unsigned width = vd->fmt.i_visible_width;
+    unsigned height = vd->fmt.i_visible_height;
+    unsigned count = 0;
+
+    while (count < req)
+    {
+        struct wl_buffer *buf;
+
+        buf = wl_shm_pool_create_buffer(sys->shm_pool, offset, width, height,
+                                        stride, WL_SHM_FORMAT_XRGB8888);
+        if (buf == NULL)
+            break;
+
+        res.p_sys = (picture_sys_t *)buf;
+        res.p[0].p_pixels = sys->base + offset;
+        offset += picsize;
+
+        picture_t *pic = picture_NewFromResource(&vd->fmt, &res);
+        if (unlikely(pic == NULL))
+        {
+            wl_buffer_destroy(buf);
+            break;
+        }
+
+        wl_buffer_add_listener(buf, &buffer_cbs, pic);
+        pics[count++] = pic;
+    }
+
+    if (count == 0)
+    {
+        wl_shm_pool_destroy(sys->shm_pool);
+        munmap(sys->base, sys->length);
+        goto error;
+    }
+
+    wl_display_flush(sys->embed->display.wl);
+
+    sys->pool = picture_pool_New (count, pics);
+    if (unlikely(sys->pool == NULL))
+    {
+        while (count > 0)
+            picture_Release(pics[--count]);
+        wl_shm_pool_destroy(sys->shm_pool);
+        goto error;
+    }
+    return sys->pool;
+
+error:
+    if (sys->base != MAP_FAILED)
+        munmap(sys->base, sys->length);
+    ftruncate(sys->fd, 0); /* "free" memory */
+    return NULL;
+}
+
+static void Prepare(vout_display_t *vd, picture_t *pic, subpicture_t *subpic)
+{
+    vout_display_sys_t *sys = vd->sys;
+    struct wl_display *display = sys->embed->display.wl;
+    struct wl_surface *surface = sys->embed->handle.wl;
+    struct wl_buffer *buf = (struct wl_buffer *)pic->p_sys;
+
+    wl_surface_attach(surface, buf, sys->x, sys->y);
+    wl_surface_damage(surface, 0, 0,
+                      vd->fmt.i_visible_width, vd->fmt.i_visible_height);
+    wl_display_flush(display);
+
+    sys->x = 0;
+    sys->y = 0;
+
+    (void) subpic;
+}
+
+static void Display(vout_display_t *vd, picture_t *pic, subpicture_t *subpic)
+{
+    vout_display_sys_t *sys = vd->sys;
+    struct wl_display *display = sys->embed->display.wl;
+    struct wl_surface *surface = sys->embed->handle.wl;
+
+    wl_surface_commit(surface);
+    // FIXME: deadlocks here
+    wl_display_roundtrip(display);
+
+    (void) pic; (void) subpic;
+}
+
+static void ResetPictures(vout_display_t *vd)
+{
+    vout_display_sys_t *sys = vd->sys;
+
+    if (sys->pool == NULL)
+        return;
+
+    picture_pool_Delete(sys->pool);
+    wl_shm_pool_destroy(sys->shm_pool);
+    munmap(sys->base, sys->length);
+
+    sys->pool = NULL;
+}
+
+static int Control(vout_display_t *vd, int query, va_list ap)
+{
+    vout_display_sys_t *sys = vd->sys;
+
+    switch (query)
+    {
+        case VOUT_DISPLAY_HIDE_MOUSE:
+            /* TODO */
+            return VLC_EGENERIC;
+
+        case VOUT_DISPLAY_RESET_PICTURES:
+        {
+            vout_display_place_t place;
+            video_format_t src;
+
+            sys->x -= vd->fmt.i_x_offset;
+            sys->y -= vd->fmt.i_y_offset;
+
+            vout_display_PlacePicture(&place, &vd->source, vd->cfg, false);
+            video_format_ApplyRotation(&src, &vd->source);
+
+            vd->fmt.i_width  = src.i_width  * place.width
+                                            / src.i_visible_width;
+            vd->fmt.i_height = src.i_height * place.height
+                                            / src.i_visible_height;
+            vd->fmt.i_visible_width  = place.width;
+            vd->fmt.i_visible_height = place.height;
+            vd->fmt.i_x_offset = src.i_x_offset * place.width
+                                                / src.i_visible_width;
+            vd->fmt.i_y_offset = src.i_y_offset * place.height
+                                                / src.i_visible_height;
+
+            sys->x += vd->fmt.i_x_offset;
+            sys->y += vd->fmt.i_y_offset;
+
+            ResetPictures(vd);
+            break;
+        }
+
+        case VOUT_DISPLAY_CHANGE_FULLSCREEN:
+        {
+            const vout_display_cfg_t *cfg =
+                va_arg(ap, const vout_display_cfg_t *);
+            return vout_window_SetFullScreen(sys->embed, cfg->is_fullscreen);
+        }
+
+        case VOUT_DISPLAY_CHANGE_WINDOW_STATE:
+        {
+            unsigned state = va_arg(ap, unsigned);
+            return vout_window_SetState(sys->embed, state);
+        }
+
+        case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
+        {
+            const vout_display_cfg_t *cfg =
+                va_arg(ap, const vout_display_cfg_t *);
+            const bool forced = va_arg(ap, int);
+
+            if (forced)
+            {
+                vout_display_SendEventDisplaySize(vd, cfg->display.width,
+                                                  cfg->display.height,
+                                                  vd->cfg->is_fullscreen);
+                return VLC_EGENERIC;
+            }
+
+            vout_display_place_t place;
+            vout_display_PlacePicture(&place, &vd->source, cfg, false);
+
+            if (place.width == vd->fmt.i_visible_width
+             && place.height == vd->fmt.i_visible_height)
+                break;
+            /* fall through */
+        }
+        case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
+        case VOUT_DISPLAY_CHANGE_ZOOM:
+        case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
+        case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
+             vout_display_SendEventPicturesInvalid(vd);
+             break;
+
+        default:
+             msg_Err(vd, "unknown request %d", query);
+             return VLC_EGENERIC;
+    }
+    return VLC_SUCCESS;
+}
+
+static void registry_global_cb(void *data, struct wl_registry *registry,
+                               uint32_t name, const char *iface, uint32_t vers)
+{
+    vout_display_t *vd = data;
+    vout_display_sys_t *sys = vd->sys;
+
+    msg_Dbg(vd, "global %3"PRIu32": %s version %"PRIu32, name, iface, vers);
+
+    if (!strcmp(iface, "wl_shm"))
+        sys->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
+}
+
+static void registry_global_remove_cb(void *data, struct wl_registry *registry,
+                                      uint32_t name)
+{
+    vout_display_t *vd = data;
+
+    msg_Dbg(vd, "global remove %3"PRIu32, name);
+    (void) registry;
+}
+
+static const struct wl_registry_listener registry_cbs =
+{
+    registry_global_cb,
+    registry_global_remove_cb,
+};
+
+static int Open(vlc_object_t *obj)
+{
+    vout_display_t *vd = (vout_display_t *)obj;
+    vout_display_sys_t *sys = malloc(sizeof (*sys));
+    if (unlikely(sys == NULL))
+        return VLC_ENOMEM;
+
+    vd->sys = sys;
+    sys->embed = NULL;
+    sys->shm = NULL;
+    sys->pool = NULL;
+    sys->fd = -1;
+    sys->x = 0;
+    sys->y = 0;
+
+    char bufpath[] = "/tmp/"PACKAGE_NAME"XXXXXX";
+    sys->fd = mkostemp(bufpath, O_CLOEXEC);
+    if (sys->fd == -1)
+    {
+        msg_Err(vd, "cannot create buffers: %s", vlc_strerror_c(errno));
+        goto error;
+    }
+    unlink(bufpath);
+
+    /* Get window */
+    vout_window_cfg_t wcfg = {
+        .type = VOUT_WINDOW_TYPE_WAYLAND,
+        .width  = vd->cfg->display.width,
+        .height = vd->cfg->display.height,
+    };
+    sys->embed = vout_display_NewWindow(vd, &wcfg);
+    if (sys->embed == NULL)
+        goto error;
+
+    struct wl_display *display = sys->embed->display.wl;
+    struct wl_registry *registry = wl_display_get_registry(display);
+    if (registry == NULL)
+        goto error;
+
+    wl_registry_add_listener(registry, &registry_cbs, vd);
+    wl_display_roundtrip(display);
+    wl_registry_destroy(registry);
+
+    if (sys->shm == NULL)
+        goto error;
+
+    /* Determine our pixel format */
+    video_format_t fmt_pic;
+
+    video_format_ApplyRotation(&fmt_pic, &vd->fmt);
+    fmt_pic.i_chroma = VLC_CODEC_RGB32;
+
+    vd->info.has_pictures_invalid = true;
+    vd->info.has_event_thread = true;
+
+    vd->fmt = fmt_pic;
+    vd->pool = Pool;
+    vd->prepare = Prepare;
+    vd->display = Display;
+    vd->control = Control;
+    vd->manage = NULL;
+
+    bool is_fullscreen = vd->cfg->is_fullscreen;
+    if (is_fullscreen && vout_window_SetFullScreen(sys->embed, true))
+        is_fullscreen = false;
+    vout_display_SendEventFullscreen(vd, is_fullscreen);
+    vout_display_SendEventDisplaySize(vd, vd->cfg->display.width,
+                                      vd->cfg->display.height, is_fullscreen);
+    return VLC_SUCCESS;
+
+error:
+    if (sys->embed != NULL)
+        vout_display_DeleteWindow(vd, sys->embed);
+    if (sys->fd != -1)
+        close(sys->fd);
+    free(sys);
+    return VLC_EGENERIC;
+}
+
+static void Close(vlc_object_t *obj)
+{
+    vout_display_t *vd = (vout_display_t *)obj;
+    vout_display_sys_t *sys = vd->sys;
+
+    ResetPictures(vd);
+
+    wl_shm_destroy(sys->shm);
+    vout_display_DeleteWindow(vd, sys->embed);
+    close(sys->fd);
+    free(sys);
+}
+
+vlc_module_begin()
+    set_shortname(N_("WL SHM"))
+    set_description(N_("Wayland shared memory video output"))
+    set_category(CAT_VIDEO)
+    set_subcategory(SUBCAT_VIDEO_VOUT)
+    set_capability("vout display", 120)
+    set_callbacks(Open, Close)
+    add_shortcut("wl")
+vlc_module_end()
index a6107d3..aadc242 100644 (file)
@@ -1177,6 +1177,7 @@ modules/video_output/sdl.c
 modules/video_output/vdummy.c
 modules/video_output/vmem.c
 modules/video_output/wl/shell_surface.c
+modules/video_output/wl/shm.c
 modules/video_output/xcb/glx.c
 modules/video_output/xcb/window.c
 modules/video_output/xcb/x11.c