diff options
Diffstat (limited to 'video/out/opengl')
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(¶m) && + 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, ¶ms, + 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, ¶ms, 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, ¶ms, 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, ¶ms, 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], ¶m); + 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, ¶ms->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, ¶ms->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 |