vo_opengl: d3d11egl: native NV12 sampling support

This uses EGL_ANGLE_stream_producer_d3d_texture_nv12 and related
extensions to map the D3D textures coming from the hardware decoder
directly in GL.

In theory this would be trivial to achieve, but unfortunately ANGLE does
not have a mechanism to "import" D3D textures as GL textures. Instead,
an awkward mechanism via EGL_KHR_stream was implemented, which involves
at least 5 extensions and a lot of glue code. (Even worse than VAAPI EGL
interop, and very far from the simplicity you get on OSX.)

The ANGLE mechanism so far supports only the NV12 texture format, which
means 10 bit won't work. It also does not work in ES3 mode yet. For
these reasons, the "old" ID3D11VideoProcessor code is kept and used as a
fallback.
This commit is contained in:
wm4 2016-05-10 20:37:03 +02:00
parent 4b3faf9dc1
commit a3d416c3d3
4 changed files with 251 additions and 20 deletions

View File

@ -187,6 +187,29 @@ static const struct d3d_decoded_format d3d11_formats[] = {
};
#undef DFMT
// Update hw_subfmt to the underlying format. Needed because AVFrame does not
// have such an attribute, so it can't be passed through, and is updated here
// instead. (But in the future, AVHWFramesContext could be used.)
static struct mp_image *d3d11va_update_image_attribs(struct lavc_ctx *s,
struct mp_image *img)
{
ID3D11Texture2D *texture = (void *)img->planes[1];
if (!texture)
return img;
D3D11_TEXTURE2D_DESC texture_desc;
ID3D11Texture2D_GetDesc(texture, &texture_desc);
for (int n = 0; n < MP_ARRAY_SIZE(d3d11_formats); n++) {
if (d3d11_formats[n].dxfmt == texture_desc.Format) {
img->params.hw_subfmt = d3d11_formats[n].mpfmt;
break;
}
}
return img;
}
static bool d3d11_format_supported(struct lavc_ctx *s, const GUID *guid,
const struct d3d_decoded_format *format)
{
@ -274,7 +297,7 @@ static int d3d11va_init_decoder(struct lavc_ctx *s, int w, int h)
.MiscFlags = 0,
.ArraySize = n_surfaces,
.Usage = D3D11_USAGE_DEFAULT,
.BindFlags = D3D11_BIND_DECODER,
.BindFlags = D3D11_BIND_DECODER | D3D11_BIND_SHADER_RESOURCE,
.CPUAccessFlags = 0,
};
hr = ID3D11Device_CreateTexture2D(p->device, &tex_desc, NULL, &texture);
@ -555,6 +578,7 @@ const struct vd_lavc_hwdec mp_vd_lavc_d3d11va = {
.uninit = d3d11va_uninit,
.init_decoder = d3d11va_init_decoder,
.allocate_image = d3d11va_allocate_image,
.process_image = d3d11va_update_image_attribs,
};
const struct vd_lavc_hwdec mp_vd_lavc_d3d11va_copy = {

View File

@ -88,6 +88,11 @@
#define GL_UNSIGNED_SHORT_8_8_REV_APPLE 0x85BB
#endif
// GL_OES_EGL_image_external, GL_NV_EGL_stream_consumer_external
#ifndef GL_TEXTURE_EXTERNAL_OES
#define GL_TEXTURE_EXTERNAL_OES 0x8D65
#endif
#undef MP_GET_GL_WORKAROUNDS
#endif // MP_GET_GL_WORKAROUNDS

View File

@ -29,6 +29,10 @@
#include "hwdec.h"
#include "video/hwdec.h"
#ifndef EGL_D3D_TEXTURE_SUBRESOURCE_ID_ANGLE
#define EGL_D3D_TEXTURE_SUBRESOURCE_ID_ANGLE 0x3AAB
#endif
struct priv {
struct mp_hwdec_ctx hwctx;
@ -44,9 +48,36 @@ struct priv {
ID3D11VideoProcessor *video_proc;
ID3D11VideoProcessorEnumerator *vp_enum;
ID3D11VideoProcessorOutputView *out_view;
struct mp_image_params image_params;
int c_w, c_h;
GLuint gl_texture;
EGLStreamKHR egl_stream;
GLuint gl_textures[3];
// EGL_KHR_stream
EGLStreamKHR (EGLAPIENTRY *CreateStreamKHR)(EGLDisplay dpy,
const EGLint *attrib_list);
EGLBoolean (EGLAPIENTRY *DestroyStreamKHR)(EGLDisplay dpy,
EGLStreamKHR stream);
// EGL_KHR_stream_consumer_gltexture
EGLBoolean (EGLAPIENTRY *StreamConsumerAcquireKHR)
(EGLDisplay dpy, EGLStreamKHR stream);
EGLBoolean (EGLAPIENTRY *StreamConsumerReleaseKHR)
(EGLDisplay dpy, EGLStreamKHR stream);
// EGL_NV_stream_consumer_gltexture_yuv
EGLBoolean (EGLAPIENTRY *StreamConsumerGLTextureExternalAttribsNV)
(EGLDisplay dpy, EGLStreamKHR stream, EGLAttrib *attrib_list);
// EGL_ANGLE_stream_producer_d3d_texture_nv12
EGLBoolean (EGLAPIENTRY *CreateStreamProducerD3DTextureNV12ANGLE)
(EGLDisplay dpy, EGLStreamKHR stream, const EGLAttrib *attrib_list);
EGLBoolean (EGLAPIENTRY *StreamPostD3DTextureNV12ANGLE)
(EGLDisplay dpy, EGLStreamKHR stream, void *texture,
const EGLAttrib *attrib_list);
};
static void destroy_video_proc(struct gl_hwdec *hw)
@ -71,8 +102,14 @@ static void destroy_objects(struct gl_hwdec *hw)
struct priv *p = hw->priv;
GL *gl = hw->gl;
gl->DeleteTextures(1, &p->gl_texture);
p->gl_texture = 0;
if (p->egl_stream)
p->DestroyStreamKHR(p->egl_display, p->egl_stream);
p->egl_stream = 0;
for (int n = 0; n < 3; n++) {
gl->DeleteTextures(1, &p->gl_textures[n]);
p->gl_textures[n] = 0;
}
if (p->egl_display && p->egl_surface) {
eglReleaseTexImage(p->egl_display, p->egl_surface, EGL_BACK_BUFFER);
@ -135,6 +172,38 @@ static int create(struct gl_hwdec *hw)
p->egl_display = egl_display;
// Optional EGLStream stuff for working without video processor.
// Note that as long as GL_OES_EGL_image_external_essl3 is not available,
// this won't work in ES 3.x mode due to missing GLSL mechanisms.
if (strstr(exts, "EGL_ANGLE_stream_producer_d3d_texture_nv12") &&
hw->gl->es == 200)
{
MP_VERBOSE(hw, "Loading EGL_ANGLE_stream_producer_d3d_texture_nv12\n");
p->CreateStreamKHR = (void *)eglGetProcAddress("eglCreateStreamKHR");
p->DestroyStreamKHR = (void *)eglGetProcAddress("eglDestroyStreamKHR");
p->StreamConsumerAcquireKHR =
(void *)eglGetProcAddress("eglStreamConsumerAcquireKHR");
p->StreamConsumerReleaseKHR =
(void *)eglGetProcAddress("eglStreamConsumerReleaseKHR");
p->StreamConsumerGLTextureExternalAttribsNV =
(void *)eglGetProcAddress("eglStreamConsumerGLTextureExternalAttribsNV");
p->CreateStreamProducerD3DTextureNV12ANGLE =
(void *)eglGetProcAddress("eglCreateStreamProducerD3DTextureNV12ANGLE");
p->StreamPostD3DTextureNV12ANGLE =
(void *)eglGetProcAddress("eglStreamPostD3DTextureNV12ANGLE");
if (!p->CreateStreamKHR || !p->DestroyStreamKHR ||
!p->StreamConsumerAcquireKHR || !p->StreamConsumerReleaseKHR ||
!p->StreamConsumerGLTextureExternalAttribsNV ||
!p->CreateStreamProducerD3DTextureNV12ANGLE ||
!p->StreamPostD3DTextureNV12ANGLE)
{
MP_ERR(hw, "Failed to load some EGLStream functions.\n");
goto fail;
}
}
EGLAttrib device = 0;
if (!p_eglQueryDisplayAttribEXT(egl_display, EGL_DEVICE_EXT, &device))
goto fail;
@ -208,6 +277,73 @@ fail:
return -1;
}
static int create_egl_stream(struct gl_hwdec *hw, struct mp_image_params *params)
{
struct priv *p = hw->priv;
GL *gl = hw->gl;
if (params->hw_subfmt != IMGFMT_NV12)
return -1;
if (!p->CreateStreamKHR)
return -1; // extensions not available
MP_VERBOSE(hw, "Using EGL_KHR_stream path.\n");
// Hope that the given texture unit range is not "in use" by anything.
// The texture units need to be bound during init only, and are free for
// use again after the initialization here is done.
int texunits = 0; // [texunits, texunits + num_planes)
int num_planes = 2;
int gl_target = GL_TEXTURE_EXTERNAL_OES;
p->egl_stream = p->CreateStreamKHR(p->egl_display, (EGLint[]){EGL_NONE});
if (!p->egl_stream)
goto fail;
for (int n = 0; n < num_planes; n++) {
gl->ActiveTexture(GL_TEXTURE0 + texunits + n);
gl->GenTextures(1, &p->gl_textures[n]);
gl->BindTexture(gl_target, p->gl_textures[n]);
gl->TexParameteri(gl_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
gl->TexParameteri(gl_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
gl->TexParameteri(gl_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
gl->TexParameteri(gl_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
EGLAttrib attrs[] = {
EGL_COLOR_BUFFER_TYPE, EGL_YUV_BUFFER_EXT,
EGL_YUV_NUMBER_OF_PLANES_EXT, num_planes,
EGL_YUV_PLANE0_TEXTURE_UNIT_NV, texunits + 0,
EGL_YUV_PLANE1_TEXTURE_UNIT_NV, texunits + 1,
EGL_NONE,
};
if (!p->StreamConsumerGLTextureExternalAttribsNV(p->egl_display, p->egl_stream,
attrs))
goto fail;
if (!p->CreateStreamProducerD3DTextureNV12ANGLE(p->egl_display, p->egl_stream,
(EGLAttrib[]){EGL_NONE}))
goto fail;
params->imgfmt = params->hw_subfmt;
for (int n = 0; n < num_planes; n++) {
gl->ActiveTexture(GL_TEXTURE0 + texunits + n);
gl->BindTexture(gl_target, 0);
}
gl->ActiveTexture(GL_TEXTURE0);
return 0;
fail:
MP_ERR(hw, "Failed to create EGLStream\n");
if (p->egl_stream)
p->DestroyStreamKHR(p->egl_display, p->egl_stream);
p->egl_stream = 0;
gl->ActiveTexture(GL_TEXTURE0);
return -1;
}
static int reinit(struct gl_hwdec *hw, struct mp_image_params *params)
{
struct priv *p = hw->priv;
@ -216,6 +352,14 @@ static int reinit(struct gl_hwdec *hw, struct mp_image_params *params)
destroy_objects(hw);
p->image_params = *params;
// If this does not work, use the video process instead.
if (create_egl_stream(hw, params) >= 0)
return 0;
MP_VERBOSE(hw, "Using ID3D11VideoProcessor path.\n");
D3D11_TEXTURE2D_DESC texdesc = {
.Width = params->w,
.Height = params->h,
@ -264,8 +408,8 @@ static int reinit(struct gl_hwdec *hw, struct mp_image_params *params)
goto fail;
}
gl->GenTextures(1, &p->gl_texture);
gl->BindTexture(GL_TEXTURE_2D, p->gl_texture);
gl->GenTextures(1, &p->gl_textures[0]);
gl->BindTexture(GL_TEXTURE_2D, p->gl_textures[0]);
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
@ -355,27 +499,19 @@ fail:
return -1;
}
static int map_frame(struct gl_hwdec *hw, struct mp_image *hw_image,
struct gl_hwdec_frame *out_frame)
static int map_frame_video_proc(struct gl_hwdec *hw, ID3D11Texture2D *d3d_tex,
int d3d_subindex, struct gl_hwdec_frame *out_frame)
{
struct priv *p = hw->priv;
HRESULT hr;
ID3D11VideoProcessorInputView *in_view = NULL;
if (!p->gl_texture)
return -1;
ID3D11Texture2D *d3d_tex = (void *)hw_image->planes[1];
int d3d_subindex = (intptr_t)hw_image->planes[2];
if (!d3d_tex)
return -1;
D3D11_TEXTURE2D_DESC texdesc;
ID3D11Texture2D_GetDesc(d3d_tex, &texdesc);
if (!p->video_proc || p->c_w != texdesc.Width || p->c_h != texdesc.Height) {
p->c_w = texdesc.Width;
p->c_h = texdesc.Height;
if (create_video_proc(hw, &hw_image->params) < 0)
if (create_video_proc(hw, &p->image_params) < 0)
return -1;
}
@ -410,16 +546,80 @@ static int map_frame(struct gl_hwdec *hw, struct mp_image *hw_image,
*out_frame = (struct gl_hwdec_frame){
.planes = {
{
.gl_texture = p->gl_texture,
.gl_texture = p->gl_textures[0],
.gl_target = GL_TEXTURE_2D,
.tex_w = hw_image->w,
.tex_h = hw_image->h,
.tex_w = p->image_params.w,
.tex_h = p->image_params.h,
},
},
};
return 0;
}
static int map_frame_egl_stream(struct gl_hwdec *hw, ID3D11Texture2D *d3d_tex,
int d3d_subindex, struct gl_hwdec_frame *out_frame)
{
struct priv *p = hw->priv;
EGLAttrib attrs[] = {
EGL_D3D_TEXTURE_SUBRESOURCE_ID_ANGLE, d3d_subindex,
EGL_NONE,
};
if (!p->StreamPostD3DTextureNV12ANGLE(p->egl_display, p->egl_stream,
(void *)d3d_tex, attrs))
return -1;
if (!p->StreamConsumerAcquireKHR(p->egl_display, p->egl_stream))
return -1;
D3D11_TEXTURE2D_DESC texdesc;
ID3D11Texture2D_GetDesc(d3d_tex, &texdesc);
*out_frame = (struct gl_hwdec_frame){
.planes = {
{
.gl_texture = p->gl_textures[0],
.gl_target = GL_TEXTURE_EXTERNAL_OES,
.tex_w = texdesc.Width,
.tex_h = texdesc.Height,
},
{
.gl_texture = p->gl_textures[1],
.gl_target = GL_TEXTURE_EXTERNAL_OES,
.tex_w = texdesc.Width / 2,
.tex_h = texdesc.Height / 2,
.swizzle = "rgba", // even in ES2 mode (no LUMINANCE_ALPHA)
},
},
};
return 0;
}
static int map_frame(struct gl_hwdec *hw, struct mp_image *hw_image,
struct gl_hwdec_frame *out_frame)
{
struct priv *p = hw->priv;
if (!p->gl_textures[0])
return -1;
ID3D11Texture2D *d3d_tex = (void *)hw_image->planes[1];
int d3d_subindex = (intptr_t)hw_image->planes[2];
if (!d3d_tex)
return -1;
return p->egl_stream
? map_frame_egl_stream(hw, d3d_tex, d3d_subindex, out_frame)
: map_frame_video_proc(hw, d3d_tex, d3d_subindex, out_frame);
}
static void unmap(struct gl_hwdec *hw)
{
struct priv *p = hw->priv;
if (p->egl_stream)
p->StreamConsumerReleaseKHR(p->egl_display, p->egl_stream);
}
const struct gl_hwdec_driver gl_hwdec_d3d11egl = {
.name = "d3d11-egl",
.api = HWDEC_D3D11VA,
@ -427,5 +627,6 @@ const struct gl_hwdec_driver gl_hwdec_d3d11egl = {
.create = create,
.reinit = reinit,
.map_frame = map_frame,
.unmap = unmap,
.destroy = destroy,
};

View File

@ -664,6 +664,7 @@ const char* mp_sampler_type(GLenum texture_target)
case GL_TEXTURE_1D: return "sampler1D";
case GL_TEXTURE_2D: return "sampler2D";
case GL_TEXTURE_RECTANGLE: return "sampler2DRect";
case GL_TEXTURE_EXTERNAL_OES: return "samplerExternalOES";
case GL_TEXTURE_3D: return "sampler3D";
default: abort();
}