From ded6b3af41cd5d41f4df728ced4b194cf0426105 Mon Sep 17 00:00:00 2001 From: Lukasz Marek Date: Sun, 24 Nov 2013 20:13:27 +0100 Subject: lavd: add opengl device It can render to OpenGL context provided by application or into SDL window Signed-off-by: Lukasz Marek --- Changelog | 1 + configure | 13 + doc/outdevs.texi | 35 ++ libavdevice/Makefile | 1 + libavdevice/alldevices.c | 1 + libavdevice/opengl_enc.c | 1221 ++++++++++++++++++++++++++++++++++++++ libavdevice/opengl_enc_shaders.h | 176 ++++++ 7 files changed, 1448 insertions(+) create mode 100644 libavdevice/opengl_enc.c create mode 100644 libavdevice/opengl_enc_shaders.h diff --git a/Changelog b/Changelog index a982b61435..2291cccc14 100644 --- a/Changelog +++ b/Changelog @@ -21,6 +21,7 @@ version - framepack filter - XYZ12 rawvideo support in NUT - Exif metadata support in WebP decoder +- OpenGL device version 2.1: diff --git a/configure b/configure index 4c649063b2..57faed5c79 100755 --- a/configure +++ b/configure @@ -251,6 +251,7 @@ External library support: --enable-libzvbi enable teletext support via libzvbi [no] --enable-openal enable OpenAL 1.1 capture support [no] --enable-opencl enable OpenCL code + --enable-opengl enable OpenGL rendering [no] --enable-openssl enable openssl [no] --enable-x11grab enable X11 grabbing [no] --disable-zlib disable zlib [autodetect] @@ -1311,6 +1312,7 @@ EXTERNAL_LIBRARY_LIST=" libzvbi openal opencl + opengl openssl x11grab zlib @@ -1554,6 +1556,7 @@ HAVE_LIST=" dxva_h ebp_available ebx_available + ES2_gl_h fast_64bit fast_clz fast_cmov @@ -1570,6 +1573,7 @@ HAVE_LIST=" getservbyport gettimeofday glob + glXGetProcAddress gnu_as gnu_windres gsm_h @@ -1603,6 +1607,7 @@ HAVE_LIST=" mprotect nanosleep openjpeg_1_5_openjpeg_h + OpenGL_gl3_h PeekNamedPipe perl pod2man @@ -1656,6 +1661,7 @@ HAVE_LIST=" vdpau_x11 vfp_args VirtualAlloc + wglGetProcAddress windows_h winsock2_h xform_asm @@ -2262,6 +2268,7 @@ libcdio_indev_deps="libcdio" libdc1394_indev_deps="libdc1394" libv4l2_indev_deps="libv4l2" openal_indev_deps="openal" +opengl_outdev_deps="opengl" oss_indev_deps_any="soundcard_h sys_soundcard_h" oss_outdev_deps_any="soundcard_h sys_soundcard_h" pulse_indev_deps="libpulse" @@ -4498,6 +4505,12 @@ enabled opencl && { check_lib2 OpenCL/cl.h clEnqueueNDRangeKernel -Wl { check_cpp_condition "OpenCL/cl.h" "defined(CL_VERSION_1_2)" || check_cpp_condition "CL/cl.h" "defined(CL_VERSION_1_2)" || die "ERROR: opencl must be installed and version must be 1.2 or compatible"; } +enabled opengl && { check_lib GL/glx.h glXGetProcAddress "-lGL" || + check_lib2 windows.h wglGetProcAddress "-lopengl32 -lgdi32" || + check_lib2 OpenGL/gl3.h glGetError "-Wl,-framework,OpenGL" || + check_lib2 ES2/gl.h glGetError "-isysroot=${sysroot} -Wl,-framework,OpenGLES" || + die "ERROR: opengl not found." + } enabled openssl && { check_lib openssl/ssl.h SSL_library_init -lssl -lcrypto || check_lib openssl/ssl.h SSL_library_init -lssl32 -leay32 || check_lib openssl/ssl.h SSL_library_init -lssl -lcrypto -lws2_32 -lgdi32 || diff --git a/doc/outdevs.texi b/doc/outdevs.texi index a204f3264a..d2ccef216a 100644 --- a/doc/outdevs.texi +++ b/doc/outdevs.texi @@ -149,6 +149,41 @@ ffmpeg -re -i INPUT -vcodec rawvideo -pix_fmt bgra -f fbdev /dev/fb0 See also @url{http://linux-fbdev.sourceforge.net/}, and fbset(1). +@section opengl +OpenGL output device. + +To enable this output device you need to configure FFmpeg with @code{--enable-opengl}. + +Device allows to render to OpenGL context. +Context may be provided by application or default SDL window is created. + +When device renders to external context, application must implement handlers for following messages: +@code{AV_CTL_MESSAGE_CREATE_WINDOW_BUFFER} - create OpenGL context on current thread. +@code{AV_CTL_MESSAGE_PREPARE_WINDOW_BUFFER} - make OpenGL context current. +@code{AV_CTL_MESSAGE_DISPLAY_WINDOW_BUFFER} - swap buffers. +@code{AV_CTL_MESSAGE_DESTROY_WINDOW_BUFFER} - destroy OpenGL context. +Application is also required to inform a device about current resolution by sending @code{AV_DEVICE_WINDOW_RESIZED} message. + +@subsection Options +@table @option + +@item background +Set background color. Black is a default. +@item no_window +Disables default SDL window when set to non-zero value. +Application must provide OpenGL context and both @code{window_size_cb} and @code{window_swap_buffers_cb} callbacks when set. +@item window_title +Set the SDL window title, if not specified default to the filename specified for the output device. +Ignored when @option{no_window} is set. + +@end table + +@subsection Examples +Play a file on SDL window using OpenGL rendering: +@example +ffmpeg -i INPUT -f opengl "window title" +@end example + @section oss OSS (Open Sound System) output device. diff --git a/libavdevice/Makefile b/libavdevice/Makefile index 531818a4f5..fb57c33a80 100644 --- a/libavdevice/Makefile +++ b/libavdevice/Makefile @@ -29,6 +29,7 @@ OBJS-$(CONFIG_IEC61883_INDEV) += iec61883.o OBJS-$(CONFIG_JACK_INDEV) += jack_audio.o timefilter.o OBJS-$(CONFIG_LAVFI_INDEV) += lavfi.o OBJS-$(CONFIG_OPENAL_INDEV) += openal-dec.o +OBJS-$(CONFIG_OPENGL_OUTDEV) += opengl_enc.o OBJS-$(CONFIG_OSS_INDEV) += oss_audio.o OBJS-$(CONFIG_OSS_OUTDEV) += oss_audio.o OBJS-$(CONFIG_PULSE_INDEV) += pulse_audio_dec.o \ diff --git a/libavdevice/alldevices.c b/libavdevice/alldevices.c index 5178f30861..63bff11b8d 100644 --- a/libavdevice/alldevices.c +++ b/libavdevice/alldevices.c @@ -56,6 +56,7 @@ void avdevice_register_all(void) REGISTER_INDEV (JACK, jack); REGISTER_INDEV (LAVFI, lavfi); REGISTER_INDEV (OPENAL, openal); + REGISTER_OUTDEV (OPENGL, opengl); REGISTER_INOUTDEV(OSS, oss); REGISTER_INOUTDEV(PULSE, pulse); REGISTER_OUTDEV (SDL, sdl); diff --git a/libavdevice/opengl_enc.c b/libavdevice/opengl_enc.c new file mode 100644 index 0000000000..1917d2727f --- /dev/null +++ b/libavdevice/opengl_enc.c @@ -0,0 +1,1221 @@ +/* + * Copyright (c) 2014 Lukasz Marek + * + * This file is part of FFmpeg. + * + * FFmpeg 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. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +//TODO: support for more formats +//TODO: support for more systems. +//TODO: implement X11, Windows, Mac OS native default window. SDL 1.2 doesn't allow to render to custom thread. + +#include +#include +#include +#include +#include + +#include "config.h" + +#if HAVE_OPENGL_GL3_H +#include +#elif HAVE_ES2_GL_H +#include +#else +#include +#include +#endif +#if HAVE_GLXGETPROCADDRESS +#include +#endif +#if HAVE_WINDOWS_H +#include +#endif + +#if HAVE_SDL +#include +#endif + +#include "libavutil/common.h" +#include "libavutil/pixdesc.h" +#include "libavutil/log.h" +#include "libavutil/opt.h" +#include "libavutil/avassert.h" +#include "libavutil/avstring.h" +#include "libavformat/avformat.h" +#include "libavdevice/avdevice.h" +#include "opengl_enc_shaders.h" + +#ifndef APIENTRY +#define APIENTRY +#endif + +/* GL_RED_COMPONENT is used for plannar pixel types. + * Only red component is sampled in shaders. + * On some platforms GL_RED is not availabe and GL_LUMINANCE have to be used, + * but since OpenGL 3.0 GL_LUMINANCE is deprecated. + * GL_RED produces RGBA = value, 0, 0, 1. + * GL_LUMINANCE produces RGBA = value, value, value, 1. + * Note: GL_INTENSITY may also be used which produce RGBA = value, value, value, value. */ +#if defined(GL_RED) +#define GL_RED_COMPONENT GL_RED +#elif defined(GL_LUMINANCE) +#define GL_RED_COMPONENT GL_LUMINANCE +#else +#define GL_RED_COMPONENT 0x1903; //GL_RED +#endif + +/* Constants not defined for iOS */ +#define FF_GL_UNSIGNED_BYTE_3_3_2 0x8032 +#define FF_GL_UNSIGNED_BYTE_2_3_3_REV 0x8362 +#define FF_GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366 + +/* MinGW exposes only OpenGL 1.1 API */ +#define FF_GL_ARRAY_BUFFER 0x8892 +#define FF_GL_ELEMENT_ARRAY_BUFFER 0x8893 +#define FF_GL_STATIC_DRAW 0x88E4 +#define FF_GL_FRAGMENT_SHADER 0x8B30 +#define FF_GL_VERTEX_SHADER 0x8B31 +#define FF_GL_COMPILE_STATUS 0x8B81 +#define FF_GL_LINK_STATUS 0x8B82 +#define FF_GL_INFO_LOG_LENGTH 0x8B84 +typedef void (APIENTRY *FF_PFNGLACTIVETEXTUREPROC) (GLenum texture); +typedef void (APIENTRY *FF_PFNGLGENBUFFERSPROC) (GLsizei n, GLuint *buffers); +typedef void (APIENTRY *FF_PFNGLDELETEBUFFERSPROC) (GLsizei n, const GLuint *buffers); +typedef void (APIENTRY *FF_PFNGLBUFFERDATAPROC) (GLenum target, ptrdiff_t size, const GLvoid *data, GLenum usage); +typedef void (APIENTRY *FF_PFNGLBINDBUFFERPROC) (GLenum target, GLuint buffer); +typedef GLint (APIENTRY *FF_PFNGLGETATTRIBLOCATIONPROC) (GLuint program, const char *name); +typedef void (APIENTRY *FF_PFNGLENABLEVERTEXATTRIBARRAYPROC) (GLuint index); +typedef void (APIENTRY *FF_PFNGLVERTEXATTRIBPOINTERPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, uintptr_t pointer); +typedef GLint (APIENTRY *FF_PFNGLGETUNIFORMLOCATIONPROC) (GLuint program, const char *name); +typedef void (APIENTRY *FF_PFNGLUNIFORM1FPROC) (GLint location, GLfloat v0); +typedef void (APIENTRY *FF_PFNGLUNIFORM1IPROC) (GLint location, GLint v0); +typedef void (APIENTRY *FF_PFNGLUNIFORMMATRIX4FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef GLuint (APIENTRY *FF_PFNGLCREATEPROGRAMPROC) (void); +typedef void (APIENTRY *FF_PFNGLDELETEPROGRAMPROC) (GLuint program); +typedef void (APIENTRY *FF_PFNGLUSEPROGRAMPROC) (GLuint program); +typedef void (APIENTRY *FF_PFNGLLINKPROGRAMPROC) (GLuint program); +typedef void (APIENTRY *FF_PFNGLGETPROGRAMIVPROC) (GLuint program, GLenum pname, GLint *params); +typedef void (APIENTRY *FF_PFNGLGETPROGRAMINFOLOGPROC) (GLuint program, GLsizei bufSize, GLsizei *length, char *infoLog); +typedef void (APIENTRY *FF_PFNGLATTACHSHADERPROC) (GLuint program, GLuint shader); +typedef GLuint (APIENTRY *FF_PFNGLCREATESHADERPROC) (GLenum type); +typedef void (APIENTRY *FF_PFNGLDELETESHADERPROC) (GLuint shader); +typedef void (APIENTRY *FF_PFNGLCOMPILESHADERPROC) (GLuint shader); +typedef void (APIENTRY *FF_PFNGLSHADERSOURCEPROC) (GLuint shader, GLsizei count, const char* *string, const GLint *length); +typedef void (APIENTRY *FF_PFNGLGETSHADERIVPROC) (GLuint shader, GLenum pname, GLint *params); +typedef void (APIENTRY *FF_PFNGLGETSHADERINFOLOGPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, char *infoLog); + +typedef struct FFOpenGLFunctions { + FF_PFNGLACTIVETEXTUREPROC glActiveTexture; //Require GL ARB multitexture + FF_PFNGLGENBUFFERSPROC glGenBuffers; //Require GL_ARB_vertex_buffer_object + FF_PFNGLDELETEBUFFERSPROC glDeleteBuffers; //Require GL_ARB_vertex_buffer_object + FF_PFNGLBUFFERDATAPROC glBufferData; //Require GL_ARB_vertex_buffer_object + FF_PFNGLBINDBUFFERPROC glBindBuffer; //Require GL_ARB_vertex_buffer_object + FF_PFNGLGETATTRIBLOCATIONPROC glGetAttribLocation; //Require GL_ARB_vertex_shader + FF_PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray; //Require GL_ARB_vertex_shader + FF_PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer; //Require GL_ARB_vertex_shader + FF_PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation; //Require GL_ARB_shader_objects + FF_PFNGLUNIFORM1FPROC glUniform1f; //Require GL_ARB_shader_objects + FF_PFNGLUNIFORM1IPROC glUniform1i; //Require GL_ARB_shader_objects + FF_PFNGLUNIFORMMATRIX4FVPROC glUniformMatrix4fv; //Require GL_ARB_shader_objects + FF_PFNGLCREATEPROGRAMPROC glCreateProgram; //Require GL_ARB_shader_objects + FF_PFNGLDELETEPROGRAMPROC glDeleteProgram; //Require GL_ARB_shader_objects + FF_PFNGLUSEPROGRAMPROC glUseProgram; //Require GL_ARB_shader_objects + FF_PFNGLLINKPROGRAMPROC glLinkProgram; //Require GL_ARB_shader_objects + FF_PFNGLGETPROGRAMIVPROC glGetProgramiv; //Require GL_ARB_shader_objects + FF_PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog; //Require GL_ARB_shader_objects + FF_PFNGLATTACHSHADERPROC glAttachShader; //Require GL_ARB_shader_objects + FF_PFNGLCREATESHADERPROC glCreateShader; //Require GL_ARB_shader_objects + FF_PFNGLDELETESHADERPROC glDeleteShader; //Require GL_ARB_shader_objects + FF_PFNGLCOMPILESHADERPROC glCompileShader; //Require GL_ARB_shader_objects + FF_PFNGLSHADERSOURCEPROC glShaderSource; //Require GL_ARB_shader_objects + FF_PFNGLGETSHADERIVPROC glGetShaderiv; //Require GL_ARB_shader_objects + FF_PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog; //Require GL_ARB_shader_objects +} FFOpenGLFunctions; + +#define OPENGL_ERROR_CHECK(ctx) \ +{\ + GLenum err_code; \ + if ((err_code = glGetError()) != GL_NO_ERROR) { \ + av_log(ctx, AV_LOG_ERROR, "OpenGL error occurred in '%s', line %d: %d\n", __FUNCTION__, __LINE__, err_code); \ + goto fail; \ + } \ +}\ + +typedef struct OpenGLVertexInfo +{ + float x, y, z; ///priv_data; + opengl->window_width = width; + opengl->window_height = height; + /* max_viewport_width == 0 means write_header was not called yet. */ + if (opengl->max_viewport_width) { + if (opengl->no_window && + (ret = avdevice_dev_to_app_control_message(h, AV_DEV_TO_APP_PREPARE_WINDOW_BUFFER, NULL , 0)) < 0) { + av_log(opengl, AV_LOG_ERROR, "Application failed to prepare window buffer.\n"); + goto end; + } + if ((ret = opengl_prepare_vertex(h)) < 0) + goto end; + ret = opengl_draw(h, NULL, 1); + } + end: + return ret; +} + +static int opengl_control_message(AVFormatContext *h, int type, void *data, size_t data_size) +{ + OpenGLContext *opengl = h->priv_data; + switch(type) { + case AV_APP_TO_DEV_WINDOW_SIZE: + if (data) { + AVDeviceRect *message = data; + return opengl_resize(h, message->width, message->height); + } + return AVERROR(EINVAL); + case AV_APP_TO_DEV_WINDOW_REPAINT: + return opengl_resize(h, opengl->window_width, opengl->window_height); + } + return AVERROR(ENOSYS); +} + +#if HAVE_SDL +static int opengl_sdl_recreate_window(OpenGLContext *opengl, int width, int height) +{ + opengl->surface = SDL_SetVideoMode(width, height, + 32, SDL_OPENGL | SDL_RESIZABLE); + if (!opengl->surface) { + av_log(opengl, AV_LOG_ERROR, "Unable to set video mode: %s\n", SDL_GetError()); + return AVERROR_EXTERNAL; + } + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + return 0; +} + +static int opengl_sdl_process_events(AVFormatContext *h) +{ + int ret; + OpenGLContext *opengl = h->priv_data; + SDL_Event event; + SDL_PumpEvents(); + while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_ALLEVENTS) > 0) { + switch (event.type) { + case SDL_QUIT: + return AVERROR(EIO); + case SDL_KEYDOWN: + switch (event.key.keysym.sym) { + case SDLK_ESCAPE: + case SDLK_q: + return AVERROR(EIO); + } + return 0; + case SDL_VIDEORESIZE: { + char buffer[100]; + int reinit; + AVDeviceRect message; + /* clean up old context because SDL_SetVideoMode may lose its state. */ + SDL_VideoDriverName(buffer, sizeof(buffer)); + reinit = !av_strncasecmp(buffer, "quartz", sizeof(buffer)); + if (reinit) { + glDeleteTextures(4, opengl->texture_name); + opengl->glprocs.glDeleteBuffers(2, &opengl->index_buffer); + } + if ((ret = opengl_sdl_recreate_window(opengl, event.resize.w, event.resize.h)) < 0) + return ret; + if (reinit && (ret = opengl_init_context(opengl)) < 0) + return ret; + message.width = opengl->surface->w; + message.height = opengl->surface->h; + return opengl_control_message(h, AV_APP_TO_DEV_WINDOW_SIZE, &message, sizeof(AVDeviceRect)); + } + } + } + return 0; +} + +static int av_cold opengl_sdl_create_window(AVFormatContext *h) +{ + int ret; + char buffer[100]; + OpenGLContext *opengl = h->priv_data; + AVDeviceRect message; + if (SDL_Init(SDL_INIT_VIDEO)) { + av_log(opengl, AV_LOG_ERROR, "Unable to initialize SDL: %s\n", SDL_GetError()); + return AVERROR_EXTERNAL; + } + if ((ret = opengl_sdl_recreate_window(opengl, opengl->width, opengl->height)) < 0) + return ret; + av_log(opengl, AV_LOG_INFO, "SDL driver: '%s'.\n", SDL_VideoDriverName(buffer, sizeof(buffer))); + message.width = opengl->surface->w; + message.height = opengl->surface->h; + opengl_control_message(h, AV_APP_TO_DEV_WINDOW_SIZE, &message, sizeof(AVDeviceRect)); + return 0; +} + +static int av_cold opengl_sdl_load_procedures(OpenGLContext *opengl) +{ + FFOpenGLFunctions *procs = &opengl->glprocs; + +#define LOAD_OPENGL_FUN(name, type) \ + procs->name = (type)SDL_GL_GetProcAddress(#name); \ + if (!procs->name) { \ + av_log(opengl, AV_LOG_ERROR, "Cannot load OpenGL function: '%s'\n", #name); \ + return AVERROR(ENOSYS); \ + } + + LOAD_OPENGL_FUN(glActiveTexture, FF_PFNGLACTIVETEXTUREPROC) + LOAD_OPENGL_FUN(glGenBuffers, FF_PFNGLGENBUFFERSPROC) + LOAD_OPENGL_FUN(glDeleteBuffers, FF_PFNGLDELETEBUFFERSPROC) + LOAD_OPENGL_FUN(glBufferData, FF_PFNGLBUFFERDATAPROC) + LOAD_OPENGL_FUN(glBindBuffer, FF_PFNGLBINDBUFFERPROC) + LOAD_OPENGL_FUN(glGetAttribLocation, FF_PFNGLGETATTRIBLOCATIONPROC) + LOAD_OPENGL_FUN(glGetUniformLocation, FF_PFNGLGETUNIFORMLOCATIONPROC) + LOAD_OPENGL_FUN(glUniform1f, FF_PFNGLUNIFORM1FPROC) + LOAD_OPENGL_FUN(glUniform1i, FF_PFNGLUNIFORM1IPROC) + LOAD_OPENGL_FUN(glUniformMatrix4fv, FF_PFNGLUNIFORMMATRIX4FVPROC) + LOAD_OPENGL_FUN(glCreateProgram, FF_PFNGLCREATEPROGRAMPROC) + LOAD_OPENGL_FUN(glDeleteProgram, FF_PFNGLDELETEPROGRAMPROC) + LOAD_OPENGL_FUN(glUseProgram, FF_PFNGLUSEPROGRAMPROC) + LOAD_OPENGL_FUN(glLinkProgram, FF_PFNGLLINKPROGRAMPROC) + LOAD_OPENGL_FUN(glGetProgramiv, FF_PFNGLGETPROGRAMIVPROC) + LOAD_OPENGL_FUN(glGetProgramInfoLog, FF_PFNGLGETPROGRAMINFOLOGPROC) + LOAD_OPENGL_FUN(glAttachShader, FF_PFNGLATTACHSHADERPROC) + LOAD_OPENGL_FUN(glCreateShader, FF_PFNGLCREATESHADERPROC) + LOAD_OPENGL_FUN(glDeleteShader, FF_PFNGLDELETESHADERPROC) + LOAD_OPENGL_FUN(glCompileShader, FF_PFNGLCOMPILESHADERPROC) + LOAD_OPENGL_FUN(glShaderSource, FF_PFNGLSHADERSOURCEPROC) + LOAD_OPENGL_FUN(glGetShaderiv, FF_PFNGLGETSHADERIVPROC) + LOAD_OPENGL_FUN(glGetShaderInfoLog, FF_PFNGLGETSHADERINFOLOGPROC) + LOAD_OPENGL_FUN(glEnableVertexAttribArray, FF_PFNGLENABLEVERTEXATTRIBARRAYPROC) + LOAD_OPENGL_FUN(glVertexAttribPointer, FF_PFNGLVERTEXATTRIBPOINTERPROC) + + return 0; + +#undef LOAD_OPENGL_FUN +} +#endif /* HAVE_SDL */ + +#if defined(__APPLE__) +static int av_cold opengl_load_procedures(OpenGLContext *opengl) +{ + FFOpenGLFunctions *procs = &opengl->glprocs; + + procs->glActiveTexture = glActiveTexture; + procs->glGenBuffers = glGenBuffers; + procs->glDeleteBuffers = glDeleteBuffers; + procs->glBufferData = glBufferData; + procs->glBindBuffer = glBindBuffer; + procs->glGetAttribLocation = glGetAttribLocation; + procs->glGetUniformLocation = glGetUniformLocation; + procs->glUniform1f = glUniform1f; + procs->glUniform1i = glUniform1i; + procs->glUniformMatrix4fv = glUniformMatrix4fv; + procs->glCreateProgram = glCreateProgram; + procs->glDeleteProgram = glDeleteProgram; + procs->glUseProgram = glUseProgram; + procs->glLinkProgram = glLinkProgram; + procs->glGetProgramiv = glGetProgramiv; + procs->glGetProgramInfoLog = glGetProgramInfoLog; + procs->glAttachShader = glAttachShader; + procs->glCreateShader = glCreateShader; + procs->glDeleteShader = glDeleteShader; + procs->glCompileShader = glCompileShader; + procs->glShaderSource = glShaderSource; + procs->glGetShaderiv = glGetShaderiv; + procs->glGetShaderInfoLog = glGetShaderInfoLog; + procs->glEnableVertexAttribArray = glEnableVertexAttribArray; + procs->glVertexAttribPointer = (FF_PFNGLVERTEXATTRIBPOINTERPROC) glVertexAttribPointer; + return 0; +} +#else +static int av_cold opengl_load_procedures(OpenGLContext *opengl) +{ + FFOpenGLFunctions *procs = &opengl->glprocs; + +#if HAVE_GLXGETPROCADDRESS +#define SelectedGetProcAddress glXGetProcAddress +#elif HAVE_WGLGETPROCADDRESS +#define SelectedGetProcAddress wglGetProcAddress +#endif + +#define LOAD_OPENGL_FUN(name, type) \ + procs->name = (type)SelectedGetProcAddress(#name); \ + if (!procs->name) { \ + av_log(opengl, AV_LOG_ERROR, "Cannot load OpenGL function: '%s'\n", #name); \ + return AVERROR(ENOSYS); \ + } + + LOAD_OPENGL_FUN(glActiveTexture, FF_PFNGLACTIVETEXTUREPROC) + LOAD_OPENGL_FUN(glGenBuffers, FF_PFNGLGENBUFFERSPROC) + LOAD_OPENGL_FUN(glDeleteBuffers, FF_PFNGLDELETEBUFFERSPROC) + LOAD_OPENGL_FUN(glBufferData, FF_PFNGLBUFFERDATAPROC) + LOAD_OPENGL_FUN(glBindBuffer, FF_PFNGLBINDBUFFERPROC) + LOAD_OPENGL_FUN(glGetAttribLocation, FF_PFNGLGETATTRIBLOCATIONPROC) + LOAD_OPENGL_FUN(glGetUniformLocation, FF_PFNGLGETUNIFORMLOCATIONPROC) + LOAD_OPENGL_FUN(glUniform1f, FF_PFNGLUNIFORM1FPROC) + LOAD_OPENGL_FUN(glUniform1i, FF_PFNGLUNIFORM1IPROC) + LOAD_OPENGL_FUN(glUniformMatrix4fv, FF_PFNGLUNIFORMMATRIX4FVPROC) + LOAD_OPENGL_FUN(glCreateProgram, FF_PFNGLCREATEPROGRAMPROC) + LOAD_OPENGL_FUN(glDeleteProgram, FF_PFNGLDELETEPROGRAMPROC) + LOAD_OPENGL_FUN(glUseProgram, FF_PFNGLUSEPROGRAMPROC) + LOAD_OPENGL_FUN(glLinkProgram, FF_PFNGLLINKPROGRAMPROC) + LOAD_OPENGL_FUN(glGetProgramiv, FF_PFNGLGETPROGRAMIVPROC) + LOAD_OPENGL_FUN(glGetProgramInfoLog, FF_PFNGLGETPROGRAMINFOLOGPROC) + LOAD_OPENGL_FUN(glAttachShader, FF_PFNGLATTACHSHADERPROC) + LOAD_OPENGL_FUN(glCreateShader, FF_PFNGLCREATESHADERPROC) + LOAD_OPENGL_FUN(glDeleteShader, FF_PFNGLDELETESHADERPROC) + LOAD_OPENGL_FUN(glCompileShader, FF_PFNGLCOMPILESHADERPROC) + LOAD_OPENGL_FUN(glShaderSource, FF_PFNGLSHADERSOURCEPROC) + LOAD_OPENGL_FUN(glGetShaderiv, FF_PFNGLGETSHADERIVPROC) + LOAD_OPENGL_FUN(glGetShaderInfoLog, FF_PFNGLGETSHADERINFOLOGPROC) + LOAD_OPENGL_FUN(glEnableVertexAttribArray, FF_PFNGLENABLEVERTEXATTRIBARRAYPROC) + LOAD_OPENGL_FUN(glVertexAttribPointer, FF_PFNGLVERTEXATTRIBPOINTERPROC) + + return 0; + +#undef SelectedGetProcAddress +#undef LOAD_OPENGL_FUN +} +#endif + +static av_always_inline void opengl_make_identity(float matrix[16]) +{ + memset(matrix, 0, 16 * sizeof(float)); + matrix[0] = matrix[5] = matrix[10] = matrix[15] = 1.0f; +} + +static av_always_inline void opengl_make_ortho(float matrix[16], + float left, float right, + float bottom, float top, + float nearZ, float farZ) +{ + float ral = right + left; + float rsl = right - left; + float tab = top + bottom; + float tsb = top - bottom; + float fan = farZ + nearZ; + float fsn = farZ - nearZ; + + memset(matrix, 0, 16 * sizeof(float)); + matrix[0] = 2.0f / rsl; + matrix[5] = 2.0f / tsb; + matrix[10] = -2.0f / fsn; + matrix[12] = -ral / rsl; + matrix[13] = -tab / tsb; + matrix[14] = -fan / fsn; + matrix[15] = 1.0f; +} + +static av_cold int opengl_read_limits(OpenGLContext *opengl) +{ + static const struct{ + const char *extention; + int major; + int minor; + } required_extensions[] = { + { "GL_ARB_multitexture", 1, 3 }, + { "GL_ARB_vertex_buffer_object", 1, 5 }, //GLX_ARB_vertex_buffer_object + { "GL_ARB_vertex_shader", 2, 0 }, + { "GL_ARB_fragment_shader", 2, 0 }, + { "GL_ARB_shader_objects", 2, 0 }, + { NULL, 0, 0 } + }; + int i, major, minor; + const char *extensions, *version; + + version = glGetString(GL_VERSION); + extensions = glGetString(GL_EXTENSIONS); + + av_log(opengl, AV_LOG_DEBUG, "OpenGL version: %s\n", version); + sscanf(version, "%d.%d", &major, &minor); + + for (i = 0; required_extensions[i].extention; i++) { + if (major < required_extensions[i].major && + (major == required_extensions[i].major && minor < required_extensions[i].minor) && + !strstr(extensions, required_extensions[i].extention)) { + av_log(opengl, AV_LOG_ERROR, "Required extension %s is not supported.\n", + required_extensions[i].extention); + av_log(opengl, AV_LOG_DEBUG, "Supported extensions are: %s\n", extensions); + return AVERROR(ENOSYS); + } + } + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &opengl->max_texture_size); + glGetIntegerv(GL_MAX_VIEWPORT_DIMS, &opengl->max_viewport_width); + opengl->non_pow_2_textures = major >= 2 || strstr(extensions, "GL_ARB_texture_non_power_of_two"); + + av_log(opengl, AV_LOG_DEBUG, "Non Power of 2 textures support: %s\n", opengl->non_pow_2_textures ? "Yes" : "No"); + av_log(opengl, AV_LOG_DEBUG, "Max texture size: %dx%d\n", opengl->max_texture_size, opengl->max_texture_size); + av_log(opengl, AV_LOG_DEBUG, "Max viewport size: %dx%d\n", + opengl->max_viewport_width, opengl->max_viewport_height); + + OPENGL_ERROR_CHECK(opengl); + return 0; + fail: + return AVERROR_EXTERNAL; +} + +static av_always_inline const char * opengl_get_fragment_shader_code(enum AVPixelFormat format) +{ + switch (format) { + case AV_PIX_FMT_YUV420P: case AV_PIX_FMT_YUV444P: + case AV_PIX_FMT_YUV422P: case AV_PIX_FMT_YUV410P: + case AV_PIX_FMT_YUV411P: case AV_PIX_FMT_YUV440P: + case AV_PIX_FMT_YUV420P16: case AV_PIX_FMT_YUV422P16: + case AV_PIX_FMT_YUV444P16: + return FF_OPENGL_FRAGMENT_SHADER_YUV_PLANAR; + case AV_PIX_FMT_YUVA420P: case AV_PIX_FMT_YUVA444P: + case AV_PIX_FMT_YUVA422P: + case AV_PIX_FMT_YUVA420P16: case AV_PIX_FMT_YUVA422P16: + case AV_PIX_FMT_YUVA444P16: + return FF_OPENGL_FRAGMENT_SHADER_YUVA_PLANAR; + case AV_PIX_FMT_RGB24: case AV_PIX_FMT_BGR24: + case AV_PIX_FMT_0RGB: case AV_PIX_FMT_RGB0: + case AV_PIX_FMT_0BGR: case AV_PIX_FMT_BGR0: + case AV_PIX_FMT_RGB565: case AV_PIX_FMT_BGR565: + case AV_PIX_FMT_RGB555: case AV_PIX_FMT_BGR555: + case AV_PIX_FMT_RGB8: case AV_PIX_FMT_BGR8: + case AV_PIX_FMT_RGB48: + return FF_OPENGL_FRAGMENT_SHADER_RGB_PACKET; + case AV_PIX_FMT_ARGB: case AV_PIX_FMT_RGBA: + case AV_PIX_FMT_ABGR: case AV_PIX_FMT_BGRA: + case AV_PIX_FMT_RGBA64: case AV_PIX_FMT_BGRA64: + return FF_OPENGL_FRAGMENT_SHADER_RGBA_PACKET; + case AV_PIX_FMT_GBRP: case AV_PIX_FMT_GBRP16: + return FF_OPENGL_FRAGMENT_SHADER_RGB_PLANAR; + case AV_PIX_FMT_GBRAP: case AV_PIX_FMT_GBRAP16: + return FF_OPENGL_FRAGMENT_SHADER_RGBA_PLANAR; + default: + break; + } + return NULL; +} + +static av_always_inline int opengl_type_size(GLenum type) +{ + switch(type) { + case GL_UNSIGNED_SHORT: + case FF_GL_UNSIGNED_SHORT_1_5_5_5_REV: + case GL_UNSIGNED_SHORT_5_6_5: + return 2; + case GL_UNSIGNED_BYTE: + case FF_GL_UNSIGNED_BYTE_3_3_2: + case FF_GL_UNSIGNED_BYTE_2_3_3_REV: + default: + break; + } + return 1; +} + +static av_cold void opengl_get_texture_params(OpenGLContext *opengl) +{ + switch(opengl->pix_fmt) { + case AV_PIX_FMT_YUV420P: case AV_PIX_FMT_YUV444P: + case AV_PIX_FMT_YUV422P: case AV_PIX_FMT_YUV410P: + case AV_PIX_FMT_YUV411P: case AV_PIX_FMT_YUV440P: + case AV_PIX_FMT_YUVA420P: case AV_PIX_FMT_YUVA444P: + case AV_PIX_FMT_YUVA422P: + case AV_PIX_FMT_GBRP: case AV_PIX_FMT_GBRAP: + opengl->format = GL_RED_COMPONENT; + opengl->type = GL_UNSIGNED_BYTE; + break; + case AV_PIX_FMT_YUV420P16: case AV_PIX_FMT_YUV422P16: + case AV_PIX_FMT_YUV444P16: + case AV_PIX_FMT_YUVA420P16: case AV_PIX_FMT_YUVA422P16: + case AV_PIX_FMT_YUVA444P16: + case AV_PIX_FMT_GBRP16: case AV_PIX_FMT_GBRAP16: + opengl->format = GL_RED_COMPONENT; + opengl->type = GL_UNSIGNED_SHORT; + break; + case AV_PIX_FMT_RGB24: case AV_PIX_FMT_BGR24: + opengl->format = GL_RGB; + opengl->type = GL_UNSIGNED_BYTE; + break; + case AV_PIX_FMT_ARGB: case AV_PIX_FMT_RGBA: + case AV_PIX_FMT_ABGR: case AV_PIX_FMT_BGRA: + case AV_PIX_FMT_0RGB: case AV_PIX_FMT_RGB0: + case AV_PIX_FMT_0BGR: case AV_PIX_FMT_BGR0: + opengl->format = GL_RGBA; + opengl->type = GL_UNSIGNED_BYTE; + break; + case AV_PIX_FMT_RGB8: + opengl->format = GL_RGB; + opengl->type = FF_GL_UNSIGNED_BYTE_3_3_2; + break; + case AV_PIX_FMT_BGR8: + opengl->format = GL_RGB; + opengl->type = FF_GL_UNSIGNED_BYTE_2_3_3_REV; + break; + case AV_PIX_FMT_RGB555: case AV_PIX_FMT_BGR555: + opengl->format = GL_RGBA; + opengl->type = FF_GL_UNSIGNED_SHORT_1_5_5_5_REV; + break; + case AV_PIX_FMT_RGB565: case AV_PIX_FMT_BGR565: + opengl->format = GL_RGB; + opengl->type = GL_UNSIGNED_SHORT_5_6_5; + break; + case AV_PIX_FMT_RGB48: + opengl->format = GL_RGB; + opengl->type = GL_UNSIGNED_SHORT; + break; + case AV_PIX_FMT_RGBA64: case AV_PIX_FMT_BGRA64: + opengl->format = GL_RGBA; + opengl->type = GL_UNSIGNED_SHORT; + break; + } +} + +static void opengl_compute_display_area(AVFormatContext *s) +{ + AVRational sar, dar; /* sample and display aspect ratios */ + OpenGLContext *opengl = s->priv_data; + AVStream *st = s->streams[0]; + AVCodecContext *encctx = st->codec; + + /* compute overlay width and height from the codec context information */ + sar = st->sample_aspect_ratio.num ? st->sample_aspect_ratio : (AVRational){ 1, 1 }; + dar = av_mul_q(sar, (AVRational){ encctx->width, encctx->height }); + + /* we suppose the screen has a 1/1 sample aspect ratio */ + /* fit in the window */ + if (av_cmp_q(dar, (AVRational){ opengl->window_width, opengl->window_height }) > 0) { + /* fit in width */ + opengl->picture_width = opengl->window_width; + opengl->picture_height = av_rescale(opengl->picture_width, dar.den, dar.num); + } else { + /* fit in height */ + opengl->picture_height = opengl->window_height; + opengl->picture_width = av_rescale(opengl->picture_height, dar.num, dar.den); + } +} + +static av_cold void opengl_get_texture_size(OpenGLContext *opengl, int in_width, int in_height, + int *out_width, int *out_height) +{ + if (opengl->non_pow_2_textures) { + *out_width = in_width; + *out_height = in_height; + } else { + int max = FFMIN(FFMAX(in_width, in_height), opengl->max_texture_size); + unsigned power_of_2 = 1; + while (power_of_2 < max) + power_of_2 *= 2; + *out_height = power_of_2; + *out_width = power_of_2; + av_log(opengl, AV_LOG_DEBUG, "Texture size calculated from %dx%d into %dx%d\n", + in_width, in_height, *out_width, *out_height); + } +} + +static av_cold void opengl_fill_color_map(OpenGLContext *opengl) +{ + const AVPixFmtDescriptor *desc; + int shift; + enum AVPixelFormat pix_fmt = opengl->pix_fmt; + + /* We need order of components, not exact position, some minor HACKs here */ + if (pix_fmt == AV_PIX_FMT_RGB565 || pix_fmt == AV_PIX_FMT_BGR555 || + pix_fmt == AV_PIX_FMT_BGR8 || pix_fmt == AV_PIX_FMT_RGB8) + pix_fmt = AV_PIX_FMT_RGB24; + else if (pix_fmt == AV_PIX_FMT_BGR565 || pix_fmt == AV_PIX_FMT_RGB555) + pix_fmt = AV_PIX_FMT_BGR24; + + desc = av_pix_fmt_desc_get(pix_fmt); + if (!(desc->flags & AV_PIX_FMT_FLAG_RGB)) + return; + +#define FILL_COMPONENT(i) { \ + shift = desc->comp[i].depth_minus1 >> 3; \ + opengl->color_map[(i << 2) + ((desc->comp[i].offset_plus1 - 1) >> shift)] = 1.0; \ + } + + memset(opengl->color_map, 0, sizeof(opengl->color_map)); + FILL_COMPONENT(0); + FILL_COMPONENT(1); + FILL_COMPONENT(2); + if (desc->flags & AV_PIX_FMT_FLAG_ALPHA) + FILL_COMPONENT(3); + +#undef FILL_COMPONENT +} + +static av_cold GLuint opengl_load_shader(OpenGLContext *opengl, GLenum type, const char *source) +{ + GLuint shader = opengl->glprocs.glCreateShader(type); + GLint result; + if (!shader) { + av_log(opengl, AV_LOG_ERROR, "glCreateShader() failed\n"); + return 0; + } + opengl->glprocs.glShaderSource(shader, 1, &source, NULL); + opengl->glprocs.glCompileShader(shader); + + opengl->glprocs.glGetShaderiv(shader, FF_GL_COMPILE_STATUS, &result); + if (!result) { + char *log; + opengl->glprocs.glGetShaderiv(shader, FF_GL_INFO_LOG_LENGTH, &result); + if (result) { + if ((log = av_malloc(result))) { + opengl->glprocs.glGetShaderInfoLog(shader, result, NULL, log); + av_log(opengl, AV_LOG_ERROR, "Compile error: %s\n", log); + av_free(log); + } + } + goto fail; + } + OPENGL_ERROR_CHECK(opengl); + return shader; + fail: + opengl->glprocs.glDeleteShader(shader); + return 0; +} + +static av_cold int opengl_compile_shaders(OpenGLContext *opengl, enum AVPixelFormat pix_fmt) +{ + GLuint vertex_shader = 0, fragment_shader = 0; + GLint result; + const char *fragment_shader_code = opengl_get_fragment_shader_code(pix_fmt); + + if (!fragment_shader_code) { + av_log(opengl, AV_LOG_ERROR, "Provided pixel format '%s' is not supported\n", + av_get_pix_fmt_name(pix_fmt)); + return AVERROR(EINVAL); + } + + vertex_shader = opengl_load_shader(opengl, FF_GL_VERTEX_SHADER, + FF_OPENGL_VERTEX_SHADER); + if (!vertex_shader) { + av_log(opengl, AV_LOG_ERROR, "Vertex shader loading failed.\n"); + goto fail; + } + fragment_shader = opengl_load_shader(opengl, FF_GL_FRAGMENT_SHADER, + fragment_shader_code); + if (!fragment_shader) { + av_log(opengl, AV_LOG_ERROR, "Fragment shader loading failed.\n"); + goto fail; + } + + opengl->program = opengl->glprocs.glCreateProgram(); + if (!opengl->program) + goto fail; + + opengl->glprocs.glAttachShader(opengl->program, vertex_shader); + opengl->glprocs.glAttachShader(opengl->program, fragment_shader); + opengl->glprocs.glLinkProgram(opengl->program); + + opengl->glprocs.glGetProgramiv(opengl->program, FF_GL_LINK_STATUS, &result); + if (!result) { + char *log; + opengl->glprocs.glGetProgramiv(opengl->program, FF_GL_INFO_LOG_LENGTH, &result); + if (result) { + log = av_malloc(result); + if (!log) + goto fail; + opengl->glprocs.glGetProgramInfoLog(opengl->program, result, NULL, log); + av_log(opengl, AV_LOG_ERROR, "Link error: %s\n", log); + av_free(log); + } + goto fail; + } + + opengl->position_attrib = opengl->glprocs.glGetAttribLocation(opengl->program, "a_position"); + opengl->texture_coords_attrib = opengl->glprocs.glGetAttribLocation(opengl->program, "a_textureCoords"); + opengl->projection_matrix_location = opengl->glprocs.glGetUniformLocation(opengl->program, "u_projectionMatrix"); + opengl->model_view_matrix_location = opengl->glprocs.glGetUniformLocation(opengl->program, "u_modelViewMatrix"); + opengl->color_map_location = opengl->glprocs.glGetUniformLocation(opengl->program, "u_colorMap"); + opengl->texture_location[0] = opengl->glprocs.glGetUniformLocation(opengl->program, "u_texture0"); + opengl->texture_location[1] = opengl->glprocs.glGetUniformLocation(opengl->program, "u_texture1"); + opengl->texture_location[2] = opengl->glprocs.glGetUniformLocation(opengl->program, "u_texture2"); + opengl->texture_location[3] = opengl->glprocs.glGetUniformLocation(opengl->program, "u_texture3"); + opengl->chroma_div_w_location = opengl->glprocs.glGetUniformLocation(opengl->program, "u_chroma_div_w"); + opengl->chroma_div_h_location = opengl->glprocs.glGetUniformLocation(opengl->program, "u_chroma_div_h"); + + OPENGL_ERROR_CHECK(opengl); + return 0; + fail: + opengl->glprocs.glDeleteShader(vertex_shader); + opengl->glprocs.glDeleteShader(fragment_shader); + opengl->glprocs.glDeleteProgram(opengl->program); + opengl->program = 0; + return AVERROR_EXTERNAL; +} + +static av_cold int opengl_configure_texture(OpenGLContext *opengl, GLuint texture, + GLsizei width, GLsizei height) +{ + if (texture) { + int new_width, new_height; + opengl_get_texture_size(opengl, width, height, &new_width, &new_height); + glBindTexture(GL_TEXTURE_2D, texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, opengl->format, new_width, new_height, 0, + opengl->format, opengl->type, NULL); + OPENGL_ERROR_CHECK(NULL); + } + return 0; + fail: + return AVERROR_EXTERNAL; +} + +static av_cold int opengl_prepare_vertex(AVFormatContext *s) +{ + OpenGLContext *opengl = s->priv_data; + int tex_w, tex_h; + + if (opengl->window_width > opengl->max_viewport_width || opengl->window_height > opengl->max_viewport_height) { + opengl->window_width = FFMAX(opengl->window_width, opengl->max_viewport_width); + opengl->window_height = FFMAX(opengl->window_height, opengl->max_viewport_height); + av_log(opengl, AV_LOG_WARNING, "Too big viewport requested, limited to %dx%d", opengl->window_width, opengl->window_height); + } + glViewport(0, 0, opengl->window_width, opengl->window_height); + opengl_make_ortho(opengl->projection_matrix, + - (float)opengl->window_width / 2.0f, (float)opengl->window_width / 2.0f, + - (float)opengl->window_height / 2.0f, (float)opengl->window_height / 2.0f, + 1.0f, -1.0f); + opengl_make_identity(opengl->model_view_matrix); + + opengl_compute_display_area(s); + + opengl->vertex[0].z = opengl->vertex[1].z = opengl->vertex[2].z = opengl->vertex[3].z = 0.0f; + opengl->vertex[0].x = opengl->vertex[1].x = - (float)opengl->picture_width / 2.0f; + opengl->vertex[2].x = opengl->vertex[3].x = (float)opengl->picture_width / 2.0f; + opengl->vertex[1].y = opengl->vertex[2].y = - (float)opengl->picture_height / 2.0f; + opengl->vertex[0].y = opengl->vertex[3].y = (float)opengl->picture_height / 2.0f; + + opengl_get_texture_size(opengl, opengl->width, opengl->height, &tex_w, &tex_h); + + opengl->vertex[0].s0 = 0.0f; + opengl->vertex[0].t0 = 0.0f; + opengl->vertex[1].s0 = 0.0f; + opengl->vertex[1].t0 = (float)opengl->height / (float)tex_h; + opengl->vertex[2].s0 = (float)opengl->width / (float)tex_w; + opengl->vertex[2].t0 = (float)opengl->height / (float)tex_h; + opengl->vertex[3].s0 = (float)opengl->width / (float)tex_w; + opengl->vertex[3].t0 = 0.0f; + + opengl->glprocs.glBindBuffer(FF_GL_ARRAY_BUFFER, opengl->vertex_buffer); + opengl->glprocs.glBufferData(FF_GL_ARRAY_BUFFER, sizeof(opengl->vertex), opengl->vertex, FF_GL_STATIC_DRAW); + opengl->glprocs.glBindBuffer(FF_GL_ARRAY_BUFFER, 0); + OPENGL_ERROR_CHECK(opengl); + return 0; + fail: + return AVERROR_EXTERNAL; +} + +static int opengl_prepare(OpenGLContext *opengl) +{ + int i; + opengl->glprocs.glUseProgram(opengl->program); + opengl->glprocs.glUniformMatrix4fv(opengl->projection_matrix_location, 1, GL_FALSE, opengl->projection_matrix); + opengl->glprocs.glUniformMatrix4fv(opengl->model_view_matrix_location, 1, GL_FALSE, opengl->model_view_matrix); + for (i = 0; i < 4; i++) + if (opengl->texture_location[i] != -1) { + opengl->glprocs.glActiveTexture(GL_TEXTURE0 + i); + glBindTexture(GL_TEXTURE_2D, opengl->texture_name[i]); + opengl->glprocs.glUniform1i(opengl->texture_location[i], i); + } + if (opengl->color_map_location != -1) + opengl->glprocs.glUniformMatrix4fv(opengl->color_map_location, 1, GL_FALSE, opengl->color_map); + if (opengl->chroma_div_h_location != -1) + opengl->glprocs.glUniform1f(opengl->chroma_div_h_location, opengl->chroma_div_h); + if (opengl->chroma_div_w_location != -1) + opengl->glprocs.glUniform1f(opengl->chroma_div_w_location, opengl->chroma_div_w); + + OPENGL_ERROR_CHECK(opengl); + return 0; + fail: + return AVERROR_EXTERNAL; +} + +static av_cold int opengl_write_trailer(AVFormatContext *h) +{ + OpenGLContext *opengl = h->priv_data; + + if (opengl->no_window && + avdevice_dev_to_app_control_message(h, AV_DEV_TO_APP_PREPARE_WINDOW_BUFFER, NULL , 0) < 0) + av_log(opengl, AV_LOG_ERROR, "Application failed to prepare window buffer.\n"); + + glDeleteTextures(4, opengl->texture_name); + if (opengl && opengl->glprocs.glDeleteBuffers) + opengl->glprocs.glDeleteBuffers(2, &opengl->index_buffer); + +#if HAVE_SDL + if (!opengl->no_window) + SDL_Quit(); +#endif + if (opengl->no_window && + avdevice_dev_to_app_control_message(h, AV_DEV_TO_APP_DESTROY_WINDOW_BUFFER, NULL , 0) < 0) + av_log(opengl, AV_LOG_ERROR, "Application failed to release window buffer.\n"); + + return 0; +} + +static av_cold int opengl_init_context(OpenGLContext *opengl) +{ + int i, ret; + const AVPixFmtDescriptor *desc; + + if ((ret = opengl_compile_shaders(opengl, opengl->pix_fmt)) < 0) + goto fail; + + desc = av_pix_fmt_desc_get(opengl->pix_fmt); + av_assert0(desc->nb_components > 0 && desc->nb_components <= 4); + glGenTextures(desc->nb_components, opengl->texture_name); + + opengl->glprocs.glGenBuffers(2, &opengl->index_buffer); + if (!opengl->index_buffer || !opengl->vertex_buffer) { + av_log(opengl, AV_LOG_ERROR, "Buffer generation failed.\n"); + ret = AVERROR_EXTERNAL; + goto fail; + } + + opengl_configure_texture(opengl, opengl->texture_name[0], opengl->width, opengl->height); + if (desc->nb_components > 1) { + int has_alpha = desc->flags & AV_PIX_FMT_FLAG_ALPHA; + int num_planes = desc->nb_components - (has_alpha ? 1 : 0); + if (opengl->non_pow_2_textures) { + opengl->chroma_div_w = 1.0f; + opengl->chroma_div_h = 1.0f; + } else { + opengl->chroma_div_w = 1 << desc->log2_chroma_w; + opengl->chroma_div_h = 1 << desc->log2_chroma_h; + } + for (i = 1; i < num_planes; i++) + if (opengl->non_pow_2_textures) + opengl_configure_texture(opengl, opengl->texture_name[i], + FF_CEIL_RSHIFT(opengl->width, desc->log2_chroma_w), + FF_CEIL_RSHIFT(opengl->height, desc->log2_chroma_h)); + else + opengl_configure_texture(opengl, opengl->texture_name[i], opengl->width, opengl->height); + if (has_alpha) + opengl_configure_texture(opengl, opengl->texture_name[3], opengl->width, opengl->height); + } + + opengl->glprocs.glBindBuffer(FF_GL_ELEMENT_ARRAY_BUFFER, opengl->index_buffer); + opengl->glprocs.glBufferData(FF_GL_ELEMENT_ARRAY_BUFFER, sizeof(g_index), g_index, FF_GL_STATIC_DRAW); + opengl->glprocs.glBindBuffer(FF_GL_ELEMENT_ARRAY_BUFFER, 0); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glClearColor((float)opengl->background[0] / 255.0f, (float)opengl->background[1] / 255.0f, + (float)opengl->background[2] / 255.0f, 1.0f); + + ret = AVERROR_EXTERNAL; + OPENGL_ERROR_CHECK(opengl); + + return 0; + fail: + return ret; +} + +static av_cold int opengl_write_header(AVFormatContext *h) +{ + OpenGLContext *opengl = h->priv_data; + AVStream *st; + int ret; + + if (h->nb_streams != 1 || + h->streams[0]->codec->codec_type != AVMEDIA_TYPE_VIDEO || + h->streams[0]->codec->codec_id != AV_CODEC_ID_RAWVIDEO) { + av_log(opengl, AV_LOG_ERROR, "Only a single video stream is supported.\n"); + return AVERROR(EINVAL); + } + st = h->streams[0]; + opengl->width = st->codec->width; + opengl->height = st->codec->height; + opengl->pix_fmt = st->codec->pix_fmt; + + if (!opengl->window_title && !opengl->no_window) + opengl->window_title = av_strdup(h->filename); + + if (!opengl->no_window) { +#if HAVE_SDL + if ((ret = opengl_sdl_create_window(h)) < 0) + goto fail; +#else + av_log(opengl, AV_LOG_ERROR, "FFmpeg is compiled without SDL. Cannot create default window.\n"); + ret = AVERROR(ENOSYS); + goto fail; +#endif + } else { + if ((ret = avdevice_dev_to_app_control_message(h, AV_DEV_TO_APP_CREATE_WINDOW_BUFFER, NULL , 0)) < 0) { + av_log(opengl, AV_LOG_ERROR, "Application failed to create window buffer.\n"); + goto fail; + } + if ((ret = avdevice_dev_to_app_control_message(h, AV_DEV_TO_APP_PREPARE_WINDOW_BUFFER, NULL , 0)) < 0) { + av_log(opengl, AV_LOG_ERROR, "Application failed to prepare window buffer.\n"); + goto fail; + } + } + + if ((ret = opengl_read_limits(opengl)) < 0) + goto fail; + + if (opengl->width > opengl->max_texture_size || opengl->height > opengl->max_texture_size) { + av_log(opengl, AV_LOG_ERROR, "Too big picture %dx%d, max supported size is %dx%d\n", + opengl->width, opengl->height, opengl->max_texture_size, opengl->max_texture_size); + ret = AVERROR(EINVAL); + goto fail; + } + + if (!opengl->no_window) { +#if HAVE_SDL + if ((ret = opengl_sdl_load_procedures(opengl)) < 0) + goto fail; +#endif + } else if ((ret = opengl_load_procedures(opengl)) < 0) + goto fail; + + opengl_fill_color_map(opengl); + opengl_get_texture_params(opengl); + + if ((ret = opengl_init_context(opengl)) < 0) + goto fail; + + if ((ret = opengl_prepare_vertex(h)) < 0) + goto fail; + + glClear(GL_COLOR_BUFFER_BIT); + +#if HAVE_SDL + if (!opengl->no_window) + SDL_GL_SwapBuffers(); +#endif + if (opengl->no_window && + (ret = avdevice_dev_to_app_control_message(h, AV_DEV_TO_APP_DISPLAY_WINDOW_BUFFER, NULL , 0)) < 0) { + av_log(opengl, AV_LOG_ERROR, "Application failed to display window buffer.\n"); + goto fail; + } + + ret = AVERROR_EXTERNAL; + OPENGL_ERROR_CHECK(opengl); + return 0; + + fail: + opengl_write_trailer(h); + return ret; +} + +static uint8_t* opengl_get_plane_pointer(OpenGLContext *opengl, AVPacket *pkt, int comp_index, + const AVPixFmtDescriptor *desc) +{ + uint8_t *data = pkt->data; + int wordsize = opengl_type_size(opengl->type); + int width_chroma = FF_CEIL_RSHIFT(opengl->width, desc->log2_chroma_w); + int height_chroma = FF_CEIL_RSHIFT(opengl->height, desc->log2_chroma_h); + int plane = desc->comp[comp_index].plane; + + switch(plane) { + case 0: + break; + case 1: + data += opengl->width * opengl->height * wordsize; + break; + case 2: + data += opengl->width * opengl->height * wordsize; + data += width_chroma * height_chroma * wordsize; + break; + case 3: + data += opengl->width * opengl->height * wordsize; + data += 2 * width_chroma * height_chroma * wordsize; + break; + default: + return NULL; + } + return data; +} + +static int opengl_draw(AVFormatContext *h, AVPacket *pkt, int repaint) +{ + OpenGLContext *opengl = h->priv_data; + enum AVPixelFormat pix_fmt = h->streams[0]->codec->pix_fmt; + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pix_fmt); + int ret; + +#if HAVE_SDL + if (!opengl->no_window && (ret = opengl_sdl_process_events(h)) < 0) + goto fail; +#endif + if (opengl->no_window && + (ret = avdevice_dev_to_app_control_message(h, AV_DEV_TO_APP_PREPARE_WINDOW_BUFFER, NULL , 0)) < 0) { + av_log(opengl, AV_LOG_ERROR, "Application failed to prepare window buffer.\n"); + goto fail; + } + + glClear(GL_COLOR_BUFFER_BIT); + + if (!repaint) { + glBindTexture(GL_TEXTURE_2D, opengl->texture_name[0]); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, opengl->width, opengl->height, opengl->format, opengl->type, + opengl_get_plane_pointer(opengl, pkt, 0, desc)); + if (desc->flags & AV_PIX_FMT_FLAG_PLANAR) { + int width_chroma = FF_CEIL_RSHIFT(opengl->width, desc->log2_chroma_w); + int height_chroma = FF_CEIL_RSHIFT(opengl->height, desc->log2_chroma_h); + glBindTexture(GL_TEXTURE_2D, opengl->texture_name[1]); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_chroma, height_chroma, opengl->format, opengl->type, + opengl_get_plane_pointer(opengl, pkt, 1, desc)); + glBindTexture(GL_TEXTURE_2D, opengl->texture_name[2]); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_chroma, height_chroma, opengl->format, opengl->type, + opengl_get_plane_pointer(opengl, pkt, 2, desc)); + if (desc->flags & AV_PIX_FMT_FLAG_ALPHA) { + glBindTexture(GL_TEXTURE_2D, opengl->texture_name[3]); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, opengl->width, opengl->height, opengl->format, opengl->type, + opengl_get_plane_pointer(opengl, pkt, 3, desc)); + } + } + } + ret = AVERROR_EXTERNAL; + OPENGL_ERROR_CHECK(opengl); + + if ((ret = opengl_prepare(opengl)) < 0) + goto fail; + + opengl->glprocs.glBindBuffer(FF_GL_ARRAY_BUFFER, opengl->vertex_buffer); + opengl->glprocs.glBindBuffer(FF_GL_ELEMENT_ARRAY_BUFFER, opengl->index_buffer); + opengl->glprocs.glVertexAttribPointer(opengl->position_attrib, 3, GL_FLOAT, GL_FALSE, sizeof(OpenGLVertexInfo), 0); + opengl->glprocs.glEnableVertexAttribArray(opengl->position_attrib); + opengl->glprocs.glVertexAttribPointer(opengl->texture_coords_attrib, 2, GL_FLOAT, GL_FALSE, sizeof(OpenGLVertexInfo), 12); + opengl->glprocs.glEnableVertexAttribArray(opengl->texture_coords_attrib); + + glDrawElements(GL_TRIANGLES, FF_ARRAY_ELEMS(g_index), GL_UNSIGNED_SHORT, 0); + + ret = AVERROR_EXTERNAL; + OPENGL_ERROR_CHECK(opengl); + +#if HAVE_SDL + if (!opengl->no_window) + SDL_GL_SwapBuffers(); +#endif + if (opengl->no_window && + (ret = avdevice_dev_to_app_control_message(h, AV_DEV_TO_APP_DISPLAY_WINDOW_BUFFER, NULL , 0)) < 0) { + av_log(opengl, AV_LOG_ERROR, "Application failed to display window buffer.\n"); + goto fail; + } + + return 0; + fail: + return ret; +} + +static int opengl_write_packet(AVFormatContext *h, AVPacket *pkt) +{ + return opengl_draw(h, pkt, 0); +} + +#define OFFSET(x) offsetof(OpenGLContext, x) +#define ENC AV_OPT_FLAG_ENCODING_PARAM +static const AVOption options[] = { + { "background", "set background color", OFFSET(background), AV_OPT_TYPE_COLOR, {.str = "black"}, CHAR_MIN, CHAR_MAX, ENC }, + { "no_window", "disable default window", OFFSET(no_window), AV_OPT_TYPE_INT, {.i64 = 0}, INT_MIN, INT_MAX, ENC }, + { "window_title", "set window title", OFFSET(window_title), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, ENC }, + { NULL } +}; + +static const AVClass opengl_class = { + .class_name = "opengl outdev", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + +AVOutputFormat ff_opengl_muxer = { + .name = "opengl", + .long_name = NULL_IF_CONFIG_SMALL("OpenGL output"), + .priv_data_size = sizeof(OpenGLContext), + .audio_codec = AV_CODEC_ID_NONE, + .video_codec = AV_CODEC_ID_RAWVIDEO, + .write_header = opengl_write_header, + .write_packet = opengl_write_packet, + .write_trailer = opengl_write_trailer, + .control_message = opengl_control_message, + .flags = AVFMT_NOFILE | AVFMT_VARIABLE_FPS | AVFMT_NOTIMESTAMPS, + .priv_class = &opengl_class, +}; diff --git a/libavdevice/opengl_enc_shaders.h b/libavdevice/opengl_enc_shaders.h new file mode 100644 index 0000000000..e3115c4f10 --- /dev/null +++ b/libavdevice/opengl_enc_shaders.h @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2014 Lukasz Marek + * + * This file is part of FFmpeg. + * + * FFmpeg 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. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVDEVICE_OPENGL_SHADERS_H +#define AVDEVICE_OPENGL_SHADERS_H + +#include "libavutil/pixfmt.h" + +const char *FF_OPENGL_VERTEX_SHADER = + "uniform mat4 u_projectionMatrix;" + "uniform mat4 u_modelViewMatrix;" + + "attribute vec4 a_position;" + "attribute vec2 a_textureCoords;" + + "varying vec2 texture_coordinate;" + + "void main()" + "{" + "gl_Position = u_projectionMatrix * (a_position * u_modelViewMatrix);" + "texture_coordinate = a_textureCoords;" + "}"; + +/** + * Fragment shader for packet RGBA formats. + */ +const char *FF_OPENGL_FRAGMENT_SHADER_RGBA_PACKET = +#if defined(GL_ES_VERSION_2_0) + "precision mediump float;" +#endif + "uniform sampler2D u_texture0;" + "uniform mat4 u_colorMap;" + + "varying vec2 texture_coordinate;" + + "void main()" + "{" + "gl_FragColor = texture2D(u_texture0, texture_coordinate) * u_colorMap;" + "}"; + +/** + * Fragment shader for packet RGB formats. + */ +const char *FF_OPENGL_FRAGMENT_SHADER_RGB_PACKET = +#if defined(GL_ES_VERSION_2_0) + "precision mediump float;" +#endif + "uniform sampler2D u_texture0;" + "uniform mat4 u_colorMap;" + + "varying vec2 texture_coordinate;" + + "void main()" + "{" + "gl_FragColor = vec4((texture2D(u_texture0, texture_coordinate) * u_colorMap).rgb, 1.0);" + "}"; + +/** + * Fragment shader for planar RGBA formats. + */ +const char *FF_OPENGL_FRAGMENT_SHADER_RGBA_PLANAR = +#if defined(GL_ES_VERSION_2_0) + "precision mediump float;" +#endif + "uniform sampler2D u_texture0;" + "uniform sampler2D u_texture1;" + "uniform sampler2D u_texture2;" + "uniform sampler2D u_texture3;" + + "varying vec2 texture_coordinate;" + + "void main()" + "{" + "gl_FragColor = vec4(texture2D(u_texture0, texture_coordinate).r," + "texture2D(u_texture1, texture_coordinate).r," + "texture2D(u_texture2, texture_coordinate).r," + "texture2D(u_texture3, texture_coordinate).r);" + "}"; + +/** + * Fragment shader for planar RGB formats. + */ +const char *FF_OPENGL_FRAGMENT_SHADER_RGB_PLANAR = +#if defined(GL_ES_VERSION_2_0) + "precision mediump float;" +#endif + "uniform sampler2D u_texture0;" + "uniform sampler2D u_texture1;" + "uniform sampler2D u_texture2;" + + "varying vec2 texture_coordinate;" + + "void main()" + "{" + "gl_FragColor = vec4(texture2D(u_texture0, texture_coordinate).r," + "texture2D(u_texture1, texture_coordinate).r," + "texture2D(u_texture2, texture_coordinate).r," + "1.0);" + "}"; + +/** + * Fragment shader for planar YUV formats. + */ +const char *FF_OPENGL_FRAGMENT_SHADER_YUV_PLANAR = +#if defined(GL_ES_VERSION_2_0) + "precision mediump float;" +#endif + "uniform sampler2D u_texture0;" + "uniform sampler2D u_texture1;" + "uniform sampler2D u_texture2;" + "uniform float u_chroma_div_w;" + "uniform float u_chroma_div_h;" + + "varying vec2 texture_coordinate;" + + "void main()" + "{" + "vec3 yuv;" + + "yuv.r = texture2D(u_texture0, texture_coordinate).r - 0.0625;" + "yuv.g = texture2D(u_texture1, vec2(texture_coordinate.x / u_chroma_div_w, texture_coordinate.y / u_chroma_div_h)).r - 0.5;" + "yuv.b = texture2D(u_texture2, vec2(texture_coordinate.x / u_chroma_div_w, texture_coordinate.y / u_chroma_div_h)).r - 0.5;" + + "gl_FragColor = clamp(vec4(mat3(1.1643, 1.16430, 1.1643," + "0.0, -0.39173, 2.0170," + "1.5958, -0.81290, 0.0) * yuv, 1.0), 0.0, 1.0);" + + "}"; + +/** + * Fragment shader for planar YUVA formats. + */ +const char *FF_OPENGL_FRAGMENT_SHADER_YUVA_PLANAR = +#if defined(GL_ES_VERSION_2_0) + "precision mediump float;" +#endif + "uniform sampler2D u_texture0;" + "uniform sampler2D u_texture1;" + "uniform sampler2D u_texture2;" + "uniform sampler2D u_texture3;" + "uniform float u_chroma_div_w;" + "uniform float u_chroma_div_h;" + + "varying vec2 texture_coordinate;" + + "void main()" + "{" + "vec3 yuv;" + + "yuv.r = texture2D(u_texture0, texture_coordinate).r - 0.0625;" + "yuv.g = texture2D(u_texture1, vec2(texture_coordinate.x / u_chroma_div_w, texture_coordinate.y / u_chroma_div_h)).r - 0.5;" + "yuv.b = texture2D(u_texture2, vec2(texture_coordinate.x / u_chroma_div_w, texture_coordinate.y / u_chroma_div_h)).r - 0.5;" + + "gl_FragColor = clamp(vec4(mat3(1.1643, 1.16430, 1.1643," + "0.0, -0.39173, 2.0170," + "1.5958, -0.81290, 0.0) * yuv, texture2D(u_texture3, texture_coordinate).r), 0.0, 1.0);" + "}"; + +#endif /* AVDEVICE_OPENGL_SHADERS_H */ -- cgit v1.2.3