3 * @brief Wayland shared memory video output module for VLC media player
5 /*****************************************************************************
6 * Copyright © 2014 Rémi Denis-Courmont
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU Lesser General Public License as published by
10 * the Free Software Foundation; either version 2.1 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this program; if not, write to the Free Software Foundation,
20 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
21 *****************************************************************************/
32 #include <sys/types.h>
37 #include <wayland-client.h>
38 #include "scaler-client-protocol.h"
40 #include <vlc_common.h>
41 #include <vlc_plugin.h>
42 #include <vlc_vout_display.h>
43 #include <vlc_picture_pool.h>
45 #define MAX_PICTURES 4
47 struct vout_display_sys_t
49 vout_window_t *embed; /* VLC window */
50 struct wl_event_queue *eventq;
52 struct wl_scaler *scaler;
53 struct wl_viewport *viewport;
55 picture_pool_t *pool; /* picture pool */
59 bool use_buffer_transform;
62 static void PictureDestroy(picture_t *pic)
64 struct wl_buffer *buf = (struct wl_buffer *)pic->p_sys;
65 const long pagemask = sysconf(_SC_PAGE_SIZE) - 1;
66 size_t picsize = pic->p[0].i_pitch * pic->p[0].i_lines;
68 munmap(pic->p[0].p_pixels, (picsize + pagemask) & ~pagemask);
69 wl_buffer_destroy(buf); /* XXX: what if wl_display is already gone? */
72 static void buffer_release_cb(void *data, struct wl_buffer *buffer)
74 picture_t *pic = data;
80 static const struct wl_buffer_listener buffer_cbs =
85 static picture_pool_t *Pool(vout_display_t *vd, unsigned req)
87 vout_display_sys_t *sys = vd->sys;
89 if (sys->pool != NULL)
92 if (req > MAX_PICTURES)
95 char bufpath[] = "/tmp/"PACKAGE_NAME"XXXXXX";
96 int fd = mkostemp(bufpath, O_CLOEXEC);
99 msg_Err(vd, "cannot create buffers: %s", vlc_strerror_c(errno));
104 /* We need one extra line to cover for horizontal crop offset */
105 unsigned stride = 4 * ((vd->fmt.i_width + 31) & ~31);
106 unsigned lines = (vd->fmt.i_height + 31 + (sys->viewport == NULL)) & ~31;
107 const long pagemask = sysconf(_SC_PAGE_SIZE) - 1;
108 size_t picsize = ((stride * lines) + pagemask) & ~pagemask;
109 size_t length = picsize * req;
111 if (ftruncate(fd, length))
113 msg_Err(vd, "cannot allocate buffers: %s", vlc_strerror_c(errno));
118 void *base = mmap(NULL, length, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
119 if (base == MAP_FAILED)
121 msg_Err(vd, "cannot map buffers: %s", vlc_strerror_c(errno));
126 memset(base, 0x80, length); /* gray fill */
129 struct wl_shm_pool *shm_pool = wl_shm_create_pool(sys->shm, fd, length);
131 if (shm_pool == NULL)
133 munmap(base, length);
137 picture_t *pics[MAX_PICTURES];
138 picture_resource_t res = {
139 .pf_destroy = PictureDestroy,
148 unsigned width = vd->fmt.i_visible_width;
149 unsigned height = vd->fmt.i_visible_height;
152 if (sys->viewport == NULL) /* Poor man's crop */
153 offset += 4 * vd->fmt.i_x_offset + stride * vd->fmt.i_y_offset;
157 struct wl_buffer *buf;
159 buf = wl_shm_pool_create_buffer(shm_pool, offset, width, height,
160 stride, WL_SHM_FORMAT_XRGB8888);
164 res.p_sys = (picture_sys_t *)buf;
165 res.p[0].p_pixels = base;
166 base = ((char *)base) + picsize;
170 picture_t *pic = picture_NewFromResource(&vd->fmt, &res);
171 if (unlikely(pic == NULL))
173 wl_buffer_destroy(buf);
177 wl_buffer_add_listener(buf, &buffer_cbs, pic);
181 wl_shm_pool_destroy(shm_pool);
182 wl_display_flush(sys->embed->display.wl);
185 munmap(base, length); /* Left-over buffers */
189 sys->pool = picture_pool_New (count, pics);
190 if (unlikely(sys->pool == NULL))
193 picture_Release(pics[--count]);
199 static void Prepare(vout_display_t *vd, picture_t *pic, subpicture_t *subpic)
201 vout_display_sys_t *sys = vd->sys;
202 struct wl_display *display = sys->embed->display.wl;
203 struct wl_surface *surface = sys->embed->handle.wl;
204 struct wl_buffer *buf = (struct wl_buffer *)pic->p_sys;
206 wl_surface_attach(surface, buf, sys->x, sys->y);
207 wl_surface_damage(surface, 0, 0,
208 vd->cfg->display.width, vd->cfg->display.height);
209 wl_display_flush(display);
217 static void Display(vout_display_t *vd, picture_t *pic, subpicture_t *subpic)
219 vout_display_sys_t *sys = vd->sys;
220 struct wl_display *display = sys->embed->display.wl;
221 struct wl_surface *surface = sys->embed->handle.wl;
223 wl_surface_commit(surface);
224 wl_display_roundtrip_queue(display, sys->eventq);
226 (void) pic; (void) subpic;
229 static void ResetPictures(vout_display_t *vd)
231 vout_display_sys_t *sys = vd->sys;
233 if (sys->pool == NULL)
236 picture_pool_Delete(sys->pool);
240 static int Control(vout_display_t *vd, int query, va_list ap)
242 vout_display_sys_t *sys = vd->sys;
246 case VOUT_DISPLAY_HIDE_MOUSE:
250 case VOUT_DISPLAY_RESET_PICTURES:
252 vout_display_place_t place;
255 assert(sys->viewport == NULL);
257 vout_display_PlacePicture(&place, &vd->source, vd->cfg, false);
258 video_format_ApplyRotation(&src, &vd->source);
260 vd->fmt.i_width = src.i_width * place.width
261 / src.i_visible_width;
262 vd->fmt.i_height = src.i_height * place.height
263 / src.i_visible_height;
264 vd->fmt.i_visible_width = place.width;
265 vd->fmt.i_visible_height = place.height;
266 vd->fmt.i_x_offset = src.i_x_offset * place.width
267 / src.i_visible_width;
268 vd->fmt.i_y_offset = src.i_y_offset * place.height
269 / src.i_visible_height;
274 case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
275 case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
276 case VOUT_DISPLAY_CHANGE_ZOOM:
277 case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
278 case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
280 const vout_display_cfg_t *cfg;
281 const video_format_t *src;
283 if (query == VOUT_DISPLAY_CHANGE_SOURCE_ASPECT
284 || query == VOUT_DISPLAY_CHANGE_SOURCE_CROP)
286 src = va_arg(ap, const video_format_t *);
292 cfg = va_arg(ap, const vout_display_cfg_t *);
295 vout_display_place_t place;
297 vout_display_PlacePicture(&place, &vd->source, vd->cfg, false);
298 sys->x += place.width / 2;
299 sys->y += place.height / 2;
301 vout_display_PlacePicture(&place, src, cfg, false);
302 sys->x -= place.width / 2;
303 sys->y -= place.height / 2;
305 if (sys->viewport != NULL)
309 video_format_ApplyRotation(&fmt, src);
310 wl_viewport_set(sys->viewport,
311 wl_fixed_from_int(fmt.i_x_offset),
312 wl_fixed_from_int(fmt.i_y_offset),
313 wl_fixed_from_int(fmt.i_visible_width),
314 wl_fixed_from_int(fmt.i_visible_height),
315 place.width, place.height);
318 vout_display_SendEventPicturesInvalid(vd);
322 msg_Err(vd, "unknown request %d", query);
328 static void shm_format_cb(void *data, struct wl_shm *shm, uint32_t format)
330 vout_display_t *vd = data;
333 memcpy(str, &format, sizeof (str));
335 if (format >= 0x20202020)
336 msg_Dbg(vd, "format %.4s (0x%08"PRIx32")", str, format);
338 msg_Dbg(vd, "format %4"PRIu32" (0x%08"PRIx32")", format, format);
342 static const struct wl_shm_listener shm_cbs =
347 static void registry_global_cb(void *data, struct wl_registry *registry,
348 uint32_t name, const char *iface, uint32_t vers)
350 vout_display_t *vd = data;
351 vout_display_sys_t *sys = vd->sys;
353 msg_Dbg(vd, "global %3"PRIu32": %s version %"PRIu32, name, iface, vers);
355 if (!strcmp(iface, "wl_shm"))
356 sys->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
358 if (!strcmp(iface, "wl_scaler"))
359 sys->scaler = wl_registry_bind(registry, name, &wl_scaler_interface,
362 if (!strcmp(iface, "wl_compositor"))
363 sys->use_buffer_transform = vers >= 2;
366 static void registry_global_remove_cb(void *data, struct wl_registry *registry,
369 vout_display_t *vd = data;
371 msg_Dbg(vd, "global remove %3"PRIu32, name);
375 static const struct wl_registry_listener registry_cbs =
378 registry_global_remove_cb,
381 static int Open(vlc_object_t *obj)
383 vout_display_t *vd = (vout_display_t *)obj;
384 vout_display_sys_t *sys = malloc(sizeof (*sys));
385 if (unlikely(sys == NULL))
396 sys->use_buffer_transform = false;
399 vout_window_cfg_t wcfg = {
400 .type = VOUT_WINDOW_TYPE_WAYLAND,
401 .width = vd->cfg->display.width,
402 .height = vd->cfg->display.height,
404 sys->embed = vout_display_NewWindow(vd, &wcfg);
405 if (sys->embed == NULL)
408 struct wl_display *display = sys->embed->display.wl;
410 sys->eventq = wl_display_create_queue(display);
411 if (sys->eventq == NULL)
414 struct wl_registry *registry = wl_display_get_registry(display);
415 if (registry == NULL)
418 wl_proxy_set_queue((struct wl_proxy *)registry, sys->eventq);
419 wl_registry_add_listener(registry, ®istry_cbs, vd);
420 wl_display_roundtrip_queue(display, sys->eventq);
421 wl_registry_destroy(registry);
423 if (sys->shm == NULL)
426 wl_shm_add_listener(sys->shm, &shm_cbs, vd);
427 wl_display_roundtrip_queue(display, sys->eventq);
429 struct wl_surface *surface = sys->embed->handle.wl;
430 if (sys->scaler != NULL)
431 sys->viewport = wl_scaler_get_viewport(sys->scaler, surface);
433 sys->viewport = NULL;
435 /* Determine our pixel format */
436 static const enum wl_output_transform transforms[8] = {
437 [ORIENT_TOP_LEFT] = WL_OUTPUT_TRANSFORM_NORMAL,
438 [ORIENT_TOP_RIGHT] = WL_OUTPUT_TRANSFORM_FLIPPED,
439 [ORIENT_BOTTOM_LEFT] = WL_OUTPUT_TRANSFORM_FLIPPED_180,
440 [ORIENT_BOTTOM_RIGHT] = WL_OUTPUT_TRANSFORM_180,
441 [ORIENT_LEFT_TOP] = WL_OUTPUT_TRANSFORM_FLIPPED_270,
442 [ORIENT_LEFT_BOTTOM] = WL_OUTPUT_TRANSFORM_90,
443 [ORIENT_RIGHT_TOP] = WL_OUTPUT_TRANSFORM_270,
444 [ORIENT_RIGHT_BOTTOM] = WL_OUTPUT_TRANSFORM_FLIPPED_90,
447 if (sys->use_buffer_transform)
449 wl_surface_set_buffer_transform(surface,
450 transforms[vd->fmt.orientation]);
454 video_format_t fmt = vd->fmt;
455 video_format_ApplyRotation(&vd->fmt, &fmt);
458 vd->fmt.i_chroma = VLC_CODEC_RGB32;
460 vd->info.has_pictures_invalid = sys->viewport == NULL;
461 vd->info.has_event_thread = true;
464 vd->prepare = Prepare;
465 vd->display = Display;
466 vd->control = Control;
472 if (sys->eventq != NULL)
473 wl_event_queue_destroy(sys->eventq);
474 if (sys->embed != NULL)
475 vout_display_DeleteWindow(vd, sys->embed);
480 static void Close(vlc_object_t *obj)
482 vout_display_t *vd = (vout_display_t *)obj;
483 vout_display_sys_t *sys = vd->sys;
487 if (sys->viewport != NULL)
488 wl_viewport_destroy(sys->viewport);
489 if (sys->scaler != NULL)
490 wl_scaler_destroy(sys->scaler);
491 wl_shm_destroy(sys->shm);
492 wl_display_flush(sys->embed->display.wl);
493 wl_event_queue_destroy(sys->eventq);
494 vout_display_DeleteWindow(vd, sys->embed);
499 set_shortname(N_("WL SHM"))
500 set_description(N_("Wayland shared memory video output"))
501 set_category(CAT_VIDEO)
502 set_subcategory(SUBCAT_VIDEO_VOUT)
503 set_capability("vout display", 120)
504 set_callbacks(Open, Close)