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