1
0
mirror of https://github.com/mpv-player/mpv synced 2024-12-11 01:16:45 +00:00
mpv/video/out/opengl/hwdec_dxva2egl.c
Philip Langdale 7bbf132e20 vo: hwdec: add AVHWDeviceType property to hwdecs
As the first step towards handling scenarios where the are multiple hwdecs for
a given image format but backed by different AVHWDeviceTypes, let us annotate
the hwdecs with their corresponding device types.

From this, we can also see how all the existing hwdecs which match the same
image format also match the same device type.
2024-08-10 14:24:36 -07:00

386 lines
12 KiB
C

/*
* 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 "video/out/gpu/hwdec.h"
#include "ra_gl.h"
#include "video/hwdec.h"
#include "video/d3d.h"
struct priv_owner {
struct mp_hwdec_ctx hwctx;
IDirect3D9Ex *d3d9ex;
IDirect3DDevice9Ex *device9ex;
EGLDisplay egl_display;
EGLConfig egl_config;
EGLint alpha;
};
struct priv {
IDirect3DDevice9Ex *device9ex; // (no own reference)
IDirect3DQuery9 *query9;
IDirect3DTexture9 *texture9;
IDirect3DSurface9 *surface9;
EGLDisplay egl_display;
EGLSurface egl_surface;
GLuint gl_texture;
};
static void uninit(struct ra_hwdec *hw)
{
struct priv_owner *p = hw->priv;
hwdec_devices_remove(hw->devs, &p->hwctx);
av_buffer_unref(&p->hwctx.av_device_ref);
if (p->device9ex)
IDirect3DDevice9Ex_Release(p->device9ex);
if (p->d3d9ex)
IDirect3D9Ex_Release(p->d3d9ex);
}
static int init(struct ra_hwdec *hw)
{
struct priv_owner *p = hw->priv;
HRESULT hr;
if (!ra_is_gl(hw->ra_ctx->ra))
return -1;
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 (!gl_check_extension(exts, "EGL_ANGLE_d3d_share_handle_client_buffer")) {
return -1;
}
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;
}
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;
}
struct mp_image_params dummy_params = {
.imgfmt = IMGFMT_DXVA2,
.w = 256,
.h = 256,
};
struct ra_hwdec_mapper *mapper = ra_hwdec_mapper_create(hw, &dummy_params);
if (!mapper)
goto fail;
ra_hwdec_mapper_free(&mapper);
p->hwctx = (struct mp_hwdec_ctx){
.driver_name = hw->driver->name,
.av_device_ref = d3d9_wrap_device_ref((IDirect3DDevice9 *)p->device9ex),
.hw_imgfmt = IMGFMT_DXVA2,
};
if (!p->hwctx.av_device_ref) {
MP_VERBOSE(hw, "Failed to create hwdevice_ctx\n");
goto fail;
}
hwdec_devices_add(hw->devs, &p->hwctx);
return 0;
fail:
return -1;
}
static void mapper_uninit(struct ra_hwdec_mapper *mapper)
{
struct priv *p = mapper->priv;
GL *gl = ra_gl_get(mapper->ra);
ra_tex_free(mapper->ra, &mapper->tex[0]);
gl->DeleteTextures(1, &p->gl_texture);
if (p->egl_display && p->egl_surface) {
eglReleaseTexImage(p->egl_display, p->egl_surface, EGL_BACK_BUFFER);
eglDestroySurface(p->egl_display, p->egl_surface);
}
if (p->surface9)
IDirect3DSurface9_Release(p->surface9);
if (p->texture9)
IDirect3DTexture9_Release(p->texture9);
if (p->query9)
IDirect3DQuery9_Release(p->query9);
}
static int mapper_init(struct ra_hwdec_mapper *mapper)
{
struct priv_owner *p_owner = mapper->owner->priv;
struct priv *p = mapper->priv;
GL *gl = ra_gl_get(mapper->ra);
HRESULT hr;
p->device9ex = p_owner->device9ex;
p->egl_display = p_owner->egl_display;
hr = IDirect3DDevice9_CreateQuery(p->device9ex, D3DQUERYTYPE_EVENT,
&p->query9);
if (FAILED(hr)) {
MP_FATAL(mapper, "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(mapper, "Failed to issue Direct3D END test query: %s\n",
mp_HRESULT_to_str(hr));
goto fail;
}
HANDLE share_handle = NULL;
hr = IDirect3DDevice9Ex_CreateTexture(p->device9ex,
mapper->src_params.w,
mapper->src_params.h,
1, D3DUSAGE_RENDERTARGET,
p_owner->alpha ?
D3DFMT_A8R8G8B8 : D3DFMT_X8R8G8B8,
D3DPOOL_DEFAULT,
&p->texture9,
&share_handle);
if (FAILED(hr)) {
MP_ERR(mapper, "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(mapper, "Failed to get Direct3D9 surface from texture: %s\n",
mp_HRESULT_to_str(hr));
goto fail;
}
EGLint attrib_list[] = {
EGL_WIDTH, mapper->src_params.w,
EGL_HEIGHT, mapper->src_params.h,
EGL_TEXTURE_FORMAT, p_owner->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_owner->egl_config, attrib_list);
if (p->egl_surface == EGL_NO_SURFACE) {
MP_ERR(mapper, "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);
struct ra_tex_params params = {
.dimensions = 2,
.w = mapper->src_params.w,
.h = mapper->src_params.h,
.d = 1,
.format = ra_find_unorm_format(mapper->ra, 1, p_owner->alpha ? 4 : 3),
.render_src = true,
.src_linear = true,
};
if (!params.format)
goto fail;
mapper->tex[0] = ra_create_wrapped_tex(mapper->ra, &params, p->gl_texture);
if (!mapper->tex[0])
goto fail;
mapper->dst_params = mapper->src_params;
mapper->dst_params.imgfmt = IMGFMT_RGB0;
mapper->dst_params.hw_subfmt = 0;
return 0;
fail:
return -1;
}
static int mapper_map(struct ra_hwdec_mapper *mapper)
{
struct priv *p = mapper->priv;
GL *gl = ra_gl_get(mapper->ra);
HRESULT hr;
RECT rc = {0, 0, mapper->src->w, mapper->src->h};
IDirect3DSurface9* hw_surface = (IDirect3DSurface9 *)mapper->src->planes[3];
hr = IDirect3DDevice9Ex_StretchRect(p->device9ex,
hw_surface, &rc,
p->surface9, &rc,
D3DTEXF_NONE);
if (FAILED(hr)) {
MP_ERR(mapper, "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(mapper, "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_ns = MP_TIME_MS_TO_NS(1);
int retries = 0;
while (true) {
hr = IDirect3DQuery9_GetData(p->query9, NULL, 0, D3DGETDATA_FLUSH);
if (FAILED(hr)) {
MP_ERR(mapper, "Failed to query Direct3D flush state\n");
return -1;
} else if (hr == S_FALSE) {
if (++retries > max_retries) {
MP_VERBOSE(mapper, "Failed to flush frame after %lld ms\n",
(long long)MP_TIME_MS_TO_NS(wait_ns * max_retries));
break;
}
mp_sleep_ns(wait_ns);
} 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);
return 0;
}
const struct ra_hwdec_driver ra_hwdec_dxva2egl = {
.name = "dxva2-egl",
.priv_size = sizeof(struct priv_owner),
.imgfmts = {IMGFMT_DXVA2, 0},
.device_type = AV_HWDEVICE_TYPE_DXVA2,
.init = init,
.uninit = uninit,
.mapper = &(const struct ra_hwdec_mapper_driver){
.priv_size = sizeof(struct priv),
.init = mapper_init,
.uninit = mapper_uninit,
.map = mapper_map,
},
};