]> git.sesse.net Git - vlc/blob - modules/video_output/wayland/shm.c
vout: remove redundant fullscreen initialization
[vlc] / modules / video_output / wayland / shm.c
1 /**
2  * @file shm.c
3  * @brief Wayland shared memory video output module for VLC media player
4  */
5 /*****************************************************************************
6  * Copyright © 2014 Rémi Denis-Courmont
7  *
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.
12  *
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.
17  *
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  *****************************************************************************/
22
23 #ifdef HAVE_CONFIG_H
24 # include <config.h>
25 #endif
26
27 #include <assert.h>
28 #include <errno.h>
29 #include <stdlib.h>
30 #include <string.h>
31
32 #include <sys/types.h>
33 #include <fcntl.h>
34 #include <sys/mman.h>
35 #include <unistd.h>
36
37 #include <wayland-client.h>
38 #include "scaler-client-protocol.h"
39
40 #include <vlc_common.h>
41 #include <vlc_plugin.h>
42 #include <vlc_vout_display.h>
43 #include <vlc_picture_pool.h>
44
45 #define MAX_PICTURES 4
46
47 struct vout_display_sys_t
48 {
49     vout_window_t *embed; /* VLC window */
50     struct wl_event_queue *eventq;
51     struct wl_shm *shm;
52     struct wl_scaler *scaler;
53     struct wl_viewport *viewport;
54
55     picture_pool_t *pool; /* picture pool */
56
57     int x;
58     int y;
59     bool use_buffer_transform;
60 };
61
62 static void PictureDestroy(picture_t *pic)
63 {
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;
67
68     munmap(pic->p[0].p_pixels, (picsize + pagemask) & ~pagemask);
69     wl_buffer_destroy(buf); /* XXX: what if wl_display is already gone? */
70 }
71
72 static void buffer_release_cb(void *data, struct wl_buffer *buffer)
73 {
74     picture_t *pic = data;
75
76     picture_Release(pic);
77     (void) buffer;
78 }
79
80 static const struct wl_buffer_listener buffer_cbs =
81 {
82     buffer_release_cb,
83 };
84
85 static picture_pool_t *Pool(vout_display_t *vd, unsigned req)
86 {
87     vout_display_sys_t *sys = vd->sys;
88
89     if (sys->pool != NULL)
90         return sys->pool;
91
92     if (req > MAX_PICTURES)
93         req = MAX_PICTURES;
94
95     char bufpath[] = "/tmp/"PACKAGE_NAME"XXXXXX";
96     int fd = mkostemp(bufpath, O_CLOEXEC);
97     if (fd == -1)
98     {
99         msg_Err(vd, "cannot create buffers: %s", vlc_strerror_c(errno));
100         return NULL;
101     }
102     unlink(bufpath);
103
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;
110
111     if (ftruncate(fd, length))
112     {
113         msg_Err(vd, "cannot allocate buffers: %s", vlc_strerror_c(errno));
114         close(fd);
115         return NULL;
116     }
117
118     void *base = mmap(NULL, length, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
119     if (base == MAP_FAILED)
120     {
121         msg_Err(vd, "cannot map buffers: %s", vlc_strerror_c(errno));
122         close(fd);
123         return NULL;
124     }
125 #ifndef NDEBUG
126     memset(base, 0x80, length); /* gray fill */
127 #endif
128
129     struct wl_shm_pool *shm_pool = wl_shm_create_pool(sys->shm, fd, length);
130     close(fd);
131     if (shm_pool == NULL)
132     {
133         munmap(base, length);
134         return NULL;
135     }
136
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,
144             },
145         },
146     };
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;
151
152     if (sys->viewport == NULL) /* Poor man's crop */
153         offset += 4 * vd->fmt.i_x_offset + stride * vd->fmt.i_y_offset;
154
155     while (count < req)
156     {
157         struct wl_buffer *buf;
158
159         buf = wl_shm_pool_create_buffer(shm_pool, offset, width, height,
160                                         stride, WL_SHM_FORMAT_XRGB8888);
161         if (buf == NULL)
162             break;
163
164         res.p_sys = (picture_sys_t *)buf;
165         res.p[0].p_pixels = base;
166         base = ((char *)base) + picsize;
167         offset += picsize;
168         length -= picsize;
169
170         picture_t *pic = picture_NewFromResource(&vd->fmt, &res);
171         if (unlikely(pic == NULL))
172         {
173             wl_buffer_destroy(buf);
174             break;
175         }
176
177         wl_buffer_add_listener(buf, &buffer_cbs, pic);
178         pics[count++] = pic;
179     }
180
181     wl_shm_pool_destroy(shm_pool);
182     wl_display_flush(sys->embed->display.wl);
183
184     if (length > 0)
185         munmap(base, length); /* Left-over buffers */
186     if (count == 0)
187         return NULL;
188
189     sys->pool = picture_pool_New (count, pics);
190     if (unlikely(sys->pool == NULL))
191     {
192         while (count > 0)
193             picture_Release(pics[--count]);
194         return NULL;
195     }
196     return sys->pool;
197 }
198
199 static void Prepare(vout_display_t *vd, picture_t *pic, subpicture_t *subpic)
200 {
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;
205
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);
210
211     sys->x = 0;
212     sys->y = 0;
213
214     (void) subpic;
215 }
216
217 static void Display(vout_display_t *vd, picture_t *pic, subpicture_t *subpic)
218 {
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;
222
223     wl_surface_commit(surface);
224     wl_display_roundtrip_queue(display, sys->eventq);
225
226     (void) pic; (void) subpic;
227 }
228
229 static void ResetPictures(vout_display_t *vd)
230 {
231     vout_display_sys_t *sys = vd->sys;
232
233     if (sys->pool == NULL)
234         return;
235
236     picture_pool_Delete(sys->pool);
237     sys->pool = NULL;
238 }
239
240 static int Control(vout_display_t *vd, int query, va_list ap)
241 {
242     vout_display_sys_t *sys = vd->sys;
243
244     switch (query)
245     {
246         case VOUT_DISPLAY_HIDE_MOUSE:
247             /* TODO */
248             return VLC_EGENERIC;
249
250         case VOUT_DISPLAY_RESET_PICTURES:
251         {
252             vout_display_place_t place;
253             video_format_t src;
254
255             assert(sys->viewport == NULL);
256
257             vout_display_PlacePicture(&place, &vd->source, vd->cfg, false);
258             video_format_ApplyRotation(&src, &vd->source);
259
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;
270             ResetPictures(vd);
271             break;
272         }
273
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:
279         {
280             const vout_display_cfg_t *cfg;
281             const video_format_t *src;
282
283             if (query == VOUT_DISPLAY_CHANGE_SOURCE_ASPECT
284              || query == VOUT_DISPLAY_CHANGE_SOURCE_CROP)
285             {
286                 src = va_arg(ap, const video_format_t *);
287                 cfg = vd->cfg;
288             }
289             else
290             {
291                 src = &vd->source;
292                 cfg = va_arg(ap, const vout_display_cfg_t *);
293             }
294
295             vout_display_place_t place;
296
297             vout_display_PlacePicture(&place, &vd->source, vd->cfg, false);
298             sys->x += place.width / 2;
299             sys->y += place.height / 2;
300
301             vout_display_PlacePicture(&place, src, cfg, false);
302             sys->x -= place.width / 2;
303             sys->y -= place.height / 2;
304
305             if (sys->viewport != NULL)
306             {
307                 video_format_t fmt;
308
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);
316             }
317             else
318                 vout_display_SendEventPicturesInvalid(vd);
319             break;
320         }
321         default:
322              msg_Err(vd, "unknown request %d", query);
323              return VLC_EGENERIC;
324     }
325     return VLC_SUCCESS;
326 }
327
328 static void shm_format_cb(void *data, struct wl_shm *shm, uint32_t format)
329 {
330     vout_display_t *vd = data;
331     char str[4];
332
333     memcpy(str, &format, sizeof (str));
334
335     if (format >= 0x20202020)
336         msg_Dbg(vd, "format %.4s (0x%08"PRIx32")", str, format);
337     else
338         msg_Dbg(vd, "format %4"PRIu32" (0x%08"PRIx32")", format, format);
339     (void) shm;
340 }
341
342 static const struct wl_shm_listener shm_cbs =
343 {
344     shm_format_cb,
345 };
346
347 static void registry_global_cb(void *data, struct wl_registry *registry,
348                                uint32_t name, const char *iface, uint32_t vers)
349 {
350     vout_display_t *vd = data;
351     vout_display_sys_t *sys = vd->sys;
352
353     msg_Dbg(vd, "global %3"PRIu32": %s version %"PRIu32, name, iface, vers);
354
355     if (!strcmp(iface, "wl_shm"))
356         sys->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
357     else
358     if (!strcmp(iface, "wl_scaler"))
359         sys->scaler = wl_registry_bind(registry, name, &wl_scaler_interface,
360                                        1);
361     else
362     if (!strcmp(iface, "wl_compositor"))
363         sys->use_buffer_transform = vers >= 2;
364 }
365
366 static void registry_global_remove_cb(void *data, struct wl_registry *registry,
367                                       uint32_t name)
368 {
369     vout_display_t *vd = data;
370
371     msg_Dbg(vd, "global remove %3"PRIu32, name);
372     (void) registry;
373 }
374
375 static const struct wl_registry_listener registry_cbs =
376 {
377     registry_global_cb,
378     registry_global_remove_cb,
379 };
380
381 static int Open(vlc_object_t *obj)
382 {
383     vout_display_t *vd = (vout_display_t *)obj;
384     vout_display_sys_t *sys = malloc(sizeof (*sys));
385     if (unlikely(sys == NULL))
386         return VLC_ENOMEM;
387
388     vd->sys = sys;
389     sys->embed = NULL;
390     sys->eventq = NULL;
391     sys->shm = NULL;
392     sys->scaler = NULL;
393     sys->pool = NULL;
394     sys->x = 0;
395     sys->y = 0;
396     sys->use_buffer_transform = false;
397
398     /* Get window */
399     vout_window_cfg_t wcfg = {
400         .type = VOUT_WINDOW_TYPE_WAYLAND,
401         .width  = vd->cfg->display.width,
402         .height = vd->cfg->display.height,
403     };
404     sys->embed = vout_display_NewWindow(vd, &wcfg);
405     if (sys->embed == NULL)
406         goto error;
407
408     struct wl_display *display = sys->embed->display.wl;
409
410     sys->eventq = wl_display_create_queue(display);
411     if (sys->eventq == NULL)
412         goto error;
413
414     struct wl_registry *registry = wl_display_get_registry(display);
415     if (registry == NULL)
416         goto error;
417
418     wl_proxy_set_queue((struct wl_proxy *)registry, sys->eventq);
419     wl_registry_add_listener(registry, &registry_cbs, vd);
420     wl_display_roundtrip_queue(display, sys->eventq);
421     wl_registry_destroy(registry);
422
423     if (sys->shm == NULL)
424         goto error;
425
426     wl_shm_add_listener(sys->shm, &shm_cbs, vd);
427     wl_display_roundtrip_queue(display, sys->eventq);
428
429     struct wl_surface *surface = sys->embed->handle.wl;
430     if (sys->scaler != NULL)
431         sys->viewport = wl_scaler_get_viewport(sys->scaler, surface);
432     else
433         sys->viewport = NULL;
434
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,
445     };
446
447     if (sys->use_buffer_transform)
448     {
449         wl_surface_set_buffer_transform(surface,
450                                         transforms[vd->fmt.orientation]);
451     }
452     else
453     {
454         video_format_t fmt = vd->fmt;
455         video_format_ApplyRotation(&vd->fmt, &fmt);
456     }
457
458     vd->fmt.i_chroma = VLC_CODEC_RGB32;
459
460     vd->info.has_pictures_invalid = sys->viewport == NULL;
461     vd->info.has_event_thread = true;
462
463     vd->pool = Pool;
464     vd->prepare = Prepare;
465     vd->display = Display;
466     vd->control = Control;
467     vd->manage = NULL;
468
469     return VLC_SUCCESS;
470
471 error:
472     if (sys->eventq != NULL)
473         wl_event_queue_destroy(sys->eventq);
474     if (sys->embed != NULL)
475         vout_display_DeleteWindow(vd, sys->embed);
476     free(sys);
477     return VLC_EGENERIC;
478 }
479
480 static void Close(vlc_object_t *obj)
481 {
482     vout_display_t *vd = (vout_display_t *)obj;
483     vout_display_sys_t *sys = vd->sys;
484
485     ResetPictures(vd);
486
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);
495     free(sys);
496 }
497
498 vlc_module_begin()
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)
505     add_shortcut("wl")
506 vlc_module_end()