diff options
Diffstat (limited to 'video/filter')
-rw-r--r-- | video/filter/refqueue.c | 4 | ||||
-rw-r--r-- | video/filter/vf_gpu.c | 117 | ||||
-rw-r--r-- | video/filter/vf_gpu.h | 35 | ||||
-rw-r--r-- | video/filter/vf_gpu_egl.c | 107 | ||||
-rw-r--r-- | video/filter/vf_gpu_vulkan.c | 115 | ||||
-rw-r--r-- | video/filter/vf_vapoursynth.c | 297 |
6 files changed, 434 insertions, 241 deletions
diff --git a/video/filter/refqueue.c b/video/filter/refqueue.c index f5124087c6..031feb8c96 100644 --- a/video/filter/refqueue.c +++ b/video/filter/refqueue.c @@ -119,10 +119,10 @@ bool mp_refqueue_is_top_field(struct mp_refqueue *q) { if (!mp_refqueue_has_output(q)) return false; - + bool tff = q->field_parity == MP_FIELD_PARITY_TFF; bool bff = q->field_parity == MP_FIELD_PARITY_BFF; - bool ret = (!!(q->queue[q->pos]->fields & MP_IMGFIELD_TOP_FIRST) ^ q->second_field + bool ret = (!!(q->queue[q->pos]->fields & MP_IMGFIELD_TOP_FIRST) ^ q->second_field && !tff && !bff); // Default parity ret = ret || (tff && !q->second_field); // Check if top field is forced ret = ret || (bff && q->second_field); // Check if bottom field is forced diff --git a/video/filter/vf_gpu.c b/video/filter/vf_gpu.c index e19faaef42..d2ba990a00 100644 --- a/video/filter/vf_gpu.c +++ b/video/filter/vf_gpu.c @@ -24,95 +24,49 @@ #include "options/options.h" #include "video/out/aspect.h" #include "video/out/gpu/video.h" -#include "video/out/opengl/egl_helpers.h" -#include "video/out/opengl/ra_gl.h" - -struct offscreen_ctx { - struct mp_log *log; - struct ra *ra; - void *priv; - - void (*set_context)(struct offscreen_ctx *ctx, bool enable); -}; - -struct gl_offscreen_ctx { - GL gl; - EGLDisplay egl_display; - EGLContext egl_context; +#include "video/filter/vf_gpu.h" + +extern const struct offscreen_context offscreen_vk; +extern const struct offscreen_context offscreen_egl; + +static const struct offscreen_context *contexts[] = { +#if HAVE_EGL + &offscreen_egl, +#endif +#if HAVE_VULKAN + &offscreen_vk, +#endif }; -static void gl_ctx_destroy(void *p) +static inline OPT_STRING_VALIDATE_FUNC(offscreen_ctx_validate_api) { - struct offscreen_ctx *ctx = p; - struct gl_offscreen_ctx *gl = ctx->priv; - - ra_free(&ctx->ra); - - if (gl->egl_context) - eglDestroyContext(gl->egl_display, gl->egl_context); + struct bstr param = bstr0(*value); + for (int i = 0; i < MP_ARRAY_SIZE(contexts); i++) { + if (bstr_equals0(param, contexts[i]->api)) + return 1; + } + return M_OPT_INVALID; } -static void gl_ctx_set_context(struct offscreen_ctx *ctx, bool enable) +static int offscreen_ctx_api_help(struct mp_log *log, const struct m_option *opt, + struct bstr name) { - struct gl_offscreen_ctx *gl = ctx->priv; - EGLContext c = enable ? gl->egl_context : EGL_NO_CONTEXT; - - if (!eglMakeCurrent(gl->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, c)) - MP_ERR(ctx, "Could not make EGL context current.\n"); + mp_info(log, "GPU APIs (offscreen contexts):\n"); + for (int i = 0; i < MP_ARRAY_SIZE(contexts); i++) + mp_info(log, " %s\n", contexts[i]->api); + return M_OPT_EXIT; } -static struct offscreen_ctx *gl_offscreen_ctx_create(struct mpv_global *global, - struct mp_log *log) +static struct offscreen_ctx *offscreen_ctx_create(struct mpv_global *global, + struct mp_log *log, + const char *api) { - struct offscreen_ctx *ctx = talloc_zero(NULL, struct offscreen_ctx); - struct gl_offscreen_ctx *gl = talloc_zero(ctx, struct gl_offscreen_ctx); - talloc_set_destructor(ctx, gl_ctx_destroy); - *ctx = (struct offscreen_ctx){ - .log = log, - .priv = gl, - .set_context = gl_ctx_set_context, - }; - - // This appears to work with Mesa. EGL 1.5 doesn't specify what a "default - // display" is at all. - gl->egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); - if (!eglInitialize(gl->egl_display, NULL, NULL)) { - MP_ERR(ctx, "Could not initialize EGL.\n"); - goto error; + for (int i = 0; i < MP_ARRAY_SIZE(contexts); i++) { + if (api && strcmp(contexts[i]->api, api) != 0) + continue; + mp_info(log, "Creating offscreen GPU context '%s'\n", contexts[i]->api); + return contexts[i]->offscreen_ctx_create(global, log); } - - // Unfortunately, mpegl_create_context() is entangled with ra_ctx. - // Fortunately, it does not need much, and we can provide a stub. - struct ra_ctx ractx = { - .log = ctx->log, - .global = global, - }; - EGLConfig config; - if (!mpegl_create_context(&ractx, gl->egl_display, &gl->egl_context, &config)) - { - MP_ERR(ctx, "Could not create EGL context.\n"); - goto error; - } - - if (!eglMakeCurrent(gl->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, - gl->egl_context)) - { - MP_ERR(ctx, "Could not make EGL context current.\n"); - goto error; - } - - mpegl_load_functions(&gl->gl, ctx->log); - ctx->ra = ra_create_gl(&gl->gl, ctx->log); - - if (!ctx->ra) - goto error; - - gl_ctx_set_context(ctx, false); - - return ctx; - -error: - talloc_free(ctx); return NULL; } @@ -124,6 +78,7 @@ static void offscreen_ctx_set_current(struct offscreen_ctx *ctx, bool enable) struct gpu_opts { int w, h; + char *api; }; struct priv { @@ -327,7 +282,7 @@ static struct mp_filter *gpu_create(struct mp_filter *parent, void *options) priv->vo_opts_cache = m_config_cache_alloc(f, f->global, &vo_sub_opts); priv->vo_opts = priv->vo_opts_cache->opts; - priv->ctx = gl_offscreen_ctx_create(f->global, f->log); + priv->ctx = offscreen_ctx_create(f->global, f->log, priv->opts->api); if (!priv->ctx) { MP_FATAL(f, "Could not create offscreen ra context.\n"); goto error; @@ -368,6 +323,8 @@ const struct mp_user_filter_entry vf_gpu = { .options = (const struct m_option[]){ {"w", OPT_INT(w)}, {"h", OPT_INT(h)}, + {"api", OPT_STRING_VALIDATE(api, offscreen_ctx_validate_api), + .help = offscreen_ctx_api_help}, {0} }, }, diff --git a/video/filter/vf_gpu.h b/video/filter/vf_gpu.h new file mode 100644 index 0000000000..2cc9a16eea --- /dev/null +++ b/video/filter/vf_gpu.h @@ -0,0 +1,35 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "common/common.h" +#include "common/global.h" + +struct offscreen_ctx { + struct mp_log *log; + struct ra *ra; + void *priv; + + void (*set_context)(struct offscreen_ctx *ctx, bool enable); +}; + +struct offscreen_context { + const char *api; + struct offscreen_ctx *(*offscreen_ctx_create)(struct mpv_global *, + struct mp_log *); +}; diff --git a/video/filter/vf_gpu_egl.c b/video/filter/vf_gpu_egl.c new file mode 100644 index 0000000000..cd90a6e06a --- /dev/null +++ b/video/filter/vf_gpu_egl.c @@ -0,0 +1,107 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "common/common.h" +#include "video/filter/vf_gpu.h" +#include "video/out/opengl/egl_helpers.h" +#include "video/out/opengl/ra_gl.h" + +struct gl_offscreen_ctx { + GL gl; + EGLDisplay egl_display; + EGLContext egl_context; +}; + +static void gl_ctx_destroy(void *p) +{ + struct offscreen_ctx *ctx = p; + struct gl_offscreen_ctx *gl = ctx->priv; + + ra_free(&ctx->ra); + + if (gl->egl_context) + eglDestroyContext(gl->egl_display, gl->egl_context); +} + +static void gl_ctx_set_context(struct offscreen_ctx *ctx, bool enable) +{ + struct gl_offscreen_ctx *gl = ctx->priv; + EGLContext c = enable ? gl->egl_context : EGL_NO_CONTEXT; + + if (!eglMakeCurrent(gl->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, c)) + MP_ERR(ctx, "Could not make EGL context current.\n"); +} + +static struct offscreen_ctx *gl_offscreen_ctx_create(struct mpv_global *global, + struct mp_log *log) +{ + struct offscreen_ctx *ctx = talloc(NULL, struct offscreen_ctx); + struct gl_offscreen_ctx *gl = talloc_zero(ctx, struct gl_offscreen_ctx); + talloc_set_destructor(ctx, gl_ctx_destroy); + *ctx = (struct offscreen_ctx){ + .log = log, + .priv = gl, + .set_context = gl_ctx_set_context, + }; + + // This appears to work with Mesa. EGL 1.5 doesn't specify what a "default + // display" is at all. + gl->egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (!eglInitialize(gl->egl_display, NULL, NULL)) { + MP_ERR(ctx, "Could not initialize EGL.\n"); + goto error; + } + + // Unfortunately, mpegl_create_context() is entangled with ra_ctx. + // Fortunately, it does not need much, and we can provide a stub. + struct ra_ctx ractx = { + .log = ctx->log, + .global = global, + }; + EGLConfig config; + if (!mpegl_create_context(&ractx, gl->egl_display, &gl->egl_context, &config)) + { + MP_ERR(ctx, "Could not create EGL context.\n"); + goto error; + } + + if (!eglMakeCurrent(gl->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, + gl->egl_context)) + { + MP_ERR(ctx, "Could not make EGL context current.\n"); + goto error; + } + + mpegl_load_functions(&gl->gl, ctx->log); + ctx->ra = ra_create_gl(&gl->gl, ctx->log); + + if (!ctx->ra) + goto error; + + gl_ctx_set_context(ctx, false); + + return ctx; + +error: + talloc_free(ctx); + return NULL; +} + +const struct offscreen_context offscreen_egl = { + .api = "egl", + .offscreen_ctx_create = gl_offscreen_ctx_create +}; diff --git a/video/filter/vf_gpu_vulkan.c b/video/filter/vf_gpu_vulkan.c new file mode 100644 index 0000000000..d477fd153a --- /dev/null +++ b/video/filter/vf_gpu_vulkan.c @@ -0,0 +1,115 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "options/m_config.h" +#include "video/filter/vf_gpu.h" +#include "video/out/placebo/ra_pl.h" +#include "video/out/placebo/utils.h" +#include "video/out/vulkan/context.h" +#include "video/out/vulkan/utils.h" + +struct vk_offscreen_ctx { + struct ra_ctx *ractx; + struct mpvk_ctx *vk; +}; + +extern const struct m_sub_options vulkan_conf; + +static void vk_ctx_destroy(void *p) +{ + struct offscreen_ctx *ctx = p; + struct vk_offscreen_ctx *vkctx = ctx->priv; + struct ra_ctx *ractx = vkctx->ractx; + struct mpvk_ctx *vk = vkctx->vk; + + if (ractx->ra) { + pl_gpu_finish(vk->gpu); + ractx->ra->fns->destroy(ctx->ra); + ractx->ra = NULL; + ctx->ra = NULL; + } + + vk->gpu = NULL; + pl_vulkan_destroy(&vk->vulkan); + mpvk_uninit(vk); + talloc_free(vk); + talloc_free(ractx); +} + +static struct offscreen_ctx *vk_offscreen_ctx_create(struct mpv_global *global, + struct mp_log *log) +{ + struct offscreen_ctx *ctx = talloc(NULL, struct offscreen_ctx); + talloc_set_destructor(ctx, vk_ctx_destroy); + *ctx = (struct offscreen_ctx){ + .log = log, + }; + + struct ra_ctx *ractx = talloc_zero(ctx, struct ra_ctx); + struct mpvk_ctx *vk = talloc_zero(ctx, struct mpvk_ctx); + ractx->log = ctx->log; + ractx->global = global; + + vk->pllog = mppl_log_create(ctx, log); + if (!vk->pllog) + goto error; + + struct pl_vk_inst_params pl_vk_params = {0}; + struct ra_ctx_opts *ctx_opts = mp_get_config_group(NULL, global, &ra_ctx_conf); + pl_vk_params.debug = ctx_opts->debug; + talloc_free(ctx_opts); + mppl_log_set_probing(vk->pllog, true); + vk->vkinst = pl_vk_inst_create(vk->pllog, &pl_vk_params); + mppl_log_set_probing(vk->pllog, false); + if (!vk->vkinst) + goto error; + + struct vulkan_opts *vk_opts = mp_get_config_group(NULL, global, &vulkan_conf); + vk->vulkan = mppl_create_vulkan(vk_opts, vk->vkinst, vk->pllog, VK_NULL_HANDLE); + talloc_free(vk_opts); + if (!vk->vulkan) + goto error; + + vk->gpu = vk->vulkan->gpu; + ractx->ra = ra_create_pl(vk->gpu, ractx->log); + if (!ractx->ra) + goto error; + + struct vk_offscreen_ctx *vkctx = talloc(ctx, struct vk_offscreen_ctx); + *vkctx = (struct vk_offscreen_ctx){ + .ractx = ractx, + .vk = vk + }; + + ctx->ra = ractx->ra; + ctx->priv = vkctx; + + return ctx; + +error: + pl_vulkan_destroy(&vk->vulkan); + mpvk_uninit(vk); + talloc_free(vk); + talloc_free(ractx); + talloc_free(ctx); + return NULL; +} + +const struct offscreen_context offscreen_vk = { + .api = "vulkan", + .offscreen_ctx_create = vk_offscreen_ctx_create +}; diff --git a/video/filter/vf_vapoursynth.c b/video/filter/vf_vapoursynth.c index 0b798c8daf..c0d4fdb474 100644 --- a/video/filter/vf_vapoursynth.c +++ b/video/filter/vf_vapoursynth.c @@ -22,8 +22,8 @@ #include <limits.h> #include <assert.h> -#include <VapourSynth.h> -#include <VSHelper.h> +#include <VapourSynth4.h> +#include <VSScript4.h> #include <libavutil/rational.h> #include <libavutil/cpu.h> @@ -46,6 +46,7 @@ struct vapoursynth_opts { char *file; int maxbuffer; int maxrequests; + char *user_data; const struct script_driver *drv; }; @@ -57,13 +58,13 @@ struct priv { VSCore *vscore; const VSAPI *vsapi; - VSNodeRef *out_node; - VSNodeRef *in_node; + VSNode *out_node; + VSNode *in_node; const struct script_driver *drv; // drv_vss - bool vs_initialized; - struct VSScript *se; + const VSSCRIPTAPI *vs_script_api; + VSScript *vs_script; struct mp_filter *f; struct mp_pin *in_pin; @@ -108,72 +109,50 @@ struct script_driver { void (*unload)(struct priv *p); // unload script and maybe vs }; -struct mpvs_fmt { - VSPresetFormat vs; - int bits, xs, ys; -}; - -static const struct mpvs_fmt mpvs_fmt_table[] = { - {pfYUV420P8, 8, 1, 1}, - {pfYUV420P9, 9, 1, 1}, - {pfYUV420P10, 10, 1, 1}, - {pfYUV420P16, 16, 1, 1}, - {pfYUV422P8, 8, 1, 0}, - {pfYUV422P9, 9, 1, 0}, - {pfYUV422P10, 10, 1, 0}, - {pfYUV422P16, 16, 1, 0}, - {pfYUV410P8, 8, 2, 2}, - {pfYUV411P8, 8, 2, 0}, - {pfYUV440P8, 8, 0, 1}, - {pfYUV444P8, 8, 0, 0}, - {pfYUV444P9, 9, 0, 0}, - {pfYUV444P10, 10, 0, 0}, - {pfYUV444P16, 16, 0, 0}, - {pfNone} -}; - -static bool compare_fmt(int imgfmt, const struct mpvs_fmt *vs) -{ - struct mp_regular_imgfmt rfmt; - if (!mp_get_regular_imgfmt(&rfmt, imgfmt)) - return false; - if (rfmt.component_pad > 0) - return false; - if (rfmt.chroma_xs != vs->xs || rfmt.chroma_ys != vs->ys) +static bool get_valid_mp_regular_imgfmt(struct mp_regular_imgfmt *reg_fmt, int imgfmt) { + if (!mp_get_regular_imgfmt(reg_fmt, imgfmt)) return false; - if (rfmt.component_size * 8 + rfmt.component_pad != vs->bits) + if (reg_fmt->component_pad > 0) return false; - if (rfmt.num_planes != 3) + if (reg_fmt->num_planes != 3) return false; for (int n = 0; n < 3; n++) { - if (rfmt.planes[n].num_components != 1) + if (reg_fmt->planes[n].num_components != 1) return false; - if (rfmt.planes[n].components[0] != n + 1) + if (reg_fmt->planes[n].components[0] != n + 1) return false; } return true; } -static VSPresetFormat mp_to_vs(int imgfmt) +static bool mp_to_vs(struct priv *p, VSVideoFormat *vsfmt, int imgfmt) { - for (int n = 0; mpvs_fmt_table[n].bits; n++) { - const struct mpvs_fmt *vsentry = &mpvs_fmt_table[n]; - if (compare_fmt(imgfmt, vsentry)) - return vsentry->vs; - } - return pfNone; + struct mp_regular_imgfmt reg_fmt; + if (!get_valid_mp_regular_imgfmt(®_fmt, imgfmt)) + return false; + + int rfmt_bits = reg_fmt.component_size * 8 + reg_fmt.component_pad; + return p->vsapi->queryVideoFormat(vsfmt, cfYUV, + reg_fmt.component_type == MP_COMPONENT_TYPE_FLOAT ? stFloat : stInteger, + rfmt_bits, reg_fmt.chroma_xs, reg_fmt.chroma_ys, p->vscore); } -static int mp_from_vs(VSPresetFormat vs) +static int mp_from_vs(const VSVideoFormat *vsfmt) { - for (int n = 0; mpvs_fmt_table[n].bits; n++) { - const struct mpvs_fmt *vsentry = &mpvs_fmt_table[n]; - if (vsentry->vs == vs) { - for (int imgfmt = IMGFMT_START; imgfmt < IMGFMT_END; imgfmt++) { - if (compare_fmt(imgfmt, vsentry)) - return imgfmt; + if (vsfmt->colorFamily == cfYUV) { + for (int imgfmt = IMGFMT_START + 1; imgfmt < IMGFMT_END; imgfmt++) { + struct mp_regular_imgfmt reg_fmt; + if (!get_valid_mp_regular_imgfmt(®_fmt, imgfmt)) + continue; + + int rfmt_bits = reg_fmt.component_size * 8 + reg_fmt.component_pad; + if ((reg_fmt.component_type == MP_COMPONENT_TYPE_FLOAT) == (vsfmt->sampleType == stFloat) && + rfmt_bits == vsfmt->bitsPerSample && + reg_fmt.chroma_xs == vsfmt->subSamplingW && + reg_fmt.chroma_ys == vsfmt->subSamplingH) + { + return imgfmt; } - break; } } return 0; @@ -183,18 +162,19 @@ static void copy_mp_to_vs_frame_props_map(struct priv *p, VSMap *map, struct mp_image *img) { struct mp_image_params *params = &img->params; - p->vsapi->propSetInt(map, "_SARNum", params->p_w, 0); - p->vsapi->propSetInt(map, "_SARDen", params->p_h, 0); + p->vsapi->mapSetInt(map, "_SARNum", params->p_w, 0); + p->vsapi->mapSetInt(map, "_SARDen", params->p_h, 0); if (params->repr.levels) { - p->vsapi->propSetInt(map, "_ColorRange", + p->vsapi->mapSetInt(map, "_ColorRange", params->repr.levels == PL_COLOR_LEVELS_LIMITED, 0); } // The docs explicitly say it uses libavcodec values. - p->vsapi->propSetInt(map, "_ColorSpace", + p->vsapi->mapSetInt(map, "_ColorSpace", pl_system_to_av(params->repr.sys), 0); if (params->chroma_location) { - p->vsapi->propSetInt(map, "_ChromaLocation", - params->chroma_location == PL_CHROMA_CENTER, 0); + // 0=left, 1=center, 2=topleft, 3=top, 4=bottomleft, 5=bottom. + p->vsapi->mapSetInt(map, "_ChromaLocation", + params->chroma_location - 1, 0); } char pict_type = 0; switch (img->pict_type) { @@ -203,45 +183,53 @@ static void copy_mp_to_vs_frame_props_map(struct priv *p, VSMap *map, case 3: pict_type = 'B'; break; } if (pict_type) - p->vsapi->propSetData(map, "_PictType", &pict_type, 1, 0); + p->vsapi->mapSetData(map, "_PictType", &pict_type, 1, dtUtf8, 0); int field = 0; if (img->fields & MP_IMGFIELD_INTERLACED) field = img->fields & MP_IMGFIELD_TOP_FIRST ? 2 : 1; - p->vsapi->propSetInt(map, "_FieldBased", field, 0); + p->vsapi->mapSetInt(map, "_FieldBased", field, 0); + + // Don't increase the reference count. It is not intended to be read externally, + // and we know it will be alive when we retrieve it. + p->vsapi->mapSetData(map, "_MP_IMAGE", (const char *)img, sizeof(*img), dtBinary, 0); } -static int set_vs_frame_props(struct priv *p, VSFrameRef *frame, +static int set_vs_frame_props(struct priv *p, VSFrame *frame, struct mp_image *img, int dur_num, int dur_den) { - VSMap *map = p->vsapi->getFramePropsRW(frame); + VSMap *map = p->vsapi->getFramePropertiesRW(frame); if (!map) return -1; - p->vsapi->propSetInt(map, "_DurationNum", dur_num, 0); - p->vsapi->propSetInt(map, "_DurationDen", dur_den, 0); + p->vsapi->mapSetInt(map, "_DurationNum", dur_num, 0); + p->vsapi->mapSetInt(map, "_DurationDen", dur_den, 0); copy_mp_to_vs_frame_props_map(p, map, img); return 0; } -static VSFrameRef *alloc_vs_frame(struct priv *p, struct mp_image_params *fmt) +static VSFrame *alloc_vs_frame(struct priv *p, struct mp_image_params *fmt) { - const VSFormat *vsfmt = - p->vsapi->getFormatPreset(mp_to_vs(fmt->imgfmt), p->vscore); - return p->vsapi->newVideoFrame(vsfmt, fmt->w, fmt->h, NULL, p->vscore); + VSVideoFormat vsfmt; + if (mp_to_vs(p, &vsfmt, fmt->imgfmt)) + return p->vsapi->newVideoFrame(&vsfmt, fmt->w, fmt->h, NULL, p->vscore); + + return NULL; } -static struct mp_image map_vs_frame(struct priv *p, const VSFrameRef *ref, - bool w) +static struct mp_image map_vs_frame(struct priv *p, const VSFrame *ref, + bool w, struct mp_image *ref_image) { - const VSFormat *fmt = p->vsapi->getFrameFormat(ref); + const VSVideoFormat *fmt = p->vsapi->getVideoFrameFormat(ref); struct mp_image img = {0}; - mp_image_setfmt(&img, mp_from_vs(fmt->id)); + if (ref_image) + img = *ref_image; + mp_image_setfmt(&img, mp_from_vs(fmt)); mp_image_set_size(&img, p->vsapi->getFrameWidth(ref, 0), p->vsapi->getFrameHeight(ref, 0)); for (int n = 0; n < img.num_planes; n++) { if (w) { - img.planes[n] = p->vsapi->getWritePtr((VSFrameRef *)ref, n); + img.planes[n] = p->vsapi->getWritePtr((VSFrame *)ref, n); } else { img.planes[n] = (uint8_t *)p->vsapi->getReadPtr(ref, n); } @@ -262,24 +250,30 @@ static void drain_oldest_buffered_frame(struct priv *p) p->in_frameno++; } -static void VS_CC vs_frame_done(void *userData, const VSFrameRef *f, int n, - VSNodeRef *node, const char *errorMsg) +static void VS_CC vs_frame_done(void *userData, const VSFrame *f, int n, + VSNode *node, const char *errorMsg) { struct priv *p = userData; struct mp_image *res = NULL; if (f) { - struct mp_image img = map_vs_frame(p, f, false); - struct mp_image dummy = {.params = p->fmt_in}; - if (p->fmt_in.w != img.w || p->fmt_in.h != img.h) - dummy.params.crop = (struct mp_rect){0, 0, img.w, img.h}; - mp_image_copy_attributes(&img, &dummy); + const VSMap *map = p->vsapi->getFramePropertiesRO(f); + if (!map) + MP_ERR(p, "Failed to get frame properties!"); + struct mp_image *mpi = NULL; + if (map) { + mpi = (void *)p->vsapi->mapGetData(map, "_MP_IMAGE", 0, NULL); + if (!mpi) + MP_ERR(p, "Failed to get mp_image attributes!"); + } + struct mp_image img = map_vs_frame(p, f, false, mpi); img.pkt_duration = -1; - const VSMap *map = p->vsapi->getFramePropsRO(f); + if (mpi && (mpi->params.w != img.w || mpi->params.h != img.h)) + img.params.crop = (struct mp_rect){0, 0, img.w, img.h}; if (map) { int err1, err2; - int num = p->vsapi->propGetInt(map, "_DurationNum", 0, &err1); - int den = p->vsapi->propGetInt(map, "_DurationDen", 0, &err2); + int num = p->vsapi->mapGetInt(map, "_DurationNum", 0, &err1); + int den = p->vsapi->mapGetInt(map, "_DurationDen", 0, &err2); if (!err1 && !err2) img.pkt_duration = num / (double)den; } @@ -440,39 +434,13 @@ done: mp_mutex_unlock(&p->lock); } -static void VS_CC infiltInit(VSMap *in, VSMap *out, void **instanceData, - VSNode *node, VSCore *core, const VSAPI *vsapi) -{ - struct priv *p = *instanceData; - // The number of frames of our input node is obviously unknown. The user - // could for example seek any time, randomly "ending" the clip. - // This specific value was suggested by the VapourSynth developer. - int enough_for_everyone = INT_MAX / 16; - - // Note: this is called from createFilter, so no need for locking. - - VSVideoInfo fmt = { - .format = p->vsapi->getFormatPreset(mp_to_vs(p->fmt_in.imgfmt), p->vscore), - .width = p->fmt_in.w, - .height = p->fmt_in.h, - .numFrames = enough_for_everyone, - }; - if (!fmt.format) { - p->vsapi->setError(out, "Unsupported input format.\n"); - return; - } - - p->vsapi->setVideoInfo(&fmt, 1, node); - p->in_node_active = true; -} - -static const VSFrameRef *VS_CC infiltGetFrame(int frameno, int activationReason, - void **instanceData, void **frameData, +static const VSFrame *VS_CC infiltGetFrame(int frameno, int activationReason, + void *instanceData, void **frameData, VSFrameContext *frameCtx, VSCore *core, const VSAPI *vsapi) { - struct priv *p = *instanceData; - VSFrameRef *ret = NULL; + struct priv *p = instanceData; + VSFrame *ret = NULL; mp_mutex_lock(&p->lock); MP_TRACE(p, "VS asking for frame %d (at %d)\n", frameno, p->in_frameno); @@ -490,7 +458,7 @@ static const VSFrameRef *VS_CC infiltGetFrame(int frameno, int activationReason, p->vsapi->setFilterError("Could not allocate VS frame", frameCtx); break; } - struct mp_image vsframe = map_vs_frame(p, ret, true); + struct mp_image vsframe = map_vs_frame(p, ret, true, NULL); mp_image_clear(&vsframe, 0, 0, p->fmt_in.w, p->fmt_in.h); struct mp_image dummy = {0}; mp_image_set_params(&dummy, &p->fmt_in); @@ -538,7 +506,7 @@ static const VSFrameRef *VS_CC infiltGetFrame(int frameno, int activationReason, } mp_mutex_unlock(&p->lock); - struct mp_image vsframe = map_vs_frame(p, ret, true); + struct mp_image vsframe = map_vs_frame(p, ret, true, NULL); mp_image_copy(&vsframe, img); int res = 1e6; int dur = img->pkt_duration * res + 0.5; @@ -624,7 +592,7 @@ static void destroy_vs(struct priv *p) static int reinit_vs(struct priv *p, struct mp_image *input) { - VSMap *vars = NULL, *in = NULL, *out = NULL; + VSMap *vars = NULL; int res = -1; destroy_vs(p); @@ -645,29 +613,41 @@ static int reinit_vs(struct priv *p, struct mp_image *input) goto error; } - in = p->vsapi->createMap(); - out = p->vsapi->createMap(); - vars = p->vsapi->createMap(); - if (!in || !out || !vars) + // The number of frames of our input node is obviously unknown. The user + // could for example seek any time, randomly "ending" the clip. + // This specific value was suggested by the VapourSynth developer. + int enough_for_everyone = INT_MAX / 16; + + VSVideoInfo vi_in = { + .width = p->fmt_in.w, + .height = p->fmt_in.h, + .numFrames = enough_for_everyone, + }; + if (!mp_to_vs(p, &vi_in.format, p->fmt_in.imgfmt)) { + MP_FATAL(p, "Unsupported input format.\n"); goto error; + } - p->vsapi->createFilter(in, out, "Input", infiltInit, infiltGetFrame, - infiltFree, fmSerial, 0, p, p->vscore); - int vserr; - p->in_node = p->vsapi->propGetNode(out, "clip", 0, &vserr); + p->in_node = p->vsapi->createVideoFilter2("Input", &vi_in, infiltGetFrame, infiltFree, + fmParallel, NULL, 0, p, p->vscore); if (!p->in_node) { MP_FATAL(p, "Could not get our own input node.\n"); goto error; } + p->in_node_active = true; + + vars = p->vsapi->createMap(); + if (!vars) + goto error; - if (p->vsapi->propSetNode(vars, "video_in", p->in_node, 0)) + if (p->vsapi->mapSetNode(vars, "video_in", p->in_node, 0)) goto error; int d_w, d_h; mp_image_params_get_dsize(&p->fmt_in, &d_w, &d_h); - p->vsapi->propSetInt(vars, "video_in_dw", d_w, 0); - p->vsapi->propSetInt(vars, "video_in_dh", d_h, 0); + p->vsapi->mapSetInt(vars, "video_in_dw", d_w, 0); + p->vsapi->mapSetInt(vars, "video_in_dh", d_h, 0); struct mp_stream_info *info = mp_filter_find_stream_info(p->f); double container_fps = input->nominal_fps; @@ -683,9 +663,10 @@ static int reinit_vs(struct priv *p, struct mp_image *input) display_res[1] = tmp[1]; } } - p->vsapi->propSetFloat(vars, "container_fps", container_fps, 0); - p->vsapi->propSetFloat(vars, "display_fps", display_fps, 0); - p->vsapi->propSetIntArray(vars, "display_res", display_res, 2); + p->vsapi->mapSetFloat(vars, "container_fps", container_fps, 0); + p->vsapi->mapSetFloat(vars, "display_fps", display_fps, 0); + p->vsapi->mapSetIntArray(vars, "display_res", display_res, 2); + p->vsapi->mapSetData(vars, "user_data", p->opts->user_data, -1, dtUtf8, 0); if (p->drv->load(p, vars) < 0) goto error; @@ -694,8 +675,8 @@ static int reinit_vs(struct priv *p, struct mp_image *input) goto error; } - const VSVideoInfo *vi = p->vsapi->getVideoInfo(p->out_node); - if (!mp_from_vs(vi->format->id)) { + const VSVideoInfo *vi_out = p->vsapi->getVideoInfo(p->out_node); + if (!mp_from_vs(&vi_out->format)) { MP_FATAL(p, "Unsupported output format.\n"); goto error; } @@ -707,8 +688,6 @@ static int reinit_vs(struct priv *p, struct mp_image *input) res = 0; error: if (p->vsapi) { - p->vsapi->freeMap(in); - p->vsapi->freeMap(out); p->vsapi->freeMap(vars); } if (res < 0) @@ -785,9 +764,11 @@ static struct mp_filter *vf_vapoursynth_create(struct mp_filter *parent, if (!conv) goto error; - for (int n = 0; mpvs_fmt_table[n].bits; n++) { - int imgfmt = mp_from_vs(mpvs_fmt_table[n].vs); - if (imgfmt) + for (int imgfmt = IMGFMT_START + 1; imgfmt < IMGFMT_END; imgfmt++) { + // due to the lack of access to VapourSynth at this point, the formats + // added to autoconvert is a superset of what's actually needed + struct mp_regular_imgfmt reg_fmt; + if (get_valid_mp_regular_imgfmt(®_fmt, imgfmt)) mp_autoconvert_add_imgfmt(conv, imgfmt, 0); } @@ -817,58 +798,56 @@ static const m_option_t vf_opts_fields[] = { OPTDEF_INT(4)}, {"concurrent-frames", OPT_CHOICE(maxrequests, {"auto", -1}), M_RANGE(1, 99), OPTDEF_INT(-1)}, + {"user-data", OPT_STRING(user_data), OPTDEF_STR("")}, {0} }; -#include <VSScript.h> - static int drv_vss_init(struct priv *p) { - if (!vsscript_init()) { + p->vs_script_api = getVSScriptAPI(VSSCRIPT_API_VERSION); + if (!p->vs_script_api) { MP_FATAL(p, "Could not initialize VapourSynth scripting.\n"); return -1; } - p->vs_initialized = true; return 0; } static void drv_vss_uninit(struct priv *p) { - if (p->vs_initialized) - vsscript_finalize(); - p->vs_initialized = false; + p->vs_script_api = NULL; } static int drv_vss_load_core(struct priv *p) { // First load an empty script to get a VSScript, so that we get the vsapi // and vscore. - if (vsscript_createScript(&p->se)) + p->vs_script = p->vs_script_api->createScript(NULL); + if (!p->vs_script) return -1; - p->vsapi = vsscript_getVSApi(); - p->vscore = vsscript_getCore(p->se); + p->vsapi = p->vs_script_api->getVSAPI(VAPOURSYNTH_API_VERSION); + p->vscore = p->vs_script_api->getCore(p->vs_script); return 0; } static int drv_vss_load(struct priv *p, VSMap *vars) { - vsscript_setVariable(p->se, vars); + p->vs_script_api->setVariables(p->vs_script, vars); - if (vsscript_evaluateFile(&p->se, p->script_path, 0)) { - MP_FATAL(p, "Script evaluation failed:\n%s\n", vsscript_getError(p->se)); + if (p->vs_script_api->evaluateFile(p->vs_script, p->script_path)) { + MP_FATAL(p, "Script evaluation failed:\n%s\n", p->vs_script_api->getError(p->v |