1 /*
2  * GStreamer
3  * Copyright (C) 2008 Julien Isorce <julien.isorce@gmail.com>
4  * Inspired from http://www.mdk.org.pl/2007/11/17/gl-colorspace-conversions
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21 
22 /**
23  * SECTION:element-glfilterglass
24  * @title: glfilterglass
25  *
26  * Map textures on moving glass.
27  *
28  * ## Examples
29  * |[
30  * gst-launch-1.0 -v videotestsrc ! glfilterglass ! glimagesink
31  * ]| A pipeline inspired from http://www.mdk.org.pl/2007/11/17/gl-colorspace-conversions
32  * FBO is required.
33  * |[
34  * gst-launch-1.0 -v videotestsrc ! glfilterglass ! video/x-raw, width=640, height=480 ! glimagesink
35  * ]| The scene is greater than the input size.
36  *
37  */
38 
39 #ifdef HAVE_CONFIG_H
40 #include "config.h"
41 #endif
42 
43 #include <math.h>
44 #include <gst/gl/gstglfuncs.h>
45 
46 #include "gstglfilterglass.h"
47 
48 #include "gstglutils.h"
49 
50 #define GST_CAT_DEFAULT gst_gl_filter_glass_debug
51 GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
52 
53 enum
54 {
55   PROP_0
56 };
57 
58 #define DEBUG_INIT \
59   GST_DEBUG_CATEGORY_INIT (gst_gl_filter_glass_debug, "glfilterglass", 0, "glfilterglass element");
60 #define gst_gl_filter_glass_parent_class parent_class
61 G_DEFINE_TYPE_WITH_CODE (GstGLFilterGlass, gst_gl_filter_glass,
62     GST_TYPE_GL_FILTER, DEBUG_INIT);
63 
64 static void gst_gl_filter_glass_set_property (GObject * object, guint prop_id,
65     const GValue * value, GParamSpec * pspec);
66 static void gst_gl_filter_glass_get_property (GObject * object, guint prop_id,
67     GValue * value, GParamSpec * pspec);
68 
69 static gboolean gst_gl_filter_glass_reset (GstBaseTransform * trans);
70 
71 static gboolean gst_gl_filter_glass_init_shader (GstGLFilter * filter);
72 static gboolean gst_gl_filter_glass_filter_texture (GstGLFilter * filter,
73     GstGLMemory * in_tex, GstGLMemory * out_tex);
74 
75 static void gst_gl_filter_glass_draw_background_gradient ();
76 static void gst_gl_filter_glass_draw_video_plane (GstGLFilter * filter,
77     gint width, gint height, guint texture, gfloat center_x, gfloat center_y,
78     gfloat start_alpha, gfloat stop_alpha, gboolean reversed, gfloat rotation);
79 
80 static gboolean gst_gl_filter_glass_callback (gpointer stuff);
81 
82 /* *INDENT-OFF* */
83 static const gchar *glass_fragment_source =
84     "uniform sampler2D tex;\n"
85     "varying float alpha;\n"
86     "void main () {\n"
87     "  float p = 0.0525;\n"
88     "  float L1 = p*1.0;\n"
89     "  float L2 = 1.0 - L1;\n"
90     "  float L3 = 1.0 - L1;\n"
91     "  float w = 1.0;\n"
92     "  float r = L1;\n"
93     "  if (gl_TexCoord[0].x < L1 && gl_TexCoord[0].y < L1)\n"
94     "      r = sqrt( (gl_TexCoord[0].x - L1) * (gl_TexCoord[0].x - L1) + (gl_TexCoord[0].y - L1) * (gl_TexCoord[0].y - L1) );\n"
95     "  else if (gl_TexCoord[0].x > L2 && gl_TexCoord[0].y < L1)\n"
96     "      r = sqrt( (gl_TexCoord[0].x - L2) * (gl_TexCoord[0].x - L2) + (gl_TexCoord[0].y - L1) * (gl_TexCoord[0].y - L1) );\n"
97     "  else if (gl_TexCoord[0].x > L2 && gl_TexCoord[0].y > L3)\n"
98     "      r = sqrt( (gl_TexCoord[0].x - L2) * (gl_TexCoord[0].x - L2) + (gl_TexCoord[0].y - L3) * (gl_TexCoord[0].y - L3) );\n"
99     "  else if (gl_TexCoord[0].x < L1 && gl_TexCoord[0].y > L3)\n"
100     "      r = sqrt( (gl_TexCoord[0].x - L1) * (gl_TexCoord[0].x - L1) + (gl_TexCoord[0].y - L3) * (gl_TexCoord[0].y - L3) );\n"
101     "  if (r > L1)\n"
102     "      w = 0.0;\n"
103     "  vec4 color = texture2D (tex, gl_TexCoord[0].st);\n"
104     "  gl_FragColor = vec4(color.rgb, alpha * w);\n"
105     "}\n";
106 
107 static const gchar *glass_vertex_source =
108     "uniform float yrot;\n"
109     "uniform float aspect;\n"
110     "const float fovy = 80.0;\n"
111     "const float znear = 1.0;\n"
112     "const float zfar = 5000.0;\n"
113     "varying float alpha;\n"
114     "void main () {\n"
115     "   float f = 1.0/(tan(radians(fovy/2.0)));\n"
116     "   float rot = radians (yrot);\n"
117     "   // replacement for gluPerspective\n"
118     "   mat4 perspective = mat4 (\n"
119     "            f/aspect, 0.0,  0.0,                      0.0,\n"
120     "            0.0,      f,    0.0,                      0.0,\n"
121     "            0.0,      0.0, (znear+zfar)/(znear-zfar), 2.0*znear*zfar/(znear-zfar),\n"
122     "            0.0,      0.0, -1.0,                      0.0 );\n"
123     "   mat4 trans = mat4 (\n"
124     "            1.0, 0.0, 0.0, 0.0,\n"
125     "            0.0, 1.0, 0.0, 0.0,\n"
126     "            0.0, 0.0, 1.0, -3.0,\n"
127     "            0.0, 0.0, 0.0, 1.0 );\n"
128     "   mat4 rotation = mat4 (\n"
129     "            cos(rot),  0.0, sin(rot), 0.0,\n"
130     "            0.0,       1.0, 0.0,      0.0,\n"
131     "            -sin(rot), 0.0, cos(rot), 0.0,\n"
132     "            0.0,       0.0, 0.0,      1.0 );\n"
133     "  gl_Position = trans * perspective * rotation * gl_ModelViewProjectionMatrix * gl_Vertex;\n"
134     "  gl_TexCoord[0] = gl_MultiTexCoord0;\n"
135     "  alpha = gl_Color.a;\n"
136     "}\n";
137 
138 static const gchar * passthrough_vertex =
139     "void main () {\n"
140     "  gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\n"
141     "  gl_FrontColor = gl_Color;\n"
142     "}\n";
143 
144 static const gchar * passthrough_fragment =
145     "void main () {\n"
146     "  gl_FragColor = gl_Color;\n"
147     "}\n";
148 /* *INDENT-ON* */
149 
150 static void
gst_gl_filter_glass_class_init(GstGLFilterGlassClass * klass)151 gst_gl_filter_glass_class_init (GstGLFilterGlassClass * klass)
152 {
153   GObjectClass *gobject_class;
154   GstElementClass *element_class;
155 
156   gobject_class = (GObjectClass *) klass;
157   element_class = GST_ELEMENT_CLASS (klass);
158 
159   gst_gl_filter_add_rgba_pad_templates (GST_GL_FILTER_CLASS (klass));
160 
161   gobject_class->set_property = gst_gl_filter_glass_set_property;
162   gobject_class->get_property = gst_gl_filter_glass_get_property;
163 
164   gst_element_class_set_metadata (element_class, "OpenGL glass filter",
165       "Filter/Effect/Video", "Glass Filter",
166       "Julien Isorce <julien.isorce@gmail.com>");
167 
168   GST_GL_FILTER_CLASS (klass)->filter_texture =
169       gst_gl_filter_glass_filter_texture;
170   GST_GL_FILTER_CLASS (klass)->init_fbo = gst_gl_filter_glass_init_shader;
171   GST_BASE_TRANSFORM_CLASS (klass)->stop = gst_gl_filter_glass_reset;
172 
173   GST_GL_BASE_FILTER_CLASS (klass)->supported_gl_api = GST_GL_API_OPENGL;
174 }
175 
176 static void
gst_gl_filter_glass_init(GstGLFilterGlass * filter)177 gst_gl_filter_glass_init (GstGLFilterGlass * filter)
178 {
179   filter->shader = NULL;
180   filter->timestamp = 0;
181 }
182 
183 static gboolean
gst_gl_filter_glass_reset(GstBaseTransform * trans)184 gst_gl_filter_glass_reset (GstBaseTransform * trans)
185 {
186   GstGLFilterGlass *glass_filter = GST_GL_FILTER_GLASS (trans);
187 
188   //blocking call, wait the opengl thread has destroyed the shader
189   if (glass_filter->shader)
190     gst_object_unref (glass_filter->shader);
191   glass_filter->shader = NULL;
192   if (glass_filter->passthrough_shader)
193     gst_object_unref (glass_filter->passthrough_shader);
194   glass_filter->passthrough_shader = NULL;
195 
196   glass_filter->start_time = 0;
197 
198   return GST_BASE_TRANSFORM_CLASS (parent_class)->stop (trans);
199 }
200 
201 static void
gst_gl_filter_glass_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)202 gst_gl_filter_glass_set_property (GObject * object, guint prop_id,
203     const GValue * value, GParamSpec * pspec)
204 {
205   //GstGLFilterGlass *filter = GST_GL_FILTER_GLASS (object);
206 
207   switch (prop_id) {
208     default:
209       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
210       break;
211   }
212 }
213 
214 static void
gst_gl_filter_glass_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)215 gst_gl_filter_glass_get_property (GObject * object, guint prop_id,
216     GValue * value, GParamSpec * pspec)
217 {
218   //GstGLFilterGlass *filter = GST_GL_FILTER_GLASS (object);
219 
220   switch (prop_id) {
221     default:
222       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
223       break;
224   }
225 }
226 
227 static gboolean
gst_gl_filter_glass_init_shader(GstGLFilter * filter)228 gst_gl_filter_glass_init_shader (GstGLFilter * filter)
229 {
230   gboolean ret;
231   GstGLFilterGlass *glass_filter = GST_GL_FILTER_GLASS (filter);
232 
233   //blocking call, wait the opengl thread has compiled the shader
234   ret =
235       gst_gl_context_gen_shader (GST_GL_BASE_FILTER (filter)->context,
236       glass_vertex_source, glass_fragment_source, &glass_filter->shader);
237   if (ret)
238     ret =
239         gst_gl_context_gen_shader (GST_GL_BASE_FILTER (filter)->context,
240         passthrough_vertex, passthrough_fragment,
241         &glass_filter->passthrough_shader);
242 
243   return ret;
244 }
245 
246 static gboolean
gst_gl_filter_glass_filter_texture(GstGLFilter * filter,GstGLMemory * in_tex,GstGLMemory * out_tex)247 gst_gl_filter_glass_filter_texture (GstGLFilter * filter, GstGLMemory * in_tex,
248     GstGLMemory * out_tex)
249 {
250   GstGLFilterGlass *glass_filter = GST_GL_FILTER_GLASS (filter);
251 
252   glass_filter->in_tex = in_tex;
253 
254   gst_gl_framebuffer_draw_to_texture (filter->fbo, out_tex,
255       gst_gl_filter_glass_callback, glass_filter);
256 
257   return TRUE;
258 }
259 
260 static gint64
get_time(void)261 get_time (void)
262 {
263   return g_get_real_time ();
264 }
265 
266 static void
gst_gl_filter_glass_draw_background_gradient(GstGLFilterGlass * glass)267 gst_gl_filter_glass_draw_background_gradient (GstGLFilterGlass * glass)
268 {
269   GstGLFilter *filter = GST_GL_FILTER (glass);
270   GstGLFuncs *gl = GST_GL_BASE_FILTER (filter)->context->gl_vtable;
271 
272 /* *INDENT-OFF* */
273   gfloat mesh[] = {
274   /* |       Vertex       |        Color         | */
275       -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f,
276        1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f,
277        1.0f,  0.8f, 0.0f, 0.0f, 0.0f, 0.2f, 1.0f,
278       -1.0f,  0.8f, 0.0f, 0.0f, 0.0f, 0.2f, 1.0f,
279       -1.0f,  1.0f, 0.0f, 0.0f, 0.0f, 0.2f, 1.0f,
280        1.0f,  1.0f, 0.0f, 0.0f, 0.0f, 0.2f, 1.0f,
281   };
282 /* *INDENT-ON* */
283 
284   GLushort indices[] = {
285     0, 1, 2,
286     0, 2, 3,
287     2, 3, 4,
288     2, 4, 5
289   };
290 
291   gl->ClientActiveTexture (GL_TEXTURE0);
292   gl->EnableClientState (GL_VERTEX_ARRAY);
293   gl->EnableClientState (GL_COLOR_ARRAY);
294 
295   gl->VertexPointer (3, GL_FLOAT, 7 * sizeof (gfloat), mesh);
296   gl->ColorPointer (4, GL_FLOAT, 7 * sizeof (gfloat), &mesh[3]);
297 
298   gl->DrawElements (GL_TRIANGLES, 12, GL_UNSIGNED_SHORT, indices);
299 
300   gl->DisableClientState (GL_VERTEX_ARRAY);
301   gl->DisableClientState (GL_COLOR_ARRAY);
302 }
303 
304 static void
gst_gl_filter_glass_draw_video_plane(GstGLFilter * filter,gint width,gint height,guint texture,gfloat center_x,gfloat center_y,gfloat start_alpha,gfloat stop_alpha,gboolean reversed,gfloat rotation)305 gst_gl_filter_glass_draw_video_plane (GstGLFilter * filter,
306     gint width, gint height, guint texture,
307     gfloat center_x, gfloat center_y,
308     gfloat start_alpha, gfloat stop_alpha, gboolean reversed, gfloat rotation)
309 {
310   GstGLFilterGlass *glass_filter = GST_GL_FILTER_GLASS (filter);
311   GstGLFuncs *gl = GST_GL_BASE_FILTER (filter)->context->gl_vtable;
312 
313   gfloat topy = reversed ? center_y - 1.0f : center_y + 1.0f;
314   gfloat bottomy = reversed ? center_y + 1.0f : center_y - 1.0f;
315 
316 /* *INDENT-OFF* */
317   gfloat mesh[] = {
318  /*|           Vertex          |TexCoord0|      Colour               |*/
319     center_x-1.6, topy,    0.0, 0.0, 1.0, 1.0, 1.0, 1.0, start_alpha,
320     center_x+1.6, topy,    0.0, 1.0, 1.0, 1.0, 1.0, 1.0, start_alpha,
321     center_x+1.6, bottomy, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, stop_alpha,
322     center_x-1.6, bottomy, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, stop_alpha,
323   };
324 /* *INDENT-ON* */
325 
326   GLushort indices[] = {
327     0, 1, 2,
328     0, 2, 3
329   };
330 
331   gl->ActiveTexture (GL_TEXTURE0);
332   gl->BindTexture (GL_TEXTURE_2D, texture);
333 
334   gst_gl_shader_set_uniform_1i (glass_filter->shader, "tex", 0);
335   gst_gl_shader_set_uniform_1f (glass_filter->shader, "yrot", rotation);
336   gst_gl_shader_set_uniform_1f (glass_filter->shader, "aspect",
337       (gfloat) width / (gfloat) height);
338 
339   gl->ClientActiveTexture (GL_TEXTURE0);
340   gl->EnableClientState (GL_TEXTURE_COORD_ARRAY);
341   gl->EnableClientState (GL_VERTEX_ARRAY);
342   gl->EnableClientState (GL_COLOR_ARRAY);
343 
344   gl->VertexPointer (3, GL_FLOAT, 9 * sizeof (gfloat), mesh);
345   gl->TexCoordPointer (2, GL_FLOAT, 9 * sizeof (gfloat), &mesh[3]);
346   gl->ColorPointer (4, GL_FLOAT, 9 * sizeof (gfloat), &mesh[5]);
347 
348   gl->DrawElements (GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices);
349 
350   gl->DisableClientState (GL_TEXTURE_COORD_ARRAY);
351   gl->DisableClientState (GL_VERTEX_ARRAY);
352   gl->DisableClientState (GL_COLOR_ARRAY);
353 }
354 
355 static gboolean
gst_gl_filter_glass_callback(gpointer stuff)356 gst_gl_filter_glass_callback (gpointer stuff)
357 {
358   gfloat rotation;
359 
360   GstGLFilter *filter = GST_GL_FILTER (stuff);
361   GstGLFilterGlass *glass_filter = GST_GL_FILTER_GLASS (stuff);
362   GstGLFuncs *gl = GST_GL_BASE_FILTER (filter)->context->gl_vtable;
363 
364   gint width = GST_VIDEO_INFO_WIDTH (&filter->out_info);
365   gint height = GST_VIDEO_INFO_HEIGHT (&filter->out_info);
366   guint texture = glass_filter->in_tex->tex_id;
367 
368   if (glass_filter->start_time == 0)
369     glass_filter->start_time = get_time ();
370   else {
371     gint64 time_left =
372         (glass_filter->timestamp / 1000) - (get_time () -
373         glass_filter->start_time);
374     time_left -= 1000000 / 25;
375     if (time_left > 2000) {
376       GST_LOG ("escape");
377       return FALSE;
378     }
379   }
380 
381   gst_gl_shader_use (glass_filter->passthrough_shader);
382 
383   gst_gl_filter_glass_draw_background_gradient (glass_filter);
384 
385   //Rotation
386   if (glass_filter->start_time != 0) {
387     gint64 time_passed = get_time () - glass_filter->start_time;
388     rotation = sin (time_passed / 1200000.0) * 45.0f;
389   } else {
390     rotation = 0.0f;
391   }
392 
393   gl->Enable (GL_BLEND);
394   gl->BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
395 
396   gst_gl_shader_use (glass_filter->shader);
397 
398   //Reflection
399   gst_gl_filter_glass_draw_video_plane (filter, width, height, texture,
400       0.0f, 2.0f, 0.3f, 0.0f, TRUE, rotation);
401 
402   //Main video
403   gst_gl_filter_glass_draw_video_plane (filter, width, height, texture,
404       0.0f, 0.0f, 1.0f, 1.0f, FALSE, rotation);
405 
406   gst_gl_context_clear_shader (GST_GL_BASE_FILTER (filter)->context);
407 
408   gl->Disable (GL_BLEND);
409 
410   return TRUE;
411 }
412