| /* |
| * GStreamer |
| * Copyright (C) 2014 Lubosz Sarnecki <lubosz@gmail.com> |
| * Copyright (C) 2016 Matthew Waters <matthew@centricular.com> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| /** |
| * SECTION:element-gltransformation |
| * |
| * Transforms video on the GPU. |
| * |
| * <refsect2> |
| * <title>Examples</title> |
| * |[ |
| * gst-launch-1.0 gltestsrc ! gltransformation rotation-z=45 ! glimagesink |
| * ]| A pipeline to rotate by 45 degrees |
| * |[ |
| * gst-launch-1.0 gltestsrc ! gltransformation translation-x=0.5 ! glimagesink |
| * ]| Translate the video by 0.5 |
| * |[ |
| * gst-launch-1.0 gltestsrc ! gltransformation scale-y=0.5 scale-x=0.5 ! glimagesink |
| * ]| Resize the video by 0.5 |
| * |[ |
| * gst-launch-1.0 gltestsrc ! gltransformation rotation-x=-45 ortho=True ! glimagesink |
| * ]| Rotate the video around the X-Axis by -45° with an orthographic projection |
| * </refsect2> |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include "gstgltransformation.h" |
| |
| #include <gst/gl/gstglapi.h> |
| #include <graphene-gobject.h> |
| |
| #define GST_CAT_DEFAULT gst_gl_transformation_debug |
| GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); |
| |
| #define gst_gl_transformation_parent_class parent_class |
| |
| enum |
| { |
| PROP_0, |
| PROP_FOV, |
| PROP_ORTHO, |
| PROP_TRANSLATION_X, |
| PROP_TRANSLATION_Y, |
| PROP_TRANSLATION_Z, |
| PROP_ROTATION_X, |
| PROP_ROTATION_Y, |
| PROP_ROTATION_Z, |
| PROP_SCALE_X, |
| PROP_SCALE_Y, |
| PROP_MVP, |
| PROP_PIVOT_X, |
| PROP_PIVOT_Y, |
| PROP_PIVOT_Z, |
| }; |
| |
| #define DEBUG_INIT \ |
| GST_DEBUG_CATEGORY_INIT (gst_gl_transformation_debug, "gltransformation", 0, "gltransformation element"); |
| |
| G_DEFINE_TYPE_WITH_CODE (GstGLTransformation, gst_gl_transformation, |
| GST_TYPE_GL_FILTER, DEBUG_INIT); |
| |
| static void gst_gl_transformation_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec); |
| static void gst_gl_transformation_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec); |
| |
| static gboolean gst_gl_transformation_set_caps (GstGLFilter * filter, |
| GstCaps * incaps, GstCaps * outcaps); |
| static gboolean gst_gl_transformation_src_event (GstBaseTransform * trans, |
| GstEvent * event); |
| |
| static void gst_gl_transformation_reset_gl (GstGLFilter * filter); |
| static gboolean gst_gl_transformation_stop (GstBaseTransform * trans); |
| static gboolean gst_gl_transformation_init_shader (GstGLFilter * filter); |
| static void gst_gl_transformation_callback (gpointer stuff); |
| static void gst_gl_transformation_build_mvp (GstGLTransformation * |
| transformation); |
| |
| static gboolean gst_gl_transformation_filter_texture (GstGLFilter * filter, |
| guint in_tex, guint out_tex); |
| |
| /* vertex source */ |
| static const gchar *cube_v_src = |
| "attribute vec4 position; \n" |
| "attribute vec2 uv; \n" |
| "uniform mat4 mvp; \n" |
| "varying vec2 out_uv; \n" |
| "void main() \n" |
| "{ \n" |
| " gl_Position = mvp * position; \n" |
| " out_uv = uv; \n" |
| "} \n"; |
| |
| /* fragment source */ |
| static const gchar *cube_f_src = |
| "#ifdef GL_ES \n" |
| " precision mediump float; \n" |
| "#endif \n" |
| "varying vec2 out_uv; \n" |
| "uniform sampler2D texture; \n" |
| "void main() \n" |
| "{ \n" |
| " gl_FragColor = texture2D (texture, out_uv);\n" |
| "} \n"; |
| |
| static void |
| gst_gl_transformation_class_init (GstGLTransformationClass * klass) |
| { |
| GObjectClass *gobject_class; |
| GstElementClass *element_class; |
| GstBaseTransformClass *base_transform_class; |
| |
| gobject_class = (GObjectClass *) klass; |
| element_class = GST_ELEMENT_CLASS (klass); |
| base_transform_class = GST_BASE_TRANSFORM_CLASS (klass); |
| |
| gobject_class->set_property = gst_gl_transformation_set_property; |
| gobject_class->get_property = gst_gl_transformation_get_property; |
| |
| base_transform_class->src_event = gst_gl_transformation_src_event; |
| |
| GST_GL_FILTER_CLASS (klass)->init_fbo = gst_gl_transformation_init_shader; |
| GST_GL_FILTER_CLASS (klass)->display_reset_cb = |
| gst_gl_transformation_reset_gl; |
| GST_GL_FILTER_CLASS (klass)->set_caps = gst_gl_transformation_set_caps; |
| GST_GL_FILTER_CLASS (klass)->filter_texture = |
| gst_gl_transformation_filter_texture; |
| GST_BASE_TRANSFORM_CLASS (klass)->stop = gst_gl_transformation_stop; |
| |
| g_object_class_install_property (gobject_class, PROP_FOV, |
| g_param_spec_float ("fov", "Fov", "Field of view angle in degrees", |
| 0.0, G_MAXFLOAT, 90.0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| g_object_class_install_property (gobject_class, PROP_ORTHO, |
| g_param_spec_boolean ("ortho", "Orthographic", |
| "Use orthographic projection", FALSE, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| /* Rotation */ |
| g_object_class_install_property (gobject_class, PROP_ROTATION_X, |
| g_param_spec_float ("rotation-x", "X Rotation", |
| "Rotates the video around the X-Axis in degrees.", |
| -G_MAXFLOAT, G_MAXFLOAT, 0.0, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| g_object_class_install_property (gobject_class, PROP_ROTATION_Y, |
| g_param_spec_float ("rotation-y", "Y Rotation", |
| "Rotates the video around the Y-Axis in degrees.", |
| -G_MAXFLOAT, G_MAXFLOAT, 0.0, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| g_object_class_install_property (gobject_class, PROP_ROTATION_Z, |
| g_param_spec_float ("rotation-z", "Z Rotation", |
| "Rotates the video around the Z-Axis in degrees.", |
| -G_MAXFLOAT, G_MAXFLOAT, 0.0, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| /* Translation */ |
| g_object_class_install_property (gobject_class, PROP_TRANSLATION_X, |
| g_param_spec_float ("translation-x", "X Translation", |
| "Translates the video at the X-Axis, in universal [0-1] coordinate.", |
| -G_MAXFLOAT, G_MAXFLOAT, 0.0, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| g_object_class_install_property (gobject_class, PROP_TRANSLATION_Y, |
| g_param_spec_float ("translation-y", "Y Translation", |
| "Translates the video at the Y-Axis, in universal [0-1] coordinate.", |
| -G_MAXFLOAT, G_MAXFLOAT, 0.0, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| g_object_class_install_property (gobject_class, PROP_TRANSLATION_Z, |
| g_param_spec_float ("translation-z", "Z Translation", |
| "Translates the video at the Z-Axis, in universal [0-1] coordinate.", |
| -G_MAXFLOAT, G_MAXFLOAT, 0.0, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| /* Scale */ |
| g_object_class_install_property (gobject_class, PROP_SCALE_X, |
| g_param_spec_float ("scale-x", "X Scale", |
| "Scale multiplier for the X-Axis.", |
| -G_MAXFLOAT, G_MAXFLOAT, 1.0, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| g_object_class_install_property (gobject_class, PROP_SCALE_Y, |
| g_param_spec_float ("scale-y", "Y Scale", |
| "Scale multiplier for the Y-Axis.", |
| -G_MAXFLOAT, G_MAXFLOAT, 1.0, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| /* Pivot */ |
| g_object_class_install_property (gobject_class, PROP_PIVOT_X, |
| g_param_spec_float ("pivot-x", "X Pivot", |
| "Rotation pivot point X coordinate, where 0 is the center," |
| " -1 the lef +1 the right boarder and <-1, >1 outside.", |
| -G_MAXFLOAT, G_MAXFLOAT, 0.0, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| g_object_class_install_property (gobject_class, PROP_PIVOT_Y, |
| g_param_spec_float ("pivot-y", "Y Pivot", |
| "Rotation pivot point X coordinate, where 0 is the center," |
| " -1 the lef +1 the right boarder and <-1, >1 outside.", |
| -G_MAXFLOAT, G_MAXFLOAT, 0.0, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| g_object_class_install_property (gobject_class, PROP_PIVOT_Z, |
| g_param_spec_float ("pivot-z", "Z Pivot", |
| "Relevant for rotation in 3D space. You look into the negative Z axis direction", |
| -G_MAXFLOAT, G_MAXFLOAT, 0.0, |
| G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); |
| |
| /* MVP */ |
| g_object_class_install_property (gobject_class, PROP_MVP, |
| g_param_spec_boxed ("mvp-matrix", |
| "Modelview Projection Matrix", |
| "The final Graphene 4x4 Matrix for transformation", |
| GRAPHENE_TYPE_MATRIX, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| gst_element_class_set_metadata (element_class, "OpenGL transformation filter", |
| "Filter/Effect/Video", "Transform video on the GPU", |
| "Lubosz Sarnecki <lubosz@gmail.com>\n" |
| "Matthew Waters <matthew@centricular.com>"); |
| |
| GST_GL_BASE_FILTER_CLASS (klass)->supported_gl_api = |
| GST_GL_API_OPENGL | GST_GL_API_OPENGL3 | GST_GL_API_GLES2; |
| } |
| |
| static void |
| gst_gl_transformation_init (GstGLTransformation * filter) |
| { |
| filter->shader = NULL; |
| filter->fov = 90; |
| filter->aspect = 1.0; |
| filter->znear = 0.1; |
| filter->zfar = 100; |
| |
| filter->xscale = 1.0; |
| filter->yscale = 1.0; |
| |
| filter->in_tex = 0; |
| |
| gst_gl_transformation_build_mvp (filter); |
| } |
| |
| static void |
| gst_gl_transformation_build_mvp (GstGLTransformation * transformation) |
| { |
| GstGLFilter *filter = GST_GL_FILTER (transformation); |
| graphene_matrix_t modelview_matrix; |
| |
| if (!filter->out_info.finfo) { |
| graphene_matrix_init_identity (&transformation->model_matrix); |
| graphene_matrix_init_identity (&transformation->view_matrix); |
| graphene_matrix_init_identity (&transformation->projection_matrix); |
| } else { |
| graphene_point3d_t translation_vector = |
| GRAPHENE_POINT3D_INIT (transformation->xtranslation * 2.0 * |
| transformation->aspect, |
| transformation->ytranslation * 2.0, |
| transformation->ztranslation * 2.0); |
| |
| graphene_point3d_t pivot_vector = |
| GRAPHENE_POINT3D_INIT (-transformation->xpivot * transformation->aspect, |
| transformation->ypivot, |
| -transformation->zpivot); |
| |
| graphene_point3d_t negative_pivot_vector; |
| |
| graphene_vec3_t eye; |
| graphene_vec3_t center; |
| graphene_vec3_t up; |
| |
| gboolean current_passthrough; |
| gboolean passthrough; |
| |
| graphene_vec3_init (&eye, 0.f, 0.f, 1.f); |
| graphene_vec3_init (¢er, 0.f, 0.f, 0.f); |
| graphene_vec3_init (&up, 0.f, 1.f, 0.f); |
| |
| /* Translate into pivot origin */ |
| graphene_matrix_init_translate (&transformation->model_matrix, |
| &pivot_vector); |
| |
| /* Scale */ |
| graphene_matrix_scale (&transformation->model_matrix, |
| transformation->xscale, transformation->yscale, 1.0f); |
| |
| /* Rotation */ |
| graphene_matrix_rotate (&transformation->model_matrix, |
| transformation->xrotation, graphene_vec3_x_axis ()); |
| graphene_matrix_rotate (&transformation->model_matrix, |
| transformation->yrotation, graphene_vec3_y_axis ()); |
| graphene_matrix_rotate (&transformation->model_matrix, |
| transformation->zrotation, graphene_vec3_z_axis ()); |
| |
| /* Translate back from pivot origin */ |
| graphene_point3d_scale (&pivot_vector, -1.0, &negative_pivot_vector); |
| graphene_matrix_translate (&transformation->model_matrix, |
| &negative_pivot_vector); |
| |
| /* Translation */ |
| graphene_matrix_translate (&transformation->model_matrix, |
| &translation_vector); |
| |
| if (transformation->ortho) { |
| graphene_matrix_init_ortho (&transformation->projection_matrix, |
| -transformation->aspect, transformation->aspect, |
| -1, 1, transformation->znear, transformation->zfar); |
| } else { |
| graphene_matrix_init_perspective (&transformation->projection_matrix, |
| transformation->fov, |
| transformation->aspect, transformation->znear, transformation->zfar); |
| } |
| |
| graphene_matrix_init_look_at (&transformation->view_matrix, &eye, ¢er, |
| &up); |
| |
| current_passthrough = |
| gst_base_transform_is_passthrough (GST_BASE_TRANSFORM (transformation)); |
| passthrough = transformation->xtranslation == 0. |
| && transformation->ytranslation == 0. |
| && transformation->ztranslation == 0. && transformation->xrotation == 0. |
| && transformation->yrotation == 0. && transformation->zrotation == 0. |
| && transformation->xscale == 1. && transformation->yscale == 1. |
| && gst_video_info_is_equal (&filter->in_info, &filter->out_info); |
| gst_base_transform_set_passthrough (GST_BASE_TRANSFORM (transformation), |
| passthrough); |
| if (current_passthrough != passthrough) { |
| gst_base_transform_reconfigure_src (GST_BASE_TRANSFORM (transformation)); |
| } |
| } |
| |
| graphene_matrix_multiply (&transformation->model_matrix, |
| &transformation->view_matrix, &modelview_matrix); |
| graphene_matrix_multiply (&modelview_matrix, |
| &transformation->projection_matrix, &transformation->mvp_matrix); |
| } |
| |
| static void |
| gst_gl_transformation_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec) |
| { |
| GstGLTransformation *filter = GST_GL_TRANSFORMATION (object); |
| |
| switch (prop_id) { |
| case PROP_FOV: |
| filter->fov = g_value_get_float (value); |
| break; |
| case PROP_ORTHO: |
| filter->ortho = g_value_get_boolean (value); |
| break; |
| case PROP_TRANSLATION_X: |
| filter->xtranslation = g_value_get_float (value); |
| break; |
| case PROP_TRANSLATION_Y: |
| filter->ytranslation = g_value_get_float (value); |
| break; |
| case PROP_TRANSLATION_Z: |
| filter->ztranslation = g_value_get_float (value); |
| break; |
| case PROP_ROTATION_X: |
| filter->xrotation = g_value_get_float (value); |
| break; |
| case PROP_ROTATION_Y: |
| filter->yrotation = g_value_get_float (value); |
| break; |
| case PROP_ROTATION_Z: |
| filter->zrotation = g_value_get_float (value); |
| break; |
| case PROP_SCALE_X: |
| filter->xscale = g_value_get_float (value); |
| break; |
| case PROP_SCALE_Y: |
| filter->yscale = g_value_get_float (value); |
| break; |
| case PROP_PIVOT_X: |
| filter->xpivot = g_value_get_float (value); |
| break; |
| case PROP_PIVOT_Y: |
| filter->ypivot = g_value_get_float (value); |
| break; |
| case PROP_PIVOT_Z: |
| filter->zpivot = g_value_get_float (value); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| gst_gl_transformation_build_mvp (filter); |
| } |
| |
| static void |
| gst_gl_transformation_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec) |
| { |
| GstGLTransformation *filter = GST_GL_TRANSFORMATION (object); |
| |
| switch (prop_id) { |
| case PROP_FOV: |
| g_value_set_float (value, filter->fov); |
| break; |
| case PROP_ORTHO: |
| g_value_set_boolean (value, filter->ortho); |
| break; |
| case PROP_TRANSLATION_X: |
| g_value_set_float (value, filter->xtranslation); |
| break; |
| case PROP_TRANSLATION_Y: |
| g_value_set_float (value, filter->ytranslation); |
| break; |
| case PROP_TRANSLATION_Z: |
| g_value_set_float (value, filter->ztranslation); |
| break; |
| case PROP_ROTATION_X: |
| g_value_set_float (value, filter->xrotation); |
| break; |
| case PROP_ROTATION_Y: |
| g_value_set_float (value, filter->yrotation); |
| break; |
| case PROP_ROTATION_Z: |
| g_value_set_float (value, filter->zrotation); |
| break; |
| case PROP_SCALE_X: |
| g_value_set_float (value, filter->xscale); |
| break; |
| case PROP_SCALE_Y: |
| g_value_set_float (value, filter->yscale); |
| break; |
| case PROP_PIVOT_X: |
| g_value_set_float (value, filter->xpivot); |
| break; |
| case PROP_PIVOT_Y: |
| g_value_set_float (value, filter->ypivot); |
| break; |
| case PROP_PIVOT_Z: |
| g_value_set_float (value, filter->zpivot); |
| break; |
| case PROP_MVP: |
| g_value_set_boxed (value, (gconstpointer) & filter->mvp_matrix); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static gboolean |
| gst_gl_transformation_set_caps (GstGLFilter * filter, GstCaps * incaps, |
| GstCaps * outcaps) |
| { |
| GstGLTransformation *transformation = GST_GL_TRANSFORMATION (filter); |
| |
| transformation->aspect = |
| (gdouble) GST_VIDEO_INFO_WIDTH (&filter->out_info) / |
| (gdouble) GST_VIDEO_INFO_HEIGHT (&filter->out_info); |
| |
| transformation->caps_change = TRUE; |
| |
| gst_gl_transformation_build_mvp (transformation); |
| |
| return TRUE; |
| } |
| |
| static void |
| _find_plane_normal (const graphene_point3d_t * A, const graphene_point3d_t * B, |
| const graphene_point3d_t * C, graphene_vec3_t * plane_normal) |
| { |
| graphene_vec3_t U, V, A_v, B_v, C_v; |
| |
| graphene_point3d_to_vec3 (A, &A_v); |
| graphene_point3d_to_vec3 (B, &B_v); |
| graphene_point3d_to_vec3 (C, &C_v); |
| |
| graphene_vec3_subtract (&B_v, &A_v, &U); |
| graphene_vec3_subtract (&C_v, &A_v, &V); |
| |
| graphene_vec3_cross (&U, &V, plane_normal); |
| graphene_vec3_normalize (plane_normal, plane_normal); |
| } |
| |
| static void |
| _find_model_coords (GstGLTransformation * transformation, |
| const graphene_point3d_t * screen_coords, graphene_point3d_t * res) |
| { |
| graphene_matrix_t modelview, inverse_proj, inverse_modelview; |
| graphene_vec4_t v1, v2; |
| graphene_point3d_t p1; |
| gfloat w; |
| |
| graphene_vec4_init (&v1, screen_coords->x, screen_coords->y, screen_coords->z, |
| 1.); |
| graphene_matrix_inverse (&transformation->projection_matrix, &inverse_proj); |
| graphene_matrix_transform_vec4 (&inverse_proj, &v1, &v2); |
| |
| /* perspective division */ |
| w = graphene_vec4_get_w (&v2); |
| p1.x = graphene_vec4_get_x (&v2) / w; |
| p1.y = graphene_vec4_get_y (&v2) / w; |
| p1.z = graphene_vec4_get_z (&v2) / w; |
| |
| graphene_matrix_multiply (&transformation->model_matrix, |
| &transformation->view_matrix, &modelview); |
| graphene_matrix_inverse (&modelview, &inverse_modelview); |
| graphene_matrix_transform_point3d (&inverse_modelview, &p1, res); |
| } |
| |
| static gboolean |
| gst_gl_transformation_src_event (GstBaseTransform * trans, GstEvent * event) |
| { |
| GstGLTransformation *transformation = GST_GL_TRANSFORMATION (trans); |
| GstGLFilter *filter = GST_GL_FILTER (trans); |
| gdouble new_x, new_y, x, y; |
| GstStructure *structure; |
| gboolean ret; |
| |
| GST_DEBUG_OBJECT (trans, "handling %s event", GST_EVENT_TYPE_NAME (event)); |
| |
| switch (GST_EVENT_TYPE (event)) { |
| case GST_EVENT_NAVIGATION: |
| event = |
| GST_EVENT (gst_mini_object_make_writable (GST_MINI_OBJECT (event))); |
| |
| structure = (GstStructure *) gst_event_get_structure (event); |
| if (gst_structure_get_double (structure, "pointer_x", &x) && |
| gst_structure_get_double (structure, "pointer_y", &y)) { |
| gfloat w = (gfloat) GST_VIDEO_INFO_WIDTH (&filter->in_info); |
| gfloat h = (gfloat) GST_VIDEO_INFO_HEIGHT (&filter->in_info); |
| graphene_point3d_t screen_point_near, screen_point_far; |
| graphene_point3d_t model_coord_near, model_coord_far; |
| graphene_point3d_t bottom_left, bottom_right, top_left, top_right; |
| graphene_point3d_t result; |
| graphene_vec3_t plane_normal; |
| graphene_plane_t video_plane; |
| gfloat d; |
| |
| GST_DEBUG_OBJECT (trans, "converting %f,%f", x, y); |
| |
| graphene_point3d_init (&top_left, -1., 1., 0.); |
| graphene_point3d_init (&top_right, 1., 1., 0.); |
| graphene_point3d_init (&bottom_left, -1., -1., 0.); |
| graphene_point3d_init (&bottom_right, 1., -1., 0.); |
| /* to NDC */ |
| graphene_point3d_init (&screen_point_near, 2. * x / w - 1., |
| 2. * y / h - 1., -1.); |
| graphene_point3d_init (&screen_point_far, 2. * x / w - 1., |
| 2. * y / h - 1., 1.); |
| |
| _find_plane_normal (&bottom_left, &top_left, &top_right, &plane_normal); |
| graphene_plane_init_from_point (&video_plane, &plane_normal, &top_left); |
| d = graphene_plane_get_constant (&video_plane); |
| |
| /* get the closest and furthest points in the viewing area for the |
| * specified screen coordinate in order to construct a ray */ |
| _find_model_coords (transformation, &screen_point_near, |
| &model_coord_near); |
| _find_model_coords (transformation, &screen_point_far, |
| &model_coord_far); |
| |
| { |
| graphene_vec3_t model_coord_near_vec3, model_coord_far_vec3; |
| graphene_vec3_t tmp, intersection, coord_dir; |
| gfloat num, denom, t; |
| |
| /* get the direction of the ray */ |
| graphene_point3d_to_vec3 (&model_coord_near, &model_coord_near_vec3); |
| graphene_point3d_to_vec3 (&model_coord_far, &model_coord_far_vec3); |
| graphene_vec3_subtract (&model_coord_near_vec3, &model_coord_far_vec3, |
| &coord_dir); |
| |
| /* Intersect the ray with the video plane to find the distance, t: |
| * Ray: P = P0 + t Pdir |
| * Plane: P dot N + d = 0 |
| * |
| * Substituting for P and rearranging gives: |
| * |
| * t = (P0 dot N + d) / (Pdir dot N) */ |
| denom = graphene_vec3_dot (&coord_dir, &plane_normal); |
| num = graphene_vec3_dot (&model_coord_near_vec3, &plane_normal); |
| t = -(num + d) / denom; |
| |
| /* video coord = P0 + t Pdir */ |
| graphene_vec3_scale (&coord_dir, t, &tmp); |
| graphene_vec3_add (&tmp, &model_coord_near_vec3, &intersection); |
| graphene_point3d_init_from_vec3 (&result, &intersection); |
| } |
| |
| new_x = (result.x / transformation->aspect + 1.) * w / 2; |
| new_y = (result.y + 1.) * h / 2; |
| |
| if (new_x < 0. || new_x > w || new_y < 0 || new_y > h) { |
| /* coords off video surface */ |
| gst_event_unref (event); |
| return TRUE; |
| } |
| |
| GST_DEBUG_OBJECT (trans, "to %fx%f", new_x, new_y); |
| gst_structure_set (structure, "pointer_x", G_TYPE_DOUBLE, new_x, |
| "pointer_y", G_TYPE_DOUBLE, new_y, NULL); |
| } |
| break; |
| default: |
| break; |
| } |
| |
| ret = GST_BASE_TRANSFORM_CLASS (parent_class)->src_event (trans, event); |
| |
| return ret; |
| } |
| |
| static void |
| gst_gl_transformation_reset_gl (GstGLFilter * filter) |
| { |
| GstGLTransformation *transformation = GST_GL_TRANSFORMATION (filter); |
| const GstGLFuncs *gl = GST_GL_BASE_FILTER (filter)->context->gl_vtable; |
| |
| if (transformation->vao) { |
| gl->DeleteVertexArrays (1, &transformation->vao); |
| transformation->vao = 0; |
| } |
| |
| if (transformation->vertex_buffer) { |
| gl->DeleteBuffers (1, &transformation->vertex_buffer); |
| transformation->vertex_buffer = 0; |
| } |
| |
| if (transformation->vbo_indices) { |
| gl->DeleteBuffers (1, &transformation->vbo_indices); |
| transformation->vbo_indices = 0; |
| } |
| |
| if (transformation->shader) { |
| gst_object_unref (transformation->shader); |
| transformation->shader = NULL; |
| } |
| } |
| |
| static gboolean |
| gst_gl_transformation_stop (GstBaseTransform * trans) |
| { |
| GstGLBaseFilter *basefilter = GST_GL_BASE_FILTER (trans); |
| GstGLTransformation *transformation = GST_GL_TRANSFORMATION (trans); |
| |
| /* blocking call, wait until the opengl thread has destroyed the shader */ |
| if (basefilter->context && transformation->shader) { |
| gst_gl_context_del_shader (basefilter->context, transformation->shader); |
| transformation->shader = NULL; |
| } |
| |
| return GST_BASE_TRANSFORM_CLASS (parent_class)->stop (trans); |
| } |
| |
| static gboolean |
| gst_gl_transformation_init_shader (GstGLFilter * filter) |
| { |
| GstGLTransformation *transformation = GST_GL_TRANSFORMATION (filter); |
| |
| if (transformation->shader) { |
| gst_object_unref (transformation->shader); |
| transformation->shader = NULL; |
| } |
| |
| if (gst_gl_context_get_gl_api (GST_GL_BASE_FILTER (filter)->context)) { |
| /* blocking call, wait until the opengl thread has compiled the shader */ |
| return gst_gl_context_gen_shader (GST_GL_BASE_FILTER (filter)->context, |
| cube_v_src, cube_f_src, &transformation->shader); |
| } |
| return TRUE; |
| } |
| |
| static gboolean |
| gst_gl_transformation_filter_texture (GstGLFilter * filter, guint in_tex, |
| guint out_tex) |
| { |
| GstGLTransformation *transformation = GST_GL_TRANSFORMATION (filter); |
| |
| transformation->in_tex = in_tex; |
| |
| /* blocking call, use a FBO */ |
| gst_gl_context_use_fbo_v2 (GST_GL_BASE_FILTER (filter)->context, |
| GST_VIDEO_INFO_WIDTH (&filter->out_info), |
| GST_VIDEO_INFO_HEIGHT (&filter->out_info), |
| filter->fbo, filter->depthbuffer, |
| out_tex, gst_gl_transformation_callback, (gpointer) transformation); |
| |
| return TRUE; |
| } |
| |
| static const GLushort indices[] = { 0, 1, 2, 3, 0 }; |
| |
| static void |
| _upload_vertices (GstGLTransformation * transformation) |
| { |
| const GstGLFuncs *gl = |
| GST_GL_BASE_FILTER (transformation)->context->gl_vtable; |
| |
| /* *INDENT-OFF* */ |
| GLfloat vertices[] = { |
| -transformation->aspect, 1.0, 0.0, 1.0, 0.0, 1.0, |
| transformation->aspect, 1.0, 0.0, 1.0, 1.0, 1.0, |
| transformation->aspect, -1.0, 0.0, 1.0, 1.0, 0.0, |
| -transformation->aspect, -1.0, 0.0, 1.0, 0.0, 0.0 |
| }; |
| /* *INDENT-ON* */ |
| |
| gl->BindBuffer (GL_ARRAY_BUFFER, transformation->vertex_buffer); |
| |
| gl->BufferData (GL_ARRAY_BUFFER, 4 * 6 * sizeof (GLfloat), vertices, |
| GL_STATIC_DRAW); |
| } |
| |
| static void |
| _bind_buffer (GstGLTransformation * transformation) |
| { |
| const GstGLFuncs *gl = |
| GST_GL_BASE_FILTER (transformation)->context->gl_vtable; |
| |
| gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, transformation->vbo_indices); |
| gl->BindBuffer (GL_ARRAY_BUFFER, transformation->vertex_buffer); |
| |
| /* Load the vertex position */ |
| gl->VertexAttribPointer (transformation->attr_position, 4, GL_FLOAT, |
| GL_FALSE, 6 * sizeof (GLfloat), (void *) 0); |
| |
| /* Load the texture coordinate */ |
| gl->VertexAttribPointer (transformation->attr_texture, 2, GL_FLOAT, GL_FALSE, |
| 6 * sizeof (GLfloat), (void *) (4 * sizeof (GLfloat))); |
| |
| gl->EnableVertexAttribArray (transformation->attr_position); |
| gl->EnableVertexAttribArray (transformation->attr_texture); |
| } |
| |
| static void |
| _unbind_buffer (GstGLTransformation * transformation) |
| { |
| const GstGLFuncs *gl = |
| GST_GL_BASE_FILTER (transformation)->context->gl_vtable; |
| |
| gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0); |
| gl->BindBuffer (GL_ARRAY_BUFFER, 0); |
| |
| gl->DisableVertexAttribArray (transformation->attr_position); |
| gl->DisableVertexAttribArray (transformation->attr_texture); |
| } |
| |
| static void |
| gst_gl_transformation_callback (gpointer stuff) |
| { |
| GstGLFilter *filter = GST_GL_FILTER (stuff); |
| GstGLTransformation *transformation = GST_GL_TRANSFORMATION (filter); |
| GstGLFuncs *gl = GST_GL_BASE_FILTER (filter)->context->gl_vtable; |
| |
| GLfloat temp_matrix[16]; |
| |
| gst_gl_context_clear_shader (GST_GL_BASE_FILTER (filter)->context); |
| gl->BindTexture (GL_TEXTURE_2D, 0); |
| |
| gl->ClearColor (0.f, 0.f, 0.f, 0.f); |
| gl->Clear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
| |
| gst_gl_shader_use (transformation->shader); |
| |
| gl->ActiveTexture (GL_TEXTURE0); |
| gl->BindTexture (GL_TEXTURE_2D, transformation->in_tex); |
| gst_gl_shader_set_uniform_1i (transformation->shader, "texture", 0); |
| |
| graphene_matrix_to_float (&transformation->mvp_matrix, temp_matrix); |
| gst_gl_shader_set_uniform_matrix_4fv (transformation->shader, "mvp", |
| 1, GL_FALSE, temp_matrix); |
| |
| if (!transformation->vertex_buffer) { |
| transformation->attr_position = |
| gst_gl_shader_get_attribute_location (transformation->shader, |
| "position"); |
| |
| transformation->attr_texture = |
| gst_gl_shader_get_attribute_location (transformation->shader, "uv"); |
| |
| if (gl->GenVertexArrays) { |
| gl->GenVertexArrays (1, &transformation->vao); |
| gl->BindVertexArray (transformation->vao); |
| } |
| |
| gl->GenBuffers (1, &transformation->vertex_buffer); |
| |
| gl->GenBuffers (1, &transformation->vbo_indices); |
| gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, transformation->vbo_indices); |
| gl->BufferData (GL_ELEMENT_ARRAY_BUFFER, sizeof (indices), indices, |
| GL_STATIC_DRAW); |
| |
| transformation->caps_change = TRUE; |
| } |
| |
| if (gl->GenVertexArrays) |
| gl->BindVertexArray (transformation->vao); |
| |
| if (transformation->caps_change) { |
| _upload_vertices (transformation); |
| _bind_buffer (transformation); |
| } else if (!gl->GenVertexArrays) { |
| _bind_buffer (transformation); |
| } |
| |
| gl->DrawElements (GL_TRIANGLE_STRIP, 5, GL_UNSIGNED_SHORT, 0); |
| |
| if (gl->GenVertexArrays) |
| gl->BindVertexArray (0); |
| else |
| _unbind_buffer (transformation); |
| |
| gst_gl_context_clear_shader (GST_GL_BASE_FILTER (filter)->context); |
| transformation->caps_change = FALSE; |
| } |