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