mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-10 03:19:40 +00:00
f790863755
With MSVC, this gives the following warning: warning C4305: 'function': truncation from 'double' to 'gfloat' Apparently, MSVC does not figure out what type to use for constants based on the assignment. This warning is very spammy, so let's try to fix it.
997 lines
34 KiB
C
997 lines
34 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
|
|
|
|
#define VEC4_FORMAT "%f,%f,%f,%f"
|
|
#define VEC4_ARGS(v) graphene_vec4_get_x (v), graphene_vec4_get_y (v), graphene_vec4_get_z (v), graphene_vec4_get_w (v)
|
|
#define VEC3_FORMAT "%f,%f,%f"
|
|
#define VEC3_ARGS(v) graphene_vec3_get_x (v), graphene_vec3_get_y (v), graphene_vec3_get_z (v)
|
|
#define VEC2_FORMAT "%f,%f"
|
|
#define VEC2_ARGS(v) graphene_vec2_get_x (v), graphene_vec2_get_y (v)
|
|
#define POINT3D_FORMAT "%f,%f,%f"
|
|
#define POINT3D_ARGS(p) (p)->x, (p)->y, (p)->z
|
|
|
|
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.1f;
|
|
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 center;
|
|
graphene_vec3_t up;
|
|
|
|
gboolean current_passthrough;
|
|
gboolean passthrough;
|
|
|
|
graphene_vec3_init (&transformation->camera_position, 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,
|
|
&transformation->camera_position, ¢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);
|
|
|
|
graphene_matrix_inverse (&transformation->model_matrix,
|
|
&transformation->inv_model_matrix);
|
|
graphene_matrix_inverse (&transformation->view_matrix,
|
|
&transformation->inv_view_matrix);
|
|
graphene_matrix_inverse (&transformation->projection_matrix,
|
|
&transformation->inv_projection_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:
|
|
/* FIXME: need to decompose this to support navigation events */
|
|
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
|
|
_intersect_plane_and_ray (graphene_plane_t * video_plane, graphene_ray_t * ray,
|
|
graphene_point3d_t * result)
|
|
{
|
|
float t = graphene_ray_get_distance_to_plane (ray, video_plane);
|
|
GST_TRACE ("Calculated a distance of %f to the plane", t);
|
|
graphene_ray_get_position_at (ray, t, result);
|
|
}
|
|
|
|
static void
|
|
_screen_coord_to_world_ray (GstGLTransformation * transformation, float x,
|
|
float y, graphene_ray_t * ray)
|
|
{
|
|
GstGLFilter *filter = GST_GL_FILTER (transformation);
|
|
gfloat w = (gfloat) GST_VIDEO_INFO_WIDTH (&filter->in_info);
|
|
gfloat h = (gfloat) GST_VIDEO_INFO_HEIGHT (&filter->in_info);
|
|
graphene_vec3_t ray_eye_vec3, ray_world_dir, *ray_origin, *ray_direction;
|
|
graphene_vec3_t ray_ortho_dir;
|
|
graphene_point3d_t ray_clip, ray_eye;
|
|
graphene_vec2_t screen_coord;
|
|
|
|
/* GL is y-flipped. i.e. 0, 0 is the bottom left corner in screen space */
|
|
graphene_vec2_init (&screen_coord, (2. * x / w - 1.) / transformation->aspect,
|
|
1. - 2. * y / h);
|
|
|
|
graphene_point3d_init (&ray_clip, graphene_vec2_get_x (&screen_coord),
|
|
graphene_vec2_get_y (&screen_coord), -1.);
|
|
graphene_matrix_transform_point3d (&transformation->inv_projection_matrix,
|
|
&ray_clip, &ray_eye);
|
|
|
|
graphene_vec3_init (&ray_eye_vec3, ray_eye.x, ray_eye.y, -1.);
|
|
|
|
if (transformation->ortho) {
|
|
graphene_vec3_init (&ray_ortho_dir, 0., 0., 1.);
|
|
|
|
ray_origin = &ray_eye_vec3;
|
|
ray_direction = &ray_ortho_dir;
|
|
} else {
|
|
graphene_matrix_transform_vec3 (&transformation->inv_view_matrix,
|
|
&ray_eye_vec3, &ray_world_dir);
|
|
graphene_vec3_normalize (&ray_world_dir, &ray_world_dir);
|
|
|
|
ray_origin = &transformation->camera_position;
|
|
ray_direction = &ray_world_dir;
|
|
}
|
|
|
|
graphene_ray_init_from_vec3 (ray, ray_origin, ray_direction);
|
|
|
|
GST_TRACE_OBJECT (transformation, "Calculated ray origin: " VEC3_FORMAT
|
|
" direction: " VEC3_FORMAT " from screen coordinates: " VEC2_FORMAT
|
|
" with %s projection",
|
|
VEC3_ARGS (ray_origin), VEC3_ARGS (ray_direction),
|
|
VEC2_ARGS (&screen_coord),
|
|
transformation->ortho ? "ortho" : "perspection");
|
|
}
|
|
|
|
static void
|
|
_init_world_video_plane (GstGLTransformation * transformation,
|
|
graphene_plane_t * video_plane)
|
|
{
|
|
graphene_point3d_t bottom_left, bottom_right, top_left, top_right;
|
|
graphene_point3d_t world_bottom_left, world_bottom_right;
|
|
graphene_point3d_t world_top_left, world_top_right;
|
|
|
|
graphene_point3d_init (&top_left, -transformation->aspect, 1., 0.);
|
|
graphene_point3d_init (&top_right, transformation->aspect, 1., 0.);
|
|
graphene_point3d_init (&bottom_left, -transformation->aspect, -1., 0.);
|
|
graphene_point3d_init (&bottom_right, transformation->aspect, -1., 0.);
|
|
|
|
graphene_matrix_transform_point3d (&transformation->model_matrix,
|
|
&bottom_left, &world_bottom_left);
|
|
graphene_matrix_transform_point3d (&transformation->model_matrix,
|
|
&bottom_right, &world_bottom_right);
|
|
graphene_matrix_transform_point3d (&transformation->model_matrix,
|
|
&top_left, &world_top_left);
|
|
graphene_matrix_transform_point3d (&transformation->model_matrix,
|
|
&top_right, &world_top_right);
|
|
|
|
graphene_plane_init_from_points (video_plane, &world_bottom_left,
|
|
&world_top_right, &world_top_left);
|
|
}
|
|
|
|
static gboolean
|
|
_screen_coord_to_model_coord (GstGLTransformation * transformation,
|
|
double x, double y, double *res_x, double *res_y)
|
|
{
|
|
GstGLFilter *filter = GST_GL_FILTER (transformation);
|
|
double w = (double) GST_VIDEO_INFO_WIDTH (&filter->in_info);
|
|
double h = (double) GST_VIDEO_INFO_HEIGHT (&filter->in_info);
|
|
graphene_point3d_t world_point, model_coord;
|
|
graphene_plane_t video_plane;
|
|
graphene_ray_t ray;
|
|
double new_x, new_y;
|
|
|
|
_init_world_video_plane (transformation, &video_plane);
|
|
_screen_coord_to_world_ray (transformation, x, y, &ray);
|
|
_intersect_plane_and_ray (&video_plane, &ray, &world_point);
|
|
graphene_matrix_transform_point3d (&transformation->inv_model_matrix,
|
|
&world_point, &model_coord);
|
|
|
|
/* ndc to pixels. We render the frame Y-flipped so need to unflip the
|
|
* y coordinate */
|
|
new_x = (model_coord.x + 1.) * w / 2;
|
|
new_y = (1. - model_coord.y) * h / 2;
|
|
|
|
if (new_x < 0. || new_x > w || new_y < 0. || new_y > h)
|
|
/* coords off video surface */
|
|
return FALSE;
|
|
|
|
GST_DEBUG_OBJECT (transformation, "converted %f,%f to %f,%f", x, y, new_x,
|
|
new_y);
|
|
|
|
if (res_x)
|
|
*res_x = new_x;
|
|
if (res_y)
|
|
*res_y = new_y;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
#if 0
|
|
/* debugging facilities for transforming vertices from model space to screen
|
|
* space */
|
|
static void
|
|
_ndc_to_viewport (GstGLTransformation * transformation, graphene_vec3_t * ndc,
|
|
int x, int y, int w, int h, float near, float far, graphene_vec3_t * result)
|
|
{
|
|
GstGLFilter *filter = GST_GL_FILTER (transformation);
|
|
/* center of the viewport */
|
|
int o_x = x + w / 2;
|
|
int o_y = y + h / 2;
|
|
|
|
graphene_vec3_init (result, graphene_vec3_get_x (ndc) * w / 2 + o_x,
|
|
graphene_vec3_get_y (ndc) * h / 2 + o_y,
|
|
(far - near) * graphene_vec3_get_z (ndc) / 2 + (far + near) / 2);
|
|
}
|
|
|
|
static void
|
|
_perspective_division (graphene_vec4_t * clip, graphene_vec3_t * result)
|
|
{
|
|
float w = graphene_vec4_get_w (clip);
|
|
|
|
graphene_vec3_init (result, graphene_vec4_get_x (clip) / w,
|
|
graphene_vec4_get_y (clip) / w, graphene_vec4_get_z (clip) / w);
|
|
}
|
|
|
|
static void
|
|
_vertex_to_screen_coord (GstGLTransformation * transformation,
|
|
graphene_vec4_t * vertex, graphene_vec3_t * view)
|
|
{
|
|
GstGLFilter *filter = GST_GL_FILTER (transformation);
|
|
gint w = GST_VIDEO_INFO_WIDTH (&filter->in_info);
|
|
gint h = GST_VIDEO_INFO_HEIGHT (&filter->in_info);
|
|
graphene_vec4_t clip;
|
|
graphene_vec3_t ndc;
|
|
|
|
graphene_matrix_transform_vec4 (&transformation->mvp_matrix, vertex, &clip);
|
|
_perspective_division (&clip, &ndc);
|
|
_ndc_to_viewport (transformation, &ndc, 0, 0, w, h, 0., 1., view);
|
|
}
|
|
#endif
|
|
|
|
static gboolean
|
|
gst_gl_transformation_src_event (GstBaseTransform * trans, GstEvent * event)
|
|
{
|
|
GstGLTransformation *transformation = GST_GL_TRANSFORMATION (trans);
|
|
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:{
|
|
gdouble x, y;
|
|
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)) {
|
|
gdouble new_x, new_y;
|
|
|
|
if (!_screen_coord_to_model_coord (transformation, x, y, &new_x,
|
|
&new_y)) {
|
|
gst_event_unref (event);
|
|
return TRUE;
|
|
}
|
|
|
|
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;
|
|
}
|