video/filter/vf_amf: add AMD Frame Rate Conversion filter
D3D11 only for now. Documentation says that only D3D11 and D3D12 is supported, but it looks like Vulkan is possible too. But I realized this after implementing this version. Vulan may be added later. Fixes: #16613
This commit is contained in:
1 parent
e72807e7a0
commit
cb1b6a54f2
7 files changed
+821
No files matched your search
@@ -0,0 +1 @@
|
||||
add `--vf=amf_frc`
|
||||
@@ -750,6 +750,57 @@ Available mpv-only filters are:
|
||||
``nvidia-true-hdr``
|
||||
Enable NVIDIA RTX Video HDR processing.
|
||||
|
||||
``amf_frc``
|
||||
AMD Frame Rate Conversion filter. Requires AMD hardware and drivers
|
||||
supporting AMF FRC.
|
||||
|
||||
AMF FRC is a technique for achieving high-end video frame rate conversion
|
||||
results from lower frame rate video inputs. The filter doubles the input
|
||||
frame rate by generating intermediate frames.
|
||||
|
||||
AMF FRC only supports D3D11 input currently. Use `--hwdec=d3d11va` or
|
||||
`--vf-pre=format=d3d11` to upload the data. `--d3d11va-zero-copy` is also
|
||||
recommended for better performance.
|
||||
|
||||
``profile=<auto|low|high|super>``
|
||||
Selects the quality profile for frame rate conversion.
|
||||
|
||||
``auto`` (default)
|
||||
Select profile based on input resolution. Uses ``high`` for
|
||||
resolutions up to 1440p, and ``super`` for 1440p and higher.
|
||||
``low``
|
||||
Less levels of hierarchical motion search. Only recommended for
|
||||
extremely low resolutions.
|
||||
``high``
|
||||
Recommended for any resolution up to 1440p.
|
||||
``super``
|
||||
More levels of hierarchical motion search. Recommended for
|
||||
resolutions 1440p or higher.
|
||||
|
||||
``mv-search=<native|performance>``
|
||||
Selects the motion vector search mode.
|
||||
|
||||
``native`` (default)
|
||||
Conduct motion search on the full resolution of source images.
|
||||
``performance``
|
||||
Conduct motion search on the down scaled source images.
|
||||
Recommended for APU or low end GPU for better performance.
|
||||
|
||||
``fallback``
|
||||
In case confidence is too low to do interpolation, fall back to simple
|
||||
frame blending. If not set, the filter will repeat the last frame instead.
|
||||
(default: no)
|
||||
|
||||
``indicator``
|
||||
Specifies whether or not the FRC indicator square is shown in the top
|
||||
left corner of the video. (default: no)
|
||||
|
||||
``future-frame``
|
||||
When enabled, the information contained in the next frame in the
|
||||
sequence will be used in FRC interpolation calculations, in addition to
|
||||
the current pair of frames. This will introduce one extra frame time of
|
||||
latency. (default: yes)
|
||||
|
||||
``fingerprint=...``
|
||||
Compute video frame fingerprints and provide them as metadata. Actually, it
|
||||
currently barely deserved to be called ``fingerprint``, because it does not
|
||||
|
||||
@@ -100,6 +100,9 @@ const struct mp_user_filter_entry *vf_list[] = {
|
||||
#if HAVE_D3D_HWACCEL
|
||||
&vf_d3d11vpp,
|
||||
#endif
|
||||
#if HAVE_AMF
|
||||
&vf_amf_frc,
|
||||
#endif
|
||||
#if (HAVE_GL && HAVE_EGL) || HAVE_VULKAN
|
||||
&vf_gpu,
|
||||
#endif
|
||||
|
||||
@@ -35,5 +35,6 @@ extern const struct mp_user_filter_entry vf_format;
|
||||
extern const struct mp_user_filter_entry vf_vdpaupp;
|
||||
extern const struct mp_user_filter_entry vf_vavpp;
|
||||
extern const struct mp_user_filter_entry vf_d3d11vpp;
|
||||
extern const struct mp_user_filter_entry vf_amf_frc;
|
||||
extern const struct mp_user_filter_entry vf_fingerprint;
|
||||
extern const struct mp_user_filter_entry vf_gpu;
|
||||
@@ -1401,6 +1401,13 @@ if features['d3d-hwaccel']
|
||||
'video/filter/vf_d3d11vpp.c')
|
||||
endif
|
||||
|
||||
amf = cc.has_header('AMF/core/Factory.h', required:
|
||||
get_option('amf').require(features['d3d-hwaccel']))
|
||||
features += {'amf': amf}
|
||||
if features['amf']
|
||||
sources += files('video/filter/vf_amf.c')
|
||||
endif
|
||||
|
||||
if features['d3d-hwaccel'] and features['egl-angle']
|
||||
sources += files('video/out/opengl/hwdec_d3d11egl.c')
|
||||
endif
|
||||
|
||||
@@ -57,6 +57,7 @@ option('sndio', type: 'feature', value: 'auto', description: 'sndio audio output
|
||||
option('wasapi', type: 'feature', value: 'auto', description: 'WASAPI audio output')
|
||||
|
||||
# video output features
|
||||
option('amf', type: 'feature', value: 'auto', description: 'AMD AMF')
|
||||
option('caca', type: 'feature', value: 'auto', description: 'CACA')
|
||||
option('cocoa', type: 'feature', value: 'auto', description: 'Cocoa')
|
||||
option('d3d11', type: 'feature', value: 'auto', description: 'Direct3D 11 video output')
|
||||
|
||||
@@ -0,0 +1,757 @@
|
||||
/*
|
||||
* 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 <stdatomic.h>
|
||||
|
||||
#include <windows.h>
|
||||
#include <d3d11.h>
|
||||
|
||||
#include <AMF/components/FRC.h>
|
||||
#include <AMF/core/Context.h>
|
||||
#include <AMF/core/Factory.h>
|
||||
#include <AMF/core/Surface.h>
|
||||
|
||||
#include <libavutil/hwcontext_d3d11va.h>
|
||||
#include <libavutil/hwcontext.h>
|
||||
|
||||
#include "filters/filter_internal.h"
|
||||
#include "filters/filter.h"
|
||||
#include "filters/user_filters.h"
|
||||
#include "osdep/windows_utils.h"
|
||||
#include "video/hwdec.h"
|
||||
#include "video/mp_image.h"
|
||||
|
||||
#define ICALL(func, that, ...) (that)->pVtbl->func((that), ##__VA_ARGS__)
|
||||
|
||||
// Refcounting helpers
|
||||
typedef atomic_uint_fast32_t mp_rc_t;
|
||||
#define mp_rc_init(rc) atomic_init(rc, 1)
|
||||
#define mp_rc_ref(rc) ((void) atomic_fetch_add_explicit(rc, 1, memory_order_acquire))
|
||||
#define mp_rc_deref(rc) (atomic_fetch_sub_explicit(rc, 1, memory_order_release) == 1)
|
||||
#define mp_rc_count(rc) atomic_load(rc)
|
||||
|
||||
struct opts {
|
||||
int profile;
|
||||
int mv_search_mode;
|
||||
bool fallback_blend;
|
||||
bool indicator;
|
||||
bool use_future_frame;
|
||||
};
|
||||
|
||||
struct amf_priv {
|
||||
mp_rc_t ref_count;
|
||||
HMODULE dll;
|
||||
AMFFactory *factory;
|
||||
AMFContext *ctx;
|
||||
AMFComponent *frc;
|
||||
};
|
||||
|
||||
struct priv {
|
||||
struct opts *opts;
|
||||
|
||||
struct mp_image_params params;
|
||||
struct mp_image *ref_image;
|
||||
|
||||
ID3D11Device *d3d_dev;
|
||||
ID3D11Texture2D *input_tex;
|
||||
|
||||
bool has_frame;
|
||||
|
||||
AVBufferRef *av_device_ref;
|
||||
|
||||
struct amf_priv *amf;
|
||||
};
|
||||
|
||||
static inline AMF_SURFACE_FORMAT imgfmt2amf(int fmt)
|
||||
{
|
||||
switch (fmt) {
|
||||
case IMGFMT_NV12:
|
||||
return AMF_SURFACE_NV12;
|
||||
case IMGFMT_BGRA:
|
||||
return AMF_SURFACE_BGRA;
|
||||
case IMGFMT_RGBA:
|
||||
return AMF_SURFACE_RGBA;
|
||||
case IMGFMT_P010:
|
||||
return AMF_SURFACE_P010;
|
||||
default:
|
||||
return AMF_SURFACE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
static void unload_amf_library(struct amf_priv *a)
|
||||
{
|
||||
mp_assert(!a->frc && !a->ctx);
|
||||
if (a->dll) {
|
||||
FreeLibrary(a->dll);
|
||||
a->dll = NULL;
|
||||
}
|
||||
a->factory = NULL;
|
||||
}
|
||||
|
||||
static int load_amf_library(struct amf_priv *a)
|
||||
{
|
||||
a->dll = LoadLibraryW(AMF_DLL_NAME);
|
||||
if (!a->dll)
|
||||
return -1;
|
||||
|
||||
AMFInit_Fn init_fn = (AMFInit_Fn)GetProcAddress(a->dll, AMF_INIT_FUNCTION_NAME);
|
||||
if (!init_fn)
|
||||
goto err;
|
||||
|
||||
AMF_RESULT res = init_fn(AMF_FULL_VERSION, &a->factory);
|
||||
if (res != AMF_OK)
|
||||
goto err;
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
unload_amf_library(a);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void flush(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
|
||||
if (p->amf->frc)
|
||||
ICALL(Flush, p->amf->frc);
|
||||
p->has_frame = false;
|
||||
mp_image_unrefp(&p->ref_image);
|
||||
}
|
||||
|
||||
static void destroy_amf(struct amf_priv *a)
|
||||
{
|
||||
if (!mp_rc_deref(&a->ref_count))
|
||||
return;
|
||||
|
||||
if (a->frc) {
|
||||
ICALL(Release, a->frc);
|
||||
a->frc = NULL;
|
||||
}
|
||||
|
||||
if (a->ctx) {
|
||||
ICALL(Release, a->ctx);
|
||||
a->ctx = NULL;
|
||||
}
|
||||
|
||||
unload_amf_library(a);
|
||||
talloc_free(a);
|
||||
}
|
||||
|
||||
static void destroy(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
struct amf_priv *a = p->amf;
|
||||
|
||||
flush(f);
|
||||
|
||||
if (a->frc)
|
||||
ICALL(Terminate, a->frc);
|
||||
|
||||
if (a->ctx)
|
||||
ICALL(Terminate, a->ctx);
|
||||
|
||||
SAFE_RELEASE(p->input_tex);
|
||||
SAFE_RELEASE(p->d3d_dev);
|
||||
av_buffer_unref(&p->av_device_ref);
|
||||
|
||||
destroy_amf(a);
|
||||
}
|
||||
|
||||
static int create_amf_context(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
struct amf_priv *a = p->amf;
|
||||
|
||||
AMF_RESULT res = ICALL(CreateContext, a->factory, &a->ctx);
|
||||
if (res != AMF_OK) {
|
||||
MP_ERR(f, "Failed to create AMF context: %d\n", res);
|
||||
goto err;
|
||||
}
|
||||
|
||||
struct mp_stream_info *info = mp_filter_find_stream_info(f);
|
||||
if (!info || !info->hwdec_devs) {
|
||||
MP_ERR(f, "No hwdec devices available for AMF initialization.\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
struct hwdec_imgfmt_request params = {
|
||||
.imgfmt = IMGFMT_D3D11,
|
||||
.probing = false,
|
||||
};
|
||||
hwdec_devices_request_for_img_fmt(info->hwdec_devs, ¶ms);
|
||||
struct mp_hwdec_ctx *hwctx =
|
||||
hwdec_devices_get_by_imgfmt_and_type(info->hwdec_devs, IMGFMT_D3D11,
|
||||
AV_HWDEVICE_TYPE_D3D11VA);
|
||||
if (!hwctx || !hwctx->av_device_ref) {
|
||||
MP_ERR(f, "No suitable D3D11 hwdec device found for AMF initialization.\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
p->av_device_ref = av_buffer_ref(hwctx->av_device_ref);
|
||||
MP_HANDLE_OOM(p->av_device_ref);
|
||||
AVHWDeviceContext *avhwctx = (void *)p->av_device_ref->data;
|
||||
AVD3D11VADeviceContext *d3dctx = avhwctx->hwctx;
|
||||
ID3D11Device_QueryInterface(d3dctx->device, &IID_ID3D11Device, (void **)&p->d3d_dev);
|
||||
|
||||
// AMF uses its own separate D3D11 device. This avoids any conflicts with
|
||||
// with our presentation device, as we run it on different thread. Things
|
||||
// work well, until there is compute shader processing done on the same device.
|
||||
res = ICALL(InitDX11, a->ctx, NULL, AMF_DX11_1);
|
||||
if (res != AMF_OK) {
|
||||
MP_ERR(f, "Failed to initialize AMF context with D3D11: %d\n", res);
|
||||
goto err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
destroy(f);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int create_amf_frc(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
struct amf_priv *a = p->amf;
|
||||
|
||||
mp_assert(!a->frc);
|
||||
AMF_RESULT res = ICALL(CreateComponent, a->factory, a->ctx, AMFFRC, &a->frc);
|
||||
if (res != AMF_OK) {
|
||||
MP_ERR(f, "Failed to create AMF FRC component: %d\n", res);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int init_frc(struct mp_filter *f, struct mp_image *src, bool reinit)
|
||||
{
|
||||
AMF_RESULT res;
|
||||
struct priv *p = f->priv;
|
||||
struct amf_priv *a = p->amf;
|
||||
|
||||
mp_assert(a->frc);
|
||||
|
||||
// Support only D3D11 input, while it's possible to input host memory, this
|
||||
// adds unnecessary complexity. We already have host memory upload filters,
|
||||
// that can be used, before calling this filter. Also using host memory here,
|
||||
// would return source (non-interpolated) frames also in host memory, which
|
||||
// again forces us to upload them back to GPU for presentation, duplicating work.
|
||||
if (p->params.imgfmt != IMGFMT_D3D11) {
|
||||
MP_ERR(f, "AMF FRC only supports D3D11 input. "
|
||||
"Use `--hwdec=d3d11va` or `--vf-pre=format=d3d11` to upload the data.\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (reinit)
|
||||
goto init;
|
||||
|
||||
AMF_ASSIGN_PROPERTY_INT64(res, a->frc, AMF_FRC_ENGINE_TYPE, AMF_MEMORY_DX11);
|
||||
if (res != AMF_OK) {
|
||||
MP_WARN(f, "Failed to set FRC engine type to DX11: %d\n", res);
|
||||
goto err;
|
||||
}
|
||||
|
||||
AMF_ASSIGN_PROPERTY_INT64(res, a->frc, AMF_FRC_MODE, FRC_x2_PRESENT);
|
||||
if (res != AMF_OK) {
|
||||
MP_ERR(f, "Failed to set FRC mode to x2_PRESENT: %d\n", res);
|
||||
goto err;
|
||||
}
|
||||
|
||||
int profile = p->opts->profile;
|
||||
if (profile == -1) {
|
||||
if (p->params.h >= 1440)
|
||||
profile = FRC_PROFILE_SUPER;
|
||||
else
|
||||
profile = FRC_PROFILE_HIGH;
|
||||
}
|
||||
profile = FRC_PROFILE_HIGH;
|
||||
AMF_ASSIGN_PROPERTY_INT64(res, a->frc, AMF_FRC_PROFILE, profile);
|
||||
if (res != AMF_OK) {
|
||||
MP_WARN(f, "Failed to set FRC profile: %d\n", res);
|
||||
goto err;
|
||||
}
|
||||
|
||||
AMF_ASSIGN_PROPERTY_INT64(res, a->frc, AMF_FRC_MV_SEARCH_MODE,
|
||||
p->opts->mv_search_mode);
|
||||
if (res != AMF_OK) {
|
||||
MP_WARN(f, "Failed to set FRC motion vector search mode: %d\n", res);
|
||||
goto err;
|
||||
}
|
||||
|
||||
AMF_ASSIGN_PROPERTY_BOOL(res, a->frc, AMF_FRC_ENABLE_FALLBACK,
|
||||
p->opts->fallback_blend);
|
||||
if (res != AMF_OK) {
|
||||
MP_WARN(f, "Failed to set FRC fallback mode: %d\n", res);
|
||||
goto err;
|
||||
}
|
||||
|
||||
AMF_ASSIGN_PROPERTY_BOOL(res, a->frc, AMF_FRC_INDICATOR,
|
||||
p->opts->indicator);
|
||||
if (res != AMF_OK) {
|
||||
MP_WARN(f, "Failed to set FRC indicator: %d\n", res);
|
||||
goto err;
|
||||
}
|
||||
|
||||
AMF_ASSIGN_PROPERTY_BOOL(res, a->frc, AMF_FRC_USE_FUTURE_FRAME,
|
||||
p->opts->use_future_frame);
|
||||
if (res != AMF_OK) {
|
||||
MP_WARN(f, "Failed to set FRC use_future_frame: %d\n", res);
|
||||
goto err;
|
||||
}
|
||||
|
||||
init:;
|
||||
AMF_SURFACE_FORMAT amf_fmt = imgfmt2amf(p->params.hw_subfmt);
|
||||
if (amf_fmt == AMF_SURFACE_UNKNOWN) {
|
||||
MP_ERR(f, "AMF FRC does not support input format: %s\n",
|
||||
mp_imgfmt_to_name(p->params.hw_subfmt));
|
||||
goto err;
|
||||
}
|
||||
|
||||
// FRC expects texture size here, not the coded image size
|
||||
D3D11_TEXTURE2D_DESC desc = {0};
|
||||
ID3D11Texture2D_GetDesc((ID3D11Texture2D *)src->planes[0], &desc);
|
||||
res = ICALL(Init, a->frc, amf_fmt, desc.Width, desc.Height);
|
||||
if (res != AMF_OK) {
|
||||
MP_ERR(f, "Failed to initialize FRC component: %d\n", res);
|
||||
goto err;
|
||||
}
|
||||
|
||||
SAFE_RELEASE(p->input_tex);
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
ICALL(Terminate, a->frc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct amf_surface_free_ctx {
|
||||
struct amf_priv *a;
|
||||
AMFSurface *surface;
|
||||
ID3D11Texture2D *tex;
|
||||
};
|
||||
|
||||
static void free_amf_surface(void *arg)
|
||||
{
|
||||
struct amf_surface_free_ctx *ctx = arg;
|
||||
ICALL(Release, ctx->surface);
|
||||
ID3D11Texture2D_Release(ctx->tex); // shared texture
|
||||
destroy_amf(ctx->a);
|
||||
talloc_free(ctx);
|
||||
}
|
||||
|
||||
static struct mp_image *mp_image_from_amf_surface(struct mp_filter *f, AMFSurface *src)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
struct amf_priv *a = p->amf;
|
||||
|
||||
mp_require(ICALL(GetMemoryType, src) == AMF_MEMORY_DX11);
|
||||
|
||||
AMFPlane *plane = ICALL(GetPlane, src, AMF_PLANE_PACKED);
|
||||
if (!plane) {
|
||||
MP_ERR(f, "Failed to get plane from AMF surface.\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ID3D11Texture2D *amf_tex = ICALL(GetNative, plane);
|
||||
if (!amf_tex) {
|
||||
MP_ERR(f, "Failed to get D3D11 texture from AMF plane.\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ID3D11Device *amf_dev = ICALL(GetDX11Device, a->ctx, AMF_DX11_1);
|
||||
mp_require(amf_dev);
|
||||
|
||||
ID3D11DeviceContext *ctx = NULL;
|
||||
ID3D11Device_GetImmediateContext(amf_dev, &ctx);
|
||||
mp_require(ctx);
|
||||
ID3D11DeviceContext_Flush(ctx);
|
||||
SAFE_RELEASE(ctx);
|
||||
|
||||
IDXGIResource* amf_res;
|
||||
HRESULT hr = ID3D11Texture2D_QueryInterface(amf_tex, &IID_IDXGIResource, (void **)&amf_res);
|
||||
if (FAILED(hr)) {
|
||||
MP_ERR(f, "Failed to query IDXGIResource from AMF texture: %#lx\n", hr);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
HANDLE amf_shared;
|
||||
hr = IDXGIResource_GetSharedHandle(amf_res, &amf_shared);
|
||||
if (FAILED(hr)) {
|
||||
MP_ERR(f, "Failed to get shared handle from IDXGIResource: %#lx\n", hr);
|
||||
SAFE_RELEASE(amf_res);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ID3D11Texture2D *tex;
|
||||
hr = ID3D11Device_OpenSharedResource(p->d3d_dev, amf_shared, &IID_ID3D11Texture2D, (void **)&tex);
|
||||
SAFE_RELEASE(amf_res);
|
||||
if (FAILED(hr)) {
|
||||
MP_ERR(f, "Failed to open shared resource: %#lx\n", hr);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// At this point, we technically own a reference to a texture, but we have to
|
||||
// keep also AMFSurface alive, as the implementation seems to reuse the same
|
||||
// underlying texture for future frames.
|
||||
struct amf_surface_free_ctx *free_ctx = talloc(NULL, struct amf_surface_free_ctx);
|
||||
free_ctx->a = a;
|
||||
free_ctx->surface = src;
|
||||
free_ctx->tex = tex;
|
||||
struct mp_image *out = mp_image_new_custom_ref(p->ref_image, (void *)free_ctx, free_amf_surface);
|
||||
if (!out) {
|
||||
MP_ERR(f, "Unexpected error when creating mp_image.\n");
|
||||
free_amf_surface(free_ctx);
|
||||
return NULL;
|
||||
}
|
||||
mp_image_copy_attributes(out, p->ref_image);
|
||||
// hwctx is not copied by mp_image_copy_attributes
|
||||
out->hwctx = av_buffer_ref(p->ref_image->hwctx);
|
||||
out->planes[0] = (uint8_t *)tex;
|
||||
out->planes[1] = 0;
|
||||
out->pts = ICALL(GetPts, src) / (double)AMF_SECOND;
|
||||
|
||||
// Keep context alive until all returned images are freed.
|
||||
mp_rc_ref(&a->ref_count);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static int copy_tex(ID3D11DeviceContext *ctx, ID3D11Device *dev,
|
||||
ID3D11Texture2D **dst, ID3D11Texture2D *src, int index)
|
||||
{
|
||||
D3D11_TEXTURE2D_DESC desc;
|
||||
ID3D11Texture2D_GetDesc(src, &desc);
|
||||
mp_require(desc.MipLevels == 1);
|
||||
|
||||
if (!*dst) {
|
||||
desc.ArraySize = 1;
|
||||
desc.Usage = D3D11_USAGE_DEFAULT;
|
||||
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
|
||||
desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED;
|
||||
|
||||
HRESULT hr = ID3D11Device_CreateTexture2D(dev, &desc, NULL, dst);
|
||||
if (FAILED(hr))
|
||||
return -1;
|
||||
}
|
||||
|
||||
const D3D11_BOX srcBox = {0, 0, 0, desc.Width, desc.Height, 1};
|
||||
ID3D11DeviceContext_CopySubresourceRegion(ctx, (ID3D11Resource *)*dst,
|
||||
0, 0, 0, 0, (ID3D11Resource *)src,
|
||||
index, &srcBox);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static AMF_STD_CALL void amf_surface_dtor(AMFSurfaceObserver *observer, AMFSurface *surface)
|
||||
{
|
||||
talloc_free(observer);
|
||||
}
|
||||
|
||||
static const AMFSurfaceObserver amf_dtor = {
|
||||
.pVtbl = &(AMFSurfaceObserverVtbl){
|
||||
.OnSurfaceDataRelease = amf_surface_dtor,
|
||||
}
|
||||
};
|
||||
|
||||
static AMFSurface *mp_image_to_amf_surface(struct mp_filter *f, struct mp_image *src)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
struct amf_priv *a = p->amf;
|
||||
IDXGIResource* resource = NULL;
|
||||
ID3D11Texture2D *amf_tex = NULL;
|
||||
AMFSurfaceObserver *dtor = talloc_dup(NULL, (AMFSurfaceObserver *)&amf_dtor);
|
||||
talloc_steal(dtor, src); // release mp_image when no longer needed
|
||||
|
||||
if (src->imgfmt != IMGFMT_D3D11) {
|
||||
MP_ERR(f, "AMF FRC only supports D3D11 input. "
|
||||
"Use `--hwdec=d3d11va` or `--vf-pre=format=d3d11` to upload the data.\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
ID3D11Texture2D *tex = (void *)src->planes[0];
|
||||
amf_int index = (intptr_t)src->planes[1];
|
||||
|
||||
ID3D11DeviceContext *ctx = NULL;
|
||||
ID3D11Device_GetImmediateContext(p->d3d_dev, &ctx);
|
||||
mp_require(ctx);
|
||||
// Textures between AMF and our D3D11 device are shared using shared handles
|
||||
// `D3D11_RESOURCE_MISC_SHARED`, which is also set on textures returned
|
||||
// from AMF. These textures are implicitly synchronized, documentation states
|
||||
// that after a shared texture is updated, `ID3D11DeviceContext::Flush()`
|
||||
// must be called on the updating device. Flushing the command queue is
|
||||
// sufficient for the driver to synchronize access to the shared resource.
|
||||
// We intentionally do not use more explicit synchronization mechanisms such
|
||||
// as `IDXGIKeyedMutex` or `D3D11_RESOURCE_MISC_SHARED_NTHANDLE`, as they
|
||||
// are not supported by AMF, nor D3D11VA decoder and would require additional
|
||||
// intermediate copies.
|
||||
// However, resources created by a D3D11VA device are not implicitly
|
||||
// synchronized, so we perform a GPU-GPU copy before sharing the texture
|
||||
// with AMF. This ensures the video decoder has finished writing to the
|
||||
// texture. A copy is faster than synchronizing through the CPU, without copies.
|
||||
copy_tex(ctx, p->d3d_dev, &p->input_tex, tex, index);
|
||||
tex = p->input_tex;
|
||||
index = 0;
|
||||
ID3D11DeviceContext_Flush(ctx);
|
||||
SAFE_RELEASE(ctx);
|
||||
|
||||
HRESULT hr = ID3D11Texture2D_QueryInterface(tex, &IID_IDXGIResource, (void **)&resource);
|
||||
if (FAILED(hr)) {
|
||||
MP_ERR(f, "Failed to query IDXGIResource from texture: %#lx\n", hr);
|
||||
goto err;
|
||||
}
|
||||
|
||||
HANDLE shared;
|
||||
hr = IDXGIResource_GetSharedHandle(resource, &shared);
|
||||
if (FAILED(hr)) {
|
||||
MP_ERR(f, "Failed to get shared handle from IDXGIResource: %#lx\n", hr);
|
||||
goto err;
|
||||
}
|
||||
|
||||
ID3D11Device *amf_dev = ICALL(GetDX11Device, a->ctx, AMF_DX11_1); // not refcounted
|
||||
hr = ID3D11Device_OpenSharedResource(amf_dev, shared, &IID_ID3D11Texture2D, (void **)&amf_tex);
|
||||
SAFE_RELEASE(resource);
|
||||
if (FAILED(hr)) {
|
||||
MP_ERR(f, "Failed to open shared resource: %#lx\n", hr);
|
||||
goto err;
|
||||
}
|
||||
|
||||
AMFSurface *out;
|
||||
AMF_RESULT res = ICALL(CreateSurfaceFromDX11Native, a->ctx, amf_tex, &out, dtor);
|
||||
if (res != AMF_OK) {
|
||||
MP_ERR(f, "Failed to create AMF surface from D3D11: %d\n", res);
|
||||
goto err;
|
||||
}
|
||||
|
||||
// Copied from <https://github.com/GPUOpen-LibrariesAndSDKs/AMF/blob/afed28d37aca1938da2eedc50599bb3535a987ec/amf/public/src/components/Capture/MediaFoundation/MFCaptureImpl.cpp#L64>
|
||||
static const GUID AMFTextureArrayIndexGUID = {0x28115527, 0xe7c3, 0x4b66, {0x99, 0xd3, 0x4f, 0x2a, 0xe6, 0xb4, 0x7f, 0xaf}};
|
||||
ID3D11Resource_SetPrivateData(amf_tex, &AMFTextureArrayIndexGUID, sizeof(index), &index);
|
||||
SAFE_RELEASE(amf_tex);
|
||||
|
||||
if (src->pts != MP_NOPTS_VALUE)
|
||||
ICALL(SetPts, out, (amf_pts)(src->pts * AMF_SECOND));
|
||||
else
|
||||
MP_WARN(f, "Warning: source image has no PTS.\n");
|
||||
|
||||
return out;
|
||||
|
||||
err:
|
||||
SAFE_RELEASE(resource);
|
||||
SAFE_RELEASE(amf_tex);
|
||||
talloc_free(dtor);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void process(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
struct amf_priv *a = p->amf;
|
||||
|
||||
if (!mp_pin_in_needs_data(f->ppins[1]))
|
||||
return;
|
||||
|
||||
if (!p->has_frame && !mp_pin_out_request_data(f->ppins[0]))
|
||||
return;
|
||||
|
||||
// AMF_REPEAT requested or draining, we likely have a frame to output
|
||||
if (p->has_frame)
|
||||
goto read_out;
|
||||
|
||||
struct mp_frame frame = mp_pin_out_read(f->ppins[0]);
|
||||
|
||||
if (frame.type == MP_FRAME_NONE) {
|
||||
MP_WARN(f, "Needs a frame, but got MP_FRAME_NONE...\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (frame.type == MP_FRAME_EOF) {
|
||||
ICALL(Drain, a->frc);
|
||||
goto read_out;
|
||||
}
|
||||
|
||||
if (frame.type != MP_FRAME_VIDEO) {
|
||||
MP_ERR(f, "Unexpected frame type: %d\n", frame.type);
|
||||
mp_frame_unref(&frame);
|
||||
mp_filter_internal_mark_failed(f);
|
||||
return;
|
||||
}
|
||||
|
||||
struct mp_image *in = frame.data;
|
||||
mp_image_unrefp(&p->ref_image);
|
||||
p->ref_image = mp_image_new_ref(in);
|
||||
|
||||
if (!mp_image_params_static_equal(&in->params, &p->params)) {
|
||||
MP_VERBOSE(f, "Input image parameters changed, reinitializing...\n");
|
||||
ICALL(Terminate, a->frc);
|
||||
p->params = in->params;
|
||||
if (init_frc(f, in, false) < 0) {
|
||||
MP_ERR(f, "FRC init failed\n");
|
||||
mp_filter_internal_mark_failed(f);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
AMFSurface *surface = mp_image_to_amf_surface(f, in);
|
||||
if (!surface) {
|
||||
MP_WARN(f, "Failed to create AMF surface\n");
|
||||
mp_filter_internal_mark_failed(f);
|
||||
return;
|
||||
}
|
||||
|
||||
AMF_RESULT res = ICALL(SubmitInput, a->frc, (AMFData *)surface);
|
||||
// This shouldn't happen according to docs, but it does...
|
||||
if (res == AMF_EOF) {
|
||||
MP_ERR(f, "FRC SubmitInput returned AMF_EOF, reinitializing...\n");
|
||||
if (init_frc(f, p->ref_image, true) < 0) {
|
||||
MP_ERR(f, "FRC reinit failed\n");
|
||||
mp_filter_internal_mark_failed(f);
|
||||
return;
|
||||
}
|
||||
res = ICALL(SubmitInput, a->frc, (AMFData *)surface);
|
||||
}
|
||||
|
||||
ICALL(Release, surface);
|
||||
|
||||
if (res == AMF_INPUT_FULL) {
|
||||
MP_WARN(f, "FRC input queue full...\n");
|
||||
mp_pin_out_unread(f->ppins[0], frame);
|
||||
return;
|
||||
}
|
||||
|
||||
if (res != AMF_OK && res != AMF_EOF) {
|
||||
MP_WARN(f, "FRC SubmitInput failed: %d\n", res);
|
||||
mp_filter_internal_mark_failed(f);
|
||||
return;
|
||||
}
|
||||
|
||||
read_out:;
|
||||
AMFSurface *amf_out = NULL;
|
||||
res = ICALL(QueryOutput, a->frc, (AMFData **)&amf_out);
|
||||
|
||||
if (res == AMF_EOF) {
|
||||
p->has_frame = false;
|
||||
mp_pin_in_write(f->ppins[1], MAKE_FRAME(MP_FRAME_EOF, NULL));
|
||||
return;
|
||||
}
|
||||
|
||||
if (res != AMF_OK && res != AMF_REPEAT) {
|
||||
MP_WARN(f, "FRC QueryOutput returned: %d\n", res);
|
||||
mp_filter_internal_mark_failed(f);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!amf_out && res == AMF_REPEAT) {
|
||||
MP_WARN(f, "FRC QueryOutput returned NULL surface with AMF_REPEAT\n");
|
||||
mp_filter_internal_mark_failed(f);
|
||||
return;
|
||||
}
|
||||
|
||||
struct mp_image *out = mp_image_from_amf_surface(f, amf_out);
|
||||
if (!out) {
|
||||
MP_WARN(f, "Failed to create mp_image from AMF surface\n");
|
||||
ICALL(Release, amf_out);
|
||||
mp_filter_internal_mark_failed(f);
|
||||
return;
|
||||
}
|
||||
|
||||
if (res == AMF_OK) {
|
||||
p->has_frame = false;
|
||||
mp_image_unrefp(&p->ref_image);
|
||||
} else if (res == AMF_REPEAT) {
|
||||
p->has_frame = true;
|
||||
} else {
|
||||
MP_ASSERT_UNREACHABLE();
|
||||
}
|
||||
|
||||
mp_pin_in_write(f->ppins[1], MAKE_FRAME(MP_FRAME_VIDEO, out));
|
||||
}
|
||||
|
||||
static const struct mp_filter_info vf_amf_frc_filter = {
|
||||
.name = "amf_frc",
|
||||
.process = process,
|
||||
.reset = flush,
|
||||
.destroy = destroy,
|
||||
.priv_size = sizeof(struct priv),
|
||||
};
|
||||
|
||||
static struct mp_filter *vf_amf_frc_create(struct mp_filter *parent,
|
||||
void *options)
|
||||
{
|
||||
struct mp_filter *f = mp_filter_create(parent, &vf_amf_frc_filter);
|
||||
if (!f) {
|
||||
talloc_free(options);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mp_filter_add_pin(f, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(f, MP_PIN_OUT, "out");
|
||||
|
||||
struct priv *p = f->priv;
|
||||
p->opts = talloc_steal(p, options);
|
||||
p->amf = talloc_zero(NULL, struct amf_priv);
|
||||
mp_rc_init(&p->amf->ref_count);
|
||||
|
||||
if (load_amf_library(p->amf) < 0) {
|
||||
MP_ERR(f, "Failed to load AMF library\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (create_amf_context(f) < 0)
|
||||
goto fail;
|
||||
|
||||
if (create_amf_frc(f) < 0)
|
||||
goto fail;
|
||||
|
||||
return f;
|
||||
|
||||
fail:
|
||||
talloc_free(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct opts
|
||||
static const m_option_t vf_opts_fields[] = {
|
||||
{"profile", OPT_CHOICE(profile,
|
||||
{"auto", -1},
|
||||
{"low", FRC_PROFILE_LOW},
|
||||
{"high", FRC_PROFILE_HIGH},
|
||||
{"super", FRC_PROFILE_SUPER})},
|
||||
{"mv-search", OPT_CHOICE(mv_search_mode,
|
||||
{"native", FRC_MV_SEARCH_NATIVE},
|
||||
{"performance", FRC_MV_SEARCH_PERFORMANCE})},
|
||||
{"fallback", OPT_BOOL(fallback_blend)},
|
||||
{"indicator", OPT_BOOL(indicator)},
|
||||
{"future-frame", OPT_BOOL(use_future_frame)},
|
||||
{0}
|
||||
};
|
||||
|
||||
const struct mp_user_filter_entry vf_amf_frc = {
|
||||
.desc = {
|
||||
.description = "AMD Frame Rate Conversion filter",
|
||||
.name = "amf_frc",
|
||||
.priv_size = sizeof(OPT_BASE_STRUCT),
|
||||
.priv_defaults = &(const OPT_BASE_STRUCT) {
|
||||
.profile = -1,
|
||||
.mv_search_mode = FRC_MV_SEARCH_NATIVE,
|
||||
.use_future_frame = true,
|
||||
},
|
||||
.options = vf_opts_fields,
|
||||
},
|
||||
.create = vf_amf_frc_create,
|
||||
};
|
||||
Reference in new issue
Block a user