diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 20:36:56 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 20:36:56 +0000 |
commit | 51de1d8436100f725f3576aefa24a2bd2057bc28 (patch) | |
tree | c6d1d5264b6d40a8d7ca34129f36b7d61e188af3 /video/out/opengl/context_angle.c | |
parent | Initial commit. (diff) | |
download | mpv-0a581925d1e517e0dd867d02641216b9f5c51c5d.tar.xz mpv-0a581925d1e517e0dd867d02641216b9f5c51c5d.zip |
Adding upstream version 0.37.0.upstream/0.37.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'video/out/opengl/context_angle.c')
-rw-r--r-- | video/out/opengl/context_angle.c | 653 |
1 files changed, 653 insertions, 0 deletions
diff --git a/video/out/opengl/context_angle.c b/video/out/opengl/context_angle.c new file mode 100644 index 0000000..553718a --- /dev/null +++ b/video/out/opengl/context_angle.c @@ -0,0 +1,653 @@ +/* + * 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_opts +const 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", + .init = angle_init, + .reconfig = angle_reconfig, + .control = angle_control, + .uninit = angle_uninit, +}; |