mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-24 17:20:36 +00:00
518e8a3fd2
Facilities are given to create fbo's and attach GL memory (renderbuffers or textures). It also keeps track of the renderable size for use with effective use with glViewport().
925 lines
31 KiB
C
925 lines
31 KiB
C
/*
|
|
* 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 gboolean gst_gl_transformation_filter_meta (GstBaseTransform * trans,
|
|
GstQuery * query, GType api, const GstStructure * params);
|
|
static gboolean gst_gl_transformation_decide_allocation (GstBaseTransform *
|
|
trans, GstQuery * query);
|
|
|
|
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 gboolean gst_gl_transformation_callback (gpointer stuff);
|
|
static void gst_gl_transformation_build_mvp (GstGLTransformation *
|
|
transformation);
|
|
|
|
static GstFlowReturn
|
|
gst_gl_transformation_prepare_output_buffer (GstBaseTransform * trans,
|
|
GstBuffer * inbuf, GstBuffer ** outbuf);
|
|
static gboolean gst_gl_transformation_filter (GstGLFilter * filter,
|
|
GstBuffer * inbuf, GstBuffer * outbuf);
|
|
static gboolean gst_gl_transformation_filter_texture (GstGLFilter * filter,
|
|
GstGLMemory * in_tex, GstGLMemory * out_tex);
|
|
|
|
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;
|
|
base_transform_class->decide_allocation =
|
|
gst_gl_transformation_decide_allocation;
|
|
base_transform_class->filter_meta = gst_gl_transformation_filter_meta;
|
|
|
|
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 = gst_gl_transformation_filter;
|
|
GST_GL_FILTER_CLASS (klass)->filter_texture =
|
|
gst_gl_transformation_filter_texture;
|
|
GST_BASE_TRANSFORM_CLASS (klass)->stop = gst_gl_transformation_stop;
|
|
GST_BASE_TRANSFORM_CLASS (klass)->prepare_output_buffer =
|
|
gst_gl_transformation_prepare_output_buffer;
|
|
|
|
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 left border, +1 the right border 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 left border, +1 the right border 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_READWRITE | 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 gboolean
|
|
gst_gl_transformation_filter_meta (GstBaseTransform * trans, GstQuery * query,
|
|
GType api, const GstStructure * params)
|
|
{
|
|
if (api == GST_VIDEO_AFFINE_TRANSFORMATION_META_API_TYPE)
|
|
return TRUE;
|
|
|
|
if (api == GST_GL_SYNC_META_API_TYPE)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_gl_transformation_decide_allocation (GstBaseTransform * trans,
|
|
GstQuery * query)
|
|
{
|
|
GstGLTransformation *transformation = GST_GL_TRANSFORMATION (trans);
|
|
|
|
if (!GST_BASE_TRANSFORM_CLASS (parent_class)->decide_allocation (trans,
|
|
query))
|
|
return FALSE;
|
|
|
|
if (gst_query_find_allocation_meta (query,
|
|
GST_VIDEO_AFFINE_TRANSFORMATION_META_API_TYPE, NULL)) {
|
|
transformation->downstream_supports_affine_meta = TRUE;
|
|
} else {
|
|
transformation->downstream_supports_affine_meta = FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
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,
|
|
gst_gl_shader_string_vertex_mat4_vertex_transform,
|
|
gst_gl_shader_string_fragment_default, &transformation->shader);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static const gfloat from_ndc_matrix[] = {
|
|
0.5f, 0.0f, 0.0, 0.5f,
|
|
0.0f, 0.5f, 0.0, 0.5f,
|
|
0.0f, 0.0f, 0.5, 0.5f,
|
|
0.0f, 0.0f, 0.0, 1.0f,
|
|
};
|
|
|
|
static const gfloat to_ndc_matrix[] = {
|
|
2.0f, 0.0f, 0.0, -1.0f,
|
|
0.0f, 2.0f, 0.0, -1.0f,
|
|
0.0f, 0.0f, 2.0, -1.0f,
|
|
0.0f, 0.0f, 0.0, 1.0f,
|
|
};
|
|
|
|
static GstFlowReturn
|
|
gst_gl_transformation_prepare_output_buffer (GstBaseTransform * trans,
|
|
GstBuffer * inbuf, GstBuffer ** outbuf)
|
|
{
|
|
GstGLTransformation *transformation = GST_GL_TRANSFORMATION (trans);
|
|
GstGLFilter *filter = GST_GL_FILTER (trans);
|
|
|
|
if (transformation->downstream_supports_affine_meta &&
|
|
gst_video_info_is_equal (&filter->in_info, &filter->out_info)) {
|
|
GstVideoAffineTransformationMeta *af_meta;
|
|
graphene_matrix_t upstream_matrix, from_ndc, to_ndc, tmp, tmp2, inv_aspect;
|
|
|
|
*outbuf = gst_buffer_make_writable (inbuf);
|
|
|
|
af_meta = gst_buffer_get_video_affine_transformation_meta (inbuf);
|
|
if (!af_meta)
|
|
af_meta = gst_buffer_add_video_affine_transformation_meta (*outbuf);
|
|
|
|
GST_LOG_OBJECT (trans, "applying transformation to existing affine "
|
|
"transformation meta");
|
|
|
|
/* apply the transformation to the existing affine meta */
|
|
graphene_matrix_init_from_float (&from_ndc, from_ndc_matrix);
|
|
graphene_matrix_init_from_float (&to_ndc, to_ndc_matrix);
|
|
graphene_matrix_init_from_float (&upstream_matrix, af_meta->matrix);
|
|
|
|
graphene_matrix_init_scale (&inv_aspect, transformation->aspect, 1., 1.);
|
|
|
|
graphene_matrix_multiply (&from_ndc, &upstream_matrix, &tmp);
|
|
graphene_matrix_multiply (&tmp, &transformation->mvp_matrix, &tmp2);
|
|
graphene_matrix_multiply (&tmp2, &inv_aspect, &tmp);
|
|
graphene_matrix_multiply (&tmp, &to_ndc, &tmp2);
|
|
|
|
graphene_matrix_to_float (&tmp2, af_meta->matrix);
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
return GST_BASE_TRANSFORM_CLASS (parent_class)->prepare_output_buffer (trans,
|
|
inbuf, outbuf);
|
|
}
|
|
|
|
static gboolean
|
|
gst_gl_transformation_filter (GstGLFilter * filter,
|
|
GstBuffer * inbuf, GstBuffer * outbuf)
|
|
{
|
|
GstGLTransformation *transformation = GST_GL_TRANSFORMATION (filter);
|
|
|
|
if (transformation->downstream_supports_affine_meta &&
|
|
gst_video_info_is_equal (&filter->in_info, &filter->out_info)) {
|
|
return TRUE;
|
|
} else {
|
|
return gst_gl_filter_filter_texture (filter, inbuf, outbuf);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_gl_transformation_filter_texture (GstGLFilter * filter,
|
|
GstGLMemory * in_tex, GstGLMemory * out_tex)
|
|
{
|
|
GstGLTransformation *transformation = GST_GL_TRANSFORMATION (filter);
|
|
|
|
transformation->in_tex = in_tex;
|
|
|
|
gst_gl_framebuffer_draw_to_texture (filter->fbo, out_tex,
|
|
(GstGLFramebufferFunc) gst_gl_transformation_callback, 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 gboolean
|
|
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->tex_id);
|
|
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,
|
|
"u_transformation", 1, GL_FALSE, temp_matrix);
|
|
|
|
if (!transformation->vertex_buffer) {
|
|
transformation->attr_position =
|
|
gst_gl_shader_get_attribute_location (transformation->shader,
|
|
"a_position");
|
|
|
|
transformation->attr_texture =
|
|
gst_gl_shader_get_attribute_location (transformation->shader,
|
|
"a_texcoord");
|
|
|
|
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;
|
|
|
|
return TRUE;
|
|
}
|