mpv/video/out/opengl/hwdec_dxva2egl.c

359 lines
11 KiB
C
Raw Normal View History

/*
* 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 <assert.h>
#include <windows.h>
#include <d3d9.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include "angle_dynamic.h"
#include "common/common.h"
#include "osdep/timer.h"
#include "osdep/windows_utils.h"
#include "hwdec.h"
#include "video/hwdec.h"
#include "video/decode/d3d.h"
struct priv {
struct mp_hwdec_ctx hwctx;
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);
hwdec_devices_remove(hw->devs, &p->hwctx);
if (p->query9)
IDirect3DQuery9_Release(p->query9);
if (p->device9ex)
IDirect3DDevice9Ex_Release(p->device9ex);
if (p->d3d9ex)
IDirect3D9Ex_Release(p->d3d9ex);
}
static int create(struct gl_hwdec *hw)
{
if (!angle_load())
return -1;
d3d_load_dlls();
EGLDisplay egl_display = eglGetCurrentDisplay();
if (!egl_display)
return -1;
if (!eglGetCurrentContext())
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;
if (!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(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;
}
p->hwctx = (struct mp_hwdec_ctx){
.type = HWDEC_DXVA2,
.driver_name = hw->driver->name,
.ctx = (IDirect3DDevice9 *)p->device9ex,
};
hwdec_devices_add(hw->devs, &p->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);
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);
vo_opengl: refactor how hwdec interop exports textures Rename gl_hwdec_driver.map_image to map_frame, and let it fill out a struct gl_hwdec_frame describing the exact texture layout. This gives more flexibility to what the hwdec interop can export. In particular, it can export strange component orders/permutations and textures with padded size. (The latter originating from cropped video.) The way gl_hwdec_frame works is in the spirit of the rest of the vo_opengl video processing code, which tends to put as much information in immediate state (as part of the dataflow), instead of declaring it globally. To some degree this duplicates the texplane and img_tex structs, but until we somehow unify those, it's better to give the hwdec state its own struct. The fact that changing the hwdec struct would require changes and testing on at least 4 platform/GPU combinations makes duplicating it almost a requirement to avoid pain later. Make gl_hwdec_driver.reinit set the new image format and remove the gl_hwdec.converted_imgfmt field. Likewise, gl_hwdec.gl_texture_target is replaced with gl_hwdec_plane.gl_target. Split out a init_image_desc function from init_format. The latter is not called in the hwdec case at all anymore. Setting up most of struct texplane is also completely separate in the hwdec and normal cases. video.c does not check whether the hwdec "mapped" image format is supported. This should not really happen anyway, and if it does, the hwdec interop backend must fail at creation time, so this is not an issue.
2016-05-10 16:29:10 +00:00
params->imgfmt = IMGFMT_RGB0;
params->hw_subfmt = 0;
return 0;
fail:
destroy_textures(hw);
return -1;
}
vo_opengl: refactor how hwdec interop exports textures Rename gl_hwdec_driver.map_image to map_frame, and let it fill out a struct gl_hwdec_frame describing the exact texture layout. This gives more flexibility to what the hwdec interop can export. In particular, it can export strange component orders/permutations and textures with padded size. (The latter originating from cropped video.) The way gl_hwdec_frame works is in the spirit of the rest of the vo_opengl video processing code, which tends to put as much information in immediate state (as part of the dataflow), instead of declaring it globally. To some degree this duplicates the texplane and img_tex structs, but until we somehow unify those, it's better to give the hwdec state its own struct. The fact that changing the hwdec struct would require changes and testing on at least 4 platform/GPU combinations makes duplicating it almost a requirement to avoid pain later. Make gl_hwdec_driver.reinit set the new image format and remove the gl_hwdec.converted_imgfmt field. Likewise, gl_hwdec.gl_texture_target is replaced with gl_hwdec_plane.gl_target. Split out a init_image_desc function from init_format. The latter is not called in the hwdec case at all anymore. Setting up most of struct texplane is also completely separate in the hwdec and normal cases. video.c does not check whether the hwdec "mapped" image format is supported. This should not really happen anyway, and if it does, the hwdec interop backend must fail at creation time, so this is not an issue.
2016-05-10 16:29:10 +00:00
static int map_frame(struct gl_hwdec *hw, struct mp_image *hw_image,
struct gl_hwdec_frame *out_frame)
{
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 = (IDirect3DSurface9 *)hw_image->planes[3];
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);
vo_opengl: refactor how hwdec interop exports textures Rename gl_hwdec_driver.map_image to map_frame, and let it fill out a struct gl_hwdec_frame describing the exact texture layout. This gives more flexibility to what the hwdec interop can export. In particular, it can export strange component orders/permutations and textures with padded size. (The latter originating from cropped video.) The way gl_hwdec_frame works is in the spirit of the rest of the vo_opengl video processing code, which tends to put as much information in immediate state (as part of the dataflow), instead of declaring it globally. To some degree this duplicates the texplane and img_tex structs, but until we somehow unify those, it's better to give the hwdec state its own struct. The fact that changing the hwdec struct would require changes and testing on at least 4 platform/GPU combinations makes duplicating it almost a requirement to avoid pain later. Make gl_hwdec_driver.reinit set the new image format and remove the gl_hwdec.converted_imgfmt field. Likewise, gl_hwdec.gl_texture_target is replaced with gl_hwdec_plane.gl_target. Split out a init_image_desc function from init_format. The latter is not called in the hwdec case at all anymore. Setting up most of struct texplane is also completely separate in the hwdec and normal cases. video.c does not check whether the hwdec "mapped" image format is supported. This should not really happen anyway, and if it does, the hwdec interop backend must fail at creation time, so this is not an issue.
2016-05-10 16:29:10 +00:00
*out_frame = (struct gl_hwdec_frame){
.planes = {
{
.gl_texture = p->gl_texture,
.gl_target = GL_TEXTURE_2D,
.tex_w = hw_image->w,
.tex_h = hw_image->h,
},
},
};
return 0;
}
const struct gl_hwdec_driver gl_hwdec_dxva2egl = {
.name = "dxva2-egl",
.api = HWDEC_DXVA2,
.imgfmt = IMGFMT_DXVA2,
.create = create,
.reinit = reinit,
vo_opengl: refactor how hwdec interop exports textures Rename gl_hwdec_driver.map_image to map_frame, and let it fill out a struct gl_hwdec_frame describing the exact texture layout. This gives more flexibility to what the hwdec interop can export. In particular, it can export strange component orders/permutations and textures with padded size. (The latter originating from cropped video.) The way gl_hwdec_frame works is in the spirit of the rest of the vo_opengl video processing code, which tends to put as much information in immediate state (as part of the dataflow), instead of declaring it globally. To some degree this duplicates the texplane and img_tex structs, but until we somehow unify those, it's better to give the hwdec state its own struct. The fact that changing the hwdec struct would require changes and testing on at least 4 platform/GPU combinations makes duplicating it almost a requirement to avoid pain later. Make gl_hwdec_driver.reinit set the new image format and remove the gl_hwdec.converted_imgfmt field. Likewise, gl_hwdec.gl_texture_target is replaced with gl_hwdec_plane.gl_target. Split out a init_image_desc function from init_format. The latter is not called in the hwdec case at all anymore. Setting up most of struct texplane is also completely separate in the hwdec and normal cases. video.c does not check whether the hwdec "mapped" image format is supported. This should not really happen anyway, and if it does, the hwdec interop backend must fail at creation time, so this is not an issue.
2016-05-10 16:29:10 +00:00
.map_frame = map_frame,
.destroy = destroy,
};