]> git.sesse.net Git - vlc/blob - modules/video_output/wl/shm.c
wl/shm: initial unaccelerated RGB video output
[vlc] / modules / video_output / wl / 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 <errno.h>
28 #include <stdlib.h>
29 #include <string.h>
30
31 #include <sys/types.h>
32 #include <fcntl.h>
33 #include <sys/mman.h>
34 #include <unistd.h>
35
36 #include <wayland-client.h>
37
38 #include <vlc_common.h>
39 #include <vlc_plugin.h>
40 #include <vlc_vout_display.h>
41 #include <vlc_picture_pool.h>
42
43 #define MAX_PICTURES 4
44
45 struct vout_display_sys_t
46 {
47     vout_window_t *embed; /* VLC window */
48     struct wl_shm *shm;
49     struct wl_shm_pool *shm_pool;
50
51     picture_pool_t *pool; /* picture pool */
52     unsigned char *base;
53     size_t length;
54     int fd;
55
56     int x;
57     int y;
58 };
59
60 static void PictureDestroy(picture_t *pic)
61 {
62     struct wl_buffer *buf = (struct wl_buffer *)pic->p_sys;
63
64     wl_buffer_destroy(buf);
65 }
66
67 static void buffer_release_cb(void *data, struct wl_buffer *buffer)
68 {
69     picture_t *pic = data;
70
71     picture_Release(pic);
72     (void) buffer;
73 }
74
75 static const struct wl_buffer_listener buffer_cbs =
76 {
77     buffer_release_cb,
78 };
79
80 static picture_pool_t *Pool(vout_display_t *vd, unsigned req)
81 {
82     vout_display_sys_t *sys = vd->sys;
83
84     if (sys->pool != NULL)
85         return sys->pool;
86
87     if (req > MAX_PICTURES)
88         req = MAX_PICTURES;
89
90     vout_display_place_t place;
91
92     vout_display_PlacePicture(&place, &vd->source, vd->cfg, false);
93
94     /* We need one extra line to cover for horizontal crop offset */
95     unsigned stride = 4 * ((vd->fmt.i_width + 31) & ~31);
96     unsigned lines = (vd->fmt.i_height + 31 + 1) & ~31;
97     const long pagemask = sysconf(_SC_PAGE_SIZE) - 1;
98     size_t picsize = ((stride * lines) + pagemask) & ~pagemask;
99
100     sys->length = picsize * req;
101
102     if (ftruncate(sys->fd, sys->length))
103     {
104         msg_Err(vd, "cannot allocate buffers: %s", vlc_strerror_c(errno));
105         return NULL;
106     }
107
108     sys->base = mmap(NULL, sys->length, PROT_READ|PROT_WRITE, MAP_SHARED,
109                      sys->fd, 0);
110     if (sys->base == MAP_FAILED)
111     {
112         msg_Err(vd, "cannot map buffers: %s", vlc_strerror_c(errno));
113         goto error;
114     }
115
116     sys->shm_pool = wl_shm_create_pool(sys->shm, sys->fd, sys->length);
117     if (sys->shm_pool == NULL)
118         goto error;
119
120     picture_t *pics[MAX_PICTURES];
121     picture_resource_t res = {
122         .pf_destroy = PictureDestroy,
123         .p = {
124             [0] = {
125                 .i_lines = lines,
126                 .i_pitch = stride,
127             },
128         },
129     };
130     size_t offset = 4 * vd->fmt.i_x_offset + stride * vd->fmt.i_y_offset;
131     unsigned width = vd->fmt.i_visible_width;
132     unsigned height = vd->fmt.i_visible_height;
133     unsigned count = 0;
134
135     while (count < req)
136     {
137         struct wl_buffer *buf;
138
139         buf = wl_shm_pool_create_buffer(sys->shm_pool, offset, width, height,
140                                         stride, WL_SHM_FORMAT_XRGB8888);
141         if (buf == NULL)
142             break;
143
144         res.p_sys = (picture_sys_t *)buf;
145         res.p[0].p_pixels = sys->base + offset;
146         offset += picsize;
147
148         picture_t *pic = picture_NewFromResource(&vd->fmt, &res);
149         if (unlikely(pic == NULL))
150         {
151             wl_buffer_destroy(buf);
152             break;
153         }
154
155         wl_buffer_add_listener(buf, &buffer_cbs, pic);
156         pics[count++] = pic;
157     }
158
159     if (count == 0)
160     {
161         wl_shm_pool_destroy(sys->shm_pool);
162         munmap(sys->base, sys->length);
163         goto error;
164     }
165
166     wl_display_flush(sys->embed->display.wl);
167
168     sys->pool = picture_pool_New (count, pics);
169     if (unlikely(sys->pool == NULL))
170     {
171         while (count > 0)
172             picture_Release(pics[--count]);
173         wl_shm_pool_destroy(sys->shm_pool);
174         goto error;
175     }
176     return sys->pool;
177
178 error:
179     if (sys->base != MAP_FAILED)
180         munmap(sys->base, sys->length);
181     ftruncate(sys->fd, 0); /* "free" memory */
182     return NULL;
183 }
184
185 static void Prepare(vout_display_t *vd, picture_t *pic, subpicture_t *subpic)
186 {
187     vout_display_sys_t *sys = vd->sys;
188     struct wl_display *display = sys->embed->display.wl;
189     struct wl_surface *surface = sys->embed->handle.wl;
190     struct wl_buffer *buf = (struct wl_buffer *)pic->p_sys;
191
192     wl_surface_attach(surface, buf, sys->x, sys->y);
193     wl_surface_damage(surface, 0, 0,
194                       vd->fmt.i_visible_width, vd->fmt.i_visible_height);
195     wl_display_flush(display);
196
197     sys->x = 0;
198     sys->y = 0;
199
200     (void) subpic;
201 }
202
203 static void Display(vout_display_t *vd, picture_t *pic, subpicture_t *subpic)
204 {
205     vout_display_sys_t *sys = vd->sys;
206     struct wl_display *display = sys->embed->display.wl;
207     struct wl_surface *surface = sys->embed->handle.wl;
208
209     wl_surface_commit(surface);
210     // FIXME: deadlocks here
211     wl_display_roundtrip(display);
212
213     (void) pic; (void) subpic;
214 }
215
216 static void ResetPictures(vout_display_t *vd)
217 {
218     vout_display_sys_t *sys = vd->sys;
219
220     if (sys->pool == NULL)
221         return;
222
223     picture_pool_Delete(sys->pool);
224     wl_shm_pool_destroy(sys->shm_pool);
225     munmap(sys->base, sys->length);
226
227     sys->pool = NULL;
228 }
229
230 static int Control(vout_display_t *vd, int query, va_list ap)
231 {
232     vout_display_sys_t *sys = vd->sys;
233
234     switch (query)
235     {
236         case VOUT_DISPLAY_HIDE_MOUSE:
237             /* TODO */
238             return VLC_EGENERIC;
239
240         case VOUT_DISPLAY_RESET_PICTURES:
241         {
242             vout_display_place_t place;
243             video_format_t src;
244
245             sys->x -= vd->fmt.i_x_offset;
246             sys->y -= vd->fmt.i_y_offset;
247
248             vout_display_PlacePicture(&place, &vd->source, vd->cfg, false);
249             video_format_ApplyRotation(&src, &vd->source);
250
251             vd->fmt.i_width  = src.i_width  * place.width
252                                             / src.i_visible_width;
253             vd->fmt.i_height = src.i_height * place.height
254                                             / src.i_visible_height;
255             vd->fmt.i_visible_width  = place.width;
256             vd->fmt.i_visible_height = place.height;
257             vd->fmt.i_x_offset = src.i_x_offset * place.width
258                                                 / src.i_visible_width;
259             vd->fmt.i_y_offset = src.i_y_offset * place.height
260                                                 / src.i_visible_height;
261
262             sys->x += vd->fmt.i_x_offset;
263             sys->y += vd->fmt.i_y_offset;
264
265             ResetPictures(vd);
266             break;
267         }
268
269         case VOUT_DISPLAY_CHANGE_FULLSCREEN:
270         {
271             const vout_display_cfg_t *cfg =
272                 va_arg(ap, const vout_display_cfg_t *);
273             return vout_window_SetFullScreen(sys->embed, cfg->is_fullscreen);
274         }
275
276         case VOUT_DISPLAY_CHANGE_WINDOW_STATE:
277         {
278             unsigned state = va_arg(ap, unsigned);
279             return vout_window_SetState(sys->embed, state);
280         }
281
282         case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
283         {
284             const vout_display_cfg_t *cfg =
285                 va_arg(ap, const vout_display_cfg_t *);
286             const bool forced = va_arg(ap, int);
287
288             if (forced)
289             {
290                 vout_display_SendEventDisplaySize(vd, cfg->display.width,
291                                                   cfg->display.height,
292                                                   vd->cfg->is_fullscreen);
293                 return VLC_EGENERIC;
294             }
295
296             vout_display_place_t place;
297             vout_display_PlacePicture(&place, &vd->source, cfg, false);
298
299             if (place.width == vd->fmt.i_visible_width
300              && place.height == vd->fmt.i_visible_height)
301                 break;
302             /* fall through */
303         }
304         case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
305         case VOUT_DISPLAY_CHANGE_ZOOM:
306         case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
307         case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
308              vout_display_SendEventPicturesInvalid(vd);
309              break;
310
311         default:
312              msg_Err(vd, "unknown request %d", query);
313              return VLC_EGENERIC;
314     }
315     return VLC_SUCCESS;
316 }
317
318 static void registry_global_cb(void *data, struct wl_registry *registry,
319                                uint32_t name, const char *iface, uint32_t vers)
320 {
321     vout_display_t *vd = data;
322     vout_display_sys_t *sys = vd->sys;
323
324     msg_Dbg(vd, "global %3"PRIu32": %s version %"PRIu32, name, iface, vers);
325
326     if (!strcmp(iface, "wl_shm"))
327         sys->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
328 }
329
330 static void registry_global_remove_cb(void *data, struct wl_registry *registry,
331                                       uint32_t name)
332 {
333     vout_display_t *vd = data;
334
335     msg_Dbg(vd, "global remove %3"PRIu32, name);
336     (void) registry;
337 }
338
339 static const struct wl_registry_listener registry_cbs =
340 {
341     registry_global_cb,
342     registry_global_remove_cb,
343 };
344
345 static int Open(vlc_object_t *obj)
346 {
347     vout_display_t *vd = (vout_display_t *)obj;
348     vout_display_sys_t *sys = malloc(sizeof (*sys));
349     if (unlikely(sys == NULL))
350         return VLC_ENOMEM;
351
352     vd->sys = sys;
353     sys->embed = NULL;
354     sys->shm = NULL;
355     sys->pool = NULL;
356     sys->fd = -1;
357     sys->x = 0;
358     sys->y = 0;
359
360     char bufpath[] = "/tmp/"PACKAGE_NAME"XXXXXX";
361     sys->fd = mkostemp(bufpath, O_CLOEXEC);
362     if (sys->fd == -1)
363     {
364         msg_Err(vd, "cannot create buffers: %s", vlc_strerror_c(errno));
365         goto error;
366     }
367     unlink(bufpath);
368
369     /* Get window */
370     vout_window_cfg_t wcfg = {
371         .type = VOUT_WINDOW_TYPE_WAYLAND,
372         .width  = vd->cfg->display.width,
373         .height = vd->cfg->display.height,
374     };
375     sys->embed = vout_display_NewWindow(vd, &wcfg);
376     if (sys->embed == NULL)
377         goto error;
378
379     struct wl_display *display = sys->embed->display.wl;
380     struct wl_registry *registry = wl_display_get_registry(display);
381     if (registry == NULL)
382         goto error;
383
384     wl_registry_add_listener(registry, &registry_cbs, vd);
385     wl_display_roundtrip(display);
386     wl_registry_destroy(registry);
387
388     if (sys->shm == NULL)
389         goto error;
390
391     /* Determine our pixel format */
392     video_format_t fmt_pic;
393
394     video_format_ApplyRotation(&fmt_pic, &vd->fmt);
395     fmt_pic.i_chroma = VLC_CODEC_RGB32;
396
397     vd->info.has_pictures_invalid = true;
398     vd->info.has_event_thread = true;
399
400     vd->fmt = fmt_pic;
401     vd->pool = Pool;
402     vd->prepare = Prepare;
403     vd->display = Display;
404     vd->control = Control;
405     vd->manage = NULL;
406
407     bool is_fullscreen = vd->cfg->is_fullscreen;
408     if (is_fullscreen && vout_window_SetFullScreen(sys->embed, true))
409         is_fullscreen = false;
410     vout_display_SendEventFullscreen(vd, is_fullscreen);
411     vout_display_SendEventDisplaySize(vd, vd->cfg->display.width,
412                                       vd->cfg->display.height, is_fullscreen);
413     return VLC_SUCCESS;
414
415 error:
416     if (sys->embed != NULL)
417         vout_display_DeleteWindow(vd, sys->embed);
418     if (sys->fd != -1)
419         close(sys->fd);
420     free(sys);
421     return VLC_EGENERIC;
422 }
423
424 static void Close(vlc_object_t *obj)
425 {
426     vout_display_t *vd = (vout_display_t *)obj;
427     vout_display_sys_t *sys = vd->sys;
428
429     ResetPictures(vd);
430
431     wl_shm_destroy(sys->shm);
432     vout_display_DeleteWindow(vd, sys->embed);
433     close(sys->fd);
434     free(sys);
435 }
436
437 vlc_module_begin()
438     set_shortname(N_("WL SHM"))
439     set_description(N_("Wayland shared memory video output"))
440     set_category(CAT_VIDEO)
441     set_subcategory(SUBCAT_VIDEO_VOUT)
442     set_capability("vout display", 120)
443     set_callbacks(Open, Close)
444     add_shortcut("wl")
445 vlc_module_end()