diff options
Diffstat (limited to 'video/out/opengl/context_dxinterop.c')
-rw-r--r-- | video/out/opengl/context_dxinterop.c | 605 |
1 files changed, 605 insertions, 0 deletions
diff --git a/video/out/opengl/context_dxinterop.c b/video/out/opengl/context_dxinterop.c new file mode 100644 index 0000000..cda696f --- /dev/null +++ b/video/out/opengl/context_dxinterop.c @@ -0,0 +1,605 @@ +/* + * 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 <windows.h> +#include <versionhelpers.h> +#include <d3d9.h> +#include <dwmapi.h> +#include "osdep/windows_utils.h" +#include "video/out/w32_common.h" +#include "context.h" +#include "utils.h" + +// For WGL_ACCESS_WRITE_DISCARD_NV, etc. +#include <GL/wglext.h> + +EXTERN_C IMAGE_DOS_HEADER __ImageBase; +#define HINST_THISCOMPONENT ((HINSTANCE)&__ImageBase) + +// mingw-w64 header typo? +#ifndef IDirect3DSwapChain9Ex_GetBackBuffer +#define IDirect3DSwapChain9Ex_GetBackBuffer IDirect3DSwapChain9EX_GetBackBuffer +#endif + +struct priv { + GL gl; + + HMODULE d3d9_dll; + HRESULT (WINAPI *Direct3DCreate9Ex)(UINT SDKVersion, IDirect3D9Ex **ppD3D); + + // Direct3D9 device and resources + IDirect3D9Ex *d3d9ex; + IDirect3DDevice9Ex *device; + HANDLE device_h; + IDirect3DSwapChain9Ex *swapchain; + IDirect3DSurface9 *backbuffer; + IDirect3DSurface9 *rtarget; + HANDLE rtarget_h; + + // OpenGL offscreen context + HWND os_wnd; + HDC os_dc; + HGLRC os_ctx; + + // OpenGL resources + GLuint texture; + GLuint main_fb; + + // Did we lose the device? + bool lost_device; + + // Requested and current parameters + int requested_swapinterval; + int width, height, swapinterval; +}; + +static __thread struct ra_ctx *current_ctx; + +static void pump_message_loop(void) +{ + // We have a hidden window on this thread (for the OpenGL context,) so pump + // its message loop at regular intervals to be safe + MSG message; + while (PeekMessageW(&message, NULL, 0, 0, PM_REMOVE)) + DispatchMessageW(&message); +} + +static void *w32gpa(const GLubyte *procName) +{ + HMODULE oglmod; + void *res = wglGetProcAddress(procName); + if (res) + return res; + oglmod = GetModuleHandleW(L"opengl32.dll"); + return GetProcAddress(oglmod, procName); +} + +static int os_ctx_create(struct ra_ctx *ctx) +{ + static const wchar_t os_wnd_class[] = L"mpv offscreen gl"; + struct priv *p = ctx->priv; + GL *gl = &p->gl; + HGLRC legacy_context = NULL; + + RegisterClassExW(&(WNDCLASSEXW) { + .cbSize = sizeof(WNDCLASSEXW), + .style = CS_OWNDC, + .lpfnWndProc = DefWindowProc, + .hInstance = HINST_THISCOMPONENT, + .lpszClassName = os_wnd_class, + }); + + // Create a hidden window for an offscreen OpenGL context. It might also be + // possible to use the VO window, but MSDN recommends against drawing to + // the same window with flip mode present and other APIs, so play it safe. + p->os_wnd = CreateWindowExW(0, os_wnd_class, os_wnd_class, 0, 0, 0, 200, + 200, NULL, NULL, HINST_THISCOMPONENT, NULL); + p->os_dc = GetDC(p->os_wnd); + if (!p->os_dc) { + MP_FATAL(ctx->vo, "Couldn't create window for offscreen rendering\n"); + goto fail; + } + + // Choose a pixel format. It probably doesn't matter what this is because + // the primary framebuffer will not be used. + PIXELFORMATDESCRIPTOR pfd = { + .nSize = sizeof pfd, + .nVersion = 1, + .dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, + .iPixelType = PFD_TYPE_RGBA, + .cColorBits = 24, + .iLayerType = PFD_MAIN_PLANE, + }; + int pf = ChoosePixelFormat(p->os_dc, &pfd); + if (!pf) { + MP_FATAL(ctx->vo, + "Couldn't choose pixelformat for offscreen rendering: %s\n", + mp_LastError_to_str()); + goto fail; + } + SetPixelFormat(p->os_dc, pf, &pfd); + + legacy_context = wglCreateContext(p->os_dc); + if (!legacy_context || !wglMakeCurrent(p->os_dc, legacy_context)) { + MP_FATAL(ctx->vo, "Couldn't create OpenGL context for offscreen rendering: %s\n", + mp_LastError_to_str()); + goto fail; + } + + const char *(GLAPIENTRY *wglGetExtensionsStringARB)(HDC hdc) + = w32gpa((const GLubyte*)"wglGetExtensionsStringARB"); + if (!wglGetExtensionsStringARB) { + MP_FATAL(ctx->vo, "The OpenGL driver does not support OpenGL 3.x\n"); + goto fail; + } + + const char *wgl_exts = wglGetExtensionsStringARB(p->os_dc); + if (!gl_check_extension(wgl_exts, "WGL_ARB_create_context")) { + MP_FATAL(ctx->vo, "The OpenGL driver does not support OpenGL 3.x\n"); + goto fail; + } + + HGLRC (GLAPIENTRY *wglCreateContextAttribsARB)(HDC hDC, HGLRC hShareContext, + const int *attribList) + = w32gpa((const GLubyte*)"wglCreateContextAttribsARB"); + if (!wglCreateContextAttribsARB) { + MP_FATAL(ctx->vo, "The OpenGL driver does not support OpenGL 3.x\n"); + goto fail; + } + + int attribs[] = { + WGL_CONTEXT_MAJOR_VERSION_ARB, 3, + WGL_CONTEXT_MINOR_VERSION_ARB, 0, + WGL_CONTEXT_FLAGS_ARB, 0, + WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, + 0 + }; + + p->os_ctx = wglCreateContextAttribsARB(p->os_dc, 0, attribs); + if (!p->os_ctx) { + // NVidia, instead of ignoring WGL_CONTEXT_FLAGS_ARB, will error out if + // it's present on pre-3.2 contexts. + // Remove it from attribs and retry the context creation. + attribs[6] = attribs[7] = 0; + p->os_ctx = wglCreateContextAttribsARB(p->os_dc, 0, attribs); + } + if (!p->os_ctx) { + MP_FATAL(ctx->vo, + "Couldn't create OpenGL 3.x context for offscreen rendering: %s\n", + mp_LastError_to_str()); + goto fail; + } + + wglMakeCurrent(p->os_dc, NULL); + wglDeleteContext(legacy_context); + legacy_context = NULL; + + if (!wglMakeCurrent(p->os_dc, p->os_ctx)) { + MP_FATAL(ctx->vo, + "Couldn't activate OpenGL 3.x context for offscreen rendering: %s\n", + mp_LastError_to_str()); + goto fail; + } + + mpgl_load_functions(gl, w32gpa, wgl_exts, ctx->vo->log); + if (!(gl->mpgl_caps & MPGL_CAP_DXINTEROP)) { + MP_FATAL(ctx->vo, "WGL_NV_DX_interop is not supported\n"); + goto fail; + } + + return 0; +fail: + if (legacy_context) { + wglMakeCurrent(p->os_dc, NULL); + wglDeleteContext(legacy_context); + } + return -1; +} + +static void os_ctx_destroy(struct ra_ctx *ctx) +{ + struct priv *p = ctx->priv; + + if (p->os_ctx) { + wglMakeCurrent(p->os_dc, NULL); + wglDeleteContext(p->os_ctx); + } + if (p->os_dc) + ReleaseDC(p->os_wnd, p->os_dc); + if (p->os_wnd) + DestroyWindow(p->os_wnd); +} + +static int d3d_size_dependent_create(struct ra_ctx *ctx) +{ + struct priv *p = ctx->priv; + GL *gl = &p->gl; + HRESULT hr; + + IDirect3DSwapChain9 *sw9; + hr = IDirect3DDevice9Ex_GetSwapChain(p->device, 0, &sw9); + if (FAILED(hr)) { + MP_ERR(ctx->vo, "Couldn't get swap chain: %s\n", mp_HRESULT_to_str(hr)); + return -1; + } + + hr = IDirect3DSwapChain9_QueryInterface(sw9, &IID_IDirect3DSwapChain9Ex, + (void**)&p->swapchain); + if (FAILED(hr)) { + SAFE_RELEASE(sw9); + MP_ERR(ctx->vo, "Obtained swap chain is not IDirect3DSwapChain9Ex: %s\n", + mp_HRESULT_to_str(hr)); + return -1; + } + SAFE_RELEASE(sw9); + + hr = IDirect3DSwapChain9Ex_GetBackBuffer(p->swapchain, 0, + D3DBACKBUFFER_TYPE_MONO, &p->backbuffer); + if (FAILED(hr)) { + MP_ERR(ctx->vo, "Couldn't get backbuffer: %s\n", mp_HRESULT_to_str(hr)); + return -1; + } + + // Get the format of the backbuffer + D3DSURFACE_DESC bb_desc = { 0 }; + IDirect3DSurface9_GetDesc(p->backbuffer, &bb_desc); + + MP_VERBOSE(ctx->vo, "DX_interop backbuffer size: %ux%u\n", + (unsigned)bb_desc.Width, (unsigned)bb_desc.Height); + MP_VERBOSE(ctx->vo, "DX_interop backbuffer format: %u\n", + (unsigned)bb_desc.Format); + + // Create a rendertarget with the same format as the backbuffer for + // rendering from OpenGL + HANDLE share_handle = NULL; + hr = IDirect3DDevice9Ex_CreateRenderTarget(p->device, bb_desc.Width, + bb_desc.Height, bb_desc.Format, D3DMULTISAMPLE_NONE, 0, FALSE, + &p->rtarget, &share_handle); + if (FAILED(hr)) { + MP_ERR(ctx->vo, "Couldn't create rendertarget: %s\n", mp_HRESULT_to_str(hr)); + return -1; + } + + // Register the share handle with WGL_NV_DX_interop. Nvidia does not + // require the use of share handles, but Intel does. + if (share_handle) + gl->DXSetResourceShareHandleNV(p->rtarget, share_handle); + + // Create the OpenGL-side texture + gl->GenTextures(1, &p->texture); + + // Now share the rendertarget with OpenGL as a texture + p->rtarget_h = gl->DXRegisterObjectNV(p->device_h, p->rtarget, p->texture, + GL_TEXTURE_2D, WGL_ACCESS_WRITE_DISCARD_NV); + if (!p->rtarget_h) { + MP_ERR(ctx->vo, "Couldn't share rendertarget with OpenGL: %s\n", + mp_LastError_to_str()); + return -1; + } + + // Lock the rendertarget for use from OpenGL. This will only be unlocked in + // swap_buffers() when it is blitted to the real Direct3D backbuffer. + if (!gl->DXLockObjectsNV(p->device_h, 1, &p->rtarget_h)) { + MP_ERR(ctx->vo, "Couldn't lock rendertarget: %s\n", + mp_LastError_to_str()); + return -1; + } + + gl->BindFramebuffer(GL_FRAMEBUFFER, p->main_fb); + gl->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, p->texture, 0); + gl->BindFramebuffer(GL_FRAMEBUFFER, 0); + + return 0; +} + +static void d3d_size_dependent_destroy(struct ra_ctx *ctx) +{ + struct priv *p = ctx->priv; + GL *gl = &p->gl; + + if (p->rtarget_h) { + gl->DXUnlockObjectsNV(p->device_h, 1, &p->rtarget_h); + gl->DXUnregisterObjectNV(p->device_h, p->rtarget_h); + } + p->rtarget_h = 0; + if (p->texture) + gl->DeleteTextures(1, &p->texture); + p->texture = 0; + + SAFE_RELEASE(p->rtarget); + SAFE_RELEASE(p->backbuffer); + SAFE_RELEASE(p->swapchain); +} + +static void fill_presentparams(struct ra_ctx *ctx, + D3DPRESENT_PARAMETERS *pparams) +{ + struct priv *p = ctx->priv; + + // Present intervals other than IMMEDIATE and ONE don't seem to work. It's + // possible that they're not compatible with FLIPEX. + UINT presentation_interval; + switch (p->requested_swapinterval) { + case 0: presentation_interval = D3DPRESENT_INTERVAL_IMMEDIATE; break; + case 1: presentation_interval = D3DPRESENT_INTERVAL_ONE; break; + default: presentation_interval = D3DPRESENT_INTERVAL_ONE; break; + } + + *pparams = (D3DPRESENT_PARAMETERS) { + .Windowed = TRUE, + .BackBufferWidth = ctx->vo->dwidth ? ctx->vo->dwidth : 1, + .BackBufferHeight = ctx->vo->dheight ? ctx->vo->dheight : 1, + // Add one frame for the backbuffer and one frame of "slack" to reduce + // contention with the window manager when acquiring the backbuffer + .BackBufferCount = ctx->vo->opts->swapchain_depth + 2, + .SwapEffect = IsWindows7OrGreater() ? D3DSWAPEFFECT_FLIPEX : D3DSWAPEFFECT_FLIP, + // Automatically get the backbuffer format from the display format + .BackBufferFormat = D3DFMT_UNKNOWN, + .PresentationInterval = presentation_interval, + .hDeviceWindow = vo_w32_hwnd(ctx->vo), + }; +} + +static int d3d_create(struct ra_ctx *ctx) +{ + struct priv *p = ctx->priv; + GL *gl = &p->gl; + HRESULT hr; + + p->d3d9_dll = LoadLibraryW(L"d3d9.dll"); + if (!p->d3d9_dll) { + MP_FATAL(ctx->vo, "Failed to load \"d3d9.dll\": %s\n", + mp_LastError_to_str()); + return -1; + } + + // WGL_NV_dx_interop requires Direct3D 9Ex on WDDM systems. Direct3D 9Ex + // also enables flip mode present for efficient rendering with the DWM. + p->Direct3DCreate9Ex = (void*)GetProcAddress(p->d3d9_dll, + "Direct3DCreate9Ex"); + if (!p->Direct3DCreate9Ex) { + MP_FATAL(ctx->vo, "Direct3D 9Ex not supported\n"); + return -1; + } + + hr = p->Direct3DCreate9Ex(D3D_SDK_VERSION, &p->d3d9ex); + if (FAILED(hr)) { + MP_FATAL(ctx->vo, "Couldn't create Direct3D9Ex: %s\n", + mp_HRESULT_to_str(hr)); + return -1; + } + + D3DPRESENT_PARAMETERS pparams; + fill_presentparams(ctx, &pparams); + + hr = IDirect3D9Ex_CreateDeviceEx(p->d3d9ex, D3DADAPTER_DEFAULT, + D3DDEVTYPE_HAL, vo_w32_hwnd(ctx->vo), + D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_PUREDEVICE | + D3DCREATE_FPU_PRESERVE | D3DCREATE_MULTITHREADED | + D3DCREATE_NOWINDOWCHANGES, + &pparams, NULL, &p->device); + if (FAILED(hr)) { + MP_FATAL(ctx->vo, "Couldn't create device: %s\n", mp_HRESULT_to_str(hr)); + return -1; + } + + IDirect3DDevice9Ex_SetMaximumFrameLatency(p->device, ctx->vo->opts->swapchain_depth); + + // Register the Direct3D device with WGL_NV_dx_interop + p->device_h = gl->DXOpenDeviceNV(p->device); + if (!p->device_h) { + MP_FATAL(ctx->vo, "Couldn't open Direct3D device from OpenGL: %s\n", + mp_LastError_to_str()); + return -1; + } + + return 0; +} + +static void d3d_destroy(struct ra_ctx *ctx) +{ + struct priv *p = ctx->priv; + GL *gl = &p->gl; + + if (p->device_h) + gl->DXCloseDeviceNV(p->device_h); + SAFE_RELEASE(p->device); + SAFE_RELEASE(p->d3d9ex); + if (p->d3d9_dll) + FreeLibrary(p->d3d9_dll); +} + +static void dxgl_uninit(struct ra_ctx *ctx) +{ + ra_gl_ctx_uninit(ctx); + d3d_size_dependent_destroy(ctx); + d3d_destroy(ctx); + os_ctx_destroy(ctx); + vo_w32_uninit(ctx->vo); + DwmEnableMMCSS(FALSE); + pump_message_loop(); +} + +static void dxgl_reset(struct ra_ctx *ctx) +{ + struct priv *p = ctx->priv; + HRESULT hr; + + // Check if the device actually needs to be reset + if (ctx->vo->dwidth == p->width && ctx->vo->dheight == p->height && + p->requested_swapinterval == p->swapinterval && !p->lost_device) + return; + + d3d_size_dependent_destroy(ctx); + + D3DPRESENT_PARAMETERS pparams; + fill_presentparams(ctx, &pparams); + + hr = IDirect3DDevice9Ex_ResetEx(p->device, &pparams, NULL); + if (FAILED(hr)) { + p->lost_device = true; + MP_ERR(ctx->vo, "Couldn't reset device: %s\n", mp_HRESULT_to_str(hr)); + return; + } + + if (d3d_size_dependent_create(ctx) < 0) { + p->lost_device = true; + MP_ERR(ctx->vo, "Couldn't recreate Direct3D objects after reset\n"); + return; + } + + MP_VERBOSE(ctx->vo, "Direct3D device reset\n"); + p->width = ctx->vo->dwidth; + p->height = ctx->vo->dheight; + p->swapinterval = p->requested_swapinterval; + p->lost_device = false; +} + +static int GLAPIENTRY dxgl_swap_interval(int interval) +{ + if (!current_ctx) + return 0; + struct priv *p = current_ctx->priv; + + p->requested_swapinterval = interval; + dxgl_reset(current_ctx); + return 1; +} + +static void dxgl_swap_buffers(struct ra_ctx *ctx) +{ + struct priv *p = ctx->priv; + GL *gl = &p->gl; + HRESULT hr; + + pump_message_loop(); + + // If the device is still lost, try to reset it again + if (p->lost_device) + dxgl_reset(ctx); + if (p->lost_device) + return; + + if (!gl->DXUnlockObjectsNV(p->device_h, 1, &p->rtarget_h)) { + MP_ERR(ctx->vo, "Couldn't unlock rendertarget for present: %s\n", + mp_LastError_to_str()); + return; + } + + // Blit the OpenGL rendertarget to the backbuffer + hr = IDirect3DDevice9Ex_StretchRect(p->device, p->rtarget, NULL, + p->backbuffer, NULL, D3DTEXF_NONE); + if (FAILED(hr)) { + MP_ERR(ctx->vo, "Couldn't stretchrect for present: %s\n", + mp_HRESULT_to_str(hr)); + return; + } + + hr = IDirect3DDevice9Ex_PresentEx(p->device, NULL, NULL, NULL, NULL, 0); + switch (hr) { + case D3DERR_DEVICELOST: + case D3DERR_DEVICEHUNG: + MP_VERBOSE(ctx->vo, "Direct3D device lost! Resetting.\n"); + p->lost_device = true; + dxgl_reset(ctx); + return; + default: + if (FAILED(hr)) + MP_ERR(ctx->vo, "Failed to present: %s\n", mp_HRESULT_to_str(hr)); + } + + if (!gl->DXLockObjectsNV(p->device_h, 1, &p->rtarget_h)) { + MP_ERR(ctx->vo, "Couldn't lock rendertarget after present: %s\n", + mp_LastError_to_str()); + } +} + +static bool dxgl_init(struct ra_ctx *ctx) +{ + struct priv *p = ctx->priv = talloc_zero(ctx, struct priv); + GL *gl = &p->gl; + + p->requested_swapinterval = 1; + + if (!vo_w32_init(ctx->vo)) + goto fail; + if (os_ctx_create(ctx) < 0) + goto fail; + + // Create the shared framebuffer + gl->GenFramebuffers(1, &p->main_fb); + + current_ctx = ctx; + gl->SwapInterval = dxgl_swap_interval; + + if (d3d_create(ctx) < 0) + goto fail; + if (d3d_size_dependent_create(ctx) < 0) + goto fail; + + static const struct ra_swapchain_fns empty_swapchain_fns = {0}; + struct ra_gl_ctx_params params = { + .swap_buffers = dxgl_swap_buffers, + .external_swapchain = &empty_swapchain_fns, + }; + + gl->flipped = true; + if (!ra_gl_ctx_init(ctx, gl, params)) + goto fail; + + ra_add_native_resource(ctx->ra, "IDirect3DDevice9Ex", p->device); + ra_add_native_resource(ctx->ra, "dxinterop_device_HANDLE", p->device_h); + + DwmEnableMMCSS(TRUE); + return true; +fail: + dxgl_uninit(ctx); + return false; +} + +static void resize(struct ra_ctx *ctx) +{ + struct priv *p = ctx->priv; + dxgl_reset(ctx); + ra_gl_ctx_resize(ctx->swapchain, ctx->vo->dwidth, ctx->vo->dheight, p->main_fb); +} + +static bool dxgl_reconfig(struct ra_ctx *ctx) +{ + vo_w32_config(ctx->vo); + resize(ctx); + return true; +} + +static int dxgl_control(struct ra_ctx *ctx, int *events, int request, + void *arg) +{ + int ret = vo_w32_control(ctx->vo, events, request, arg); + if (*events & VO_EVENT_RESIZE) + resize(ctx); + return ret; +} + +const struct ra_ctx_fns ra_ctx_dxgl = { + .type = "opengl", + .name = "dxinterop", + .init = dxgl_init, + .reconfig = dxgl_reconfig, + .control = dxgl_control, + .uninit = dxgl_uninit, +}; |