summaryrefslogtreecommitdiffstats
path: root/video/out/opengl
diff options
context:
space:
mode:
Diffstat (limited to 'video/out/opengl')
-rw-r--r--video/out/opengl/angle_dynamic.c39
-rw-r--r--video/out/opengl/angle_dynamic.h89
-rw-r--r--video/out/opengl/common.c694
-rw-r--r--video/out/opengl/common.h258
-rw-r--r--video/out/opengl/context.c324
-rw-r--r--video/out/opengl/context.h58
-rw-r--r--video/out/opengl/context_android.c130
-rw-r--r--video/out/opengl/context_angle.c653
-rw-r--r--video/out/opengl/context_drm_egl.c744
-rw-r--r--video/out/opengl/context_dxinterop.c605
-rw-r--r--video/out/opengl/context_glx.c351
-rw-r--r--video/out/opengl/context_rpi.c327
-rw-r--r--video/out/opengl/context_wayland.c230
-rw-r--r--video/out/opengl/context_win.c378
-rw-r--r--video/out/opengl/context_x11egl.c225
-rw-r--r--video/out/opengl/egl_helpers.c381
-rw-r--r--video/out/opengl/egl_helpers.h38
-rw-r--r--video/out/opengl/formats.c196
-rw-r--r--video/out/opengl/formats.h51
-rw-r--r--video/out/opengl/gl_headers.h799
-rw-r--r--video/out/opengl/hwdec_d3d11egl.c363
-rw-r--r--video/out/opengl/hwdec_dxva2egl.c384
-rw-r--r--video/out/opengl/hwdec_dxva2gldx.c247
-rw-r--r--video/out/opengl/hwdec_rpi.c384
-rw-r--r--video/out/opengl/hwdec_vdpau.c251
-rw-r--r--video/out/opengl/libmpv_gl.c114
-rw-r--r--video/out/opengl/ra_gl.c1208
-rw-r--r--video/out/opengl/ra_gl.h17
-rw-r--r--video/out/opengl/utils.c282
-rw-r--r--video/out/opengl/utils.h57
30 files changed, 9877 insertions, 0 deletions
diff --git a/video/out/opengl/angle_dynamic.c b/video/out/opengl/angle_dynamic.c
new file mode 100644
index 0000000..2483828
--- /dev/null
+++ b/video/out/opengl/angle_dynamic.c
@@ -0,0 +1,39 @@
+#include <windows.h>
+
+#include "angle_dynamic.h"
+
+#include "common/common.h"
+#include "osdep/threads.h"
+
+#if HAVE_EGL_ANGLE_LIB
+bool angle_load(void)
+{
+ return true;
+}
+#else
+#define ANGLE_DECL(NAME, VAR) \
+ VAR;
+ANGLE_FNS(ANGLE_DECL)
+
+static bool angle_loaded;
+static mp_once angle_load_once = MP_STATIC_ONCE_INITIALIZER;
+
+static void angle_do_load(void)
+{
+ // Note: we let this handle "leak", as the functions remain valid forever.
+ HANDLE angle_dll = LoadLibraryW(L"LIBEGL.DLL");
+ if (!angle_dll)
+ return;
+#define ANGLE_LOAD_ENTRY(NAME, VAR) \
+ NAME = (void *)GetProcAddress(angle_dll, #NAME); \
+ if (!NAME) return;
+ ANGLE_FNS(ANGLE_LOAD_ENTRY)
+ angle_loaded = true;
+}
+
+bool angle_load(void)
+{
+ mp_exec_once(&angle_load_once, angle_do_load);
+ return angle_loaded;
+}
+#endif
diff --git a/video/out/opengl/angle_dynamic.h b/video/out/opengl/angle_dynamic.h
new file mode 100644
index 0000000..d419c3f
--- /dev/null
+++ b/video/out/opengl/angle_dynamic.h
@@ -0,0 +1,89 @@
+// Based on Khronos headers, thus MIT licensed.
+
+#ifndef MP_ANGLE_DYNAMIC_H
+#define MP_ANGLE_DYNAMIC_H
+
+#include <stdbool.h>
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
+#include "config.h"
+
+#define ANGLE_FNS(FN) \
+ FN(eglBindAPI, EGLBoolean (*EGLAPIENTRY PFN_eglBindAPI)(EGLenum)) \
+ FN(eglBindTexImage, EGLBoolean (*EGLAPIENTRY PFN_eglBindTexImage) \
+ (EGLDisplay, EGLSurface, EGLint)) \
+ FN(eglChooseConfig, EGLBoolean (*EGLAPIENTRY PFN_eglChooseConfig) \
+ (EGLDisplay, const EGLint *, EGLConfig *, EGLint, EGLint *)) \
+ FN(eglCreateContext, EGLContext (*EGLAPIENTRY PFN_eglCreateContext) \
+ (EGLDisplay, EGLConfig, EGLContext, const EGLint *)) \
+ FN(eglCreatePbufferFromClientBuffer, EGLSurface (*EGLAPIENTRY \
+ PFN_eglCreatePbufferFromClientBuffer)(EGLDisplay, EGLenum, \
+ EGLClientBuffer, EGLConfig, const EGLint *)) \
+ FN(eglCreateWindowSurface, EGLSurface (*EGLAPIENTRY \
+ PFN_eglCreateWindowSurface)(EGLDisplay, EGLConfig, \
+ EGLNativeWindowType, const EGLint *)) \
+ FN(eglDestroyContext, EGLBoolean (*EGLAPIENTRY PFN_eglDestroyContext) \
+ (EGLDisplay, EGLContext)) \
+ FN(eglDestroySurface, EGLBoolean (*EGLAPIENTRY PFN_eglDestroySurface) \
+ (EGLDisplay, EGLSurface)) \
+ FN(eglGetConfigAttrib, EGLBoolean (*EGLAPIENTRY PFN_eglGetConfigAttrib) \
+ (EGLDisplay, EGLConfig, EGLint, EGLint *)) \
+ FN(eglGetCurrentContext, EGLContext (*EGLAPIENTRY \
+ PFN_eglGetCurrentContext)(void)) \
+ FN(eglGetCurrentDisplay, EGLDisplay (*EGLAPIENTRY \
+ PFN_eglGetCurrentDisplay)(void)) \
+ FN(eglGetDisplay, EGLDisplay (*EGLAPIENTRY PFN_eglGetDisplay) \
+ (EGLNativeDisplayType)) \
+ FN(eglGetError, EGLint (*EGLAPIENTRY PFN_eglGetError)(void)) \
+ FN(eglGetProcAddress, void *(*EGLAPIENTRY \
+ PFN_eglGetProcAddress)(const char *)) \
+ FN(eglInitialize, EGLBoolean (*EGLAPIENTRY PFN_eglInitialize) \
+ (EGLDisplay, EGLint *, EGLint *)) \
+ FN(eglMakeCurrent, EGLBoolean (*EGLAPIENTRY PFN_eglMakeCurrent) \
+ (EGLDisplay, EGLSurface, EGLSurface, EGLContext)) \
+ FN(eglQueryString, const char *(*EGLAPIENTRY PFN_eglQueryString) \
+ (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(eglWaitClient, EGLBoolean (*EGLAPIENTRY PFN_eglWaitClient)(void))
+
+#define ANGLE_EXT_DECL(NAME, VAR) \
+ extern VAR;
+ANGLE_FNS(ANGLE_EXT_DECL)
+
+bool angle_load(void);
+
+// Source compatibility to statically linked ANGLE.
+#if !HAVE_EGL_ANGLE_LIB
+#define eglBindAPI PFN_eglBindAPI
+#define eglBindTexImage PFN_eglBindTexImage
+#define eglChooseConfig PFN_eglChooseConfig
+#define eglCreateContext PFN_eglCreateContext
+#define eglCreatePbufferFromClientBuffer PFN_eglCreatePbufferFromClientBuffer
+#define eglCreateWindowSurface PFN_eglCreateWindowSurface
+#define eglDestroyContext PFN_eglDestroyContext
+#define eglDestroySurface PFN_eglDestroySurface
+#define eglGetConfigAttrib PFN_eglGetConfigAttrib
+#define eglGetCurrentContext PFN_eglGetCurrentContext
+#define eglGetCurrentDisplay PFN_eglGetCurrentDisplay
+#define eglGetDisplay PFN_eglGetDisplay
+#define eglGetError PFN_eglGetError
+#define eglGetProcAddress PFN_eglGetProcAddress
+#define eglInitialize PFN_eglInitialize
+#define eglMakeCurrent PFN_eglMakeCurrent
+#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/common.c b/video/out/opengl/common.c
new file mode 100644
index 0000000..ee26508
--- /dev/null
+++ b/video/out/opengl/common.c
@@ -0,0 +1,694 @@
+/*
+ * common OpenGL routines
+ *
+ * copyleft (C) 2005-2010 Reimar Döffinger <Reimar.Doeffinger@gmx.de>
+ * Special thanks go to the xine team and Matthias Hopf, whose video_out_opengl.c
+ * gave me lots of good ideas.
+ *
+ * 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 <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <stdbool.h>
+#include <math.h>
+#include <assert.h>
+
+#include "common.h"
+#include "common/common.h"
+#include "utils.h"
+
+// This guesses if the current GL context is a suspected software renderer.
+static bool is_software_gl(GL *gl)
+{
+ const char *renderer = gl->GetString(GL_RENDERER);
+ // Note we don't attempt to blacklist Microsoft's fallback implementation.
+ // It only provides OpenGL 1.1 and will be skipped anyway.
+ return !renderer ||
+ strcmp(renderer, "Software Rasterizer") == 0 ||
+ strstr(renderer, "llvmpipe") ||
+ strstr(renderer, "softpipe") ||
+ strcmp(renderer, "Mesa X11") == 0 ||
+ strcmp(renderer, "Apple Software Renderer") == 0;
+}
+
+// This guesses whether our DR path is fast or slow
+static bool is_fast_dr(GL *gl)
+{
+ const char *vendor = gl->GetString(GL_VENDOR);
+ if (!vendor)
+ return false;
+
+ return strcasecmp(vendor, "AMD") == 0 ||
+ strcasecmp(vendor, "NVIDIA Corporation") == 0 ||
+ strcasecmp(vendor, "ATI Technologies Inc.") == 0; // AMD on Windows
+}
+
+static void GLAPIENTRY dummy_glBindFramebuffer(GLenum target, GLuint framebuffer)
+{
+ assert(framebuffer == 0);
+}
+
+#define FN_OFFS(name) offsetof(GL, name)
+
+#define DEF_FN(name) {FN_OFFS(name), "gl" # name}
+#define DEF_FN_NAME(name, str) {FN_OFFS(name), str}
+
+struct gl_function {
+ ptrdiff_t offset;
+ char *name;
+};
+
+struct gl_functions {
+ const char *extension; // introduced with this extension in any version
+ int provides; // bitfield of MPGL_CAP_* constants
+ int ver_core; // introduced as required function
+ int ver_es_core; // introduced as required GL ES function
+ int ver_exclude; // not applicable to versions >= ver_exclude
+ int ver_es_exclude; // same for GLES
+ const struct gl_function *functions;
+};
+
+#define MAX_FN_COUNT 100 // max functions per gl_functions section
+
+// Note: to keep the number of sections low, some functions are in multiple
+// sections (if there are tricky combinations of GL/ES versions)
+static const struct gl_functions gl_functions[] = {
+ // GL 2.1+ desktop and GLES 2.0+ (anything we support)
+ // Probably all of these are in GL 2.0 too, but we require GLSL 120.
+ {
+ .ver_core = 210,
+ .ver_es_core = 200,
+ .functions = (const struct gl_function[]) {
+ DEF_FN(ActiveTexture),
+ DEF_FN(AttachShader),
+ DEF_FN(BindAttribLocation),
+ DEF_FN(BindBuffer),
+ DEF_FN(BindTexture),
+ DEF_FN(BlendFuncSeparate),
+ DEF_FN(BufferData),
+ DEF_FN(BufferSubData),
+ DEF_FN(Clear),
+ DEF_FN(ClearColor),
+ DEF_FN(CompileShader),
+ DEF_FN(CreateProgram),
+ DEF_FN(CreateShader),
+ DEF_FN(DeleteBuffers),
+ DEF_FN(DeleteProgram),
+ DEF_FN(DeleteShader),
+ DEF_FN(DeleteTextures),
+ DEF_FN(Disable),
+ DEF_FN(DisableVertexAttribArray),
+ DEF_FN(DrawArrays),
+ DEF_FN(Enable),
+ DEF_FN(EnableVertexAttribArray),
+ DEF_FN(Finish),
+ DEF_FN(Flush),
+ DEF_FN(GenBuffers),
+ DEF_FN(GenTextures),
+ DEF_FN(GetAttribLocation),
+ DEF_FN(GetError),
+ DEF_FN(GetIntegerv),
+ DEF_FN(GetProgramInfoLog),
+ DEF_FN(GetProgramiv),
+ DEF_FN(GetShaderInfoLog),
+ DEF_FN(GetShaderiv),
+ DEF_FN(GetString),
+ DEF_FN(GetUniformLocation),
+ DEF_FN(LinkProgram),
+ DEF_FN(PixelStorei),
+ DEF_FN(ReadPixels),
+ DEF_FN(Scissor),
+ DEF_FN(ShaderSource),
+ DEF_FN(TexImage2D),
+ DEF_FN(TexParameteri),
+ DEF_FN(TexSubImage2D),
+ DEF_FN(Uniform1f),
+ DEF_FN(Uniform2f),
+ DEF_FN(Uniform3f),
+ DEF_FN(Uniform1i),
+ DEF_FN(UniformMatrix2fv),
+ DEF_FN(UniformMatrix3fv),
+ DEF_FN(UseProgram),
+ DEF_FN(VertexAttribPointer),
+ DEF_FN(Viewport),
+ {0}
+ },
+ },
+ // GL 2.1+ desktop only (and GLSL 120 shaders)
+ {
+ .ver_core = 210,
+ .provides = MPGL_CAP_ROW_LENGTH | MPGL_CAP_1D_TEX,
+ .functions = (const struct gl_function[]) {
+ DEF_FN(DrawBuffer),
+ DEF_FN(GetTexLevelParameteriv),
+ DEF_FN(ReadBuffer),
+ DEF_FN(TexImage1D),
+ DEF_FN(UnmapBuffer),
+ {0}
+ },
+ },
+ // GL 2.1 has this as extension only.
+ {
+ .ver_exclude = 300,
+ .ver_es_exclude = 300,
+ .extension = "GL_ARB_map_buffer_range",
+ .functions = (const struct gl_function[]) {
+ DEF_FN(MapBufferRange),
+ {0}
+ },
+ },
+ // GL 3.0+ and ES 3.x core only functions.
+ {
+ .ver_core = 300,
+ .ver_es_core = 300,
+ .functions = (const struct gl_function[]) {
+ DEF_FN(BindBufferBase),
+ DEF_FN(BlitFramebuffer),
+ DEF_FN(GetStringi),
+ DEF_FN(MapBufferRange),
+ // for ES 3.0
+ DEF_FN(ReadBuffer),
+ DEF_FN(UnmapBuffer),
+ {0}
+ },
+ },
+ // For ES 3.1 core
+ {
+ .ver_es_core = 310,
+ .functions = (const struct gl_function[]) {
+ DEF_FN(GetTexLevelParameteriv),
+ {0}
+ },
+ },
+ {
+ .ver_core = 210,
+ .ver_es_core = 300,
+ .provides = MPGL_CAP_3D_TEX,
+ .functions = (const struct gl_function[]) {
+ DEF_FN(TexImage3D),
+ {0}
+ },
+ },
+ // Useful for ES 2.0
+ {
+ .ver_core = 110,
+ .ver_es_core = 300,
+ .extension = "GL_EXT_unpack_subimage",
+ .provides = MPGL_CAP_ROW_LENGTH,
+ },
+ // Framebuffers, extension in GL 2.x, core in GL 3.x core.
+ {
+ .ver_core = 300,
+ .ver_es_core = 200,
+ .extension = "GL_ARB_framebuffer_object",
+ .provides = MPGL_CAP_FB,
+ .functions = (const struct gl_function[]) {
+ DEF_FN(BindFramebuffer),
+ DEF_FN(GenFramebuffers),
+ DEF_FN(DeleteFramebuffers),
+ DEF_FN(CheckFramebufferStatus),
+ DEF_FN(FramebufferTexture2D),
+ DEF_FN(GetFramebufferAttachmentParameteriv),
+ {0}
+ },
+ },
+ // VAOs, extension in GL 2.x, core in GL 3.x core.
+ {
+ .ver_core = 300,
+ .ver_es_core = 300,
+ .extension = "GL_ARB_vertex_array_object",
+ .provides = MPGL_CAP_VAO,
+ .functions = (const struct gl_function[]) {
+ DEF_FN(GenVertexArrays),
+ DEF_FN(BindVertexArray),
+ DEF_FN(DeleteVertexArrays),
+ {0}
+ }
+ },
+ // GL_RED / GL_RG textures, extension in GL 2.x, core in GL 3.x core.
+ {
+ .ver_core = 300,
+ .ver_es_core = 300,
+ .extension = "GL_ARB_texture_rg",
+ .provides = MPGL_CAP_TEX_RG,
+ },
+ {
+ .ver_core = 300,
+ .ver_es_core = 300,
+ .extension = "GL_EXT_texture_rg",
+ .provides = MPGL_CAP_TEX_RG,
+ },
+ // GL_R16 etc.
+ {
+ .extension = "GL_EXT_texture_norm16",
+ .provides = MPGL_CAP_EXT16,
+ .ver_exclude = 1, // never in desktop GL
+ },
+ // Float texture support for GL 2.x
+ {
+ .extension = "GL_ARB_texture_float",
+ .provides = MPGL_CAP_ARB_FLOAT,
+ .ver_exclude = 300,
+ .ver_es_exclude = 1,
+ },
+ // 16 bit float textures that can be rendered to in GLES
+ {
+ .extension = "GL_EXT_color_buffer_half_float",
+ .provides = MPGL_CAP_EXT_CR_HFLOAT,
+ .ver_exclude = 1,
+ .ver_es_exclude = 320,
+ },
+ {
+ .ver_core = 320,
+ .ver_es_core = 300,
+ .extension = "GL_ARB_sync",
+ .functions = (const struct gl_function[]) {
+ DEF_FN(FenceSync),
+ DEF_FN(ClientWaitSync),
+ DEF_FN(DeleteSync),
+ {0}
+ },
+ },
+ {
+ .ver_core = 330,
+ .extension = "GL_ARB_timer_query",
+ .functions = (const struct gl_function[]) {
+ DEF_FN(GenQueries),
+ DEF_FN(DeleteQueries),
+ DEF_FN(BeginQuery),
+ DEF_FN(EndQuery),
+ DEF_FN(QueryCounter),
+ DEF_FN(IsQuery),
+ DEF_FN(GetQueryObjectiv),
+ DEF_FN(GetQueryObjecti64v),
+ DEF_FN(GetQueryObjectuiv),
+ DEF_FN(GetQueryObjectui64v),
+ {0}
+ },
+ },
+ {
+ .extension = "GL_EXT_disjoint_timer_query",
+ .functions = (const struct gl_function[]) {
+ DEF_FN_NAME(GenQueries, "glGenQueriesEXT"),
+ DEF_FN_NAME(DeleteQueries, "glDeleteQueriesEXT"),
+ DEF_FN_NAME(BeginQuery, "glBeginQueryEXT"),
+ DEF_FN_NAME(EndQuery, "glEndQueryEXT"),
+ DEF_FN_NAME(QueryCounter, "glQueryCounterEXT"),
+ DEF_FN_NAME(IsQuery, "glIsQueryEXT"),
+ DEF_FN_NAME(GetQueryObjectiv, "glGetQueryObjectivEXT"),
+ DEF_FN_NAME(GetQueryObjecti64v, "glGetQueryObjecti64vEXT"),
+ DEF_FN_NAME(GetQueryObjectuiv, "glGetQueryObjectuivEXT"),
+ DEF_FN_NAME(GetQueryObjectui64v, "glGetQueryObjectui64vEXT"),
+ {0}
+ },
+ },
+ {
+ .ver_core = 430,
+ .extension = "GL_ARB_invalidate_subdata",
+ .functions = (const struct gl_function[]) {
+ DEF_FN(InvalidateTexImage),
+ {0}
+ },
+ },
+ {
+ .ver_core = 430,
+ .ver_es_core = 300,
+ .functions = (const struct gl_function[]) {
+ DEF_FN(InvalidateFramebuffer),
+ {0}
+ },
+ },
+ {
+ .ver_core = 410,
+ .ver_es_core = 300,
+ .extension = "GL_ARB_get_program_binary",
+ .functions = (const struct gl_function[]) {
+ DEF_FN(GetProgramBinary),
+ DEF_FN(ProgramBinary),
+ {0}
+ },
+ },
+ {
+ .ver_core = 440,
+ .extension = "GL_ARB_buffer_storage",
+ .functions = (const struct gl_function[]) {
+ DEF_FN(BufferStorage),
+ {0}
+ },
+ },
+ // Equivalent extension for ES
+ {
+ .extension = "GL_EXT_buffer_storage",
+ .functions = (const struct gl_function[]) {
+ DEF_FN_NAME(BufferStorage, "glBufferStorageEXT"),
+ {0}
+ },
+ },
+ {
+ .ver_core = 420,
+ .ver_es_core = 310,
+ .extension = "GL_ARB_shader_image_load_store",
+ .functions = (const struct gl_function[]) {
+ DEF_FN(BindImageTexture),
+ DEF_FN(MemoryBarrier),
+ {0}
+ },
+ },
+ {
+ .ver_core = 310,
+ .ver_es_core = 300,
+ .extension = "GL_ARB_uniform_buffer_object",
+ .provides = MPGL_CAP_UBO,
+ },
+ {
+ .ver_core = 430,
+ .ver_es_core = 310,
+ .extension = "GL_ARB_shader_storage_buffer_object",
+ .provides = MPGL_CAP_SSBO,
+ },
+ {
+ .ver_core = 430,
+ .ver_es_core = 310,
+ .extension = "GL_ARB_compute_shader",
+ .functions = (const struct gl_function[]) {
+ DEF_FN(DispatchCompute),
+ {0},
+ },
+ },
+ {
+ .ver_core = 430,
+ .extension = "GL_ARB_arrays_of_arrays",
+ .provides = MPGL_CAP_NESTED_ARRAY,
+ },
+ // Swap control, always an OS specific extension
+ // The OSX code loads this manually.
+ {
+ .extension = "GLX_SGI_swap_control",
+ .functions = (const struct gl_function[]) {
+ DEF_FN_NAME(SwapInterval, "glXSwapIntervalSGI"),
+ {0},
+ },
+ },
+ // This one overrides GLX_SGI_swap_control on platforms using mesa. The
+ // only difference is that it supports glXSwapInterval(0).
+ {
+ .extension = "GLX_MESA_swap_control",
+ .functions = (const struct gl_function[]) {
+ DEF_FN_NAME(SwapInterval, "glXSwapIntervalMESA"),
+ {0},
+ },
+ },
+ {
+ .extension = "WGL_EXT_swap_control",
+ .functions = (const struct gl_function[]) {
+ DEF_FN_NAME(SwapInterval, "wglSwapIntervalEXT"),
+ {0},
+ },
+ },
+ {
+ .extension = "GLX_SGI_video_sync",
+ .functions = (const struct gl_function[]) {
+ DEF_FN_NAME(GetVideoSync, "glXGetVideoSyncSGI"),
+ DEF_FN_NAME(WaitVideoSync, "glXWaitVideoSyncSGI"),
+ {0},
+ },
+ },
+ // For gl_hwdec_vdpau.c
+ // http://www.opengl.org/registry/specs/NV/vdpau_interop.txt
+ {
+ .extension = "GL_NV_vdpau_interop",
+ .provides = MPGL_CAP_VDPAU,
+ .functions = (const struct gl_function[]) {
+ // (only functions needed by us)
+ DEF_FN(VDPAUInitNV),
+ DEF_FN(VDPAUFiniNV),
+ DEF_FN(VDPAURegisterOutputSurfaceNV),
+ DEF_FN(VDPAURegisterVideoSurfaceNV),
+ DEF_FN(VDPAUUnregisterSurfaceNV),
+ DEF_FN(VDPAUSurfaceAccessNV),
+ DEF_FN(VDPAUMapSurfacesNV),
+ DEF_FN(VDPAUUnmapSurfacesNV),
+ {0}
+ },
+ },
+#if HAVE_GL_DXINTEROP
+ {
+ .extension = "WGL_NV_DX_interop",
+ .provides = MPGL_CAP_DXINTEROP,
+ .functions = (const struct gl_function[]) {
+ DEF_FN_NAME(DXSetResourceShareHandleNV, "wglDXSetResourceShareHandleNV"),
+ DEF_FN_NAME(DXOpenDeviceNV, "wglDXOpenDeviceNV"),
+ DEF_FN_NAME(DXCloseDeviceNV, "wglDXCloseDeviceNV"),
+ DEF_FN_NAME(DXRegisterObjectNV, "wglDXRegisterObjectNV"),
+ DEF_FN_NAME(DXUnregisterObjectNV, "wglDXUnregisterObjectNV"),
+ DEF_FN_NAME(DXLockObjectsNV, "wglDXLockObjectsNV"),
+ DEF_FN_NAME(DXUnlockObjectsNV, "wglDXUnlockObjectsNV"),
+ {0}
+ },
+ },
+#endif
+ // Apple Packed YUV Formats
+ // For gl_hwdec_vda.c
+ // http://www.opengl.org/registry/specs/APPLE/rgb_422.txt
+ {
+ .extension = "GL_APPLE_rgb_422",
+ .provides = MPGL_CAP_APPLE_RGB_422,
+ },
+ {
+ .ver_core = 430,
+ .extension = "GL_ARB_debug_output",
+ .provides = MPGL_CAP_DEBUG,
+ .functions = (const struct gl_function[]) {
+ // (only functions needed by us)
+ DEF_FN(DebugMessageCallback),
+ {0}
+ },
+ },
+ // ES version uses a different extension.
+ {
+ .ver_es_core = 320,
+ .extension = "GL_KHR_debug",
+ .provides = MPGL_CAP_DEBUG,
+ .functions = (const struct gl_function[]) {
+ // (only functions needed by us)
+ DEF_FN(DebugMessageCallback),
+ {0}
+ },
+ },
+ {
+ .extension = "GL_ANGLE_translated_shader_source",
+ .functions = (const struct gl_function[]) {
+ DEF_FN(GetTranslatedShaderSourceANGLE),
+ {0}
+ },
+ },
+};
+
+#undef FN_OFFS
+#undef DEF_FN_HARD
+#undef DEF_FN
+#undef DEF_FN_NAME
+
+// Fill the GL struct with function pointers and extensions from the current
+// GL context. Called by the backend.
+// get_fn: function to resolve function names
+// ext2: an extra extension string
+// log: used to output messages
+void mpgl_load_functions2(GL *gl, void *(*get_fn)(void *ctx, const char *n),
+ void *fn_ctx, const char *ext2, struct mp_log *log)
+{
+ talloc_free(gl->extensions);
+ *gl = (GL) {
+ .extensions = talloc_strdup(gl, ext2 ? ext2 : ""),
+ .get_fn = get_fn,
+ .fn_ctx = fn_ctx,
+ };
+
+ gl->GetString = get_fn(fn_ctx, "glGetString");
+ if (!gl->GetString) {
+ mp_err(log, "Can't load OpenGL functions.\n");
+ goto error;
+ }
+
+ int major = 0, minor = 0;
+ const char *version_string = gl->GetString(GL_VERSION);
+ if (!version_string) {
+ mp_fatal(log, "glGetString(GL_VERSION) returned NULL.\n");
+ goto error;
+ }
+ mp_verbose(log, "GL_VERSION='%s'\n", version_string);
+ if (strncmp(version_string, "OpenGL ES ", 10) == 0) {
+ version_string += 10;
+ gl->es = 100;
+ }
+ if (sscanf(version_string, "%d.%d", &major, &minor) < 2)
+ goto error;
+ gl->version = MPGL_VER(major, minor);
+ mp_verbose(log, "Detected %s %d.%d.\n", gl->es ? "GLES" : "desktop OpenGL",
+ major, minor);
+
+ if (gl->es) {
+ gl->es = gl->version;
+ gl->version = 0;
+ if (gl->es < 200) {
+ mp_fatal(log, "At least GLESv2 required.\n");
+ goto error;
+ }
+ }
+
+ mp_verbose(log, "GL_VENDOR='%s'\n", gl->GetString(GL_VENDOR));
+ mp_verbose(log, "GL_RENDERER='%s'\n", gl->GetString(GL_RENDERER));
+ const char *shader = gl->GetString(GL_SHADING_LANGUAGE_VERSION);
+ if (shader)
+ mp_verbose(log, "GL_SHADING_LANGUAGE_VERSION='%s'\n", shader);
+
+ if (gl->version >= 300) {
+ gl->GetStringi = get_fn(fn_ctx, "glGetStringi");
+ gl->GetIntegerv = get_fn(fn_ctx, "glGetIntegerv");
+
+ if (!(gl->GetStringi && gl->GetIntegerv))
+ goto error;
+
+ GLint exts;
+ gl->GetIntegerv(GL_NUM_EXTENSIONS, &exts);
+ for (int n = 0; n < exts; n++) {
+ const char *ext = gl->GetStringi(GL_EXTENSIONS, n);
+ gl->extensions = talloc_asprintf_append(gl->extensions, " %s", ext);
+ }
+
+ } else {
+ const char *ext = (char*)gl->GetString(GL_EXTENSIONS);
+ gl->extensions = talloc_asprintf_append(gl->extensions, " %s", ext);
+ }
+
+ mp_dbg(log, "Combined OpenGL extensions string:\n%s\n", gl->extensions);
+
+ for (int n = 0; n < MP_ARRAY_SIZE(gl_functions); n++) {
+ const struct gl_functions *section = &gl_functions[n];
+ int version = gl->es ? gl->es : gl->version;
+ int ver_core = gl->es ? section->ver_es_core : section->ver_core;
+
+ // NOTE: Function entrypoints can exist, even if they do not work.
+ // We must always check extension strings and versions.
+
+ if (gl->version && section->ver_exclude &&
+ gl->version >= section->ver_exclude)
+ continue;
+ if (gl->es && section->ver_es_exclude &&
+ gl->es >= section->ver_es_exclude)
+ continue;
+
+ bool exists = false, must_exist = false;
+ if (ver_core)
+ must_exist = version >= ver_core;
+
+ if (section->extension)
+ exists = gl_check_extension(gl->extensions, section->extension);
+
+ exists |= must_exist;
+ if (!exists)
+ continue;
+
+ void *loaded[MAX_FN_COUNT] = {0};
+ bool all_loaded = true;
+ const struct gl_function *fnlist = section->functions;
+
+ for (int i = 0; fnlist && fnlist[i].name; i++) {
+ const struct gl_function *fn = &fnlist[i];
+ void *ptr = get_fn(fn_ctx, fn->name);
+ if (!ptr) {
+ all_loaded = false;
+ if (must_exist) {
+ mp_err(log, "GL %d.%d function %s not found.\n",
+ MPGL_VER_GET_MAJOR(ver_core),
+ MPGL_VER_GET_MINOR(ver_core), fn->name);
+ goto error;
+ } else {
+ mp_warn(log, "Function %s from extension %s not found.\n",
+ fn->name, section->extension);
+ }
+ break;
+ }
+ assert(i < MAX_FN_COUNT);
+ loaded[i] = ptr;
+ }
+
+ if (all_loaded) {
+ gl->mpgl_caps |= section->provides;
+ for (int i = 0; fnlist && fnlist[i].name; i++) {
+ const struct gl_function *fn = &fnlist[i];
+ void **funcptr = (void**)(((char*)gl) + fn->offset);
+ if (loaded[i])
+ *funcptr = loaded[i];
+ }
+ if (!must_exist && section->extension)
+ mp_verbose(log, "Loaded extension %s.\n", section->extension);
+ }
+ }
+
+ gl->glsl_version = 0;
+ if (gl->es) {
+ if (gl->es >= 200)
+ gl->glsl_version = 100;
+ if (gl->es >= 300)
+ gl->glsl_version = gl->es;
+ } else {
+ gl->glsl_version = 120;
+ int glsl_major = 0, glsl_minor = 0;
+ if (shader && sscanf(shader, "%d.%d", &glsl_major, &glsl_minor) == 2)
+ gl->glsl_version = glsl_major * 100 + glsl_minor;
+ // restrict GLSL version to be forwards compatible
+ gl->glsl_version = MPMIN(gl->glsl_version, 440);
+ }
+
+ if (is_software_gl(gl)) {
+ gl->mpgl_caps |= MPGL_CAP_SW;
+ mp_verbose(log, "Detected suspected software renderer.\n");
+ }
+
+ if (!is_fast_dr(gl))
+ gl->mpgl_caps |= MPGL_CAP_SLOW_DR;
+
+ // GL_ARB_compute_shader & GL_ARB_shader_image_load_store
+ if (gl->DispatchCompute && gl->BindImageTexture)
+ gl->mpgl_caps |= MPGL_CAP_COMPUTE_SHADER;
+
+ // Provided for simpler handling if no framebuffer support is available.
+ if (!gl->BindFramebuffer)
+ gl->BindFramebuffer = &dummy_glBindFramebuffer;
+ return;
+
+error:
+ gl->version = 0;
+ gl->es = 0;
+ gl->mpgl_caps = 0;
+}
+
+static void *get_procaddr_wrapper(void *ctx, const char *name)
+{
+ void *(*getProcAddress)(const GLubyte *) = ctx;
+ return getProcAddress ? getProcAddress((const GLubyte*)name) : NULL;
+}
+
+void mpgl_load_functions(GL *gl, void *(*getProcAddress)(const GLubyte *),
+ const char *ext2, struct mp_log *log)
+{
+ mpgl_load_functions2(gl, get_procaddr_wrapper, getProcAddress, ext2, log);
+}
diff --git a/video/out/opengl/common.h b/video/out/opengl/common.h
new file mode 100644
index 0000000..a6b02c9
--- /dev/null
+++ b/video/out/opengl/common.h
@@ -0,0 +1,258 @@
+/*
+ * 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/>.
+ */
+
+#ifndef MPLAYER_GL_COMMON_H
+#define MPLAYER_GL_COMMON_H
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "config.h"
+#include "common/msg.h"
+#include "misc/bstr.h"
+
+#include "video/csputils.h"
+#include "video/mp_image.h"
+#include "video/out/vo.h"
+#include "video/out/gpu/ra.h"
+
+#include "gl_headers.h"
+
+#if HAVE_GL_WIN32
+#include <windows.h>
+#endif
+
+struct GL;
+typedef struct GL GL;
+
+enum {
+ MPGL_CAP_ROW_LENGTH = (1 << 4), // GL_[UN]PACK_ROW_LENGTH
+ MPGL_CAP_FB = (1 << 5),
+ MPGL_CAP_VAO = (1 << 6),
+ MPGL_CAP_TEX_RG = (1 << 10), // GL_ARB_texture_rg / GL 3.x
+ MPGL_CAP_VDPAU = (1 << 11), // GL_NV_vdpau_interop
+ MPGL_CAP_APPLE_RGB_422 = (1 << 12), // GL_APPLE_rgb_422
+ MPGL_CAP_1D_TEX = (1 << 14),
+ MPGL_CAP_3D_TEX = (1 << 15),
+ MPGL_CAP_DEBUG = (1 << 16),
+ MPGL_CAP_DXINTEROP = (1 << 17), // WGL_NV_DX_interop
+ MPGL_CAP_EXT16 = (1 << 18), // GL_EXT_texture_norm16
+ MPGL_CAP_ARB_FLOAT = (1 << 19), // GL_ARB_texture_float
+ MPGL_CAP_EXT_CR_HFLOAT = (1 << 20), // GL_EXT_color_buffer_half_float
+ MPGL_CAP_UBO = (1 << 21), // GL_ARB_uniform_buffer_object
+ MPGL_CAP_SSBO = (1 << 22), // GL_ARB_shader_storage_buffer_object
+ MPGL_CAP_COMPUTE_SHADER = (1 << 23), // GL_ARB_compute_shader & GL_ARB_shader_image_load_store
+ MPGL_CAP_NESTED_ARRAY = (1 << 24), // GL_ARB_arrays_of_arrays
+
+ MPGL_CAP_SLOW_DR = (1 << 29), // direct rendering is assumed to be slow
+ MPGL_CAP_SW = (1 << 30), // indirect or sw renderer
+};
+
+// E.g. 310 means 3.1
+// Code doesn't have to use the macros; they are for convenience only.
+#define MPGL_VER(major, minor) (((major) * 100) + (minor) * 10)
+#define MPGL_VER_GET_MAJOR(ver) ((unsigned)(ver) / 100)
+#define MPGL_VER_GET_MINOR(ver) ((unsigned)(ver) % 100 / 10)
+
+#define MPGL_VER_P(ver) MPGL_VER_GET_MAJOR(ver), MPGL_VER_GET_MINOR(ver)
+
+void mpgl_load_functions(GL *gl, void *(*getProcAddress)(const GLubyte *),
+ const char *ext2, struct mp_log *log);
+void mpgl_load_functions2(GL *gl, void *(*get_fn)(void *ctx, const char *n),
+ void *fn_ctx, const char *ext2, struct mp_log *log);
+
+typedef void (GLAPIENTRY *MP_GLDEBUGPROC)(GLenum, GLenum, GLuint, GLenum,
+ GLsizei, const GLchar *,const void *);
+
+//function pointers loaded from the OpenGL library
+struct GL {
+ int version; // MPGL_VER() mangled (e.g. 210 for 2.1)
+ int es; // es version (e.g. 300), 0 for desktop GL
+ int glsl_version; // e.g. 130 for GLSL 1.30
+ char *extensions; // Equivalent to GL_EXTENSIONS
+ int mpgl_caps; // Bitfield of MPGL_CAP_* constants
+ bool debug_context; // use of e.g. GLX_CONTEXT_DEBUG_BIT_ARB
+
+ // Set to false if the implementation follows normal GL semantics, which is
+ // upside down. Set to true if it does *not*, i.e. if rendering is right
+ // side up
+ bool flipped;
+
+ // Copy of function pointer used to load GL.
+ // Caution: Not necessarily valid to use after VO init has completed!
+ void *(*get_fn)(void *ctx, const char *n);
+ void *fn_ctx;
+
+ void (GLAPIENTRY *Viewport)(GLint, GLint, GLsizei, GLsizei);
+ void (GLAPIENTRY *Clear)(GLbitfield);
+ void (GLAPIENTRY *GenTextures)(GLsizei, GLuint *);
+ void (GLAPIENTRY *DeleteTextures)(GLsizei, const GLuint *);
+ void (GLAPIENTRY *ClearColor)(GLclampf, GLclampf, GLclampf, GLclampf);
+ void (GLAPIENTRY *Enable)(GLenum);
+ void (GLAPIENTRY *Disable)(GLenum);
+ const GLubyte *(GLAPIENTRY * GetString)(GLenum);
+ void (GLAPIENTRY *BlendFuncSeparate)(GLenum, GLenum, GLenum, GLenum);
+ void (GLAPIENTRY *Flush)(void);
+ void (GLAPIENTRY *Finish)(void);
+ void (GLAPIENTRY *PixelStorei)(GLenum, GLint);
+ void (GLAPIENTRY *TexImage1D)(GLenum, GLint, GLint, GLsizei, GLint,
+ GLenum, GLenum, const GLvoid *);
+ void (GLAPIENTRY *TexImage2D)(GLenum, GLint, GLint, GLsizei, GLsizei,
+ GLint, GLenum, GLenum, const GLvoid *);
+ void (GLAPIENTRY *TexSubImage2D)(GLenum, GLint, GLint, GLint,
+ GLsizei, GLsizei, GLenum, GLenum,
+ const GLvoid *);
+ void (GLAPIENTRY *TexParameteri)(GLenum, GLenum, GLint);
+ void (GLAPIENTRY *GetIntegerv)(GLenum, GLint *);
+ void (GLAPIENTRY *ReadPixels)(GLint, GLint, GLsizei, GLsizei, GLenum,
+ GLenum, GLvoid *);
+ void (GLAPIENTRY *ReadBuffer)(GLenum);
+ void (GLAPIENTRY *DrawBuffer)(GLenum);
+ void (GLAPIENTRY *DrawArrays)(GLenum, GLint, GLsizei);
+ GLenum (GLAPIENTRY *GetError)(void);
+ void (GLAPIENTRY *GetTexLevelParameteriv)(GLenum, GLint, GLenum, GLint *);
+ void (GLAPIENTRY *Scissor)(GLint, GLint, GLsizei, GLsizei);
+
+ void (GLAPIENTRY *GenBuffers)(GLsizei, GLuint *);
+ void (GLAPIENTRY *DeleteBuffers)(GLsizei, const GLuint *);
+ void (GLAPIENTRY *BindBuffer)(GLenum, GLuint);
+ void (GLAPIENTRY *BindBufferBase)(GLenum, GLuint, GLuint);
+ GLvoid * (GLAPIENTRY *MapBufferRange)(GLenum, GLintptr, GLsizeiptr,
+ GLbitfield);
+ GLboolean (GLAPIENTRY *UnmapBuffer)(GLenum);
+ void (GLAPIENTRY *BufferData)(GLenum, intptr_t, const GLvoid *, GLenum);
+ void (GLAPIENTRY *BufferSubData)(GLenum, GLintptr, GLsizeiptr, const GLvoid *);
+ void (GLAPIENTRY *ActiveTexture)(GLenum);
+ void (GLAPIENTRY *BindTexture)(GLenum, GLuint);
+ int (GLAPIENTRY *SwapInterval)(int);
+ void (GLAPIENTRY *TexImage3D)(GLenum, GLint, GLenum, GLsizei, GLsizei,
+ GLsizei, GLint, GLenum, GLenum,
+ const GLvoid *);
+
+ void (GLAPIENTRY *GenVertexArrays)(GLsizei, GLuint *);
+ void (GLAPIENTRY *BindVertexArray)(GLuint);
+ GLint (GLAPIENTRY *GetAttribLocation)(GLuint, const GLchar *);
+ void (GLAPIENTRY *EnableVertexAttribArray)(GLuint);
+ void (GLAPIENTRY *DisableVertexAttribArray)(GLuint);
+ void (GLAPIENTRY *VertexAttribPointer)(GLuint, GLint, GLenum, GLboolean,
+ GLsizei, const GLvoid *);
+ void (GLAPIENTRY *DeleteVertexArrays)(GLsizei, const GLuint *);
+ void (GLAPIENTRY *UseProgram)(GLuint);
+ GLint (GLAPIENTRY *GetUniformLocation)(GLuint, const GLchar *);
+ void (GLAPIENTRY *CompileShader)(GLuint);
+ GLuint (GLAPIENTRY *CreateProgram)(void);
+ GLuint (GLAPIENTRY *CreateShader)(GLenum);
+ void (GLAPIENTRY *ShaderSource)(GLuint, GLsizei, const GLchar **,
+ const GLint *);
+ void (GLAPIENTRY *LinkProgram)(GLuint);
+ void (GLAPIENTRY *AttachShader)(GLuint, GLuint);
+ void (GLAPIENTRY *DeleteShader)(GLuint);
+ void (GLAPIENTRY *DeleteProgram)(GLuint);
+ void (GLAPIENTRY *GetShaderInfoLog)(GLuint, GLsizei, GLsizei *, GLchar *);
+ void (GLAPIENTRY *GetShaderiv)(GLuint, GLenum, GLint *);
+ void (GLAPIENTRY *GetProgramInfoLog)(GLuint, GLsizei, GLsizei *, GLchar *);
+ void (GLAPIENTRY *GetProgramiv)(GLenum, GLenum, GLint *);
+ void (GLAPIENTRY *GetProgramBinary)(GLuint, GLsizei, GLsizei *, GLenum *,
+ void *);
+ void (GLAPIENTRY *ProgramBinary)(GLuint, GLenum, const void *, GLsizei);
+
+ void (GLAPIENTRY *DispatchCompute)(GLuint, GLuint, GLuint);
+ void (GLAPIENTRY *BindImageTexture)(GLuint, GLuint, GLint, GLboolean,
+ GLint, GLenum, GLenum);
+ void (GLAPIENTRY *MemoryBarrier)(GLbitfield);
+
+ const GLubyte* (GLAPIENTRY *GetStringi)(GLenum, GLuint);
+ void (GLAPIENTRY *BindAttribLocation)(GLuint, GLuint, const GLchar *);
+ void (GLAPIENTRY *BindFramebuffer)(GLenum, GLuint);
+ void (GLAPIENTRY *GenFramebuffers)(GLsizei, GLuint *);
+ void (GLAPIENTRY *DeleteFramebuffers)(GLsizei, const GLuint *);
+ GLenum (GLAPIENTRY *CheckFramebufferStatus)(GLenum);
+ void (GLAPIENTRY *FramebufferTexture2D)(GLenum, GLenum, GLenum, GLuint,
+ GLint);
+ void (GLAPIENTRY *BlitFramebuffer)(GLint, GLint, GLint, GLint, GLint, GLint,
+ GLint, GLint, GLbitfield, GLenum);
+ void (GLAPIENTRY *GetFramebufferAttachmentParameteriv)(GLenum, GLenum,
+ GLenum, GLint *);
+
+ void (GLAPIENTRY *Uniform1f)(GLint, GLfloat);
+ void (GLAPIENTRY *Uniform2f)(GLint, GLfloat, GLfloat);
+ void (GLAPIENTRY *Uniform3f)(GLint, GLfloat, GLfloat, GLfloat);
+ void (GLAPIENTRY *Uniform4f)(GLint, GLfloat, GLfloat, GLfloat, GLfloat);
+ void (GLAPIENTRY *Uniform1i)(GLint, GLint);
+ void (GLAPIENTRY *UniformMatrix2fv)(GLint, GLsizei, GLboolean,
+ const GLfloat *);
+ void (GLAPIENTRY *UniformMatrix3fv)(GLint, GLsizei, GLboolean,
+ const GLfloat *);
+
+ void (GLAPIENTRY *InvalidateTexImage)(GLuint, GLint);
+ void (GLAPIENTRY *InvalidateFramebuffer)(GLenum, GLsizei, const GLenum *);
+
+ GLsync (GLAPIENTRY *FenceSync)(GLenum, GLbitfield);
+ GLenum (GLAPIENTRY *ClientWaitSync)(GLsync, GLbitfield, GLuint64);
+ void (GLAPIENTRY *DeleteSync)(GLsync sync);
+
+ void (GLAPIENTRY *BufferStorage)(GLenum, intptr_t, const GLvoid *, GLenum);
+
+ void (GLAPIENTRY *GenQueries)(GLsizei, GLuint *);
+ void (GLAPIENTRY *DeleteQueries)(GLsizei, const GLuint *);
+ void (GLAPIENTRY *BeginQuery)(GLenum, GLuint);
+ void (GLAPIENTRY *EndQuery)(GLenum);
+ void (GLAPIENTRY *QueryCounter)(GLuint, GLenum);
+ GLboolean (GLAPIENTRY *IsQuery)(GLuint);
+ void (GLAPIENTRY *GetQueryObjectiv)(GLuint, GLenum, GLint *);
+ void (GLAPIENTRY *GetQueryObjecti64v)(GLuint, GLenum, GLint64 *);
+ void (GLAPIENTRY *GetQueryObjectuiv)(GLuint, GLenum, GLuint *);
+ void (GLAPIENTRY *GetQueryObjectui64v)(GLuint, GLenum, GLuint64 *);
+
+ void (GLAPIENTRY *VDPAUInitNV)(const GLvoid *, const GLvoid *);
+ void (GLAPIENTRY *VDPAUFiniNV)(void);
+ GLvdpauSurfaceNV (GLAPIENTRY *VDPAURegisterOutputSurfaceNV)
+ (GLvoid *, GLenum, GLsizei, const GLuint *);
+ GLvdpauSurfaceNV (GLAPIENTRY *VDPAURegisterVideoSurfaceNV)
+ (GLvoid *, GLenum, GLsizei, const GLuint *);
+ void (GLAPIENTRY *VDPAUUnregisterSurfaceNV)(GLvdpauSurfaceNV);
+ void (GLAPIENTRY *VDPAUSurfaceAccessNV)(GLvdpauSurfaceNV, GLenum);
+ void (GLAPIENTRY *VDPAUMapSurfacesNV)(GLsizei, const GLvdpauSurfaceNV *);
+ void (GLAPIENTRY *VDPAUUnmapSurfacesNV)(GLsizei, const GLvdpauSurfaceNV *);
+
+#if HAVE_GL_WIN32
+ // The HANDLE type might not be present on non-Win32
+ BOOL (GLAPIENTRY *DXSetResourceShareHandleNV)(void *dxObject,
+ HANDLE shareHandle);
+ HANDLE (GLAPIENTRY *DXOpenDeviceNV)(void *dxDevice);
+ BOOL (GLAPIENTRY *DXCloseDeviceNV)(HANDLE hDevice);
+ HANDLE (GLAPIENTRY *DXRegisterObjectNV)(HANDLE hDevice, void *dxObject,
+ GLuint name, GLenum type, GLenum access);
+ BOOL (GLAPIENTRY *DXUnregisterObjectNV)(HANDLE hDevice, HANDLE hObject);
+ BOOL (GLAPIENTRY *DXLockObjectsNV)(HANDLE hDevice, GLint count,
+ HANDLE *hObjects);
+ BOOL (GLAPIENTRY *DXUnlockObjectsNV)(HANDLE hDevice, GLint count,
+ HANDLE *hObjects);
+#endif
+
+ GLint (GLAPIENTRY *GetVideoSync)(GLuint *);
+ GLint (GLAPIENTRY *WaitVideoSync)(GLint, GLint, unsigned int *);
+
+ void (GLAPIENTRY *GetTranslatedShaderSourceANGLE)(GLuint, GLsizei,
+ GLsizei*, GLchar* source);
+
+ void (GLAPIENTRY *DebugMessageCallback)(MP_GLDEBUGPROC callback,
+ const void *userParam);
+};
+
+#endif /* MPLAYER_GL_COMMON_H */
diff --git a/video/out/opengl/context.c b/video/out/opengl/context.c
new file mode 100644
index 0000000..05e279b
--- /dev/null
+++ b/video/out/opengl/context.c
@@ -0,0 +1,324 @@
+/*
+ * 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 "options/m_config.h"
+#include "context.h"
+#include "ra_gl.h"
+#include "utils.h"
+
+// 0-terminated list of desktop GL versions a backend should try to
+// initialize. Each entry is the minimum required version.
+const int mpgl_min_required_gl_versions[] = {
+ /*
+ * Nvidia drivers will not provide the highest supported version
+ * when 320 core is requested. Instead, it just returns 3.2. This
+ * would be bad, as we actually want compute shaders that require
+ * 4.2, so we have to request a sufficiently high version. We use
+ * 440 to maximise driver compatibility as we don't need anything
+ * from newer versions.
+ */
+ 440,
+ 320,
+ 210,
+ 0
+};
+
+enum {
+ FLUSH_NO = 0,
+ FLUSH_YES,
+ FLUSH_AUTO,
+};
+
+struct opengl_opts {
+ bool use_glfinish;
+ bool waitvsync;
+ int vsync_pattern[2];
+ int swapinterval;
+ int early_flush;
+ int gles_mode;
+};
+
+#define OPT_BASE_STRUCT struct opengl_opts
+const struct m_sub_options opengl_conf = {
+ .opts = (const struct m_option[]) {
+ {"opengl-glfinish", OPT_BOOL(use_glfinish)},
+ {"opengl-waitvsync", OPT_BOOL(waitvsync)},
+ {"opengl-swapinterval", OPT_INT(swapinterval)},
+ {"opengl-check-pattern-a", OPT_INT(vsync_pattern[0])},
+ {"opengl-check-pattern-b", OPT_INT(vsync_pattern[1])},
+ {"opengl-es", OPT_CHOICE(gles_mode,
+ {"auto", GLES_AUTO}, {"yes", GLES_YES}, {"no", GLES_NO})},
+ {"opengl-early-flush", OPT_CHOICE(early_flush,
+ {"no", FLUSH_NO}, {"yes", FLUSH_YES}, {"auto", FLUSH_AUTO})},
+ {0},
+ },
+ .defaults = &(const struct opengl_opts) {
+ .swapinterval = 1,
+ },
+ .size = sizeof(struct opengl_opts),
+};
+
+struct priv {
+ GL *gl;
+ struct mp_log *log;
+ struct ra_gl_ctx_params params;
+ struct opengl_opts *opts;
+ struct ra_swapchain_fns fns;
+ GLuint main_fb;
+ struct ra_tex *wrapped_fb; // corresponds to main_fb
+ // for debugging:
+ int frames_rendered;
+ unsigned int prev_sgi_sync_count;
+ // for gl_vsync_pattern
+ int last_pattern;
+ int matches, mismatches;
+ // for swapchain_depth simulation
+ GLsync *vsync_fences;
+ int num_vsync_fences;
+};
+
+enum gles_mode ra_gl_ctx_get_glesmode(struct ra_ctx *ctx)
+{
+ void *tmp = talloc_new(NULL);
+ struct opengl_opts *opts;
+ enum gles_mode mode;
+
+ opts = mp_get_config_group(tmp, ctx->global, &opengl_conf);
+ mode = opts->gles_mode;
+
+ talloc_free(tmp);
+ return mode;
+}
+
+void ra_gl_ctx_uninit(struct ra_ctx *ctx)
+{
+ if (ctx->swapchain) {
+ struct priv *p = ctx->swapchain->priv;
+ if (ctx->ra && p->wrapped_fb)
+ ra_tex_free(ctx->ra, &p->wrapped_fb);
+ talloc_free(ctx->swapchain);
+ ctx->swapchain = NULL;
+ }
+
+ // Clean up any potentially left-over debug callback
+ if (ctx->ra)
+ ra_gl_set_debug(ctx->ra, false);
+
+ ra_free(&ctx->ra);
+}
+
+static const struct ra_swapchain_fns ra_gl_swapchain_fns;
+
+bool ra_gl_ctx_init(struct ra_ctx *ctx, GL *gl, struct ra_gl_ctx_params params)
+{
+ struct ra_swapchain *sw = ctx->swapchain = talloc_ptrtype(NULL, sw);
+ *sw = (struct ra_swapchain) {
+ .ctx = ctx,
+ };
+
+ struct priv *p = sw->priv = talloc_ptrtype(sw, p);
+ *p = (struct priv) {
+ .gl = gl,
+ .log = ctx->log,
+ .params = params,
+ .opts = mp_get_config_group(p, ctx->global, &opengl_conf),
+ .fns = ra_gl_swapchain_fns,
+ };
+
+ sw->fns = &p->fns;
+
+ const struct ra_swapchain_fns *ext = p->params.external_swapchain;
+ if (ext) {
+ if (ext->color_depth)
+ p->fns.color_depth = ext->color_depth;
+ if (ext->start_frame)
+ p->fns.start_frame = ext->start_frame;
+ if (ext->submit_frame)
+ p->fns.submit_frame = ext->submit_frame;
+ if (ext->swap_buffers)
+ p->fns.swap_buffers = ext->swap_buffers;
+ }
+
+ if (!gl->version && !gl->es)
+ return false;
+
+ if (gl->mpgl_caps & MPGL_CAP_SW) {
+ MP_WARN(p, "Suspected software renderer or indirect context.\n");
+ if (ctx->opts.probing && !ctx->opts.allow_sw)
+ return false;
+ }
+
+ gl->debug_context = ctx->opts.debug;
+
+ if (gl->SwapInterval) {
+ gl->SwapInterval(p->opts->swapinterval);
+ } else {
+ MP_VERBOSE(p, "GL_*_swap_control extension missing.\n");
+ }
+
+ ctx->ra = ra_create_gl(p->gl, ctx->log);
+ return !!ctx->ra;
+}
+
+void ra_gl_ctx_resize(struct ra_swapchain *sw, int w, int h, int fbo)
+{
+ struct priv *p = sw->priv;
+ if (p->main_fb == fbo && p->wrapped_fb && p->wrapped_fb->params.w == w
+ && p->wrapped_fb->params.h == h)
+ return;
+
+ if (p->wrapped_fb)
+ ra_tex_free(sw->ctx->ra, &p->wrapped_fb);
+
+ p->main_fb = fbo;
+ p->wrapped_fb = ra_create_wrapped_fb(sw->ctx->ra, fbo, w, h);
+}
+
+int ra_gl_ctx_color_depth(struct ra_swapchain *sw)
+{
+ struct priv *p = sw->priv;
+ GL *gl = p->gl;
+
+ if (!p->wrapped_fb)
+ return 0;
+
+ if ((gl->es < 300 && !gl->version) || !(gl->mpgl_caps & MPGL_CAP_FB))
+ return 0;
+
+ gl->BindFramebuffer(GL_FRAMEBUFFER, p->main_fb);
+
+ GLenum obj = gl->version ? GL_BACK_LEFT : GL_BACK;
+ if (p->main_fb)
+ obj = GL_COLOR_ATTACHMENT0;
+
+ GLint depth_g = 0;
+
+ gl->GetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, obj,
+ GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE, &depth_g);
+
+ gl->BindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ return depth_g;
+}
+
+bool ra_gl_ctx_start_frame(struct ra_swapchain *sw, struct ra_fbo *out_fbo)
+{
+ struct priv *p = sw->priv;
+
+ bool visible = true;
+ if (p->params.check_visible)
+ visible = p->params.check_visible(sw->ctx);
+
+ // If out_fbo is NULL, this was called from vo_gpu_next. Bail out.
+ if (!out_fbo || !visible)
+ return visible;
+
+ *out_fbo = (struct ra_fbo) {
+ .tex = p->wrapped_fb,
+ .flip = !p->gl->flipped, // OpenGL FBs are normally flipped
+ };
+ return true;
+}
+
+bool ra_gl_ctx_submit_frame(struct ra_swapchain *sw, const struct vo_frame *frame)
+{
+ struct priv *p = sw->priv;
+ GL *gl = p->gl;
+
+ if (p->opts->use_glfinish)
+ gl->Finish();
+
+ if (gl->FenceSync && !p->params.external_swapchain) {
+ GLsync fence = gl->FenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
+ if (fence)
+ MP_TARRAY_APPEND(p, p->vsync_fences, p->num_vsync_fences, fence);
+ }
+
+ switch (p->opts->early_flush) {
+ case FLUSH_AUTO:
+ if (frame->display_synced)
+ break;
+ MP_FALLTHROUGH;
+ case FLUSH_YES:
+ gl->Flush();
+ }
+
+ return true;
+}
+
+static void check_pattern(struct priv *p, int item)
+{
+ int expected = p->opts->vsync_pattern[p->last_pattern];
+ if (item == expected) {
+ p->last_pattern++;
+ if (p->last_pattern >= 2)
+ p->last_pattern = 0;
+ p->matches++;
+ } else {
+ p->mismatches++;
+ MP_WARN(p, "wrong pattern, expected %d got %d (hit: %d, mis: %d)\n",
+ expected, item, p->matches, p->mismatches);
+ }
+}
+
+void ra_gl_ctx_swap_buffers(struct ra_swapchain *sw)
+{
+ struct priv *p = sw->priv;
+ GL *gl = p->gl;
+
+ p->params.swap_buffers(sw->ctx);
+ p->frames_rendered++;
+
+ if (p->frames_rendered > 5 && !sw->ctx->opts.debug)
+ ra_gl_set_debug(sw->ctx->ra, false);
+
+ if ((p->opts->waitvsync || p->opts->vsync_pattern[0])
+ && gl->GetVideoSync)
+ {
+ unsigned int n1 = 0, n2 = 0;
+ gl->GetVideoSync(&n1);
+ if (p->opts->waitvsync)
+ gl->WaitVideoSync(2, (n1 + 1) % 2, &n2);
+ int step = n1 - p->prev_sgi_sync_count;
+ p->prev_sgi_sync_count = n1;
+ MP_DBG(p, "Flip counts: %u->%u, step=%d\n", n1, n2, step);
+ if (p->opts->vsync_pattern[0])
+ check_pattern(p, step);
+ }
+
+ while (p->num_vsync_fences >= sw->ctx->vo->opts->swapchain_depth) {
+ gl->ClientWaitSync(p->vsync_fences[0], GL_SYNC_FLUSH_COMMANDS_BIT, 1e9);
+ gl->DeleteSync(p->vsync_fences[0]);
+ MP_TARRAY_REMOVE_AT(p->vsync_fences, p->num_vsync_fences, 0);
+ }
+}
+
+static void ra_gl_ctx_get_vsync(struct ra_swapchain *sw,
+ struct vo_vsync_info *info)
+{
+ struct priv *p = sw->priv;
+ if (p->params.get_vsync)
+ p->params.get_vsync(sw->ctx, info);
+}
+
+static const struct ra_swapchain_fns ra_gl_swapchain_fns = {
+ .color_depth = ra_gl_ctx_color_depth,
+ .start_frame = ra_gl_ctx_start_frame,
+ .submit_frame = ra_gl_ctx_submit_frame,
+ .swap_buffers = ra_gl_ctx_swap_buffers,
+ .get_vsync = ra_gl_ctx_get_vsync,
+};
diff --git a/video/out/opengl/context.h b/video/out/opengl/context.h
new file mode 100644
index 0000000..c96450e
--- /dev/null
+++ b/video/out/opengl/context.h
@@ -0,0 +1,58 @@
+#pragma once
+
+#include "common/global.h"
+#include "video/out/gpu/context.h"
+#include "common.h"
+
+extern const int mpgl_min_required_gl_versions[];
+
+enum gles_mode {
+ GLES_AUTO = 0,
+ GLES_YES,
+ GLES_NO,
+};
+
+// Returns the gles mode based on the --opengl opts.
+enum gles_mode ra_gl_ctx_get_glesmode(struct ra_ctx *ctx);
+
+// These are a set of helpers for ra_ctx providers based on ra_gl.
+// The init function also initializes ctx->ra and ctx->swapchain, so the user
+// doesn't have to do this manually. (Similarly, the uninit function will
+// clean them up)
+
+struct ra_gl_ctx_params {
+ // For special contexts (i.e. wayland) that want to check visibility
+ // before drawing a frame.
+ bool (*check_visible)(struct ra_ctx *ctx);
+
+ // Set to the platform-specific function to swap buffers, like
+ // glXSwapBuffers, eglSwapBuffers etc. This will be called by
+ // ra_gl_ctx_swap_buffers. Required unless you either never call that
+ // function or if you override it yourself.
+ void (*swap_buffers)(struct ra_ctx *ctx);
+
+ // See ra_swapchain_fns.get_vsync.
+ void (*get_vsync)(struct ra_ctx *ctx, struct vo_vsync_info *info);
+
+ // If this is set to non-NULL, then the ra_gl_ctx will consider the GL
+ // implementation to be using an external swapchain, which disables the
+ // software simulation of --swapchain-depth. Any functions defined by this
+ // ra_swapchain_fns structs will entirely replace the equivalent ra_gl_ctx
+ // functions in the resulting ra_swapchain.
+ const struct ra_swapchain_fns *external_swapchain;
+};
+
+void ra_gl_ctx_uninit(struct ra_ctx *ctx);
+bool ra_gl_ctx_init(struct ra_ctx *ctx, GL *gl, struct ra_gl_ctx_params params);
+
+// Call this any time the window size or main framebuffer changes
+void ra_gl_ctx_resize(struct ra_swapchain *sw, int w, int h, int fbo);
+
+// These functions are normally set in the ra_swapchain->fns, but if an
+// implementation has a need to override this fns struct with custom functions
+// for whatever reason, these can be used to inherit the original behavior.
+int ra_gl_ctx_color_depth(struct ra_swapchain *sw);
+struct mp_image *ra_gl_ctx_screenshot(struct ra_swapchain *sw);
+bool ra_gl_ctx_start_frame(struct ra_swapchain *sw, struct ra_fbo *out_fbo);
+bool ra_gl_ctx_submit_frame(struct ra_swapchain *sw, const struct vo_frame *frame);
+void ra_gl_ctx_swap_buffers(struct ra_swapchain *sw);
diff --git a/video/out/opengl/context_android.c b/video/out/opengl/context_android.c
new file mode 100644
index 0000000..bc1717c
--- /dev/null
+++ b/video/out/opengl/context_android.c
@@ -0,0 +1,130 @@
+/*
+ * 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 <EGL/egl.h>
+#include <EGL/eglext.h>
+
+#include "video/out/android_common.h"
+#include "egl_helpers.h"
+#include "common/common.h"
+#include "context.h"
+
+struct priv {
+ struct GL gl;
+ EGLDisplay egl_display;
+ EGLContext egl_context;
+ EGLSurface egl_surface;
+};
+
+static void android_swap_buffers(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+ eglSwapBuffers(p->egl_display, p->egl_surface);
+}
+
+static void android_uninit(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+ ra_gl_ctx_uninit(ctx);
+
+ if (p->egl_surface) {
+ eglMakeCurrent(p->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ EGL_NO_CONTEXT);
+ eglDestroySurface(p->egl_display, p->egl_surface);
+ }
+ if (p->egl_context)
+ eglDestroyContext(p->egl_display, p->egl_context);
+
+ vo_android_uninit(ctx->vo);
+}
+
+static bool android_init(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
+
+ if (!vo_android_init(ctx->vo))
+ goto fail;
+
+ p->egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ if (!eglInitialize(p->egl_display, NULL, NULL)) {
+ MP_FATAL(ctx, "EGL failed to initialize.\n");
+ goto fail;
+ }
+
+ EGLConfig config;
+ if (!mpegl_create_context(ctx, p->egl_display, &p->egl_context, &config))
+ goto fail;
+
+ ANativeWindow *native_window = vo_android_native_window(ctx->vo);
+ EGLint format;
+ eglGetConfigAttrib(p->egl_display, config, EGL_NATIVE_VISUAL_ID, &format);
+ ANativeWindow_setBuffersGeometry(native_window, 0, 0, format);
+
+ p->egl_surface = eglCreateWindowSurface(p->egl_display, config,
+ (EGLNativeWindowType)native_window, NULL);
+
+ if (p->egl_surface == EGL_NO_SURFACE) {
+ MP_FATAL(ctx, "Could not create EGL surface!\n");
+ goto fail;
+ }
+
+ if (!eglMakeCurrent(p->egl_display, p->egl_surface, p->egl_surface,
+ p->egl_context)) {
+ MP_FATAL(ctx, "Failed to set context!\n");
+ goto fail;
+ }
+
+ mpegl_load_functions(&p->gl, ctx->log);
+
+ struct ra_gl_ctx_params params = {
+ .swap_buffers = android_swap_buffers,
+ };
+
+ if (!ra_gl_ctx_init(ctx, &p->gl, params))
+ goto fail;
+
+ return true;
+fail:
+ android_uninit(ctx);
+ return false;
+}
+
+static bool android_reconfig(struct ra_ctx *ctx)
+{
+ int w, h;
+ if (!vo_android_surface_size(ctx->vo, &w, &h))
+ return false;
+
+ ctx->vo->dwidth = w;
+ ctx->vo->dheight = h;
+ ra_gl_ctx_resize(ctx->swapchain, w, h, 0);
+ return true;
+}
+
+static int android_control(struct ra_ctx *ctx, int *events, int request, void *arg)
+{
+ return VO_NOTIMPL;
+}
+
+const struct ra_ctx_fns ra_ctx_android = {
+ .type = "opengl",
+ .name = "android",
+ .reconfig = android_reconfig,
+ .control = android_control,
+ .init = android_init,
+ .uninit = android_uninit,
+};
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,
+};
diff --git a/video/out/opengl/context_drm_egl.c b/video/out/opengl/context_drm_egl.c
new file mode 100644
index 0000000..2db428f
--- /dev/null
+++ b/video/out/opengl/context_drm_egl.c
@@ -0,0 +1,744 @@
+/*
+ * 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 <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <string.h>
+#include <poll.h>
+#include <unistd.h>
+
+#include <gbm.h>
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <drm_fourcc.h>
+
+#include "libmpv/render_gl.h"
+#include "common/common.h"
+#include "osdep/timer.h"
+#include "video/out/drm_atomic.h"
+#include "video/out/drm_common.h"
+#include "video/out/present_sync.h"
+
+#include "egl_helpers.h"
+#include "common.h"
+#include "context.h"
+
+#ifndef EGL_PLATFORM_GBM_MESA
+#define EGL_PLATFORM_GBM_MESA 0x31D7
+#endif
+
+#ifndef EGL_PLATFORM_GBM_KHR
+#define EGL_PLATFORM_GBM_KHR 0x31D7
+#endif
+
+struct gbm_frame {
+ struct gbm_bo *bo;
+};
+
+struct gbm {
+ struct gbm_surface *surface;
+ struct gbm_device *device;
+ struct gbm_frame **bo_queue;
+ unsigned int num_bos;
+};
+
+struct egl {
+ EGLDisplay display;
+ EGLContext context;
+ EGLSurface surface;
+};
+
+struct priv {
+ GL gl;
+
+ struct egl egl;
+ struct gbm gbm;
+
+ GLsync *vsync_fences;
+ unsigned int num_vsync_fences;
+
+ uint32_t gbm_format;
+ uint64_t *gbm_modifiers;
+ unsigned int num_gbm_modifiers;
+
+ struct mpv_opengl_drm_params_v2 drm_params;
+ struct mpv_opengl_drm_draw_surface_size draw_surface_size;
+};
+
+// Not general. Limited to only the formats being used in this module
+static const char *gbm_format_to_string(uint32_t format)
+{
+ switch (format) {
+ case GBM_FORMAT_XRGB8888:
+ return "GBM_FORMAT_XRGB8888";
+ case GBM_FORMAT_ARGB8888:
+ return "GBM_FORMAT_ARGB8888";
+ case GBM_FORMAT_XBGR8888:
+ return "GBM_FORMAT_XBGR8888";
+ case GBM_FORMAT_ABGR8888:
+ return "GBM_FORMAT_ABGR8888";
+ case GBM_FORMAT_XRGB2101010:
+ return "GBM_FORMAT_XRGB2101010";
+ case GBM_FORMAT_ARGB2101010:
+ return "GBM_FORMAT_ARGB2101010";
+ case GBM_FORMAT_XBGR2101010:
+ return "GBM_FORMAT_XBGR2101010";
+ case GBM_FORMAT_ABGR2101010:
+ return "GBM_FORMAT_ABGR2101010";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+// Allow falling back to an ARGB EGLConfig when we have an XRGB framebuffer.
+// Also allow falling back to an XRGB EGLConfig for ARGB framebuffers, since
+// this seems necessary to work with broken Mali drivers that don't report
+// their EGLConfigs as supporting alpha properly.
+static uint32_t fallback_format_for(uint32_t format)
+{
+ switch (format) {
+ case GBM_FORMAT_XRGB8888:
+ return GBM_FORMAT_ARGB8888;
+ case GBM_FORMAT_ARGB8888:
+ return GBM_FORMAT_XRGB8888;
+ case GBM_FORMAT_XBGR8888:
+ return GBM_FORMAT_ABGR8888;
+ case GBM_FORMAT_ABGR8888:
+ return GBM_FORMAT_XBGR8888;
+ case GBM_FORMAT_XRGB2101010:
+ return GBM_FORMAT_ARGB2101010;
+ case GBM_FORMAT_ARGB2101010:
+ return GBM_FORMAT_XRGB2101010;
+ case GBM_FORMAT_XBGR2101010:
+ return GBM_FORMAT_ABGR2101010;
+ case GBM_FORMAT_ABGR2101010:
+ return GBM_FORMAT_XBGR2101010;
+ default:
+ return 0;
+ }
+}
+
+static int match_config_to_visual(void *user_data, EGLConfig *configs, int num_configs)
+{
+ struct ra_ctx *ctx = (struct ra_ctx*)user_data;
+ struct priv *p = ctx->priv;
+ const EGLint visual_id[] = {
+ (EGLint)p->gbm_format,
+ (EGLint)fallback_format_for(p->gbm_format),
+ 0
+ };
+
+ for (unsigned int i = 0; visual_id[i] != 0; ++i) {
+ MP_VERBOSE(ctx, "Attempting to find EGLConfig matching %s\n",
+ gbm_format_to_string(visual_id[i]));
+ for (unsigned int j = 0; j < num_configs; ++j) {
+ EGLint id;
+
+ if (!eglGetConfigAttrib(p->egl.display, configs[j], EGL_NATIVE_VISUAL_ID, &id))
+ continue;
+
+ if (visual_id[i] == id) {
+ MP_VERBOSE(ctx, "Found matching EGLConfig for %s\n",
+ gbm_format_to_string(visual_id[i]));
+ return j;
+ }
+ }
+ MP_VERBOSE(ctx, "No matching EGLConfig for %s\n", gbm_format_to_string(visual_id[i]));
+ }
+
+ MP_ERR(ctx, "Could not find EGLConfig matching the GBM visual (%s).\n",
+ gbm_format_to_string(p->gbm_format));
+ return -1;
+}
+
+static EGLDisplay egl_get_display(struct gbm_device *gbm_device)
+{
+ EGLDisplay ret;
+
+ ret = mpegl_get_display(EGL_PLATFORM_GBM_MESA, "EGL_MESA_platform_gbm", gbm_device);
+ if (ret != EGL_NO_DISPLAY)
+ return ret;
+
+ ret = mpegl_get_display(EGL_PLATFORM_GBM_KHR, "EGL_KHR_platform_gbm", gbm_device);
+ if (ret != EGL_NO_DISPLAY)
+ return ret;
+
+ return eglGetDisplay(gbm_device);
+}
+
+static bool init_egl(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+ MP_VERBOSE(ctx, "Initializing EGL\n");
+ p->egl.display = egl_get_display(p->gbm.device);
+
+ if (p->egl.display == EGL_NO_DISPLAY) {
+ MP_ERR(ctx, "Failed to get EGL display.\n");
+ return false;
+ }
+ if (!eglInitialize(p->egl.display, NULL, NULL)) {
+ MP_ERR(ctx, "Failed to initialize EGL.\n");
+ return false;
+ }
+ EGLConfig config;
+ if (!mpegl_create_context_cb(ctx,
+ p->egl.display,
+ (struct mpegl_cb){match_config_to_visual, ctx},
+ &p->egl.context,
+ &config))
+ return false;
+
+ MP_VERBOSE(ctx, "Initializing EGL surface\n");
+ p->egl.surface = mpegl_create_window_surface(
+ p->egl.display, config, p->gbm.surface);
+ if (p->egl.surface == EGL_NO_SURFACE) {
+ p->egl.surface = eglCreateWindowSurface(
+ p->egl.display, config, p->gbm.surface, NULL);
+ }
+ if (p->egl.surface == EGL_NO_SURFACE) {
+ MP_ERR(ctx, "Failed to create EGL surface.\n");
+ return false;
+ }
+ return true;
+}
+
+static bool init_gbm(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+ struct vo_drm_state *drm = ctx->vo->drm;
+ MP_VERBOSE(ctx->vo, "Creating GBM device\n");
+ p->gbm.device = gbm_create_device(drm->fd);
+ if (!p->gbm.device) {
+ MP_ERR(ctx->vo, "Failed to create GBM device.\n");
+ return false;
+ }
+
+ MP_VERBOSE(ctx->vo, "Initializing GBM surface (%d x %d)\n",
+ p->draw_surface_size.width, p->draw_surface_size.height);
+ if (p->num_gbm_modifiers == 0) {
+ p->gbm.surface = gbm_surface_create(
+ p->gbm.device,
+ p->draw_surface_size.width,
+ p->draw_surface_size.height,
+ p->gbm_format,
+ GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING);
+ } else {
+ p->gbm.surface = gbm_surface_create_with_modifiers(
+ p->gbm.device,
+ p->draw_surface_size.width,
+ p->draw_surface_size.height,
+ p->gbm_format,
+ p->gbm_modifiers,
+ p->num_gbm_modifiers);
+ }
+ if (!p->gbm.surface) {
+ MP_ERR(ctx->vo, "Failed to create GBM surface.\n");
+ return false;
+ }
+ return true;
+}
+
+static void framebuffer_destroy_callback(struct gbm_bo *bo, void *data)
+{
+ struct framebuffer *fb = data;
+ if (fb) {
+ drmModeRmFB(fb->fd, fb->id);
+ }
+}
+
+static void update_framebuffer_from_bo(struct ra_ctx *ctx, struct gbm_bo *bo)
+{
+ struct priv *p = ctx->priv;
+ struct vo_drm_state *drm = ctx->vo->drm;
+ struct framebuffer *fb = gbm_bo_get_user_data(bo);
+ if (fb) {
+ drm->fb = fb;
+ return;
+ }
+
+ fb = talloc_zero(ctx, struct framebuffer);
+ fb->fd = drm->fd;
+ fb->width = gbm_bo_get_width(bo);
+ fb->height = gbm_bo_get_height(bo);
+ uint64_t modifier = gbm_bo_get_modifier(bo);
+
+ int ret;
+ if (p->num_gbm_modifiers == 0 || modifier == DRM_FORMAT_MOD_INVALID) {
+ uint32_t stride = gbm_bo_get_stride(bo);
+ uint32_t handle = gbm_bo_get_handle(bo).u32;
+ ret = drmModeAddFB2(fb->fd, fb->width, fb->height,
+ p->gbm_format,
+ (uint32_t[4]){handle, 0, 0, 0},
+ (uint32_t[4]){stride, 0, 0, 0},
+ (uint32_t[4]){0, 0, 0, 0},
+ &fb->id, 0);
+ } else {
+ MP_VERBOSE(ctx, "GBM surface using modifier 0x%"PRIX64"\n", modifier);
+
+ uint32_t handles[4] = {0};
+ uint32_t strides[4] = {0};
+ uint32_t offsets[4] = {0};
+ uint64_t modifiers[4] = {0};
+
+ const int num_planes = gbm_bo_get_plane_count(bo);
+ for (int i = 0; i < num_planes; ++i) {
+ handles[i] = gbm_bo_get_handle_for_plane(bo, i).u32;
+ strides[i] = gbm_bo_get_stride_for_plane(bo, i);
+ offsets[i] = gbm_bo_get_offset(bo, i);
+ modifiers[i] = modifier;
+ }
+
+ ret = drmModeAddFB2WithModifiers(fb->fd, fb->width, fb->height,
+ p->gbm_format,
+ handles, strides, offsets, modifiers,
+ &fb->id, DRM_MODE_FB_MODIFIERS);
+ }
+ if (ret) {
+ MP_ERR(ctx->vo, "Failed to create framebuffer: %s\n", mp_strerror(errno));
+ }
+ gbm_bo_set_user_data(bo, fb, framebuffer_destroy_callback);
+ drm->fb = fb;
+}
+
+static void queue_flip(struct ra_ctx *ctx, struct gbm_frame *frame)
+{
+ struct vo_drm_state *drm = ctx->vo->drm;
+
+ update_framebuffer_from_bo(ctx, frame->bo);
+
+ struct drm_atomic_context *atomic_ctx = drm->atomic_context;
+ drm_object_set_property(atomic_ctx->request, atomic_ctx->draw_plane, "FB_ID", drm->fb->id);
+ drm_object_set_property(atomic_ctx->request, atomic_ctx->draw_plane, "CRTC_ID", atomic_ctx->crtc->id);
+ drm_object_set_property(atomic_ctx->request, atomic_ctx->draw_plane, "ZPOS", 1);
+
+ int ret = drmModeAtomicCommit(drm->fd, atomic_ctx->request,
+ DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT, drm);
+
+ if (ret)
+ MP_WARN(ctx->vo, "Failed to commit atomic request: %s\n", mp_strerror(ret));
+ drm->waiting_for_flip = !ret;
+
+ drmModeAtomicFree(atomic_ctx->request);
+ atomic_ctx->request = drmModeAtomicAlloc();
+}
+
+static void enqueue_bo(struct ra_ctx *ctx, struct gbm_bo *bo)
+{
+ struct priv *p = ctx->priv;
+
+ struct gbm_frame *new_frame = talloc(p, struct gbm_frame);
+ new_frame->bo = bo;
+ MP_TARRAY_APPEND(p, p->gbm.bo_queue, p->gbm.num_bos, new_frame);
+}
+
+static void dequeue_bo(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+
+ talloc_free(p->gbm.bo_queue[0]);
+ MP_TARRAY_REMOVE_AT(p->gbm.bo_queue, p->gbm.num_bos, 0);
+}
+
+static void swapchain_step(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+
+ if (!(p->gbm.num_bos > 0))
+ return;
+
+ if (p->gbm.bo_queue[0]->bo)
+ gbm_surface_release_buffer(p->gbm.surface, p->gbm.bo_queue[0]->bo);
+ dequeue_bo(ctx);
+}
+
+static void new_fence(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+
+ if (p->gl.FenceSync) {
+ GLsync fence = p->gl.FenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
+ if (fence)
+ MP_TARRAY_APPEND(p, p->vsync_fences, p->num_vsync_fences, fence);
+ }
+}
+
+static void wait_fence(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+
+ while (p->num_vsync_fences && (p->num_vsync_fences >= p->gbm.num_bos)) {
+ p->gl.ClientWaitSync(p->vsync_fences[0], GL_SYNC_FLUSH_COMMANDS_BIT, 1e9);
+ p->gl.DeleteSync(p->vsync_fences[0]);
+ MP_TARRAY_REMOVE_AT(p->vsync_fences, p->num_vsync_fences, 0);
+ }
+}
+
+static bool drm_egl_start_frame(struct ra_swapchain *sw, struct ra_fbo *out_fbo)
+{
+ struct ra_ctx *ctx = sw->ctx;
+ struct priv *p = ctx->priv;
+ struct vo_drm_state *drm = ctx->vo->drm;
+
+ if (!drm->atomic_context->request) {
+ drm->atomic_context->request = drmModeAtomicAlloc();
+ p->drm_params.atomic_request_ptr = &drm->atomic_context->request;
+ }
+
+ return ra_gl_ctx_start_frame(sw, out_fbo);
+}
+
+static bool drm_egl_submit_frame(struct ra_swapchain *sw, const struct vo_frame *frame)
+{
+ struct ra_ctx *ctx = sw->ctx;
+ struct vo_drm_state *drm = ctx->vo->drm;
+
+ drm->still = frame->still;
+
+ return ra_gl_ctx_submit_frame(sw, frame);
+}
+
+static void drm_egl_swap_buffers(struct ra_swapchain *sw)
+{
+ struct ra_ctx *ctx = sw->ctx;
+ struct priv *p = ctx->priv;
+ struct vo_drm_state *drm = ctx->vo->drm;
+ const bool drain = drm->paused || drm->still; // True when we need to drain the swapchain
+
+ if (!drm->active)
+ return;
+
+ wait_fence(ctx);
+
+ eglSwapBuffers(p->egl.display, p->egl.surface);
+
+ struct gbm_bo *new_bo = gbm_surface_lock_front_buffer(p->gbm.surface);
+ if (!new_bo) {
+ MP_ERR(ctx->vo, "Couldn't lock front buffer\n");
+ return;
+ }
+ enqueue_bo(ctx, new_bo);
+ new_fence(ctx);
+
+ while (drain || p->gbm.num_bos > ctx->vo->opts->swapchain_depth ||
+ !gbm_surface_has_free_buffers(p->gbm.surface)) {
+ if (drm->waiting_for_flip) {
+ vo_drm_wait_on_flip(drm);
+ swapchain_step(ctx);
+ }
+ if (p->gbm.num_bos <= 1)
+ break;
+ if (!p->gbm.bo_queue[1] || !p->gbm.bo_queue[1]->bo) {
+ MP_ERR(ctx->vo, "Hole in swapchain?\n");
+ swapchain_step(ctx);
+ continue;
+ }
+ queue_flip(ctx, p->gbm.bo_queue[1]);
+ }
+}
+
+static const struct ra_swapchain_fns drm_egl_swapchain = {
+ .start_frame = drm_egl_start_frame,
+ .submit_frame = drm_egl_submit_frame,
+ .swap_buffers = drm_egl_swap_buffers,
+};
+
+static void drm_egl_uninit(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+ struct vo_drm_state *drm = ctx->vo->drm;
+ if (drm) {
+ struct drm_atomic_context *atomic_ctx = drm->atomic_context;
+
+ if (drmModeAtomicCommit(drm->fd, atomic_ctx->request, 0, NULL))
+ MP_ERR(ctx->vo, "Failed to commit atomic request: %s\n",
+ mp_strerror(errno));
+
+ drmModeAtomicFree(atomic_ctx->request);
+ }
+
+ ra_gl_ctx_uninit(ctx);
+ vo_drm_uninit(ctx->vo);
+
+ if (p) {
+ // According to GBM documentation all BO:s must be released
+ // before gbm_surface_destroy can be called on the surface.
+ while (p->gbm.num_bos) {
+ swapchain_step(ctx);
+ }
+
+ eglMakeCurrent(p->egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ EGL_NO_CONTEXT);
+ if (p->egl.display != EGL_NO_DISPLAY) {
+ eglDestroySurface(p->egl.display, p->egl.surface);
+ eglDestroyContext(p->egl.display, p->egl.context);
+ }
+ if (p->gbm.surface)
+ gbm_surface_destroy(p->gbm.surface);
+ eglTerminate(p->egl.display);
+ gbm_device_destroy(p->gbm.device);
+
+ if (p->drm_params.render_fd != -1)
+ close(p->drm_params.render_fd);
+ }
+}
+
+// If the draw plane supports ARGB we want to use that, but if it doesn't we
+// fall back on XRGB. If we do not have atomic there is no particular reason to
+// be using ARGB (drmprime hwdec will not work without atomic, anyway), so we
+// fall back to XRGB (another reason is that we do not have the convenient
+// atomic_ctx and its convenient plane fields).
+static bool probe_gbm_format(struct ra_ctx *ctx, uint32_t argb_format, uint32_t xrgb_format)
+{
+ struct priv *p = ctx->priv;
+ struct vo_drm_state *drm = ctx->vo->drm;
+
+ drmModePlane *drmplane = drmModeGetPlane(drm->fd, drm->atomic_context->draw_plane->id);
+ bool have_argb = false;
+ bool have_xrgb = false;
+ bool result = false;
+ for (unsigned int i = 0; i < drmplane->count_formats; ++i) {
+ if (drmplane->formats[i] == argb_format) {
+ have_argb = true;
+ } else if (drmplane->formats[i] == xrgb_format) {
+ have_xrgb = true;
+ }
+ }
+
+ if (have_argb) {
+ p->gbm_format = argb_format;
+ MP_VERBOSE(ctx->vo, "%s supported by draw plane.\n", gbm_format_to_string(argb_format));
+ result = true;
+ } else if (have_xrgb) {
+ p->gbm_format = xrgb_format;
+ MP_VERBOSE(ctx->vo, "%s not supported by draw plane: Falling back to %s.\n",
+ gbm_format_to_string(argb_format), gbm_format_to_string(xrgb_format));
+ result = true;
+ }
+
+ drmModeFreePlane(drmplane);
+ return result;
+}
+
+static bool probe_gbm_modifiers(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+ struct vo_drm_state *drm = ctx->vo->drm;
+
+ drmModePropertyBlobPtr blob = drm_object_get_property_blob(drm->atomic_context->draw_plane,
+ "IN_FORMATS");
+ if (!blob) {
+ MP_VERBOSE(ctx->vo, "Failed to find IN_FORMATS property\n");
+ return false;
+ }
+
+ struct drm_format_modifier_blob *data = blob->data;
+ uint32_t *fmts = (uint32_t *)((char *)data + data->formats_offset);
+ struct drm_format_modifier *mods =
+ (struct drm_format_modifier *)((char *)data + data->modifiers_offset);
+
+ for (unsigned int j = 0; j < data->count_modifiers; ++j) {
+ struct drm_format_modifier *mod = &mods[j];
+ for (uint64_t k = 0; k < 64; ++k) {
+ if (mod->formats & (1ull << k)) {
+ uint32_t fmt = fmts[k + mod->offset];
+ if (fmt == p->gbm_format) {
+ MP_TARRAY_APPEND(p, p->gbm_modifiers,
+ p->num_gbm_modifiers, mod->modifier);
+ MP_VERBOSE(ctx->vo, "Supported modifier: 0x%"PRIX64"\n",
+ (uint64_t)mod->modifier);
+ break;
+ }
+ }
+ }
+ }
+ drmModeFreePropertyBlob(blob);
+
+ if (p->num_gbm_modifiers == 0) {
+ MP_VERBOSE(ctx->vo, "No supported DRM modifiers found.\n");
+ }
+ return true;
+}
+
+static void drm_egl_get_vsync(struct ra_ctx *ctx, struct vo_vsync_info *info)
+{
+ struct vo_drm_state *drm = ctx->vo->drm;
+ present_sync_get_info(drm->present, info);
+}
+
+static bool drm_egl_init(struct ra_ctx *ctx)
+{
+ if (!vo_drm_init(ctx->vo))
+ goto err;
+
+ struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
+ struct vo_drm_state *drm = ctx->vo->drm;
+
+ if (ctx->vo->drm->opts->draw_surface_size.wh_valid) {
+ p->draw_surface_size.width = ctx->vo->drm->opts->draw_surface_size.w;
+ p->draw_surface_size.height = ctx->vo->drm->opts->draw_surface_size.h;
+ } else {
+ p->draw_surface_size.width = drm->mode.mode.hdisplay;
+ p->draw_surface_size.height = drm->mode.mode.vdisplay;
+ }
+
+ drm->width = p->draw_surface_size.width;
+ drm->height = p->draw_surface_size.height;
+
+ uint32_t argb_format;
+ uint32_t xrgb_format;
+ switch (ctx->vo->drm->opts->drm_format) {
+ case DRM_OPTS_FORMAT_XRGB2101010:
+ argb_format = GBM_FORMAT_ARGB2101010;
+ xrgb_format = GBM_FORMAT_XRGB2101010;
+ break;
+ case DRM_OPTS_FORMAT_XBGR2101010:
+ argb_format = GBM_FORMAT_ABGR2101010;
+ xrgb_format = GBM_FORMAT_XBGR2101010;
+ break;
+ case DRM_OPTS_FORMAT_XBGR8888:
+ argb_format = GBM_FORMAT_ABGR8888;
+ xrgb_format = GBM_FORMAT_XBGR8888;
+ break;
+ default:
+ argb_format = GBM_FORMAT_ARGB8888;
+ xrgb_format = GBM_FORMAT_XRGB8888;
+ break;
+ }
+
+ if (!probe_gbm_format(ctx, argb_format, xrgb_format)) {
+ MP_ERR(ctx->vo, "No suitable format found on draw plane (tried: %s and %s).\n",
+ gbm_format_to_string(argb_format), gbm_format_to_string(xrgb_format));
+ goto err;
+ }
+
+ // It is not fatal if this fails. We'll just try without modifiers.
+ probe_gbm_modifiers(ctx);
+
+ if (!init_gbm(ctx)) {
+ MP_ERR(ctx->vo, "Failed to setup GBM.\n");
+ goto err;
+ }
+
+ if (!init_egl(ctx)) {
+ MP_ERR(ctx->vo, "Failed to setup EGL.\n");
+ goto err;
+ }
+
+ if (!eglMakeCurrent(p->egl.display, p->egl.surface, p->egl.surface,
+ p->egl.context)) {
+ MP_ERR(ctx->vo, "Failed to make context current.\n");
+ goto err;
+ }
+
+ mpegl_load_functions(&p->gl, ctx->vo->log);
+ // required by gbm_surface_lock_front_buffer
+ eglSwapBuffers(p->egl.display, p->egl.surface);
+
+ MP_VERBOSE(ctx, "Preparing framebuffer\n");
+ struct gbm_bo *new_bo = gbm_surface_lock_front_buffer(p->gbm.surface);
+ if (!new_bo) {
+ MP_ERR(ctx, "Failed to lock GBM surface.\n");
+ goto err;
+ }
+
+ enqueue_bo(ctx, new_bo);
+ update_framebuffer_from_bo(ctx, new_bo);
+ if (!drm->fb || !drm->fb->id) {
+ MP_ERR(ctx, "Failed to create framebuffer.\n");
+ goto err;
+ }
+
+ if (!vo_drm_acquire_crtc(ctx->vo->drm)) {
+ MP_ERR(ctx, "Failed to set CRTC for connector %u: %s\n",
+ drm->connector->connector_id, mp_strerror(errno));
+ goto err;
+ }
+
+ vo_drm_set_monitor_par(ctx->vo);
+
+ p->drm_params.fd = drm->fd;
+ p->drm_params.crtc_id = drm->crtc_id;
+ p->drm_params.connector_id = drm->connector->connector_id;
+ p->drm_params.atomic_request_ptr = &drm->atomic_context->request;
+ char *rendernode_path = drmGetRenderDeviceNameFromFd(drm->fd);
+ if (rendernode_path) {
+ MP_VERBOSE(ctx, "Opening render node \"%s\"\n", rendernode_path);
+ p->drm_params.render_fd = open(rendernode_path, O_RDWR | O_CLOEXEC);
+ if (p->drm_params.render_fd == -1) {
+ MP_WARN(ctx, "Cannot open render node: %s\n", mp_strerror(errno));
+ }
+ free(rendernode_path);
+ } else {
+ p->drm_params.render_fd = -1;
+ MP_VERBOSE(ctx, "Could not find path to render node.\n");
+ }
+
+ struct ra_gl_ctx_params params = {
+ .external_swapchain = &drm_egl_swapchain,
+ .get_vsync = &drm_egl_get_vsync,
+ };
+ if (!ra_gl_ctx_init(ctx, &p->gl, params))
+ goto err;
+
+ ra_add_native_resource(ctx->ra, "drm_params_v2", &p->drm_params);
+ ra_add_native_resource(ctx->ra, "drm_draw_surface_size", &p->draw_surface_size);
+
+ return true;
+
+err:
+ drm_egl_uninit(ctx);
+ return false;
+}
+
+static bool drm_egl_reconfig(struct ra_ctx *ctx)
+{
+ struct vo_drm_state *drm = ctx->vo->drm;
+ ctx->vo->dwidth = drm->fb->width;
+ ctx->vo->dheight = drm->fb->height;
+ ra_gl_ctx_resize(ctx->swapchain, drm->fb->width, drm->fb->height, 0);
+ return true;
+}
+
+static int drm_egl_control(struct ra_ctx *ctx, int *events, int request,
+ void *arg)
+{
+ int ret = vo_drm_control(ctx->vo, events, request, arg);
+ return ret;
+}
+
+static void drm_egl_wait_events(struct ra_ctx *ctx, int64_t until_time_ns)
+{
+ vo_drm_wait_events(ctx->vo, until_time_ns);
+}
+
+static void drm_egl_wakeup(struct ra_ctx *ctx)
+{
+ vo_drm_wakeup(ctx->vo);
+}
+
+const struct ra_ctx_fns ra_ctx_drm_egl = {
+ .type = "opengl",
+ .name = "drm",
+ .reconfig = drm_egl_reconfig,
+ .control = drm_egl_control,
+ .init = drm_egl_init,
+ .uninit = drm_egl_uninit,
+ .wait_events = drm_egl_wait_events,
+ .wakeup = drm_egl_wakeup,
+};
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,
+};
diff --git a/video/out/opengl/context_glx.c b/video/out/opengl/context_glx.c
new file mode 100644
index 0000000..4062224
--- /dev/null
+++ b/video/out/opengl/context_glx.c
@@ -0,0 +1,351 @@
+/*
+ * 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 <X11/Xlib.h>
+#include <GL/glx.h>
+
+// FreeBSD 10.0-CURRENT lacks the GLX_ARB_create_context extension completely
+#ifndef GLX_CONTEXT_MAJOR_VERSION_ARB
+#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091
+#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092
+#define GLX_CONTEXT_FLAGS_ARB 0x2094
+#define GLX_CONTEXT_PROFILE_MASK_ARB 0x9126
+#ifndef __APPLE__
+// These are respectively 0x00000001 and 0x00000002 on OSX
+#define GLX_CONTEXT_DEBUG_BIT_ARB 0x0001
+#define GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x0002
+#endif
+#define GLX_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001
+#define GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002
+#endif
+// GLX_EXT_create_context_es2_profile
+#ifndef GLX_CONTEXT_ES2_PROFILE_BIT_EXT
+#define GLX_CONTEXT_ES2_PROFILE_BIT_EXT 0x00000004
+#endif
+
+#include "osdep/timer.h"
+#include "video/out/present_sync.h"
+#include "video/out/x11_common.h"
+#include "context.h"
+#include "utils.h"
+
+struct priv {
+ GL gl;
+ XVisualInfo *vinfo;
+ GLXContext context;
+ GLXFBConfig fbc;
+};
+
+static void glx_uninit(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+ ra_gl_ctx_uninit(ctx);
+
+ if (p->vinfo)
+ XFree(p->vinfo);
+ if (p->context) {
+ Display *display = ctx->vo->x11->display;
+ glXMakeCurrent(display, None, NULL);
+ glXDestroyContext(display, p->context);
+ }
+
+ vo_x11_uninit(ctx->vo);
+}
+
+typedef GLXContext (*glXCreateContextAttribsARBProc)
+ (Display*, GLXFBConfig, GLXContext, Bool, const int*);
+
+static bool create_context_x11(struct ra_ctx *ctx, GL *gl, bool es)
+{
+ struct priv *p = ctx->priv;
+ struct vo *vo = ctx->vo;
+
+ glXCreateContextAttribsARBProc glXCreateContextAttribsARB =
+ (glXCreateContextAttribsARBProc)
+ glXGetProcAddressARB((const GLubyte *)"glXCreateContextAttribsARB");
+
+ const char *glxstr =
+ glXQueryExtensionsString(vo->x11->display, vo->x11->screen);
+ if (!glxstr) {
+ MP_ERR(ctx, "GLX did not advertise any extensions\n");
+ return false;
+ }
+
+ if (!gl_check_extension(glxstr, "GLX_ARB_create_context_profile") ||
+ !glXCreateContextAttribsARB) {
+ MP_ERR(ctx, "GLX does not support GLX_ARB_create_context_profile\n");
+ return false;
+ }
+
+ int ctx_flags = ctx->opts.debug ? GLX_CONTEXT_DEBUG_BIT_ARB : 0;
+ int profile_mask = GLX_CONTEXT_CORE_PROFILE_BIT_ARB;
+
+ if (es) {
+ profile_mask = GLX_CONTEXT_ES2_PROFILE_BIT_EXT;
+ if (!gl_check_extension(glxstr, "GLX_EXT_create_context_es2_profile"))
+ return false;
+ }
+
+ int context_attribs[] = {
+ GLX_CONTEXT_MAJOR_VERSION_ARB, 0,
+ GLX_CONTEXT_MINOR_VERSION_ARB, 0,
+ GLX_CONTEXT_PROFILE_MASK_ARB, profile_mask,
+ GLX_CONTEXT_FLAGS_ARB, ctx_flags,
+ None
+ };
+
+ GLXContext context;
+
+ if (!es) {
+ for (int n = 0; mpgl_min_required_gl_versions[n]; n++) {
+ int version = mpgl_min_required_gl_versions[n];
+ MP_VERBOSE(ctx, "Creating OpenGL %d.%d context...\n",
+ MPGL_VER_P(version));
+
+ context_attribs[1] = MPGL_VER_GET_MAJOR(version);
+ context_attribs[3] = MPGL_VER_GET_MINOR(version);
+
+ vo_x11_silence_xlib(1);
+ context = glXCreateContextAttribsARB(vo->x11->display,
+ p->fbc, 0, True,
+ context_attribs);
+ vo_x11_silence_xlib(-1);
+
+ if (context)
+ break;
+ }
+ } else {
+ context_attribs[1] = 2;
+
+ vo_x11_silence_xlib(1);
+ context = glXCreateContextAttribsARB(vo->x11->display,
+ p->fbc, 0, True,
+ context_attribs);
+ vo_x11_silence_xlib(-1);
+ }
+
+ if (!context)
+ return false;
+
+ // set context
+ if (!glXMakeCurrent(vo->x11->display, vo->x11->window, context)) {
+ MP_FATAL(vo, "Could not set GLX context!\n");
+ glXDestroyContext(vo->x11->display, context);
+ return false;
+ }
+
+ p->context = context;
+
+ mpgl_load_functions(gl, (void *)glXGetProcAddressARB, glxstr, vo->log);
+ return true;
+}
+
+// The GL3/FBC initialization code roughly follows/copies from:
+// http://www.opengl.org/wiki/Tutorial:_OpenGL_3.0_Context_Creation_(GLX)
+// but also uses some of the old code.
+
+static GLXFBConfig select_fb_config(struct vo *vo, const int *attribs, bool alpha)
+{
+ int fbcount;
+ GLXFBConfig *fbc = glXChooseFBConfig(vo->x11->display, vo->x11->screen,
+ attribs, &fbcount);
+ if (!fbc)
+ return NULL;
+
+ // The list in fbc is sorted (so that the first element is the best).
+ GLXFBConfig fbconfig = fbcount > 0 ? fbc[0] : NULL;
+
+ if (alpha) {
+ for (int n = 0; n < fbcount; n++) {
+ XVisualInfo *v = glXGetVisualFromFBConfig(vo->x11->display, fbc[n]);
+ if (v) {
+ bool is_rgba = vo_x11_is_rgba_visual(v);
+ XFree(v);
+ if (is_rgba) {
+ fbconfig = fbc[n];
+ break;
+ }
+ }
+ }
+ }
+
+ XFree(fbc);
+
+ return fbconfig;
+}
+
+static void set_glx_attrib(int *attribs, int name, int value)
+{
+ for (int n = 0; attribs[n * 2 + 0] != None; n++) {
+ if (attribs[n * 2 + 0] == name) {
+ attribs[n * 2 + 1] = value;
+ break;
+ }
+ }
+}
+
+static bool glx_check_visible(struct ra_ctx *ctx)
+{
+ return vo_x11_check_visible(ctx->vo);
+}
+
+static void glx_swap_buffers(struct ra_ctx *ctx)
+{
+ glXSwapBuffers(ctx->vo->x11->display, ctx->vo->x11->window);
+ if (ctx->vo->x11->use_present)
+ present_sync_swap(ctx->vo->x11->present);
+}
+
+static void glx_get_vsync(struct ra_ctx *ctx, struct vo_vsync_info *info)
+{
+ struct vo_x11_state *x11 = ctx->vo->x11;
+ if (ctx->vo->x11->use_present)
+ present_sync_get_info(x11->present, info);
+}
+
+static bool glx_init(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
+ struct vo *vo = ctx->vo;
+ GL *gl = &p->gl;
+
+ if (!vo_x11_init(ctx->vo))
+ goto uninit;
+
+ int glx_major, glx_minor;
+
+ if (!glXQueryVersion(vo->x11->display, &glx_major, &glx_minor)) {
+ MP_ERR(ctx, "GLX not found.\n");
+ goto uninit;
+ }
+ // FBConfigs were added in GLX version 1.3.
+ if (MPGL_VER(glx_major, glx_minor) < MPGL_VER(1, 3)) {
+ MP_ERR(ctx, "GLX version older than 1.3.\n");
+ goto uninit;
+ }
+
+ int glx_attribs[] = {
+ GLX_X_RENDERABLE, True,
+ GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
+ GLX_RED_SIZE, 1,
+ GLX_GREEN_SIZE, 1,
+ GLX_BLUE_SIZE, 1,
+ GLX_ALPHA_SIZE, 0,
+ GLX_DOUBLEBUFFER, True,
+ None
+ };
+ GLXFBConfig fbc = NULL;
+ if (ctx->opts.want_alpha) {
+ set_glx_attrib(glx_attribs, GLX_ALPHA_SIZE, 1);
+ fbc = select_fb_config(vo, glx_attribs, true);
+ if (!fbc)
+ set_glx_attrib(glx_attribs, GLX_ALPHA_SIZE, 0);
+ }
+ if (!fbc)
+ fbc = select_fb_config(vo, glx_attribs, false);
+ if (!fbc) {
+ MP_ERR(ctx, "no GLX support present\n");
+ goto uninit;
+ }
+
+ int fbid = -1;
+ if (!glXGetFBConfigAttrib(vo->x11->display, fbc, GLX_FBCONFIG_ID, &fbid))
+ MP_VERBOSE(ctx, "GLX chose FB config with ID 0x%x\n", fbid);
+
+ p->fbc = fbc;
+ p->vinfo = glXGetVisualFromFBConfig(vo->x11->display, fbc);
+ if (p->vinfo) {
+ MP_VERBOSE(ctx, "GLX chose visual with ID 0x%x\n",
+ (int)p->vinfo->visualid);
+ } else {
+ MP_WARN(ctx, "Selected GLX FB config has no associated X visual\n");
+ }
+
+ if (!vo_x11_create_vo_window(vo, p->vinfo, "gl"))
+ goto uninit;
+
+ bool success = false;
+ enum gles_mode mode = ra_gl_ctx_get_glesmode(ctx);
+
+ if (mode == GLES_NO || mode == GLES_AUTO)
+ success = create_context_x11(ctx, gl, false);
+ if (!success && (mode == GLES_YES || mode == GLES_AUTO))
+ success = create_context_x11(ctx, gl, true);
+ if (success && !glXIsDirect(vo->x11->display, p->context))
+ gl->mpgl_caps |= MPGL_CAP_SW;
+ if (!success)
+ goto uninit;
+
+ struct ra_gl_ctx_params params = {
+ .check_visible = glx_check_visible,
+ .swap_buffers = glx_swap_buffers,
+ .get_vsync = glx_get_vsync,
+ };
+
+ if (!ra_gl_ctx_init(ctx, gl, params))
+ goto uninit;
+
+ ra_add_native_resource(ctx->ra, "x11", vo->x11->display);
+
+ return true;
+
+uninit:
+ glx_uninit(ctx);
+ return false;
+}
+
+
+static void resize(struct ra_ctx *ctx)
+{
+ ra_gl_ctx_resize(ctx->swapchain, ctx->vo->dwidth, ctx->vo->dheight, 0);
+}
+
+static bool glx_reconfig(struct ra_ctx *ctx)
+{
+ vo_x11_config_vo_window(ctx->vo);
+ resize(ctx);
+ return true;
+}
+
+static int glx_control(struct ra_ctx *ctx, int *events, int request, void *arg)
+{
+ int ret = vo_x11_control(ctx->vo, events, request, arg);
+ if (*events & VO_EVENT_RESIZE)
+ resize(ctx);
+ return ret;
+}
+
+static void glx_wakeup(struct ra_ctx *ctx)
+{
+ vo_x11_wakeup(ctx->vo);
+}
+
+static void glx_wait_events(struct ra_ctx *ctx, int64_t until_time_ns)
+{
+ vo_x11_wait_events(ctx->vo, until_time_ns);
+}
+
+const struct ra_ctx_fns ra_ctx_glx = {
+ .type = "opengl",
+ .name = "x11",
+ .reconfig = glx_reconfig,
+ .control = glx_control,
+ .wakeup = glx_wakeup,
+ .wait_events = glx_wait_events,
+ .init = glx_init,
+ .uninit = glx_uninit,
+};
diff --git a/video/out/opengl/context_rpi.c b/video/out/opengl/context_rpi.c
new file mode 100644
index 0000000..0b6babb
--- /dev/null
+++ b/video/out/opengl/context_rpi.c
@@ -0,0 +1,327 @@
+/*
+ * 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 <assert.h>
+#include <stdatomic.h>
+#include <stddef.h>
+
+#include <bcm_host.h>
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
+#include "common/common.h"
+#include "video/out/win_state.h"
+#include "context.h"
+#include "egl_helpers.h"
+
+struct priv {
+ struct GL gl;
+ DISPMANX_DISPLAY_HANDLE_T display;
+ DISPMANX_ELEMENT_HANDLE_T window;
+ DISPMANX_UPDATE_HANDLE_T update;
+ EGLDisplay egl_display;
+ EGLConfig egl_config;
+ EGLContext egl_context;
+ EGLSurface egl_surface;
+ // yep, the API keeps a pointer to it
+ EGL_DISPMANX_WINDOW_T egl_window;
+ int x, y, w, h;
+ double display_fps;
+ atomic_int reload_display;
+ int win_params[4];
+};
+
+static void tv_callback(void *callback_data, uint32_t reason, uint32_t param1,
+ uint32_t param2)
+{
+ struct ra_ctx *ctx = callback_data;
+ struct priv *p = ctx->priv;
+ atomic_store(&p->reload_display, true);
+ vo_wakeup(ctx->vo);
+}
+
+static void destroy_dispmanx(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+
+ if (p->egl_surface) {
+ eglMakeCurrent(p->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ EGL_NO_CONTEXT);
+ eglDestroySurface(p->egl_display, p->egl_surface);
+ p->egl_surface = EGL_NO_SURFACE;
+ }
+
+ if (p->window)
+ vc_dispmanx_element_remove(p->update, p->window);
+ p->window = 0;
+ if (p->display)
+ vc_dispmanx_display_close(p->display);
+ p->display = 0;
+ if (p->update)
+ vc_dispmanx_update_submit_sync(p->update);
+ p->update = 0;
+}
+
+static void rpi_uninit(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+ ra_gl_ctx_uninit(ctx);
+
+ vc_tv_unregister_callback_full(tv_callback, ctx);
+
+ destroy_dispmanx(ctx);
+
+ if (p->egl_context)
+ eglDestroyContext(p->egl_display, p->egl_context);
+ p->egl_context = EGL_NO_CONTEXT;
+ eglReleaseThread();
+ p->egl_display = EGL_NO_DISPLAY;
+}
+
+static bool recreate_dispmanx(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+ int display_nr = 0;
+ int layer = 0;
+
+ MP_VERBOSE(ctx, "Recreating DISPMANX state...\n");
+
+ destroy_dispmanx(ctx);
+
+ p->display = vc_dispmanx_display_open(display_nr);
+ p->update = vc_dispmanx_update_start(0);
+ if (!p->display || !p->update) {
+ MP_FATAL(ctx, "Could not get DISPMANX objects.\n");
+ goto fail;
+ }
+
+ uint32_t dispw, disph;
+ if (graphics_get_display_size(0, &dispw, &disph) < 0) {
+ MP_FATAL(ctx, "Could not get display size.\n");
+ goto fail;
+ }
+ p->w = dispw;
+ p->h = disph;
+
+ if (ctx->vo->opts->fullscreen) {
+ p->x = p->y = 0;
+ } else {
+ struct vo_win_geometry geo;
+ struct mp_rect screenrc = {0, 0, p->w, p->h};
+
+ vo_calc_window_geometry(ctx->vo, &screenrc, &geo);
+
+ mp_rect_intersection(&geo.win, &screenrc);
+
+ p->x = geo.win.x0;
+ p->y = geo.win.y0;
+ p->w = geo.win.x1 - geo.win.x0;
+ p->h = geo.win.y1 - geo.win.y0;
+ }
+
+ // dispmanx is like a neanderthal version of Wayland - you can add an
+ // overlay any place on the screen.
+ VC_RECT_T dst = {.x = p->x, .y = p->y, .width = p->w, .height = p->h};
+ VC_RECT_T src = {.width = p->w << 16, .height = p->h << 16};
+ VC_DISPMANX_ALPHA_T alpha = {
+ .flags = DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS,
+ .opacity = 0xFF,
+ };
+ p->window = vc_dispmanx_element_add(p->update, p->display, layer, &dst, 0,
+ &src, DISPMANX_PROTECTION_NONE, &alpha,
+ 0, 0);
+ if (!p->window) {
+ MP_FATAL(ctx, "Could not add DISPMANX element.\n");
+ goto fail;
+ }
+
+ vc_dispmanx_update_submit_sync(p->update);
+ p->update = vc_dispmanx_update_start(0);
+
+ p->egl_window = (EGL_DISPMANX_WINDOW_T){
+ .element = p->window,
+ .width = p->w,
+ .height = p->h,
+ };
+ p->egl_surface = eglCreateWindowSurface(p->egl_display, p->egl_config,
+ &p->egl_window, NULL);
+
+ if (p->egl_surface == EGL_NO_SURFACE) {
+ MP_FATAL(ctx, "Could not create EGL surface!\n");
+ goto fail;
+ }
+
+ if (!eglMakeCurrent(p->egl_display, p->egl_surface, p->egl_surface,
+ p->egl_context))
+ {
+ MP_FATAL(ctx, "Failed to set context!\n");
+ goto fail;
+ }
+
+ p->display_fps = 0;
+ TV_GET_STATE_RESP_T tvstate;
+ TV_DISPLAY_STATE_T tvstate_disp;
+ if (!vc_tv_get_state(&tvstate) && !vc_tv_get_display_state(&tvstate_disp)) {
+ if (tvstate_disp.state & (VC_HDMI_HDMI | VC_HDMI_DVI)) {
+ p->display_fps = tvstate_disp.display.hdmi.frame_rate;
+
+ HDMI_PROPERTY_PARAM_T param = {
+ .property = HDMI_PROPERTY_PIXEL_CLOCK_TYPE,
+ };
+ if (!vc_tv_hdmi_get_property(&param) &&
+ param.param1 == HDMI_PIXEL_CLOCK_TYPE_NTSC)
+ p->display_fps = p->display_fps / 1.001;
+ } else {
+ p->display_fps = tvstate_disp.display.sdtv.frame_rate;
+ }
+ }
+
+ p->win_params[0] = display_nr;
+ p->win_params[1] = layer;
+ p->win_params[2] = p->x;
+ p->win_params[3] = p->y;
+
+ ctx->vo->dwidth = p->w;
+ ctx->vo->dheight = p->h;
+ if (ctx->swapchain)
+ ra_gl_ctx_resize(ctx->swapchain, p->w, p->h, 0);
+
+ ctx->vo->want_redraw = true;
+
+ vo_event(ctx->vo, VO_EVENT_WIN_STATE);
+ return true;
+
+fail:
+ destroy_dispmanx(ctx);
+ return false;
+}
+
+static void rpi_swap_buffers(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+ eglSwapBuffers(p->egl_display, p->egl_surface);
+}
+
+static bool rpi_init(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
+
+ bcm_host_init();
+
+ vc_tv_register_callback(tv_callback, ctx);
+
+ p->egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ if (!eglInitialize(p->egl_display, NULL, NULL)) {
+ MP_FATAL(ctx, "EGL failed to initialize.\n");
+ goto fail;
+ }
+
+ if (!mpegl_create_context(ctx, p->egl_display, &p->egl_context, &p->egl_config))
+ goto fail;
+
+ if (!recreate_dispmanx(ctx))
+ goto fail;
+
+ mpegl_load_functions(&p->gl, ctx->log);
+
+ struct ra_gl_ctx_params params = {
+ .swap_buffers = rpi_swap_buffers,
+ };
+
+ if (!ra_gl_ctx_init(ctx, &p->gl, params))
+ goto fail;
+
+ ra_add_native_resource(ctx->ra, "MPV_RPI_WINDOW", p->win_params);
+
+ ra_gl_ctx_resize(ctx->swapchain, ctx->vo->dwidth, ctx->vo->dheight, 0);
+ return true;
+
+fail:
+ rpi_uninit(ctx);
+ return false;
+}
+
+static bool rpi_reconfig(struct ra_ctx *ctx)
+{
+ return recreate_dispmanx(ctx);
+}
+
+static struct mp_image *take_screenshot(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+
+ if (!p->display)
+ return NULL;
+
+ struct mp_image *img = mp_image_alloc(IMGFMT_BGR0, p->w, p->h);
+ if (!img)
+ return NULL;
+
+ DISPMANX_RESOURCE_HANDLE_T resource =
+ vc_dispmanx_resource_create(VC_IMAGE_ARGB8888,
+ img->w | ((img->w * 4) << 16), img->h,
+ &(int32_t){0});
+ if (!resource)
+ goto fail;
+
+ if (vc_dispmanx_snapshot(p->display, resource, 0))
+ goto fail;
+
+ VC_RECT_T rc = {.width = img->w, .height = img->h};
+ if (vc_dispmanx_resource_read_data(resource, &rc, img->planes[0], img->stride[0]))
+ goto fail;
+
+ vc_dispmanx_resource_delete(resource);
+ return img;
+
+fail:
+ vc_dispmanx_resource_delete(resource);
+ talloc_free(img);
+ return NULL;
+}
+
+static int rpi_control(struct ra_ctx *ctx, int *events, int request, void *arg)
+{
+ struct priv *p = ctx->priv;
+
+ switch (request) {
+ case VOCTRL_SCREENSHOT_WIN:
+ *(struct mp_image **)arg = take_screenshot(ctx);
+ return VO_TRUE;
+ case VOCTRL_CHECK_EVENTS:
+ if (atomic_fetch_and(&p->reload_display, 0)) {
+ MP_WARN(ctx, "Recovering from display mode switch...\n");
+ recreate_dispmanx(ctx);
+ }
+ return VO_TRUE;
+ case VOCTRL_GET_DISPLAY_FPS:
+ *(double *)arg = p->display_fps;
+ return VO_TRUE;
+ }
+
+ return VO_NOTIMPL;
+}
+
+const struct ra_ctx_fns ra_ctx_rpi = {
+ .type = "opengl",
+ .name = "rpi",
+ .reconfig = rpi_reconfig,
+ .control = rpi_control,
+ .init = rpi_init,
+ .uninit = rpi_uninit,
+};
diff --git a/video/out/opengl/context_wayland.c b/video/out/opengl/context_wayland.c
new file mode 100644
index 0000000..26c5268
--- /dev/null
+++ b/video/out/opengl/context_wayland.c
@@ -0,0 +1,230 @@
+/*
+ * This file is part of mpv video player.
+ * Copyright © 2013 Alexander Preisinger <alexander.preisinger@gmail.com>
+ *
+ * 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 <wayland-egl.h>
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
+#include "video/out/present_sync.h"
+#include "video/out/wayland_common.h"
+#include "context.h"
+#include "egl_helpers.h"
+#include "utils.h"
+
+#define EGL_PLATFORM_WAYLAND_EXT 0x31D8
+
+struct priv {
+ GL gl;
+ EGLDisplay egl_display;
+ EGLContext egl_context;
+ EGLSurface egl_surface;
+ EGLConfig egl_config;
+ struct wl_egl_window *egl_window;
+};
+
+static void resize(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+ struct vo_wayland_state *wl = ctx->vo->wl;
+
+ MP_VERBOSE(wl, "Handling resize on the egl side\n");
+
+ const int32_t width = mp_rect_w(wl->geometry);
+ const int32_t height = mp_rect_h(wl->geometry);
+
+ vo_wayland_set_opaque_region(wl, ctx->opts.want_alpha);
+ if (p->egl_window)
+ wl_egl_window_resize(p->egl_window, width, height, 0, 0);
+
+ wl->vo->dwidth = width;
+ wl->vo->dheight = height;
+
+ vo_wayland_handle_fractional_scale(wl);
+}
+
+static bool wayland_egl_check_visible(struct ra_ctx *ctx)
+{
+ return vo_wayland_check_visible(ctx->vo);
+}
+
+static void wayland_egl_swap_buffers(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+ struct vo_wayland_state *wl = ctx->vo->wl;
+
+ eglSwapBuffers(p->egl_display, p->egl_surface);
+
+ if (!wl->opts->disable_vsync)
+ vo_wayland_wait_frame(wl);
+
+ if (wl->use_present)
+ present_sync_swap(wl->present);
+}
+
+static void wayland_egl_get_vsync(struct ra_ctx *ctx, struct vo_vsync_info *info)
+{
+ struct vo_wayland_state *wl = ctx->vo->wl;
+ if (wl->use_present)
+ present_sync_get_info(wl->present, info);
+}
+
+static bool egl_create_context(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
+ struct vo_wayland_state *wl = ctx->vo->wl;
+
+ if (!(p->egl_display = mpegl_get_display(EGL_PLATFORM_WAYLAND_EXT,
+ "EGL_EXT_platform_wayland",
+ wl->display)))
+ return false;
+
+ if (eglInitialize(p->egl_display, NULL, NULL) != EGL_TRUE)
+ return false;
+
+ if (!mpegl_create_context(ctx, p->egl_display, &p->egl_context,
+ &p->egl_config))
+ return false;
+
+ eglMakeCurrent(p->egl_display, NULL, NULL, p->egl_context);
+
+ mpegl_load_functions(&p->gl, wl->log);
+
+ struct ra_gl_ctx_params params = {
+ .check_visible = wayland_egl_check_visible,
+ .swap_buffers = wayland_egl_swap_buffers,
+ .get_vsync = wayland_egl_get_vsync,
+ };
+
+ if (!ra_gl_ctx_init(ctx, &p->gl, params))
+ return false;
+
+ ra_add_native_resource(ctx->ra, "wl", wl->display);
+
+ return true;
+}
+
+static void egl_create_window(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+ struct vo_wayland_state *wl = ctx->vo->wl;
+
+ p->egl_window = wl_egl_window_create(wl->surface,
+ mp_rect_w(wl->geometry),
+ mp_rect_h(wl->geometry));
+
+ p->egl_surface = mpegl_create_window_surface(
+ p->egl_display, p->egl_config, p->egl_window);
+ if (p->egl_surface == EGL_NO_SURFACE) {
+ p->egl_surface = eglCreateWindowSurface(
+ p->egl_display, p->egl_config, p->egl_window, NULL);
+ }
+
+ eglMakeCurrent(p->egl_display, p->egl_surface, p->egl_surface, p->egl_context);
+ // eglMakeCurrent may not configure the draw or read buffers if the context
+ // has been made current previously. On nvidia GL_NONE is bound because EGL_NO_SURFACE
+ // is used initially and we must bind the read and draw buffers here.
+ if(!p->gl.es) {
+ p->gl.ReadBuffer(GL_BACK);
+ p->gl.DrawBuffer(GL_BACK);
+ }
+
+ eglSwapInterval(p->egl_display, 0);
+}
+
+static bool wayland_egl_reconfig(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+
+ if (!vo_wayland_reconfig(ctx->vo))
+ return false;
+
+ if (!p->egl_window)
+ egl_create_window(ctx);
+
+ return true;
+}
+
+static void wayland_egl_uninit(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+
+ ra_gl_ctx_uninit(ctx);
+
+ if (p->egl_context) {
+ eglReleaseThread();
+ if (p->egl_window)
+ wl_egl_window_destroy(p->egl_window);
+ eglDestroySurface(p->egl_display, p->egl_surface);
+ eglMakeCurrent(p->egl_display, NULL, NULL, EGL_NO_CONTEXT);
+ eglDestroyContext(p->egl_display, p->egl_context);
+ p->egl_context = NULL;
+ }
+ eglTerminate(p->egl_display);
+
+ vo_wayland_uninit(ctx->vo);
+}
+
+static int wayland_egl_control(struct ra_ctx *ctx, int *events, int request,
+ void *data)
+{
+ struct vo_wayland_state *wl = ctx->vo->wl;
+ int r = vo_wayland_control(ctx->vo, events, request, data);
+
+ if (*events & VO_EVENT_RESIZE) {
+ resize(ctx);
+ ra_gl_ctx_resize(ctx->swapchain, wl->vo->dwidth, wl->vo->dheight, 0);
+ }
+
+ return r;
+}
+
+static void wayland_egl_wakeup(struct ra_ctx *ctx)
+{
+ vo_wayland_wakeup(ctx->vo);
+}
+
+static void wayland_egl_wait_events(struct ra_ctx *ctx, int64_t until_time_ns)
+{
+ vo_wayland_wait_events(ctx->vo, until_time_ns);
+}
+
+static void wayland_egl_update_render_opts(struct ra_ctx *ctx)
+{
+ struct vo_wayland_state *wl = ctx->vo->wl;
+ vo_wayland_set_opaque_region(wl, ctx->opts.want_alpha);
+ wl_surface_commit(wl->surface);
+}
+
+static bool wayland_egl_init(struct ra_ctx *ctx)
+{
+ if (!vo_wayland_init(ctx->vo))
+ return false;
+ return egl_create_context(ctx);
+}
+
+const struct ra_ctx_fns ra_ctx_wayland_egl = {
+ .type = "opengl",
+ .name = "wayland",
+ .reconfig = wayland_egl_reconfig,
+ .control = wayland_egl_control,
+ .wakeup = wayland_egl_wakeup,
+ .wait_events = wayland_egl_wait_events,
+ .update_render_opts = wayland_egl_update_render_opts,
+ .init = wayland_egl_init,
+ .uninit = wayland_egl_uninit,
+};
diff --git a/video/out/opengl/context_win.c b/video/out/opengl/context_win.c
new file mode 100644
index 0000000..968b176
--- /dev/null
+++ b/video/out/opengl/context_win.c
@@ -0,0 +1,378 @@
+/*
+ * 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 <assert.h>
+#include <windows.h>
+#include <dwmapi.h>
+
+#include "options/m_config.h"
+#include "video/out/w32_common.h"
+#include "context.h"
+#include "utils.h"
+
+#if !defined(WGL_CONTEXT_MAJOR_VERSION_ARB)
+/* these are supposed to be defined in wingdi.h but mingw's is too old */
+/* only the bits actually used by mplayer are defined */
+/* reference: http://www.opengl.org/registry/specs/ARB/wgl_create_context.txt */
+
+#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091
+#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092
+#define WGL_CONTEXT_FLAGS_ARB 0x2094
+#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126
+#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x0002
+#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001
+#endif
+
+struct wingl_opts {
+ int wingl_dwm_flush;
+};
+
+#define OPT_BASE_STRUCT struct wingl_opts
+const struct m_sub_options wingl_conf = {
+ .opts = (const struct m_option[]) {
+ {"opengl-dwmflush", OPT_CHOICE(wingl_dwm_flush,
+ {"no", -1}, {"auto", 0}, {"windowed", 1}, {"yes", 2})},
+ {0}
+ },
+ .size = sizeof(struct wingl_opts),
+};
+
+struct priv {
+ GL gl;
+
+ int opt_swapinterval;
+ int current_swapinterval;
+
+ int (GLAPIENTRY *real_wglSwapInterval)(int);
+ struct m_config_cache *opts_cache;
+ struct wingl_opts *opts;
+
+ HGLRC context;
+ HDC hdc;
+};
+
+static void wgl_uninit(struct ra_ctx *ctx);
+
+static __thread struct priv *current_wgl_context;
+
+static int GLAPIENTRY wgl_swap_interval(int interval)
+{
+ if (current_wgl_context)
+ current_wgl_context->opt_swapinterval = interval;
+ return 0;
+}
+
+static bool create_dc(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+ HWND win = vo_w32_hwnd(ctx->vo);
+
+ if (p->hdc)
+ return true;
+
+ HDC hdc = GetDC(win);
+ if (!hdc)
+ return false;
+
+ PIXELFORMATDESCRIPTOR pfd;
+ memset(&pfd, 0, sizeof pfd);
+ pfd.nSize = sizeof pfd;
+ pfd.nVersion = 1;
+ pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
+
+ pfd.iPixelType = PFD_TYPE_RGBA;
+ pfd.cColorBits = 24;
+ pfd.iLayerType = PFD_MAIN_PLANE;
+ int pf = ChoosePixelFormat(hdc, &pfd);
+
+ if (!pf) {
+ MP_ERR(ctx->vo, "unable to select a valid pixel format!\n");
+ ReleaseDC(win, hdc);
+ return false;
+ }
+
+ SetPixelFormat(hdc, pf, &pfd);
+
+ p->hdc = hdc;
+ return true;
+}
+
+static void *wglgpa(const GLubyte *procName)
+{
+ HMODULE oglmod;
+ void *res = wglGetProcAddress(procName);
+ if (res)
+ return res;
+ oglmod = GetModuleHandle(L"opengl32.dll");
+ return GetProcAddress(oglmod, procName);
+}
+
+static bool create_context_wgl_old(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+
+ HDC windc = p->hdc;
+ bool res = false;
+
+ HGLRC context = wglCreateContext(windc);
+ if (!context) {
+ MP_FATAL(ctx->vo, "Could not create GL context!\n");
+ return res;
+ }
+
+ if (!wglMakeCurrent(windc, context)) {
+ MP_FATAL(ctx->vo, "Could not set GL context!\n");
+ wglDeleteContext(context);
+ return res;
+ }
+
+ p->context = context;
+ return true;
+}
+
+static bool create_context_wgl_gl3(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+
+ HDC windc = p->hdc;
+ HGLRC context = 0;
+
+ // A legacy context is needed to get access to the new functions.
+ HGLRC legacy_context = wglCreateContext(windc);
+ if (!legacy_context) {
+ MP_FATAL(ctx->vo, "Could not create GL context!\n");
+ return false;
+ }
+
+ // set context
+ if (!wglMakeCurrent(windc, legacy_context)) {
+ MP_FATAL(ctx->vo, "Could not set GL context!\n");
+ goto out;
+ }
+
+ const char *(GLAPIENTRY *wglGetExtensionsStringARB)(HDC hdc)
+ = wglgpa((const GLubyte*)"wglGetExtensionsStringARB");
+
+ if (!wglGetExtensionsStringARB)
+ goto unsupported;
+
+ const char *wgl_exts = wglGetExtensionsStringARB(windc);
+ if (!gl_check_extension(wgl_exts, "WGL_ARB_create_context"))
+ goto unsupported;
+
+ HGLRC (GLAPIENTRY *wglCreateContextAttribsARB)(HDC hDC, HGLRC hShareContext,
+ const int *attribList)
+ = wglgpa((const GLubyte*)"wglCreateContextAttribsARB");
+
+ if (!wglCreateContextAttribsARB)
+ goto unsupported;
+
+ 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
+ };
+
+ context = wglCreateContextAttribsARB(windc, 0, attribs);
+ if (!context) {
+ // 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;
+ context = wglCreateContextAttribsARB(windc, 0, attribs);
+ }
+ if (!context) {
+ int err = GetLastError();
+ MP_FATAL(ctx->vo, "Could not create an OpenGL 3.x context: error 0x%x\n", err);
+ goto out;
+ }
+
+ wglMakeCurrent(windc, NULL);
+ wglDeleteContext(legacy_context);
+
+ if (!wglMakeCurrent(windc, context)) {
+ MP_FATAL(ctx->vo, "Could not set GL3 context!\n");
+ wglDeleteContext(context);
+ return false;
+ }
+
+ p->context = context;
+ return true;
+
+unsupported:
+ MP_ERR(ctx->vo, "The OpenGL driver does not support OpenGL 3.x \n");
+out:
+ wglMakeCurrent(windc, NULL);
+ wglDeleteContext(legacy_context);
+ return false;
+}
+
+static void create_ctx(void *ptr)
+{
+ struct ra_ctx *ctx = ptr;
+ struct priv *p = ctx->priv;
+
+ if (!create_dc(ctx))
+ return;
+
+ create_context_wgl_gl3(ctx);
+ if (!p->context)
+ create_context_wgl_old(ctx);
+
+ wglMakeCurrent(p->hdc, NULL);
+}
+
+static bool compositor_active(struct ra_ctx *ctx)
+{
+ // For Windows 7.
+ BOOL enabled = 0;
+ if (FAILED(DwmIsCompositionEnabled(&enabled)) || !enabled)
+ return false;
+
+ // This works at least on Windows 8.1: it returns an error in fullscreen,
+ // which is also when we get consistent timings without DwmFlush. Might
+ // be cargo-cult.
+ DWM_TIMING_INFO info = { .cbSize = sizeof(DWM_TIMING_INFO) };
+ if (FAILED(DwmGetCompositionTimingInfo(0, &info)))
+ return false;
+
+ return true;
+}
+
+static void wgl_swap_buffers(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+ SwapBuffers(p->hdc);
+
+ // default if we don't DwmFLush
+ int new_swapinterval = p->opt_swapinterval;
+
+ if (p->opts->wingl_dwm_flush >= 0) {
+ if ((p->opts->wingl_dwm_flush == 1 && !ctx->vo->opts->fullscreen) ||
+ (p->opts->wingl_dwm_flush == 2) ||
+ (p->opts->wingl_dwm_flush == 0 && compositor_active(ctx)))
+ {
+ if (DwmFlush() == S_OK)
+ new_swapinterval = 0;
+ }
+ }
+
+ if (new_swapinterval != p->current_swapinterval &&
+ p->real_wglSwapInterval)
+ {
+ p->real_wglSwapInterval(new_swapinterval);
+ MP_VERBOSE(ctx->vo, "set SwapInterval(%d)\n", new_swapinterval);
+ }
+ p->current_swapinterval = new_swapinterval;
+}
+
+static bool wgl_init(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
+ GL *gl = &p->gl;
+
+ p->opts_cache = m_config_cache_alloc(ctx, ctx->global, &wingl_conf);
+ p->opts = p->opts_cache->opts;
+
+ if (!vo_w32_init(ctx->vo))
+ goto fail;
+
+ vo_w32_run_on_thread(ctx->vo, create_ctx, ctx);
+ if (!p->context)
+ goto fail;
+
+ current_wgl_context = p;
+ wglMakeCurrent(p->hdc, p->context);
+
+ mpgl_load_functions(gl, wglgpa, NULL, ctx->vo->log);
+
+ if (!gl->SwapInterval)
+ MP_VERBOSE(ctx->vo, "WGL_EXT_swap_control missing.\n");
+ p->real_wglSwapInterval = gl->SwapInterval;
+ gl->SwapInterval = wgl_swap_interval;
+ p->current_swapinterval = -1;
+
+ struct ra_gl_ctx_params params = {
+ .swap_buffers = wgl_swap_buffers,
+ };
+
+ if (!ra_gl_ctx_init(ctx, gl, params))
+ goto fail;
+
+ DwmEnableMMCSS(TRUE);
+ return true;
+
+fail:
+ wgl_uninit(ctx);
+ return false;
+}
+
+static void resize(struct ra_ctx *ctx)
+{
+ ra_gl_ctx_resize(ctx->swapchain, ctx->vo->dwidth, ctx->vo->dheight, 0);
+}
+
+static bool wgl_reconfig(struct ra_ctx *ctx)
+{
+ vo_w32_config(ctx->vo);
+ resize(ctx);
+ return true;
+}
+
+static void destroy_gl(void *ptr)
+{
+ struct ra_ctx *ctx = ptr;
+ struct priv *p = ctx->priv;
+ if (p->context)
+ wglDeleteContext(p->context);
+ p->context = 0;
+ if (p->hdc)
+ ReleaseDC(vo_w32_hwnd(ctx->vo), p->hdc);
+ p->hdc = NULL;
+ current_wgl_context = NULL;
+}
+
+static void wgl_uninit(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+ ra_gl_ctx_uninit(ctx);
+ if (p->context)
+ wglMakeCurrent(p->hdc, 0);
+ vo_w32_run_on_thread(ctx->vo, destroy_gl, ctx);
+
+ DwmEnableMMCSS(FALSE);
+ vo_w32_uninit(ctx->vo);
+}
+
+static int wgl_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_wgl = {
+ .type = "opengl",
+ .name = "win",
+ .init = wgl_init,
+ .reconfig = wgl_reconfig,
+ .control = wgl_control,
+ .uninit = wgl_uninit,
+};
diff --git a/video/out/opengl/context_x11egl.c b/video/out/opengl/context_x11egl.c
new file mode 100644
index 0000000..3201f29
--- /dev/null
+++ b/video/out/opengl/context_x11egl.c
@@ -0,0 +1,225 @@
+/*
+ * 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 <assert.h>
+
+#include <X11/Xlib.h>
+#include <X11/extensions/Xpresent.h>
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
+#include "common/common.h"
+#include "video/out/present_sync.h"
+#include "video/out/x11_common.h"
+#include "context.h"
+#include "egl_helpers.h"
+#include "utils.h"
+
+#define EGL_PLATFORM_X11_EXT 0x31D5
+
+struct priv {
+ GL gl;
+ EGLDisplay egl_display;
+ EGLContext egl_context;
+ EGLSurface egl_surface;
+};
+
+static void mpegl_uninit(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+ ra_gl_ctx_uninit(ctx);
+
+ eglMakeCurrent(p->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ EGL_NO_CONTEXT);
+ eglTerminate(p->egl_display);
+ vo_x11_uninit(ctx->vo);
+}
+
+static int pick_xrgba_config(void *user_data, EGLConfig *configs, int num_configs)
+{
+ struct ra_ctx *ctx = user_data;
+ struct priv *p = ctx->priv;
+ struct vo *vo = ctx->vo;
+
+ for (int n = 0; n < num_configs; n++) {
+ int vID = 0, num;
+ eglGetConfigAttrib(p->egl_display, configs[n], EGL_NATIVE_VISUAL_ID, &vID);
+ XVisualInfo template = {.visualid = vID};
+ XVisualInfo *vi = XGetVisualInfo(vo->x11->display, VisualIDMask,
+ &template, &num);
+ if (vi) {
+ bool is_rgba = vo_x11_is_rgba_visual(vi);
+ XFree(vi);
+ if (is_rgba)
+ return n;
+ }
+ }
+
+ return 0;
+}
+
+static bool mpegl_check_visible(struct ra_ctx *ctx)
+{
+ return vo_x11_check_visible(ctx->vo);
+}
+
+static void mpegl_swap_buffers(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+
+ eglSwapBuffers(p->egl_display, p->egl_surface);
+ if (ctx->vo->x11->use_present)
+ present_sync_swap(ctx->vo->x11->present);
+}
+
+static void mpegl_get_vsync(struct ra_ctx *ctx, struct vo_vsync_info *info)
+{
+ struct vo_x11_state *x11 = ctx->vo->x11;
+ if (ctx->vo->x11->use_present)
+ present_sync_get_info(x11->present, info);
+}
+
+static bool mpegl_init(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
+ struct vo *vo = ctx->vo;
+ int msgl = ctx->opts.probing ? MSGL_V : MSGL_FATAL;
+
+ if (!vo_x11_init(vo))
+ goto uninit;
+
+ p->egl_display = mpegl_get_display(EGL_PLATFORM_X11_EXT,
+ "EGL_EXT_platform_x11",
+ vo->x11->display);
+ if (!eglInitialize(p->egl_display, NULL, NULL)) {
+ MP_MSG(ctx, msgl, "Could not initialize EGL.\n");
+ goto uninit;
+ }
+
+ struct mpegl_cb cb = {
+ .user_data = ctx,
+ .refine_config = ctx->opts.want_alpha ? pick_xrgba_config : NULL,
+ };
+
+ EGLConfig config;
+ if (!mpegl_create_context_cb(ctx, p->egl_display, cb, &p->egl_context, &config))
+ goto uninit;
+
+ int cid, vID, n;
+ if (!eglGetConfigAttrib(p->egl_display, config, EGL_CONFIG_ID, &cid)) {
+ MP_FATAL(ctx, "Getting EGL_CONFIG_ID failed!\n");
+ goto uninit;
+ }
+ if (!eglGetConfigAttrib(p->egl_display, config, EGL_NATIVE_VISUAL_ID, &vID)) {
+ MP_FATAL(ctx, "Getting X visual ID failed!\n");
+ goto uninit;
+ }
+ MP_VERBOSE(ctx, "Choosing visual EGL config 0x%x, visual ID 0x%x\n", cid, vID);
+ XVisualInfo template = {.visualid = vID};
+ XVisualInfo *vi = XGetVisualInfo(vo->x11->display, VisualIDMask, &template, &n);
+
+ if (!vi) {
+ MP_FATAL(ctx, "Getting X visual failed!\n");
+ goto uninit;
+ }
+
+ if (!vo_x11_create_vo_window(vo, vi, "gl")) {
+ XFree(vi);
+ goto uninit;
+ }
+
+ XFree(vi);
+
+ p->egl_surface = mpegl_create_window_surface(
+ p->egl_display, config, &vo->x11->window);
+ if (p->egl_surface == EGL_NO_SURFACE) {
+ p->egl_surface = eglCreateWindowSurface(
+ p->egl_display, config, (EGLNativeWindowType)vo->x11->window, NULL);
+ }
+ if (p->egl_surface == EGL_NO_SURFACE) {
+ MP_FATAL(ctx, "Could not create EGL surface!\n");
+ goto uninit;
+ }
+
+ if (!eglMakeCurrent(p->egl_display, p->egl_surface, p->egl_surface,
+ p->egl_context))
+ {
+ MP_FATAL(ctx, "Could not make context current!\n");
+ goto uninit;
+ }
+
+ mpegl_load_functions(&p->gl, ctx->log);
+
+ struct ra_gl_ctx_params params = {
+ .check_visible = mpegl_check_visible,
+ .swap_buffers = mpegl_swap_buffers,
+ .get_vsync = mpegl_get_vsync,
+ };
+
+ if (!ra_gl_ctx_init(ctx, &p->gl, params))
+ goto uninit;
+
+ ra_add_native_resource(ctx->ra, "x11", vo->x11->display);
+
+ return true;
+
+uninit:
+ mpegl_uninit(ctx);
+ return false;
+}
+
+static void resize(struct ra_ctx *ctx)
+{
+ ra_gl_ctx_resize(ctx->swapchain, ctx->vo->dwidth, ctx->vo->dheight, 0);
+}
+
+static bool mpegl_reconfig(struct ra_ctx *ctx)
+{
+ vo_x11_config_vo_window(ctx->vo);
+ resize(ctx);
+ return true;
+}
+
+static int mpegl_control(struct ra_ctx *ctx, int *events, int request,
+ void *arg)
+{
+ int ret = vo_x11_control(ctx->vo, events, request, arg);
+ if (*events & VO_EVENT_RESIZE)
+ resize(ctx);
+ return ret;
+}
+
+static void mpegl_wakeup(struct ra_ctx *ctx)
+{
+ vo_x11_wakeup(ctx->vo);
+}
+
+static void mpegl_wait_events(struct ra_ctx *ctx, int64_t until_time_ns)
+{
+ vo_x11_wait_events(ctx->vo, until_time_ns);
+}
+
+const struct ra_ctx_fns ra_ctx_x11_egl = {
+ .type = "opengl",
+ .name = "x11egl",
+ .reconfig = mpegl_reconfig,
+ .control = mpegl_control,
+ .wakeup = mpegl_wakeup,
+ .wait_events = mpegl_wait_events,
+ .init = mpegl_init,
+ .uninit = mpegl_uninit,
+};
diff --git a/video/out/opengl/egl_helpers.c b/video/out/opengl/egl_helpers.c
new file mode 100644
index 0000000..3bf6239
--- /dev/null
+++ b/video/out/opengl/egl_helpers.c
@@ -0,0 +1,381 @@
+/*
+ * 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 "config.h"
+
+#if HAVE_LIBDL
+#include <dlfcn.h>
+#endif
+
+#include "common/common.h"
+
+#include "egl_helpers.h"
+#include "common.h"
+#include "utils.h"
+#include "context.h"
+
+#if 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
+#define EGL_CONTEXT_MINOR_VERSION 0x30FB
+#define EGL_CONTEXT_OPENGL_PROFILE_MASK 0x30FD
+#define EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT 0x00000001
+#define EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE 0x31B1
+typedef intptr_t EGLAttrib;
+#endif
+
+// Not every EGL provider (like RPI) has these.
+#ifndef EGL_CONTEXT_FLAGS_KHR
+#define EGL_CONTEXT_FLAGS_KHR EGL_NONE
+#endif
+
+#ifndef EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR
+#define EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR 0
+#endif
+
+struct mp_egl_config_attr {
+ int attrib;
+ const char *name;
+};
+
+#define MP_EGL_ATTRIB(id) {id, # id}
+
+static const struct mp_egl_config_attr mp_egl_attribs[] = {
+ MP_EGL_ATTRIB(EGL_CONFIG_ID),
+ MP_EGL_ATTRIB(EGL_RED_SIZE),
+ MP_EGL_ATTRIB(EGL_GREEN_SIZE),
+ MP_EGL_ATTRIB(EGL_BLUE_SIZE),
+ MP_EGL_ATTRIB(EGL_ALPHA_SIZE),
+ MP_EGL_ATTRIB(EGL_COLOR_BUFFER_TYPE),
+ MP_EGL_ATTRIB(EGL_CONFIG_CAVEAT),
+ MP_EGL_ATTRIB(EGL_CONFORMANT),
+ MP_EGL_ATTRIB(EGL_NATIVE_VISUAL_ID),
+};
+
+static void dump_egl_config(struct mp_log *log, int msgl, EGLDisplay display,
+ EGLConfig config)
+{
+ for (int n = 0; n < MP_ARRAY_SIZE(mp_egl_attribs); n++) {
+ const char *name = mp_egl_attribs[n].name;
+ EGLint v = -1;
+ if (eglGetConfigAttrib(display, config, mp_egl_attribs[n].attrib, &v)) {
+ mp_msg(log, msgl, " %s=0x%x\n", name, v);
+ } else {
+ mp_msg(log, msgl, " %s=<error>\n", name);
+ }
+ }
+}
+
+static void *mpegl_get_proc_address(void *ctx, const char *name)
+{
+ void *p = eglGetProcAddress(name);
+#if defined(__GLIBC__) && HAVE_LIBDL
+ // Some crappy ARM/Linux things do not provide EGL 1.5, so above call does
+ // not necessarily return function pointers for core functions. Try to get
+ // them from a loaded GLES lib. As POSIX leaves RTLD_DEFAULT "reserved",
+ // use it only with glibc.
+ if (!p)
+ p = dlsym(RTLD_DEFAULT, name);
+#endif
+ return p;
+}
+
+static bool create_context(struct ra_ctx *ctx, EGLDisplay display,
+ bool es, struct mpegl_cb cb,
+ EGLContext *out_context, EGLConfig *out_config)
+{
+ int msgl = ctx->opts.probing ? MSGL_V : MSGL_FATAL;
+
+ EGLenum api;
+ EGLint rend;
+ const char *name;
+
+ if (!es) {
+ api = EGL_OPENGL_API;
+ rend = EGL_OPENGL_BIT;
+ name = "Desktop OpenGL";
+ } else {
+ api = EGL_OPENGL_ES_API;
+ rend = EGL_OPENGL_ES2_BIT;
+ name = "GLES 2.x +";
+ }
+
+ MP_VERBOSE(ctx, "Trying to create %s context.\n", name);
+
+ if (!eglBindAPI(api)) {
+ MP_VERBOSE(ctx, "Could not bind API!\n");
+ return false;
+ }
+
+ EGLint attributes[] = {
+ EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+ EGL_RED_SIZE, 8,
+ EGL_GREEN_SIZE, 8,
+ EGL_BLUE_SIZE, 8,
+ EGL_ALPHA_SIZE, ctx->opts.want_alpha ? 8 : 0,
+ EGL_RENDERABLE_TYPE, rend,
+ EGL_NONE
+ };
+
+ EGLint num_configs;
+ if (!eglChooseConfig(display, attributes, NULL, 0, &num_configs))
+ num_configs = 0;
+
+ EGLConfig *configs = talloc_array(NULL, EGLConfig, num_configs);
+ if (!eglChooseConfig(display, attributes, configs, num_configs, &num_configs))
+ num_configs = 0;
+
+ if (!num_configs) {
+ talloc_free(configs);
+ MP_MSG(ctx, msgl, "Could not choose EGLConfig for %s!\n", name);
+ return false;
+ }
+
+ for (int n = 0; n < num_configs; n++)
+ dump_egl_config(ctx->log, MSGL_TRACE, display, configs[n]);
+
+ int chosen = 0;
+ if (cb.refine_config)
+ chosen = cb.refine_config(cb.user_data, configs, num_configs);
+ if (chosen < 0) {
+ talloc_free(configs);
+ MP_MSG(ctx, msgl, "Could not refine EGLConfig for %s!\n", name);
+ return false;
+ }
+ EGLConfig config = configs[chosen];
+
+ talloc_free(configs);
+
+ MP_DBG(ctx, "Chosen EGLConfig:\n");
+ dump_egl_config(ctx->log, MSGL_DEBUG, display, config);
+
+ int ctx_flags = ctx->opts.debug ? EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR : 0;
+ EGLContext *egl_ctx = NULL;
+
+ if (!es) {
+ for (int n = 0; mpgl_min_required_gl_versions[n]; n++) {
+ int ver = mpgl_min_required_gl_versions[n];
+
+ EGLint attrs[] = {
+ EGL_CONTEXT_MAJOR_VERSION, MPGL_VER_GET_MAJOR(ver),
+ EGL_CONTEXT_MINOR_VERSION, MPGL_VER_GET_MINOR(ver),
+ EGL_CONTEXT_OPENGL_PROFILE_MASK,
+ ver >= 320 ? EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT : 0,
+ EGL_CONTEXT_FLAGS_KHR, ctx_flags,
+ EGL_NONE
+ };
+
+ egl_ctx = eglCreateContext(display, config, EGL_NO_CONTEXT, attrs);
+ if (egl_ctx)
+ break;
+ }
+ }
+ if (!egl_ctx) {
+ // Fallback for EGL 1.4 without EGL_KHR_create_context or GLES
+ // Add the context flags only for GLES - GL has been attempted above
+ EGLint attrs[] = {
+ EGL_CONTEXT_CLIENT_VERSION, 2,
+ es ? EGL_CONTEXT_FLAGS_KHR : EGL_NONE, ctx_flags,
+ EGL_NONE
+ };
+
+ egl_ctx = eglCreateContext(display, config, EGL_NO_CONTEXT, attrs);
+ }
+
+ if (!egl_ctx) {
+ MP_MSG(ctx, msgl, "Could not create EGL context for %s!\n", name);
+ return false;
+ }
+
+ *out_context = egl_ctx;
+ *out_config = config;
+ return true;
+}
+
+#define STR_OR_ERR(s) ((s) ? (s) : "(error)")
+
+// Create a context and return it and the config it was created with. If it
+// returns false, the out_* pointers are set to NULL.
+// vo_flags is a combination of VOFLAG_* values.
+bool mpegl_create_context(struct ra_ctx *ctx, EGLDisplay display,
+ EGLContext *out_context, EGLConfig *out_config)
+{
+ return mpegl_create_context_cb(ctx, display, (struct mpegl_cb){0},
+ out_context, out_config);
+}
+
+// Create a context and return it and the config it was created with. If it
+// returns false, the out_* pointers are set to NULL.
+bool mpegl_create_context_cb(struct ra_ctx *ctx, EGLDisplay display,
+ struct mpegl_cb cb, EGLContext *out_context,
+ EGLConfig *out_config)
+{
+ *out_context = NULL;
+ *out_config = NULL;
+
+ const char *version = eglQueryString(display, EGL_VERSION);
+ const char *vendor = eglQueryString(display, EGL_VENDOR);
+ const char *apis = eglQueryString(display, EGL_CLIENT_APIS);
+ MP_VERBOSE(ctx, "EGL_VERSION=%s\nEGL_VENDOR=%s\nEGL_CLIENT_APIS=%s\n",
+ STR_OR_ERR(version), STR_OR_ERR(vendor), STR_OR_ERR(apis));
+
+ enum gles_mode mode = ra_gl_ctx_get_glesmode(ctx);
+
+ if ((mode == GLES_NO || mode == GLES_AUTO) &&
+ create_context(ctx, display, false, cb, out_context, out_config))
+ return true;
+
+ if ((mode == GLES_YES || mode == GLES_AUTO) &&
+ create_context(ctx, display, true, cb, out_context, out_config))
+ return true;
+
+ int msgl = ctx->opts.probing ? MSGL_V : MSGL_ERR;
+ MP_MSG(ctx, msgl, "Could not create a GL context.\n");
+ return false;
+}
+
+static int GLAPIENTRY swap_interval(int interval)
+{
+ EGLDisplay display = eglGetCurrentDisplay();
+ if (!display)
+ return 1;
+ return !eglSwapInterval(display, interval);
+}
+
+// Load gl version and function pointers into *gl.
+// Expects a current EGL context set.
+void mpegl_load_functions(struct GL *gl, struct mp_log *log)
+{
+ const char *egl_exts = "";
+ EGLDisplay display = eglGetCurrentDisplay();
+ if (display != EGL_NO_DISPLAY)
+ egl_exts = eglQueryString(display, EGL_EXTENSIONS);
+
+ mpgl_load_functions2(gl, mpegl_get_proc_address, NULL, egl_exts, log);
+ if (!gl->SwapInterval)
+ gl->SwapInterval = swap_interval;
+}
+
+static bool is_egl15(void)
+{
+ // It appears that EGL 1.4 is specified to _require_ an initialized display
+ // for EGL_VERSION, while EGL 1.5 is _required_ to return the EGL version.
+ const char *ver = eglQueryString(EGL_NO_DISPLAY, EGL_VERSION);
+ // Of course we have to go through the excruciating pain of parsing a
+ // version string, since EGL provides no other way without a display. In
+ // theory version!=NULL is already proof enough that it's 1.5, but be
+ // extra defensive, since this should have been true for EGL_EXTENSIONS as
+ // well, but then they added an extension that modified standard behavior.
+ int ma = 0, mi = 0;
+ return ver && sscanf(ver, "%d.%d", &ma, &mi) == 2 && (ma > 1 || mi >= 5);
+}
+
+// This is similar to eglGetPlatformDisplay(platform, native_display, NULL),
+// except that it 1. may use eglGetPlatformDisplayEXT, 2. checks for the
+// platform client extension platform_ext_name, and 3. does not support passing
+// an attrib list, because the type for that parameter is different in the EXT
+// and standard functions (EGL can't not fuck up, no matter what).
+// platform: e.g. EGL_PLATFORM_X11_KHR
+// platform_ext_name: e.g. "EGL_KHR_platform_x11"
+// native_display: e.g. X11 Display*
+// Returns EGL_NO_DISPLAY on failure.
+// Warning: the EGL version can be different at runtime depending on the chosen
+// platform, so this might return a display corresponding to some older EGL
+// version (often 1.4).
+// Often, there are two extension variants of a platform (KHR and EXT). If you
+// need to check both, call this function twice. (Why do they define them twice?
+// They're crazy.)
+EGLDisplay mpegl_get_display(EGLenum platform, const char *platform_ext_name,
+ void *native_display)
+{
+ // EGL is awful. Designed as ultra-portable library, it fails at dealing
+ // with slightly more complex environment than its short-sighted design
+ // could deal with. So they invented an awful, awful kludge that modifies
+ // EGL standard behavior, the EGL_EXT_client_extensions extension. EGL 1.4
+ // normally is to return NULL when querying EGL_EXTENSIONS on EGL_NO_DISPLAY,
+ // however, with that extension, it'll return the set of "client extensions",
+ // which may include EGL_EXT_platform_base.
+
+ // Prerequisite: check the platform extension.
+ // If this is either EGL 1.5, or 1.4 with EGL_EXT_client_extensions, then
+ // this must return a valid extension string.
+ const char *exts = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
+ if (!gl_check_extension(exts, platform_ext_name))
+ return EGL_NO_DISPLAY;
+
+ // Before we go through the EGL 1.4 BS, try if we can use native EGL 1.5
+ if (is_egl15()) {
+ // This is EGL 1.5. It must support querying standard functions through
+ // eglGetProcAddress(). Note that on EGL 1.4, even if the function is
+ // unknown, it could return non-NULL anyway (because EGL is crazy).
+ EGLDisplay (EGLAPIENTRYP GetPlatformDisplay)
+ (EGLenum, void *, const EGLAttrib *) =
+ (void *)eglGetProcAddress("eglGetPlatformDisplay");
+ // (It should be impossible to be NULL, but uh.)
+ if (GetPlatformDisplay)
+ return GetPlatformDisplay(platform, native_display, NULL);
+ }
+
+ if (!gl_check_extension(exts, "EGL_EXT_platform_base"))
+ return EGL_NO_DISPLAY;
+
+ EGLDisplay (EGLAPIENTRYP GetPlatformDisplayEXT)(EGLenum, void*, const EGLint*)
+ = (void *)eglGetProcAddress("eglGetPlatformDisplayEXT");
+
+ // (It should be impossible to be NULL, but uh.)
+ if (GetPlatformDisplayEXT)
+ return GetPlatformDisplayEXT(platform, native_display, NULL);
+
+ return EGL_NO_DISPLAY;
+}
+
+// The same mess but with eglCreatePlatformWindowSurface(EXT)
+// again no support for an attribute list because the type differs
+// Returns EGL_NO_SURFACE on failure.
+EGLSurface mpegl_create_window_surface(EGLDisplay dpy, EGLConfig config,
+ void *native_window)
+{
+ // Use the EGL 1.5 function if possible
+ if (is_egl15()) {
+ EGLSurface (EGLAPIENTRYP CreatePlatformWindowSurface)
+ (EGLDisplay, EGLConfig, void *, const EGLAttrib *) =
+ (void *)eglGetProcAddress("eglCreatePlatformWindowSurface");
+ // (It should be impossible to be NULL, but uh.)
+ if (CreatePlatformWindowSurface)
+ return CreatePlatformWindowSurface(dpy, config, native_window, NULL);
+ }
+
+ // Check the extension that provides the *EXT function
+ const char *exts = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
+ if (!gl_check_extension(exts, "EGL_EXT_platform_base"))
+ return EGL_NO_SURFACE;
+
+ EGLSurface (EGLAPIENTRYP CreatePlatformWindowSurfaceEXT)
+ (EGLDisplay, EGLConfig, void *, const EGLint *) =
+ (void *)eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT");
+ // (It should be impossible to be NULL, but uh.)
+ if (CreatePlatformWindowSurfaceEXT)
+ return CreatePlatformWindowSurfaceEXT(dpy, config, native_window, NULL);
+
+ return EGL_NO_SURFACE;
+}
diff --git a/video/out/opengl/egl_helpers.h b/video/out/opengl/egl_helpers.h
new file mode 100644
index 0000000..32ec5d1
--- /dev/null
+++ b/video/out/opengl/egl_helpers.h
@@ -0,0 +1,38 @@
+#ifndef MP_GL_EGL_HELPERS_H
+#define MP_GL_EGL_HELPERS_H
+
+#include <stdbool.h>
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
+#include "video/out/gpu/context.h"
+
+struct mp_log;
+
+bool mpegl_create_context(struct ra_ctx *ctx, EGLDisplay display,
+ EGLContext *out_context, EGLConfig *out_config);
+
+struct mpegl_cb {
+ // if set, pick the desired config from the given list and return its index
+ // defaults to 0 (they are sorted by eglChooseConfig). return a negative
+ // number to indicate an error condition or that no suitable configs could
+ // be found.
+ int (*refine_config)(void *user_data, EGLConfig *configs, int num_configs);
+ void *user_data;
+};
+
+bool mpegl_create_context_cb(struct ra_ctx *ctx, EGLDisplay display,
+ struct mpegl_cb cb, EGLContext *out_context,
+ EGLConfig *out_config);
+
+struct GL;
+void mpegl_load_functions(struct GL *gl, struct mp_log *log);
+
+EGLDisplay mpegl_get_display(EGLenum platform, const char *platform_ext_name,
+ void *native_display);
+
+EGLSurface mpegl_create_window_surface(EGLDisplay dpy, EGLConfig config,
+ void *native_window);
+
+#endif
diff --git a/video/out/opengl/formats.c b/video/out/opengl/formats.c
new file mode 100644
index 0000000..a0b79e2
--- /dev/null
+++ b/video/out/opengl/formats.c
@@ -0,0 +1,196 @@
+#include "common/common.h"
+#include "formats.h"
+
+enum {
+ // --- GL type aliases (for readability)
+ T_U8 = GL_UNSIGNED_BYTE,
+ T_U16 = GL_UNSIGNED_SHORT,
+ T_FL = GL_FLOAT,
+};
+
+// List of allowed formats, and their usability for bilinear filtering and FBOs.
+// This is limited to combinations that are useful for our renderer.
+const struct gl_format gl_formats[] = {
+ // These are used for desktop GL 3+, and GLES 3+ with GL_EXT_texture_norm16.
+ {"r8", GL_R8, GL_RED, T_U8, F_CF | F_GL3 | F_GL2F | F_ES3},
+ {"rg8", GL_RG8, GL_RG, T_U8, F_CF | F_GL3 | F_GL2F | F_ES3},
+ {"rgb8", GL_RGB8, GL_RGB, T_U8, F_CF | F_GL3 | F_GL2F | F_ES3},
+ {"rgba8", GL_RGBA8, GL_RGBA, T_U8, F_CF | F_GL3 | F_GL2F | F_ES3},
+ {"r16", GL_R16, GL_RED, T_U16, F_CF | F_GL3 | F_GL2F | F_EXT16},
+ {"rg16", GL_RG16, GL_RG, T_U16, F_CF | F_GL3 | F_GL2F | F_EXT16},
+ {"rgb16", GL_RGB16, GL_RGB, T_U16, F_CF | F_GL3 | F_GL2F},
+ {"rgba16", GL_RGBA16, GL_RGBA, T_U16, F_CF | F_GL3 | F_GL2F | F_EXT16},
+
+ // Specifically not color-renderable.
+ {"rgb16", GL_RGB16, GL_RGB, T_U16, F_TF | F_EXT16},
+
+ // GL2 legacy. Ignores possibly present FBO extensions (no CF flag set).
+ {"l8", GL_LUMINANCE8, GL_LUMINANCE, T_U8, F_TF | F_GL2},
+ {"la8", GL_LUMINANCE8_ALPHA8, GL_LUMINANCE_ALPHA, T_U8, F_TF | F_GL2},
+ {"rgb8", GL_RGB8, GL_RGB, T_U8, F_TF | F_GL2},
+ {"rgba8", GL_RGBA8, GL_RGBA, T_U8, F_TF | F_GL2},
+ {"l16", GL_LUMINANCE16, GL_LUMINANCE, T_U16, F_TF | F_GL2},
+ {"la16", GL_LUMINANCE16_ALPHA16, GL_LUMINANCE_ALPHA, T_U16, F_TF | F_GL2},
+ {"rgb16", GL_RGB16, GL_RGB, T_U16, F_TF | F_GL2},
+ {"rgba16", GL_RGBA16, GL_RGBA, T_U16, F_TF | F_GL2},
+
+ // ES3 legacy. This is literally to compensate for Apple bugs in their iOS
+ // interop (can they do anything right?). ES3 still allows these formats,
+ // but they are deprecated.
+ {"l" , GL_LUMINANCE,GL_LUMINANCE, T_U8, F_CF | F_ES3},
+ {"la",GL_LUMINANCE_ALPHA,GL_LUMINANCE_ALPHA, T_U8, F_CF | F_ES3},
+
+ // ES2 legacy
+ {"l" , GL_LUMINANCE,GL_LUMINANCE, T_U8, F_TF | F_ES2},
+ {"la",GL_LUMINANCE_ALPHA,GL_LUMINANCE_ALPHA, T_U8, F_TF | F_ES2},
+ {"rgb", GL_RGB, GL_RGB, T_U8, F_TF | F_ES2},
+ {"rgba", GL_RGBA, GL_RGBA, T_U8, F_TF | F_ES2},
+
+ // Non-normalized integer formats.
+ // Follows ES 3.0 as to which are color-renderable.
+ {"r8ui", GL_R8UI, GL_RED_INTEGER, T_U8, F_CR | F_GL3 | F_ES3},
+ {"rg8ui", GL_RG8UI, GL_RG_INTEGER, T_U8, F_CR | F_GL3 | F_ES3},
+ {"rgb8ui", GL_RGB8UI, GL_RGB_INTEGER, T_U8, F_GL3 | F_ES3},
+ {"rgba8ui", GL_RGBA8UI, GL_RGBA_INTEGER, T_U8, F_CR | F_GL3 | F_ES3},
+ {"r16ui", GL_R16UI, GL_RED_INTEGER, T_U16, F_CR | F_GL3 | F_ES3},
+ {"rg16ui", GL_RG16UI, GL_RG_INTEGER, T_U16, F_CR | F_GL3 | F_ES3},
+ {"rgb16ui", GL_RGB16UI, GL_RGB_INTEGER, T_U16, F_GL3 | F_ES3},
+ {"rgba16ui",GL_RGBA16UI, GL_RGBA_INTEGER, T_U16, F_CR | F_GL3 | F_ES3},
+
+ // On GL3+ or GL2.1 with GL_ARB_texture_float, floats work fully.
+ {"r16f", GL_R16F, GL_RED, T_FL, F_F16 | F_CF | F_GL3 | F_GL2F},
+ {"rg16f", GL_RG16F, GL_RG, T_FL, F_F16 | F_CF | F_GL3 | F_GL2F},
+ {"rgb16f", GL_RGB16F, GL_RGB, T_FL, F_F16 | F_CF | F_GL3 | F_GL2F},
+ {"rgba16f", GL_RGBA16F, GL_RGBA, T_FL, F_F16 | F_CF | F_GL3 | F_GL2F},
+ {"r32f", GL_R32F, GL_RED, T_FL, F_CF | F_GL3 | F_GL2F},
+ {"rg32f", GL_RG32F, GL_RG, T_FL, F_CF | F_GL3 | F_GL2F},
+ {"rgb32f", GL_RGB32F, GL_RGB, T_FL, F_CF | F_GL3 | F_GL2F},
+ {"rgba32f", GL_RGBA32F, GL_RGBA, T_FL, F_CF | F_GL3 | F_GL2F},
+
+ // Note: we simply don't support float anything on ES2, despite extensions.
+ // We also don't bother with non-filterable float formats, and we ignore
+ // 32 bit float formats that are not blendable when rendering to them.
+
+ // On ES3.2+, both 16 bit floats work fully (except 3-component formats).
+ // F_EXTF16 implies extensions that also enable 16 bit floats fully.
+ {"r16f", GL_R16F, GL_RED, T_FL, F_F16 | F_CF | F_ES32 | F_EXTF16},
+ {"rg16f", GL_RG16F, GL_RG, T_FL, F_F16 | F_CF | F_ES32 | F_EXTF16},
+ {"rgb16f", GL_RGB16F, GL_RGB, T_FL, F_F16 | F_TF | F_ES32 | F_EXTF16},
+ {"rgba16f", GL_RGBA16F, GL_RGBA, T_FL, F_F16 | F_CF | F_ES32 | F_EXTF16},
+
+ // On ES3.0+, 16 bit floats are texture-filterable.
+ // Don't bother with 32 bit floats; they exist but are neither CR nor TF.
+ {"r16f", GL_R16F, GL_RED, T_FL, F_F16 | F_TF | F_ES3},
+ {"rg16f", GL_RG16F, GL_RG, T_FL, F_F16 | F_TF | F_ES3},
+ {"rgb16f", GL_RGB16F, GL_RGB, T_FL, F_F16 | F_TF | F_ES3},
+ {"rgba16f", GL_RGBA16F, GL_RGBA, T_FL, F_F16 | F_TF | F_ES3},
+
+ // These might be useful as FBO formats.
+ {"rgb10_a2",GL_RGB10_A2, GL_RGBA,
+ GL_UNSIGNED_INT_2_10_10_10_REV, F_CF | F_GL3 | F_ES3},
+ {"rgba12", GL_RGBA12, GL_RGBA, T_U16, F_CF | F_GL2 | F_GL3},
+ {"rgb10", GL_RGB10, GL_RGB, T_U16, F_CF | F_GL2 | F_GL3},
+
+ // Special formats.
+ {"rgb565", GL_RGB8, GL_RGB,
+ GL_UNSIGNED_SHORT_5_6_5, F_TF | F_GL2 | F_GL3},
+ // Worthless, but needed by OSX videotoolbox interop on old Apple hardware.
+ {"appleyp", GL_RGB, GL_RGB_422_APPLE,
+ GL_UNSIGNED_SHORT_8_8_APPLE, F_TF | F_APPL},
+
+ {0}
+};
+
+// Return an or-ed combination of all F_ flags that apply.
+int gl_format_feature_flags(GL *gl)
+{
+ return (gl->version == 210 ? F_GL2 : 0)
+ | (gl->version >= 300 ? F_GL3 : 0)
+ | (gl->es == 200 ? F_ES2 : 0)
+ | (gl->es >= 300 ? F_ES3 : 0)
+ | (gl->es >= 320 ? F_ES32 : 0)
+ | (gl->mpgl_caps & MPGL_CAP_EXT16 ? F_EXT16 : 0)
+ | ((gl->es >= 300 &&
+ (gl->mpgl_caps & MPGL_CAP_EXT_CR_HFLOAT)) ? F_EXTF16 : 0)
+ | ((gl->version == 210 &&
+ (gl->mpgl_caps & MPGL_CAP_ARB_FLOAT) &&
+ (gl->mpgl_caps & MPGL_CAP_TEX_RG) &&
+ (gl->mpgl_caps & MPGL_CAP_FB)) ? F_GL2F : 0)
+ | (gl->mpgl_caps & MPGL_CAP_APPLE_RGB_422 ? F_APPL : 0);
+}
+
+int gl_format_type(const struct gl_format *format)
+{
+ if (!format)
+ return 0;
+ if (format->type == GL_FLOAT)
+ return MPGL_TYPE_FLOAT;
+ if (gl_integer_format_to_base(format->format))
+ return MPGL_TYPE_UINT;
+ return MPGL_TYPE_UNORM;
+}
+
+// Return base internal format of an integer format, or 0 if it's not integer.
+// "format" is like in struct gl_format.
+GLenum gl_integer_format_to_base(GLenum format)
+{
+ switch (format) {
+ case GL_RED_INTEGER: return GL_RED;
+ case GL_RG_INTEGER: return GL_RG;
+ case GL_RGB_INTEGER: return GL_RGB;
+ case GL_RGBA_INTEGER: return GL_RGBA;
+ }
+ return 0;
+}
+
+// Return the number of bytes per component this format implies.
+// Returns 0 for formats with non-byte alignments and formats which
+// merge multiple components (like GL_UNSIGNED_SHORT_5_6_5).
+// "type" is like in struct gl_format.
+int gl_component_size(GLenum type)
+{
+ switch (type) {
+ case GL_UNSIGNED_BYTE: return 1;
+ case GL_UNSIGNED_SHORT: return 2;
+ case GL_FLOAT: return 4;
+ }
+ return 0;
+}
+
+// Return the number of separate color components.
+// "format" is like in struct gl_format.
+int gl_format_components(GLenum format)
+{
+ switch (format) {
+ case GL_RED:
+ case GL_RED_INTEGER:
+ case GL_LUMINANCE:
+ return 1;
+ case GL_RG:
+ case GL_RG_INTEGER:
+ case GL_LUMINANCE_ALPHA:
+ return 2;
+ case GL_RGB:
+ case GL_RGB_INTEGER:
+ return 3;
+ case GL_RGBA:
+ case GL_RGBA_INTEGER:
+ return 4;
+ }
+ return 0;
+}
+
+// Return the number of bytes per pixel for the given format.
+// Parameter names like in struct gl_format.
+int gl_bytes_per_pixel(GLenum format, GLenum type)
+{
+ // Formats with merged components are special.
+ switch (type) {
+ case GL_UNSIGNED_INT_2_10_10_10_REV: return 4;
+ case GL_UNSIGNED_SHORT_5_6_5: return 2;
+ case GL_UNSIGNED_SHORT_8_8_APPLE: return 2;
+ case GL_UNSIGNED_SHORT_8_8_REV_APPLE: return 2;
+ }
+
+ return gl_component_size(type) * gl_format_components(format);
+}
diff --git a/video/out/opengl/formats.h b/video/out/opengl/formats.h
new file mode 100644
index 0000000..f727a3b
--- /dev/null
+++ b/video/out/opengl/formats.h
@@ -0,0 +1,51 @@
+#ifndef MPGL_FORMATS_H_
+#define MPGL_FORMATS_H_
+
+#include "common.h"
+
+struct gl_format {
+ const char *name; // symbolic name for user interaction/debugging
+ GLint internal_format; // glTexImage argument
+ GLenum format; // glTexImage argument
+ GLenum type; // e.g. GL_UNSIGNED_SHORT
+ int flags; // F_* flags
+};
+
+enum {
+ // --- gl_format.flags
+
+ // Version flags. If at least 1 flag matches, the format entry is considered
+ // supported on the current GL context.
+ F_GL2 = 1 << 0, // GL2.1-only
+ F_GL3 = 1 << 1, // GL3.0 or later
+ F_ES2 = 1 << 2, // ES2-only
+ F_ES3 = 1 << 3, // ES3.0 or later
+ F_ES32 = 1 << 4, // ES3.2 or later
+ F_EXT16 = 1 << 5, // ES with GL_EXT_texture_norm16
+ F_EXTF16 = 1 << 6, // GL_EXT_color_buffer_half_float
+ F_GL2F = 1 << 7, // GL2.1-only with texture_rg + texture_float + FBOs
+ F_APPL = 1 << 8, // GL_APPLE_rgb_422
+
+ // Feature flags. They are additional and signal presence of features.
+ F_CR = 1 << 16, // color-renderable
+ F_TF = 1 << 17, // texture-filterable with GL_LINEAR
+ F_CF = F_CR | F_TF,
+ F_F16 = 1 << 18, // uses half-floats (16 bit) internally, even though
+ // the format is still GL_FLOAT (32 bit)
+
+ // --- Other constants.
+ MPGL_TYPE_UNORM = RA_CTYPE_UNORM, // normalized integer (fixed point) formats
+ MPGL_TYPE_UINT = RA_CTYPE_UINT, // full integer formats
+ MPGL_TYPE_FLOAT = RA_CTYPE_FLOAT, // float formats (both full and half)
+};
+
+extern const struct gl_format gl_formats[];
+
+int gl_format_feature_flags(GL *gl);
+int gl_format_type(const struct gl_format *format);
+GLenum gl_integer_format_to_base(GLenum format);
+int gl_component_size(GLenum type);
+int gl_format_components(GLenum format);
+int gl_bytes_per_pixel(GLenum format, GLenum type);
+
+#endif
diff --git a/video/out/opengl/gl_headers.h b/video/out/opengl/gl_headers.h
new file mode 100644
index 0000000..5c36718
--- /dev/null
+++ b/video/out/opengl/gl_headers.h
@@ -0,0 +1,799 @@
+/*
+ * Parts of OpenGL(ES) needed by the OpenGL renderer.
+ *
+ * This excludes function declarations.
+ *
+ * This header is based on:
+ * - Khronos GLES headers (MIT)
+ * - mpv or MPlayer code (LGPL 2.1 or later)
+ * - probably Mesa GL headers (MIT)
+ */
+
+#ifndef MPV_GL_HEADERS_H
+#define MPV_GL_HEADERS_H
+
+#include <stdint.h>
+
+// Enable this to use system headers instead.
+#if 0
+#include <GL/gl.h>
+#include <GLES3/gl3.h>
+#endif
+
+#ifndef GLAPIENTRY
+#ifdef _WIN32
+#define GLAPIENTRY __stdcall
+#else
+#define GLAPIENTRY
+#endif
+#endif
+
+// Typedefs. This needs to work with system headers too (consider GLX), and
+// before C11, duplicated typedefs were an error. So try to tolerate at least
+// Mesa.
+#ifdef GL_TRUE
+ // Tolerate old Mesa which has only definitions up to GL 2.0.
+ #define MP_GET_GL_TYPES_2_0 0
+ #ifdef GL_VERSION_3_2
+ #define MP_GET_GL_TYPES_3_2 0
+ #else
+ #define MP_GET_GL_TYPES_3_2 1
+ #endif
+#else
+ // Get them all.
+ #define MP_GET_GL_TYPES_2_0 1
+ #define MP_GET_GL_TYPES_3_2 1
+#endif
+
+#if MP_GET_GL_TYPES_2_0
+// GL_VERSION_1_0, GL_ES_VERSION_2_0
+typedef unsigned int GLbitfield;
+typedef unsigned char GLboolean;
+typedef unsigned int GLenum;
+typedef float GLfloat;
+typedef int GLint;
+typedef int GLsizei;
+typedef uint8_t GLubyte;
+typedef unsigned int GLuint;
+typedef void GLvoid;
+// GL 1.1 GL_VERSION_1_1, GL_ES_VERSION_2_0
+typedef float GLclampf;
+// GL 1.5 GL_VERSION_1_5, GL_ES_VERSION_2_0
+typedef intptr_t GLintptr;
+typedef ptrdiff_t GLsizeiptr;
+// GL 2.0 GL_VERSION_2_0, GL_ES_VERSION_2_0
+typedef int8_t GLbyte;
+typedef char GLchar;
+typedef short GLshort;
+typedef unsigned short GLushort;
+#endif
+
+#if MP_GET_GL_TYPES_3_2
+// GL 3.2 GL_VERSION_3_2, GL_ES_VERSION_2_0
+typedef int64_t GLint64;
+typedef struct __GLsync *GLsync;
+typedef uint64_t GLuint64;
+#endif
+
+// --- GL 1.1
+
+#define GL_BACK_LEFT 0x0402
+#define GL_TEXTURE_1D 0x0DE0
+#define GL_RGB16 0x8054
+#define GL_RGB10 0x8052
+#define GL_RGBA12 0x805A
+#define GL_RGBA16 0x805B
+#define GL_TEXTURE_RED_SIZE 0x805C
+#define GL_TEXTURE_GREEN_SIZE 0x805D
+#define GL_TEXTURE_BLUE_SIZE 0x805E
+#define GL_TEXTURE_ALPHA_SIZE 0x805F
+
+// --- GL 1.1 (removed from 3.0 core and not in GLES 2/3)
+
+#define GL_TEXTURE_LUMINANCE_SIZE 0x8060
+#define GL_LUMINANCE8 0x8040
+#define GL_LUMINANCE8_ALPHA8 0x8045
+#define GL_LUMINANCE16 0x8042
+#define GL_LUMINANCE16_ALPHA16 0x8048
+
+// --- GL 1.5
+
+#define GL_READ_ONLY 0x88B8
+#define GL_WRITE_ONLY 0x88B9
+#define GL_READ_WRITE 0x88BA
+
+// --- GL 3.0
+
+#define GL_R16 0x822A
+#define GL_RG16 0x822C
+
+// --- GL 3.1
+
+#define GL_TEXTURE_RECTANGLE 0x84F5
+
+// --- GL 3.3 or GL_ARB_timer_query
+
+#define GL_TIME_ELAPSED 0x88BF
+#define GL_TIMESTAMP 0x8E28
+
+// --- GL 4.3 or GL_ARB_debug_output
+
+#define GL_DEBUG_SEVERITY_HIGH 0x9146
+#define GL_DEBUG_SEVERITY_MEDIUM 0x9147
+#define GL_DEBUG_SEVERITY_LOW 0x9148
+#define GL_DEBUG_SEVERITY_NOTIFICATION 0x826B
+
+// --- GL 4.4 or GL_ARB_buffer_storage
+
+#define GL_MAP_PERSISTENT_BIT 0x0040
+#define GL_MAP_COHERENT_BIT 0x0080
+#define GL_DYNAMIC_STORAGE_BIT 0x0100
+#define GL_CLIENT_STORAGE_BIT 0x0200
+
+// --- GL 4.2 or GL_ARB_image_load_store
+
+#define GL_TEXTURE_FETCH_BARRIER_BIT 0x00000008
+
+// --- GL 4.3 or GL_ARB_compute_shader
+
+#define GL_COMPUTE_SHADER 0x91B9
+#define GL_MAX_COMPUTE_SHARED_MEMORY_SIZE 0x8262
+#define GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS 0x90EB
+
+// --- GL 4.3 or GL_ARB_shader_storage_buffer_object
+
+#define GL_SHADER_STORAGE_BUFFER 0x90D2
+#define GL_SHADER_STORAGE_BARRIER_BIT 0x00002000
+
+// --- GL_NV_vdpau_interop
+
+#define GLvdpauSurfaceNV GLintptr
+#define GL_WRITE_DISCARD_NV 0x88BE
+
+// --- GL_OES_EGL_image_external, GL_NV_EGL_stream_consumer_external
+
+#define GL_TEXTURE_EXTERNAL_OES 0x8D65
+
+// --- GL_APPLE_rgb_422
+
+#define GL_RGB_422_APPLE 0x8A1F
+#define GL_UNSIGNED_SHORT_8_8_APPLE 0x85BA
+#define GL_UNSIGNED_SHORT_8_8_REV_APPLE 0x85BB
+
+// --- GL_ANGLE_translated_shader_source
+
+#define GL_TRANSLATED_SHADER_SOURCE_LENGTH_ANGLE 0x93A0
+
+// ---- GLES 2
+
+#define GL_DEPTH_BUFFER_BIT 0x00000100
+#define GL_STENCIL_BUFFER_BIT 0x00000400
+#define GL_COLOR_BUFFER_BIT 0x00004000
+#define GL_FALSE 0
+#define GL_TRUE 1
+#define GL_POINTS 0x0000
+#define GL_LINES 0x0001
+#define GL_LINE_LOOP 0x0002
+#define GL_LINE_STRIP 0x0003
+#define GL_TRIANGLES 0x0004
+#define GL_TRIANGLE_STRIP 0x0005
+#define GL_TRIANGLE_FAN 0x0006
+#define GL_ZERO 0
+#define GL_ONE 1
+#define GL_SRC_COLOR 0x0300
+#define GL_ONE_MINUS_SRC_COLOR 0x0301
+#define GL_SRC_ALPHA 0x0302
+#define GL_ONE_MINUS_SRC_ALPHA 0x0303
+#define GL_DST_ALPHA 0x0304
+#define GL_ONE_MINUS_DST_ALPHA 0x0305
+#define GL_DST_COLOR 0x0306
+#define GL_ONE_MINUS_DST_COLOR 0x0307
+#define GL_SRC_ALPHA_SATURATE 0x0308
+#define GL_FUNC_ADD 0x8006
+#define GL_BLEND_EQUATION 0x8009
+#define GL_BLEND_EQUATION_RGB 0x8009
+#define GL_BLEND_EQUATION_ALPHA 0x883D
+#define GL_FUNC_SUBTRACT 0x800A
+#define GL_FUNC_REVERSE_SUBTRACT 0x800B
+#define GL_BLEND_DST_RGB 0x80C8
+#define GL_BLEND_SRC_RGB 0x80C9
+#define GL_BLEND_DST_ALPHA 0x80CA
+#define GL_BLEND_SRC_ALPHA 0x80CB
+#define GL_CONSTANT_COLOR 0x8001
+#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002
+#define GL_CONSTANT_ALPHA 0x8003
+#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004
+#define GL_BLEND_COLOR 0x8005
+#define GL_ARRAY_BUFFER 0x8892
+#define GL_ELEMENT_ARRAY_BUFFER 0x8893
+#define GL_ARRAY_BUFFER_BINDING 0x8894
+#define GL_ELEMENT_ARRAY_BUFFER_BINDING 0x8895
+#define GL_STREAM_DRAW 0x88E0
+#define GL_STATIC_DRAW 0x88E4
+#define GL_DYNAMIC_DRAW 0x88E8
+#define GL_BUFFER_SIZE 0x8764
+#define GL_BUFFER_USAGE 0x8765
+#define GL_CURRENT_VERTEX_ATTRIB 0x8626
+#define GL_FRONT 0x0404
+#define GL_BACK 0x0405
+#define GL_FRONT_AND_BACK 0x0408
+#define GL_TEXTURE_2D 0x0DE1
+#define GL_CULL_FACE 0x0B44
+#define GL_BLEND 0x0BE2
+#define GL_DITHER 0x0BD0
+#define GL_STENCIL_TEST 0x0B90
+#define GL_DEPTH_TEST 0x0B71
+#define GL_SCISSOR_TEST 0x0C11
+#define GL_POLYGON_OFFSET_FILL 0x8037
+#define GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E
+#define GL_SAMPLE_COVERAGE 0x80A0
+#define GL_NO_ERROR 0
+#define GL_INVALID_ENUM 0x0500
+#define GL_INVALID_VALUE 0x0501
+#define GL_INVALID_OPERATION 0x0502
+#define GL_OUT_OF_MEMORY 0x0505
+#define GL_CW 0x0900
+#define GL_CCW 0x0901
+#define GL_LINE_WIDTH 0x0B21
+#define GL_ALIASED_POINT_SIZE_RANGE 0x846D
+#define GL_ALIASED_LINE_WIDTH_RANGE 0x846E
+#define GL_CULL_FACE_MODE 0x0B45
+#define GL_FRONT_FACE 0x0B46
+#define GL_DEPTH_RANGE 0x0B70
+#define GL_DEPTH_WRITEMASK 0x0B72
+#define GL_DEPTH_CLEAR_VALUE 0x0B73
+#define GL_DEPTH_FUNC 0x0B74
+#define GL_STENCIL_CLEAR_VALUE 0x0B91
+#define GL_STENCIL_FUNC 0x0B92
+#define GL_STENCIL_FAIL 0x0B94
+#define GL_STENCIL_PASS_DEPTH_FAIL 0x0B95
+#define GL_STENCIL_PASS_DEPTH_PASS 0x0B96
+#define GL_STENCIL_REF 0x0B97
+#define GL_STENCIL_VALUE_MASK 0x0B93
+#define GL_STENCIL_WRITEMASK 0x0B98
+#define GL_STENCIL_BACK_FUNC 0x8800
+#define GL_STENCIL_BACK_FAIL 0x8801
+#define GL_STENCIL_BACK_PASS_DEPTH_FAIL 0x8802
+#define GL_STENCIL_BACK_PASS_DEPTH_PASS 0x8803
+#define GL_STENCIL_BACK_REF 0x8CA3
+#define GL_STENCIL_BACK_VALUE_MASK 0x8CA4
+#define GL_STENCIL_BACK_WRITEMASK 0x8CA5
+#define GL_VIEWPORT 0x0BA2
+#define GL_SCISSOR_BOX 0x0C10
+#define GL_COLOR_CLEAR_VALUE 0x0C22
+#define GL_COLOR_WRITEMASK 0x0C23
+#define GL_UNPACK_ALIGNMENT 0x0CF5
+#define GL_PACK_ALIGNMENT 0x0D05
+#define GL_MAX_TEXTURE_SIZE 0x0D33
+#define GL_MAX_VIEWPORT_DIMS 0x0D3A
+#define GL_SUBPIXEL_BITS 0x0D50
+#define GL_RED_BITS 0x0D52
+#define GL_GREEN_BITS 0x0D53
+#define GL_BLUE_BITS 0x0D54
+#define GL_ALPHA_BITS 0x0D55
+#define GL_DEPTH_BITS 0x0D56
+#define GL_STENCIL_BITS 0x0D57
+#define GL_POLYGON_OFFSET_UNITS 0x2A00
+#define GL_POLYGON_OFFSET_FACTOR 0x8038
+#define GL_TEXTURE_BINDING_2D 0x8069
+#define GL_SAMPLE_BUFFERS 0x80A8
+#define GL_SAMPLES 0x80A9
+#define GL_SAMPLE_COVERAGE_VALUE 0x80AA
+#define GL_SAMPLE_COVERAGE_INVERT 0x80AB
+#define GL_NUM_COMPRESSED_TEXTURE_FORMATS 0x86A2
+#define GL_COMPRESSED_TEXTURE_FORMATS 0x86A3
+#define GL_DONT_CARE 0x1100
+#define GL_FASTEST 0x1101
+#define GL_NICEST 0x1102
+#define GL_GENERATE_MIPMAP_HINT 0x8192
+#define GL_BYTE 0x1400
+#define GL_UNSIGNED_BYTE 0x1401
+#define GL_SHORT 0x1402
+#define GL_UNSIGNED_SHORT 0x1403
+#define GL_INT 0x1404
+#define GL_UNSIGNED_INT 0x1405
+#define GL_FLOAT 0x1406
+#define GL_FIXED 0x140C
+#define GL_DEPTH_COMPONENT 0x1902
+#define GL_ALPHA 0x1906
+#define GL_RGB 0x1907
+#define GL_RGBA 0x1908
+#define GL_LUMINANCE 0x1909
+#define GL_LUMINANCE_ALPHA 0x190A
+#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033
+#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034
+#define GL_UNSIGNED_SHORT_5_6_5 0x8363
+#define GL_FRAGMENT_SHADER 0x8B30
+#define GL_VERTEX_SHADER 0x8B31
+#define GL_MAX_VERTEX_ATTRIBS 0x8869
+#define GL_MAX_VERTEX_UNIFORM_VECTORS 0x8DFB
+#define GL_MAX_VARYING_VECTORS 0x8DFC
+#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 0x8B4D
+#define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS 0x8B4C
+#define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872
+#define GL_MAX_FRAGMENT_UNIFORM_VECTORS 0x8DFD
+#define GL_SHADER_TYPE 0x8B4F
+#define GL_DELETE_STATUS 0x8B80
+#define GL_LINK_STATUS 0x8B82
+#define GL_VALIDATE_STATUS 0x8B83
+#define GL_ATTACHED_SHADERS 0x8B85
+#define GL_ACTIVE_UNIFORMS 0x8B86
+#define GL_ACTIVE_UNIFORM_MAX_LENGTH 0x8B87
+#define GL_ACTIVE_ATTRIBUTES 0x8B89
+#define GL_ACTIVE_ATTRIBUTE_MAX_LENGTH 0x8B8A
+#define GL_SHADING_LANGUAGE_VERSION 0x8B8C
+#define GL_CURRENT_PROGRAM 0x8B8D
+#define GL_NEVER 0x0200
+#define GL_LESS 0x0201
+#define GL_EQUAL 0x0202
+#define GL_LEQUAL 0x0203
+#define GL_GREATER 0x0204
+#define GL_NOTEQUAL 0x0205
+#define GL_GEQUAL 0x0206
+#define GL_ALWAYS 0x0207
+#define GL_KEEP 0x1E00
+#define GL_REPLACE 0x1E01
+#define GL_INCR 0x1E02
+#define GL_DECR 0x1E03
+#define GL_INVERT 0x150A
+#define GL_INCR_WRAP 0x8507
+#define GL_DECR_WRAP 0x8508
+#define GL_VENDOR 0x1F00
+#define GL_RENDERER 0x1F01
+#define GL_VERSION 0x1F02
+#define GL_EXTENSIONS 0x1F03
+#define GL_NEAREST 0x2600
+#define GL_LINEAR 0x2601
+#define GL_NEAREST_MIPMAP_NEAREST 0x2700
+#define GL_LINEAR_MIPMAP_NEAREST 0x2701
+#define GL_NEAREST_MIPMAP_LINEAR 0x2702
+#define GL_LINEAR_MIPMAP_LINEAR 0x2703
+#define GL_TEXTURE_MAG_FILTER 0x2800
+#define GL_TEXTURE_MIN_FILTER 0x2801
+#define GL_TEXTURE_WRAP_S 0x2802
+#define GL_TEXTURE_WRAP_T 0x2803
+#define GL_TEXTURE 0x1702
+#define GL_TEXTURE_CUBE_MAP 0x8513
+#define GL_TEXTURE_BINDING_CUBE_MAP 0x8514
+#define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515
+#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516
+#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517
+#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518
+#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519
+#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A
+#define GL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C
+#define GL_TEXTURE0 0x84C0
+#define GL_TEXTURE1 0x84C1
+#define GL_TEXTURE2 0x84C2
+#define GL_TEXTURE3 0x84C3
+#define GL_TEXTURE4 0x84C4
+#define GL_TEXTURE5 0x84C5
+#define GL_TEXTURE6 0x84C6
+#define GL_TEXTURE7 0x84C7
+#define GL_TEXTURE8 0x84C8
+#define GL_TEXTURE9 0x84C9
+#define GL_TEXTURE10 0x84CA
+#define GL_TEXTURE11 0x84CB
+#define GL_TEXTURE12 0x84CC
+#define GL_TEXTURE13 0x84CD
+#define GL_TEXTURE14 0x84CE
+#define GL_TEXTURE15 0x84CF
+#define GL_TEXTURE16 0x84D0
+#define GL_TEXTURE17 0x84D1
+#define GL_TEXTURE18 0x84D2
+#define GL_TEXTURE19 0x84D3
+#define GL_TEXTURE20 0x84D4
+#define GL_TEXTURE21 0x84D5
+#define GL_TEXTURE22 0x84D6
+#define GL_TEXTURE23 0x84D7
+#define GL_TEXTURE24 0x84D8
+#define GL_TEXTURE25 0x84D9
+#define GL_TEXTURE26 0x84DA
+#define GL_TEXTURE27 0x84DB
+#define GL_TEXTURE28 0x84DC
+#define GL_TEXTURE29 0x84DD
+#define GL_TEXTURE30 0x84DE
+#define GL_TEXTURE31 0x84DF
+#define GL_ACTIVE_TEXTURE 0x84E0
+#define GL_REPEAT 0x2901
+#define GL_CLAMP_TO_EDGE 0x812F
+#define GL_MIRRORED_REPEAT 0x8370
+#define GL_FLOAT_VEC2 0x8B50
+#define GL_FLOAT_VEC3 0x8B51
+#define GL_FLOAT_VEC4 0x8B52
+#define GL_INT_VEC2 0x8B53
+#define GL_INT_VEC3 0x8B54
+#define GL_INT_VEC4 0x8B55
+#define GL_BOOL 0x8B56
+#define GL_BOOL_VEC2 0x8B57
+#define GL_BOOL_VEC3 0x8B58
+#define GL_BOOL_VEC4 0x8B59
+#define GL_FLOAT_MAT2 0x8B5A
+#define GL_FLOAT_MAT3 0x8B5B
+#define GL_FLOAT_MAT4 0x8B5C
+#define GL_SAMPLER_2D 0x8B5E
+#define GL_SAMPLER_CUBE 0x8B60
+#define GL_VERTEX_ATTRIB_ARRAY_ENABLED 0x8622
+#define GL_VERTEX_ATTRIB_ARRAY_SIZE 0x8623
+#define GL_VERTEX_ATTRIB_ARRAY_STRIDE 0x8624
+#define GL_VERTEX_ATTRIB_ARRAY_TYPE 0x8625
+#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED 0x886A
+#define GL_VERTEX_ATTRIB_ARRAY_POINTER 0x8645
+#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING 0x889F
+#define GL_IMPLEMENTATION_COLOR_READ_TYPE 0x8B9A
+#define GL_IMPLEMENTATION_COLOR_READ_FORMAT 0x8B9B
+#define GL_COMPILE_STATUS 0x8B81
+#define GL_INFO_LOG_LENGTH 0x8B84
+#define GL_SHADER_SOURCE_LENGTH 0x8B88
+#define GL_SHADER_COMPILER 0x8DFA
+#define GL_SHADER_BINARY_FORMATS 0x8DF8
+#define GL_NUM_SHADER_BINARY_FORMATS 0x8DF9
+#define GL_LOW_FLOAT 0x8DF0
+#define GL_MEDIUM_FLOAT 0x8DF1
+#define GL_HIGH_FLOAT 0x8DF2
+#define GL_LOW_INT 0x8DF3
+#define GL_MEDIUM_INT 0x8DF4
+#define GL_HIGH_INT 0x8DF5
+#define GL_FRAMEBUFFER 0x8D40
+#define GL_RENDERBUFFER 0x8D41
+#define GL_RGBA4 0x8056
+#define GL_RGB5_A1 0x8057
+#define GL_RGB565 0x8D62
+#define GL_DEPTH_COMPONENT16 0x81A5
+#define GL_STENCIL_INDEX8 0x8D48
+#define GL_RENDERBUFFER_WIDTH 0x8D42
+#define GL_RENDERBUFFER_HEIGHT 0x8D43
+#define GL_RENDERBUFFER_INTERNAL_FORMAT 0x8D44
+#define GL_RENDERBUFFER_RED_SIZE 0x8D50
+#define GL_RENDERBUFFER_GREEN_SIZE 0x8D51
+#define GL_RENDERBUFFER_BLUE_SIZE 0x8D52
+#define GL_RENDERBUFFER_ALPHA_SIZE 0x8D53
+#define GL_RENDERBUFFER_DEPTH_SIZE 0x8D54
+#define GL_RENDERBUFFER_STENCIL_SIZE 0x8D55
+#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE 0x8CD0
+#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME 0x8CD1
+#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL 0x8CD2
+#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE 0x8CD3
+#define GL_COLOR_ATTACHMENT0 0x8CE0
+#define GL_DEPTH_ATTACHMENT 0x8D00
+#define GL_STENCIL_ATTACHMENT 0x8D20
+#define GL_NONE 0
+#define GL_FRAMEBUFFER_COMPLETE 0x8CD5
+#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT 0x8CD6
+#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 0x8CD7
+#define GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS 0x8CD9
+#define GL_FRAMEBUFFER_UNSUPPORTED 0x8CDD
+#define GL_FRAMEBUFFER_BINDING 0x8CA6
+#define GL_RENDERBUFFER_BINDING 0x8CA7
+#define GL_MAX_RENDERBUFFER_SIZE 0x84E8
+#define GL_INVALID_FRAMEBUFFER_OPERATION 0x0506
+
+// ---- GLES 3
+
+#ifndef GL_READ_BUFFER
+typedef unsigned short GLhalf;
+#endif
+
+#define GL_READ_BUFFER 0x0C02
+#define GL_UNPACK_ROW_LENGTH 0x0CF2
+#define GL_UNPACK_SKIP_ROWS 0x0CF3
+#define GL_UNPACK_SKIP_PIXELS 0x0CF4
+#define GL_PACK_ROW_LENGTH 0x0D02
+#define GL_PACK_SKIP_ROWS 0x0D03
+#define GL_PACK_SKIP_PIXELS 0x0D04
+#define GL_COLOR 0x1800
+#define GL_DEPTH 0x1801
+#define GL_STENCIL 0x1802
+#define GL_RED 0x1903
+#define GL_RGB8 0x8051
+#define GL_RGBA8 0x8058
+#define GL_RGB10_A2 0x8059
+#define GL_TEXTURE_BINDING_3D 0x806A
+#define GL_UNPACK_SKIP_IMAGES 0x806D
+#define GL_UNPACK_IMAGE_HEIGHT 0x806E
+#define GL_TEXTURE_3D 0x806F
+#define GL_TEXTURE_WRAP_R 0x8072
+#define GL_MAX_3D_TEXTURE_SIZE 0x8073
+#define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368
+#define GL_MAX_ELEMENTS_VERTICES 0x80E8
+#define GL_MAX_ELEMENTS_INDICES 0x80E9
+#define GL_TEXTURE_MIN_LOD 0x813A
+#define GL_TEXTURE_MAX_LOD 0x813B
+#define GL_TEXTURE_BASE_LEVEL 0x813C
+#define GL_TEXTURE_MAX_LEVEL 0x813D
+#define GL_MIN 0x8007
+#define GL_MAX 0x8008
+#define GL_DEPTH_COMPONENT24 0x81A6
+#define GL_MAX_TEXTURE_LOD_BIAS 0x84FD
+#define GL_TEXTURE_COMPARE_MODE 0x884C
+#define GL_TEXTURE_COMPARE_FUNC 0x884D
+#define GL_CURRENT_QUERY 0x8865
+#define GL_QUERY_RESULT 0x8866
+#define GL_QUERY_RESULT_AVAILABLE 0x8867
+#define GL_BUFFER_MAPPED 0x88BC
+#define GL_BUFFER_MAP_POINTER 0x88BD
+#define GL_STREAM_READ 0x88E1
+#define GL_STREAM_COPY 0x88E2
+#define GL_STATIC_READ 0x88E5
+#define GL_STATIC_COPY 0x88E6
+#define GL_DYNAMIC_READ 0x88E9
+#define GL_DYNAMIC_COPY 0x88EA
+#define GL_MAX_DRAW_BUFFERS 0x8824
+#define GL_DRAW_BUFFER0 0x8825
+#define GL_DRAW_BUFFER1 0x8826
+#define GL_DRAW_BUFFER2 0x8827
+#define GL_DRAW_BUFFER3 0x8828
+#define GL_DRAW_BUFFER4 0x8829
+#define GL_DRAW_BUFFER5 0x882A
+#define GL_DRAW_BUFFER6 0x882B
+#define GL_DRAW_BUFFER7 0x882C
+#define GL_DRAW_BUFFER8 0x882D
+#define GL_DRAW_BUFFER9 0x882E
+#define GL_DRAW_BUFFER10 0x882F
+#define GL_DRAW_BUFFER11 0x8830
+#define GL_DRAW_BUFFER12 0x8831
+#define GL_DRAW_BUFFER13 0x8832
+#define GL_DRAW_BUFFER14 0x8833
+#define GL_DRAW_BUFFER15 0x8834
+#define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS 0x8B49
+#define GL_MAX_VERTEX_UNIFORM_COMPONENTS 0x8B4A
+#define GL_SAMPLER_3D 0x8B5F
+#define GL_SAMPLER_2D_SHADOW 0x8B62
+#define GL_FRAGMENT_SHADER_DERIVATIVE_HINT 0x8B8B
+#define GL_PIXEL_PACK_BUFFER 0x88EB
+#define GL_PIXEL_UNPACK_BUFFER 0x88EC
+#define GL_PIXEL_PACK_BUFFER_BINDING 0x88ED
+#define GL_PIXEL_UNPACK_BUFFER_BINDING 0x88EF
+#define GL_FLOAT_MAT2x3 0x8B65
+#define GL_FLOAT_MAT2x4 0x8B66
+#define GL_FLOAT_MAT3x2 0x8B67
+#define GL_FLOAT_MAT3x4 0x8B68
+#define GL_FLOAT_MAT4x2 0x8B69
+#define GL_FLOAT_MAT4x3 0x8B6A
+#define GL_SRGB 0x8C40
+#define GL_SRGB8 0x8C41
+#define GL_SRGB8_ALPHA8 0x8C43
+#define GL_COMPARE_REF_TO_TEXTURE 0x884E
+#define GL_MAJOR_VERSION 0x821B
+#define GL_MINOR_VERSION 0x821C
+#define GL_NUM_EXTENSIONS 0x821D
+#define GL_RGBA32F 0x8814
+#define GL_RGB32F 0x8815
+#define GL_RGBA16F 0x881A
+#define GL_RGB16F 0x881B
+#define GL_VERTEX_ATTRIB_ARRAY_INTEGER 0x88FD
+#define GL_MAX_ARRAY_TEXTURE_LAYERS 0x88FF
+#define GL_MIN_PROGRAM_TEXEL_OFFSET 0x8904
+#define GL_MAX_PROGRAM_TEXEL_OFFSET 0x8905
+#define GL_MAX_VARYING_COMPONENTS 0x8B4B
+#define GL_TEXTURE_2D_ARRAY 0x8C1A
+#define GL_TEXTURE_BINDING_2D_ARRAY 0x8C1D
+#define GL_R11F_G11F_B10F 0x8C3A
+#define GL_UNSIGNED_INT_10F_11F_11F_REV 0x8C3B
+#define GL_RGB9_E5 0x8C3D
+#define GL_UNSIGNED_INT_5_9_9_9_REV 0x8C3E
+#define GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH 0x8C76
+#define GL_TRANSFORM_FEEDBACK_BUFFER_MODE 0x8C7F
+#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS 0x8C80
+#define GL_TRANSFORM_FEEDBACK_VARYINGS 0x8C83
+#define GL_TRANSFORM_FEEDBACK_BUFFER_START 0x8C84
+#define GL_TRANSFORM_FEEDBACK_BUFFER_SIZE 0x8C85
+#define GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN 0x8C88
+#define GL_RASTERIZER_DISCARD 0x8C89
+#define GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS 0x8C8A
+#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS 0x8C8B
+#define GL_INTERLEAVED_ATTRIBS 0x8C8C
+#define GL_SEPARATE_ATTRIBS 0x8C8D
+#define GL_TRANSFORM_FEEDBACK_BUFFER 0x8C8E
+#define GL_TRANSFORM_FEEDBACK_BUFFER_BINDING 0x8C8F
+#define GL_RGBA32UI 0x8D70
+#define GL_RGB32UI 0x8D71
+#define GL_RGBA16UI 0x8D76
+#define GL_RGB16UI 0x8D77
+#define GL_RGBA8UI 0x8D7C
+#define GL_RGB8UI 0x8D7D
+#define GL_RGBA32I 0x8D82
+#define GL_RGB32I 0x8D83
+#define GL_RGBA16I 0x8D88
+#define GL_RGB16I 0x8D89
+#define GL_RGBA8I 0x8D8E
+#define GL_RGB8I 0x8D8F
+#define GL_RED_INTEGER 0x8D94
+#define GL_RGB_INTEGER 0x8D98
+#define GL_RGBA_INTEGER 0x8D99
+#define GL_SAMPLER_2D_ARRAY 0x8DC1
+#define GL_SAMPLER_2D_ARRAY_SHADOW 0x8DC4
+#define GL_SAMPLER_CUBE_SHADOW 0x8DC5
+#define GL_UNSIGNED_INT_VEC2 0x8DC6
+#define GL_UNSIGNED_INT_VEC3 0x8DC7
+#define GL_UNSIGNED_INT_VEC4 0x8DC8
+#define GL_INT_SAMPLER_2D 0x8DCA
+#define GL_INT_SAMPLER_3D 0x8DCB
+#define GL_INT_SAMPLER_CUBE 0x8DCC
+#define GL_INT_SAMPLER_2D_ARRAY 0x8DCF
+#define GL_UNSIGNED_INT_SAMPLER_2D 0x8DD2
+#define GL_UNSIGNED_INT_SAMPLER_3D 0x8DD3
+#define GL_UNSIGNED_INT_SAMPLER_CUBE 0x8DD4
+#define GL_UNSIGNED_INT_SAMPLER_2D_ARRAY 0x8DD7
+#define GL_BUFFER_ACCESS_FLAGS 0x911F
+#define GL_BUFFER_MAP_LENGTH 0x9120
+#define GL_BUFFER_MAP_OFFSET 0x9121
+#define GL_DEPTH_COMPONENT32F 0x8CAC
+#define GL_DEPTH32F_STENCIL8 0x8CAD
+#define GL_FLOAT_32_UNSIGNED_INT_24_8_REV 0x8DAD
+#define GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING 0x8210
+#define GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE 0x8211
+#define GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE 0x8212
+#define GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE 0x8213
+#define GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE 0x8214
+#define GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE 0x8215
+#define GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE 0x8216
+#define GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE 0x8217
+#define GL_FRAMEBUFFER_DEFAULT 0x8218
+#define GL_FRAMEBUFFER_UNDEFINED 0x8219
+#define GL_DEPTH_STENCIL_ATTACHMENT 0x821A
+#define GL_DEPTH_STENCIL 0x84F9
+#define GL_UNSIGNED_INT_24_8 0x84FA
+#define GL_DEPTH24_STENCIL8 0x88F0
+#define GL_UNSIGNED_NORMALIZED 0x8C17
+#define GL_DRAW_FRAMEBUFFER_BINDING 0x8CA6
+#define GL_READ_FRAMEBUFFER 0x8CA8
+#define GL_DRAW_FRAMEBUFFER 0x8CA9
+#define GL_READ_FRAMEBUFFER_BINDING 0x8CAA
+#define GL_RENDERBUFFER_SAMPLES 0x8CAB
+#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER 0x8CD4
+#define GL_MAX_COLOR_ATTACHMENTS 0x8CDF
+#define GL_COLOR_ATTACHMENT1 0x8CE1
+#define GL_COLOR_ATTACHMENT2 0x8CE2
+#define GL_COLOR_ATTACHMENT3 0x8CE3
+#define GL_COLOR_ATTACHMENT4 0x8CE4
+#define GL_COLOR_ATTACHMENT5 0x8CE5
+#define GL_COLOR_ATTACHMENT6 0x8CE6
+#define GL_COLOR_ATTACHMENT7 0x8CE7
+#define GL_COLOR_ATTACHMENT8 0x8CE8
+#define GL_COLOR_ATTACHMENT9 0x8CE9
+#define GL_COLOR_ATTACHMENT10 0x8CEA
+#define GL_COLOR_ATTACHMENT11 0x8CEB
+#define GL_COLOR_ATTACHMENT12 0x8CEC
+#define GL_COLOR_ATTACHMENT13 0x8CED
+#define GL_COLOR_ATTACHMENT14 0x8CEE
+#define GL_COLOR_ATTACHMENT15 0x8CEF
+#define GL_COLOR_ATTACHMENT16 0x8CF0
+#define GL_COLOR_ATTACHMENT17 0x8CF1
+#define GL_COLOR_ATTACHMENT18 0x8CF2
+#define GL_COLOR_ATTACHMENT19 0x8CF3
+#define GL_COLOR_ATTACHMENT20 0x8CF4
+#define GL_COLOR_ATTACHMENT21 0x8CF5
+#define GL_COLOR_ATTACHMENT22 0x8CF6
+#define GL_COLOR_ATTACHMENT23 0x8CF7
+#define GL_COLOR_ATTACHMENT24 0x8CF8
+#define GL_COLOR_ATTACHMENT25 0x8CF9
+#define GL_COLOR_ATTACHMENT26 0x8CFA
+#define GL_COLOR_ATTACHMENT27 0x8CFB
+#define GL_COLOR_ATTACHMENT28 0x8CFC
+#define GL_COLOR_ATTACHMENT29 0x8CFD
+#define GL_COLOR_ATTACHMENT30 0x8CFE
+#define GL_COLOR_ATTACHMENT31 0x8CFF
+#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE 0x8D56
+#define GL_MAX_SAMPLES 0x8D57
+#define GL_HALF_FLOAT 0x140B
+#define GL_MAP_READ_BIT 0x0001
+#define GL_MAP_WRITE_BIT 0x0002
+#define GL_MAP_INVALIDATE_RANGE_BIT 0x0004
+#define GL_MAP_INVALIDATE_BUFFER_BIT 0x0008
+#define GL_MAP_FLUSH_EXPLICIT_BIT 0x0010
+#define GL_MAP_UNSYNCHRONIZED_BIT 0x0020
+#define GL_RG 0x8227
+#define GL_RG_INTEGER 0x8228
+#define GL_R8 0x8229
+#define GL_RG8 0x822B
+#define GL_R16F 0x822D
+#define GL_R32F 0x822E
+#define GL_RG16F 0x822F
+#define GL_RG32F 0x8230
+#define GL_R8I 0x8231
+#define GL_R8UI 0x8232
+#define GL_R16I 0x8233
+#define GL_R16UI 0x8234
+#define GL_R32I 0x8235
+#define GL_R32UI 0x8236
+#define GL_RG8I 0x8237
+#define GL_RG8UI 0x8238
+#define GL_RG16I 0x8239
+#define GL_RG16UI 0x823A
+#define GL_RG32I 0x823B
+#define GL_RG32UI 0x823C
+#define GL_VERTEX_ARRAY_BINDING 0x85B5
+#define GL_R8_SNORM 0x8F94
+#define GL_RG8_SNORM 0x8F95
+#define GL_RGB8_SNORM 0x8F96
+#define GL_RGBA8_SNORM 0x8F97
+#define GL_SIGNED_NORMALIZED 0x8F9C
+#define GL_PRIMITIVE_RESTART_FIXED_INDEX 0x8D69
+#define GL_COPY_READ_BUFFER 0x8F36
+#define GL_COPY_WRITE_BUFFER 0x8F37
+#define GL_COPY_READ_BUFFER_BINDING 0x8F36
+#define GL_COPY_WRITE_BUFFER_BINDING 0x8F37
+#define GL_UNIFORM_BUFFER 0x8A11
+#define GL_UNIFORM_BUFFER_BINDING 0x8A28
+#define GL_UNIFORM_BUFFER_START 0x8A29
+#define GL_UNIFORM_BUFFER_SIZE 0x8A2A
+#define GL_MAX_VERTEX_UNIFORM_BLOCKS 0x8A2B
+#define GL_MAX_FRAGMENT_UNIFORM_BLOCKS 0x8A2D
+#define GL_MAX_COMBINED_UNIFORM_BLOCKS 0x8A2E
+#define GL_MAX_UNIFORM_BUFFER_BINDINGS 0x8A2F
+#define GL_MAX_UNIFORM_BLOCK_SIZE 0x8A30
+#define GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS 0x8A31
+#define GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS 0x8A33
+#define GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT 0x8A34
+#define GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH 0x8A35
+#define GL_ACTIVE_UNIFORM_BLOCKS 0x8A36
+#define GL_UNIFORM_TYPE 0x8A37
+#define GL_UNIFORM_SIZE 0x8A38
+#define GL_UNIFORM_NAME_LENGTH 0x8A39
+#define GL_UNIFORM_BLOCK_INDEX 0x8A3A
+#define GL_UNIFORM_OFFSET 0x8A3B
+#define GL_UNIFORM_ARRAY_STRIDE 0x8A3C
+#define GL_UNIFORM_MATRIX_STRIDE 0x8A3D
+#define GL_UNIFORM_IS_ROW_MAJOR 0x8A3E
+#define GL_UNIFORM_BLOCK_BINDING 0x8A3F
+#define GL_UNIFORM_BLOCK_DATA_SIZE 0x8A40
+#define GL_UNIFORM_BLOCK_NAME_LENGTH 0x8A41
+#define GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS 0x8A42
+#define GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES 0x8A43
+#define GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER 0x8A44
+#define GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER 0x8A46
+#define GL_INVALID_INDEX 0xFFFFFFFFu
+#define GL_MAX_VERTEX_OUTPUT_COMPONENTS 0x9122
+#define GL_MAX_FRAGMENT_INPUT_COMPONENTS 0x9125
+#define GL_MAX_SERVER_WAIT_TIMEOUT 0x9111
+#define GL_OBJECT_TYPE 0x9112
+#define GL_SYNC_CONDITION 0x9113
+#define GL_SYNC_STATUS 0x9114
+#define GL_SYNC_FLAGS 0x9115
+#define GL_SYNC_FENCE 0x9116
+#define GL_SYNC_GPU_COMMANDS_COMPLETE 0x9117
+#define GL_UNSIGNALED 0x9118
+#define GL_SIGNALED 0x9119
+#define GL_ALREADY_SIGNALED 0x911A
+#define GL_TIMEOUT_EXPIRED 0x911B
+#define GL_CONDITION_SATISFIED 0x911C
+#define GL_WAIT_FAILED 0x911D
+#define GL_SYNC_FLUSH_COMMANDS_BIT 0x00000001
+#define GL_TIMEOUT_IGNORED 0xFFFFFFFFFFFFFFFFull
+#define GL_VERTEX_ATTRIB_ARRAY_DIVISOR 0x88FE
+#define GL_ANY_SAMPLES_PASSED 0x8C2F
+#define GL_ANY_SAMPLES_PASSED_CONSERVATIVE 0x8D6A
+#define GL_SAMPLER_BINDING 0x8919
+#define GL_RGB10_A2UI 0x906F
+#define GL_TEXTURE_SWIZZLE_R 0x8E42
+#define GL_TEXTURE_SWIZZLE_G 0x8E43
+#define GL_TEXTURE_SWIZZLE_B 0x8E44
+#define GL_TEXTURE_SWIZZLE_A 0x8E45
+#define GL_GREEN 0x1904
+#define GL_BLUE 0x1905
+#define GL_INT_2_10_10_10_REV 0x8D9F
+#define GL_TRANSFORM_FEEDBACK 0x8E22
+#define GL_TRANSFORM_FEEDBACK_PAUSED 0x8E23
+#define GL_TRANSFORM_FEEDBACK_ACTIVE 0x8E24
+#define GL_TRANSFORM_FEEDBACK_BINDING 0x8E25
+#define GL_PROGRAM_BINARY_RETRIEVABLE_HINT 0x8257
+#define GL_PROGRAM_BINARY_LENGTH 0x8741
+#define GL_NUM_PROGRAM_BINARY_FORMATS 0x87FE
+#define GL_PROGRAM_BINARY_FORMATS 0x87FF
+#define GL_COMPRESSED_R11_EAC 0x9270
+#define GL_COMPRESSED_SIGNED_R11_EAC 0x9271
+#define GL_COMPRESSED_RG11_EAC 0x9272
+#define GL_COMPRESSED_SIGNED_RG11_EAC 0x9273
+#define GL_COMPRESSED_RGB8_ETC2 0x9274
+#define GL_COMPRESSED_SRGB8_ETC2 0x9275
+#define GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 0x9276
+#define GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 0x9277
+#define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278
+#define GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC 0x9279
+#define GL_TEXTURE_IMMUTABLE_FORMAT 0x912F
+#define GL_MAX_ELEMENT_INDEX 0x8D6B
+#define GL_NUM_SAMPLE_COUNTS 0x9380
+#define GL_TEXTURE_IMMUTABLE_LEVELS 0x82DF
+
+#endif
diff --git a/video/out/opengl/hwdec_d3d11egl.c b/video/out/opengl/hwdec_d3d11egl.c
new file mode 100644
index 0000000..c312091
--- /dev/null
+++ b/video/out/opengl/hwdec_d3d11egl.c
@@ -0,0 +1,363 @@
+/*
+ * 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 <assert.h>
+#include <windows.h>
+#include <d3d11.h>
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
+#include "angle_dynamic.h"
+
+#include "common/common.h"
+#include "osdep/timer.h"
+#include "osdep/windows_utils.h"
+#include "video/out/gpu/hwdec.h"
+#include "ra_gl.h"
+#include "video/hwdec.h"
+#include "video/d3d.h"
+
+#ifndef EGL_D3D_TEXTURE_SUBRESOURCE_ID_ANGLE
+#define EGL_D3D_TEXTURE_SUBRESOURCE_ID_ANGLE 0x33AB
+#endif
+
+struct priv_owner {
+ struct mp_hwdec_ctx hwctx;
+
+ ID3D11Device *d3d11_device;
+ EGLDisplay egl_display;
+
+ // EGL_KHR_stream
+ EGLStreamKHR (EGLAPIENTRY *CreateStreamKHR)(EGLDisplay dpy,
+ const EGLint *attrib_list);
+ EGLBoolean (EGLAPIENTRY *DestroyStreamKHR)(EGLDisplay dpy,
+ EGLStreamKHR stream);
+
+ // EGL_KHR_stream_consumer_gltexture
+ EGLBoolean (EGLAPIENTRY *StreamConsumerAcquireKHR)
+ (EGLDisplay dpy, EGLStreamKHR stream);
+ EGLBoolean (EGLAPIENTRY *StreamConsumerReleaseKHR)
+ (EGLDisplay dpy, EGLStreamKHR stream);
+
+ // EGL_NV_stream_consumer_gltexture_yuv
+ EGLBoolean (EGLAPIENTRY *StreamConsumerGLTextureExternalAttribsNV)
+ (EGLDisplay dpy, EGLStreamKHR stream, EGLAttrib *attrib_list);
+
+ // EGL_ANGLE_stream_producer_d3d_texture
+ EGLBoolean (EGLAPIENTRY *CreateStreamProducerD3DTextureANGLE)
+ (EGLDisplay dpy, EGLStreamKHR stream, const EGLAttrib *attrib_list);
+ EGLBoolean (EGLAPIENTRY *StreamPostD3DTextureANGLE)
+ (EGLDisplay dpy, EGLStreamKHR stream, void *texture,
+ const EGLAttrib *attrib_list);
+};
+
+struct priv {
+ EGLStreamKHR egl_stream;
+ GLuint gl_textures[2];
+};
+
+static void uninit(struct ra_hwdec *hw)
+{
+ struct priv_owner *p = hw->priv;
+
+ hwdec_devices_remove(hw->devs, &p->hwctx);
+
+ if (p->d3d11_device)
+ ID3D11Device_Release(p->d3d11_device);
+}
+
+static int init(struct ra_hwdec *hw)
+{
+ struct priv_owner *p = hw->priv;
+ HRESULT hr;
+
+ if (!ra_is_gl(hw->ra_ctx->ra))
+ return -1;
+ if (!angle_load())
+ return -1;
+
+ EGLDisplay egl_display = eglGetCurrentDisplay();
+ if (!egl_display)
+ return -1;
+
+ if (!eglGetCurrentContext())
+ return -1;
+
+ GL *gl = ra_gl_get(hw->ra_ctx->ra);
+
+ const char *exts = eglQueryString(egl_display, EGL_EXTENSIONS);
+ if (!gl_check_extension(exts, "EGL_ANGLE_d3d_share_handle_client_buffer") ||
+ !gl_check_extension(exts, "EGL_ANGLE_stream_producer_d3d_texture") ||
+ !(gl_check_extension(gl->extensions, "GL_OES_EGL_image_external_essl3") ||
+ gl->es == 200) ||
+ !gl_check_extension(exts, "EGL_EXT_device_query") ||
+ !(gl->mpgl_caps & MPGL_CAP_TEX_RG))
+ return -1;
+
+ p->egl_display = egl_display;
+
+ p->CreateStreamKHR = (void *)eglGetProcAddress("eglCreateStreamKHR");
+ p->DestroyStreamKHR = (void *)eglGetProcAddress("eglDestroyStreamKHR");
+ p->StreamConsumerAcquireKHR =
+ (void *)eglGetProcAddress("eglStreamConsumerAcquireKHR");
+ p->StreamConsumerReleaseKHR =
+ (void *)eglGetProcAddress("eglStreamConsumerReleaseKHR");
+ p->StreamConsumerGLTextureExternalAttribsNV =
+ (void *)eglGetProcAddress("eglStreamConsumerGLTextureExternalAttribsNV");
+ p->CreateStreamProducerD3DTextureANGLE =
+ (void *)eglGetProcAddress("eglCreateStreamProducerD3DTextureANGLE");
+ p->StreamPostD3DTextureANGLE =
+ (void *)eglGetProcAddress("eglStreamPostD3DTextureANGLE");
+
+ if (!p->CreateStreamKHR || !p->DestroyStreamKHR ||
+ !p->StreamConsumerAcquireKHR || !p->StreamConsumerReleaseKHR ||
+ !p->StreamConsumerGLTextureExternalAttribsNV ||
+ !p->CreateStreamProducerD3DTextureANGLE ||
+ !p->StreamPostD3DTextureANGLE)
+ {
+ MP_ERR(hw, "Failed to load some EGLStream functions.\n");
+ goto fail;
+ }
+
+ static const char *es2_exts[] = {"GL_NV_EGL_stream_consumer_external", 0};
+ static const char *es3_exts[] = {"GL_NV_EGL_stream_consumer_external",
+ "GL_OES_EGL_image_external_essl3", 0};
+ hw->glsl_extensions = gl->es == 200 ? es2_exts : es3_exts;
+
+ PFNEGLQUERYDISPLAYATTRIBEXTPROC p_eglQueryDisplayAttribEXT =
+ (void *)eglGetProcAddress("eglQueryDisplayAttribEXT");
+ PFNEGLQUERYDEVICEATTRIBEXTPROC p_eglQueryDeviceAttribEXT =
+ (void *)eglGetProcAddress("eglQueryDeviceAttribEXT");
+ if (!p_eglQueryDisplayAttribEXT || !p_eglQueryDeviceAttribEXT)
+ goto fail;
+
+ EGLAttrib device = 0;
+ if (!p_eglQueryDisplayAttribEXT(egl_display, EGL_DEVICE_EXT, &device))
+ goto fail;
+ EGLAttrib d3d_device = 0;
+ if (!p_eglQueryDeviceAttribEXT((EGLDeviceEXT)device,
+ EGL_D3D11_DEVICE_ANGLE, &d3d_device))
+ {
+ MP_ERR(hw, "Could not get EGL_D3D11_DEVICE_ANGLE from ANGLE.\n");
+ goto fail;
+ }
+
+ p->d3d11_device = (ID3D11Device *)d3d_device;
+ if (!p->d3d11_device)
+ goto fail;
+ ID3D11Device_AddRef(p->d3d11_device);
+
+ if (!d3d11_check_decoding(p->d3d11_device)) {
+ MP_VERBOSE(hw, "D3D11 video decoding not supported on this system.\n");
+ goto fail;
+ }
+
+ ID3D10Multithread *multithread;
+ hr = ID3D11Device_QueryInterface(p->d3d11_device, &IID_ID3D10Multithread,
+ (void **)&multithread);
+ if (FAILED(hr)) {
+ MP_ERR(hw, "Failed to get Multithread interface: %s\n",
+ mp_HRESULT_to_str(hr));
+ goto fail;
+ }
+ ID3D10Multithread_SetMultithreadProtected(multithread, TRUE);
+ ID3D10Multithread_Release(multithread);
+
+ static const int subfmts[] = {IMGFMT_NV12, IMGFMT_P010, 0};
+ p->hwctx = (struct mp_hwdec_ctx){
+ .driver_name = hw->driver->name,
+ .av_device_ref = d3d11_wrap_device_ref(p->d3d11_device),
+ .supported_formats = subfmts,
+ .hw_imgfmt = IMGFMT_D3D11,
+ };
+
+ if (!p->hwctx.av_device_ref) {
+ MP_VERBOSE(hw, "Failed to create hwdevice_ctx\n");
+ return -1;
+ }
+
+ hwdec_devices_add(hw->devs, &p->hwctx);
+
+ return 0;
+fail:
+ return -1;
+}
+
+static void mapper_uninit(struct ra_hwdec_mapper *mapper)
+{
+ struct priv_owner *o = mapper->owner->priv;
+ struct priv *p = mapper->priv;
+ GL *gl = ra_gl_get(mapper->ra);
+
+ if (p->egl_stream)
+ o->DestroyStreamKHR(o->egl_display, p->egl_stream);
+ p->egl_stream = 0;
+
+ gl->DeleteTextures(2, p->gl_textures);
+}
+
+static int mapper_init(struct ra_hwdec_mapper *mapper)
+{
+ struct priv_owner *o = mapper->owner->priv;
+ struct priv *p = mapper->priv;
+ GL *gl = ra_gl_get(mapper->ra);
+
+ struct ra_imgfmt_desc desc = {0};
+
+ ra_get_imgfmt_desc(mapper->ra, mapper->src_params.hw_subfmt, &desc);
+
+ // ANGLE hardcodes the list of accepted formats. This is a subset.
+ if ((mapper->src_params.hw_subfmt != IMGFMT_NV12 &&
+ mapper->src_params.hw_subfmt != IMGFMT_P010) ||
+ desc.num_planes < 1 || desc.num_planes > 2)
+ {
+ MP_FATAL(mapper, "Format not supported.\n");
+ return -1;
+ }
+
+ mapper->dst_params = mapper->src_params;
+ mapper->dst_params.imgfmt = mapper->src_params.hw_subfmt;
+ mapper->dst_params.hw_subfmt = 0;
+
+ // The texture units need to be bound during init only, and are free for
+ // use again after the initialization here is done.
+ int texunits = 0; // [texunits, texunits + num_planes)
+ int num_planes = desc.num_planes;
+ int gl_target = GL_TEXTURE_EXTERNAL_OES;
+
+ p->egl_stream = o->CreateStreamKHR(o->egl_display, (EGLint[]){EGL_NONE});
+ if (!p->egl_stream)
+ goto fail;
+
+ EGLAttrib attrs[(2 + 2 + 1) * 2] = {
+ EGL_COLOR_BUFFER_TYPE, EGL_YUV_BUFFER_EXT,
+ EGL_YUV_NUMBER_OF_PLANES_EXT, num_planes,
+ };
+
+ for (int n = 0; n < num_planes; n++) {
+ gl->ActiveTexture(GL_TEXTURE0 + texunits + n);
+ gl->GenTextures(1, &p->gl_textures[n]);
+ gl->BindTexture(gl_target, p->gl_textures[n]);
+ gl->TexParameteri(gl_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ gl->TexParameteri(gl_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ gl->TexParameteri(gl_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ gl->TexParameteri(gl_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ attrs[(2 + n) * 2 + 0] = EGL_YUV_PLANE0_TEXTURE_UNIT_NV + n;
+ attrs[(2 + n) * 2 + 1] = texunits + n;
+ }
+
+ attrs[(2 + num_planes) * 2 + 0] = EGL_NONE;
+
+ if (!o->StreamConsumerGLTextureExternalAttribsNV(o->egl_display, p->egl_stream,
+ attrs))
+ goto fail;
+
+ if (!o->CreateStreamProducerD3DTextureANGLE(o->egl_display, p->egl_stream,
+ (EGLAttrib[]){EGL_NONE}))
+ goto fail;
+
+ for (int n = 0; n < num_planes; n++) {
+ gl->ActiveTexture(GL_TEXTURE0 + texunits + n);
+ gl->BindTexture(gl_target, 0);
+ }
+ gl->ActiveTexture(GL_TEXTURE0);
+ return 0;
+fail:
+ gl->ActiveTexture(GL_TEXTURE0);
+ MP_ERR(mapper, "Failed to create EGLStream\n");
+ return -1;
+}
+
+static int mapper_map(struct ra_hwdec_mapper *mapper)
+{
+ struct priv_owner *o = mapper->owner->priv;
+ struct priv *p = mapper->priv;
+
+ ID3D11Texture2D *d3d_tex = (void *)mapper->src->planes[0];
+ int d3d_subindex = (intptr_t)mapper->src->planes[1];
+ if (!d3d_tex)
+ return -1;
+
+ EGLAttrib attrs[] = {
+ EGL_D3D_TEXTURE_SUBRESOURCE_ID_ANGLE, d3d_subindex,
+ EGL_NONE,
+ };
+ if (!o->StreamPostD3DTextureANGLE(o->egl_display, p->egl_stream,
+ (void *)d3d_tex, attrs))
+ {
+ // ANGLE changed the enum ID of this without warning at one point.
+ attrs[0] = attrs[0] == 0x33AB ? 0x3AAB : 0x33AB;
+ if (!o->StreamPostD3DTextureANGLE(o->egl_display, p->egl_stream,
+ (void *)d3d_tex, attrs))
+ return -1;
+ }
+
+ if (!o->StreamConsumerAcquireKHR(o->egl_display, p->egl_stream))
+ return -1;
+
+ D3D11_TEXTURE2D_DESC texdesc;
+ ID3D11Texture2D_GetDesc(d3d_tex, &texdesc);
+
+ for (int n = 0; n < 2; n++) {
+ struct ra_tex_params params = {
+ .dimensions = 2,
+ .w = texdesc.Width / (n ? 2 : 1),
+ .h = texdesc.Height / (n ? 2 : 1),
+ .d = 1,
+ .format = ra_find_unorm_format(mapper->ra, 1, n ? 2 : 1),
+ .render_src = true,
+ .src_linear = true,
+ .external_oes = true,
+ };
+ if (!params.format)
+ return -1;
+
+ mapper->tex[n] = ra_create_wrapped_tex(mapper->ra, &params,
+ p->gl_textures[n]);
+ if (!mapper->tex[n])
+ return -1;
+ }
+
+ return 0;
+}
+
+static void mapper_unmap(struct ra_hwdec_mapper *mapper)
+{
+ struct priv_owner *o = mapper->owner->priv;
+ struct priv *p = mapper->priv;
+
+ for (int n = 0; n < 2; n++)
+ ra_tex_free(mapper->ra, &mapper->tex[n]);
+ if (p->egl_stream)
+ o->StreamConsumerReleaseKHR(o->egl_display, p->egl_stream);
+}
+
+const struct ra_hwdec_driver ra_hwdec_d3d11egl = {
+ .name = "d3d11-egl",
+ .priv_size = sizeof(struct priv_owner),
+ .imgfmts = {IMGFMT_D3D11, 0},
+ .init = init,
+ .uninit = uninit,
+ .mapper = &(const struct ra_hwdec_mapper_driver){
+ .priv_size = sizeof(struct priv),
+ .init = mapper_init,
+ .uninit = mapper_uninit,
+ .map = mapper_map,
+ .unmap = mapper_unmap,
+ },
+};
diff --git a/video/out/opengl/hwdec_dxva2egl.c b/video/out/opengl/hwdec_dxva2egl.c
new file mode 100644
index 0000000..979ef59
--- /dev/null
+++ b/video/out/opengl/hwdec_dxva2egl.c
@@ -0,0 +1,384 @@
+/*
+ * 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 <assert.h>
+#include <windows.h>
+#include <d3d9.h>
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
+#include "angle_dynamic.h"
+
+#include "common/common.h"
+#include "osdep/timer.h"
+#include "osdep/windows_utils.h"
+#include "video/out/gpu/hwdec.h"
+#include "ra_gl.h"
+#include "video/hwdec.h"
+#include "video/d3d.h"
+
+struct priv_owner {
+ struct mp_hwdec_ctx hwctx;
+ IDirect3D9Ex *d3d9ex;
+ IDirect3DDevice9Ex *device9ex;
+
+ EGLDisplay egl_display;
+ EGLConfig egl_config;
+ EGLint alpha;
+};
+
+struct priv {
+ IDirect3DDevice9Ex *device9ex; // (no own reference)
+ IDirect3DQuery9 *query9;
+ IDirect3DTexture9 *texture9;
+ IDirect3DSurface9 *surface9;
+
+ EGLDisplay egl_display;
+ EGLSurface egl_surface;
+
+ GLuint gl_texture;
+};
+
+static void uninit(struct ra_hwdec *hw)
+{
+ struct priv_owner *p = hw->priv;
+
+ hwdec_devices_remove(hw->devs, &p->hwctx);
+ av_buffer_unref(&p->hwctx.av_device_ref);
+
+ if (p->device9ex)
+ IDirect3DDevice9Ex_Release(p->device9ex);
+
+ if (p->d3d9ex)
+ IDirect3D9Ex_Release(p->d3d9ex);
+}
+
+static int init(struct ra_hwdec *hw)
+{
+ struct priv_owner *p = hw->priv;
+ HRESULT hr;
+
+ if (!ra_is_gl(hw->ra_ctx->ra))
+ return -1;
+ if (!angle_load())
+ return -1;
+
+ d3d_load_dlls();
+
+ EGLDisplay egl_display = eglGetCurrentDisplay();
+ if (!egl_display)
+ return -1;
+
+ if (!eglGetCurrentContext())
+ return -1;
+
+ const char *exts = eglQueryString(egl_display, EGL_EXTENSIONS);
+ if (!gl_check_extension(exts, "EGL_ANGLE_d3d_share_handle_client_buffer")) {
+ return -1;
+ }
+
+ p->egl_display = egl_display;
+
+ if (!d3d9_dll) {
+ MP_FATAL(hw, "Failed to load \"d3d9.dll\": %s\n",
+ mp_LastError_to_str());
+ goto fail;
+ }
+
+ HRESULT (WINAPI *Direct3DCreate9Ex)(UINT SDKVersion, IDirect3D9Ex **ppD3D);
+ Direct3DCreate9Ex = (void *)GetProcAddress(d3d9_dll, "Direct3DCreate9Ex");
+ if (!Direct3DCreate9Ex) {
+ MP_FATAL(hw, "Direct3D 9Ex not supported\n");
+ goto fail;
+ }
+
+ hr = Direct3DCreate9Ex(D3D_SDK_VERSION, &p->d3d9ex);
+ if (FAILED(hr)) {
+ MP_FATAL(hw, "Couldn't create Direct3D9Ex: %s\n",
+ mp_HRESULT_to_str(hr));
+ goto fail;
+ }
+
+ // We must create our own Direct3D9Ex device. ANGLE can give us the device
+ // it's using, but that's probably a ID3D11Device.
+ // (copied from chromium dxva_video_decode_accelerator_win.cc)
+ D3DPRESENT_PARAMETERS present_params = {
+ .BackBufferWidth = 1,
+ .BackBufferHeight = 1,
+ .BackBufferFormat = D3DFMT_UNKNOWN,
+ .BackBufferCount = 1,
+ .SwapEffect = D3DSWAPEFFECT_DISCARD,
+ .hDeviceWindow = NULL,
+ .Windowed = TRUE,
+ .Flags = D3DPRESENTFLAG_VIDEO,
+ .FullScreen_RefreshRateInHz = 0,
+ .PresentationInterval = 0,
+ };
+ hr = IDirect3D9Ex_CreateDeviceEx(p->d3d9ex,
+ D3DADAPTER_DEFAULT,
+ D3DDEVTYPE_HAL,
+ NULL,
+ D3DCREATE_FPU_PRESERVE |
+ D3DCREATE_HARDWARE_VERTEXPROCESSING |
+ D3DCREATE_DISABLE_PSGP_THREADING |
+ D3DCREATE_MULTITHREADED,
+ &present_params,
+ NULL,
+ &p->device9ex);
+ if (FAILED(hr)) {
+ MP_FATAL(hw, "Failed to create Direct3D9Ex device: %s\n",
+ mp_HRESULT_to_str(hr));
+ goto fail;
+ }
+
+ EGLint attrs[] = {
+ EGL_BUFFER_SIZE, 32,
+ EGL_RED_SIZE, 8,
+ EGL_GREEN_SIZE, 8,
+ EGL_BLUE_SIZE, 8,
+ EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
+ EGL_ALPHA_SIZE, 0,
+ EGL_NONE
+ };
+ EGLint count;
+ if (!eglChooseConfig(p->egl_display, attrs, &p->egl_config, 1, &count) ||
+ !count) {
+ MP_ERR(hw, "Failed to get EGL surface configuration\n");
+ goto fail;
+ }
+
+ if (!eglGetConfigAttrib(p->egl_display, p->egl_config,
+ EGL_BIND_TO_TEXTURE_RGBA, &p->alpha)) {
+ MP_FATAL(hw, "Failed to query EGL surface alpha\n");
+ goto fail;
+ }
+
+ struct mp_image_params dummy_params = {
+ .imgfmt = IMGFMT_DXVA2,
+ .w = 256,
+ .h = 256,
+ };
+ struct ra_hwdec_mapper *mapper = ra_hwdec_mapper_create(hw, &dummy_params);
+ if (!mapper)
+ goto fail;
+ ra_hwdec_mapper_free(&mapper);
+
+ p->hwctx = (struct mp_hwdec_ctx){
+ .driver_name = hw->driver->name,
+ .av_device_ref = d3d9_wrap_device_ref((IDirect3DDevice9 *)p->device9ex),
+ .hw_imgfmt = IMGFMT_DXVA2,
+ };
+
+ if (!p->hwctx.av_device_ref) {
+ MP_VERBOSE(hw, "Failed to create hwdevice_ctx\n");
+ goto fail;
+ }
+
+ hwdec_devices_add(hw->devs, &p->hwctx);
+
+ return 0;
+fail:
+ return -1;
+}
+
+static void mapper_uninit(struct ra_hwdec_mapper *mapper)
+{
+ struct priv *p = mapper->priv;
+ GL *gl = ra_gl_get(mapper->ra);
+
+ ra_tex_free(mapper->ra, &mapper->tex[0]);
+ gl->DeleteTextures(1, &p->gl_texture);
+
+ if (p->egl_display && p->egl_surface) {
+ eglReleaseTexImage(p->egl_display, p->egl_surface, EGL_BACK_BUFFER);
+ eglDestroySurface(p->egl_display, p->egl_surface);
+ }
+
+ if (p->surface9)
+ IDirect3DSurface9_Release(p->surface9);
+
+ if (p->texture9)
+ IDirect3DTexture9_Release(p->texture9);
+
+ if (p->query9)
+ IDirect3DQuery9_Release(p->query9);
+}
+
+static int mapper_init(struct ra_hwdec_mapper *mapper)
+{
+ struct priv_owner *p_owner = mapper->owner->priv;
+ struct priv *p = mapper->priv;
+ GL *gl = ra_gl_get(mapper->ra);
+ HRESULT hr;
+
+ p->device9ex = p_owner->device9ex;
+ p->egl_display = p_owner->egl_display;
+
+ hr = IDirect3DDevice9_CreateQuery(p->device9ex, D3DQUERYTYPE_EVENT,
+ &p->query9);
+ if (FAILED(hr)) {
+ MP_FATAL(mapper, "Failed to create Direct3D query interface: %s\n",
+ mp_HRESULT_to_str(hr));
+ goto fail;
+ }
+
+ // Test the query API
+ hr = IDirect3DQuery9_Issue(p->query9, D3DISSUE_END);
+ if (FAILED(hr)) {
+ MP_FATAL(mapper, "Failed to issue Direct3D END test query: %s\n",
+ mp_HRESULT_to_str(hr));
+ goto fail;
+ }
+
+ HANDLE share_handle = NULL;
+ hr = IDirect3DDevice9Ex_CreateTexture(p->device9ex,
+ mapper->src_params.w,
+ mapper->src_params.h,
+ 1, D3DUSAGE_RENDERTARGET,
+ p_owner->alpha ?
+ D3DFMT_A8R8G8B8 : D3DFMT_X8R8G8B8,
+ D3DPOOL_DEFAULT,
+ &p->texture9,
+ &share_handle);
+ if (FAILED(hr)) {
+ MP_ERR(mapper, "Failed to create Direct3D9 texture: %s\n",
+ mp_HRESULT_to_str(hr));
+ goto fail;
+ }
+
+ hr = IDirect3DTexture9_GetSurfaceLevel(p->texture9, 0, &p->surface9);
+ if (FAILED(hr)) {
+ MP_ERR(mapper, "Failed to get Direct3D9 surface from texture: %s\n",
+ mp_HRESULT_to_str(hr));
+ goto fail;
+ }
+
+ EGLint attrib_list[] = {
+ EGL_WIDTH, mapper->src_params.w,
+ EGL_HEIGHT, mapper->src_params.h,
+ EGL_TEXTURE_FORMAT, p_owner->alpha ? EGL_TEXTURE_RGBA : EGL_TEXTURE_RGB,
+ EGL_TEXTURE_TARGET, EGL_TEXTURE_2D,
+ EGL_NONE
+ };
+ p->egl_surface = eglCreatePbufferFromClientBuffer(
+ p->egl_display, EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE,
+ share_handle, p_owner->egl_config, attrib_list);
+ if (p->egl_surface == EGL_NO_SURFACE) {
+ MP_ERR(mapper, "Failed to create EGL surface\n");
+ goto fail;
+ }
+
+ gl->GenTextures(1, &p->gl_texture);
+ gl->BindTexture(GL_TEXTURE_2D, p->gl_texture);
+ gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ gl->BindTexture(GL_TEXTURE_2D, 0);
+
+ struct ra_tex_params params = {
+ .dimensions = 2,
+ .w = mapper->src_params.w,
+ .h = mapper->src_params.h,
+ .d = 1,
+ .format = ra_find_unorm_format(mapper->ra, 1, p_owner->alpha ? 4 : 3),
+ .render_src = true,
+ .src_linear = true,
+ };
+ if (!params.format)
+ goto fail;
+
+ mapper->tex[0] = ra_create_wrapped_tex(mapper->ra, &params, p->gl_texture);
+ if (!mapper->tex[0])
+ goto fail;
+
+ mapper->dst_params = mapper->src_params;
+ mapper->dst_params.imgfmt = IMGFMT_RGB0;
+ mapper->dst_params.hw_subfmt = 0;
+ return 0;
+fail:
+ return -1;
+}
+
+static int mapper_map(struct ra_hwdec_mapper *mapper)
+{
+ struct priv *p = mapper->priv;
+ GL *gl = ra_gl_get(mapper->ra);
+
+ HRESULT hr;
+ RECT rc = {0, 0, mapper->src->w, mapper->src->h};
+ IDirect3DSurface9* hw_surface = (IDirect3DSurface9 *)mapper->src->planes[3];
+ hr = IDirect3DDevice9Ex_StretchRect(p->device9ex,
+ hw_surface, &rc,
+ p->surface9, &rc,
+ D3DTEXF_NONE);
+ if (FAILED(hr)) {
+ MP_ERR(mapper, "Direct3D RGB conversion failed: %s\n",
+ mp_HRESULT_to_str(hr));
+ return -1;
+ }
+
+ hr = IDirect3DQuery9_Issue(p->query9, D3DISSUE_END);
+ if (FAILED(hr)) {
+ MP_ERR(mapper, "Failed to issue Direct3D END query\n");
+ return -1;
+ }
+
+ // There doesn't appear to be an efficient way to do a blocking flush
+ // of the above StretchRect. Timeout of 8ms is required to reliably
+ // render 4k on Intel Haswell, Ivybridge and Cherry Trail Atom.
+ const int max_retries = 8;
+ const int64_t wait_ns = MP_TIME_MS_TO_NS(1);
+ int retries = 0;
+ while (true) {
+ hr = IDirect3DQuery9_GetData(p->query9, NULL, 0, D3DGETDATA_FLUSH);
+ if (FAILED(hr)) {
+ MP_ERR(mapper, "Failed to query Direct3D flush state\n");
+ return -1;
+ } else if (hr == S_FALSE) {
+ if (++retries > max_retries) {
+ MP_VERBOSE(mapper, "Failed to flush frame after %lld ms\n",
+ (long long)MP_TIME_MS_TO_NS(wait_ns * max_retries));
+ break;
+ }
+ mp_sleep_ns(wait_ns);
+ } else {
+ break;
+ }
+ }
+
+ gl->BindTexture(GL_TEXTURE_2D, p->gl_texture);
+ eglBindTexImage(p->egl_display, p->egl_surface, EGL_BACK_BUFFER);
+ gl->BindTexture(GL_TEXTURE_2D, 0);
+
+ return 0;
+}
+
+const struct ra_hwdec_driver ra_hwdec_dxva2egl = {
+ .name = "dxva2-egl",
+ .priv_size = sizeof(struct priv_owner),
+ .imgfmts = {IMGFMT_DXVA2, 0},
+ .init = init,
+ .uninit = uninit,
+ .mapper = &(const struct ra_hwdec_mapper_driver){
+ .priv_size = sizeof(struct priv),
+ .init = mapper_init,
+ .uninit = mapper_uninit,
+ .map = mapper_map,
+ },
+};
diff --git a/video/out/opengl/hwdec_dxva2gldx.c b/video/out/opengl/hwdec_dxva2gldx.c
new file mode 100644
index 0000000..0172813
--- /dev/null
+++ b/video/out/opengl/hwdec_dxva2gldx.c
@@ -0,0 +1,247 @@
+/*
+ * 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 <d3d9.h>
+#include <assert.h>
+
+#include "common/common.h"
+#include "osdep/windows_utils.h"
+#include "video/out/gpu/hwdec.h"
+#include "ra_gl.h"
+#include "video/hwdec.h"
+#include "video/d3d.h"
+
+// for WGL_ACCESS_READ_ONLY_NV
+#include <GL/wglext.h>
+
+#define SHARED_SURFACE_D3DFMT D3DFMT_X8R8G8B8
+
+struct priv_owner {
+ struct mp_hwdec_ctx hwctx;
+ IDirect3DDevice9Ex *device;
+ HANDLE device_h;
+};
+
+struct priv {
+ IDirect3DDevice9Ex *device;
+ HANDLE device_h;
+ IDirect3DSurface9 *rtarget;
+ HANDLE rtarget_h;
+ GLuint texture;
+};
+
+static void uninit(struct ra_hwdec *hw)
+{
+ struct priv_owner *p = hw->priv;
+
+ hwdec_devices_remove(hw->devs, &p->hwctx);
+ av_buffer_unref(&p->hwctx.av_device_ref);
+
+ if (p->device)
+ IDirect3DDevice9Ex_Release(p->device);
+}
+
+static int init(struct ra_hwdec *hw)
+{
+ struct priv_owner *p = hw->priv;
+ struct ra *ra = hw->ra_ctx->ra;
+
+ if (!ra_is_gl(ra))
+ return -1;
+ GL *gl = ra_gl_get(ra);
+ if (!(gl->mpgl_caps & MPGL_CAP_DXINTEROP))
+ return -1;
+
+ // AMD drivers won't open multiple dxinterop HANDLES on the same D3D device,
+ // so we request the one already in use by context_dxinterop
+ p->device_h = ra_get_native_resource(ra, "dxinterop_device_HANDLE");
+ if (!p->device_h)
+ return -1;
+
+ // But we also still need the actual D3D device
+ p->device = ra_get_native_resource(ra, "IDirect3DDevice9Ex");
+ if (!p->device)
+ return -1;
+ IDirect3DDevice9Ex_AddRef(p->device);
+
+ p->hwctx = (struct mp_hwdec_ctx){
+ .driver_name = hw->driver->name,
+ .av_device_ref = d3d9_wrap_device_ref((IDirect3DDevice9 *)p->device),
+ .hw_imgfmt = IMGFMT_DXVA2,
+ };
+
+ if (!p->hwctx.av_device_ref) {
+ MP_VERBOSE(hw, "Failed to create hwdevice_ctx\n");
+ return -1;
+ }
+
+ hwdec_devices_add(hw->devs, &p->hwctx);
+ return 0;
+}
+
+static void mapper_uninit(struct ra_hwdec_mapper *mapper)
+{
+ struct priv *p = mapper->priv;
+ GL *gl = ra_gl_get(mapper->ra);
+
+ if (p->rtarget_h && p->device_h) {
+ if (!gl->DXUnlockObjectsNV(p->device_h, 1, &p->rtarget_h)) {
+ MP_ERR(mapper, "Failed unlocking texture for access by OpenGL: %s\n",
+ mp_LastError_to_str());
+ }
+ }
+
+ if (p->rtarget_h) {
+ if (!gl->DXUnregisterObjectNV(p->device_h, p->rtarget_h)) {
+ MP_ERR(mapper, "Failed to unregister Direct3D surface with OpenGL: %s\n",
+ mp_LastError_to_str());
+ } else {
+ p->rtarget_h = 0;
+ }
+ }
+
+ gl->DeleteTextures(1, &p->texture);
+ p->texture = 0;
+
+ if (p->rtarget) {
+ IDirect3DSurface9_Release(p->rtarget);
+ p->rtarget = NULL;
+ }
+
+ ra_tex_free(mapper->ra, &mapper->tex[0]);
+}
+
+static int mapper_init(struct ra_hwdec_mapper *mapper)
+{
+ struct priv_owner *p_owner = mapper->owner->priv;
+ struct priv *p = mapper->priv;
+ GL *gl = ra_gl_get(mapper->ra);
+ HRESULT hr;
+
+ p->device = p_owner->device;
+ p->device_h = p_owner->device_h;
+
+ HANDLE share_handle = NULL;
+ hr = IDirect3DDevice9Ex_CreateRenderTarget(
+ p->device,
+ mapper->src_params.w, mapper->src_params.h,
+ SHARED_SURFACE_D3DFMT, D3DMULTISAMPLE_NONE, 0, FALSE,
+ &p->rtarget, &share_handle);
+ if (FAILED(hr)) {
+ MP_ERR(mapper, "Failed creating offscreen Direct3D surface: %s\n",
+ mp_HRESULT_to_str(hr));
+ return -1;
+ }
+
+ if (share_handle &&
+ !gl->DXSetResourceShareHandleNV(p->rtarget, share_handle)) {
+ MP_ERR(mapper, "Failed setting Direct3D/OpenGL share handle for surface: %s\n",
+ mp_LastError_to_str());
+ return -1;
+ }
+
+ gl->GenTextures(1, &p->texture);
+ gl->BindTexture(GL_TEXTURE_2D, p->texture);
+ gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ gl->BindTexture(GL_TEXTURE_2D, 0);
+
+ p->rtarget_h = gl->DXRegisterObjectNV(p->device_h, p->rtarget, p->texture,
+ GL_TEXTURE_2D,
+ WGL_ACCESS_READ_ONLY_NV);
+ if (!p->rtarget_h) {
+ MP_ERR(mapper, "Failed to register Direct3D surface with OpenGL: %s\n",
+ mp_LastError_to_str());
+ return -1;
+ }
+
+ if (!gl->DXLockObjectsNV(p->device_h, 1, &p->rtarget_h)) {
+ MP_ERR(mapper, "Failed locking texture for access by OpenGL %s\n",
+ mp_LastError_to_str());
+ return -1;
+ }
+
+ struct ra_tex_params params = {
+ .dimensions = 2,
+ .w = mapper->src_params.w,
+ .h = mapper->src_params.h,
+ .d = 1,
+ .format = ra_find_unorm_format(mapper->ra, 1, 4),
+ .render_src = true,
+ .src_linear = true,
+ };
+ if (!params.format)
+ return -1;
+
+ mapper->tex[0] = ra_create_wrapped_tex(mapper->ra, &params, p->texture);
+ if (!mapper->tex[0])
+ return -1;
+
+ mapper->dst_params = mapper->src_params;
+ mapper->dst_params.imgfmt = IMGFMT_RGB0;
+ mapper->dst_params.hw_subfmt = 0;
+
+ return 0;
+}
+
+static int mapper_map(struct ra_hwdec_mapper *mapper)
+{
+ struct priv *p = mapper->priv;
+ GL *gl = ra_gl_get(mapper->ra);
+ HRESULT hr;
+
+ if (!gl->DXUnlockObjectsNV(p->device_h, 1, &p->rtarget_h)) {
+ MP_ERR(mapper, "Failed unlocking texture for access by OpenGL: %s\n",
+ mp_LastError_to_str());
+ return -1;
+ }
+
+ IDirect3DSurface9* hw_surface = (IDirect3DSurface9 *)mapper->src->planes[3];
+ RECT rc = {0, 0, mapper->src->w, mapper->src->h};
+ hr = IDirect3DDevice9Ex_StretchRect(p->device,
+ hw_surface, &rc,
+ p->rtarget, &rc,
+ D3DTEXF_NONE);
+ if (FAILED(hr)) {
+ MP_ERR(mapper, "Direct3D RGB conversion failed: %s", mp_HRESULT_to_str(hr));
+ return -1;
+ }
+
+ if (!gl->DXLockObjectsNV(p->device_h, 1, &p->rtarget_h)) {
+ MP_ERR(mapper, "Failed locking texture for access by OpenGL: %s\n",
+ mp_LastError_to_str());
+ return -1;
+ }
+
+ return 0;
+}
+
+const struct ra_hwdec_driver ra_hwdec_dxva2gldx = {
+ .name = "dxva2-dxinterop",
+ .priv_size = sizeof(struct priv_owner),
+ .imgfmts = {IMGFMT_DXVA2, 0},
+ .init = init,
+ .uninit = uninit,
+ .mapper = &(const struct ra_hwdec_mapper_driver){
+ .priv_size = sizeof(struct priv),
+ .init = mapper_init,
+ .uninit = mapper_uninit,
+ .map = mapper_map,
+ },
+};
diff --git a/video/out/opengl/hwdec_rpi.c b/video/out/opengl/hwdec_rpi.c
new file mode 100644
index 0000000..5362832
--- /dev/null
+++ b/video/out/opengl/hwdec_rpi.c
@@ -0,0 +1,384 @@
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <stdbool.h>
+#include <assert.h>
+
+#include <bcm_host.h>
+#include <interface/mmal/mmal.h>
+#include <interface/mmal/util/mmal_util.h>
+#include <interface/mmal/util/mmal_default_components.h>
+#include <interface/mmal/vc/mmal_vc_api.h>
+
+#include <libavutil/rational.h>
+
+#include "common/common.h"
+#include "common/msg.h"
+#include "video/mp_image.h"
+#include "video/out/gpu/hwdec.h"
+
+#include "common.h"
+
+struct priv {
+ struct mp_log *log;
+
+ struct mp_image_params params;
+
+ MMAL_COMPONENT_T *renderer;
+ bool renderer_enabled;
+
+ // for RAM input
+ MMAL_POOL_T *swpool;
+
+ struct mp_image *current_frame;
+
+ struct mp_rect src, dst;
+ int cur_window[4]; // raw user params
+};
+
+// Magic alignments (in pixels) expected by the MMAL internals.
+#define ALIGN_W 32
+#define ALIGN_H 16
+
+// Make mpi point to buffer, assuming MMAL_ENCODING_I420.
+// buffer can be NULL.
+// Return the required buffer space.
+static size_t layout_buffer(struct mp_image *mpi, MMAL_BUFFER_HEADER_T *buffer,
+ struct mp_image_params *params)
+{
+ assert(params->imgfmt == IMGFMT_420P);
+ mp_image_set_params(mpi, params);
+ int w = MP_ALIGN_UP(params->w, ALIGN_W);
+ int h = MP_ALIGN_UP(params->h, ALIGN_H);
+ uint8_t *cur = buffer ? buffer->data : NULL;
+ size_t size = 0;
+ for (int i = 0; i < 3; i++) {
+ int div = i ? 2 : 1;
+ mpi->planes[i] = cur;
+ mpi->stride[i] = w / div;
+ size_t plane_size = h / div * mpi->stride[i];
+ if (cur)
+ cur += plane_size;
+ size += plane_size;
+ }
+ return size;
+}
+
+static MMAL_FOURCC_T map_csp(enum mp_csp csp)
+{
+ switch (csp) {
+ case MP_CSP_BT_601: return MMAL_COLOR_SPACE_ITUR_BT601;
+ case MP_CSP_BT_709: return MMAL_COLOR_SPACE_ITUR_BT709;
+ case MP_CSP_SMPTE_240M: return MMAL_COLOR_SPACE_SMPTE240M;
+ default: return MMAL_COLOR_SPACE_UNKNOWN;
+ }
+}
+
+static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
+{
+ mmal_buffer_header_release(buffer);
+}
+
+static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
+{
+ struct mp_image *mpi = buffer->user_data;
+ talloc_free(mpi);
+}
+
+static void disable_renderer(struct ra_hwdec *hw)
+{
+ struct priv *p = hw->priv;
+
+ if (p->renderer_enabled) {
+ mmal_port_disable(p->renderer->control);
+ mmal_port_disable(p->renderer->input[0]);
+
+ mmal_port_flush(p->renderer->control);
+ mmal_port_flush(p->renderer->input[0]);
+
+ mmal_component_disable(p->renderer);
+ }
+ mmal_pool_destroy(p->swpool);
+ p->swpool = NULL;
+ p->renderer_enabled = false;
+}
+
+// check_window_only: assume params and dst/src rc are unchanged
+static void update_overlay(struct ra_hwdec *hw, bool check_window_only)
+{
+ struct priv *p = hw->priv;
+ MMAL_PORT_T *input = p->renderer->input[0];
+ struct mp_rect src = p->src;
+ struct mp_rect dst = p->dst;
+
+ int defs[4] = {0, 0, 0, 0};
+ int *z = ra_get_native_resource(hw->ra_ctx->ra, "MPV_RPI_WINDOW");
+ if (!z)
+ z = defs;
+
+ // As documented in the libmpv openglcb headers.
+ int display = z[0];
+ int layer = z[1];
+ int x = z[2];
+ int y = z[3];
+
+ if (check_window_only && memcmp(z, p->cur_window, sizeof(p->cur_window)) == 0)
+ return;
+
+ memcpy(p->cur_window, z, sizeof(p->cur_window));
+
+ int rotate[] = {MMAL_DISPLAY_ROT0,
+ MMAL_DISPLAY_ROT90,
+ MMAL_DISPLAY_ROT180,
+ MMAL_DISPLAY_ROT270};
+
+ int src_w = src.x1 - src.x0, src_h = src.y1 - src.y0,
+ dst_w = dst.x1 - dst.x0, dst_h = dst.y1 - dst.y0;
+ int p_x, p_y;
+ av_reduce(&p_x, &p_y, dst_w * src_h, src_w * dst_h, 16000);
+ MMAL_DISPLAYREGION_T dr = {
+ .hdr = { .id = MMAL_PARAMETER_DISPLAYREGION,
+ .size = sizeof(MMAL_DISPLAYREGION_T), },
+ .src_rect = { .x = src.x0, .y = src.y0,
+ .width = src_w, .height = src_h },
+ .dest_rect = { .x = dst.x0 + x, .y = dst.y0 + y,
+ .width = dst_w, .height = dst_h },
+ .layer = layer - 1, // under the GL layer
+ .display_num = display,
+ .pixel_x = p_x,
+ .pixel_y = p_y,
+ .transform = rotate[p->params.rotate / 90],
+ .fullscreen = 0,
+ .set = MMAL_DISPLAY_SET_SRC_RECT | MMAL_DISPLAY_SET_DEST_RECT |
+ MMAL_DISPLAY_SET_LAYER | MMAL_DISPLAY_SET_NUM |
+ MMAL_DISPLAY_SET_PIXEL | MMAL_DISPLAY_SET_TRANSFORM |
+ MMAL_DISPLAY_SET_FULLSCREEN,
+ };
+
+ if (p->params.rotate % 180 == 90) {
+ MPSWAP(int, dr.src_rect.x, dr.src_rect.y);
+ MPSWAP(int, dr.src_rect.width, dr.src_rect.height);
+ }
+
+ if (mmal_port_parameter_set(input, &dr.hdr))
+ MP_WARN(p, "could not set video rectangle\n");
+}
+
+static int enable_renderer(struct ra_hwdec *hw)
+{
+ struct priv *p = hw->priv;
+ MMAL_PORT_T *input = p->renderer->input[0];
+ struct mp_image_params *params = &p->params;
+
+ if (p->renderer_enabled)
+ return 0;
+
+ if (!params->imgfmt)
+ return -1;
+
+ bool opaque = params->imgfmt == IMGFMT_MMAL;
+
+ input->format->encoding = opaque ? MMAL_ENCODING_OPAQUE : MMAL_ENCODING_I420;
+ input->format->es->video.width = MP_ALIGN_UP(params->w, ALIGN_W);
+ input->format->es->video.height = MP_ALIGN_UP(params->h, ALIGN_H);
+ input->format->es->video.crop = (MMAL_RECT_T){0, 0, params->w, params->h};
+ input->format->es->video.par = (MMAL_RATIONAL_T){params->p_w, params->p_h};
+ input->format->es->video.color_space = map_csp(params->color.space);
+
+ if (mmal_port_format_commit(input))
+ return -1;
+
+ input->buffer_num = MPMAX(input->buffer_num_min,
+ input->buffer_num_recommended) + 3;
+ input->buffer_size = MPMAX(input->buffer_size_min,
+ input->buffer_size_recommended);
+
+ if (!opaque) {
+ size_t size = layout_buffer(&(struct mp_image){0}, NULL, params);
+ if (input->buffer_size != size) {
+ MP_FATAL(hw, "We disagree with MMAL about buffer sizes.\n");
+ return -1;
+ }
+
+ p->swpool = mmal_pool_create(input->buffer_num, input->buffer_size);
+ if (!p->swpool) {
+ MP_FATAL(hw, "Could not allocate buffer pool.\n");
+ return -1;
+ }
+ }
+
+ update_overlay(hw, false);
+
+ p->renderer_enabled = true;
+
+ if (mmal_port_enable(p->renderer->control, control_port_cb))
+ return -1;
+
+ if (mmal_port_enable(input, input_port_cb))
+ return -1;
+
+ if (mmal_component_enable(p->renderer)) {
+ MP_FATAL(hw, "Failed to enable video renderer.\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static void free_mmal_buffer(void *arg)
+{
+ MMAL_BUFFER_HEADER_T *buffer = arg;
+ mmal_buffer_header_release(buffer);
+}
+
+static struct mp_image *upload(struct ra_hwdec *hw, struct mp_image *hw_image)
+{
+ struct priv *p = hw->priv;
+
+ MMAL_BUFFER_HEADER_T *buffer = mmal_queue_wait(p->swpool->queue);
+ if (!buffer) {
+ MP_ERR(hw, "Can't allocate buffer.\n");
+ return NULL;
+ }
+ mmal_buffer_header_reset(buffer);
+
+ struct mp_image *new_ref = mp_image_new_custom_ref(NULL, buffer,
+ free_mmal_buffer);
+ if (!new_ref) {
+ mmal_buffer_header_release(buffer);
+ MP_ERR(hw, "Out of memory.\n");
+ return NULL;
+ }
+
+ mp_image_setfmt(new_ref, IMGFMT_MMAL);
+ new_ref->planes[3] = (void *)buffer;
+
+ struct mp_image dmpi = {0};
+ buffer->length = layout_buffer(&dmpi, buffer, &p->params);
+ mp_image_copy(&dmpi, hw_image);
+
+ return new_ref;
+}
+
+static int overlay_frame(struct ra_hwdec *hw, struct mp_image *hw_image,
+ struct mp_rect *src, struct mp_rect *dst, bool newframe)
+{
+ struct priv *p = hw->priv;
+
+ if (hw_image && !mp_image_params_equal(&p->params, &hw_image->params)) {
+ p->params = hw_image->params;
+
+ disable_renderer(hw);
+ mp_image_unrefp(&p->current_frame);
+
+ if (enable_renderer(hw) < 0)
+ return -1;
+ }
+
+ if (hw_image && p->current_frame && !newframe) {
+ if (!mp_rect_equals(&p->src, src) ||mp_rect_equals(&p->dst, dst)) {
+ p->src = *src;
+ p->dst = *dst;
+ update_overlay(hw, false);
+ }
+ return 0; // don't reupload
+ }
+
+ mp_image_unrefp(&p->current_frame);
+
+ if (!hw_image) {
+ disable_renderer(hw);
+ return 0;
+ }
+
+ if (enable_renderer(hw) < 0)
+ return -1;
+
+ update_overlay(hw, true);
+
+ struct mp_image *mpi = NULL;
+ if (hw_image->imgfmt == IMGFMT_MMAL) {
+ mpi = mp_image_new_ref(hw_image);
+ } else {
+ mpi = upload(hw, hw_image);
+ }
+
+ if (!mpi) {
+ disable_renderer(hw);
+ return -1;
+ }
+
+ MMAL_BUFFER_HEADER_T *ref = (void *)mpi->planes[3];
+
+ // Assume this field is free for use by us.
+ ref->user_data = mpi;
+
+ if (mmal_port_send_buffer(p->renderer->input[0], ref)) {
+ MP_ERR(hw, "could not queue picture!\n");
+ talloc_free(mpi);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void destroy(struct ra_hwdec *hw)
+{
+ struct priv *p = hw->priv;
+
+ disable_renderer(hw);
+
+ if (p->renderer)
+ mmal_component_release(p->renderer);
+
+ mmal_vc_deinit();
+}
+
+static int create(struct ra_hwdec *hw)
+{
+ struct priv *p = hw->priv;
+ p->log = hw->log;
+
+ bcm_host_init();
+
+ if (mmal_vc_init()) {
+ MP_FATAL(hw, "Could not initialize MMAL.\n");
+ return -1;
+ }
+
+ if (mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_RENDERER, &p->renderer))
+ {
+ MP_FATAL(hw, "Could not create MMAL renderer.\n");
+ mmal_vc_deinit();
+ return -1;
+ }
+
+ return 0;
+}
+
+const struct ra_hwdec_driver ra_hwdec_rpi_overlay = {
+ .name = "rpi-overlay",
+ .priv_size = sizeof(struct priv),
+ .imgfmts = {IMGFMT_MMAL, IMGFMT_420P, 0},
+ .init = create,
+ .overlay_frame = overlay_frame,
+ .uninit = destroy,
+};
diff --git a/video/out/opengl/hwdec_vdpau.c b/video/out/opengl/hwdec_vdpau.c
new file mode 100644
index 0000000..acdc703
--- /dev/null
+++ b/video/out/opengl/hwdec_vdpau.c
@@ -0,0 +1,251 @@
+/*
+ * 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 <stddef.h>
+#include <assert.h>
+
+#include "video/out/gpu/hwdec.h"
+#include "ra_gl.h"
+#include "video/vdpau.h"
+#include "video/vdpau_mixer.h"
+
+// This is a GL_NV_vdpau_interop specification bug, and headers (unfortunately)
+// follow it. I'm not sure about the original nvidia headers.
+#define BRAINDEATH(x) ((void *)(uintptr_t)(x))
+
+struct priv_owner {
+ struct mp_vdpau_ctx *ctx;
+};
+
+struct priv {
+ struct mp_vdpau_ctx *ctx;
+ GL *gl;
+ uint64_t preemption_counter;
+ GLuint gl_texture;
+ bool vdpgl_initialized;
+ GLvdpauSurfaceNV vdpgl_surface;
+ VdpOutputSurface vdp_surface;
+ struct mp_vdpau_mixer *mixer;
+ struct ra_imgfmt_desc direct_desc;
+ bool mapped;
+};
+
+static int init(struct ra_hwdec *hw)
+{
+ struct ra *ra = hw->ra_ctx->ra;
+ Display *x11disp = ra_get_native_resource(ra, "x11");
+ if (!x11disp || !ra_is_gl(ra))
+ return -1;
+ GL *gl = ra_gl_get(ra);
+ if (!(gl->mpgl_caps & MPGL_CAP_VDPAU))
+ return -1;
+ struct priv_owner *p = hw->priv;
+ p->ctx = mp_vdpau_create_device_x11(hw->log, x11disp, true);
+ if (!p->ctx)
+ return -1;
+ if (mp_vdpau_handle_preemption(p->ctx, NULL) < 1)
+ return -1;
+ if (hw->probing && mp_vdpau_guess_if_emulated(p->ctx))
+ return -1;
+ p->ctx->hwctx.driver_name = hw->driver->name;
+ p->ctx->hwctx.hw_imgfmt = IMGFMT_VDPAU;
+ hwdec_devices_add(hw->devs, &p->ctx->hwctx);
+ return 0;
+}
+
+static void uninit(struct ra_hwdec *hw)
+{
+ struct priv_owner *p = hw->priv;
+
+ if (p->ctx)
+ hwdec_devices_remove(hw->devs, &p->ctx->hwctx);
+ mp_vdpau_destroy(p->ctx);
+}
+
+static void mapper_unmap(struct ra_hwdec_mapper *mapper)
+{
+ struct priv *p = mapper->priv;
+ GL *gl = p->gl;
+
+ for (int n = 0; n < 4; n++)
+ ra_tex_free(mapper->ra, &mapper->tex[n]);
+
+ if (p->mapped) {
+ gl->VDPAUUnmapSurfacesNV(1, &p->vdpgl_surface);
+ }
+ p->mapped = false;
+}
+
+static void mark_vdpau_objects_uninitialized(struct ra_hwdec_mapper *mapper)
+{
+ struct priv *p = mapper->priv;
+
+ p->vdp_surface = VDP_INVALID_HANDLE;
+ p->mapped = false;
+}
+
+static void mapper_uninit(struct ra_hwdec_mapper *mapper)
+{
+ struct priv *p = mapper->priv;
+ GL *gl = p->gl;
+ struct vdp_functions *vdp = &p->ctx->vdp;
+ VdpStatus vdp_st;
+
+ assert(!p->mapped);
+
+ if (p->vdpgl_surface)
+ gl->VDPAUUnregisterSurfaceNV(p->vdpgl_surface);
+ p->vdpgl_surface = 0;
+
+ gl->DeleteTextures(1, &p->gl_texture);
+
+ if (p->vdp_surface != VDP_INVALID_HANDLE) {
+ vdp_st = vdp->output_surface_destroy(p->vdp_surface);
+ CHECK_VDP_WARNING(mapper, "Error when calling vdp_output_surface_destroy");
+ }
+ p->vdp_surface = VDP_INVALID_HANDLE;
+
+ gl_check_error(gl, mapper->log, "Before uninitializing OpenGL interop");
+
+ if (p->vdpgl_initialized)
+ gl->VDPAUFiniNV();
+
+ p->vdpgl_initialized = false;
+
+ gl_check_error(gl, mapper->log, "After uninitializing OpenGL interop");
+
+ mp_vdpau_mixer_destroy(p->mixer);
+}
+
+static int mapper_init(struct ra_hwdec_mapper *mapper)
+{
+ struct priv_owner *p_owner = mapper->owner->priv;
+ struct priv *p = mapper->priv;
+
+ p->gl = ra_gl_get(mapper->ra);
+ p->ctx = p_owner->ctx;
+
+ GL *gl = p->gl;
+ struct vdp_functions *vdp = &p->ctx->vdp;
+ VdpStatus vdp_st;
+
+ p->vdp_surface = VDP_INVALID_HANDLE;
+ p->mixer = mp_vdpau_mixer_create(p->ctx, mapper->log);
+ if (!p->mixer)
+ return -1;
+
+ mapper->dst_params = mapper->src_params;
+
+ if (mp_vdpau_handle_preemption(p->ctx, &p->preemption_counter) < 0)
+ return -1;
+
+ gl->VDPAUInitNV(BRAINDEATH(p->ctx->vdp_device), p->ctx->get_proc_address);
+
+ p->vdpgl_initialized = true;
+
+ gl->GenTextures(1, &p->gl_texture);
+
+ gl->BindTexture(GL_TEXTURE_2D, p->gl_texture);
+ gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ gl->BindTexture(GL_TEXTURE_2D, 0);
+
+ vdp_st = vdp->output_surface_create(p->ctx->vdp_device,
+ VDP_RGBA_FORMAT_B8G8R8A8,
+ mapper->src_params.w,
+ mapper->src_params.h,
+ &p->vdp_surface);
+ CHECK_VDP_ERROR(mapper, "Error when calling vdp_output_surface_create");
+
+ p->vdpgl_surface = gl->VDPAURegisterOutputSurfaceNV(BRAINDEATH(p->vdp_surface),
+ GL_TEXTURE_2D,
+ 1, &p->gl_texture);
+ if (!p->vdpgl_surface)
+ return -1;
+
+ gl->VDPAUSurfaceAccessNV(p->vdpgl_surface, GL_READ_ONLY);
+
+ mapper->dst_params.imgfmt = IMGFMT_RGB0;
+ mapper->dst_params.hw_subfmt = 0;
+
+ gl_check_error(gl, mapper->log, "After initializing vdpau OpenGL interop");
+
+ return 0;
+}
+
+static int mapper_map(struct ra_hwdec_mapper *mapper)
+{
+ struct priv *p = mapper->priv;
+ GL *gl = p->gl;
+
+ int pe = mp_vdpau_handle_preemption(p->ctx, &p->preemption_counter);
+ if (pe < 1) {
+ mark_vdpau_objects_uninitialized(mapper);
+ if (pe < 0)
+ return -1;
+ mapper_uninit(mapper);
+ if (mapper_init(mapper) < 0)
+ return -1;
+ }
+
+ if (!p->vdpgl_surface)
+ return -1;
+
+ mp_vdpau_mixer_render(p->mixer, NULL, p->vdp_surface, NULL, mapper->src,
+ NULL);
+
+ gl->VDPAUMapSurfacesNV(1, &p->vdpgl_surface);
+
+ p->mapped = true;
+
+ struct ra_tex_params params = {
+ .dimensions = 2,
+ .w = mapper->src_params.w,
+ .h = mapper->src_params.h,
+ .d = 1,
+ .format = ra_find_unorm_format(mapper->ra, 1, 4),
+ .render_src = true,
+ .src_linear = true,
+ };
+
+ if (!params.format)
+ return -1;
+
+ mapper->tex[0] =
+ ra_create_wrapped_tex(mapper->ra, &params, p->gl_texture);
+ if (!mapper->tex[0])
+ return -1;
+
+ return 0;
+}
+
+const struct ra_hwdec_driver ra_hwdec_vdpau = {
+ .name = "vdpau-gl",
+ .priv_size = sizeof(struct priv_owner),
+ .imgfmts = {IMGFMT_VDPAU, 0},
+ .init = init,
+ .uninit = uninit,
+ .mapper = &(const struct ra_hwdec_mapper_driver){
+ .priv_size = sizeof(struct priv),
+ .init = mapper_init,
+ .uninit = mapper_uninit,
+ .map = mapper_map,
+ .unmap = mapper_unmap,
+ },
+};
diff --git a/video/out/opengl/libmpv_gl.c b/video/out/opengl/libmpv_gl.c
new file mode 100644
index 0000000..c297c13
--- /dev/null
+++ b/video/out/opengl/libmpv_gl.c
@@ -0,0 +1,114 @@
+#include "common.h"
+#include "context.h"
+#include "ra_gl.h"
+#include "options/m_config.h"
+#include "libmpv/render_gl.h"
+#include "video/out/gpu/libmpv_gpu.h"
+#include "video/out/gpu/ra.h"
+
+struct priv {
+ GL *gl;
+ struct ra_ctx *ra_ctx;
+};
+
+static int init(struct libmpv_gpu_context *ctx, mpv_render_param *params)
+{
+ ctx->priv = talloc_zero(NULL, struct priv);
+ struct priv *p = ctx->priv;
+
+ mpv_opengl_init_params *init_params =
+ get_mpv_render_param(params, MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, NULL);
+ if (!init_params)
+ return MPV_ERROR_INVALID_PARAMETER;
+
+ p->gl = talloc_zero(p, GL);
+
+ mpgl_load_functions2(p->gl, init_params->get_proc_address,
+ init_params->get_proc_address_ctx,
+ NULL, ctx->log);
+ if (!p->gl->version && !p->gl->es) {
+ MP_FATAL(ctx, "OpenGL not initialized.\n");
+ return MPV_ERROR_UNSUPPORTED;
+ }
+
+ // initialize a blank ra_ctx to reuse ra_gl_ctx
+ p->ra_ctx = talloc_zero(p, struct ra_ctx);
+ p->ra_ctx->log = ctx->log;
+ p->ra_ctx->global = ctx->global;
+ p->ra_ctx->opts = (struct ra_ctx_opts) {
+ .allow_sw = true,
+ };
+
+ static const struct ra_swapchain_fns empty_swapchain_fns = {0};
+ struct ra_gl_ctx_params gl_params = {
+ // vo_libmpv is essentially like a gigantic external swapchain where
+ // the user is in charge of presentation / swapping etc. But we don't
+ // actually need to provide any of these functions, since we can just
+ // not call them to begin with - so just set it to an empty object to
+ // signal to ra_gl_p that we don't care about its latency emulation
+ // functionality
+ .external_swapchain = &empty_swapchain_fns
+ };
+
+ p->gl->SwapInterval = NULL; // we shouldn't randomly change this, so lock it
+ if (!ra_gl_ctx_init(p->ra_ctx, p->gl, gl_params))
+ return MPV_ERROR_UNSUPPORTED;
+
+ struct ra_ctx_opts *ctx_opts = mp_get_config_group(ctx, ctx->global, &ra_ctx_conf);
+ p->ra_ctx->opts.debug = ctx_opts->debug;
+ p->gl->debug_context = ctx_opts->debug;
+ ra_gl_set_debug(p->ra_ctx->ra, ctx_opts->debug);
+ talloc_free(ctx_opts);
+
+ ctx->ra_ctx = p->ra_ctx;
+
+ return 0;
+}
+
+static int wrap_fbo(struct libmpv_gpu_context *ctx, mpv_render_param *params,
+ struct ra_tex **out)
+{
+ struct priv *p = ctx->priv;
+
+ mpv_opengl_fbo *fbo =
+ get_mpv_render_param(params, MPV_RENDER_PARAM_OPENGL_FBO, NULL);
+ if (!fbo)
+ return MPV_ERROR_INVALID_PARAMETER;
+
+ if (fbo->fbo && !(p->gl->mpgl_caps & MPGL_CAP_FB)) {
+ MP_FATAL(ctx, "Rendering to FBO requested, but no FBO extension found!\n");
+ return MPV_ERROR_UNSUPPORTED;
+ }
+
+ struct ra_swapchain *sw = p->ra_ctx->swapchain;
+ struct ra_fbo target;
+ ra_gl_ctx_resize(sw, fbo->w, fbo->h, fbo->fbo);
+ ra_gl_ctx_start_frame(sw, &target);
+ *out = target.tex;
+ return 0;
+}
+
+static void done_frame(struct libmpv_gpu_context *ctx, bool ds)
+{
+ struct priv *p = ctx->priv;
+
+ struct ra_swapchain *sw = p->ra_ctx->swapchain;
+ struct vo_frame dummy = {.display_synced = ds};
+ ra_gl_ctx_submit_frame(sw, &dummy);
+}
+
+static void destroy(struct libmpv_gpu_context *ctx)
+{
+ struct priv *p = ctx->priv;
+
+ if (p->ra_ctx)
+ ra_gl_ctx_uninit(p->ra_ctx);
+}
+
+const struct libmpv_gpu_context_fns libmpv_gpu_context_gl = {
+ .api_name = MPV_RENDER_API_TYPE_OPENGL,
+ .init = init,
+ .wrap_fbo = wrap_fbo,
+ .done_frame = done_frame,
+ .destroy = destroy,
+};
diff --git a/video/out/opengl/ra_gl.c b/video/out/opengl/ra_gl.c
new file mode 100644
index 0000000..f535f1f
--- /dev/null
+++ b/video/out/opengl/ra_gl.c
@@ -0,0 +1,1208 @@
+#include <libavutil/intreadwrite.h>
+
+#include "formats.h"
+#include "utils.h"
+#include "ra_gl.h"
+
+static struct ra_fns ra_fns_gl;
+
+// For ra.priv
+struct ra_gl {
+ GL *gl;
+ bool debug_enable;
+ bool timer_active; // hack for GL_TIME_ELAPSED limitations
+};
+
+// For ra_tex.priv
+struct ra_tex_gl {
+ struct ra_buf_pool pbo; // for ra.use_pbo
+ bool own_objects;
+ GLenum target;
+ GLuint texture; // 0 if no texture data associated
+ GLuint fbo; // 0 if no rendering requested, or it default framebuffer
+ // These 3 fields can be 0 if unknown.
+ GLint internal_format;
+ GLenum format;
+ GLenum type;
+};
+
+// For ra_buf.priv
+struct ra_buf_gl {
+ GLenum target;
+ GLuint buffer;
+ GLsync fence;
+};
+
+// For ra_renderpass.priv
+struct ra_renderpass_gl {
+ GLuint program;
+ // 1 entry for each ra_renderpass_params.inputs[] entry
+ GLint *uniform_loc;
+ int num_uniform_loc; // == ra_renderpass_params.num_inputs
+ struct gl_vao vao;
+};
+
+// (Init time only.)
+static void probe_real_size(GL *gl, struct ra_format *fmt)
+{
+ const struct gl_format *gl_fmt = fmt->priv;
+
+ if (!gl->GetTexLevelParameteriv)
+ return; // GLES
+
+ bool is_la = gl_fmt->format == GL_LUMINANCE ||
+ gl_fmt->format == GL_LUMINANCE_ALPHA;
+ if (is_la && gl->es)
+ return; // GLES doesn't provide GL_TEXTURE_LUMINANCE_SIZE.
+
+ GLuint tex;
+ gl->GenTextures(1, &tex);
+ gl->BindTexture(GL_TEXTURE_2D, tex);
+ gl->TexImage2D(GL_TEXTURE_2D, 0, gl_fmt->internal_format, 64, 64, 0,
+ gl_fmt->format, gl_fmt->type, NULL);
+ for (int i = 0; i < fmt->num_components; i++) {
+ const GLenum pnames[] = {
+ GL_TEXTURE_RED_SIZE,
+ GL_TEXTURE_GREEN_SIZE,
+ GL_TEXTURE_BLUE_SIZE,
+ GL_TEXTURE_ALPHA_SIZE,
+ GL_TEXTURE_LUMINANCE_SIZE,
+ GL_TEXTURE_ALPHA_SIZE,
+ };
+ int comp = is_la ? i + 4 : i;
+ assert(comp < MP_ARRAY_SIZE(pnames));
+ GLint param = -1;
+ gl->GetTexLevelParameteriv(GL_TEXTURE_2D, 0, pnames[comp], &param);
+ fmt->component_depth[i] = param > 0 ? param : 0;
+ }
+ gl->DeleteTextures(1, &tex);
+}
+
+static int ra_init_gl(struct ra *ra, GL *gl)
+{
+ if (gl->version < 210 && gl->es < 200) {
+ MP_ERR(ra, "At least OpenGL 2.1 or OpenGL ES 2.0 required.\n");
+ return -1;
+ }
+
+ struct ra_gl *p = ra->priv = talloc_zero(NULL, struct ra_gl);
+ p->gl = gl;
+
+ ra_gl_set_debug(ra, true);
+
+ ra->fns = &ra_fns_gl;
+ ra->glsl_version = gl->glsl_version;
+ ra->glsl_es = gl->es > 0;
+
+ static const int caps_map[][2] = {
+ {RA_CAP_DIRECT_UPLOAD, 0},
+ {RA_CAP_GLOBAL_UNIFORM, 0},
+ {RA_CAP_FRAGCOORD, 0},
+ {RA_CAP_TEX_1D, MPGL_CAP_1D_TEX},
+ {RA_CAP_TEX_3D, MPGL_CAP_3D_TEX},
+ {RA_CAP_COMPUTE, MPGL_CAP_COMPUTE_SHADER},
+ {RA_CAP_NUM_GROUPS, MPGL_CAP_COMPUTE_SHADER},
+ {RA_CAP_NESTED_ARRAY, MPGL_CAP_NESTED_ARRAY},
+ {RA_CAP_SLOW_DR, MPGL_CAP_SLOW_DR},
+ };
+
+ for (int i = 0; i < MP_ARRAY_SIZE(caps_map); i++) {
+ if ((gl->mpgl_caps & caps_map[i][1]) == caps_map[i][1])
+ ra->caps |= caps_map[i][0];
+ }
+
+ if (gl->BindBufferBase) {
+ if (gl->mpgl_caps & MPGL_CAP_UBO)
+ ra->caps |= RA_CAP_BUF_RO;
+ if (gl->mpgl_caps & MPGL_CAP_SSBO)
+ ra->caps |= RA_CAP_BUF_RW;
+ }
+
+ // textureGather is only supported in GLSL 400+ / ES 310+
+ if (ra->glsl_version >= (ra->glsl_es ? 310 : 400))
+ ra->caps |= RA_CAP_GATHER;
+
+ if (gl->BlitFramebuffer)
+ ra->caps |= RA_CAP_BLIT;
+
+ // Disable compute shaders for GLSL < 420. This work-around is needed since
+ // some buggy OpenGL drivers expose compute shaders for lower GLSL versions,
+ // despite the spec requiring 420+.
+ if (ra->glsl_version < (ra->glsl_es ? 310 : 420)) {
+ ra->caps &= ~RA_CAP_COMPUTE;
+ }
+
+ // While we can handle compute shaders on GLES the spec (intentionally)
+ // does not support binding textures for writing, which all uses inside mpv
+ // would require. So disable it unconditionally anyway.
+ if (ra->glsl_es)
+ ra->caps &= ~RA_CAP_COMPUTE;
+
+ int gl_fmt_features = gl_format_feature_flags(gl);
+
+ for (int n = 0; gl_formats[n].internal_format; n++) {
+ const struct gl_format *gl_fmt = &gl_formats[n];
+
+ if (!(gl_fmt->flags & gl_fmt_features))
+ continue;
+
+ struct ra_format *fmt = talloc_zero(ra, struct ra_format);
+ *fmt = (struct ra_format){
+ .name = gl_fmt->name,
+ .priv = (void *)gl_fmt,
+ .ctype = gl_format_type(gl_fmt),
+ .num_components = gl_format_components(gl_fmt->format),
+ .ordered = gl_fmt->format != GL_RGB_422_APPLE,
+ .pixel_size = gl_bytes_per_pixel(gl_fmt->format, gl_fmt->type),
+ .luminance_alpha = gl_fmt->format == GL_LUMINANCE_ALPHA,
+ .linear_filter = gl_fmt->flags & F_TF,
+ .renderable = (gl_fmt->flags & F_CR) &&
+ (gl->mpgl_caps & MPGL_CAP_FB),
+ // TODO: Check whether it's a storable format
+ // https://www.khronos.org/opengl/wiki/Image_Load_Store
+ .storable = true,
+ };
+
+ int csize = gl_component_size(gl_fmt->type) * 8;
+ int depth = csize;
+
+ if (gl_fmt->flags & F_F16) {
+ depth = 16;
+ csize = 32; // always upload as GL_FLOAT (simpler for us)
+ }
+
+ for (int i = 0; i < fmt->num_components; i++) {
+ fmt->component_size[i] = csize;
+ fmt->component_depth[i] = depth;
+ }
+
+ if (fmt->ctype == RA_CTYPE_UNORM && depth != 8)
+ probe_real_size(gl, fmt);
+
+ // Special formats for which OpenGL happens to have direct support.
+ if (strcmp(fmt->name, "rgb565") == 0) {
+ fmt->special_imgfmt = IMGFMT_RGB565;
+ struct ra_imgfmt_desc *desc = talloc_zero(fmt, struct ra_imgfmt_desc);
+ fmt->special_imgfmt_desc = desc;
+ desc->num_planes = 1;
+ desc->planes[0] = fmt;
+ for (int i = 0; i < 3; i++)
+ desc->components[0][i] = i + 1;
+ desc->chroma_w = desc->chroma_h = 1;
+ }
+ if (strcmp(fmt->name, "rgb10_a2") == 0) {
+ fmt->special_imgfmt = IMGFMT_RGB30;
+ struct ra_imgfmt_desc *desc = talloc_zero(fmt, struct ra_imgfmt_desc);
+ fmt->special_imgfmt_desc = desc;
+ desc->component_bits = 10;
+ desc->num_planes = 1;
+ desc->planes[0] = fmt;
+ for (int i = 0; i < 3; i++)
+ desc->components[0][i] = 3 - i;
+ desc->chroma_w = desc->chroma_h = 1;
+ }
+ if (strcmp(fmt->name, "appleyp") == 0) {
+ fmt->special_imgfmt = IMGFMT_UYVY;
+ struct ra_imgfmt_desc *desc = talloc_zero(fmt, struct ra_imgfmt_desc);
+ fmt->special_imgfmt_desc = desc;
+ desc->num_planes = 1;
+ desc->planes[0] = fmt;
+ desc->components[0][0] = 3;
+ desc->components[0][1] = 1;
+ desc->components[0][2] = 2;
+ desc->chroma_w = desc->chroma_h = 1;
+ }
+
+ fmt->glsl_format = ra_fmt_glsl_format(fmt);
+
+ MP_TARRAY_APPEND(ra, ra->formats, ra->num_formats, fmt);
+ }
+
+ GLint ival;
+ gl->GetIntegerv(GL_MAX_TEXTURE_SIZE, &ival);
+ ra->max_texture_wh = ival;
+
+ if (ra->caps & RA_CAP_COMPUTE) {
+ gl->GetIntegerv(GL_MAX_COMPUTE_SHARED_MEMORY_SIZE, &ival);
+ ra->max_shmem = ival;
+ gl->GetIntegerv(GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS, &ival);
+ ra->max_compute_group_threads = ival;
+ }
+
+ gl->Disable(GL_DITHER);
+
+ if (!ra_find_unorm_format(ra, 2, 1))
+ MP_VERBOSE(ra, "16 bit UNORM textures not available.\n");
+
+ return 0;
+}
+
+struct ra *ra_create_gl(GL *gl, struct mp_log *log)
+{
+ struct ra *ra = talloc_zero(NULL, struct ra);
+ ra->log = log;
+ if (ra_init_gl(ra, gl) < 0) {
+ talloc_free(ra);
+ return NULL;
+ }
+ return ra;
+}
+
+static void gl_destroy(struct ra *ra)
+{
+ talloc_free(ra->priv);
+}
+
+void ra_gl_set_debug(struct ra *ra, bool enable)
+{
+ struct ra_gl *p = ra->priv;
+ GL *gl = ra_gl_get(ra);
+
+ p->debug_enable = enable;
+ if (gl->debug_context)
+ gl_set_debug_logger(gl, enable ? ra->log : NULL);
+}
+
+static void gl_tex_destroy(struct ra *ra, struct ra_tex *tex)
+{
+ GL *gl = ra_gl_get(ra);
+ struct ra_tex_gl *tex_gl = tex->priv;
+
+ ra_buf_pool_uninit(ra, &tex_gl->pbo);
+
+ if (tex_gl->own_objects) {
+ if (tex_gl->fbo)
+ gl->DeleteFramebuffers(1, &tex_gl->fbo);
+
+ gl->DeleteTextures(1, &tex_gl->texture);
+ }
+ talloc_free(tex_gl);
+ talloc_free(tex);
+}
+
+static struct ra_tex *gl_tex_create_blank(struct ra *ra,
+ const struct ra_tex_params *params)
+{
+ struct ra_tex *tex = talloc_zero(NULL, struct ra_tex);
+ tex->params = *params;
+ tex->params.initial_data = NULL;
+ struct ra_tex_gl *tex_gl = tex->priv = talloc_zero(NULL, struct ra_tex_gl);
+
+ const struct gl_format *fmt = params->format->priv;
+ tex_gl->internal_format = fmt->internal_format;
+ tex_gl->format = fmt->format;
+ tex_gl->type = fmt->type;
+ switch (params->dimensions) {
+ case 1: tex_gl->target = GL_TEXTURE_1D; break;
+ case 2: tex_gl->target = GL_TEXTURE_2D; break;
+ case 3: tex_gl->target = GL_TEXTURE_3D; break;
+ default: MP_ASSERT_UNREACHABLE();
+ }
+ if (params->non_normalized) {
+ assert(params->dimensions == 2);
+ tex_gl->target = GL_TEXTURE_RECTANGLE;
+ }
+ if (params->external_oes) {
+ assert(params->dimensions == 2 && !params->non_normalized);
+ tex_gl->target = GL_TEXTURE_EXTERNAL_OES;
+ }
+
+ if (params->downloadable && !(params->dimensions == 2 &&
+ params->format->renderable))
+ {
+ gl_tex_destroy(ra, tex);
+ return NULL;
+ }
+
+ return tex;
+}
+
+static struct ra_tex *gl_tex_create(struct ra *ra,
+ const struct ra_tex_params *params)
+{
+ GL *gl = ra_gl_get(ra);
+ assert(!params->format->dummy_format);
+
+ struct ra_tex *tex = gl_tex_create_blank(ra, params);
+ if (!tex)
+ return NULL;
+ struct ra_tex_gl *tex_gl = tex->priv;
+
+ tex_gl->own_objects = true;
+
+ gl->GenTextures(1, &tex_gl->texture);
+ gl->BindTexture(tex_gl->target, tex_gl->texture);
+
+ GLint filter = params->src_linear ? GL_LINEAR : GL_NEAREST;
+ GLint wrap = params->src_repeat ? GL_REPEAT : GL_CLAMP_TO_EDGE;
+ gl->TexParameteri(tex_gl->target, GL_TEXTURE_MIN_FILTER, filter);
+ gl->TexParameteri(tex_gl->target, GL_TEXTURE_MAG_FILTER, filter);
+ gl->TexParameteri(tex_gl->target, GL_TEXTURE_WRAP_S, wrap);
+ if (params->dimensions > 1)
+ gl->TexParameteri(tex_gl->target, GL_TEXTURE_WRAP_T, wrap);
+ if (params->dimensions > 2)
+ gl->TexParameteri(tex_gl->target, GL_TEXTURE_WRAP_R, wrap);
+
+ gl->PixelStorei(GL_UNPACK_ALIGNMENT, 1);
+ switch (params->dimensions) {
+ case 1:
+ gl->TexImage1D(tex_gl->target, 0, tex_gl->internal_format, params->w,
+ 0, tex_gl->format, tex_gl->type, params->initial_data);
+ break;
+ case 2:
+ gl->TexImage2D(tex_gl->target, 0, tex_gl->internal_format, params->w,
+ params->h, 0, tex_gl->format, tex_gl->type,
+ params->initial_data);
+ break;
+ case 3:
+ gl->TexImage3D(tex_gl->target, 0, tex_gl->internal_format, params->w,
+ params->h, params->d, 0, tex_gl->format, tex_gl->type,
+ params->initial_data);
+ break;
+ }
+ gl->PixelStorei(GL_UNPACK_ALIGNMENT, 4);
+
+ gl->BindTexture(tex_gl->target, 0);
+
+ gl_check_error(gl, ra->log, "after creating texture");
+
+ // Even blitting needs an FBO in OpenGL for strange reasons.
+ // Download is handled by reading from an FBO.
+ if (tex->params.render_dst || tex->params.blit_src ||
+ tex->params.blit_dst || tex->params.downloadable)
+ {
+ if (!tex->params.format->renderable) {
+ MP_ERR(ra, "Trying to create renderable texture with unsupported "
+ "format.\n");
+ ra_tex_free(ra, &tex);
+ return NULL;
+ }
+
+ assert(gl->mpgl_caps & MPGL_CAP_FB);
+
+ gl->GenFramebuffers(1, &tex_gl->fbo);
+ gl->BindFramebuffer(GL_FRAMEBUFFER, tex_gl->fbo);
+ gl->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_TEXTURE_2D, tex_gl->texture, 0);
+ GLenum err = gl->CheckFramebufferStatus(GL_FRAMEBUFFER);
+ gl->BindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ if (err != GL_FRAMEBUFFER_COMPLETE) {
+ MP_ERR(ra, "Error: framebuffer completeness check failed (error=%d).\n",
+ (int)err);
+ ra_tex_free(ra, &tex);
+ return NULL;
+ }
+
+
+ gl_check_error(gl, ra->log, "after creating framebuffer");
+ }
+
+ return tex;
+}
+
+// Create a ra_tex that merely wraps an existing texture. The returned object
+// is freed with ra_tex_free(), but this will not delete the texture passed to
+// this function.
+// Some features are unsupported, e.g. setting params->initial_data or render_dst.
+struct ra_tex *ra_create_wrapped_tex(struct ra *ra,
+ const struct ra_tex_params *params,
+ GLuint gl_texture)
+{
+ struct ra_tex *tex = gl_tex_create_blank(ra, params);
+ if (!tex)
+ return NULL;
+ struct ra_tex_gl *tex_gl = tex->priv;
+ tex_gl->texture = gl_texture;
+ return tex;
+}
+
+static const struct ra_format fbo_dummy_format = {
+ .name = "unknown_fbo",
+ .priv = (void *)&(const struct gl_format){
+ .name = "unknown",
+ .format = GL_RGBA,
+ .flags = F_CR,
+ },
+ .renderable = true,
+ .dummy_format = true,
+};
+
+// Create a ra_tex that merely wraps an existing framebuffer. gl_fbo can be 0
+// to wrap the default framebuffer.
+// The returned object is freed with ra_tex_free(), but this will not delete
+// the framebuffer object passed to this function.
+struct ra_tex *ra_create_wrapped_fb(struct ra *ra, GLuint gl_fbo, int w, int h)
+{
+ struct ra_tex *tex = talloc_zero(ra, struct ra_tex);
+ *tex = (struct ra_tex){
+ .params = {
+ .dimensions = 2,
+ .w = w, .h = h, .d = 1,
+ .format = &fbo_dummy_format,
+ .render_dst = true,
+ .blit_src = true,
+ .blit_dst = true,
+ },
+ };
+
+ struct ra_tex_gl *tex_gl = tex->priv = talloc_zero(NULL, struct ra_tex_gl);
+ *tex_gl = (struct ra_tex_gl){
+ .fbo = gl_fbo,
+ .internal_format = 0,
+ .format = GL_RGBA,
+ .type = 0,
+ };
+
+ return tex;
+}
+
+GL *ra_gl_get(struct ra *ra)
+{
+ struct ra_gl *p = ra->priv;
+ return p->gl;
+}
+
+// Return the associate glTexImage arguments for the given format. Sets all
+// fields to 0 on failure.
+void ra_gl_get_format(const struct ra_format *fmt, GLint *out_internal_format,
+ GLenum *out_format, GLenum *out_type)
+{
+ const struct gl_format *gl_format = fmt->priv;
+ *out_internal_format = gl_format->internal_format;
+ *out_format = gl_format->format;
+ *out_type = gl_format->type;
+}
+
+void ra_gl_get_raw_tex(struct ra *ra, struct ra_tex *tex,
+ GLuint *out_texture, GLenum *out_target)
+{
+ struct ra_tex_gl *tex_gl = tex->priv;
+ *out_texture = tex_gl->texture;
+ *out_target = tex_gl->target;
+}
+
+// Return whether the ra instance was created with ra_create_gl(). This is the
+// _only_ function that can be called on a ra instance of any type.
+bool ra_is_gl(struct ra *ra)
+{
+ return ra->fns == &ra_fns_gl;
+}
+
+static bool gl_tex_upload(struct ra *ra,
+ const struct ra_tex_upload_params *params)
+{
+ GL *gl = ra_gl_get(ra);
+ struct ra_tex *tex = params->tex;
+ struct ra_buf *buf = params->buf;
+ struct ra_tex_gl *tex_gl = tex->priv;
+ struct ra_buf_gl *buf_gl = buf ? buf->priv : NULL;
+ assert(tex->params.host_mutable);
+ assert(!params->buf || !params->src);
+
+ if (ra->use_pbo && !params->buf)
+ return ra_tex_upload_pbo(ra, &tex_gl->pbo, params);
+
+ const void *src = params->src;
+ if (buf) {
+ gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, buf_gl->buffer);
+ src = (void *)params->buf_offset;
+ }
+
+ gl->BindTexture(tex_gl->target, tex_gl->texture);
+ if (params->invalidate && gl->InvalidateTexImage)
+ gl->InvalidateTexImage(tex_gl->texture, 0);
+
+ switch (tex->params.dimensions) {
+ case 1:
+ gl->TexImage1D(tex_gl->target, 0, tex_gl->internal_format,
+ tex->params.w, 0, tex_gl->format, tex_gl->type, src);
+ break;
+ case 2: {
+ struct mp_rect rc = {0, 0, tex->params.w, tex->params.h};
+ if (params->rc)
+ rc = *params->rc;
+ gl_upload_tex(gl, tex_gl->target, tex_gl->format, tex_gl->type,
+ src, params->stride, rc.x0, rc.y0, rc.x1 - rc.x0,
+ rc.y1 - rc.y0);
+ break;
+ }
+ case 3:
+ gl->PixelStorei(GL_UNPACK_ALIGNMENT, 1);
+ gl->TexImage3D(GL_TEXTURE_3D, 0, tex_gl->internal_format, tex->params.w,
+ tex->params.h, tex->params.d, 0, tex_gl->format,
+ tex_gl->type, src);
+ gl->PixelStorei(GL_UNPACK_ALIGNMENT, 4);
+ break;
+ }
+
+ gl->BindTexture(tex_gl->target, 0);
+
+ if (buf) {
+ gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+ if (buf->params.host_mapped) {
+ // Make sure the PBO is not reused until GL is done with it. If a
+ // previous operation is pending, "update" it by creating a new
+ // fence that will cover the previous operation as well.
+ gl->DeleteSync(buf_gl->fence);
+ buf_gl->fence = gl->FenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
+ }
+ }
+
+ return true;
+}
+
+static bool gl_tex_download(struct ra *ra, struct ra_tex_download_params *params)
+{
+ GL *gl = ra_gl_get(ra);
+ struct ra_tex *tex = params->tex;
+ struct ra_tex_gl *tex_gl = tex->priv;
+ if (!tex_gl->fbo)
+ return false;
+ return gl_read_fbo_contents(gl, tex_gl->fbo, 1, tex_gl->format, tex_gl->type,
+ tex->params.w, tex->params.h, params->dst,
+ params->stride);
+}
+
+static void gl_buf_destroy(struct ra *ra, struct ra_buf *buf)
+{
+ if (!buf)
+ return;
+
+ GL *gl = ra_gl_get(ra);
+ struct ra_buf_gl *buf_gl = buf->priv;
+
+ if (buf_gl->fence)
+ gl->DeleteSync(buf_gl->fence);
+
+ if (buf->data) {
+ gl->BindBuffer(buf_gl->target, buf_gl->buffer);
+ gl->UnmapBuffer(buf_gl->target);
+ gl->BindBuffer(buf_gl->target, 0);
+ }
+ gl->DeleteBuffers(1, &buf_gl->buffer);
+
+ talloc_free(buf_gl);
+ talloc_free(buf);
+}
+
+static struct ra_buf *gl_buf_create(struct ra *ra,
+ const struct ra_buf_params *params)
+{
+ GL *gl = ra_gl_get(ra);
+
+ if (params->host_mapped && !gl->BufferStorage)
+ return NULL;
+
+ struct ra_buf *buf = talloc_zero(NULL, struct ra_buf);
+ buf->params = *params;
+ buf->params.initial_data = NULL;
+
+ struct ra_buf_gl *buf_gl = buf->priv = talloc_zero(NULL, struct ra_buf_gl);
+ gl->GenBuffers(1, &buf_gl->buffer);
+
+ switch (params->type) {
+ case RA_BUF_TYPE_TEX_UPLOAD: buf_gl->target = GL_PIXEL_UNPACK_BUFFER; break;
+ case RA_BUF_TYPE_SHADER_STORAGE: buf_gl->target = GL_SHADER_STORAGE_BUFFER; break;
+ case RA_BUF_TYPE_UNIFORM: buf_gl->target = GL_UNIFORM_BUFFER; break;
+ default: abort();
+ };
+
+ gl->BindBuffer(buf_gl->target, buf_gl->buffer);
+
+ if (params->host_mapped) {
+ unsigned flags = GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT |
+ GL_MAP_READ_BIT | GL_MAP_WRITE_BIT;
+
+ unsigned storflags = flags;
+ if (params->type == RA_BUF_TYPE_TEX_UPLOAD)
+ storflags |= GL_CLIENT_STORAGE_BIT;
+
+ gl->BufferStorage(buf_gl->target, params->size, params->initial_data,
+ storflags);
+ buf->data = gl->MapBufferRange(buf_gl->target, 0, params->size, flags);
+ if (!buf->data) {
+ gl_check_error(gl, ra->log, "mapping buffer");
+ gl_buf_destroy(ra, buf);
+ buf = NULL;
+ }
+ } else {
+ GLenum hint;
+ switch (params->type) {
+ case RA_BUF_TYPE_TEX_UPLOAD: hint = GL_STREAM_DRAW; break;
+ case RA_BUF_TYPE_SHADER_STORAGE: hint = GL_STREAM_COPY; break;
+ case RA_BUF_TYPE_UNIFORM: hint = GL_STATIC_DRAW; break;
+ default: MP_ASSERT_UNREACHABLE();
+ }
+
+ gl->BufferData(buf_gl->target, params->size, params->initial_data, hint);
+ }
+
+ gl->BindBuffer(buf_gl->target, 0);
+ return buf;
+}
+
+static void gl_buf_update(struct ra *ra, struct ra_buf *buf, ptrdiff_t offset,
+ const void *data, size_t size)
+{
+ GL *gl = ra_gl_get(ra);
+ struct ra_buf_gl *buf_gl = buf->priv;
+ assert(buf->params.host_mutable);
+
+ gl->BindBuffer(buf_gl->target, buf_gl->buffer);
+ gl->BufferSubData(buf_gl->target, offset, size, data);
+ gl->BindBuffer(buf_gl->target, 0);
+}
+
+static bool gl_buf_poll(struct ra *ra, struct ra_buf *buf)
+{
+ // Non-persistently mapped buffers are always implicitly reusable in OpenGL,
+ // the implementation will create more buffers under the hood if needed.
+ if (!buf->data)
+ return true;
+
+ GL *gl = ra_gl_get(ra);
+ struct ra_buf_gl *buf_gl = buf->priv;
+
+ if (buf_gl->fence) {
+ GLenum res = gl->ClientWaitSync(buf_gl->fence, 0, 0); // non-blocking
+ if (res == GL_ALREADY_SIGNALED) {
+ gl->DeleteSync(buf_gl->fence);
+ buf_gl->fence = NULL;
+ }
+ }
+
+ return !buf_gl->fence;
+}
+
+static void gl_clear(struct ra *ra, struct ra_tex *dst, float color[4],
+ struct mp_rect *scissor)
+{
+ GL *gl = ra_gl_get(ra);
+
+ assert(dst->params.render_dst);
+ struct ra_tex_gl *dst_gl = dst->priv;
+
+ gl->BindFramebuffer(GL_FRAMEBUFFER, dst_gl->fbo);
+
+ gl->Scissor(scissor->x0, scissor->y0,
+ scissor->x1 - scissor->x0,
+ scissor->y1 - scissor->y0);
+
+ gl->Enable(GL_SCISSOR_TEST);
+ gl->ClearColor(color[0], color[1], color[2], color[3]);
+ gl->Clear(GL_COLOR_BUFFER_BIT);
+ gl->Disable(GL_SCISSOR_TEST);
+
+ gl->BindFramebuffer(GL_FRAMEBUFFER, 0);
+}
+
+static void gl_blit(struct ra *ra, struct ra_tex *dst, struct ra_tex *src,
+ struct mp_rect *dst_rc, struct mp_rect *src_rc)
+{
+ GL *gl = ra_gl_get(ra);
+
+ assert(src->params.blit_src);
+ assert(dst->params.blit_dst);
+
+ struct ra_tex_gl *src_gl = src->priv;
+ struct ra_tex_gl *dst_gl = dst->priv;
+
+ gl->BindFramebuffer(GL_READ_FRAMEBUFFER, src_gl->fbo);
+ gl->BindFramebuffer(GL_DRAW_FRAMEBUFFER, dst_gl->fbo);
+ gl->BlitFramebuffer(src_rc->x0, src_rc->y0, src_rc->x1, src_rc->y1,
+ dst_rc->x0, dst_rc->y0, dst_rc->x1, dst_rc->y1,
+ GL_COLOR_BUFFER_BIT, GL_NEAREST);
+ gl->BindFramebuffer(GL_READ_FRAMEBUFFER, 0);
+ gl->BindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
+}
+
+static int gl_desc_namespace(struct ra *ra, enum ra_vartype type)
+{
+ return type;
+}
+
+static void gl_renderpass_destroy(struct ra *ra, struct ra_renderpass *pass)
+{
+ GL *gl = ra_gl_get(ra);
+ struct ra_renderpass_gl *pass_gl = pass->priv;
+ gl->DeleteProgram(pass_gl->program);
+ gl_vao_uninit(&pass_gl->vao);
+
+ talloc_free(pass_gl);
+ talloc_free(pass);
+}
+
+static const char *shader_typestr(GLenum type)
+{
+ switch (type) {
+ case GL_VERTEX_SHADER: return "vertex";
+ case GL_FRAGMENT_SHADER: return "fragment";
+ case GL_COMPUTE_SHADER: return "compute";
+ default: MP_ASSERT_UNREACHABLE();
+ }
+}
+
+static void compile_attach_shader(struct ra *ra, GLuint program,
+ GLenum type, const char *source, bool *ok)
+{
+ GL *gl = ra_gl_get(ra);
+
+ GLuint shader = gl->CreateShader(type);
+ gl->ShaderSource(shader, 1, &source, NULL);
+ gl->CompileShader(shader);
+ GLint status = 0;
+ gl->GetShaderiv(shader, GL_COMPILE_STATUS, &status);
+ GLint log_length = 0;
+ gl->GetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);
+
+ int pri = status ? (log_length > 1 ? MSGL_V : MSGL_DEBUG) : MSGL_ERR;
+ const char *typestr = shader_typestr(type);
+ if (mp_msg_test(ra->log, pri)) {
+ MP_MSG(ra, pri, "%s shader source:\n", typestr);
+ mp_log_source(ra->log, pri, source);
+ }
+ if (log_length > 1) {
+ GLchar *logstr = talloc_zero_size(NULL, log_length + 1);
+ gl->GetShaderInfoLog(shader, log_length, NULL, logstr);
+ MP_MSG(ra, pri, "%s shader compile log (status=%d):\n%s\n",
+ typestr, status, logstr);
+ talloc_free(logstr);
+ }
+ if (gl->GetTranslatedShaderSourceANGLE && mp_msg_test(ra->log, MSGL_DEBUG)) {
+ GLint len = 0;
+ gl->GetShaderiv(shader, GL_TRANSLATED_SHADER_SOURCE_LENGTH_ANGLE, &len);
+ if (len > 0) {
+ GLchar *sstr = talloc_zero_size(NULL, len + 1);
+ gl->GetTranslatedShaderSourceANGLE(shader, len, NULL, sstr);
+ MP_DBG(ra, "Translated shader:\n");
+ mp_log_source(ra->log, MSGL_DEBUG, sstr);
+ }
+ }
+
+ gl->AttachShader(program, shader);
+ gl->DeleteShader(shader);
+
+ *ok &= status;
+}
+
+static void link_shader(struct ra *ra, GLuint program, bool *ok)
+{
+ GL *gl = ra_gl_get(ra);
+
+ gl->LinkProgram(program);
+ GLint status = 0;
+ gl->GetProgramiv(program, GL_LINK_STATUS, &status);
+ GLint log_length = 0;
+ gl->GetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length);
+
+ int pri = status ? (log_length > 1 ? MSGL_V : MSGL_DEBUG) : MSGL_ERR;
+ if (mp_msg_test(ra->log, pri)) {
+ GLchar *logstr = talloc_zero_size(NULL, log_length + 1);
+ gl->GetProgramInfoLog(program, log_length, NULL, logstr);
+ MP_MSG(ra, pri, "shader link log (status=%d): %s\n", status, logstr);
+ talloc_free(logstr);
+ }
+
+ *ok &= status;
+}
+
+// either 'compute' or both 'vertex' and 'frag' are needed
+static GLuint compile_program(struct ra *ra, const struct ra_renderpass_params *p)
+{
+ GL *gl = ra_gl_get(ra);
+
+ GLuint prog = gl->CreateProgram();
+ bool ok = true;
+ if (p->type == RA_RENDERPASS_TYPE_COMPUTE)
+ compile_attach_shader(ra, prog, GL_COMPUTE_SHADER, p->compute_shader, &ok);
+ if (p->type == RA_RENDERPASS_TYPE_RASTER) {
+ compile_attach_shader(ra, prog, GL_VERTEX_SHADER, p->vertex_shader, &ok);
+ compile_attach_shader(ra, prog, GL_FRAGMENT_SHADER, p->frag_shader, &ok);
+ for (int n = 0; n < p->num_vertex_attribs; n++)
+ gl->BindAttribLocation(prog, n, p->vertex_attribs[n].name);
+ }
+ link_shader(ra, prog, &ok);
+ if (!ok) {
+ gl->DeleteProgram(prog);
+ prog = 0;
+ }
+ return prog;
+}
+
+static GLuint load_program(struct ra *ra, const struct ra_renderpass_params *p,
+ bstr *out_cached_data)
+{
+ GL *gl = ra_gl_get(ra);
+
+ GLuint prog = 0;
+
+ if (gl->ProgramBinary && p->cached_program.len > 4) {
+ GLenum format = AV_RL32(p->cached_program.start);
+ prog = gl->CreateProgram();
+ gl_check_error(gl, ra->log, "before loading program");
+ gl->ProgramBinary(prog, format, p->cached_program.start + 4,
+ p->cached_program.len - 4);
+ gl->GetError(); // discard potential useless error
+ GLint status = 0;
+ gl->GetProgramiv(prog, GL_LINK_STATUS, &status);
+ if (status) {
+ MP_DBG(ra, "Loading binary program succeeded.\n");
+ } else {
+ gl->DeleteProgram(prog);
+ prog = 0;
+ }
+ }
+
+ if (!prog) {
+ prog = compile_program(ra, p);
+
+ if (gl->GetProgramBinary && prog) {
+ GLint size = 0;
+ gl->GetProgramiv(prog, GL_PROGRAM_BINARY_LENGTH, &size);
+ uint8_t *buffer = talloc_size(NULL, size + 4);
+ GLsizei actual_size = 0;
+ GLenum binary_format = 0;
+ if (size > 0) {
+ gl->GetProgramBinary(prog, size, &actual_size, &binary_format,
+ buffer + 4);
+ }
+ AV_WL32(buffer, binary_format);
+ if (actual_size) {
+ *out_cached_data = (bstr){buffer, actual_size + 4};
+ } else {
+ talloc_free(buffer);
+ }
+ }
+ }
+
+ return prog;
+}
+
+static struct ra_renderpass *gl_renderpass_create(struct ra *ra,
+ const struct ra_renderpass_params *params)
+{
+ GL *gl = ra_gl_get(ra);
+
+ struct ra_renderpass *pass = talloc_zero(NULL, struct ra_renderpass);
+ pass->params = *ra_renderpass_params_copy(pass, params);
+ pass->params.cached_program = (bstr){0};
+ struct ra_renderpass_gl *pass_gl = pass->priv =
+ talloc_zero(NULL, struct ra_renderpass_gl);
+
+ bstr cached = {0};
+ pass_gl->program = load_program(ra, params, &cached);
+ if (!pass_gl->program) {
+ gl_renderpass_destroy(ra, pass);
+ return NULL;
+ }
+
+ talloc_steal(pass, cached.start);
+ pass->params.cached_program = cached;
+
+ gl->UseProgram(pass_gl->program);
+ for (int n = 0; n < params->num_inputs; n++) {
+ GLint loc =
+ gl->GetUniformLocation(pass_gl->program, params->inputs[n].name);
+ MP_TARRAY_APPEND(pass_gl, pass_gl->uniform_loc, pass_gl->num_uniform_loc,
+ loc);
+
+ // For compatibility with older OpenGL, we need to explicitly update
+ // the texture/image unit bindings after creating the shader program,
+ // since specifying it directly requires GLSL 4.20+
+ switch (params->inputs[n].type) {
+ case RA_VARTYPE_TEX:
+ case RA_VARTYPE_IMG_W:
+ gl->Uniform1i(loc, params->inputs[n].binding);
+ break;
+ }
+ }
+ gl->UseProgram(0);
+
+ gl_vao_init(&pass_gl->vao, gl, pass->params.vertex_stride,
+ pass->params.vertex_attribs, pass->params.num_vertex_attribs);
+
+ return pass;
+}
+
+static GLenum map_blend(enum ra_blend blend)
+{
+ switch (blend) {
+ case RA_BLEND_ZERO: return GL_ZERO;
+ case RA_BLEND_ONE: return GL_ONE;
+ case RA_BLEND_SRC_ALPHA: return GL_SRC_ALPHA;
+ case RA_BLEND_ONE_MINUS_SRC_ALPHA: return GL_ONE_MINUS_SRC_ALPHA;
+ default: return 0;
+ }
+}
+
+// Assumes program is current (gl->UseProgram(program)).
+static void update_uniform(struct ra *ra, struct ra_renderpass *pass,
+ struct ra_renderpass_input_val *val)
+{
+ GL *gl = ra_gl_get(ra);
+ struct ra_renderpass_gl *pass_gl = pass->priv;
+
+ struct ra_renderpass_input *input = &pass->params.inputs[val->index];
+ assert(val->index >= 0 && val->index < pass_gl->num_uniform_loc);
+ GLint loc = pass_gl->uniform_loc[val->index];
+
+ switch (input->type) {
+ case RA_VARTYPE_INT: {
+ assert(input->dim_v * input->dim_m == 1);
+ if (loc < 0)
+ break;
+ gl->Uniform1i(loc, *(int *)val->data);
+ break;
+ }
+ case RA_VARTYPE_FLOAT: {
+ float *f = val->data;
+ if (loc < 0)
+ break;
+ if (input->dim_m == 1) {
+ switch (input->dim_v) {
+ case 1: gl->Uniform1f(loc, f[0]); break;
+ case 2: gl->Uniform2f(loc, f[0], f[1]); break;
+ case 3: gl->Uniform3f(loc, f[0], f[1], f[2]); break;
+ case 4: gl->Uniform4f(loc, f[0], f[1], f[2], f[3]); break;
+ default: MP_ASSERT_UNREACHABLE();
+ }
+ } else if (input->dim_v == 2 && input->dim_m == 2) {
+ gl->UniformMatrix2fv(loc, 1, GL_FALSE, f);
+ } else if (input->dim_v == 3 && input->dim_m == 3) {
+ gl->UniformMatrix3fv(loc, 1, GL_FALSE, f);
+ } else {
+ MP_ASSERT_UNREACHABLE();
+ }
+ break;
+ }
+ case RA_VARTYPE_IMG_W: {
+ struct ra_tex *tex = *(struct ra_tex **)val->data;
+ struct ra_tex_gl *tex_gl = tex->priv;
+ assert(tex->params.storage_dst);
+ gl->BindImageTexture(input->binding, tex_gl->texture, 0, GL_FALSE, 0,
+ GL_WRITE_ONLY, tex_gl->internal_format);
+ break;
+ }
+ case RA_VARTYPE_TEX: {
+ struct ra_tex *tex = *(struct ra_tex **)val->data;
+ struct ra_tex_gl *tex_gl = tex->priv;
+ assert(tex->params.render_src);
+ gl->ActiveTexture(GL_TEXTURE0 + input->binding);
+ gl->BindTexture(tex_gl->target, tex_gl->texture);
+ break;
+ }
+ case RA_VARTYPE_BUF_RO: // fall through
+ case RA_VARTYPE_BUF_RW: {
+ struct ra_buf *buf = *(struct ra_buf **)val->data;
+ struct ra_buf_gl *buf_gl = buf->priv;
+ gl->BindBufferBase(buf_gl->target, input->binding, buf_gl->buffer);
+ // SSBOs are not implicitly coherent in OpengL
+ if (input->type == RA_VARTYPE_BUF_RW)
+ gl->MemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
+ break;
+ }
+ default:
+ MP_ASSERT_UNREACHABLE();
+ }
+}
+
+static void disable_binding(struct ra *ra, struct ra_renderpass *pass,
+ struct ra_renderpass_input_val *val)
+{
+ GL *gl = ra_gl_get(ra);
+
+ struct ra_renderpass_input *input = &pass->params.inputs[val->index];
+
+ switch (input->type) {
+ case RA_VARTYPE_IMG_W: /* fall through */
+ case RA_VARTYPE_TEX: {
+ struct ra_tex *tex = *(struct ra_tex **)val->data;
+ struct ra_tex_gl *tex_gl = tex->priv;
+ assert(tex->params.render_src);
+ if (input->type == RA_VARTYPE_TEX) {
+ gl->ActiveTexture(GL_TEXTURE0 + input->binding);
+ gl->BindTexture(tex_gl->target, 0);
+ } else {
+ gl->BindImageTexture(input->binding, 0, 0, GL_FALSE, 0,
+ GL_WRITE_ONLY, tex_gl->internal_format);
+ }
+ break;
+ }
+ case RA_VARTYPE_BUF_RW:
+ gl->BindBufferBase(GL_SHADER_STORAGE_BUFFER, input->binding, 0);
+ break;
+ }
+}
+
+static void gl_renderpass_run(struct ra *ra,
+ const struct ra_renderpass_run_params *params)
+{
+ GL *gl = ra_gl_get(ra);
+ struct ra_renderpass *pass = params->pass;
+ struct ra_renderpass_gl *pass_gl = pass->priv;
+
+ gl->UseProgram(pass_gl->program);
+
+ for (int n = 0; n < params->num_values; n++)
+ update_uniform(ra, pass, &params->values[n]);
+ gl->ActiveTexture(GL_TEXTURE0);
+
+ switch (pass->params.type) {
+ case RA_RENDERPASS_TYPE_RASTER: {
+ struct ra_tex_gl *target_gl = params->target->priv;
+ assert(params->target->params.render_dst);
+ assert(params->target->params.format == pass->params.target_format);
+ gl->BindFramebuffer(GL_FRAMEBUFFER, target_gl->fbo);
+ if (pass->params.invalidate_target && gl->InvalidateFramebuffer) {
+ GLenum fb = target_gl->fbo ? GL_COLOR_ATTACHMENT0 : GL_COLOR;
+ gl->InvalidateFramebuffer(GL_FRAMEBUFFER, 1, &fb);
+ }
+ gl->Viewport(params->viewport.x0, params->viewport.y0,
+ mp_rect_w(params->viewport),
+ mp_rect_h(params->viewport));
+ gl->Scissor(params->scissors.x0, params->scissors.y0,
+ mp_rect_w(params->scissors),
+ mp_rect_h(params->scissors));
+ gl->Enable(GL_SCISSOR_TEST);
+ if (pass->params.enable_blend) {
+ gl->BlendFuncSeparate(map_blend(pass->params.blend_src_rgb),
+ map_blend(pass->params.blend_dst_rgb),
+ map_blend(pass->params.blend_src_alpha),
+ map_blend(pass->params.blend_dst_alpha));
+ gl->Enable(GL_BLEND);
+ }
+ gl_vao_draw_data(&pass_gl->vao, GL_TRIANGLES, params->vertex_data,
+ params->vertex_count);
+ gl->Disable(GL_SCISSOR_TEST);
+ gl->Disable(GL_BLEND);
+ gl->BindFramebuffer(GL_FRAMEBUFFER, 0);
+ break;
+ }
+ case RA_RENDERPASS_TYPE_COMPUTE: {
+ gl->DispatchCompute(params->compute_groups[0],
+ params->compute_groups[1],
+ params->compute_groups[2]);
+
+ gl->MemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT);
+ break;
+ }
+ default: MP_ASSERT_UNREACHABLE();
+ }
+
+ for (int n = 0; n < params->num_values; n++)
+ disable_binding(ra, pass, &params->values[n]);
+ gl->ActiveTexture(GL_TEXTURE0);
+
+ gl->UseProgram(0);
+}
+
+// Timers in GL use query objects, and are asynchronous. So pool a few of
+// these together. GL_QUERY_OBJECT_NUM should be large enough to avoid this
+// ever blocking. We can afford to throw query objects around, there's no
+// practical limit on them and their overhead is small.
+
+#define GL_QUERY_OBJECT_NUM 8
+
+struct gl_timer {
+ GLuint query[GL_QUERY_OBJECT_NUM];
+ int idx;
+ uint64_t result;
+ bool active;
+};
+
+static ra_timer *gl_timer_create(struct ra *ra)
+{
+ GL *gl = ra_gl_get(ra);
+
+ if (!gl->GenQueries)
+ return NULL;
+
+ struct gl_timer *timer = talloc_zero(NULL, struct gl_timer);
+ gl->GenQueries(GL_QUERY_OBJECT_NUM, timer->query);
+
+ return (ra_timer *)timer;
+}
+
+static void gl_timer_destroy(struct ra *ra, ra_timer *ratimer)
+{
+ if (!ratimer)
+ return;
+
+ GL *gl = ra_gl_get(ra);
+ struct gl_timer *timer = ratimer;
+
+ gl->DeleteQueries(GL_QUERY_OBJECT_NUM, timer->query);
+ talloc_free(timer);
+}
+
+static void gl_timer_start(struct ra *ra, ra_timer *ratimer)
+{
+ struct ra_gl *p = ra->priv;
+ GL *gl = p->gl;
+ struct gl_timer *timer = ratimer;
+
+ // GL_TIME_ELAPSED queries are not re-entrant, so just do nothing instead
+ // of crashing. Work-around for shitty GL limitations
+ if (p->timer_active)
+ return;
+
+ // If this query object already contains a result, we need to retrieve it
+ timer->result = 0;
+ if (gl->IsQuery(timer->query[timer->idx])) {
+ gl->GetQueryObjectui64v(timer->query[timer->idx], GL_QUERY_RESULT,
+ &timer->result);
+ }
+
+ gl->BeginQuery(GL_TIME_ELAPSED, timer->query[timer->idx++]);
+ timer->idx %= GL_QUERY_OBJECT_NUM;
+
+ p->timer_active = timer->active = true;
+}
+
+static uint64_t gl_timer_stop(struct ra *ra, ra_timer *ratimer)
+{
+ struct ra_gl *p = ra->priv;
+ GL *gl = p->gl;
+ struct gl_timer *timer = ratimer;
+
+ if (!timer->active)
+ return 0;
+
+ gl->EndQuery(GL_TIME_ELAPSED);
+ p->timer_active = timer->active = false;
+
+ return timer->result;
+}
+
+static void gl_debug_marker(struct ra *ra, const char *msg)
+{
+ struct ra_gl *p = ra->priv;
+
+ if (p->debug_enable)
+ gl_check_error(p->gl, ra->log, msg);
+}
+
+static struct ra_fns ra_fns_gl = {
+ .destroy = gl_destroy,
+ .tex_create = gl_tex_create,
+ .tex_destroy = gl_tex_destroy,
+ .tex_upload = gl_tex_upload,
+ .tex_download = gl_tex_download,
+ .buf_create = gl_buf_create,
+ .buf_destroy = gl_buf_destroy,
+ .buf_update = gl_buf_update,
+ .buf_poll = gl_buf_poll,
+ .clear = gl_clear,
+ .blit = gl_blit,
+ .uniform_layout = std140_layout,
+ .desc_namespace = gl_desc_namespace,
+ .renderpass_create = gl_renderpass_create,
+ .renderpass_destroy = gl_renderpass_destroy,
+ .renderpass_run = gl_renderpass_run,
+ .timer_create = gl_timer_create,
+ .timer_destroy = gl_timer_destroy,
+ .timer_start = gl_timer_start,
+ .timer_stop = gl_timer_stop,
+ .debug_marker = gl_debug_marker,
+};
diff --git a/video/out/opengl/ra_gl.h b/video/out/opengl/ra_gl.h
new file mode 100644
index 0000000..9844977
--- /dev/null
+++ b/video/out/opengl/ra_gl.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include "common.h"
+#include "utils.h"
+
+struct ra *ra_create_gl(GL *gl, struct mp_log *log);
+struct ra_tex *ra_create_wrapped_tex(struct ra *ra,
+ const struct ra_tex_params *params,
+ GLuint gl_texture);
+struct ra_tex *ra_create_wrapped_fb(struct ra *ra, GLuint gl_fbo, int w, int h);
+GL *ra_gl_get(struct ra *ra);
+void ra_gl_set_debug(struct ra *ra, bool enable);
+void ra_gl_get_format(const struct ra_format *fmt, GLint *out_internal_format,
+ GLenum *out_format, GLenum *out_type);
+void ra_gl_get_raw_tex(struct ra *ra, struct ra_tex *tex,
+ GLuint *out_texture, GLenum *out_target);
+bool ra_is_gl(struct ra *ra);
diff --git a/video/out/opengl/utils.c b/video/out/opengl/utils.c
new file mode 100644
index 0000000..a551ce4
--- /dev/null
+++ b/video/out/opengl/utils.c
@@ -0,0 +1,282 @@
+/*
+ * This file is part of mpv.
+ * Parts based on MPlayer code by Reimar Döffinger.
+ *
+ * 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 <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <assert.h>
+
+#include <libavutil/sha.h>
+#include <libavutil/intreadwrite.h>
+#include <libavutil/mem.h>
+
+#include "osdep/io.h"
+
+#include "common/common.h"
+#include "options/path.h"
+#include "stream/stream.h"
+#include "formats.h"
+#include "utils.h"
+
+// GLU has this as gluErrorString (we don't use GLU, as it is legacy-OpenGL)
+static const char *gl_error_to_string(GLenum error)
+{
+ switch (error) {
+ case GL_INVALID_ENUM: return "INVALID_ENUM";
+ case GL_INVALID_VALUE: return "INVALID_VALUE";
+ case GL_INVALID_OPERATION: return "INVALID_OPERATION";
+ case GL_INVALID_FRAMEBUFFER_OPERATION: return "INVALID_FRAMEBUFFER_OPERATION";
+ case GL_OUT_OF_MEMORY: return "OUT_OF_MEMORY";
+ default: return "unknown";
+ }
+}
+
+void gl_check_error(GL *gl, struct mp_log *log, const char *info)
+{
+ for (;;) {
+ GLenum error = gl->GetError();
+ if (error == GL_NO_ERROR)
+ break;
+ mp_msg(log, MSGL_ERR, "%s: OpenGL error %s.\n", info,
+ gl_error_to_string(error));
+ }
+}
+
+static int get_alignment(int stride)
+{
+ if (stride % 8 == 0)
+ return 8;
+ if (stride % 4 == 0)
+ return 4;
+ if (stride % 2 == 0)
+ return 2;
+ return 1;
+}
+
+// upload a texture, handling things like stride and slices
+// target: texture target, usually GL_TEXTURE_2D
+// format, type: texture parameters
+// dataptr, stride: image data
+// x, y, width, height: part of the image to upload
+void gl_upload_tex(GL *gl, GLenum target, GLenum format, GLenum type,
+ const void *dataptr, int stride,
+ int x, int y, int w, int h)
+{
+ int bpp = gl_bytes_per_pixel(format, type);
+ const uint8_t *data = dataptr;
+ int y_max = y + h;
+ if (w <= 0 || h <= 0 || !bpp)
+ return;
+ assert(stride > 0);
+ gl->PixelStorei(GL_UNPACK_ALIGNMENT, get_alignment(stride));
+ int slice = h;
+ if (gl->mpgl_caps & MPGL_CAP_ROW_LENGTH) {
+ // this is not always correct, but should work for MPlayer
+ gl->PixelStorei(GL_UNPACK_ROW_LENGTH, stride / bpp);
+ } else {
+ if (stride != bpp * w)
+ slice = 1; // very inefficient, but at least it works
+ }
+ for (; y + slice <= y_max; y += slice) {
+ gl->TexSubImage2D(target, 0, x, y, w, slice, format, type, data);
+ data += stride * slice;
+ }
+ if (y < y_max)
+ gl->TexSubImage2D(target, 0, x, y, w, y_max - y, format, type, data);
+ if (gl->mpgl_caps & MPGL_CAP_ROW_LENGTH)
+ gl->PixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+ gl->PixelStorei(GL_UNPACK_ALIGNMENT, 4);
+}
+
+bool gl_read_fbo_contents(GL *gl, int fbo, int dir, GLenum format, GLenum type,
+ int w, int h, uint8_t *dst, int dst_stride)
+{
+ assert(dir == 1 || dir == -1);
+ if (fbo == 0 && gl->es)
+ return false; // ES can't read from front buffer
+ gl->BindFramebuffer(GL_FRAMEBUFFER, fbo);
+ GLenum obj = fbo ? GL_COLOR_ATTACHMENT0 : GL_FRONT;
+ gl->PixelStorei(GL_PACK_ALIGNMENT, 1);
+ gl->ReadBuffer(obj);
+ // reading by line allows flipping, and avoids stride-related trouble
+ int y1 = dir > 0 ? 0 : h;
+ for (int y = 0; y < h; y++)
+ gl->ReadPixels(0, y, w, 1, format, type, dst + (y1 + dir * y) * dst_stride);
+ gl->PixelStorei(GL_PACK_ALIGNMENT, 4);
+ gl->BindFramebuffer(GL_FRAMEBUFFER, 0);
+ return true;
+}
+
+static void gl_vao_enable_attribs(struct gl_vao *vao)
+{
+ GL *gl = vao->gl;
+
+ for (int n = 0; n < vao->num_entries; n++) {
+ const struct ra_renderpass_input *e = &vao->entries[n];
+ GLenum type = 0;
+ bool normalized = false;
+ switch (e->type) {
+ case RA_VARTYPE_INT:
+ type = GL_INT;
+ break;
+ case RA_VARTYPE_FLOAT:
+ type = GL_FLOAT;
+ break;
+ case RA_VARTYPE_BYTE_UNORM:
+ type = GL_UNSIGNED_BYTE;
+ normalized = true;
+ break;
+ default:
+ abort();
+ }
+ assert(e->dim_m == 1);
+
+ gl->EnableVertexAttribArray(n);
+ gl->VertexAttribPointer(n, e->dim_v, type, normalized,
+ vao->stride, (void *)(intptr_t)e->offset);
+ }
+}
+
+void gl_vao_init(struct gl_vao *vao, GL *gl, int stride,
+ const struct ra_renderpass_input *entries,
+ int num_entries)
+{
+ assert(!vao->vao);
+ assert(!vao->buffer);
+
+ *vao = (struct gl_vao){
+ .gl = gl,
+ .stride = stride,
+ .entries = entries,
+ .num_entries = num_entries,
+ };
+
+ gl->GenBuffers(1, &vao->buffer);
+
+ if (gl->BindVertexArray) {
+ gl->BindBuffer(GL_ARRAY_BUFFER, vao->buffer);
+
+ gl->GenVertexArrays(1, &vao->vao);
+ gl->BindVertexArray(vao->vao);
+ gl_vao_enable_attribs(vao);
+ gl->BindVertexArray(0);
+
+ gl->BindBuffer(GL_ARRAY_BUFFER, 0);
+ }
+}
+
+void gl_vao_uninit(struct gl_vao *vao)
+{
+ GL *gl = vao->gl;
+ if (!gl)
+ return;
+
+ if (gl->DeleteVertexArrays)
+ gl->DeleteVertexArrays(1, &vao->vao);
+ gl->DeleteBuffers(1, &vao->buffer);
+
+ *vao = (struct gl_vao){0};
+}
+
+static void gl_vao_bind(struct gl_vao *vao)
+{
+ GL *gl = vao->gl;
+
+ if (gl->BindVertexArray) {
+ gl->BindVertexArray(vao->vao);
+ } else {
+ gl->BindBuffer(GL_ARRAY_BUFFER, vao->buffer);
+ gl_vao_enable_attribs(vao);
+ gl->BindBuffer(GL_ARRAY_BUFFER, 0);
+ }
+}
+
+static void gl_vao_unbind(struct gl_vao *vao)
+{
+ GL *gl = vao->gl;
+
+ if (gl->BindVertexArray) {
+ gl->BindVertexArray(0);
+ } else {
+ for (int n = 0; n < vao->num_entries; n++)
+ gl->DisableVertexAttribArray(n);
+ }
+}
+
+// Draw the vertex data (as described by the gl_vao_entry entries) in ptr
+// to the screen. num is the number of vertexes. prim is usually GL_TRIANGLES.
+// If ptr is NULL, then skip the upload, and use the data uploaded with the
+// previous call.
+void gl_vao_draw_data(struct gl_vao *vao, GLenum prim, void *ptr, size_t num)
+{
+ GL *gl = vao->gl;
+
+ if (ptr) {
+ gl->BindBuffer(GL_ARRAY_BUFFER, vao->buffer);
+ gl->BufferData(GL_ARRAY_BUFFER, num * vao->stride, ptr, GL_STREAM_DRAW);
+ gl->BindBuffer(GL_ARRAY_BUFFER, 0);
+ }
+
+ gl_vao_bind(vao);
+
+ gl->DrawArrays(prim, 0, num);
+
+ gl_vao_unbind(vao);
+}
+
+static void GLAPIENTRY gl_debug_cb(GLenum source, GLenum type, GLuint id,
+ GLenum severity, GLsizei length,
+ const GLchar *message, const void *userParam)
+{
+ // keep in mind that the debug callback can be asynchronous
+ struct mp_log *log = (void *)userParam;
+ int level = MSGL_ERR;
+ switch (severity) {
+ case GL_DEBUG_SEVERITY_NOTIFICATION:level = MSGL_V; break;
+ case GL_DEBUG_SEVERITY_LOW: level = MSGL_INFO; break;
+ case GL_DEBUG_SEVERITY_MEDIUM: level = MSGL_WARN; break;
+ case GL_DEBUG_SEVERITY_HIGH: level = MSGL_ERR; break;
+ }
+ mp_msg(log, level, "GL: %s\n", message);
+}
+
+void gl_set_debug_logger(GL *gl, struct mp_log *log)
+{
+ if (gl->DebugMessageCallback)
+ gl->DebugMessageCallback(log ? gl_debug_cb : NULL, log);
+}
+
+// Given a GL combined extension string in extensions, find out whether ext
+// is included in it. Basically, a word search.
+bool gl_check_extension(const char *extensions, const char *ext)
+{
+ int len = strlen(ext);
+ const char *cur = extensions;
+ while (cur) {
+ cur = strstr(cur, ext);
+ if (!cur)
+ break;
+ if ((cur == extensions || cur[-1] == ' ') &&
+ (cur[len] == '\0' || cur[len] == ' '))
+ return true;
+ cur += len;
+ }
+ return false;
+}
diff --git a/video/out/opengl/utils.h b/video/out/opengl/utils.h
new file mode 100644
index 0000000..9bcadae
--- /dev/null
+++ b/video/out/opengl/utils.h
@@ -0,0 +1,57 @@
+/*
+ * This file is part of mpv.
+ * Parts based on MPlayer code by Reimar Döffinger.
+ *
+ * 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/>.
+ */
+
+#ifndef MP_GL_UTILS_
+#define MP_GL_UTILS_
+
+#include <math.h>
+
+#include "video/out/gpu/utils.h"
+#include "common.h"
+
+struct mp_log;
+
+void gl_check_error(GL *gl, struct mp_log *log, const char *info);
+
+void gl_upload_tex(GL *gl, GLenum target, GLenum format, GLenum type,
+ const void *dataptr, int stride,
+ int x, int y, int w, int h);
+
+bool gl_read_fbo_contents(GL *gl, int fbo, int dir, GLenum format, GLenum type,
+ int w, int h, uint8_t *dst, int dst_stride);
+
+struct gl_vao {
+ GL *gl;
+ GLuint vao; // the VAO object, or 0 if unsupported by driver
+ GLuint buffer; // GL_ARRAY_BUFFER used for the data
+ int stride; // size of each element (interleaved elements are assumed)
+ const struct ra_renderpass_input *entries;
+ int num_entries;
+};
+
+void gl_vao_init(struct gl_vao *vao, GL *gl, int stride,
+ const struct ra_renderpass_input *entries,
+ int num_entries);
+void gl_vao_uninit(struct gl_vao *vao);
+void gl_vao_draw_data(struct gl_vao *vao, GLenum prim, void *ptr, size_t num);
+
+void gl_set_debug_logger(GL *gl, struct mp_log *log);
+
+bool gl_check_extension(const char *extensions, const char *ext);
+
+#endif