1 /*****************************************************************************
2  * converter_vaapi.c: OpenGL VAAPI opaque converter
3  *****************************************************************************
4  * Copyright (C) 2017 VLC authors and VideoLAN
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation; either version 2.1 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19  *****************************************************************************/
20 
21 #ifdef HAVE_CONFIG_H
22 # include "config.h"
23 #endif
24 
25 #include "converter.h"
26 #include "../../hw/vaapi/vlc_vaapi.h"
27 #include <vlc_vout_window.h>
28 
29 #include <assert.h>
30 
31 #include <EGL/egl.h>
32 #include <EGL/eglext.h>
33 #include <va/va_drmcommon.h>
34 
35 #ifdef HAVE_VA_WL
36 # include <va/va_wayland.h>
37 #endif
38 
39 #ifdef HAVE_VA_X11
40 # include <va/va_x11.h>
41 # include <vlc_xlib.h>
42 #endif
43 
44 #ifdef HAVE_VA_DRM
45 # include <va/va_drm.h>
46 # include <vlc_fs.h>
47 # include <fcntl.h>
48 #endif
49 
50 struct priv
51 {
52     struct vlc_vaapi_instance *vainst;
53     VADisplay vadpy;
54     VASurfaceID *va_surface_ids;
55     PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;
56 
57     unsigned fourcc;
58     EGLint drm_fourccs[3];
59 
60     struct {
61         picture_t *  pic;
62         VAImage      va_image;
63         VABufferInfo va_buffer_info;
64         void *       egl_images[3];
65     } last;
66 };
67 
68 static EGLImageKHR
vaegl_image_create(const opengl_tex_converter_t * tc,EGLint w,EGLint h,EGLint fourcc,EGLint fd,EGLint offset,EGLint pitch)69 vaegl_image_create(const opengl_tex_converter_t *tc, EGLint w, EGLint h,
70                    EGLint fourcc, EGLint fd, EGLint offset, EGLint pitch)
71 {
72     EGLint attribs[] = {
73         EGL_WIDTH, w,
74         EGL_HEIGHT, h,
75         EGL_LINUX_DRM_FOURCC_EXT, fourcc,
76         EGL_DMA_BUF_PLANE0_FD_EXT, fd,
77         EGL_DMA_BUF_PLANE0_OFFSET_EXT, offset,
78         EGL_DMA_BUF_PLANE0_PITCH_EXT, pitch,
79         EGL_NONE
80     };
81 
82     return tc->gl->egl.createImageKHR(tc->gl, EGL_LINUX_DMA_BUF_EXT, NULL,
83                                       attribs);
84 }
85 
86 static void
vaegl_image_destroy(const opengl_tex_converter_t * tc,EGLImageKHR image)87 vaegl_image_destroy(const opengl_tex_converter_t *tc, EGLImageKHR image)
88 {
89     tc->gl->egl.destroyImageKHR(tc->gl, image);
90 }
91 
92 static void
vaegl_release_last_pic(const opengl_tex_converter_t * tc,struct priv * priv)93 vaegl_release_last_pic(const opengl_tex_converter_t *tc, struct priv *priv)
94 {
95     vlc_object_t *o = VLC_OBJECT(tc->gl);
96 
97     for (unsigned i = 0; i < priv->last.va_image.num_planes; ++i)
98         vaegl_image_destroy(tc, priv->last.egl_images[i]);
99 
100     vlc_vaapi_ReleaseBufferHandle(o, priv->vadpy, priv->last.va_image.buf);
101 
102     vlc_vaapi_DestroyImage(o, priv->vadpy, priv->last.va_image.image_id);
103 
104     picture_Release(priv->last.pic);
105 }
106 
107 static int
vaegl_init_fourcc(const opengl_tex_converter_t * tc,struct priv * priv,unsigned va_fourcc)108 vaegl_init_fourcc(const opengl_tex_converter_t *tc, struct priv *priv,
109                   unsigned va_fourcc)
110 {
111     (void) tc;
112     switch (va_fourcc)
113     {
114         case VA_FOURCC_NV12:
115             priv->drm_fourccs[0] = VLC_FOURCC('R', '8', ' ', ' ');
116             priv->drm_fourccs[1] = VLC_FOURCC('G', 'R', '8', '8');
117             break;
118         case VA_FOURCC_P010:
119             priv->drm_fourccs[0] = VLC_FOURCC('R', '1', '6', ' ');
120             priv->drm_fourccs[1] = VLC_FOURCC('G', 'R', '3', '2');
121             break;
122 #if 0
123         /* TODO: the following fourcc are not handled for now */
124         case VA_FOURCC_RGBA:
125             priv->drm_fourccs[0] = VLC_FOURCC('G', 'R', '3', '2');
126             break;
127         case VA_FOURCC_BGRA:
128             priv->drm_fourccs[0] = VLC_FOURCC('G', 'R', '3', '2');
129             break;
130         case VA_FOURCC_YV12:
131             priv->drm_fourccs[0] = VLC_FOURCC('R', '8', ' ', ' ');
132             priv->drm_fourccs[1] = VLC_FOURCC('R', '8', ' ', ' ');
133             priv->drm_fourccs[2] = VLC_FOURCC('R', '8', ' ', ' ');
134             break;
135         case VA_FOURCC_422H:
136             priv->drm_fourccs[0] = VLC_FOURCC('R', '8', ' ', ' ');
137             priv->drm_fourccs[1] = VLC_FOURCC('R', '8', ' ', ' ');
138             priv->drm_fourccs[2] = VLC_FOURCC('R', '8', ' ', ' ');
139             break;
140         case VA_FOURCC_UYVY:
141             priv->drm_fourccs[0] = VLC_FOURCC('R', '1', '6', ' ');
142             break;
143         case VA_FOURCC_444P:
144             priv->drm_fourccs[0] = VLC_FOURCC('R', '1', '6', ' ');
145             priv->drm_fourccs[1] = VLC_FOURCC('R', '1', '6', ' ');
146             priv->drm_fourccs[2] = VLC_FOURCC('R', '1', '6', ' ');
147             break;
148 #endif
149         default: return VLC_EGENERIC;
150     }
151     priv->fourcc = va_fourcc;
152     return VLC_SUCCESS;
153 }
154 
155 static int
tc_vaegl_update(const opengl_tex_converter_t * tc,GLuint * textures,const GLsizei * tex_width,const GLsizei * tex_height,picture_t * pic,const size_t * plane_offset)156 tc_vaegl_update(const opengl_tex_converter_t *tc, GLuint *textures,
157                 const GLsizei *tex_width, const GLsizei *tex_height,
158                 picture_t *pic, const size_t *plane_offset)
159 {
160     (void) plane_offset;
161     struct priv *priv = tc->priv;
162     vlc_object_t *o = VLC_OBJECT(tc->gl);
163     VAImage va_image;
164     VABufferInfo va_buffer_info;
165     EGLImageKHR egl_images[3] = { };
166     bool release_image = false, release_buffer_info = false;
167 
168     if (pic == priv->last.pic)
169     {
170         va_image = priv->last.va_image;
171         va_buffer_info = priv->last.va_buffer_info;
172         for (unsigned i = 0; i < priv->last.va_image.num_planes; ++i)
173             egl_images[i] = priv->last.egl_images[i];
174     }
175     else
176     {
177         if (vlc_vaapi_DeriveImage(o, priv->vadpy, vlc_vaapi_PicGetSurface(pic),
178                                   &va_image))
179             goto error;
180         release_image = true;
181 
182         assert(va_image.format.fourcc == priv->fourcc);
183 
184         va_buffer_info = (VABufferInfo) {
185             .mem_type = VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME
186         };
187         if (vlc_vaapi_AcquireBufferHandle(o, priv->vadpy, va_image.buf,
188                                           &va_buffer_info))
189             goto error;
190         release_buffer_info = true;
191     }
192 
193     for (unsigned i = 0; i < va_image.num_planes; ++i)
194     {
195         egl_images[i] =
196             vaegl_image_create(tc, tex_width[i], tex_height[i],
197                                priv->drm_fourccs[i], va_buffer_info.handle,
198                                va_image.offsets[i], va_image.pitches[i]);
199         if (egl_images[i] == NULL)
200             goto error;
201 
202         tc->vt->BindTexture(tc->tex_target, textures[i]);
203 
204         priv->glEGLImageTargetTexture2DOES(tc->tex_target, egl_images[i]);
205     }
206 
207     if (pic != priv->last.pic)
208     {
209         if (priv->last.pic != NULL)
210             vaegl_release_last_pic(tc, priv);
211         priv->last.pic = picture_Hold(pic);
212         priv->last.va_image = va_image;
213         priv->last.va_buffer_info = va_buffer_info;
214         for (unsigned i = 0; i < va_image.num_planes; ++i)
215             priv->last.egl_images[i] = egl_images[i];
216     }
217 
218     return VLC_SUCCESS;
219 
220 error:
221     if (release_image)
222     {
223         if (release_buffer_info)
224             vlc_vaapi_ReleaseBufferHandle(o, priv->vadpy, va_image.buf);
225 
226         for (unsigned i = 0; i < 3 && egl_images[i] != NULL; ++i)
227             vaegl_image_destroy(tc, egl_images[i]);
228 
229         vlc_vaapi_DestroyImage(o, priv->vadpy, va_image.image_id);
230     }
231     return VLC_EGENERIC;
232 }
233 
234 static picture_pool_t *
tc_vaegl_get_pool(const opengl_tex_converter_t * tc,unsigned requested_count)235 tc_vaegl_get_pool(const opengl_tex_converter_t *tc, unsigned requested_count)
236 {
237     vlc_object_t *o = VLC_OBJECT(tc->gl);
238     struct priv *priv = tc->priv;
239 
240     picture_pool_t *pool =
241         vlc_vaapi_PoolNew(VLC_OBJECT(tc->gl), priv->vainst, priv->vadpy,
242                           requested_count, &priv->va_surface_ids, &tc->fmt,
243                           true);
244     if (!pool)
245         return NULL;
246 
247     /* Check if a surface from the pool can be derived and displayed via dmabuf
248      * */
249     bool success = false;
250     VAImage va_image = { .image_id = VA_INVALID_ID };
251     if (vlc_vaapi_DeriveImage(o, priv->vadpy, priv->va_surface_ids[0],
252                               &va_image))
253         goto error;
254 
255     assert(va_image.format.fourcc == priv->fourcc);
256 
257     VABufferInfo va_buffer_info = (VABufferInfo) {
258         .mem_type = VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME
259     };
260     if (vlc_vaapi_AcquireBufferHandle(o ,priv->vadpy, va_image.buf,
261                                       &va_buffer_info))
262         goto error;
263 
264     for (unsigned i = 0; i < va_image.num_planes; ++i)
265     {
266         EGLint w = (va_image.width * tc->texs[i].w.num) / tc->texs[i].w.den;
267         EGLint h = (va_image.height * tc->texs[i].h.num) / tc->texs[i].h.den;
268         EGLImageKHR egl_image =
269             vaegl_image_create(tc, w, h, priv->drm_fourccs[i], va_buffer_info.handle,
270                                va_image.offsets[i], va_image.pitches[i]);
271         if (egl_image == NULL)
272         {
273             msg_Warn(o, "Can't create Image KHR: kernel too old ?");
274             goto error;
275         }
276         vaegl_image_destroy(tc, egl_image);
277     }
278 
279     success = true;
280 error:
281     if (va_image.image_id != VA_INVALID_ID)
282     {
283         if (va_image.buf != VA_INVALID_ID)
284             vlc_vaapi_ReleaseBufferHandle(o, priv->vadpy, va_image.buf);
285         vlc_vaapi_DestroyImage(o, priv->vadpy, va_image.image_id);
286     }
287     if (!success)
288     {
289         picture_pool_Release(pool);
290         pool = NULL;
291     }
292     return pool;
293 }
294 
295 static void
Close(vlc_object_t * obj)296 Close(vlc_object_t *obj)
297 {
298     opengl_tex_converter_t *tc = (void *)obj;
299     struct priv *priv = tc->priv;
300 
301     if (priv->last.pic != NULL)
302         vaegl_release_last_pic(tc, priv);
303 
304     vlc_vaapi_ReleaseInstance(priv->vainst);
305 
306     free(tc->priv);
307 }
308 
309 static int
tc_va_check_interop_blacklist(opengl_tex_converter_t * tc,VADisplay * vadpy)310 tc_va_check_interop_blacklist(opengl_tex_converter_t *tc, VADisplay *vadpy)
311 {
312     const char *vendor = vaQueryVendorString(vadpy);
313     if (vendor == NULL)
314         return VLC_SUCCESS;
315 
316 #define BL_SIZE_MAX 19
317     static const char blacklist_prefix[][BL_SIZE_MAX] = {
318         /* XXX: case insensitive and alphabetical order */
319         "mesa gallium vaapi",
320     };
321 
322     char vendor_prefix[BL_SIZE_MAX];
323     strncpy(vendor_prefix, vendor, BL_SIZE_MAX);
324     vendor_prefix[BL_SIZE_MAX - 1] = '\0';
325 
326     const char *found = bsearch(vendor_prefix, blacklist_prefix,
327                                 ARRAY_SIZE(blacklist_prefix),
328                                 BL_SIZE_MAX, (void *) strcasecmp);
329     if (found != NULL)
330     {
331         msg_Warn(tc->gl, "The '%s' driver is blacklisted: no interop", found);
332         return VLC_EGENERIC;
333     }
334 
335     return VLC_SUCCESS;
336 }
337 
338 #ifdef HAVE_VA_X11
339 static void
x11_native_destroy_cb(VANativeDisplay native)340 x11_native_destroy_cb(VANativeDisplay native)
341 {
342     XCloseDisplay(native);
343 }
344 
345 static int
x11_init_vaapi_instance(opengl_tex_converter_t * tc,struct priv * priv)346 x11_init_vaapi_instance(opengl_tex_converter_t *tc, struct priv *priv)
347 {
348     if (!vlc_xlib_init(VLC_OBJECT(tc->gl)))
349         return VLC_EGENERIC;
350 
351     Display *x11dpy = XOpenDisplay(tc->gl->surface->display.x11);
352     if (x11dpy == NULL)
353         return VLC_EGENERIC;
354 
355     priv->vadpy = vaGetDisplay(x11dpy);
356     if (priv->vadpy == NULL)
357     {
358         x11_native_destroy_cb(x11dpy);
359         return VLC_EGENERIC;
360     }
361 
362     priv->vainst = vlc_vaapi_InitializeInstance(VLC_OBJECT(tc->gl), priv->vadpy,
363                                                 x11dpy, x11_native_destroy_cb);
364     return priv->vainst != NULL ? VLC_SUCCESS : VLC_EGENERIC;
365 }
366 #endif
367 
368 #ifdef HAVE_VA_DRM
369 static int
drm_init_vaapi_instance(opengl_tex_converter_t * tc,struct priv * priv)370 drm_init_vaapi_instance(opengl_tex_converter_t *tc, struct priv *priv)
371 {
372     priv->vainst =
373         vlc_vaapi_InitializeInstanceDRM(VLC_OBJECT(tc->gl), vaGetDisplayDRM,
374                                         &priv->vadpy, NULL);
375     return priv->vainst != NULL ? VLC_SUCCESS : VLC_EGENERIC;
376 }
377 #endif
378 
379 #ifdef HAVE_VA_WL
380 static int
wl_init_vaapi_instance(opengl_tex_converter_t * tc,struct priv * priv)381 wl_init_vaapi_instance(opengl_tex_converter_t *tc, struct priv *priv)
382 {
383     priv->vadpy = vaGetDisplayWl(tc->gl->surface->display.wl);
384     if (priv->vadpy == NULL)
385         return VLC_EGENERIC;
386 
387     priv->vainst = vlc_vaapi_InitializeInstance(VLC_OBJECT(tc->gl), priv->vadpy,
388                                                 NULL, NULL);
389     return priv->vainst != NULL ? VLC_SUCCESS : VLC_EGENERIC;
390 }
391 #endif
392 
393 
394 static int
Open(vlc_object_t * obj)395 Open(vlc_object_t *obj)
396 {
397     opengl_tex_converter_t *tc = (void *) obj;
398 
399     if (!vlc_vaapi_IsChromaOpaque(tc->fmt.i_chroma)
400      || tc->gl->ext != VLC_GL_EXT_EGL
401      || tc->gl->egl.createImageKHR == NULL
402      || tc->gl->egl.destroyImageKHR == NULL)
403         return VLC_EGENERIC;
404 
405     if (!HasExtension(tc->glexts, "GL_OES_EGL_image"))
406         return VLC_EGENERIC;
407 
408     const char *eglexts = tc->gl->egl.queryString(tc->gl, EGL_EXTENSIONS);
409     if (eglexts == NULL || !HasExtension(eglexts, "EGL_EXT_image_dma_buf_import"))
410         return VLC_EGENERIC;
411 
412     struct priv *priv = tc->priv = calloc(1, sizeof(struct priv));
413     if (unlikely(tc->priv == NULL))
414         goto error;
415     priv->fourcc = 0;
416     priv->vainst = NULL;
417 
418     int va_fourcc;
419     int vlc_sw_chroma;
420     switch (tc->fmt.i_chroma)
421     {
422         case VLC_CODEC_VAAPI_420:
423             va_fourcc = VA_FOURCC_NV12;
424             vlc_sw_chroma = VLC_CODEC_NV12;
425             break;
426         case VLC_CODEC_VAAPI_420_10BPP:
427             va_fourcc = VA_FOURCC_P010;
428             vlc_sw_chroma = VLC_CODEC_P010;
429             break;
430         default:
431             vlc_assert_unreachable();
432     }
433 
434     if (vaegl_init_fourcc(tc, priv, va_fourcc))
435         goto error;
436 
437     priv->glEGLImageTargetTexture2DOES =
438         vlc_gl_GetProcAddress(tc->gl, "glEGLImageTargetTexture2DOES");
439     if (priv->glEGLImageTargetTexture2DOES == NULL)
440         goto error;
441 
442     int ret = VLC_EGENERIC;
443 #if defined (HAVE_VA_X11)
444     if (tc->gl->surface->type == VOUT_WINDOW_TYPE_XID)
445         ret = x11_init_vaapi_instance(tc, priv);
446 #elif defined(HAVE_VA_WL)
447     if (tc->gl->surface->type == VOUT_WINDOW_TYPE_WAYLAND)
448         ret = wl_init_vaapi_instance(tc, priv);
449 #elif defined (HAVE_VA_DRM)
450     ret = drm_init_vaapi_instance(tc, priv);
451 #else
452 # error need X11/WL/DRM support
453 #endif
454 
455     if (ret != VLC_SUCCESS)
456         goto error;
457 
458     assert(priv->vadpy != NULL && priv->vainst != NULL);
459 
460     if (tc_va_check_interop_blacklist(tc, priv->vadpy))
461         goto error;
462 
463     tc->fshader = opengl_fragment_shader_init(tc, GL_TEXTURE_2D, vlc_sw_chroma,
464                                               tc->fmt.space);
465     if (tc->fshader == 0)
466         goto error;
467 
468     tc->pf_update  = tc_vaegl_update;
469     tc->pf_get_pool = tc_vaegl_get_pool;
470 
471     return VLC_SUCCESS;
472 error:
473     if (priv && priv->vainst)
474         vlc_vaapi_ReleaseInstance(priv->vainst);
475     free(priv);
476     return VLC_EGENERIC;
477 }
478 
479 #if defined (HAVE_VA_X11)
480 # define PRIORITY 2
481 # define SHORTCUT "vaapi_x11"
482 # define DESCRIPTION_SUFFIX "X11"
483 #elif defined(HAVE_VA_WL)
484 # define PRIORITY 2
485 # define SHORTCUT "vaapi_wl"
486 # define DESCRIPTION_SUFFIX "Wayland"
487 #elif defined (HAVE_VA_DRM)
488 # define PRIORITY 1
489 # define SHORTCUT "vaapi_drm"
490 # define DESCRIPTION_SUFFIX "DRM"
491 #endif
492 
493 vlc_module_begin ()
494     set_description("VA-API OpenGL surface converter for " DESCRIPTION_SUFFIX)
495     set_capability("glconv", PRIORITY)
496     set_callbacks(Open, Close)
497     set_category(CAT_VIDEO)
498     set_subcategory(SUBCAT_VIDEO_VOUT)
499     add_shortcut("vaapi", SHORTCUT)
500 vlc_module_end ()
501