lavfi: Add filters to run arbitrary OpenCL programs
[ffmpeg.git] / libavfilter / vf_program_opencl.c
1 /*
2  * This file is part of FFmpeg.
3  *
4  * FFmpeg is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * FFmpeg is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with FFmpeg; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17  */
18
19 #include "libavutil/avstring.h"
20 #include "libavutil/buffer.h"
21 #include "libavutil/common.h"
22 #include "libavutil/hwcontext.h"
23 #include "libavutil/hwcontext_opencl.h"
24 #include "libavutil/log.h"
25 #include "libavutil/mem.h"
26 #include "libavutil/pixdesc.h"
27 #include "libavutil/opt.h"
28
29 #include "avfilter.h"
30 #include "framesync.h"
31 #include "internal.h"
32 #include "opencl.h"
33 #include "video.h"
34
35 typedef struct ProgramOpenCLContext {
36     OpenCLFilterContext ocf;
37
38     int                 loaded;
39     cl_uint             index;
40     cl_kernel           kernel;
41     cl_command_queue    command_queue;
42
43     FFFrameSync         fs;
44     AVFrame           **frames;
45
46     const char         *source_file;
47     const char         *kernel_name;
48     int                 nb_inputs;
49     int                 width, height;
50     enum AVPixelFormat  source_format;
51     AVRational          source_rate;
52 } ProgramOpenCLContext;
53
54 static int program_opencl_load(AVFilterContext *avctx)
55 {
56     ProgramOpenCLContext *ctx = avctx->priv;
57     cl_int cle;
58     int err;
59
60     err = ff_opencl_filter_load_program_from_file(avctx, ctx->source_file);
61     if (err < 0)
62         return err;
63
64     ctx->command_queue = clCreateCommandQueue(ctx->ocf.hwctx->context,
65                                               ctx->ocf.hwctx->device_id,
66                                               0, &cle);
67     if (!ctx->command_queue) {
68         av_log(avctx, AV_LOG_ERROR, "Failed to create OpenCL "
69                "command queue: %d.\n", cle);
70         return AVERROR(EIO);
71     }
72
73     ctx->kernel = clCreateKernel(ctx->ocf.program, ctx->kernel_name, &cle);
74     if (!ctx->kernel) {
75         if (cle == CL_INVALID_KERNEL_NAME) {
76             av_log(avctx, AV_LOG_ERROR, "Kernel function '%s' not found in "
77                    "program.\n", ctx->kernel_name);
78         } else {
79             av_log(avctx, AV_LOG_ERROR, "Failed to create kernel: %d.\n", cle);
80         }
81         return AVERROR(EIO);
82     }
83
84     ctx->loaded = 1;
85     return 0;
86 }
87
88 static int program_opencl_run(AVFilterContext *avctx)
89 {
90     AVFilterLink     *outlink = avctx->outputs[0];
91     ProgramOpenCLContext *ctx = avctx->priv;
92     AVFrame *output = NULL;
93     cl_int cle;
94     size_t global_work[2];
95     cl_mem src, dst;
96     int err, input, plane;
97
98     if (!ctx->loaded) {
99         err = program_opencl_load(avctx);
100         if (err < 0)
101             return err;
102     }
103
104     output = ff_get_video_buffer(outlink, outlink->w, outlink->h);
105     if (!output) {
106         err = AVERROR(ENOMEM);
107         goto fail;
108     }
109
110     for (plane = 0; plane < FF_ARRAY_ELEMS(output->data); plane++) {
111         dst = (cl_mem)output->data[plane];
112         if (!dst)
113             break;
114
115         cle = clSetKernelArg(ctx->kernel, 0, sizeof(cl_mem), &dst);
116         if (cle != CL_SUCCESS) {
117             av_log(avctx, AV_LOG_ERROR, "Failed to set kernel "
118                    "destination image argument: %d.\n", cle);
119             goto fail;
120         }
121         cle = clSetKernelArg(ctx->kernel, 1, sizeof(cl_uint), &ctx->index);
122         if (cle != CL_SUCCESS) {
123             av_log(avctx, AV_LOG_ERROR, "Failed to set kernel "
124                    "index argument: %d.\n", cle);
125             goto fail;
126         }
127
128         for (input = 0; input < ctx->nb_inputs; input++) {
129             av_assert0(ctx->frames[input]);
130
131             src = (cl_mem)ctx->frames[input]->data[plane];
132             av_assert0(src);
133
134             cle = clSetKernelArg(ctx->kernel, 2 + input, sizeof(cl_mem), &src);
135             if (cle != CL_SUCCESS) {
136                 av_log(avctx, AV_LOG_ERROR, "Failed to set kernel "
137                        "source image argument %d: %d.\n", input, cle);
138                 goto fail;
139             }
140         }
141
142         cle = clGetImageInfo(dst, CL_IMAGE_WIDTH,  sizeof(size_t),
143                              &global_work[0], NULL);
144         cle = clGetImageInfo(dst, CL_IMAGE_HEIGHT, sizeof(size_t),
145                              &global_work[1], NULL);
146
147         av_log(avctx, AV_LOG_DEBUG, "Run kernel on plane %d "
148                "(%zux%zu).\n", plane, global_work[0], global_work[1]);
149
150         cle = clEnqueueNDRangeKernel(ctx->command_queue, ctx->kernel, 2, NULL,
151                                      global_work, NULL, 0, NULL, NULL);
152         if (cle != CL_SUCCESS) {
153             av_log(avctx, AV_LOG_ERROR, "Failed to enqueue kernel: %d.\n",
154                    cle);
155             err = AVERROR(EIO);
156             goto fail;
157         }
158     }
159
160     cle = clFinish(ctx->command_queue);
161     if (cle != CL_SUCCESS) {
162         av_log(avctx, AV_LOG_ERROR, "Failed to finish command queue: %d.\n",
163                cle);
164         err = AVERROR(EIO);
165         goto fail;
166     }
167
168     if (ctx->nb_inputs > 0) {
169         err = av_frame_copy_props(output, ctx->frames[0]);
170         if (err < 0)
171             goto fail;
172     } else {
173         output->pts = ctx->index;
174     }
175     ++ctx->index;
176
177     av_log(ctx, AV_LOG_DEBUG, "Filter output: %s, %ux%u (%"PRId64").\n",
178            av_get_pix_fmt_name(output->format),
179            output->width, output->height, output->pts);
180
181     return ff_filter_frame(outlink, output);
182
183 fail:
184     clFinish(ctx->command_queue);
185     av_frame_free(&output);
186     return err;
187 }
188
189 static int program_opencl_request_frame(AVFilterLink *outlink)
190 {
191     AVFilterContext *avctx = outlink->src;
192
193     return program_opencl_run(avctx);
194 }
195
196 static int program_opencl_filter(FFFrameSync *fs)
197 {
198     AVFilterContext    *avctx = fs->parent;
199     ProgramOpenCLContext *ctx = avctx->priv;
200     int err, i;
201
202     for (i = 0; i < ctx->nb_inputs; i++) {
203         err = ff_framesync_get_frame(&ctx->fs, i, &ctx->frames[i], 0);
204         if (err < 0)
205             return err;
206     }
207
208     return program_opencl_run(avctx);
209 }
210
211 static int program_opencl_activate(AVFilterContext *avctx)
212 {
213     ProgramOpenCLContext *ctx = avctx->priv;
214
215     av_assert0(ctx->nb_inputs > 0);
216
217     return ff_framesync_activate(&ctx->fs);
218 }
219
220 static int program_opencl_config_output(AVFilterLink *outlink)
221 {
222     AVFilterContext    *avctx = outlink->src;
223     ProgramOpenCLContext *ctx = avctx->priv;
224     int err;
225
226     err = ff_opencl_filter_config_output(outlink);
227     if (err < 0)
228         return err;
229
230     if (ctx->nb_inputs > 0) {
231         FFFrameSyncIn *in;
232         int i;
233
234         err = ff_framesync_init(&ctx->fs, avctx, ctx->nb_inputs);
235         if (err < 0)
236             return err;
237
238         ctx->fs.opaque = ctx;
239         ctx->fs.on_event = &program_opencl_filter;
240
241         in = ctx->fs.in;
242         for (i = 0; i < ctx->nb_inputs; i++) {
243             const AVFilterLink *inlink = avctx->inputs[i];
244
245             in[i].time_base = inlink->time_base;
246             in[i].sync      = 1;
247             in[i].before    = EXT_STOP;
248             in[i].after     = EXT_INFINITY;
249         }
250
251         err = ff_framesync_configure(&ctx->fs);
252         if (err < 0)
253             return err;
254
255     } else {
256         outlink->time_base = av_inv_q(ctx->source_rate);
257     }
258
259     return 0;
260 }
261
262 static av_cold int program_opencl_init(AVFilterContext *avctx)
263 {
264     ProgramOpenCLContext *ctx = avctx->priv;
265     int err;
266
267     ff_opencl_filter_init(avctx);
268
269     ctx->ocf.output_width  = ctx->width;
270     ctx->ocf.output_height = ctx->height;
271
272     if (!strcmp(avctx->filter->name, "openclsrc")) {
273         if (!ctx->ocf.output_width || !ctx->ocf.output_height) {
274             av_log(avctx, AV_LOG_ERROR, "OpenCL source requires output "
275                    "dimensions to be specified.\n");
276             return AVERROR(EINVAL);
277         }
278
279         ctx->nb_inputs = 0;
280         ctx->ocf.output_format = ctx->source_format;
281     } else {
282         int i;
283
284         ctx->frames = av_mallocz_array(ctx->nb_inputs,
285                                        sizeof(*ctx->frames));
286         if (!ctx->frames)
287             return AVERROR(ENOMEM);
288
289         for (i = 0; i < ctx->nb_inputs; i++) {
290             AVFilterPad input;
291             memset(&input, 0, sizeof(input));
292
293             input.type = AVMEDIA_TYPE_VIDEO;
294             input.name = av_asprintf("input%d", i);
295             if (!input.name)
296                 return AVERROR(ENOMEM);
297
298             input.config_props = &ff_opencl_filter_config_input;
299
300             err = ff_insert_inpad(avctx, i, &input);
301             if (err < 0) {
302                 av_freep(&input.name);
303                 return err;
304             }
305         }
306     }
307
308     return 0;
309 }
310
311 static av_cold void program_opencl_uninit(AVFilterContext *avctx)
312 {
313     ProgramOpenCLContext *ctx = avctx->priv;
314     cl_int cle;
315     int i;
316
317     if (ctx->nb_inputs > 0) {
318         ff_framesync_uninit(&ctx->fs);
319
320         av_freep(&ctx->frames);
321         for (i = 0; i < avctx->nb_inputs; i++)
322             av_freep(&avctx->input_pads[i].name);
323     }
324
325     if (ctx->kernel) {
326         cle = clReleaseKernel(ctx->kernel);
327         if (cle != CL_SUCCESS)
328             av_log(avctx, AV_LOG_ERROR, "Failed to release "
329                    "kernel: %d.\n", cle);
330     }
331
332     if (ctx->command_queue) {
333         cle = clReleaseCommandQueue(ctx->command_queue);
334         if (cle != CL_SUCCESS)
335             av_log(avctx, AV_LOG_ERROR, "Failed to release "
336                    "command queue: %d.\n", cle);
337     }
338
339     ff_opencl_filter_uninit(avctx);
340 }
341
342 #define OFFSET(x) offsetof(ProgramOpenCLContext, x)
343 #define FLAGS (AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM)
344
345 #if CONFIG_PROGRAM_OPENCL_FILTER
346
347 static const AVOption program_opencl_options[] = {
348     { "source", "OpenCL program source file", OFFSET(source_file),
349       AV_OPT_TYPE_STRING, { .str = NULL }, .flags = FLAGS },
350     { "kernel", "Kernel name in program",     OFFSET(kernel_name),
351       AV_OPT_TYPE_STRING, { .str = NULL }, .flags = FLAGS },
352
353     { "inputs", "Number of inputs", OFFSET(nb_inputs),
354       AV_OPT_TYPE_INT,              { .i64 = 1 }, 1, INT_MAX, FLAGS },
355
356     { "size",   "Video size",       OFFSET(width),
357       AV_OPT_TYPE_IMAGE_SIZE,       { .str = NULL }, 0, 0, FLAGS },
358     { "s",      "Video size",       OFFSET(width),
359       AV_OPT_TYPE_IMAGE_SIZE,       { .str = NULL }, 0, 0, FLAGS },
360
361     { NULL },
362 };
363
364 FRAMESYNC_DEFINE_CLASS(program_opencl, ProgramOpenCLContext, fs);
365
366 static const AVFilterPad program_opencl_outputs[] = {
367     {
368         .name         = "default",
369         .type         = AVMEDIA_TYPE_VIDEO,
370         .config_props = &program_opencl_config_output,
371     },
372     { NULL }
373 };
374
375 AVFilter ff_vf_program_opencl = {
376     .name           = "program_opencl",
377     .description    = NULL_IF_CONFIG_SMALL("Filter video using an OpenCL program"),
378     .priv_size      = sizeof(ProgramOpenCLContext),
379     .priv_class     = &program_opencl_class,
380     .preinit        = &program_opencl_framesync_preinit,
381     .init           = &program_opencl_init,
382     .uninit         = &program_opencl_uninit,
383     .query_formats  = &ff_opencl_filter_query_formats,
384     .activate       = &program_opencl_activate,
385     .inputs         = NULL,
386     .outputs        = program_opencl_outputs,
387     .flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE,
388 };
389
390 #endif
391
392 #if CONFIG_OPENCLSRC_FILTER
393
394 static const AVOption openclsrc_options[] = {
395     { "source", "OpenCL program source file", OFFSET(source_file),
396       AV_OPT_TYPE_STRING, { .str = NULL }, .flags = FLAGS },
397     { "kernel", "Kernel name in program",     OFFSET(kernel_name),
398       AV_OPT_TYPE_STRING, { .str = NULL }, .flags = FLAGS },
399
400     { "size",   "Video size",       OFFSET(width),
401       AV_OPT_TYPE_IMAGE_SIZE,       { .str = NULL }, 0, 0, FLAGS },
402     { "s",      "Video size",       OFFSET(width),
403       AV_OPT_TYPE_IMAGE_SIZE,       { .str = NULL }, 0, 0, FLAGS },
404
405     { "format", "Video format",     OFFSET(source_format),
406       AV_OPT_TYPE_PIXEL_FMT,        { .i64 = AV_PIX_FMT_NONE }, -1, INT_MAX, FLAGS },
407
408     { "rate",   "Video frame rate", OFFSET(source_rate),
409       AV_OPT_TYPE_VIDEO_RATE,       { .str = "25" }, 0, INT_MAX, FLAGS },
410     { "r",      "Video frame rate", OFFSET(source_rate),
411       AV_OPT_TYPE_VIDEO_RATE,       { .str = "25" }, 0, INT_MAX, FLAGS },
412
413     { NULL },
414 };
415
416 AVFILTER_DEFINE_CLASS(openclsrc);
417
418 static const AVFilterPad openclsrc_outputs[] = {
419     {
420         .name          = "default",
421         .type          = AVMEDIA_TYPE_VIDEO,
422         .config_props  = &program_opencl_config_output,
423         .request_frame = &program_opencl_request_frame,
424     },
425     { NULL }
426 };
427
428 AVFilter ff_vsrc_openclsrc = {
429     .name           = "openclsrc",
430     .description    = NULL_IF_CONFIG_SMALL("Generate video using an OpenCL program"),
431     .priv_size      = sizeof(ProgramOpenCLContext),
432     .priv_class     = &openclsrc_class,
433     .init           = &program_opencl_init,
434     .uninit         = &program_opencl_uninit,
435     .query_formats  = &ff_opencl_filter_query_formats,
436     .inputs         = NULL,
437     .outputs        = openclsrc_outputs,
438     .flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE,
439 };
440
441 #endif