Uses gstreamer for a simple decoder.  If decoder can give us dma-buf's
directly, we'll directly use that as a texture (zero copy), otherwise
memcpy into a buffer from gbm.  This should work with both hw and sw
decoders.

Probably room for improvement.  And the interface between gl and the
decoder is pretty simple so I suppose other decoders would be possible.
(But hopefully they could already be supported via gstreamer.)

Signed-off-by: Rob Clark <[email protected]>
---
 Makefile.am   |   6 +
 common.h      |  20 +++
 configure.ac  |   9 ++
 cube-tex.c    |   5 +-
 cube-video.c  | 386 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gst-decoder.c | 340 +++++++++++++++++++++++++++++++++++++++++++++++++++
 kmscube.c     |  20 ++-
 7 files changed, 782 insertions(+), 4 deletions(-)
 create mode 100644 cube-video.c
 create mode 100644 gst-decoder.c

diff --git a/Makefile.am b/Makefile.am
index 270b760..a36087d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -53,3 +53,9 @@ kmscube_SOURCES = \
        frame-512x512-NV12.c \
        frame-512x512-RGBA.c \
        kmscube.c
+
+if ENABLE_GST
+kmscube_LDADD += $(GST_LIBS)
+kmscube_CFLAGS += $(GST_CFLAGS)
+kmscube_SOURCES += cube-video.c gst-decoder.c
+endif
diff --git a/common.h b/common.h
index df07b61..ba7bcd1 100644
--- a/common.h
+++ b/common.h
@@ -24,6 +24,10 @@
 #ifndef _COMMON_H
 #define _COMMON_H
 
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
 #include <GLES2/gl2.h>
 #include <GLES2/gl2ext.h>
 #include <EGL/egl.h>
@@ -85,9 +89,25 @@ enum mode {
        RGBA,          /* single-plane RGBA */
        NV12_2IMG,     /* NV12, handled as two textures and converted to RGB in 
shader */
        NV12_1IMG,     /* NV12, imported as planar YUV eglimg */
+       VIDEO,         /* video textured cube */
 };
 
 const struct egl * init_cube_smooth(const struct gbm *gbm);
 const struct egl * init_cube_tex(const struct gbm *gbm, enum mode mode);
+#ifdef HAVE_GST
+struct decoder;
+struct decoder * video_init(const struct egl *egl, const struct gbm *gbm, 
const char *filename);
+EGLImage video_frame(struct decoder *dec);
+void video_deinit(struct decoder *dec);
+const struct egl * init_cube_video(const struct gbm *gbm, const char *video);
+#else
+static inline const struct egl *
+init_cube_video(const struct gbm *gbm, const char *video)
+{
+       (void)gbm; (void)video;
+       printf("no GStreamer support!\n");
+       return NULL;
+}
+#endif
 
 #endif /* _COMMON_H */
diff --git a/configure.ac b/configure.ac
index 4df788c..242164f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -40,5 +40,14 @@ PKG_CHECK_MODULES(GBM, gbm)
 PKG_CHECK_MODULES(EGL, egl)
 PKG_CHECK_MODULES(GLES2, glesv2)
 
+# Check for gst and enable cube-video conditionally:
+PKG_CHECK_MODULES(GST, gstreamer-1.0 gstreamer-plugins-base-1.0 
gstreamer-app-1.0 gstreamer-allocators-1.0 gstreamer-video-1.0 glib-2.0,
+                [HAVE_GST=yes], [HAVE_GST=no])
+if test "x$HAVE_GST" = "xyes"; then
+       AC_DEFINE(HAVE_GST, 1, [Have GStreamer support])
+       AC_MSG_NOTICE([Building cube-video support])
+fi
+AM_CONDITIONAL(ENABLE_GST, [test "x$HAVE_GST" = "xyes"])
+
 AC_CONFIG_FILES([Makefile])
 AC_OUTPUT
diff --git a/cube-tex.c b/cube-tex.c
index 0caeaea..0c3c8e3 100644
--- a/cube-tex.c
+++ b/cube-tex.c
@@ -46,7 +46,7 @@ struct {
        GLuint tex[2];
 } gl;
 
-const struct egl *egl = &gl.egl;
+static const struct egl *egl = &gl.egl;
 
 static const GLfloat vVertices[] = {
                // front
@@ -81,7 +81,7 @@ static const GLfloat vVertices[] = {
                +1.0f, -1.0f, +1.0f,
 };
 
-GLfloat vTexCoords[] = {
+static GLfloat vTexCoords[] = {
                //front
                1.0f, 1.0f,
                0.0f, 1.0f,
@@ -443,6 +443,7 @@ static int init_tex(enum mode mode)
        case NV12_1IMG:
                return init_tex_nv12_1img();
        case SMOOTH:
+       case VIDEO:
                assert(!"unreachable");
                return -1;
        }
diff --git a/cube-video.c b/cube-video.c
new file mode 100644
index 0000000..cbea8ec
--- /dev/null
+++ b/cube-video.c
@@ -0,0 +1,386 @@
+/*
+ * Copyright (c) 2017 Rob Clark <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sub license,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial portions
+ * of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#define _GNU_SOURCE
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "common.h"
+#include "esUtil.h"
+
+struct {
+       struct egl egl;
+
+       GLfloat aspect;
+       const struct gbm *gbm;
+
+       GLuint program, blit_program;
+       /* uniform handles: */
+       GLint modelviewmatrix, modelviewprojectionmatrix, normalmatrix;
+       GLint texture, blit_texture;
+       GLuint vbo;
+       GLuint positionsoffset, texcoordsoffset, normalsoffset;
+       GLuint tex;
+
+       /* video decoder: */
+       struct decoder *decoder;
+       int filenames_count, idx;
+       const char *filenames[32];
+} gl;
+
+static const struct egl *egl = &gl.egl;
+
+static const GLfloat vVertices[] = {
+               // front
+               -1.0f, -1.0f, +1.0f,
+               +1.0f, -1.0f, +1.0f,
+               -1.0f, +1.0f, +1.0f,
+               +1.0f, +1.0f, +1.0f,
+               // back
+               +1.0f, -1.0f, -1.0f,
+               -1.0f, -1.0f, -1.0f,
+               +1.0f, +1.0f, -1.0f,
+               -1.0f, +1.0f, -1.0f,
+               // right
+               +1.0f, -1.0f, +1.0f,
+               +1.0f, -1.0f, -1.0f,
+               +1.0f, +1.0f, +1.0f,
+               +1.0f, +1.0f, -1.0f,
+               // left
+               -1.0f, -1.0f, -1.0f,
+               -1.0f, -1.0f, +1.0f,
+               -1.0f, +1.0f, -1.0f,
+               -1.0f, +1.0f, +1.0f,
+               // top
+               -1.0f, +1.0f, +1.0f,
+               +1.0f, +1.0f, +1.0f,
+               -1.0f, +1.0f, -1.0f,
+               +1.0f, +1.0f, -1.0f,
+               // bottom
+               -1.0f, -1.0f, -1.0f,
+               +1.0f, -1.0f, -1.0f,
+               -1.0f, -1.0f, +1.0f,
+               +1.0f, -1.0f, +1.0f,
+};
+
+static GLfloat vTexCoords[] = {
+               //front
+               0.0f, 1.0f,
+               1.0f, 1.0f,
+               0.0f, 0.0f,
+               1.0f, 0.0f,
+               //back
+               0.0f, 1.0f,
+               1.0f, 1.0f,
+               0.0f, 0.0f,
+               1.0f, 0.0f,
+               //right
+               0.0f, 1.0f,
+               1.0f, 1.0f,
+               0.0f, 0.0f,
+               1.0f, 0.0f,
+               //left
+               0.0f, 1.0f,
+               1.0f, 1.0f,
+               0.0f, 0.0f,
+               1.0f, 0.0f,
+               //top
+               0.0f, 1.0f,
+               1.0f, 1.0f,
+               0.0f, 0.0f,
+               1.0f, 0.0f,
+               //bottom
+               0.0f, 1.0f,
+               1.0f, 1.0f,
+               0.0f, 0.0f,
+               1.0f, 0.0f,
+};
+
+static const GLfloat vNormals[] = {
+               // front
+               +0.0f, +0.0f, +1.0f, // forward
+               +0.0f, +0.0f, +1.0f, // forward
+               +0.0f, +0.0f, +1.0f, // forward
+               +0.0f, +0.0f, +1.0f, // forward
+               // back
+               +0.0f, +0.0f, -1.0f, // backward
+               +0.0f, +0.0f, -1.0f, // backward
+               +0.0f, +0.0f, -1.0f, // backward
+               +0.0f, +0.0f, -1.0f, // backward
+               // right
+               +1.0f, +0.0f, +0.0f, // right
+               +1.0f, +0.0f, +0.0f, // right
+               +1.0f, +0.0f, +0.0f, // right
+               +1.0f, +0.0f, +0.0f, // right
+               // left
+               -1.0f, +0.0f, +0.0f, // left
+               -1.0f, +0.0f, +0.0f, // left
+               -1.0f, +0.0f, +0.0f, // left
+               -1.0f, +0.0f, +0.0f, // left
+               // top
+               +0.0f, +1.0f, +0.0f, // up
+               +0.0f, +1.0f, +0.0f, // up
+               +0.0f, +1.0f, +0.0f, // up
+               +0.0f, +1.0f, +0.0f, // up
+               // bottom
+               +0.0f, -1.0f, +0.0f, // down
+               +0.0f, -1.0f, +0.0f, // down
+               +0.0f, -1.0f, +0.0f, // down
+               +0.0f, -1.0f, +0.0f  // down
+};
+
+static const char *blit_vs =
+               "attribute vec4 in_position;        \n"
+               "attribute vec2 in_TexCoord;        \n"
+               "                                   \n"
+               "varying vec2 vTexCoord;            \n"
+               "                                   \n"
+               "void main()                        \n"
+               "{                                  \n"
+               "    gl_Position = in_position;     \n"
+               "    vTexCoord = in_TexCoord;       \n"
+               "}                                  \n";
+
+static const char *blit_fs =
+               "#extension GL_OES_EGL_image_external : enable\n"
+               "precision mediump float;           \n"
+               "                                   \n"
+               "uniform samplerExternalOES uTex;   \n"
+               "                                   \n"
+               "varying vec2 vTexCoord;            \n"
+               "                                   \n"
+               "void main()                        \n"
+               "{                                  \n"
+               "    gl_FragColor = texture2D(uTex, vTexCoord);\n"
+               "}                                  \n";
+
+static const char *vertex_shader_source =
+               "uniform mat4 modelviewMatrix;      \n"
+               "uniform mat4 modelviewprojectionMatrix;\n"
+               "uniform mat3 normalMatrix;         \n"
+               "                                   \n"
+               "attribute vec4 in_position;        \n"
+               "attribute vec2 in_TexCoord;        \n"
+               "attribute vec3 in_normal;          \n"
+               "                                   \n"
+               "vec4 lightSource = vec4(2.0, 2.0, 20.0, 0.0);\n"
+               "                                   \n"
+               "varying vec4 vVaryingColor;        \n"
+               "varying vec2 vTexCoord;            \n"
+               "                                   \n"
+               "void main()                        \n"
+               "{                                  \n"
+               "    gl_Position = modelviewprojectionMatrix * in_position;\n"
+               "    vec3 vEyeNormal = normalMatrix * in_normal;\n"
+               "    vec4 vPosition4 = modelviewMatrix * in_position;\n"
+               "    vec3 vPosition3 = vPosition4.xyz / vPosition4.w;\n"
+               "    vec3 vLightDir = normalize(lightSource.xyz - 
vPosition3);\n"
+               "    float diff = max(0.0, dot(vEyeNormal, vLightDir));\n"
+               "    vVaryingColor = vec4(diff * vec3(1.0, 1.0, 1.0), 1.0);\n"
+               "    vTexCoord = in_TexCoord; \n"
+               "}                            \n";
+
+static const char *fragment_shader_source =
+               "#extension GL_OES_EGL_image_external : enable\n"
+               "precision mediump float;           \n"
+               "                                   \n"
+               "uniform samplerExternalOES uTex;   \n"
+               "                                   \n"
+               "varying vec4 vVaryingColor;        \n"
+               "varying vec2 vTexCoord;            \n"
+               "                                   \n"
+               "void main()                        \n"
+               "{                                  \n"
+               "    gl_FragColor = vVaryingColor * texture2D(uTex, 
vTexCoord);\n"
+               "}                                  \n";
+
+
+static void draw_cube_video(unsigned i)
+{
+       ESMatrix modelview;
+       EGLImage frame;
+
+       frame = video_frame(gl.decoder);
+       if (!frame) {
+               /* end of stream */
+               glDeleteTextures(1, &gl.tex);
+               glGenTextures(1, &gl.tex);
+               video_deinit(gl.decoder);
+               gl.idx = (gl.idx + 1) % gl.filenames_count;
+               gl.decoder = video_init(&gl.egl, gl.gbm, gl.filenames[gl.idx]);
+       }
+
+       glUseProgram(gl.blit_program);
+
+       glActiveTexture(GL_TEXTURE0);
+       glBindTexture(GL_TEXTURE_EXTERNAL_OES, gl.tex);
+       glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, 
GL_LINEAR);
+       glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, 
GL_LINEAR);
+       glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, 
GL_CLAMP_TO_EDGE);
+       glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, 
GL_CLAMP_TO_EDGE);
+       egl->glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, frame);
+
+       /* clear the color buffer */
+       glClearColor(0.5, 0.5, 0.5, 1.0);
+       glClear(GL_COLOR_BUFFER_BIT);
+
+       glUseProgram(gl.blit_program);
+       glUniform1i(gl.blit_texture, 0); /* '0' refers to texture unit 0. */
+       glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+
+       glUseProgram(gl.program);
+
+       esMatrixLoadIdentity(&modelview);
+       esTranslate(&modelview, 0.0f, 0.0f, -8.0f);
+       esRotate(&modelview, 45.0f + (0.25f * i), 1.0f, 0.0f, 0.0f);
+       esRotate(&modelview, 45.0f - (0.5f * i), 0.0f, 1.0f, 0.0f);
+       esRotate(&modelview, 10.0f + (0.15f * i), 0.0f, 0.0f, 1.0f);
+
+       ESMatrix projection;
+       esMatrixLoadIdentity(&projection);
+       esFrustum(&projection, -2.1f, +2.1f, -2.1f * gl.aspect, +2.1f * 
gl.aspect, 6.0f, 10.0f);
+
+       ESMatrix modelviewprojection;
+       esMatrixLoadIdentity(&modelviewprojection);
+       esMatrixMultiply(&modelviewprojection, &modelview, &projection);
+
+       float normal[9];
+       normal[0] = modelview.m[0][0];
+       normal[1] = modelview.m[0][1];
+       normal[2] = modelview.m[0][2];
+       normal[3] = modelview.m[1][0];
+       normal[4] = modelview.m[1][1];
+       normal[5] = modelview.m[1][2];
+       normal[6] = modelview.m[2][0];
+       normal[7] = modelview.m[2][1];
+       normal[8] = modelview.m[2][2];
+
+       glUniformMatrix4fv(gl.modelviewmatrix, 1, GL_FALSE, &modelview.m[0][0]);
+       glUniformMatrix4fv(gl.modelviewprojectionmatrix, 1, GL_FALSE, 
&modelviewprojection.m[0][0]);
+       glUniformMatrix3fv(gl.normalmatrix, 1, GL_FALSE, normal);
+       glUniform1i(gl.texture, 0); /* '0' refers to texture unit 0. */
+
+       glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+       glDrawArrays(GL_TRIANGLE_STRIP, 4, 4);
+       glDrawArrays(GL_TRIANGLE_STRIP, 8, 4);
+       glDrawArrays(GL_TRIANGLE_STRIP, 12, 4);
+       glDrawArrays(GL_TRIANGLE_STRIP, 16, 4);
+       glDrawArrays(GL_TRIANGLE_STRIP, 20, 4);
+}
+
+const struct egl * init_cube_video(const struct gbm *gbm, const char 
*filenames)
+{
+       char *fnames, *s;
+       int ret, i = 0;
+
+       ret = init_egl(&gl.egl, gbm);
+       if (ret)
+               return NULL;
+
+       if (!gl.egl.eglCreateImageKHR) {
+               printf("no eglCreateImageKHR\n");
+               return NULL;
+       }
+
+       fnames = strdup(filenames);
+       while ((s = strstr(fnames, ","))) {
+               gl.filenames[i] = fnames;
+               s[0] = '\0';
+               fnames = &s[1];
+               i++;
+       }
+       gl.filenames[i] = fnames;
+       gl.filenames_count = ++i;
+
+       gl.decoder = video_init(&gl.egl, gbm, gl.filenames[gl.idx]);
+       if (!gl.decoder) {
+               printf("cannot create video decoder\n");
+               return NULL;
+       }
+
+       gl.aspect = (GLfloat)(gbm->height) / (GLfloat)(gbm->width);
+       gl.gbm = gbm;
+
+       ret = create_program(blit_vs, blit_fs);
+       if (ret < 0)
+               return NULL;
+
+       gl.blit_program = ret;
+
+       glBindAttribLocation(gl.blit_program, 0, "in_position");
+       glBindAttribLocation(gl.blit_program, 1, "in_TexCoord");
+
+       ret = link_program(gl.blit_program);
+       if (ret)
+               return NULL;
+
+       gl.blit_texture = glGetUniformLocation(gl.blit_program, "uTex");
+
+       ret = create_program(vertex_shader_source, fragment_shader_source);
+       if (ret < 0)
+               return NULL;
+
+       gl.program = ret;
+
+       glBindAttribLocation(gl.program, 0, "in_position");
+       glBindAttribLocation(gl.program, 1, "in_TexCoord");
+       glBindAttribLocation(gl.program, 2, "in_normal");
+
+       ret = link_program(gl.program);
+       if (ret)
+               return NULL;
+
+       gl.modelviewmatrix = glGetUniformLocation(gl.program, 
"modelviewMatrix");
+       gl.modelviewprojectionmatrix = glGetUniformLocation(gl.program, 
"modelviewprojectionMatrix");
+       gl.normalmatrix = glGetUniformLocation(gl.program, "normalMatrix");
+       gl.texture   = glGetUniformLocation(gl.program, "uTex");
+
+       glViewport(0, 0, gbm->width, gbm->height);
+       glEnable(GL_CULL_FACE);
+
+       gl.positionsoffset = 0;
+       gl.texcoordsoffset = sizeof(vVertices);
+       gl.normalsoffset = sizeof(vVertices) + sizeof(vTexCoords);
+
+       glGenBuffers(1, &gl.vbo);
+       glBindBuffer(GL_ARRAY_BUFFER, gl.vbo);
+       glBufferData(GL_ARRAY_BUFFER, sizeof(vVertices) + sizeof(vTexCoords) + 
sizeof(vNormals), 0, GL_STATIC_DRAW);
+       glBufferSubData(GL_ARRAY_BUFFER, gl.positionsoffset, sizeof(vVertices), 
&vVertices[0]);
+       glBufferSubData(GL_ARRAY_BUFFER, gl.texcoordsoffset, 
sizeof(vTexCoords), &vTexCoords[0]);
+       glBufferSubData(GL_ARRAY_BUFFER, gl.normalsoffset, sizeof(vNormals), 
&vNormals[0]);
+       glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (const GLvoid 
*)(intptr_t)gl.positionsoffset);
+       glEnableVertexAttribArray(0);
+       glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, (const GLvoid 
*)(intptr_t)gl.texcoordsoffset);
+       glEnableVertexAttribArray(1);
+       glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 0, (const GLvoid 
*)(intptr_t)gl.normalsoffset);
+       glEnableVertexAttribArray(2);
+
+       glGenTextures(1, &gl.tex);
+
+       gl.egl.draw = draw_cube_video;
+
+       return &gl.egl;
+}
diff --git a/gst-decoder.c b/gst-decoder.c
new file mode 100644
index 0000000..1140213
--- /dev/null
+++ b/gst-decoder.c
@@ -0,0 +1,340 @@
+/*
+ * Copyright (c) 2017 Rob Clark <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sub license,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial portions
+ * of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <assert.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "common.h"
+
+#include <drm_fourcc.h>
+
+#include <gst/gst.h>
+#include <gst/gstmemory.h>
+#include <gst/gstpad.h>
+#include <gst/allocators/gstdmabuf.h>
+#include <gst/app/gstappsink.h>
+#include <gst/video/gstvideometa.h>
+
+struct decoder {
+       GMainLoop          *loop;
+       GstElement         *pipeline;
+       GstElement         *sink;
+       pthread_t           gst_thread;
+
+       uint32_t            format;
+       GstVideoInfo        info;
+
+       const struct gbm   *gbm;
+       const struct egl   *egl;
+       unsigned            frame;
+
+       EGLImage            last_frame;
+       GstSample          *last_samp;
+};
+
+static GstPadProbeReturn
+pad_probe(GstPad *pad, GstPadProbeInfo *info, gpointer user_data)
+{
+       struct decoder *dec = user_data;
+       GstQuery *query = GST_PAD_PROBE_INFO_QUERY(info);
+       gboolean need_pool;
+       GstCaps *caps;
+
+       (void)pad;
+
+       if (GST_QUERY_TYPE(query) != GST_QUERY_ALLOCATION)
+               return GST_PAD_PROBE_OK;
+
+       gst_query_parse_allocation(query, &caps, &need_pool);
+
+       if (!caps) {
+               GST_ERROR("allocation query without caps");
+               return GST_PAD_PROBE_OK;
+       }
+
+       if (!gst_video_info_from_caps(&dec->info, caps)) {
+               GST_ERROR("allocation query with invalid caps");
+               return GST_PAD_PROBE_OK;
+       }
+
+       switch (dec->info.finfo->format) {
+       case GST_VIDEO_FORMAT_I420:
+               dec->format = DRM_FORMAT_YUV420;
+               break;
+       case GST_VIDEO_FORMAT_NV12:
+               dec->format = DRM_FORMAT_NV12;
+               break;
+       default:
+               GST_ERROR("unknown format\n");
+               return GST_PAD_PROBE_OK;
+       }
+
+       GST_DEBUG("got: %ux%u@%4.4s\n", dec->info.width, dec->info.height,
+                       (char *)&dec->format);
+
+       return GST_PAD_PROBE_OK;
+}
+
+static void *
+gst_thread_func(void *args)
+{
+       struct decoder *dec = args;
+       g_main_loop_run(dec->loop);
+       return NULL;
+}
+
+static void
+element_added_cb(GstBin *bin, GstElement *element, gpointer user_data)
+{
+       (void)user_data;
+       (void)bin;
+
+       printf("added: %s\n", GST_OBJECT_NAME(element));
+
+       // XXX is there a better way to do this, like match class name?
+       if (strstr(GST_OBJECT_NAME(element), "v4l2video0dec") == 
GST_OBJECT_NAME(element)) {
+               /* yes, "capture" rather than "output" because v4l2 is bonkers 
*/
+               gst_util_set_object_arg(G_OBJECT(element), "capture-io-mode", 
"dmabuf");
+       }
+}
+
+struct decoder *
+video_init(const struct egl *egl, const struct gbm *gbm, const char *filename)
+{
+       struct decoder *dec;
+       GstElement *src, *decodebin;
+
+       dec = calloc(1, sizeof(*dec));
+       dec->loop = g_main_loop_new(NULL, FALSE);
+       dec->gbm = gbm;
+       dec->egl = egl;
+
+       /* Setup pipeline: */
+       static const char *pipeline =
+               "filesrc name=\"src\" ! decodebin name=\"decode\" ! video/x-raw 
! appsink sync=false name=\"sink\"";
+       dec->pipeline = gst_parse_launch(pipeline, NULL);
+
+       dec->sink = gst_bin_get_by_name(GST_BIN(dec->pipeline), "sink");
+
+       src = gst_bin_get_by_name(GST_BIN(dec->pipeline), "src");
+       g_object_set(G_OBJECT(src), "location", filename, NULL);
+       gst_object_unref(src);
+
+       /* if we don't limit max-buffers then we can let the decoder outrun
+        * vsync and quickly chew up 100's of MB of buffers:
+        */
+       g_object_set(G_OBJECT(dec->sink), "max-buffers", 2, NULL);
+
+       gst_pad_add_probe(gst_element_get_static_pad(dec->sink, "sink"),
+                       GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM,
+                       pad_probe, dec, NULL);
+
+       /* hack to make sure we get dmabuf's from v4l2video0dec.. */
+       decodebin = gst_bin_get_by_name(GST_BIN(dec->pipeline), "decode");
+       g_signal_connect(decodebin, "element-added", 
G_CALLBACK(element_added_cb), dec);
+
+       /* let 'er rip! */
+       gst_element_set_state(dec->pipeline, GST_STATE_PLAYING);
+
+       pthread_create(&dec->gst_thread, NULL, gst_thread_func, dec);
+
+       return dec;
+}
+
+static void
+set_last_frame(struct decoder *dec, EGLImage frame, GstSample *samp)
+{
+       if (dec->last_frame)
+               dec->egl->eglDestroyImageKHR(dec->egl->display, 
dec->last_frame);
+       dec->last_frame = frame;
+       if (dec->last_samp)
+               gst_sample_unref(dec->last_samp);
+       dec->last_samp = samp;
+}
+
+// TODO this could probably be a helper re-used by cube-tex:
+static int
+buf_to_fd(const struct gbm *gbm, int size, void *ptr)
+{
+       struct gbm_bo *bo;
+       void *map, *map_data = NULL;
+       uint32_t stride;
+       int fd;
+
+       /* NOTE: do not actually use GBM_BO_USE_WRITE since that gets us a dumb 
buffer: */
+       bo = gbm_bo_create(gbm->dev, size, 1, GBM_FORMAT_R8, GBM_BO_USE_LINEAR);
+
+       map = gbm_bo_map(bo, 0, 0, size, 1, GBM_BO_TRANSFER_WRITE, &stride, 
&map_data);
+
+       memcpy(map, ptr, size);
+
+       gbm_bo_unmap(bo, map_data);
+
+       fd = gbm_bo_get_fd(bo);
+
+       /* we have the fd now, no longer need the bo: */
+       gbm_bo_destroy(bo);
+
+       return fd;
+}
+
+static EGLImage
+buffer_to_image(struct decoder *dec, GstBuffer *buf)
+{
+       struct { int fd, offset, stride; } planes[3];
+       GstVideoMeta *meta = gst_buffer_get_video_meta(buf);
+       EGLImage image;
+       unsigned nmems = gst_buffer_n_memory(buf);
+       unsigned nplanes = (dec->format == DRM_FORMAT_YUV420) ? 3 : 2;
+       unsigned i;
+
+       if (nmems == nplanes) {
+               // XXX TODO..
+       } else if (nmems == 1) {
+               GstMemory *mem = gst_buffer_peek_memory(buf, 0);
+               int fd;
+
+               if (dec->frame == 0) {
+                       printf("%s zero-copy\n", gst_is_dmabuf_memory(mem) ? 
"using" : "not");
+               }
+
+               if (gst_is_dmabuf_memory(mem)) {
+                       fd = dup(gst_dmabuf_memory_get_fd(mem));
+               } else {
+                       GstMapInfo info;
+                       gst_memory_map(mem, &info, GST_MAP_READ);
+                       fd = buf_to_fd(dec->gbm, info.size, info.data);
+                       gst_memory_unmap(mem, &info);
+               }
+
+               // XXX why don't we get meta??
+               if (meta) {
+                       for (i = 0; i < nplanes; i++) {
+                               planes[i].fd = fd;
+                               planes[i].offset = meta->offset[i];
+                               planes[i].stride = meta->stride[i];
+                       }
+               } else {
+                       int offset = 0, stride = dec->info.width, height = 
dec->info.height;
+
+                       for (i = 0; i < nplanes; i++) {
+
+                               if (i == 1) {
+                                       height /= 2;
+                                       if (nplanes == 3)
+                                               stride /= 2;
+                               }
+
+                               planes[i].fd = fd;
+                               planes[i].offset = offset;
+                               planes[i].stride = stride;
+
+                               offset += stride * height;
+                       }
+               }
+       }
+
+       if (dec->format == DRM_FORMAT_NV12) {
+               const EGLint attr[] = {
+                       EGL_WIDTH, dec->info.width,
+                       EGL_HEIGHT, dec->info.height,
+                       EGL_LINUX_DRM_FOURCC_EXT, dec->format,
+                       EGL_DMA_BUF_PLANE0_FD_EXT, planes[0].fd,
+                       EGL_DMA_BUF_PLANE0_OFFSET_EXT, planes[0].offset,
+                       EGL_DMA_BUF_PLANE0_PITCH_EXT, planes[0].stride,
+                       EGL_DMA_BUF_PLANE1_FD_EXT, planes[1].fd,
+                       EGL_DMA_BUF_PLANE1_OFFSET_EXT, planes[1].offset,
+                       EGL_DMA_BUF_PLANE1_PITCH_EXT, planes[1].stride,
+                       EGL_NONE
+               };
+
+               image = dec->egl->eglCreateImageKHR(dec->egl->display, 
EGL_NO_CONTEXT,
+                               EGL_LINUX_DMA_BUF_EXT, NULL, attr);
+       } else {
+               const EGLint attr[] = {
+                       EGL_WIDTH, dec->info.width,
+                       EGL_HEIGHT, dec->info.height,
+                       EGL_LINUX_DRM_FOURCC_EXT, dec->format,
+                       EGL_DMA_BUF_PLANE0_FD_EXT, planes[0].fd,
+                       EGL_DMA_BUF_PLANE0_OFFSET_EXT, planes[0].offset,
+                       EGL_DMA_BUF_PLANE0_PITCH_EXT, planes[0].stride,
+                       EGL_DMA_BUF_PLANE1_FD_EXT, planes[1].fd,
+                       EGL_DMA_BUF_PLANE1_OFFSET_EXT, planes[1].offset,
+                       EGL_DMA_BUF_PLANE1_PITCH_EXT, planes[1].stride,
+                       EGL_DMA_BUF_PLANE2_FD_EXT, planes[2].fd,
+                       EGL_DMA_BUF_PLANE2_OFFSET_EXT, planes[2].offset,
+                       EGL_DMA_BUF_PLANE2_PITCH_EXT, planes[2].stride,
+                       EGL_NONE
+               };
+
+               image = dec->egl->eglCreateImageKHR(dec->egl->display, 
EGL_NO_CONTEXT,
+                               EGL_LINUX_DMA_BUF_EXT, NULL, attr);
+       }
+
+       for (unsigned i = 0; i < nmems; i++)
+               close(planes[i].fd);
+
+       return image;
+}
+
+EGLImage
+video_frame(struct decoder *dec)
+{
+       GstSample *samp;
+       GstBuffer *buf;
+       EGLImage   frame = NULL;
+
+       samp = gst_app_sink_pull_sample(GST_APP_SINK(dec->sink));
+       if (!samp)
+               return NULL;
+
+       buf = gst_sample_get_buffer(samp);
+
+       // TODO inline buffer_to_image??
+       frame = buffer_to_image(dec, buf);
+
+       // TODO in the zero-copy dmabuf case it would be nice to associate
+       // the eglimg w/ the buffer to avoid recreating it every frame..
+
+       set_last_frame(dec, frame, samp);
+
+       dec->frame++;
+
+       return frame;
+}
+
+void video_deinit(struct decoder *dec)
+{
+       set_last_frame(dec, NULL, NULL);
+       gst_element_set_state(dec->pipeline, GST_STATE_NULL);
+       gst_object_unref(dec->sink);
+       gst_object_unref(dec->pipeline);
+       g_main_loop_quit(dec->loop);
+       g_main_loop_unref(dec->loop);
+       pthread_join(dec->gst_thread, 0);
+       free(dec);
+}
diff --git a/kmscube.c b/kmscube.c
index fcfd902..3140574 100644
--- a/kmscube.c
+++ b/kmscube.c
@@ -31,6 +31,9 @@
 #include "common.h"
 #include "drm-common.h"
 
+#ifdef HAVE_GST
+#  include <gst/gst.h>
+#endif
 
 #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
 
@@ -38,12 +41,13 @@ static const struct egl *egl;
 static const struct gbm *gbm;
 static const struct drm *drm;
 
-static const char *shortopts = "AD:M:";
+static const char *shortopts = "AD:M:V:";
 
 static const struct option longopts[] = {
        {"atomic", no_argument,       0, 'M'},
        {"device", required_argument, 0, 'D'},
        {"mode",   required_argument, 0, 'M'},
+       {"video",  required_argument, 0, 'V'},
        {0, 0, 0, 0}
 };
 
@@ -58,17 +62,23 @@ static void usage(const char *name)
                        "        smooth    -  smooth shaded cube (default)\n"
                        "        rgba      -  rgba textured cube\n"
                        "        nv12-2img -  yuv textured (color conversion in 
shader)\n"
-                       "        nv12-1img -  yuv textured (single nv12 
texture)\n",
+                       "        nv12-1img -  yuv textured (single nv12 
texture)\n"
+                       "    -V, --video=FILE         video textured cube\n",
                        name);
 }
 
 int main(int argc, char *argv[])
 {
        const char *device = "/dev/dri/card0";
+       const char *video = NULL;
        enum mode mode = SMOOTH;
        int atomic = 0;
        int opt;
 
+#ifdef HAVE_GST
+       gst_init(&argc, &argv);
+#endif
+
        while ((opt = getopt_long_only(argc, argv, shortopts, longopts, NULL)) 
!= -1) {
                switch (opt) {
                case 'A':
@@ -92,6 +102,10 @@ int main(int argc, char *argv[])
                                return -1;
                        }
                        break;
+               case 'V':
+                       mode = VIDEO;
+                       video = optarg;
+                       break;
                default:
                        usage(argv[0]);
                        return -1;
@@ -115,6 +129,8 @@ int main(int argc, char *argv[])
 
        if (mode == SMOOTH)
                egl = init_cube_smooth(gbm);
+       else if (mode == VIDEO)
+               egl = init_cube_video(gbm, video);
        else
                egl = init_cube_tex(gbm, mode);
 
-- 
2.9.3

_______________________________________________
mesa-dev mailing list
[email protected]
https://lists.freedesktop.org/mailman/listinfo/mesa-dev

Reply via email to