From e0250b9604b24f32d53b409d8c48d16faa2caebc Mon Sep 17 00:00:00 2001 From: James Ross-Gowan Date: Sat, 4 Feb 2017 19:16:02 +1100 Subject: vo_opengl: angle: rewrite with custom swap chain This replaces the old backend that exclusively used EGL windowing with one that can also use ANGLE's ability to render to directly to a texture. The advantage of this is that it allows mpv to create the swap chain itself and this allows mpv to use a flip-mode swap chain on a HWND (which avoids problems with DirectComposition) and to use a longer swap chain that has six backbuffers by default (which reportedly fixes problems with rendering 24fps video on 24Hz monitors.) Also, "screenshot window" should now work on DXGI 1.2 and up (Windows 8 and up.) --- DOCS/man/options.rst | 68 ++- osdep/windows_utils.c | 8 + video/out/opengl/angle_dynamic.h | 7 +- video/out/opengl/context.c | 1 - video/out/opengl/context_angle.c | 871 ++++++++++++++++++++++++++++----------- video/out/opengl/egl_helpers.c | 6 + wscript | 3 +- 7 files changed, 724 insertions(+), 240 deletions(-) diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst index 3c205b9da5..13fd4351ca 100644 --- a/DOCS/man/options.rst +++ b/DOCS/man/options.rst @@ -4331,16 +4331,70 @@ The following video options are currently all specific to ``--vo=opengl`` and Windows only. -``--opengl-dcomposition=`` - Allows DirectComposition when using the ANGLE backend (default: yes). - DirectComposition implies flip-model presentation, which can improve - rendering efficiency on Windows 8+ by avoiding a copy of the video frame. - mpv uses it by default where possible, but it can cause poor behaviour with - some drivers, such as a black screen or graphical corruption when leaving - full-screen mode. Use "no" to disable it. +``--angle-d3d11-feature-level=<11_0|10_1|10_0|9_3>`` + Selects a specific feature level when using the ANGLE backend with D3D11. + By default, the highest available feature level is used. This option can be + used to select a lower feature level, which is mainly useful for debugging. + Note that OpenGL ES 3.0 is only supported at feature level 10_1 or higher. + Most extended OpenGL features will not work at lower feature levels + (similar to ``--opengl-dumb-mode``). Windows with ANGLE only. +``--angle-d3d11-warp=`` + Use WARP (Windows Advanced Rasterization Platform) when using the ANGLE + backend with D3D11 (default: auto). This is a high performance software + renderer. By default, it is used when the Direct3D hardware does not + support Direct3D 11 feature level 9_3. While the extended OpenGL features + will work with WARP, they can be very slow. + + Windows with ANGLE only. + +``--angle-egl-windowing=`` + Use ANGLE's built in EGL windowing functions to create a swap chain + (default: auto). If this is set to ``no`` and the D3D11 renderer is in use, + ANGLE's built in swap chain will not be used and a custom swap chain that + is optimized for video rendering will be created instead. If set to + ``auto``, a custom swap chain will be used for D3D11 and the built in swap + chain will be used for D3D9. This option is mainly for debugging purposes, + in case the custom swap chain has poor performance or does not work. + + If set to ``yes``, the ``--angle-max-frame-latency`` and + ``--angle-swapchain-length`` options will have no effect. + + Windows with ANGLE only. + +``--angle-max-frame-latency=<1-16>`` + Sets the maximum number of frames that the system is allowed to queue for + rendering with the ANGLE backend (default: 3). Lower values should make + VSync timing more accurate, but a value of ``1`` requires powerful + hardware, since the CPU will not be able to "render ahead" of the GPU. + + Windows with ANGLE only. + +``--angle-renderer=`` + Forces a specific renderer when using the ANGLE backend (default: auto). In + auto mode this will pick D3D11 for systems that support Direct3D 11 feature + level 9_3 or higher, and D3D9 otherwise. This option is mainly for + debugging purposes. Normally there is no reason to force a specific + renderer, though ``--angle-renderer=d3d9`` may give slightly better + performance on old hardware. Note that the D3D9 renderer only supports + OpenGL ES 2.0, so most extended OpenGL features will not work if this + renderer is selected (similar to ``--opengl-dumb-mode``). + + Windows with ANGLE only. + +``--angle-swapchain-length=<2-16>`` + Sets the number of buffers in the D3D11 presentation queue when using the + ANGLE backend (default: 6). At least 2 are required, since one is the back + buffer that mpv renders to and the other is the front buffer that is + presented by the DWM. Additional buffers can improve performance, because + for example, mpv will not have to wait on the DWM to release the front + buffer before rendering a new frame to it. For this reason, Microsoft + recommends at least 4. + + Windows 8+ with ANGLE only. + ``--opengl-sw`` Continue even if a software renderer is detected. diff --git a/osdep/windows_utils.c b/osdep/windows_utils.c index a1ea32191a..a60eba3d26 100644 --- a/osdep/windows_utils.c +++ b/osdep/windows_utils.c @@ -22,6 +22,7 @@ #include #include #include +#include #include "windows_utils.h" @@ -118,6 +119,13 @@ static char *hresult_to_str(const HRESULT hr) E(D3DERR_CANNOTPROTECTCONTENT) E(D3DERR_UNSUPPORTEDCRYPTO) E(D3DERR_PRESENT_STATISTICS_DISJOINT) + E(DXGI_ERROR_DEVICE_HUNG) + E(DXGI_ERROR_DEVICE_REMOVED) + E(DXGI_ERROR_DEVICE_RESET) + E(DXGI_ERROR_DRIVER_INTERNAL_ERROR) + E(DXGI_ERROR_INVALID_CALL) + E(DXGI_ERROR_WAS_STILL_DRAWING) + E(DXGI_STATUS_OCCLUDED) default: return ""; } diff --git a/video/out/opengl/angle_dynamic.h b/video/out/opengl/angle_dynamic.h index 87ad85c268..12a0c692eb 100644 --- a/video/out/opengl/angle_dynamic.h +++ b/video/out/opengl/angle_dynamic.h @@ -45,9 +45,12 @@ (EGLDisplay, EGLint)) \ FN(eglSwapBuffers, EGLBoolean (*EGLAPIENTRY PFN_eglSwapBuffers) \ (EGLDisplay, EGLSurface)) \ + FN(eglSwapInterval, EGLBoolean (*EGLAPIENTRY PFN_eglSwapInterval) \ + (EGLDisplay, EGLint)) \ FN(eglReleaseTexImage, EGLBoolean (*EGLAPIENTRY PFN_eglReleaseTexImage) \ (EGLDisplay, EGLSurface, EGLint)) \ - FN(eglTerminate, EGLBoolean (*EGLAPIENTRY PFN_eglTerminate)(EGLDisplay)) + FN(eglTerminate, EGLBoolean (*EGLAPIENTRY PFN_eglTerminate)(EGLDisplay)) \ + FN(eglWaitClient, EGLBoolean (*EGLAPIENTRY PFN_eglWaitClient)(void)) #define ANGLE_EXT_DECL(NAME, VAR) \ extern VAR; @@ -76,7 +79,9 @@ bool angle_load(void); #define eglQueryString PFN_eglQueryString #define eglReleaseTexImage PFN_eglReleaseTexImage #define eglSwapBuffers PFN_eglSwapBuffers +#define eglSwapInterval PFN_eglSwapInterval #define eglTerminate PFN_eglTerminate +#define eglWaitClient PFN_eglWaitClient #endif #endif diff --git a/video/out/opengl/context.c b/video/out/opengl/context.c index 478421385c..f7fce4fae5 100644 --- a/video/out/opengl/context.c +++ b/video/out/opengl/context.c @@ -57,7 +57,6 @@ static const struct mpgl_driver *const backends[] = { #endif #if HAVE_EGL_ANGLE &mpgl_driver_angle, - &mpgl_driver_angle_es2, #endif #if HAVE_GL_WIN32 &mpgl_driver_w32, diff --git a/video/out/opengl/context_angle.c b/video/out/opengl/context_angle.c index 5af18e9928..9ea7fb43bb 100644 --- a/video/out/opengl/context_angle.c +++ b/video/out/opengl/context_angle.c @@ -19,15 +19,21 @@ #include #include #include -#include +#include +#include #include "angle_dynamic.h" +#include "egl_helpers.h" #include "common/common.h" #include "options/m_config.h" #include "video/out/w32_common.h" +#include "osdep/windows_utils.h" #include "context.h" +#ifndef EGL_D3D_TEXTURE_ANGLE +#define EGL_D3D_TEXTURE_ANGLE 0x33A3 +#endif #ifndef EGL_OPTIMAL_SURFACE_ORIENTATION_ANGLE #define EGL_OPTIMAL_SURFACE_ORIENTATION_ANGLE 0x33A7 #define EGL_SURFACE_ORIENTATION_ANGLE 0x33A8 @@ -37,96 +43,189 @@ // Windows 8 enum value, not present in mingw-w64 headers #define DXGI_ADAPTER_FLAG_SOFTWARE (2) +enum { + RENDERER_AUTO, + RENDERER_D3D9, + RENDERER_D3D11, +}; + struct angle_opts { - int allow_direct_composition; + int renderer; + int d3d11_warp; + int d3d11_feature_level; + int egl_windowing; + int swapchain_length; // Currently only works with DXGI 1.2+ + int max_frame_latency; }; #define OPT_BASE_STRUCT struct angle_opts const struct m_sub_options angle_conf = { .opts = (const struct m_option[]) { - OPT_FLAG("opengl-dcomposition", allow_direct_composition, 0), + OPT_CHOICE("angle-renderer", renderer, 0, + ({"auto", RENDERER_AUTO}, + {"d3d9", RENDERER_D3D9}, + {"d3d11", RENDERER_D3D11})), + OPT_CHOICE("angle-d3d11-warp", d3d11_warp, 0, + ({"auto", -1}, + {"no", 0}, + {"yes", 1})), + OPT_CHOICE("angle-d3d11-feature-level", d3d11_feature_level, 0, + ({"11_0", D3D_FEATURE_LEVEL_11_0}, + {"10_1", D3D_FEATURE_LEVEL_10_1}, + {"10_0", D3D_FEATURE_LEVEL_10_0}, + {"9_3", D3D_FEATURE_LEVEL_9_3})), + OPT_CHOICE("angle-egl-windowing", egl_windowing, 0, + ({"auto", -1}, + {"no", 0}, + {"yes", 1})), + OPT_INTRANGE("angle-swapchain-length", swapchain_length, 0, 2, 16), + OPT_INTRANGE("angle-max-frame-latency", max_frame_latency, 0, 1, 16), {0} }, .defaults = &(const struct angle_opts) { - .allow_direct_composition = 1, + .renderer = RENDERER_AUTO, + .d3d11_warp = -1, + .d3d11_feature_level = D3D_FEATURE_LEVEL_11_0, + .egl_windowing = -1, + .swapchain_length = 6, + .max_frame_latency = 3, }, .size = sizeof(struct angle_opts), }; struct priv { + IDXGIFactory1 *dxgi_factory; + IDXGIFactory2 *dxgi_factory2; + IDXGIAdapter1 *dxgi_adapter; + IDXGIDevice1 *dxgi_device; + IDXGISwapChain *dxgi_swapchain; + IDXGISwapChain1 *dxgi_swapchain1; + + ID3D11Device *d3d11_device; + ID3D11DeviceContext *d3d11_context; + ID3D11Texture2D *d3d11_backbuffer; + + EGLConfig egl_config; EGLDisplay egl_display; + EGLDeviceEXT egl_device; EGLContext egl_context; - EGLSurface egl_surface; - bool use_es2; + EGLSurface egl_window; // For the EGL windowing surface only + EGLSurface egl_backbuffer; // For the DXGI swap chain based surface + + int sc_width, sc_height; // Swap chain width and height + int swapinterval; + bool sw_adapter_msg_shown; - PFNEGLPOSTSUBBUFFERNVPROC eglPostSubBufferNV; + struct angle_opts *opts; }; -static void angle_uninit(MPGLContext *ctx) +static __thread struct MPGLContext *current_ctx; + +static void update_sizes(MPGLContext *ctx) { struct priv *p = ctx->priv; + p->sc_width = ctx->vo->dwidth ? ctx->vo->dwidth : 1; + p->sc_height = ctx->vo->dheight ? ctx->vo->dheight : 1; +} - if (p->egl_context) { +static void d3d11_backbuffer_release(MPGLContext *ctx) +{ + struct priv *p = ctx->priv; + + if (p->egl_backbuffer) { eglMakeCurrent(p->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - eglDestroyContext(p->egl_display, p->egl_context); + eglDestroySurface(p->egl_display, p->egl_backbuffer); } - p->egl_context = EGL_NO_CONTEXT; - if (p->egl_display) - eglTerminate(p->egl_display); - vo_w32_uninit(ctx->vo); + p->egl_backbuffer = EGL_NO_SURFACE; + + SAFE_RELEASE(p->d3d11_backbuffer); } -static EGLConfig select_fb_config_egl(struct MPGLContext *ctx) +static bool d3d11_backbuffer_get(MPGLContext *ctx) { struct priv *p = ctx->priv; + struct vo *vo = ctx->vo; + HRESULT hr; + + hr = IDXGISwapChain_GetBuffer(p->dxgi_swapchain, 0, &IID_ID3D11Texture2D, + (void**)&p->d3d11_backbuffer); + if (FAILED(hr)) { + MP_FATAL(vo, "Couldn't get swap chain back buffer\n"); + return false; + } - EGLint attributes[] = { - EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_BLUE_SIZE, 8, - EGL_DEPTH_SIZE, 0, - EGL_NONE + EGLint pbuffer_attributes[] = { + EGL_TEXTURE_FORMAT, EGL_TEXTURE_RGBA, + EGL_TEXTURE_TARGET, EGL_TEXTURE_2D, + EGL_NONE, }; + p->egl_backbuffer = eglCreatePbufferFromClientBuffer(p->egl_display, + EGL_D3D_TEXTURE_ANGLE, p->d3d11_backbuffer, p->egl_config, + pbuffer_attributes); + if (!p->egl_backbuffer) { + MP_FATAL(vo, "Couldn't create EGL pbuffer\n"); + return false; + } + + eglMakeCurrent(p->egl_display, p->egl_backbuffer, p->egl_backbuffer, + p->egl_context); + return true; +} - EGLint config_count; - EGLConfig config; +static void d3d11_backbuffer_resize(MPGLContext *ctx) +{ + struct priv *p = ctx->priv; + struct vo *vo = ctx->vo; + HRESULT hr; - eglChooseConfig(p->egl_display, attributes, &config, 1, &config_count); + int old_sc_width = p->sc_width; + int old_sc_height = p->sc_height; - if (!config_count) { - MP_FATAL(ctx->vo, "Could find EGL configuration!\n"); - return NULL; - } + update_sizes(ctx); + // Avoid unnecessary resizing + if (old_sc_width == p->sc_width && old_sc_height == p->sc_height) + return; - return config; + // All references to backbuffers must be released before ResizeBuffers + // (including references held by ANGLE) + d3d11_backbuffer_release(ctx); + + // The DirectX runtime may report errors related to the device like + // DXGI_ERROR_DEVICE_REMOVED at this point + hr = IDXGISwapChain_ResizeBuffers(p->dxgi_swapchain, 0, p->sc_width, + p->sc_height, DXGI_FORMAT_UNKNOWN, 0); + if (FAILED(hr)) + MP_FATAL(vo, "Couldn't resize swapchain: %s\n", mp_HRESULT_to_str(hr)); + + if (!d3d11_backbuffer_get(ctx)) + MP_FATAL(vo, "Couldn't get back buffer after resize\n"); } -static bool create_context_egl(MPGLContext *ctx, EGLConfig config, int version) +static void d3d11_device_destroy(MPGLContext *ctx) { struct priv *p = ctx->priv; - EGLint context_attributes[] = { - EGL_CONTEXT_CLIENT_VERSION, version, - EGL_NONE - }; + PFNEGLRELEASEDEVICEANGLEPROC eglReleaseDeviceANGLE = + (PFNEGLRELEASEDEVICEANGLEPROC)eglGetProcAddress("eglReleaseDeviceANGLE"); - p->egl_context = eglCreateContext(p->egl_display, config, - EGL_NO_CONTEXT, context_attributes); - - if (p->egl_context == EGL_NO_CONTEXT) { - MP_VERBOSE(ctx->vo, "Could not create EGL GLES %d context!\n", version); - return false; - } + if (p->egl_display) + eglTerminate(p->egl_display); + p->egl_display = EGL_NO_DISPLAY; - eglMakeCurrent(p->egl_display, p->egl_surface, p->egl_surface, - p->egl_context); + if (p->egl_device && eglReleaseDeviceANGLE) + eglReleaseDeviceANGLE(p->egl_device); + p->egl_device = 0; - return true; + SAFE_RELEASE(p->d3d11_device); + SAFE_RELEASE(p->dxgi_device); + SAFE_RELEASE(p->dxgi_adapter); + SAFE_RELEASE(p->dxgi_factory); + SAFE_RELEASE(p->dxgi_factory2); } -static void show_sw_adapter_msg(struct MPGLContext *ctx) +static void show_sw_adapter_msg(MPGLContext *ctx) { struct priv *p = ctx->priv; if (p->sw_adapter_msg_shown) @@ -135,182 +234,309 @@ static void show_sw_adapter_msg(struct MPGLContext *ctx) p->sw_adapter_msg_shown = true; } -static void d3d_init(struct MPGLContext *ctx) +static bool d3d11_device_create(MPGLContext *ctx, int flags) { - HRESULT hr; struct priv *p = ctx->priv; struct vo *vo = ctx->vo; - IDXGIDevice *dxgi_dev = NULL; - IDXGIAdapter *dxgi_adapter = NULL; - IDXGIAdapter1 *dxgi_adapter1 = NULL; - IDXGIFactory *dxgi_factory = NULL; - - PFNEGLQUERYDISPLAYATTRIBEXTPROC eglQueryDisplayAttribEXT = - (PFNEGLQUERYDISPLAYATTRIBEXTPROC)eglGetProcAddress("eglQueryDisplayAttribEXT"); - PFNEGLQUERYDEVICEATTRIBEXTPROC eglQueryDeviceAttribEXT = - (PFNEGLQUERYDEVICEATTRIBEXTPROC)eglGetProcAddress("eglQueryDeviceAttribEXT"); - if (!eglQueryDisplayAttribEXT || !eglQueryDeviceAttribEXT) { - MP_VERBOSE(vo, "Missing EGL_EXT_device_query\n"); - goto done; + struct angle_opts *o = p->opts; + HRESULT hr; + + HMODULE d3d11_dll = LoadLibraryW(L"d3d11.dll"); + if (!d3d11_dll) { + MP_FATAL(vo, "Failed to load d3d11.dll\n"); + return false; } - EGLAttrib dev_attr; - if (!eglQueryDisplayAttribEXT(p->egl_display, EGL_DEVICE_EXT, &dev_attr)) { - MP_VERBOSE(vo, "Missing EGL_EXT_device_query\n"); - goto done; + PFN_D3D11_CREATE_DEVICE D3D11CreateDevice = (PFN_D3D11_CREATE_DEVICE) + GetProcAddress(d3d11_dll, "D3D11CreateDevice"); + if (!D3D11CreateDevice) { + MP_FATAL(vo, "D3D11CreateDevice entry point not found\n"); + return false; } - // If ANGLE is in D3D11 mode, get the underlying ID3D11Device - EGLDeviceEXT dev = (EGLDeviceEXT)dev_attr; - EGLAttrib d3d11_dev_attr; - if (eglQueryDeviceAttribEXT(dev, EGL_D3D11_DEVICE_ANGLE, &d3d11_dev_attr)) { - ID3D11Device *d3d11_dev = (ID3D11Device*)d3d11_dev_attr; + D3D_FEATURE_LEVEL *levels = (D3D_FEATURE_LEVEL[]) { + D3D_FEATURE_LEVEL_11_0, + D3D_FEATURE_LEVEL_10_1, + D3D_FEATURE_LEVEL_10_0, + D3D_FEATURE_LEVEL_9_3, + }; + D3D_FEATURE_LEVEL selected_level; + int level_count = 4; + + // Only try feature levels less than or equal to the user specified level + while (level_count && levels[0] > o->d3d11_feature_level) { + levels++; + level_count--; + } - hr = ID3D11Device_QueryInterface(d3d11_dev, &IID_IDXGIDevice, - (void**)&dxgi_dev); - if (FAILED(hr)) { - MP_ERR(vo, "Device is not a IDXGIDevice\n"); - goto done; - } + // Try a HW adapter first unless WARP is forced + hr = E_FAIL; + if ((FAILED(hr) && o->d3d11_warp == -1) || o->d3d11_warp == 0) { + hr = D3D11CreateDevice(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, levels, + level_count, D3D11_SDK_VERSION, &p->d3d11_device, &selected_level, + &p->d3d11_context); + } + // Try WARP if it is forced or if the HW adapter failed + if ((FAILED(hr) && o->d3d11_warp == -1) || o->d3d11_warp == 1) { + hr = D3D11CreateDevice(NULL, D3D_DRIVER_TYPE_WARP, NULL, 0, levels, + level_count, D3D11_SDK_VERSION, &p->d3d11_device, &selected_level, + &p->d3d11_context); + if (SUCCEEDED(hr)) + show_sw_adapter_msg(ctx); + } + if (FAILED(hr)) { + MP_FATAL(vo, "Couldn't create Direct3D 11 device: %s\n", + mp_HRESULT_to_str(hr)); + return false; + } - hr = IDXGIDevice_GetAdapter(dxgi_dev, &dxgi_adapter); - if (FAILED(hr)) { - MP_ERR(vo, "Couldn't get IDXGIAdapter\n"); - goto done; - } + MP_VERBOSE(vo, "Using Direct3D 11 feature level %u_%u\n", + ((unsigned)selected_level) >> 12, + (((unsigned)selected_level) >> 8) & 0xf); - // Windows 8 can choose a software adapter even if mpv didn't ask for - // one. If this is the case, show a warning message. - hr = IDXGIAdapter_QueryInterface(dxgi_adapter, &IID_IDXGIAdapter1, - (void**)&dxgi_adapter1); - if (SUCCEEDED(hr)) { - DXGI_ADAPTER_DESC1 desc; - hr = IDXGIAdapter1_GetDesc1(dxgi_adapter1, &desc); - if (SUCCEEDED(hr)) { - if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) - show_sw_adapter_msg(ctx); - - // If the primary display adapter is a software adapter, the - // DXGI_ADAPTER_FLAG_SOFTWARE won't be set, but the device IDs - // should still match the Microsoft Basic Render Driver - if (desc.VendorId == 0x1414 && desc.DeviceId == 0x8c) - show_sw_adapter_msg(ctx); - } - } + hr = ID3D11Device_QueryInterface(p->d3d11_device, &IID_IDXGIDevice1, + (void**)&p->dxgi_device); + if (FAILED(hr)) { + MP_FATAL(vo, "Couldn't get DXGI device\n"); + return false; + } - hr = IDXGIAdapter_GetParent(dxgi_adapter, &IID_IDXGIFactory, - (void**)&dxgi_factory); - if (FAILED(hr)) { - MP_ERR(vo, "Couldn't get IDXGIFactory\n"); - goto done; - } + IDXGIDevice1_SetMaximumFrameLatency(p->dxgi_device, o->max_frame_latency); - // Prevent DXGI from making changes to the VO window, otherwise in - // non-DirectComposition mode it will hook the Alt+Enter keystroke and - // make it trigger an ugly transition to exclusive fullscreen mode - // instead of running the user-set command. - IDXGIFactory_MakeWindowAssociation(dxgi_factory, vo_w32_hwnd(vo), - DXGI_MWA_NO_WINDOW_CHANGES | DXGI_MWA_NO_ALT_ENTER | - DXGI_MWA_NO_PRINT_SCREEN); + hr = IDXGIDevice1_GetParent(p->dxgi_device, &IID_IDXGIAdapter1, + (void**)&p->dxgi_adapter); + if (FAILED(hr)) { + MP_FATAL(vo, "Couldn't get DXGI adapter\n"); + return false; } -done: - if (dxgi_dev) - IDXGIDevice_Release(dxgi_dev); - if (dxgi_adapter) - IDXGIAdapter_Release(dxgi_adapter); - if (dxgi_adapter1) - IDXGIAdapter1_Release(dxgi_adapter1); - if (dxgi_factory) - IDXGIFactory_Release(dxgi_factory); + // Query some properties of the adapter in order to warn the user if they + // are using a software adapter + DXGI_ADAPTER_DESC1 desc; + hr = IDXGIAdapter1_GetDesc1(p->dxgi_adapter, &desc); + if (SUCCEEDED(hr)) { + if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) + show_sw_adapter_msg(ctx); + + // If the primary display adapter is a software adapter, the + // DXGI_ADAPTER_FLAG_SOFTWARE won't be set, but the device IDs + // should still match the Microsoft Basic Render Driver + if (desc.VendorId == 0x1414 && desc.DeviceId == 0x8c) + show_sw_adapter_msg(ctx); + } + + hr = IDXGIAdapter1_GetParent(p->dxgi_adapter, &IID_IDXGIFactory1, + (void**)&p->dxgi_factory); + if (FAILED(hr)) { + MP_FATAL(vo, "Couldn't get DXGI factory\n"); + return false; + } + + IDXGIFactory1_QueryInterface(p->dxgi_factory, &IID_IDXGIFactory2, + (void**)&p->dxgi_factory2); + + PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT = + (PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT"); + if (!eglGetPlatformDisplayEXT) { + MP_FATAL(vo, "Missing EGL_EXT_platform_base\n"); + return false; + } + PFNEGLCREATEDEVICEANGLEPROC eglCreateDeviceANGLE = + (PFNEGLCREATEDEVICEANGLEPROC)eglGetProcAddress("eglCreateDeviceANGLE"); + if (!eglCreateDeviceANGLE) { + MP_FATAL(vo, "Missing EGL_EXT_platform_device\n"); + return false; + } + + p->egl_device = eglCreateDeviceANGLE(EGL_D3D11_DEVICE_ANGLE, + p->d3d11_device, NULL); + if (!p->egl_device) { + MP_FATAL(vo, "Couldn't create EGL device\n"); + return false; + } + + p->egl_display = eglGetPlatformDisplayEXT(EGL_PLATFORM_DEVICE_EXT, + p->egl_device, NULL); + if (!p->egl_display) { + MP_FATAL(vo, "Couldn't get EGL display\n"); + return false; + } + + return true; } -static void *get_proc_address(const GLubyte *proc_name) +static void d3d11_swapchain_surface_destroy(MPGLContext *ctx) { - return eglGetProcAddress(proc_name); + struct priv *p = ctx->priv; + SAFE_RELEASE(p->dxgi_swapchain); + SAFE_RELEASE(p->dxgi_swapchain1); + d3d11_backbuffer_release(ctx); } -static int angle_init(struct MPGLContext *ctx, int flags) +static bool d3d11_swapchain_create_1_2(MPGLContext *ctx, int flags) { struct priv *p = ctx->priv; struct vo *vo = ctx->vo; + HRESULT hr; - p->opts = mp_get_config_group(ctx, ctx->global, &angle_conf); + update_sizes(ctx); + DXGI_SWAP_CHAIN_DESC1 desc1 = { + .Width = p->sc_width, + .Height = p->sc_height, + .Format = DXGI_FORMAT_R8G8B8A8_UNORM, + .SampleDesc = { .Count = 1 }, + .BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT | + DXGI_USAGE_SHADER_INPUT, + .BufferCount = p->opts->swapchain_length, + .SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL, + }; - if (!angle_load()) { - MP_VERBOSE(vo, "Failed to load LIBEGL.DLL\n"); - goto fail; + hr = IDXGIFactory2_CreateSwapChainForHwnd(p->dxgi_factory2, + (IUnknown*)p->d3d11_device, vo_w32_hwnd(vo), &desc1, NULL, NULL, + &p->dxgi_swapchain1); + if (FAILED(hr)) { + MP_FATAL(vo, "Couldn't create DXGI 1.2+ swap chain: %s\n", + mp_HRESULT_to_str(hr)); + return false; } - if (!vo_w32_init(vo)) - goto fail; + hr = IDXGISwapChain1_QueryInterface(p->dxgi_swapchain1, + &IID_IDXGISwapChain, (void**)&p->dxgi_swapchain); + if (FAILED(hr)) { + MP_FATAL(vo, "Couldn't create DXGI 1.2+ swap chain\n"); + return false; + } + + return true; +} + +static bool d3d11_swapchain_create_1_1(MPGLContext *ctx, int flags) +{ + struct priv *p = ctx->priv; + struct vo *vo = ctx->vo; + HRESULT hr; - HDC dc = GetDC(vo_w32_hwnd(vo)); - if (!dc) { - MP_FATAL(vo, "Couldn't get DC\n"); + update_sizes(ctx); + DXGI_SWAP_CHAIN_DESC desc = { + .BufferDesc = { + .Width = p->sc_width, + .Height = p->sc_height, + .Format = DXGI_FORMAT_R8G8B8A8_UNORM + }, + .SampleDesc = { .Count = 1 }, + .BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT | + DXGI_USAGE_SHADER_INPUT, + .BufferCount = 1, + .OutputWindow = vo_w32_hwnd(vo), + .Windowed = TRUE, + .SwapEffect = DXGI_SWAP_EFFECT_DISCARD, + }; + + hr = IDXGIFactory1_CreateSwapChain(p->dxgi_factory, + (IUnknown*)p->d3d11_device, &desc, &p->dxgi_swapchain); + if (FAILED(hr)) { + MP_FATAL(vo, "Couldn't create DXGI 1.1 swap chain: %s\n", + mp_HRESULT_to_str(hr)); + return false; + } + + return true; +} + +static bool d3d11_swapchain_surface_create(MPGLContext *ctx, int flags) +{ + struct priv *p = ctx->priv; + struct vo *vo = ctx->vo; + + if (p->dxgi_factory2) { + // Create a DXGI 1.2+ (Windows 8+) swap chain if possible + if (!d3d11_swapchain_create_1_2(ctx, flags)) + goto fail; + MP_VERBOSE(vo, "Using DXGI 1.2+\n"); + } else if (p->dxgi_factory) { + // Fall back to DXGI 1.1 (Windows 7) + if (!d3d11_swapchain_create_1_1(ctx, flags)) + goto fail; + MP_VERBOSE(vo, "Using DXGI 1.1\n"); + } else { goto fail; } + // Prevent DXGI from making changes to the VO window, otherwise it will + // hook the Alt+Enter keystroke and make it trigger an ugly transition to + // exclusive fullscreen mode instead of running the user-set command. + IDXGIFactory_MakeWindowAssociation(p->dxgi_factory, vo_w32_hwnd(vo), + DXGI_MWA_NO_WINDOW_CHANGES | DXGI_MWA_NO_ALT_ENTER | + DXGI_MWA_NO_PRINT_SCREEN); + + if (!d3d11_backbuffer_get(ctx)) + goto fail; + + // EGL_D3D_TEXTURE_ANGLE pbuffers are always flipped vertically + ctx->flip_v = true; + return true; + +fail: + d3d11_swapchain_surface_destroy(ctx); + return false; +} + +static void d3d9_device_destroy(MPGLContext *ctx) +{ + struct priv *p = ctx->priv; + + if (p->egl_display) + eglTerminate(p->egl_display); + p->egl_display = EGL_NO_DISPLAY; +} + +static bool d3d9_device_create(MPGLContext *ctx, int flags) +{ + struct priv *p = ctx->priv; + struct vo *vo = ctx->vo; PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT"); if (!eglGetPlatformDisplayEXT) { MP_FATAL(vo, "Missing EGL_EXT_platform_base\n"); - goto fail; + return false; } - EGLint d3d_types[] = {EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE, - EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE, - EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE}; - EGLint d3d_dev_types[] = {EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE, - EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE, - EGL_PLATFORM_ANGLE_DEVICE_TYPE_WARP_ANGLE}; - for (int i = 0; i < MP_ARRAY_SIZE(d3d_types); i++) { - EGLint display_attributes[] = { - EGL_PLATFORM_ANGLE_TYPE_ANGLE, - d3d_types[i], - EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE, - d3d_dev_types[i], - EGL_NONE, - }; - - p->egl_display = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, dc, - display_attributes); - if (p->egl_display == EGL_NO_DISPLAY) - continue; - - if (!eglInitialize(p->egl_display, NULL, NULL)) { - p->egl_display = EGL_NO_DISPLAY; - continue; - } - - if (d3d_dev_types[i] == EGL_PLATFORM_ANGLE_DEVICE_TYPE_WARP_ANGLE) - show_sw_adapter_msg(ctx); - break; - } + EGLint display_attributes[] = { + EGL_PLATFORM_ANGLE_TYPE_ANGLE, + EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE, + EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE, + EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE, + EGL_NONE, + }; + p->egl_display = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, + EGL_DEFAULT_DISPLAY, display_attributes); if (p->egl_display == EGL_NO_DISPLAY) { MP_FATAL(vo, "Couldn't get display\n"); - goto fail; + return false; } - const char *exts = eglQueryString(p->egl_display, EGL_EXTENSIONS); - if (exts) - MP_DBG(ctx->vo, "EGL extensions: %s\n", exts); + return true; +} - eglBindAPI(EGL_OPENGL_ES_API); - if (eglGetError() != EGL_SUCCESS) { - MP_FATAL(vo, "Couldn't bind GLES API\n"); - goto fail; +static void egl_window_surface_destroy(MPGLContext *ctx) +{ + struct priv *p = ctx->priv; + if (p->egl_window) { + eglMakeCurrent(p->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT); } +} - EGLConfig config = select_fb_config_egl(ctx); - if (!config) - goto fail; +static bool egl_window_surface_create(MPGLContext *ctx, int flags) +{ + struct priv *p = ctx->priv; + struct vo *vo = ctx->vo; int window_attribs_len = 0; EGLint *window_attribs = NULL; EGLint flip_val; - if (eglGetConfigAttrib(p->egl_display, config, + if (eglGetConfigAttrib(p->egl_display, p->egl_config, EGL_OPTIMAL_SURFACE_ORIENTATION_ANGLE, &flip_val)) { if (flip_val == EGL_SURFACE_ORIENTATION_INVERT_Y_ANGLE) { @@ -323,88 +549,283 @@ static int angle_init(struct MPGLContext *ctx, int flags) } } - // EGL_DIRECT_COMPOSITION_ANGLE enables the use of flip-mode present, which - // avoids a copy of the video image and lowers vsync jitter, though the - // extension is only present on Windows 8 and up, and might have subpar - // behavior with some drivers (Intel? symptom - whole desktop is black for - // some seconds after spending some minutes in fullscreen and then leaving - // fullscreen). - if (p->opts->allow_direct_composition && - strstr(exts, "EGL_ANGLE_direct_composition")) - { - MP_TARRAY_APPEND(NULL, window_attribs, window_attribs_len, - EGL_DIRECT_COMPOSITION_ANGLE); - MP_TARRAY_APPEND(NULL, window_attribs, window_attribs_len, EGL_TRUE); - MP_VERBOSE(vo, "Using DirectComposition.\n"); - } - MP_TARRAY_APPEND(NULL, window_attribs, window_attribs_len, EGL_NONE); - p->egl_surface = eglCreateWindowSurface(p->egl_display, config, - vo_w32_hwnd(vo), window_attribs); + p->egl_window = eglCreateWindowSurface(p->egl_display, p->egl_config, + vo_w32_hwnd(vo), window_attribs); talloc_free(window_attribs); - if (p->egl_surface == EGL_NO_SURFACE) { - MP_FATAL(ctx->vo, "Could not create EGL surface!\n"); + if (!p->egl_window) { + MP_FATAL(vo, "Could not create EGL surface!\n"); goto fail; } - if (!(!p->use_es2 && create_context_egl(ctx, config, 3)) && - !create_context_egl(ctx, config, 2)) - { - MP_FATAL(ctx->vo, "Could not create EGL context!\n"); + eglMakeCurrent(p->egl_display, p->egl_window, p->egl_window, + p->egl_context); + return true; +fail: + egl_window_surface_destroy(ctx); + return false; +} + +static void angle_uninit(struct MPGLContext *ctx) +{ + struct priv *p = ctx->priv; + + DwmEnableMMCSS(FALSE); + + // Uninit the EGL surface implementation that is being used. Note: This may + // result in the *_destroy function being called twice since it is also + // called when the surface create function fails. This is fine because the + // *_destroy functions are idempotent. + if (p->dxgi_swapchain) + d3d11_swapchain_surface_destroy(ctx); + else + egl_window_surface_destroy(ctx); + + if (p->egl_context) { + eglMakeCurrent(p->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT); + eglDestroyContext(p->egl_display, p->egl_context); + } + p->egl_context = EGL_NO_CONTEXT; + + // Uninit the EGL device implementation that is being used + if (p->d3d11_device) + d3d11_device_destroy(ctx); + else + d3d9_device_destroy(ctx); + + vo_w32_uninit(ctx->vo); +} + +static int GLAPIENTRY angle_swap_interval(int interval) +{ + if (!current_ctx) + return 0; + struct priv *p = current_ctx->priv; + + if (p->dxgi_swapchain) { + p->swapinterval = MPCLAMP(interval, 0, 4); + return 1; + } else { + return eglSwapInterval(p->egl_display, interval); + } +} + +static void *get_proc_address(const GLubyte *proc_name) +{ + return eglGetProcAddress(proc_name); +} + +static int angle_init(struct MPGLContext *ctx, int flags) +{ + struct priv *p = ctx->priv; + struct vo *vo = ctx->vo; + + p->opts = mp_get_config_group(ctx, ctx->global, &angle_conf); + struct angle_opts *o = p->opts; + + // DWM MMCSS cargo-cult. The dxinterop backend also does this. + DwmEnableMMCSS(TRUE); + + if (!angle_load()) { + MP_VERBOSE(vo, "Failed to load LIBEGL.DLL\n"); goto fail; } - // Configure the underlying Direct3D device - d3d_init(ctx); + // Create the underlying EGL device implementation + bool device_ok = false; + if ((!device_ok && !o->renderer) || o->renderer == RENDERER_D3D11) + device_ok = d3d11_device_create(ctx, flags); + if ((!device_ok && !o->renderer) || o->renderer == RENDERER_D3D9) + device_ok = d3d9_device_create(ctx, flags); + if (!device_ok) + goto fail; - if (strstr(exts, "EGL_NV_post_sub_buffer")) { - p->eglPostSubBufferNV = - (PFNEGLPOSTSUBBUFFERNVPROC)eglGetProcAddress("eglPostSubBufferNV"); + if (!eglInitialize(p->egl_display, NULL, NULL)) { + MP_FATAL(vo, "Couldn't initialize EGL\n"); + return false; + } + + if (!vo_w32_init(vo)) + goto fail; + + const char *exts = eglQueryString(p->egl_display, EGL_EXTENSIONS); + if (exts) + MP_DBG(ctx->vo, "EGL extensions: %s\n", exts); + + if (!mpegl_create_context(p->egl_display, vo->log, flags | VOFLAG_GLES, + &p->egl_context, &p->egl_config)) + { + MP_FATAL(vo, "Could not create EGL context!\n"); + goto fail; } + // Create the underlying EGL surface implementation + bool surface_ok = false; + if ((!surface_ok && o->egl_windowing == -1) || o->egl_windowing == 0) + surface_ok = d3d11_swapchain_surface_create(ctx, flags); + if ((!surface_ok && o->egl_windowing == -1) || o->egl_windowing == 1) + surface_ok = egl_window_surface_create(ctx, flags); + if (!surface_ok) + goto fail; + mpgl_load_functions(ctx->gl, get_proc_address, NULL, vo->log); - return 0; + current_ctx = ctx; + ctx->gl->SwapInterval = angle_swap_interval; + + return 0; fail: angle_uninit(ctx); return -1; } -static int angle_init_es2(struct MPGLContext *ctx, int flags) -{ - struct priv *p = ctx->priv; - p->use_es2 = true; - if (ctx->vo->probing) { - MP_VERBOSE(ctx->vo, "Not using this by default.\n"); - return -1; - } - return angle_init(ctx, flags); -} - static int angle_reconfig(struct MPGLContext *ctx) { vo_w32_config(ctx->vo); return 0; } +static struct mp_image *d3d11_screenshot(MPGLContext *ctx) +{ + struct priv *p = ctx->priv; + ID3D11Texture2D *frontbuffer = NULL; + ID3D11Texture2D *staging = NULL; + struct mp_image *img = NULL; + HRESULT hr; + + if (!p->dxgi_swapchain1) + goto done; + + // Validate the swap chain. This screenshot method will only work on DXGI + // 1.2+ flip/sequential swap chains. It's probably not possible at all with + // discard swap chains, since by definition, the backbuffer contents is + // discarded on Present(). + DXGI_SWAP_CHAIN_DESC1 scd; + hr = IDXGISwapChain1_GetDesc1(p->dxgi_swapchain1, &scd); + if (FAILED(hr)) + goto done; + if (scd.SwapEffect != DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL) + goto done; + + // Get the last buffer that was presented with Present(). This should be + // the n-1th buffer for a swap chain of length n. + hr = IDXGISwapChain_GetBuffer(p->dxgi_swapchain, scd.BufferCount - 1, + &IID_ID3D11Texture2D, (void**)&frontbuffer); + if (FAILED(hr)) + goto done; + + D3D11_TEXTURE2D_DESC td; + ID3D11Texture2D_GetDesc(frontbuffer, &td); + if (td.SampleDesc.Count > 1) + goto done; + + // Validate the backbuffer format and convert to an mpv IMGFMT + enum mp_imgfmt fmt; + switch (td.Format) { + case DXGI_FORMAT_B8G8R8A8_UNORM: fmt = IMGFMT_BGR0; break; + case DXGI_FORMAT_R8G8B8A8_UNORM: fmt = IMGFMT_RGB0; break; + default: + goto done; + } + + // Create a staging texture based on the frontbuffer with CPU access + td.BindFlags = 0; + td.MiscFlags = 0; + td.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + td.Usage = D3D11_USAGE_STAGING; + hr = ID3D11Device_CreateTexture2D(p->d3d11_device, &td, 0, &staging); + if (FAILED(hr)) + goto done; + + ID3D11DeviceContext_CopyResource(p->d3d11_context, + (ID3D11Resource*)staging, (ID3D11Resource*)frontbuffer); + + // Attempt to map the staging texture to CPU-accessible memory + D3D11_MAPPED_SUBRESOURCE lock; + hr = ID3D11DeviceContext_Map(p->d3d11_context, (ID3D11Resource*)staging, + 0, D3D11_MAP_READ, 0, &lock); + if (FAILED(hr)) + goto done; + + img = mp_image_alloc(fmt, td.Width, td.Height); + if (!img) + return NULL; + for (int i = 0; i < td.Height; i++) { + memcpy(img->planes[0] + img->stride[0] * i, + (char*)lock.pData + lock.RowPitch * i, td.Width * 4); + } + + ID3D11DeviceContext_Unmap(p->d3d11_context, (ID3D11Resource*)staging, 0); + +done: + SAFE_RELEASE(frontbuffer); + SAFE_RELEASE(staging); + return img; +} + static int angle_control(MPGLContext *ctx, int *events, int request, void *arg) { struct priv *p = ctx->priv; - int r = vo_w32_control(ctx->vo, events, request, arg); - // Calling eglPostSubBufferNV with a 0-sized region doesn't present a frame - // or block, but it does update the swapchain to match the window size - // See: https://groups.google.com/d/msg/angleproject/RvyVkjRCQGU/gfKfT64IAgAJ - if ((*events & VO_EVENT_RESIZE) && p->eglPostSubBufferNV) - p->eglPostSubBufferNV(p->egl_display, p->egl_surface, 0, 0, 0, 0); + // Try a D3D11-specific method of taking a window screenshot + if (request == VOCTRL_SCREENSHOT_WIN) { + struct mp_image *img = d3d11_screenshot(ctx); + if (img) { + *(struct mp_image **)arg = img; + return true; + } + } + int r = vo_w32_control(ctx->vo, events, request, arg); + if (*events & VO_EVENT_RESIZE) { + if (p->dxgi_swapchain) + d3d11_backbuffer_resize(ctx); + else + eglWaitClient(); // Should get ANGLE to resize its swapchain + } return r; } +static void d3d11_swap_buffers(MPGLContext *ctx) +{ + struct priv *p = ctx->priv; + + // Calling Present() on a flip-sequential swap chain will silently change + // the underlying storage of the back buffer to point to the next buffer in + // the chain. This results in the RTVs for the back buffer becoming + // unbound. Since ANGLE doesn't know we called Present(), it will continue + // using the unbound RTVs, so we must save and restore them ourselves. + ID3D11RenderTargetView *rtvs[D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT] = {0}; + ID3D11DepthStencilView *dsv = NULL; + ID3D11DeviceContext_OMGetRenderTargets(p->d3d11_context, + MP_ARRAY_SIZE(rtvs), rtvs, &dsv); + + HRESULT hr = IDXGISwapChain_Present(p->dxgi_swapchain, p->swapinterval, 0); + if (FAILED(hr)) + MP_FATAL(ctx->vo, "Couldn't present: %s\n", mp_HRESULT_to_str(hr)); + + // Restore the RTVs and release the objects + ID3D11DeviceContext_OMSetRenderTargets(p->d3d11_context, + MP_ARRAY_SIZE(rtvs), rtvs, dsv); + for (int i = 0; i < 8; i++) + SAFE_RELEASE(rtvs[i]); + SAFE_RELEASE(dsv); +} + +static void egl_swap_buffers(MPGLContext *ctx) +{ + struct priv *p = ctx->priv; + eglSwapBuffers(p->egl_display, p->egl_window); +} + static void angle_swap_buffers(MPGLContext *ctx) { struct priv *p = ctx->priv; - eglSwapBuffers(p->egl_display, p->egl_surface); + if (p->dxgi_swapchain) + d3d11_swap_buffers(ctx); + else + egl_swap_buffers(ctx); } const struct mpgl_driver mpgl_driver_angle = { @@ -416,13 +837,3 @@ const struct mpgl_driver mpgl_driver_angle = { .control = angle_control, .uninit = angle_uninit, }; - -const struct mpgl_driver mpgl_driver_angle_es2 = { - .name = "angle-es2", - .priv_size = sizeof(struct priv), - .init = angle_init_es2, - .reconfig = angle_reconfig, - .swap_buffers = angle_swap_buffers, - .control = angle_control, - .uninit = angle_uninit, -}; diff --git a/video/out/opengl/egl_helpers.c b/video/out/opengl/egl_helpers.c index 7f01d93307..465d945ce8 100644 --- a/video/out/opengl/egl_helpers.c +++ b/video/out/opengl/egl_helpers.c @@ -21,6 +21,12 @@ #include "common.h" #include "context.h" +#ifdef HAVE_EGL_ANGLE +// On Windows, egl_helpers.c is only used by ANGLE, where the EGL functions may +// be loaded dynamically from ANGLE DLLs +#include "angle_dynamic.h" +#endif + // EGL 1.5 #ifndef EGL_CONTEXT_OPENGL_PROFILE_MASK #define EGL_CONTEXT_MAJOR_VERSION 0x3098 diff --git a/wscript b/wscript index 2cddc830d7..e660ff7003 100644 --- a/wscript +++ b/wscript @@ -792,7 +792,8 @@ video_output_features = [ }, { 'name': 'egl-helpers', 'desc': 'EGL helper functions', - 'deps_any': [ 'egl-x11', 'mali-fbdev', 'rpi', 'gl-wayland', 'egl-drm' ], + 'deps_any': [ 'egl-x11', 'mali-fbdev', 'rpi', 'gl-wayland', 'egl-drm', + 'egl-angle' ], 'func': check_true } ] -- cgit v1.2.3