diff --git a/video/out/opengl/hwdec.c b/video/out/opengl/hwdec.c index 300197ac00..b58af9bae9 100644 --- a/video/out/opengl/hwdec.c +++ b/video/out/opengl/hwdec.c @@ -28,6 +28,7 @@ extern const struct gl_hwdec_driver gl_hwdec_vaegl; extern const struct gl_hwdec_driver gl_hwdec_vaglx; extern const struct gl_hwdec_driver gl_hwdec_videotoolbox; extern const struct gl_hwdec_driver gl_hwdec_vdpau; +extern const struct gl_hwdec_driver gl_hwdec_dxva2egl; extern const struct gl_hwdec_driver gl_hwdec_dxva2gldx; extern const struct gl_hwdec_driver gl_hwdec_dxva2; @@ -45,6 +46,9 @@ static const struct gl_hwdec_driver *const mpgl_hwdec_drivers[] = { &gl_hwdec_videotoolbox, #endif #if HAVE_DXVA2_HWACCEL +#if HAVE_EGL_ANGLE + &gl_hwdec_dxva2egl, +#endif #if HAVE_GL_DXINTEROP &gl_hwdec_dxva2gldx, #endif diff --git a/video/out/opengl/hwdec_dxva2egl.c b/video/out/opengl/hwdec_dxva2egl.c new file mode 100644 index 0000000000..f2c15145ef --- /dev/null +++ b/video/out/opengl/hwdec_dxva2egl.c @@ -0,0 +1,346 @@ +/* + * 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 . + */ + +#include +#include +#include +#include + +#include "common/common.h" +#include "osdep/timer.h" +#include "osdep/windows_utils.h" +#include "hwdec.h" +#include "video/dxva2.h" +#include "video/d3d.h" +#include "video/hwdec.h" + +struct priv { + struct mp_d3d_ctx ctx; + + HMODULE d3d9_dll; + IDirect3D9Ex *d3d9ex; + IDirect3DDevice9Ex *device9ex; + IDirect3DQuery9 *query9; + IDirect3DTexture9 *texture9; + IDirect3DSurface9 *surface9; + + EGLDisplay egl_display; + EGLConfig egl_config; + EGLint alpha; + EGLSurface egl_surface; + + GLuint gl_texture; +}; + +static void destroy_textures(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_display && p->egl_surface) { + eglReleaseTexImage(p->egl_display, p->egl_surface, EGL_BACK_BUFFER); + eglDestroySurface(p->egl_display, p->egl_surface); + p->egl_surface = NULL; + } + + if (p->surface9) { + IDirect3DSurface9_Release(p->surface9); + p->surface9 = NULL; + } + + if (p->texture9) { + IDirect3DTexture9_Release(p->texture9); + p->texture9 = NULL; + } +} + +static void destroy(struct gl_hwdec *hw) +{ + struct priv *p = hw->priv; + + destroy_textures(hw); + + if (p->query9) + IDirect3DQuery9_Release(p->query9); + + if (p->device9ex) + IDirect3DDevice9Ex_Release(p->device9ex); + + if (p->d3d9ex) + IDirect3D9Ex_Release(p->d3d9ex); + + if (p->d3d9_dll) + FreeLibrary(p->d3d9_dll); +} + +static int create(struct gl_hwdec *hw) +{ + if (hw->hwctx) + return -1; + + EGLDisplay egl_display = eglGetCurrentDisplay(); + if (!egl_display) + return -1; + + const char *exts = eglQueryString(egl_display, EGL_EXTENSIONS); + if (!exts || + !strstr(exts, "EGL_ANGLE_d3d_share_handle_client_buffer")) { + return -1; + } + + HRESULT hr; + struct priv *p = talloc_zero(hw, struct priv); + hw->priv = p; + + p->egl_display = egl_display; + + p->d3d9_dll = LoadLibraryW(L"d3d9.dll"); + if (!p->d3d9_dll) { + MP_FATAL(hw, "Failed to load \"d3d9.dll\": %s\n", + mp_LastError_to_str()); + goto fail; + } + + HRESULT (WINAPI *Direct3DCreate9Ex)(UINT SDKVersion, IDirect3D9Ex **ppD3D); + Direct3DCreate9Ex = (void *)GetProcAddress(p->d3d9_dll, "Direct3DCreate9Ex"); + if (!Direct3DCreate9Ex) { + MP_FATAL(hw, "Direct3D 9Ex not supported\n"); + goto fail; + } + + hr = Direct3DCreate9Ex(D3D_SDK_VERSION, &p->d3d9ex); + if (FAILED(hr)) { + MP_FATAL(hw, "Couldn't create Direct3D9Ex: %s\n", + mp_HRESULT_to_str(hr)); + goto fail; + } + + // We must create our own Direct3D9Ex device. ANGLE can give us the device + // it's using, but that's probably a ID3D11Device. + // (copied from chromium dxva_video_decode_accelerator_win.cc) + D3DPRESENT_PARAMETERS present_params = { + .BackBufferWidth = 1, + .BackBufferHeight = 1, + .BackBufferFormat = D3DFMT_UNKNOWN, + .BackBufferCount = 1, + .SwapEffect = D3DSWAPEFFECT_DISCARD, + .hDeviceWindow = NULL, + .Windowed = TRUE, + .Flags = D3DPRESENTFLAG_VIDEO, + .FullScreen_RefreshRateInHz = 0, + .PresentationInterval = 0, + }; + hr = IDirect3D9Ex_CreateDeviceEx(p->d3d9ex, + D3DADAPTER_DEFAULT, + D3DDEVTYPE_HAL, + NULL, + D3DCREATE_FPU_PRESERVE | + D3DCREATE_HARDWARE_VERTEXPROCESSING | + D3DCREATE_DISABLE_PSGP_THREADING | + D3DCREATE_MULTITHREADED, + &present_params, + NULL, + &p->device9ex); + if (FAILED(hr)) { + MP_FATAL(hw, "Failed to create Direct3D9Ex device: %s\n", + mp_HRESULT_to_str(hr)); + goto fail; + } + + hr = IDirect3DDevice9_CreateQuery(p->device9ex, D3DQUERYTYPE_EVENT, + &p->query9); + if (FAILED(hr)) { + MP_FATAL(hw, "Failed to create Direct3D query interface: %s\n", + mp_HRESULT_to_str(hr)); + goto fail; + } + + // Test the query API + hr = IDirect3DQuery9_Issue(p->query9, D3DISSUE_END); + if (FAILED(hr)) { + MP_FATAL(hw, "Failed to issue Direct3D END test query: %s\n", + mp_HRESULT_to_str(hr)); + goto fail; + } + + EGLint attrs[] = { + EGL_BUFFER_SIZE, 32, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, + EGL_ALPHA_SIZE, 0, + EGL_NONE + }; + EGLint count; + if (!eglChooseConfig(p->egl_display, attrs, &p->egl_config, 1, &count) || + !count) { + MP_ERR(hw, "Failed to get EGL surface configuration\n"); + goto fail; + } + + if (!eglGetConfigAttrib(p->egl_display, p->egl_config, + EGL_BIND_TO_TEXTURE_RGBA, &p->alpha)) { + MP_FATAL(hw, "Failed to query EGL surface alpha\n"); + goto fail; + } + + hw->converted_imgfmt = p->alpha ? IMGFMT_RGBA : IMGFMT_RGB0; + MP_VERBOSE(hw, "Using image format %s\n", + mp_imgfmt_to_name(hw->converted_imgfmt)); + + p->ctx.d3d9_device = (IDirect3DDevice9 *)p->device9ex; + p->ctx.hwctx.type = HWDEC_DXVA2; + p->ctx.hwctx.d3d_ctx = &p->ctx; + + hw->hwctx = &p->ctx.hwctx; + return 0; +fail: + destroy(hw); + return -1; +} + +static int reinit(struct gl_hwdec *hw, struct mp_image_params *params) +{ + struct priv *p = hw->priv; + GL *gl = hw->gl; + HRESULT hr; + + destroy_textures(hw); + + assert(params->imgfmt == hw->driver->imgfmt); + + HANDLE share_handle = NULL; + hr = IDirect3DDevice9Ex_CreateTexture(p->device9ex, + params->w, params->h, + 1, D3DUSAGE_RENDERTARGET, + p->alpha ? D3DFMT_A8R8G8B8 : D3DFMT_X8R8G8B8, + D3DPOOL_DEFAULT, + &p->texture9, + &share_handle); + if (FAILED(hr)) { + MP_ERR(hw, "Failed to create Direct3D9 texture: %s\n", + mp_HRESULT_to_str(hr)); + goto fail; + } + + hr = IDirect3DTexture9_GetSurfaceLevel(p->texture9, 0, &p->surface9); + if (FAILED(hr)) { + MP_ERR(hw, "Failed to get Direct3D9 surface from texture: %s\n", + mp_HRESULT_to_str(hr)); + goto fail; + } + + EGLint attrib_list[] = { + EGL_WIDTH, params->w, + EGL_HEIGHT, params->h, + EGL_TEXTURE_FORMAT, p->alpha ? EGL_TEXTURE_RGBA : EGL_TEXTURE_RGB, + EGL_TEXTURE_TARGET, EGL_TEXTURE_2D, + EGL_NONE + }; + p->egl_surface = eglCreatePbufferFromClientBuffer( + p->egl_display, EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE, + share_handle, p->egl_config, attrib_list); + if (p->egl_surface == EGL_NO_SURFACE) { + MP_ERR(hw, "Failed to create EGL surface\n"); + goto fail; + } + + gl->GenTextures(1, &p->gl_texture); + gl->BindTexture(GL_TEXTURE_2D, p->gl_texture); + 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); + gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + gl->BindTexture(GL_TEXTURE_2D, 0); + + return 0; +fail: + destroy_textures(hw); + return -1; +} + +static int map_image(struct gl_hwdec *hw, struct mp_image *hw_image, + GLuint *out_textures) +{ + struct priv *p = hw->priv; + GL *gl = hw->gl; + if (!p->surface9 || !p->egl_surface || !p->gl_texture) + return -1; + + HRESULT hr; + RECT rc = {0, 0, hw_image->w, hw_image->h}; + IDirect3DSurface9* hw_surface = d3d9_surface_in_mp_image(hw_image); + hr = IDirect3DDevice9Ex_StretchRect(p->device9ex, + hw_surface, &rc, + p->surface9, &rc, + D3DTEXF_NONE); + if (FAILED(hr)) { + MP_ERR(hw, "Direct3D RGB conversion failed: %s\n", + mp_HRESULT_to_str(hr)); + return -1; + } + + hr = IDirect3DQuery9_Issue(p->query9, D3DISSUE_END); + if (FAILED(hr)) { + MP_ERR(hw, "Failed to issue Direct3D END query\n"); + return -1; + } + + // There doesn't appear to be an efficient way to do a blocking flush + // of the above StretchRect. Timeout of 8ms is required to reliably + // render 4k on Intel Haswell, Ivybridge and Cherry Trail Atom. + const int max_retries = 8; + const int64_t wait_us = 1000; + int retries = 0; + while (true) { + hr = IDirect3DQuery9_GetData(p->query9, NULL, 0, D3DGETDATA_FLUSH); + if (FAILED(hr)) { + MP_ERR(hw, "Failed to query Direct3D flush state\n"); + return -1; + } else if (hr == S_FALSE) { + if (++retries > max_retries) { + MP_VERBOSE(hw, "Failed to flush frame after %lld ms\n", + (long long)(wait_us * max_retries) / 1000); + break; + } + mp_sleep_us(wait_us); + } else { + break; + } + } + + gl->BindTexture(GL_TEXTURE_2D, p->gl_texture); + eglBindTexImage(p->egl_display, p->egl_surface, EGL_BACK_BUFFER); + gl->BindTexture(GL_TEXTURE_2D, 0); + + out_textures[0] = p->gl_texture; + return 0; +} + +const struct gl_hwdec_driver gl_hwdec_dxva2egl = { + .name = "dxva2-egl", + .api = HWDEC_DXVA2, + .imgfmt = IMGFMT_DXVA2, + .create = create, + .reinit = reinit, + .map_image = map_image, + .destroy = destroy, +}; diff --git a/wscript_build.py b/wscript_build.py index 030c997b98..f6e5dd9cad 100644 --- a/wscript_build.py +++ b/wscript_build.py @@ -340,6 +340,7 @@ def build(ctx): ( "video/out/opengl/hwdec.c", "gl" ), ( "video/out/opengl/hwdec_dxva2.c", "gl-win32" ), ( "video/out/opengl/hwdec_dxva2gldx.c", "gl-dxinterop" ), + ( "video/out/opengl/hwdec_dxva2egl.c", "egl-angle" ), ( "video/out/opengl/hwdec_vaegl.c", "vaapi-egl" ), ( "video/out/opengl/hwdec_vaglx.c", "vaapi-glx" ), ( "video/out/opengl/hwdec_osx.c", "videotoolbox-gl" ),