Merge commit '4f373a5111f900af54301907132942f95276285c'
[ffmpeg.git] / libavdevice / opengl_enc.c
index 32a77c3..434ae97 100644 (file)
 
 #include "config.h"
 
+#if HAVE_WINDOWS_H
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#endif
 #if HAVE_OPENGL_GL3_H
 #include <OpenGL/gl3.h>
 #elif HAVE_ES2_GL_H
@@ -41,9 +45,6 @@
 #if HAVE_GLXGETPROCADDRESS
 #include <GL/glx.h>
 #endif
-#if HAVE_WINDOWS_H
-#include <windows.h>
-#endif
 
 #if HAVE_SDL
 #include <SDL.h>
@@ -56,6 +57,7 @@
 #include "libavutil/avassert.h"
 #include "libavutil/avstring.h"
 #include "libavformat/avformat.h"
+#include "libavformat/internal.h"
 #include "libavdevice/avdevice.h"
 #include "opengl_enc_shaders.h"
 
 #define APIENTRY
 #endif
 
-/* GL_RED_COMPONENT is used for plannar pixel types.
+/* FF_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,
+ * On some platforms GL_RED is not available 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
+#define FF_GL_RED_COMPONENT GL_RED
 #elif defined(GL_LUMINANCE)
-#define GL_RED_COMPONENT GL_LUMINANCE
+#define FF_GL_RED_COMPONENT GL_LUMINANCE
 #else
-#define GL_RED_COMPONENT 0x1903; //GL_RED
+#define FF_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
+#define FF_GL_UNPACK_ROW_LENGTH          0x0CF2
 
 /* MinGW exposes only OpenGL 1.1 API */
 #define FF_GL_ARRAY_BUFFER                0x8892
@@ -162,7 +165,7 @@ typedef struct OpenGLVertexInfo
 } OpenGLVertexInfo;
 
 /* defines 2 triangles to display */
-static GLushort g_index[6] =
+static const GLushort g_index[6] =
 {
     0, 1, 2,
     0, 3, 2,
@@ -176,6 +179,7 @@ typedef struct OpenGLContext {
 #endif
     FFOpenGLFunctions glprocs;
 
+    int inited;                        ///< Set to 1 when write_header was successfully called.
     uint8_t background[4];             ///< Background color
     int no_window;                     ///< 0 for create default window
     char *window_title;                ///< Title of the window
@@ -185,9 +189,12 @@ typedef struct OpenGLContext {
     GLint max_viewport_width;          ///< Maximum viewport size
     GLint max_viewport_height;         ///< Maximum viewport size
     int non_pow_2_textures;            ///< 1 when non power of 2 textures are supported
+    int unpack_subimage;               ///< 1 when GL_EXT_unpack_subimage is available
 
     /* Current OpenGL configuration */
     GLuint program;                    ///< Shader program
+    GLuint vertex_shader;              ///< Vertex shader
+    GLuint fragment_shader;            ///< Fragment shader for current pix_pmt
     GLuint texture_name[4];            ///< Textures' IDs
     GLuint index_buffer;               ///< Index buffer
     GLuint vertex_buffer;              ///< Vertex buffer
@@ -225,21 +232,21 @@ static const struct OpenGLFormatDesc {
     GLenum format;
     GLenum type;
 } opengl_format_desc[] = {
-    { AV_PIX_FMT_YUV420P,    &FF_OPENGL_FRAGMENT_SHADER_YUV_PLANAR,  GL_RED_COMPONENT, GL_UNSIGNED_BYTE },
-    { AV_PIX_FMT_YUV444P,    &FF_OPENGL_FRAGMENT_SHADER_YUV_PLANAR,  GL_RED_COMPONENT, GL_UNSIGNED_BYTE },
-    { AV_PIX_FMT_YUV422P,    &FF_OPENGL_FRAGMENT_SHADER_YUV_PLANAR,  GL_RED_COMPONENT, GL_UNSIGNED_BYTE },
-    { AV_PIX_FMT_YUV410P,    &FF_OPENGL_FRAGMENT_SHADER_YUV_PLANAR,  GL_RED_COMPONENT, GL_UNSIGNED_BYTE },
-    { AV_PIX_FMT_YUV411P,    &FF_OPENGL_FRAGMENT_SHADER_YUV_PLANAR,  GL_RED_COMPONENT, GL_UNSIGNED_BYTE },
-    { AV_PIX_FMT_YUV440P,    &FF_OPENGL_FRAGMENT_SHADER_YUV_PLANAR,  GL_RED_COMPONENT, GL_UNSIGNED_BYTE },
-    { AV_PIX_FMT_YUV420P16,  &FF_OPENGL_FRAGMENT_SHADER_YUV_PLANAR,  GL_RED_COMPONENT, GL_UNSIGNED_SHORT },
-    { AV_PIX_FMT_YUV422P16,  &FF_OPENGL_FRAGMENT_SHADER_YUV_PLANAR,  GL_RED_COMPONENT, GL_UNSIGNED_SHORT },
-    { AV_PIX_FMT_YUV444P16,  &FF_OPENGL_FRAGMENT_SHADER_YUV_PLANAR,  GL_RED_COMPONENT, GL_UNSIGNED_SHORT },
-    { AV_PIX_FMT_YUVA420P,   &FF_OPENGL_FRAGMENT_SHADER_YUVA_PLANAR, GL_RED_COMPONENT, GL_UNSIGNED_BYTE },
-    { AV_PIX_FMT_YUVA444P,   &FF_OPENGL_FRAGMENT_SHADER_YUVA_PLANAR, GL_RED_COMPONENT, GL_UNSIGNED_BYTE },
-    { AV_PIX_FMT_YUVA422P,   &FF_OPENGL_FRAGMENT_SHADER_YUVA_PLANAR, GL_RED_COMPONENT, GL_UNSIGNED_BYTE },
-    { AV_PIX_FMT_YUVA420P16, &FF_OPENGL_FRAGMENT_SHADER_YUVA_PLANAR, GL_RED_COMPONENT, GL_UNSIGNED_SHORT },
-    { AV_PIX_FMT_YUVA422P16, &FF_OPENGL_FRAGMENT_SHADER_YUVA_PLANAR, GL_RED_COMPONENT, GL_UNSIGNED_SHORT },
-    { AV_PIX_FMT_YUVA444P16, &FF_OPENGL_FRAGMENT_SHADER_YUVA_PLANAR, GL_RED_COMPONENT, GL_UNSIGNED_SHORT },
+    { AV_PIX_FMT_YUV420P,    &FF_OPENGL_FRAGMENT_SHADER_YUV_PLANAR,  FF_GL_RED_COMPONENT, GL_UNSIGNED_BYTE },
+    { AV_PIX_FMT_YUV444P,    &FF_OPENGL_FRAGMENT_SHADER_YUV_PLANAR,  FF_GL_RED_COMPONENT, GL_UNSIGNED_BYTE },
+    { AV_PIX_FMT_YUV422P,    &FF_OPENGL_FRAGMENT_SHADER_YUV_PLANAR,  FF_GL_RED_COMPONENT, GL_UNSIGNED_BYTE },
+    { AV_PIX_FMT_YUV410P,    &FF_OPENGL_FRAGMENT_SHADER_YUV_PLANAR,  FF_GL_RED_COMPONENT, GL_UNSIGNED_BYTE },
+    { AV_PIX_FMT_YUV411P,    &FF_OPENGL_FRAGMENT_SHADER_YUV_PLANAR,  FF_GL_RED_COMPONENT, GL_UNSIGNED_BYTE },
+    { AV_PIX_FMT_YUV440P,    &FF_OPENGL_FRAGMENT_SHADER_YUV_PLANAR,  FF_GL_RED_COMPONENT, GL_UNSIGNED_BYTE },
+    { AV_PIX_FMT_YUV420P16,  &FF_OPENGL_FRAGMENT_SHADER_YUV_PLANAR,  FF_GL_RED_COMPONENT, GL_UNSIGNED_SHORT },
+    { AV_PIX_FMT_YUV422P16,  &FF_OPENGL_FRAGMENT_SHADER_YUV_PLANAR,  FF_GL_RED_COMPONENT, GL_UNSIGNED_SHORT },
+    { AV_PIX_FMT_YUV444P16,  &FF_OPENGL_FRAGMENT_SHADER_YUV_PLANAR,  FF_GL_RED_COMPONENT, GL_UNSIGNED_SHORT },
+    { AV_PIX_FMT_YUVA420P,   &FF_OPENGL_FRAGMENT_SHADER_YUVA_PLANAR, FF_GL_RED_COMPONENT, GL_UNSIGNED_BYTE },
+    { AV_PIX_FMT_YUVA444P,   &FF_OPENGL_FRAGMENT_SHADER_YUVA_PLANAR, FF_GL_RED_COMPONENT, GL_UNSIGNED_BYTE },
+    { AV_PIX_FMT_YUVA422P,   &FF_OPENGL_FRAGMENT_SHADER_YUVA_PLANAR, FF_GL_RED_COMPONENT, GL_UNSIGNED_BYTE },
+    { AV_PIX_FMT_YUVA420P16, &FF_OPENGL_FRAGMENT_SHADER_YUVA_PLANAR, FF_GL_RED_COMPONENT, GL_UNSIGNED_SHORT },
+    { AV_PIX_FMT_YUVA422P16, &FF_OPENGL_FRAGMENT_SHADER_YUVA_PLANAR, FF_GL_RED_COMPONENT, GL_UNSIGNED_SHORT },
+    { AV_PIX_FMT_YUVA444P16, &FF_OPENGL_FRAGMENT_SHADER_YUVA_PLANAR, FF_GL_RED_COMPONENT, GL_UNSIGNED_SHORT },
     { AV_PIX_FMT_RGB24,      &FF_OPENGL_FRAGMENT_SHADER_RGB_PACKET,  GL_RGB, GL_UNSIGNED_BYTE },
     { AV_PIX_FMT_BGR24,      &FF_OPENGL_FRAGMENT_SHADER_RGB_PACKET,  GL_RGB, GL_UNSIGNED_BYTE },
     { AV_PIX_FMT_0RGB,       &FF_OPENGL_FRAGMENT_SHADER_RGB_PACKET,  GL_RGBA, GL_UNSIGNED_BYTE },
@@ -259,25 +266,52 @@ static const struct OpenGLFormatDesc {
     { AV_PIX_FMT_BGRA,       &FF_OPENGL_FRAGMENT_SHADER_RGBA_PACKET, GL_RGBA, GL_UNSIGNED_BYTE },
     { AV_PIX_FMT_RGBA64,     &FF_OPENGL_FRAGMENT_SHADER_RGBA_PACKET, GL_RGBA, GL_UNSIGNED_SHORT },
     { AV_PIX_FMT_BGRA64,     &FF_OPENGL_FRAGMENT_SHADER_RGBA_PACKET, GL_RGBA, GL_UNSIGNED_SHORT },
-    { AV_PIX_FMT_GBRP,       &FF_OPENGL_FRAGMENT_SHADER_RGB_PLANAR,  GL_RED_COMPONENT, GL_UNSIGNED_BYTE },
-    { AV_PIX_FMT_GBRP16,     &FF_OPENGL_FRAGMENT_SHADER_RGB_PLANAR,  GL_RED_COMPONENT, GL_UNSIGNED_SHORT },
-    { AV_PIX_FMT_GBRAP,      &FF_OPENGL_FRAGMENT_SHADER_RGBA_PLANAR, GL_RED_COMPONENT, GL_UNSIGNED_BYTE },
-    { AV_PIX_FMT_GBRAP16,    &FF_OPENGL_FRAGMENT_SHADER_RGBA_PLANAR, GL_RED_COMPONENT, GL_UNSIGNED_SHORT },
+    { AV_PIX_FMT_GBRP,       &FF_OPENGL_FRAGMENT_SHADER_RGB_PLANAR,  FF_GL_RED_COMPONENT, GL_UNSIGNED_BYTE },
+    { AV_PIX_FMT_GBRP16,     &FF_OPENGL_FRAGMENT_SHADER_RGB_PLANAR,  FF_GL_RED_COMPONENT, GL_UNSIGNED_SHORT },
+    { AV_PIX_FMT_GBRAP,      &FF_OPENGL_FRAGMENT_SHADER_RGBA_PLANAR, FF_GL_RED_COMPONENT, GL_UNSIGNED_BYTE },
+    { AV_PIX_FMT_GBRAP16,    &FF_OPENGL_FRAGMENT_SHADER_RGBA_PLANAR, FF_GL_RED_COMPONENT, GL_UNSIGNED_SHORT },
+    { AV_PIX_FMT_GRAY8,      &FF_OPENGL_FRAGMENT_SHADER_GRAY,        FF_GL_RED_COMPONENT, GL_UNSIGNED_BYTE },
+    { AV_PIX_FMT_GRAY16,     &FF_OPENGL_FRAGMENT_SHADER_GRAY,        FF_GL_RED_COMPONENT, GL_UNSIGNED_SHORT },
     { AV_PIX_FMT_NONE,       NULL }
 };
 
 static av_cold int opengl_prepare_vertex(AVFormatContext *s);
-static int opengl_draw(AVFormatContext *h, AVPacket *pkt, int repaint);
+static int opengl_draw(AVFormatContext *h, void *intput, int repaint, int is_pkt);
 static av_cold int opengl_init_context(OpenGLContext *opengl);
 
+static av_cold void opengl_deinit_context(OpenGLContext *opengl)
+{
+    glDeleteTextures(4, opengl->texture_name);
+    opengl->texture_name[0] = opengl->texture_name[1] =
+    opengl->texture_name[2] = opengl->texture_name[3] = 0;
+    if (opengl->glprocs.glUseProgram)
+        opengl->glprocs.glUseProgram(0);
+    if (opengl->glprocs.glDeleteProgram) {
+        opengl->glprocs.glDeleteProgram(opengl->program);
+        opengl->program = 0;
+    }
+    if (opengl->glprocs.glDeleteShader) {
+        opengl->glprocs.glDeleteShader(opengl->vertex_shader);
+        opengl->glprocs.glDeleteShader(opengl->fragment_shader);
+        opengl->vertex_shader = opengl->fragment_shader = 0;
+    }
+    if (opengl->glprocs.glBindBuffer) {
+        opengl->glprocs.glBindBuffer(FF_GL_ARRAY_BUFFER, 0);
+        opengl->glprocs.glBindBuffer(FF_GL_ELEMENT_ARRAY_BUFFER, 0);
+    }
+    if (opengl->glprocs.glDeleteBuffers) {
+        opengl->glprocs.glDeleteBuffers(2, &opengl->index_buffer);
+        opengl->vertex_buffer = opengl->index_buffer = 0;
+    }
+}
+
 static int opengl_resize(AVFormatContext *h, int width, int height)
 {
     int ret = 0;
     OpenGLContext *opengl = h->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->inited) {
         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");
@@ -285,7 +319,7 @@ static int opengl_resize(AVFormatContext *h, int width, int height)
         }
         if ((ret = opengl_prepare_vertex(h)) < 0)
             goto end;
-        ret = opengl_draw(h, NULL, 1);
+        ret = opengl_draw(h, NULL, 1, 0);
     }
   end:
     return ret;
@@ -349,8 +383,7 @@ static int opengl_sdl_process_events(AVFormatContext *h)
             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);
+                opengl_deinit_context(opengl);
             }
             if ((ret = opengl_sdl_recreate_window(opengl, event.resize.w, event.resize.h)) < 0)
                 return ret;
@@ -375,11 +408,13 @@ static int av_cold opengl_sdl_create_window(AVFormatContext *h)
         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)
+    if ((ret = opengl_sdl_recreate_window(opengl, opengl->window_width,
+                                          opengl->window_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;
+    SDL_WM_SetCaption(opengl->window_title, NULL);
     opengl_control_message(h, AV_APP_TO_DEV_WINDOW_SIZE, &message, sizeof(AVDeviceRect));
     return 0;
 }
@@ -432,6 +467,11 @@ static int av_cold opengl_load_procedures(OpenGLContext *opengl)
 {
     FFOpenGLFunctions *procs = &opengl->glprocs;
 
+#if HAVE_SDL
+    if (!opengl->no_window)
+        return opengl_sdl_load_procedures(opengl);
+#endif
+
     procs->glActiveTexture = glActiveTexture;
     procs->glGenBuffers = glGenBuffers;
     procs->glDeleteBuffers = glDeleteBuffers;
@@ -477,6 +517,11 @@ static int av_cold opengl_load_procedures(OpenGLContext *opengl)
         return AVERROR(ENOSYS); \
     }
 
+#if HAVE_SDL
+    if (!opengl->no_window)
+        return opengl_sdl_load_procedures(opengl);
+#endif
+
     LOAD_OPENGL_FUN(glActiveTexture, FF_PFNGLACTIVETEXTUREPROC)
     LOAD_OPENGL_FUN(glGenBuffers, FF_PFNGLGENBUFFERSPROC)
     LOAD_OPENGL_FUN(glDeleteBuffers, FF_PFNGLDELETEBUFFERSPROC)
@@ -510,16 +555,14 @@ static int av_cold opengl_load_procedures(OpenGLContext *opengl)
 }
 #endif
 
-static av_always_inline void opengl_make_identity(float matrix[16])
+static 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)
+static 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;
@@ -541,7 +584,7 @@ static av_always_inline void opengl_make_ortho(float matrix[16],
 static av_cold int opengl_read_limits(OpenGLContext *opengl)
 {
     static const struct{
-        const char *extention;
+        const char *extension;
         int major;
         int minor;
     } required_extensions[] = {
@@ -561,12 +604,12 @@ static av_cold int opengl_read_limits(OpenGLContext *opengl)
     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++) {
+    for (i = 0; required_extensions[i].extension; i++) {
         if (major < required_extensions[i].major &&
             (major == required_extensions[i].major && minor < required_extensions[i].minor) &&
-            !strstr(extensions, required_extensions[i].extention)) {
+            !strstr(extensions, required_extensions[i].extension)) {
             av_log(opengl, AV_LOG_ERROR, "Required extension %s is not supported.\n",
-                   required_extensions[i].extention);
+                   required_extensions[i].extension);
             av_log(opengl, AV_LOG_DEBUG, "Supported extensions are: %s\n", extensions);
             return AVERROR(ENOSYS);
         }
@@ -574,8 +617,14 @@ static av_cold int opengl_read_limits(OpenGLContext *opengl)
     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");
+#if defined(GL_ES_VERSION_2_0)
+    opengl->unpack_subimage = !!strstr(extensions, "GL_EXT_unpack_subimage");
+#else
+    opengl->unpack_subimage = 1;
+#endif
 
     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, "Unpack Subimage extension support: %s\n", opengl->unpack_subimage ? "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);
@@ -596,7 +645,7 @@ static const char* opengl_get_fragment_shader_code(enum AVPixelFormat format)
     return NULL;
 }
 
-static av_always_inline int opengl_type_size(GLenum type)
+static int opengl_type_size(GLenum type)
 {
     switch(type) {
     case GL_UNSIGNED_SHORT:
@@ -731,7 +780,6 @@ static av_cold GLuint opengl_load_shader(OpenGLContext *opengl, GLenum type, con
 
 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);
 
@@ -741,15 +789,15 @@ static av_cold int opengl_compile_shaders(OpenGLContext *opengl, enum AVPixelFor
         return AVERROR(EINVAL);
     }
 
-    vertex_shader = opengl_load_shader(opengl, FF_GL_VERTEX_SHADER,
-                                       FF_OPENGL_VERTEX_SHADER);
-    if (!vertex_shader) {
+    opengl->vertex_shader = opengl_load_shader(opengl, FF_GL_VERTEX_SHADER,
+                                               FF_OPENGL_VERTEX_SHADER);
+    if (!opengl->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) {
+    opengl->fragment_shader = opengl_load_shader(opengl, FF_GL_FRAGMENT_SHADER,
+                                                 fragment_shader_code);
+    if (!opengl->fragment_shader) {
         av_log(opengl, AV_LOG_ERROR, "Fragment shader loading failed.\n");
         goto fail;
     }
@@ -758,8 +806,8 @@ static av_cold int opengl_compile_shaders(OpenGLContext *opengl, enum AVPixelFor
     if (!opengl->program)
         goto fail;
 
-    opengl->glprocs.glAttachShader(opengl->program, vertex_shader);
-    opengl->glprocs.glAttachShader(opengl->program, fragment_shader);
+    opengl->glprocs.glAttachShader(opengl->program, opengl->vertex_shader);
+    opengl->glprocs.glAttachShader(opengl->program, opengl->fragment_shader);
     opengl->glprocs.glLinkProgram(opengl->program);
 
     opengl->glprocs.glGetProgramiv(opengl->program, FF_GL_LINK_STATUS, &result);
@@ -792,10 +840,10 @@ static av_cold int opengl_compile_shaders(OpenGLContext *opengl, enum AVPixelFor
     OPENGL_ERROR_CHECK(opengl);
     return 0;
   fail:
-    opengl->glprocs.glDeleteShader(vertex_shader);
-    opengl->glprocs.glDeleteShader(fragment_shader);
+    opengl->glprocs.glDeleteShader(opengl->vertex_shader);
+    opengl->glprocs.glDeleteShader(opengl->fragment_shader);
     opengl->glprocs.glDeleteProgram(opengl->program);
-    opengl->program = 0;
+    opengl->fragment_shader = opengl->vertex_shader = opengl->program = 0;
     return AVERROR_EXTERNAL;
 }
 
@@ -825,8 +873,8 @@ static av_cold int opengl_prepare_vertex(AVFormatContext *s)
     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);
+        opengl->window_width = FFMIN(opengl->window_width, opengl->max_viewport_width);
+        opengl->window_height = FFMIN(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);
@@ -889,25 +937,64 @@ static int opengl_prepare(OpenGLContext *opengl)
     return AVERROR_EXTERNAL;
 }
 
-static av_cold int opengl_write_trailer(AVFormatContext *h)
+static int opengl_create_window(AVFormatContext *h)
 {
     OpenGLContext *opengl = h->priv_data;
+    int ret;
 
-    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 (!opengl->no_window) {
+#if HAVE_SDL
+        if ((ret = opengl_sdl_create_window(h)) < 0) {
+            av_log(opengl, AV_LOG_ERROR, "Cannot create default SDL window.\n");
+            return ret;
+        }
+#else
+        av_log(opengl, AV_LOG_ERROR, "FFmpeg is compiled without SDL. Cannot create default window.\n");
+        return AVERROR(ENOSYS);
+#endif
+    } else {
+        AVDeviceRect message;
+        message.x = message.y = 0;
+        message.width = opengl->window_width;
+        message.height = opengl->window_height;
+        if ((ret = avdevice_dev_to_app_control_message(h, AV_DEV_TO_APP_CREATE_WINDOW_BUFFER,
+                                                       &message , sizeof(message))) < 0) {
+            av_log(opengl, AV_LOG_ERROR, "Application failed to create window buffer.\n");
+            return ret;
+        }
+        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");
+            return ret;
+        }
+    }
+    return 0;
+}
 
+static int opengl_release_window(AVFormatContext *h)
+{
+    int ret;
+    OpenGLContext *opengl = h->priv_data;
+    if (!opengl->no_window) {
 #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)
+    } else if ((ret = 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 ret;
+    }
+    return 0;
+}
+
+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");
+
+    opengl_deinit_context(opengl);
+    opengl_release_window(h);
 
     return 0;
 }
@@ -987,29 +1074,16 @@ static av_cold int opengl_write_header(AVFormatContext *h)
     opengl->width = st->codec->width;
     opengl->height = st->codec->height;
     opengl->pix_fmt = st->codec->pix_fmt;
+    if (!opengl->window_width)
+        opengl->window_width = opengl->width;
+    if (!opengl->window_height)
+        opengl->window_height = opengl->height;
 
     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);
+    if ((ret = opengl_create_window(h)))
         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;
@@ -1021,12 +1095,7 @@ static av_cold int opengl_write_header(AVFormatContext *h)
         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)
+    if ((ret = opengl_load_procedures(opengl)) < 0)
         goto fail;
 
     opengl_fill_color_map(opengl);
@@ -1052,6 +1121,8 @@ static av_cold int opengl_write_header(AVFormatContext *h)
 
     ret = AVERROR_EXTERNAL;
     OPENGL_ERROR_CHECK(opengl);
+
+    opengl->inited = 1;
     return 0;
 
   fail:
@@ -1088,7 +1159,45 @@ static uint8_t* opengl_get_plane_pointer(OpenGLContext *opengl, AVPacket *pkt, i
     return data;
 }
 
-static int opengl_draw(AVFormatContext *h, AVPacket *pkt, int repaint)
+#define LOAD_TEXTURE_DATA(comp_index, sub)                                                  \
+{                                                                                           \
+    int width = sub ? FF_CEIL_RSHIFT(opengl->width, desc->log2_chroma_w) : opengl->width;   \
+    int height = sub ? FF_CEIL_RSHIFT(opengl->height, desc->log2_chroma_h): opengl->height; \
+    uint8_t *data;                                                                          \
+    int plane = desc->comp[comp_index].plane;                                               \
+                                                                                            \
+    glBindTexture(GL_TEXTURE_2D, opengl->texture_name[comp_index]);                         \
+    if (!is_pkt) {                                                                          \
+        GLint length = ((AVFrame *)input)->linesize[plane];                                 \
+        int bytes_per_pixel = opengl_type_size(opengl->type);                               \
+        if (!(desc->flags & AV_PIX_FMT_FLAG_PLANAR))                                        \
+            bytes_per_pixel *= desc->nb_components;                                         \
+        data = ((AVFrame *)input)->data[plane];                                             \
+        if (!(length % bytes_per_pixel) &&                                                  \
+            (opengl->unpack_subimage || ((length / bytes_per_pixel) == width))) {           \
+            length /= bytes_per_pixel;                                                      \
+            if (length != width)                                                            \
+                glPixelStorei(FF_GL_UNPACK_ROW_LENGTH, length);                             \
+            glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height,                          \
+                            opengl->format, opengl->type, data);                            \
+            if (length != width)                                                            \
+                glPixelStorei(FF_GL_UNPACK_ROW_LENGTH, 0);                                  \
+        } else {                                                                            \
+            int h;                                                                          \
+            for (h = 0; h < height; h++) {                                                  \
+                glTexSubImage2D(GL_TEXTURE_2D, 0, 0, h, width, 1,                           \
+                                opengl->format, opengl->type, data);                        \
+                data += length;                                                             \
+            }                                                                               \
+        }                                                                                   \
+    } else {                                                                                \
+        data = opengl_get_plane_pointer(opengl, input, comp_index, desc);                   \
+        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height,                              \
+                        opengl->format, opengl->type, data);                                \
+    }                                                                                       \
+}
+
+static int opengl_draw(AVFormatContext *h, void *input, int repaint, int is_pkt)
 {
     OpenGLContext *opengl = h->priv_data;
     enum AVPixelFormat pix_fmt = h->streams[0]->codec->pix_fmt;
@@ -1108,23 +1217,14 @@ static int opengl_draw(AVFormatContext *h, AVPacket *pkt, int repaint)
     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 (is_pkt)
+            glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+        LOAD_TEXTURE_DATA(0, 0)
         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));
-            }
+            LOAD_TEXTURE_DATA(1, 1)
+            LOAD_TEXTURE_DATA(2, 1)
+            if (desc->flags & AV_PIX_FMT_FLAG_ALPHA)
+                LOAD_TEXTURE_DATA(3, 0)
         }
     }
     ret = AVERROR_EXTERNAL;
@@ -1162,7 +1262,15 @@ static int opengl_draw(AVFormatContext *h, AVPacket *pkt, int repaint)
 
 static int opengl_write_packet(AVFormatContext *h, AVPacket *pkt)
 {
-    return opengl_draw(h, pkt, 0);
+    return opengl_draw(h, pkt, 0, 1);
+}
+
+static int opengl_write_frame(AVFormatContext *h, int stream_index,
+                              AVFrame **frame, unsigned flags)
+{
+    if ((flags & AV_WRITE_UNCODED_FRAME_QUERY))
+        return 0;
+    return opengl_draw(h, *frame, 0, 0);
 }
 
 #define OFFSET(x) offsetof(OpenGLContext, x)
@@ -1171,6 +1279,7 @@ 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 },
+    { "window_size",  "set window size",        OFFSET(window_width), AV_OPT_TYPE_IMAGE_SIZE, {.str = NULL}, 0, 0, ENC },
     { NULL }
 };
 
@@ -1179,6 +1288,7 @@ static const AVClass opengl_class = {
     .item_name  = av_default_item_name,
     .option     = options,
     .version    = LIBAVUTIL_VERSION_INT,
+    .category   = AV_CLASS_CATEGORY_DEVICE_VIDEO_OUTPUT,
 };
 
 AVOutputFormat ff_opengl_muxer = {
@@ -1189,6 +1299,7 @@ AVOutputFormat ff_opengl_muxer = {
     .video_codec    = AV_CODEC_ID_RAWVIDEO,
     .write_header   = opengl_write_header,
     .write_packet   = opengl_write_packet,
+    .write_uncoded_frame = opengl_write_frame,
     .write_trailer  = opengl_write_trailer,
     .control_message = opengl_control_message,
     .flags          = AVFMT_NOFILE | AVFMT_VARIABLE_FPS | AVFMT_NOTIMESTAMPS,