Something went wrong. Try again.
🎥 Command line media player
Something went wrong. Try again.
C
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655/* * 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 <EGL/egl.h>#include <EGL/eglext.h>#include <EGL/eglext_angle.h>#include <d3d11.h>#include <dxgi1_2.h>#include <dwmapi.h>
#include "angle_dynamic.h"#include "egl_helpers.h"#include "video/out/gpu/d3d11_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"#include "utils.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#define EGL_SURFACE_ORIENTATION_INVERT_Y_ANGLE 0x0002#endif
enum { RENDERER_AUTO, RENDERER_D3D9, RENDERER_D3D11,};
struct angle_opts { int renderer; int d3d11_warp; int d3d11_feature_level; int egl_windowing; bool flip;};
#define OPT_BASE_STRUCT struct angle_optsconst struct m_sub_options angle_conf = { .opts = (const struct m_option[]) { {"angle-renderer", OPT_CHOICE(renderer, {"auto", RENDERER_AUTO}, {"d3d9", RENDERER_D3D9}, {"d3d11", RENDERER_D3D11})}, {"angle-d3d11-warp", OPT_CHOICE(d3d11_warp, {"auto", -1}, {"no", 0}, {"yes", 1})}, {"angle-d3d11-feature-level", OPT_CHOICE(d3d11_feature_level, {"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})}, {"angle-egl-windowing", OPT_CHOICE(egl_windowing, {"auto", -1}, {"no", 0}, {"yes", 1})}, {"angle-flip", OPT_BOOL(flip)}, {0} }, .defaults = &(const struct angle_opts) { .renderer = RENDERER_AUTO, .d3d11_warp = -1, .d3d11_feature_level = D3D_FEATURE_LEVEL_11_0, .egl_windowing = -1, .flip = true, }, .size = sizeof(struct angle_opts),};
struct priv { GL gl;
IDXGISwapChain *dxgi_swapchain;
ID3D11Device *d3d11_device; ID3D11DeviceContext *d3d11_context; ID3D11Texture2D *d3d11_backbuffer;
EGLConfig egl_config; EGLDisplay egl_display; EGLDeviceEXT egl_device; EGLContext egl_context; 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 flipped;
struct angle_opts *opts;};
static __thread struct ra_ctx *current_ctx;
static void update_sizes(struct ra_ctx *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;}
static void d3d11_backbuffer_release(struct ra_ctx *ctx){ struct priv *p = ctx->priv;
if (p->egl_backbuffer) { eglMakeCurrent(p->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroySurface(p->egl_display, p->egl_backbuffer); } p->egl_backbuffer = EGL_NO_SURFACE;
SAFE_RELEASE(p->d3d11_backbuffer);}
static bool d3d11_backbuffer_get(struct ra_ctx *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 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;}
static void d3d11_backbuffer_resize(struct ra_ctx *ctx){ struct priv *p = ctx->priv; struct vo *vo = ctx->vo; HRESULT hr;
int old_sc_width = p->sc_width; int old_sc_height = p->sc_height;
update_sizes(ctx); // Avoid unnecessary resizing if (old_sc_width == p->sc_width && old_sc_height == p->sc_height) return;
// 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 void d3d11_device_destroy(struct ra_ctx *ctx){ struct priv *p = ctx->priv;
PFNEGLRELEASEDEVICEANGLEPROC eglReleaseDeviceANGLE = (PFNEGLRELEASEDEVICEANGLEPROC)eglGetProcAddress("eglReleaseDeviceANGLE");
if (p->egl_display) eglTerminate(p->egl_display); p->egl_display = EGL_NO_DISPLAY;
if (p->egl_device && eglReleaseDeviceANGLE) eglReleaseDeviceANGLE(p->egl_device); p->egl_device = 0;
SAFE_RELEASE(p->d3d11_device);}
static bool d3d11_device_create(struct ra_ctx *ctx){ struct priv *p = ctx->priv; struct vo *vo = ctx->vo; struct angle_opts *o = p->opts;
struct d3d11_device_opts device_opts = { .allow_warp = o->d3d11_warp != 0, .force_warp = o->d3d11_warp == 1, .max_feature_level = o->d3d11_feature_level, .min_feature_level = D3D_FEATURE_LEVEL_9_3, .max_frame_latency = ctx->vo->opts->swapchain_depth, }; if (!mp_d3d11_create_present_device(vo->log, &device_opts, &p->d3d11_device)) return false; ID3D11Device_GetImmediateContext(p->d3d11_device, &p->d3d11_context);
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 d3d11_swapchain_surface_destroy(struct ra_ctx *ctx){ struct priv *p = ctx->priv;
bool had_swapchain = p->dxgi_swapchain; SAFE_RELEASE(p->dxgi_swapchain); d3d11_backbuffer_release(ctx);
// Ensure the swapchain is destroyed by flushing the D3D11 immediate // context. This is needed because the HWND may be reused. See: // https://msdn.microsoft.com/en-us/library/windows/desktop/ff476425.aspx if (had_swapchain && p->d3d11_context) ID3D11DeviceContext_Flush(p->d3d11_context);}
static bool d3d11_swapchain_surface_create(struct ra_ctx *ctx){ struct priv *p = ctx->priv; struct vo *vo = ctx->vo; struct angle_opts *o = p->opts;
if (!p->d3d11_device) goto fail;
update_sizes(ctx); struct d3d11_swapchain_opts swapchain_opts = { .window = vo_w32_hwnd(vo), .width = p->sc_width, .height = p->sc_height, .flip = o->flip, // Add one frame for the backbuffer and one frame of "slack" to reduce // contention with the window manager when acquiring the backbuffer .length = ctx->vo->opts->swapchain_depth + 2, .usage = DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT, }; if (!mp_d3d11_create_swapchain(p->d3d11_device, vo->log, &swapchain_opts, &p->dxgi_swapchain)) goto fail; if (!d3d11_backbuffer_get(ctx)) goto fail;
p->flipped = true; return true;
fail: d3d11_swapchain_surface_destroy(ctx); return false;}
static void d3d9_device_destroy(struct ra_ctx *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(struct ra_ctx *ctx){ 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"); return false; }
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"); return false; }
return true;}
static void egl_window_surface_destroy(struct ra_ctx *ctx){ struct priv *p = ctx->priv; if (p->egl_window) { eglMakeCurrent(p->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); }}
static bool egl_window_surface_create(struct ra_ctx *ctx){ 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, p->egl_config, EGL_OPTIMAL_SURFACE_ORIENTATION_ANGLE, &flip_val)) { if (flip_val == EGL_SURFACE_ORIENTATION_INVERT_Y_ANGLE) { MP_TARRAY_APPEND(NULL, window_attribs, window_attribs_len, EGL_SURFACE_ORIENTATION_ANGLE); MP_TARRAY_APPEND(NULL, window_attribs, window_attribs_len, EGL_SURFACE_ORIENTATION_INVERT_Y_ANGLE); p->flipped = true; MP_VERBOSE(vo, "Rendering flipped.\n"); } }
MP_TARRAY_APPEND(NULL, window_attribs, window_attribs_len, EGL_NONE); p->egl_window = eglCreateWindowSurface(p->egl_display, p->egl_config, vo_w32_hwnd(vo), window_attribs); talloc_free(window_attribs); if (!p->egl_window) { MP_FATAL(vo, "Could not create EGL surface!\n"); goto fail; }
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 context_destroy(struct ra_ctx *ctx){ struct priv *p = ctx->priv; 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;}
static bool context_init(struct ra_ctx *ctx){ struct priv *p = ctx->priv; struct vo *vo = ctx->vo;
if (!eglInitialize(p->egl_display, NULL, NULL)) { MP_FATAL(vo, "Couldn't initialize EGL\n"); goto fail; }
const char *exts = eglQueryString(p->egl_display, EGL_EXTENSIONS); if (exts) MP_DBG(vo, "EGL extensions: %s\n", exts);
if (!mpegl_create_context(ctx, p->egl_display, &p->egl_context, &p->egl_config)) { MP_FATAL(vo, "Could not create EGL context!\n"); goto fail; }
return true;fail: context_destroy(ctx); return false;}
static void angle_uninit(struct ra_ctx *ctx){ struct priv *p = ctx->priv;
ra_gl_ctx_uninit(ctx);
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);
context_destroy(ctx);
// 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 d3d11_swap_buffers(struct ra_ctx *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 < MP_ARRAY_SIZE(rtvs); i++) SAFE_RELEASE(rtvs[i]); SAFE_RELEASE(dsv);}
static void egl_swap_buffers(struct ra_ctx *ctx){ struct priv *p = ctx->priv; eglSwapBuffers(p->egl_display, p->egl_window);}
static void angle_swap_buffers(struct ra_ctx *ctx){ struct priv *p = ctx->priv; if (p->dxgi_swapchain) d3d11_swap_buffers(ctx); else egl_swap_buffers(ctx);}
static int angle_color_depth(struct ra_swapchain *sw){ // Only 8-bit output is supported at the moment return 8;}
static bool angle_submit_frame(struct ra_swapchain *sw, const struct vo_frame *frame){ struct priv *p = sw->ctx->priv; bool ret = ra_gl_ctx_submit_frame(sw, frame); if (p->d3d11_context) { // DXGI Present doesn't flush the immediate context, which can make // timers inaccurate, since the end queries might not be sent until the // next frame. Fix this by flushing the context now. ID3D11DeviceContext_Flush(p->d3d11_context); } return ret;}
static bool angle_init(struct ra_ctx *ctx){ struct priv *p = ctx->priv = talloc_zero(ctx, struct priv); struct vo *vo = ctx->vo; GL *gl = &p->gl;
p->opts = mp_get_config_group(ctx, ctx->global, &angle_conf); struct angle_opts *o = p->opts;
if (!angle_load()) { MP_VERBOSE(vo, "Failed to load LIBEGL.DLL\n"); goto fail; }
// Create the underlying EGL device implementation bool context_ok = false; if ((!context_ok && !o->renderer) || o->renderer == RENDERER_D3D11) { context_ok = d3d11_device_create(ctx); if (context_ok) { context_ok = context_init(ctx); if (!context_ok) d3d11_device_destroy(ctx); } } if ((!context_ok && !o->renderer) || o->renderer == RENDERER_D3D9) { context_ok = d3d9_device_create(ctx); if (context_ok) { MP_VERBOSE(vo, "Using Direct3D 9\n");
context_ok = context_init(ctx); if (!context_ok) d3d9_device_destroy(ctx); } } if (!context_ok) goto fail;
if (!vo_w32_init(vo)) 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); } if ((!surface_ok && o->egl_windowing == -1) || o->egl_windowing == 1) { surface_ok = egl_window_surface_create(ctx); if (surface_ok) MP_VERBOSE(vo, "Using EGL windowing\n"); } if (!surface_ok) goto fail;
mpegl_load_functions(gl, vo->log);
current_ctx = ctx; gl->SwapInterval = angle_swap_interval;
// Custom swapchain impl for the D3D11 swapchain-based surface static const struct ra_swapchain_fns dxgi_swapchain_fns = { .color_depth = angle_color_depth, .submit_frame = angle_submit_frame, }; struct ra_gl_ctx_params params = { .swap_buffers = angle_swap_buffers, .external_swapchain = p->dxgi_swapchain ? &dxgi_swapchain_fns : NULL, };
gl->flipped = p->flipped; if (!ra_gl_ctx_init(ctx, gl, params)) goto fail;
DwmEnableMMCSS(TRUE); // DWM MMCSS cargo-cult. The dxgl backend also does this.
return true;fail: angle_uninit(ctx); return false;}
static void resize(struct ra_ctx *ctx){ struct priv *p = ctx->priv; if (p->dxgi_swapchain) d3d11_backbuffer_resize(ctx); else eglWaitClient(); // Should get ANGLE to resize its swapchain ra_gl_ctx_resize(ctx->swapchain, ctx->vo->dwidth, ctx->vo->dheight, 0);}
static bool angle_reconfig(struct ra_ctx *ctx){ vo_w32_config(ctx->vo); resize(ctx); return true;}
static int angle_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_angle = { .type = "opengl", .name = "angle", .description = "Win32/ANGLE (via Direct3D)", .init = angle_init, .reconfig = angle_reconfig, .control = angle_control, .uninit = angle_uninit,};