vout: change "vout display" activate/deactivate callbacks.
[vlc.git] / modules / video_output / wayland / shm.c
blob1964d47c774e8f86d73e513756b25be09ae5bee3
1 /**
2 * @file shm.c
3 * @brief Wayland shared memory video output module for VLC media player
4 */
5 /*****************************************************************************
6 * Copyright © 2014, 2017 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 *****************************************************************************/
23 #ifdef HAVE_CONFIG_H
24 # include <config.h>
25 #endif
27 #include <assert.h>
28 #include <errno.h>
29 #include <stdlib.h>
30 #include <string.h>
32 #include <sys/types.h>
33 #include <sys/mman.h>
34 #include <unistd.h>
36 #include <wayland-client.h>
37 #include "viewporter-client-protocol.h"
39 #include <vlc_common.h>
40 #include <vlc_plugin.h>
41 #include <vlc_vout_display.h>
42 #include <vlc_picture_pool.h>
43 #include <vlc_fs.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;
51 struct wl_shm *shm;
52 struct wp_viewporter *viewporter;
53 struct wp_viewport *viewport;
55 picture_pool_t *pool; /* picture pool */
57 int x;
58 int y;
59 unsigned display_width;
60 unsigned display_height;
61 bool use_buffer_transform;
64 static void PictureDestroy(picture_t *pic)
66 const long pagemask = sysconf(_SC_PAGE_SIZE) - 1;
67 size_t picsize = pic->p[0].i_pitch * pic->p[0].i_lines;
69 munmap(pic->p[0].p_pixels, (picsize + pagemask) & ~pagemask);
70 free(pic);
73 static void buffer_release_cb(void *data, struct wl_buffer *buffer)
75 picture_t *pic = data;
77 assert(pic != NULL);
78 wl_buffer_set_user_data(buffer, NULL);
79 picture_Release(pic);
82 static const struct wl_buffer_listener buffer_cbs =
84 buffer_release_cb,
87 static picture_pool_t *Pool(vout_display_t *vd, unsigned req)
89 vout_display_sys_t *sys = vd->sys;
91 if (sys->pool != NULL)
92 return sys->pool;
94 if (req > MAX_PICTURES)
95 req = MAX_PICTURES;
97 int fd = vlc_memfd();
98 if (fd == -1)
100 msg_Err(vd, "cannot create buffers: %s", vlc_strerror_c(errno));
101 return NULL;
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));
114 vlc_close(fd);
115 return NULL;
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));
122 vlc_close(fd);
123 return NULL;
125 #ifndef NDEBUG
126 memset(base, 0x80, length); /* gray fill */
127 #endif
129 struct wl_shm_pool *shm_pool = wl_shm_create_pool(sys->shm, fd, length);
130 vlc_close(fd);
131 if (shm_pool == NULL)
133 munmap(base, length);
134 return NULL;
137 picture_t *pics[MAX_PICTURES];
138 picture_resource_t res = {
139 .pf_destroy = PictureDestroy,
140 .p = {
141 [0] = {
142 .i_lines = lines,
143 .i_pitch = stride,
147 size_t offset = 0;
148 unsigned width = vd->fmt.i_visible_width;
149 unsigned height = vd->fmt.i_visible_height;
150 unsigned count = 0;
152 if (sys->viewport == NULL) /* Poor man's crop */
153 offset += 4 * vd->fmt.i_x_offset + stride * vd->fmt.i_y_offset;
155 while (count < req)
157 struct wl_buffer *buf;
159 buf = wl_shm_pool_create_buffer(shm_pool, offset, width, height,
160 stride, WL_SHM_FORMAT_XRGB8888);
161 if (buf == NULL)
162 break;
164 res.p_sys = buf;
165 res.p[0].p_pixels = base;
166 base = ((char *)base) + picsize;
167 offset += picsize;
168 length -= picsize;
170 picture_t *pic = picture_NewFromResource(&vd->fmt, &res);
171 if (unlikely(pic == NULL))
173 wl_buffer_destroy(buf);
174 break;
177 wl_buffer_add_listener(buf, &buffer_cbs, NULL);
178 pics[count++] = pic;
181 wl_shm_pool_destroy(shm_pool);
182 wl_display_flush(sys->embed->display.wl);
184 if (length > 0)
185 munmap(base, length); /* Left-over buffers */
186 if (count == 0)
187 return NULL;
189 sys->pool = picture_pool_New (count, pics);
190 if (unlikely(sys->pool == NULL))
192 while (count > 0)
193 picture_Release(pics[--count]);
195 return sys->pool;
198 static void Prepare(vout_display_t *vd, picture_t *pic, subpicture_t *subpic,
199 vlc_tick_t date)
201 VLC_UNUSED(date);
202 vout_display_sys_t *sys = vd->sys;
203 struct wl_display *display = sys->embed->display.wl;
204 struct wl_surface *surface = sys->embed->handle.wl;
205 struct wl_buffer *buf = (struct wl_buffer *)pic->p_sys;
207 picture_Hold(pic);
209 wl_buffer_set_user_data(buf, pic);
210 wl_surface_attach(surface, buf, 0, 0);
211 wl_surface_damage(surface, 0, 0, sys->display_width, sys->display_height);
212 wl_display_flush(display);
214 sys->x = 0;
215 sys->y = 0;
217 (void) subpic;
220 static void Display(vout_display_t *vd, picture_t *pic)
222 vout_display_sys_t *sys = vd->sys;
223 struct wl_display *display = sys->embed->display.wl;
224 struct wl_surface *surface = sys->embed->handle.wl;
226 wl_surface_commit(surface);
227 wl_display_roundtrip_queue(display, sys->eventq);
229 (void) pic;
232 static void PictureCheckAttached(void *data, picture_t *pic)
234 unsigned *countp = data;
236 (*countp) += wl_buffer_get_user_data(pic->p_sys) != NULL;
239 static unsigned CountActiveBuffers(picture_pool_t *pool)
241 unsigned count = 0;
243 picture_pool_Enum(pool, PictureCheckAttached, &count);
244 return count;
247 static void PictureBufferDestroy(void *data, picture_t *pic)
249 struct wl_buffer *buf = (struct wl_buffer *)pic->p_sys;
251 wl_buffer_destroy(buf);
252 (void) data;
255 static void ResetPictures(vout_display_t *vd)
257 vout_display_sys_t *sys = vd->sys;
258 struct wl_display *display = sys->embed->display.wl;
259 struct wl_surface *surface = sys->embed->handle.wl;
261 if (sys->pool == NULL)
262 return;
264 wl_surface_attach(surface, NULL, 0, 0);
265 wl_surface_commit(surface);
267 /* Wait until all picture buffers are released by the server */
268 while (CountActiveBuffers(sys->pool) > 0)
269 wl_display_roundtrip_queue(display, sys->eventq);
271 /* Destroy the buffers */
272 picture_pool_Enum(sys->pool, PictureBufferDestroy, NULL);
273 picture_pool_Release(sys->pool);
274 sys->pool = NULL;
277 static int Control(vout_display_t *vd, int query, va_list ap)
279 vout_display_sys_t *sys = vd->sys;
281 switch (query)
283 case VOUT_DISPLAY_RESET_PICTURES:
285 const vout_display_cfg_t *cfg = va_arg(ap, const vout_display_cfg_t *);
286 video_format_t *fmt = va_arg(ap, video_format_t *);
287 vout_display_place_t place;
288 video_format_t src;
289 assert(sys->viewport == NULL);
291 vout_display_PlacePicture(&place, &vd->source, cfg, false);
292 video_format_ApplyRotation(&src, &vd->source);
294 fmt->i_width = src.i_width * place.width
295 / src.i_visible_width;
296 fmt->i_height = src.i_height * place.height
297 / src.i_visible_height;
298 fmt->i_visible_width = place.width;
299 fmt->i_visible_height = place.height;
300 fmt->i_x_offset = src.i_x_offset * place.width
301 / src.i_visible_width;
302 fmt->i_y_offset = src.i_y_offset * place.height
303 / src.i_visible_height;
305 ResetPictures(vd);
306 break;
309 case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
310 case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
311 case VOUT_DISPLAY_CHANGE_ZOOM:
312 case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
313 case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
315 const vout_display_cfg_t *cfg = va_arg(ap, const vout_display_cfg_t *);
316 sys->display_width = cfg->display.width;
317 sys->display_height = cfg->display.height;
319 if (sys->viewport != NULL)
321 video_format_t fmt;
322 vout_display_place_t place;
324 video_format_ApplyRotation(&fmt, &vd->source);
325 vout_display_PlacePicture(&place, &vd->source, cfg, false);
327 wp_viewport_set_source(sys->viewport,
328 wl_fixed_from_int(fmt.i_x_offset),
329 wl_fixed_from_int(fmt.i_y_offset),
330 wl_fixed_from_int(fmt.i_visible_width),
331 wl_fixed_from_int(fmt.i_visible_height));
332 wp_viewport_set_destination(sys->viewport,
333 place.width, place.height);
335 else
336 vout_display_SendEventPicturesInvalid(vd);
337 break;
339 default:
340 msg_Err(vd, "unknown request %d", query);
341 return VLC_EGENERIC;
343 return VLC_SUCCESS;
346 static void shm_format_cb(void *data, struct wl_shm *shm, uint32_t format)
348 vout_display_t *vd = data;
349 char str[4];
351 memcpy(str, &format, sizeof (str));
353 if (format >= 0x20202020)
354 msg_Dbg(vd, "format %.4s (0x%08"PRIx32")", str, format);
355 else
356 msg_Dbg(vd, "format %4"PRIu32" (0x%08"PRIx32")", format, format);
357 (void) shm;
360 static const struct wl_shm_listener shm_cbs =
362 shm_format_cb,
365 static void registry_global_cb(void *data, struct wl_registry *registry,
366 uint32_t name, const char *iface, uint32_t vers)
368 vout_display_t *vd = data;
369 vout_display_sys_t *sys = vd->sys;
371 msg_Dbg(vd, "global %3"PRIu32": %s version %"PRIu32, name, iface, vers);
373 if (!strcmp(iface, "wl_shm"))
374 sys->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
375 else
376 if (!strcmp(iface, "wp_viewporter"))
377 sys->viewporter = wl_registry_bind(registry, name,
378 &wp_viewporter_interface, 1);
379 else
380 if (!strcmp(iface, "wl_compositor"))
381 sys->use_buffer_transform = vers >= 2;
384 static void registry_global_remove_cb(void *data, struct wl_registry *registry,
385 uint32_t name)
387 vout_display_t *vd = data;
389 msg_Dbg(vd, "global remove %3"PRIu32, name);
390 (void) registry;
393 static const struct wl_registry_listener registry_cbs =
395 registry_global_cb,
396 registry_global_remove_cb,
399 static int Open(vout_display_t *vd, const vout_display_cfg_t *cfg,
400 video_format_t *fmtp, vlc_video_context *context)
402 if (cfg->window->type != VOUT_WINDOW_TYPE_WAYLAND)
403 return VLC_EGENERIC;
405 vout_display_sys_t *sys = malloc(sizeof (*sys));
406 if (unlikely(sys == NULL))
407 return VLC_ENOMEM;
409 vd->sys = sys;
410 sys->embed = NULL;
411 sys->eventq = NULL;
412 sys->shm = NULL;
413 sys->viewporter = NULL;
414 sys->pool = NULL;
415 sys->x = 0;
416 sys->y = 0;
417 sys->display_width = cfg->display.width;
418 sys->display_height = cfg->display.height;
419 sys->use_buffer_transform = false;
421 /* Get window */
422 sys->embed = cfg->window;
423 assert(sys->embed != NULL);
425 struct wl_display *display = sys->embed->display.wl;
427 sys->eventq = wl_display_create_queue(display);
428 if (sys->eventq == NULL)
429 goto error;
431 struct wl_registry *registry = wl_display_get_registry(display);
432 if (registry == NULL)
433 goto error;
435 wl_proxy_set_queue((struct wl_proxy *)registry, sys->eventq);
436 wl_registry_add_listener(registry, &registry_cbs, vd);
437 wl_display_roundtrip_queue(display, sys->eventq);
438 wl_registry_destroy(registry);
440 if (sys->shm == NULL)
441 goto error;
443 wl_shm_add_listener(sys->shm, &shm_cbs, vd);
444 wl_display_roundtrip_queue(display, sys->eventq);
446 struct wl_surface *surface = sys->embed->handle.wl;
447 if (sys->viewporter != NULL)
448 sys->viewport = wp_viewporter_get_viewport(sys->viewporter, surface);
449 else
450 sys->viewport = NULL;
452 /* Determine our pixel format */
453 static const enum wl_output_transform transforms[8] = {
454 [ORIENT_TOP_LEFT] = WL_OUTPUT_TRANSFORM_NORMAL,
455 [ORIENT_TOP_RIGHT] = WL_OUTPUT_TRANSFORM_FLIPPED,
456 [ORIENT_BOTTOM_LEFT] = WL_OUTPUT_TRANSFORM_FLIPPED_180,
457 [ORIENT_BOTTOM_RIGHT] = WL_OUTPUT_TRANSFORM_180,
458 [ORIENT_LEFT_TOP] = WL_OUTPUT_TRANSFORM_FLIPPED_270,
459 [ORIENT_LEFT_BOTTOM] = WL_OUTPUT_TRANSFORM_90,
460 [ORIENT_RIGHT_TOP] = WL_OUTPUT_TRANSFORM_270,
461 [ORIENT_RIGHT_BOTTOM] = WL_OUTPUT_TRANSFORM_FLIPPED_90,
464 if (sys->use_buffer_transform)
466 wl_surface_set_buffer_transform(surface,
467 transforms[fmtp->orientation]);
469 else
471 video_format_t fmt = *fmtp;
472 video_format_ApplyRotation(fmtp, &fmt);
475 fmtp->i_chroma = VLC_CODEC_RGB32;
477 vd->info.has_pictures_invalid = sys->viewport == NULL;
479 vd->pool = Pool;
480 vd->prepare = Prepare;
481 vd->display = Display;
482 vd->control = Control;
484 (void) context;
485 return VLC_SUCCESS;
487 error:
488 if (sys->eventq != NULL)
489 wl_event_queue_destroy(sys->eventq);
490 free(sys);
491 return VLC_EGENERIC;
494 static void Close(vout_display_t *vd)
496 vout_display_sys_t *sys = vd->sys;
498 ResetPictures(vd);
500 if (sys->viewport != NULL)
501 wp_viewport_destroy(sys->viewport);
502 if (sys->viewporter != NULL)
503 wp_viewporter_destroy(sys->viewporter);
504 wl_shm_destroy(sys->shm);
505 wl_display_flush(sys->embed->display.wl);
506 wl_event_queue_destroy(sys->eventq);
507 free(sys);
510 vlc_module_begin()
511 set_shortname(N_("WL SHM"))
512 set_description(N_("Wayland shared memory video output"))
513 set_category(CAT_VIDEO)
514 set_subcategory(SUBCAT_VIDEO_VOUT)
515 set_capability("vout display", 170)
516 set_callbacks(Open, Close)
517 add_shortcut("wl")
518 vlc_module_end()