/* * 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 "osdep/windows_utils.h" #include "video/out/w32_common.h" #include "context.h" #include "utils.h" // For WGL_ACCESS_WRITE_DISCARD_NV, etc. #include 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 (!strstr(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, .flipped = true, .external_swapchain = &empty_swapchain_fns, }; 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, };