1 /*****************************************************************************
2  * chroma.c: VLC picture to VAAPI surface or vice versa
3  *****************************************************************************
4  * Copyright (C) 2017 VLC authors, VideoLAN and VideoLabs
5  *
6  * Author: Victorien Le Couviour--Tuffet <victorien.lecouviour.tuffet@gmail.com>
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 
29 #include <va/va.h>
30 
31 #include <vlc_common.h>
32 #include <vlc_filter.h>
33 #include <vlc_plugin.h>
34 
35 #include "../../video_chroma/copy.h"
36 #include "filters.h"
37 
38 # define DEST_PICS_POOL_SZ 3
39 
40 struct filter_sys_t
41 {
42     struct vlc_vaapi_instance *va_inst;
43     VADisplay           dpy;
44     picture_pool_t *    dest_pics;
45     VASurfaceID *       va_surface_ids;
46     copy_cache_t        cache;
47 
48     bool                derive_failed;
49     bool                image_fallback_failed;
50 };
51 
CreateFallbackImage(filter_t * filter,picture_t * src_pic,VADisplay va_dpy,VAImage * image_fallback)52 static int CreateFallbackImage(filter_t *filter, picture_t *src_pic,
53                                VADisplay va_dpy, VAImage *image_fallback)
54 {
55     int count = vaMaxNumImageFormats(va_dpy);
56 
57     VAImageFormat *fmts = vlc_alloc(count, sizeof (*fmts));
58     if (unlikely(fmts == NULL))
59         return VLC_ENOMEM;
60 
61     if (vaQueryImageFormats(va_dpy, fmts, &count))
62     {
63         free(fmts);
64         return VLC_EGENERIC;
65     }
66 
67     int i;
68     for (i = 0; i < count; i++)
69         if (fmts[i].fourcc == VA_FOURCC_NV12
70          || fmts[i].fourcc == VA_FOURCC_P010)
71             break;
72 
73     int ret;
74     if ((fmts[i].fourcc == VA_FOURCC_NV12 || fmts[i].fourcc == VA_FOURCC_P010)
75      && !vlc_vaapi_CreateImage(VLC_OBJECT(filter), va_dpy, &fmts[i],
76                                src_pic->format.i_width, src_pic->format.i_height,
77                                image_fallback))
78         ret = VLC_SUCCESS;
79     else
80         ret = VLC_EGENERIC;
81 
82     free(fmts);
83 
84     return ret;
85 }
86 
87 static inline void
FillPictureFromVAImage(picture_t * dest,VAImage * src_img,uint8_t * src_buf,copy_cache_t * cache)88 FillPictureFromVAImage(picture_t *dest,
89                        VAImage *src_img, uint8_t *src_buf, copy_cache_t *cache)
90 {
91     const uint8_t * src_planes[2] = { src_buf + src_img->offsets[0],
92                                       src_buf + src_img->offsets[1] };
93     const size_t    src_pitches[2] = { src_img->pitches[0],
94                                        src_img->pitches[1] };
95 
96     switch (src_img->format.fourcc)
97     {
98     case VA_FOURCC_NV12:
99     {
100         assert(dest->format.i_chroma == VLC_CODEC_I420);
101         Copy420_SP_to_P(dest, src_planes, src_pitches, src_img->height, cache);
102         break;
103     }
104     case VA_FOURCC_P010:
105         switch (dest->format.i_chroma)
106         {
107             case VLC_CODEC_P010:
108                 Copy420_SP_to_SP(dest, src_planes, src_pitches, src_img->height,
109                                  cache);
110                 break;
111             case VLC_CODEC_I420_10L:
112                 Copy420_16_SP_to_P(dest, src_planes, src_pitches,
113                                    src_img->height, 6, cache);
114                 break;
115             default:
116                 vlc_assert_unreachable();
117         }
118         break;
119     default:
120         vlc_assert_unreachable();
121         break;
122     }
123 }
124 
125 static picture_t *
DownloadSurface(filter_t * filter,picture_t * src_pic)126 DownloadSurface(filter_t *filter, picture_t *src_pic)
127 {
128     filter_sys_t *const filter_sys = filter->p_sys;
129     VADisplay           va_dpy = vlc_vaapi_PicGetDisplay(src_pic);
130     VAImage             src_img;
131     void *              src_buf;
132 
133     picture_t *dest = filter_NewPicture(filter);
134     if (!dest)
135     {
136         msg_Err(filter, "filter_NewPicture failed");
137         goto ret;
138     }
139 
140     VAImageID image_fallback_id = VA_INVALID_ID;
141     VASurfaceID surface = vlc_vaapi_PicGetSurface(src_pic);
142     if (vaSyncSurface(va_dpy, surface))
143         goto error;
144 
145     if (filter_sys->derive_failed ||
146         vlc_vaapi_DeriveImage(VLC_OBJECT(filter), va_dpy, surface, &src_img))
147     {
148         if (filter_sys->image_fallback_failed)
149             goto error;
150 
151         filter_sys->derive_failed = true;
152 
153         VAImage image_fallback;
154         if (CreateFallbackImage(filter, src_pic, va_dpy, &image_fallback))
155         {
156             filter_sys->image_fallback_failed = true;
157             goto error;
158         }
159         image_fallback_id = image_fallback.image_id;
160 
161         if (vaGetImage(va_dpy, surface, 0, 0, src_pic->format.i_width,
162                        src_pic->format.i_height, image_fallback_id))
163         {
164             filter_sys->image_fallback_failed = true;
165             goto error;
166         }
167         src_img = image_fallback;
168     }
169 
170     if (vlc_vaapi_MapBuffer(VLC_OBJECT(filter), va_dpy, src_img.buf, &src_buf))
171         goto error;
172 
173     FillPictureFromVAImage(dest, &src_img, src_buf, &filter->p_sys->cache);
174 
175     vlc_vaapi_UnmapBuffer(VLC_OBJECT(filter), va_dpy, src_img.buf);
176     vlc_vaapi_DestroyImage(VLC_OBJECT(filter), va_dpy, src_img.image_id);
177 
178     picture_CopyProperties(dest, src_pic);
179 ret:
180     picture_Release(src_pic);
181     return dest;
182 
183 error:
184     if (image_fallback_id != VA_INVALID_ID)
185         vlc_vaapi_DestroyImage(VLC_OBJECT(filter), va_dpy, image_fallback_id);
186 
187     picture_Release(dest);
188     dest = NULL;
189     goto ret;
190 }
191 
192 static inline void
FillVAImageFromPicture(VAImage * dest_img,uint8_t * dest_buf,picture_t * dest_pic,picture_t * src,copy_cache_t * cache)193 FillVAImageFromPicture(VAImage *dest_img, uint8_t *dest_buf,
194                        picture_t *dest_pic, picture_t *src,
195                        copy_cache_t *cache)
196 {
197     const uint8_t * src_planes[3] = { src->p[Y_PLANE].p_pixels,
198                                       src->p[U_PLANE].p_pixels,
199                                       src->p[V_PLANE].p_pixels };
200     const size_t    src_pitches[3] = { src->p[Y_PLANE].i_pitch,
201                                        src->p[U_PLANE].i_pitch,
202                                        src->p[V_PLANE].i_pitch };
203     void *const     tmp[2] = { dest_pic->p[0].p_pixels,
204                                dest_pic->p[1].p_pixels };
205 
206     dest_pic->p[0].p_pixels = dest_buf + dest_img->offsets[0];
207     dest_pic->p[1].p_pixels = dest_buf + dest_img->offsets[1];
208     dest_pic->p[0].i_pitch = dest_img->pitches[0];
209     dest_pic->p[1].i_pitch = dest_img->pitches[1];
210 
211     switch (src->format.i_chroma)
212     {
213     case VLC_CODEC_I420:
214         assert(dest_pic->format.i_chroma == VLC_CODEC_VAAPI_420);
215         Copy420_P_to_SP(dest_pic, src_planes, src_pitches,
216                         src->format.i_height, cache);
217 
218         break;
219     case VLC_CODEC_I420_10L:
220         assert(dest_pic->format.i_chroma == VLC_CODEC_VAAPI_420_10BPP);
221         Copy420_16_P_to_SP(dest_pic, src_planes, src_pitches,
222                            src->format.i_height, -6, cache);
223         break;
224     case VLC_CODEC_P010:
225     {
226         assert(dest_pic->format.i_chroma == VLC_CODEC_VAAPI_420_10BPP);
227         Copy420_SP_to_SP(dest_pic,  src_planes, src_pitches,
228                          src->format.i_height, cache);
229         break;
230     }
231     default:
232         vlc_assert_unreachable();
233     }
234 
235     dest_pic->p[0].p_pixels = tmp[0];
236     dest_pic->p[1].p_pixels = tmp[1];
237 }
238 
239 static picture_t *
UploadSurface(filter_t * filter,picture_t * src)240 UploadSurface(filter_t *filter, picture_t *src)
241 {
242     VADisplay const va_dpy = filter->p_sys->dpy;
243     VAImage         dest_img;
244     void *          dest_buf;
245     picture_t *     dest_pic = picture_pool_Wait(filter->p_sys->dest_pics);
246 
247     if (!dest_pic)
248     {
249         msg_Err(filter, "cannot retrieve picture from the dest pics pool");
250         goto ret;
251     }
252     vlc_vaapi_PicAttachContext(dest_pic);
253     picture_CopyProperties(dest_pic, src);
254 
255     if (vlc_vaapi_DeriveImage(VLC_OBJECT(filter), va_dpy,
256                               vlc_vaapi_PicGetSurface(dest_pic), &dest_img)
257         || vlc_vaapi_MapBuffer(VLC_OBJECT(filter), va_dpy,
258                                dest_img.buf, &dest_buf))
259         goto error;
260 
261     FillVAImageFromPicture(&dest_img, dest_buf, dest_pic,
262                            src, &filter->p_sys->cache);
263 
264     if (vlc_vaapi_UnmapBuffer(VLC_OBJECT(filter), va_dpy, dest_img.buf)
265         || vlc_vaapi_DestroyImage(VLC_OBJECT(filter),
266                                   va_dpy, dest_img.image_id))
267         goto error;
268 
269 ret:
270     picture_Release(src);
271     return dest_pic;
272 
273 error:
274     picture_Release(dest_pic);
275     dest_pic = NULL;
276     goto ret;
277 }
278 
CheckFmt(const video_format_t * in,const video_format_t * out,bool * upload,uint8_t * pixel_bytes)279 static int CheckFmt(const video_format_t *in, const video_format_t *out,
280                     bool *upload, uint8_t *pixel_bytes)
281 {
282     *pixel_bytes = 1;
283     *upload = false;
284     switch (in->i_chroma)
285     {
286         case VLC_CODEC_VAAPI_420:
287             if (out->i_chroma == VLC_CODEC_I420)
288                 return VLC_SUCCESS;
289             break;
290         case VLC_CODEC_VAAPI_420_10BPP:
291             if (out->i_chroma == VLC_CODEC_P010
292              || out->i_chroma == VLC_CODEC_I420_10L)
293             {
294                 *pixel_bytes = 2;
295                 return VLC_SUCCESS;
296             }
297             break;
298     }
299 
300     *upload = true;
301     switch (out->i_chroma)
302     {
303         case VLC_CODEC_VAAPI_420:
304             if (in->i_chroma == VLC_CODEC_I420)
305                 return VLC_SUCCESS;
306             break;
307         case VLC_CODEC_VAAPI_420_10BPP:
308             if (in->i_chroma == VLC_CODEC_P010
309              || in->i_chroma == VLC_CODEC_I420_10L)
310             {
311                 *pixel_bytes = 2;
312                 return VLC_SUCCESS;
313             }
314             break;
315     }
316     return VLC_EGENERIC;
317 }
318 
319 int
vlc_vaapi_OpenChroma(vlc_object_t * obj)320 vlc_vaapi_OpenChroma(vlc_object_t *obj)
321 {
322     filter_t *const     filter = (filter_t *)obj;
323     filter_sys_t *      filter_sys;
324 
325     if (filter->fmt_in.video.i_height != filter->fmt_out.video.i_height
326      || filter->fmt_in.video.i_width != filter->fmt_out.video.i_width
327      || filter->fmt_in.video.orientation != filter->fmt_out.video.orientation)
328         return VLC_EGENERIC;
329 
330     bool is_upload;
331     uint8_t pixel_bytes;
332     if (CheckFmt(&filter->fmt_in.video, &filter->fmt_out.video, &is_upload,
333                  &pixel_bytes))
334         return VLC_EGENERIC;
335 
336     filter->pf_video_filter = is_upload ? UploadSurface : DownloadSurface;
337 
338     if (!(filter_sys = calloc(1, sizeof(filter_sys_t))))
339     {
340         msg_Err(obj, "unable to allocate memory");
341         return VLC_ENOMEM;
342     }
343     filter_sys->derive_failed = false;
344     filter_sys->image_fallback_failed = false;
345     if (is_upload)
346     {
347         filter_sys->va_inst = vlc_vaapi_FilterHoldInstance(filter,
348                                                            &filter_sys->dpy);
349 
350         if (filter_sys->va_inst == NULL)
351         {
352             free(filter_sys);
353             return VLC_EGENERIC;
354         }
355 
356         filter_sys->dest_pics =
357             vlc_vaapi_PoolNew(obj, filter_sys->va_inst, filter_sys->dpy,
358                               DEST_PICS_POOL_SZ, &filter_sys->va_surface_ids,
359                               &filter->fmt_out.video, true);
360         if (!filter_sys->dest_pics)
361         {
362             vlc_vaapi_FilterReleaseInstance(filter, filter_sys->va_inst);
363             free(filter_sys);
364             return VLC_EGENERIC;
365         }
366     }
367     else
368     {
369         /* Don't fetch the vaapi instance since it may be not created yet at
370          * this point (in case of cpu rendering) */
371         filter_sys->va_inst = NULL;
372         filter_sys->dpy = NULL;
373         filter_sys->dest_pics = NULL;
374     }
375 
376     if (CopyInitCache(&filter_sys->cache, filter->fmt_in.video.i_width
377                       * pixel_bytes))
378     {
379         if (is_upload)
380         {
381             picture_pool_Release(filter_sys->dest_pics);
382             vlc_vaapi_FilterReleaseInstance(filter, filter_sys->va_inst);
383         }
384         free(filter_sys);
385         return VLC_EGENERIC;
386     }
387 
388     filter->p_sys = filter_sys;
389     msg_Warn(obj, "Using SW chroma filter for %dx%d %4.4s -> %4.4s",
390              filter->fmt_in.video.i_width,
391              filter->fmt_in.video.i_height,
392              (const char *) &filter->fmt_in.video.i_chroma,
393              (const char *) &filter->fmt_out.video.i_chroma);
394 
395     return VLC_SUCCESS;
396 }
397 
398 void
vlc_vaapi_CloseChroma(vlc_object_t * obj)399 vlc_vaapi_CloseChroma(vlc_object_t *obj)
400 {
401     filter_t *filter = (filter_t *)obj;
402     filter_sys_t *const filter_sys = filter->p_sys;
403 
404     if (filter_sys->dest_pics)
405         picture_pool_Release(filter_sys->dest_pics);
406     if (filter_sys->va_inst != NULL)
407         vlc_vaapi_FilterReleaseInstance(filter, filter_sys->va_inst);
408     CopyCleanCache(&filter_sys->cache);
409 
410     free(filter_sys);
411 }
412