blob: e201f7492cc643a393b03693db20ffc053da2a77 [file] [log] [blame]
/*
* glshader gstreamer plugin
* Copyrithg (C) 2008 Filippo Argiolas <filippo.argiolas@gmail.com>
* Copyright (C) 2009 Luc Deschenaux <luc.deschenaux@freesurf.ch>
*
* 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-glshader
*
* Filter loading OpenGL fragment shader from file
*
* <refsect2>
* <title>Examples</title>
* |[
* gst-launch videotestsrc ! glupload ! glshader location=myshader.fs ! glimagesink
* ]|
* FBO (Frame Buffer Object) and GLSL (OpenGL Shading Language) are required.
* </refsect2>
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gst/gst.h>
#include <gst/gl/gstglshadervariables.h>
#include "gstglfiltershader.h"
/* horizontal filter */
static gchar *hfilter_fragment_source;
static gchar *hfilter_fragment_variables[2];
enum
{
PROP_0,
PROP_LOCATION,
PROP_PRESET,
PROP_VARIABLES
};
static const gchar text_vertex_shader[] =
"attribute vec4 a_position; \n"
"attribute vec2 a_texcoord; \n"
"varying vec2 v_texcoord; \n"
"void main() \n"
"{ \n"
" gl_Position = a_position; \n"
" v_texcoord = a_texcoord; \n" "} \n";
#define GST_CAT_DEFAULT gst_gl_filtershader_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
#define DEBUG_INIT \
GST_DEBUG_CATEGORY_INIT (gst_gl_filtershader_debug, "glshader", 0, "glshader element");
#define gst_gl_filtershader_parent_class parent_class
G_DEFINE_TYPE_WITH_CODE (GstGLFilterShader, gst_gl_filtershader,
GST_TYPE_GL_FILTER, DEBUG_INIT);
static void gst_gl_filtershader_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static void gst_gl_filtershader_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
static gboolean gst_gl_filter_filtershader_reset (GstBaseTransform * trans);
static gboolean gst_gl_filtershader_load_shader (GstGLFilterShader *
filter_shader, char *filename, char **storage);
static gboolean gst_gl_filtershader_load_variables (GstGLFilterShader *
filter_shader, char *filename, char **storage);
static gboolean gst_gl_filtershader_init_shader (GstGLFilter * filter);
static gboolean gst_gl_filtershader_filter (GstGLFilter * filter,
GstBuffer * inbuf, GstBuffer * outbuf);
static gboolean gst_gl_filtershader_filter_texture (GstGLFilter * filter,
guint in_tex, guint out_tex);
static void gst_gl_filtershader_hcallback (gint width, gint height,
guint texture, gpointer stuff);
static void
gst_gl_filtershader_init_resources (GstGLFilter * filter)
{
GstGLContext *context = GST_GL_BASE_FILTER (filter)->context;
guint internal_format;
internal_format =
gst_gl_sized_gl_format_from_gl_format_type (context, GL_RGBA,
GL_UNSIGNED_BYTE);
glTexImage2D (GL_TEXTURE_2D, 0, internal_format,
GST_VIDEO_INFO_WIDTH (&filter->out_info),
GST_VIDEO_INFO_HEIGHT (&filter->out_info), 0, GL_RGBA, GL_UNSIGNED_BYTE,
NULL);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
static void
gst_gl_filtershader_reset_resources (GstGLFilter * filter)
{
//GstGLFilterShader *filtershader = GST_GL_FILTERSHADER (filter);
}
static void
gst_gl_filtershader_class_init (GstGLFilterShaderClass * klass)
{
GObjectClass *gobject_class;
GstElementClass *element_class;
gobject_class = (GObjectClass *) klass;
element_class = GST_ELEMENT_CLASS (klass);
gobject_class->set_property = gst_gl_filtershader_set_property;
gobject_class->get_property = gst_gl_filtershader_get_property;
g_object_class_install_property (gobject_class, PROP_LOCATION,
g_param_spec_string ("location", "File Location",
"Location of the GLSL file to load", NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_PRESET,
g_param_spec_string ("preset", "Preset File Location",
"Location of the shader uniform variables preset file", NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_VARIABLES,
g_param_spec_string ("vars", "Uniform variables",
"Set the shader uniform variables", NULL,
G_PARAM_WRITABLE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
gst_element_class_set_metadata (element_class,
"OpenGL fragment shader filter", "Filter/Effect",
"Load GLSL fragment shader from file", "<luc.deschenaux@freesurf.ch>");
GST_BASE_TRANSFORM_CLASS (klass)->stop = gst_gl_filter_filtershader_reset;
GST_GL_FILTER_CLASS (klass)->filter = gst_gl_filtershader_filter;
GST_GL_FILTER_CLASS (klass)->filter_texture =
gst_gl_filtershader_filter_texture;
GST_GL_FILTER_CLASS (klass)->display_init_cb =
gst_gl_filtershader_init_resources;
GST_GL_FILTER_CLASS (klass)->display_reset_cb =
gst_gl_filtershader_reset_resources;
GST_GL_FILTER_CLASS (klass)->init_fbo = gst_gl_filtershader_init_shader;
GST_GL_BASE_FILTER_CLASS (klass)->supported_gl_api =
GST_GL_API_OPENGL | GST_GL_API_GLES2 | GST_GL_API_OPENGL3;
}
static void
gst_gl_filtershader_init (GstGLFilterShader * filtershader)
{
filtershader->shader0 = NULL;
}
static gboolean
gst_gl_filter_filtershader_reset (GstBaseTransform * trans)
{
GstGLFilterShader *filtershader = GST_GL_FILTERSHADER (trans);
//blocking call, wait the opengl thread has destroyed the shader
if (filtershader->shader0)
gst_gl_context_del_shader (GST_GL_BASE_FILTER (trans)->context,
filtershader->shader0);
filtershader->shader0 = NULL;
return GST_BASE_TRANSFORM_CLASS (parent_class)->stop (trans);
}
static void
gst_gl_filtershader_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstGLFilterShader *filtershader = GST_GL_FILTERSHADER (object);
switch (prop_id) {
case PROP_LOCATION:
if (filtershader->filename) {
g_free (filtershader->filename);
}
if (filtershader->compiled) {
if (filtershader->shader0)
gst_gl_context_del_shader (GST_GL_BASE_FILTER (filtershader)->context,
filtershader->shader0);
filtershader->shader0 = 0;
}
filtershader->filename = g_strdup (g_value_get_string (value));
filtershader->compiled = 0;
filtershader->texSet = 0;
break;
case PROP_PRESET:
if (filtershader->presetfile) {
g_free (filtershader->presetfile);
}
filtershader->presetfile = g_strdup (g_value_get_string (value));
if (hfilter_fragment_variables[0]) {
g_free (hfilter_fragment_variables[0]);
hfilter_fragment_variables[0] = 0;
}
if (!filtershader->presetfile[0]) {
g_free (filtershader->presetfile);
filtershader->presetfile = 0;
}
break;
case PROP_VARIABLES:
if (hfilter_fragment_variables[1]) {
g_free (hfilter_fragment_variables[1]);
}
hfilter_fragment_variables[1] = g_strdup (g_value_get_string (value));
if (!hfilter_fragment_variables[1][0]) {
g_free (hfilter_fragment_variables[1]);
hfilter_fragment_variables[1] = 0;
}
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_gl_filtershader_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstGLFilterShader *filtershader = GST_GL_FILTERSHADER (object);
switch (prop_id) {
case PROP_LOCATION:
g_value_set_string (value, filtershader->filename);
break;
case PROP_PRESET:
g_value_set_string (value, filtershader->presetfile);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static gboolean
gst_gl_filtershader_load_shader (GstGLFilterShader * filter_shader,
char *filename, char **storage)
{
GError *error = NULL;
gsize length;
g_return_val_if_fail (storage != NULL, FALSE);
if (!filename) {
GST_ELEMENT_ERROR (filter_shader, RESOURCE, NOT_FOUND,
("A shader file is required"), (NULL));
return FALSE;
}
if (!g_file_get_contents (filename, storage, &length, &error)) {
GST_ELEMENT_ERROR (filter_shader, RESOURCE, NOT_FOUND, ("%s",
error->message), (NULL));
g_error_free (error);
return FALSE;
}
return TRUE;
}
static gboolean
gst_gl_filtershader_load_variables (GstGLFilterShader * filter_shader,
char *filename, char **storage)
{
GError *error = NULL;
gsize length;
if (storage[0]) {
g_free (storage[0]);
storage[0] = 0;
}
if (!filename)
return TRUE;
if (!g_file_get_contents (filename, storage, &length, &error)) {
GST_ELEMENT_ERROR (filter_shader, RESOURCE, NOT_FOUND, ("%s",
error->message), (NULL));
g_error_free (error);
return FALSE;
}
return TRUE;
}
static void
gst_gl_filtershader_variables_parse (GstGLShader * shader, gchar * variables)
{
gst_gl_shadervariables_parse (shader, variables, 0);
}
static gboolean
gst_gl_filtershader_init_shader (GstGLFilter * filter)
{
GstGLFilterShader *filtershader = GST_GL_FILTERSHADER (filter);
if (!gst_gl_filtershader_load_shader (filtershader, filtershader->filename,
&hfilter_fragment_source))
return FALSE;
//blocking call, wait the opengl thread has compiled the shader
if (!gst_gl_context_gen_shader (GST_GL_BASE_FILTER (filter)->context,
text_vertex_shader, hfilter_fragment_source, &filtershader->shader0))
return FALSE;
if (!gst_gl_filtershader_load_variables (filtershader,
filtershader->presetfile, &hfilter_fragment_variables[0]))
return FALSE;
filtershader->compiled = 1;
return TRUE;
}
static inline gboolean
_gst_clock_time_to_double (GstClockTime time, gdouble * result)
{
if (!GST_CLOCK_TIME_IS_VALID (time))
return FALSE;
*result = (gdouble) time / GST_SECOND;
return TRUE;
}
static inline gboolean
_gint64_time_val_to_double (gint64 time, gdouble * result)
{
if (time == -1)
return FALSE;
*result = (gdouble) time / GST_USECOND;
return TRUE;
}
static gboolean
gst_gl_filtershader_filter (GstGLFilter * filter, GstBuffer * inbuf,
GstBuffer * outbuf)
{
GstGLFilterShader *filtershader = GST_GL_FILTERSHADER (filter);
if (!_gst_clock_time_to_double (GST_BUFFER_PTS (inbuf), &filtershader->time)) {
if (!_gst_clock_time_to_double (GST_BUFFER_DTS (inbuf),
&filtershader->time))
_gint64_time_val_to_double (g_get_monotonic_time (), &filtershader->time);
}
return gst_gl_filter_filter_texture (filter, inbuf, outbuf);
}
static gboolean
gst_gl_filtershader_filter_texture (GstGLFilter * filter, guint in_tex,
guint out_tex)
{
GstGLFilterShader *filtershader = GST_GL_FILTERSHADER (filter);
gst_gl_filter_render_to_target (filter, TRUE, in_tex, out_tex,
gst_gl_filtershader_hcallback, filtershader);
return TRUE;
}
static void
gst_gl_filtershader_hcallback (gint width, gint height, guint texture,
gpointer stuff)
{
GstGLFilter *filter = GST_GL_FILTER (stuff);
GstGLFilterShader *filtershader = GST_GL_FILTERSHADER (filter);
GstGLFuncs *gl = GST_GL_BASE_FILTER (filter)->context->gl_vtable;
gst_gl_shader_use (filtershader->shader0);
gl->ActiveTexture (GL_TEXTURE0);
gl->BindTexture (GL_TEXTURE_2D, texture);
gst_gl_shader_set_uniform_1i (filtershader->shader0, "tex", 0);
gst_gl_shader_set_uniform_1f (filtershader->shader0, "width", width);
gst_gl_shader_set_uniform_1f (filtershader->shader0, "height", height);
gst_gl_shader_set_uniform_1f (filtershader->shader0, "time",
filtershader->time);
filter->draw_attr_position_loc =
gst_gl_shader_get_attribute_location (filtershader->shader0,
"a_position");
filter->draw_attr_texture_loc =
gst_gl_shader_get_attribute_location (filtershader->shader0,
"a_texcoord");
if (hfilter_fragment_variables[0]) {
gst_gl_filtershader_variables_parse (filtershader->shader0,
hfilter_fragment_variables[0]);
g_free (hfilter_fragment_variables[0]);
hfilter_fragment_variables[0] = 0;
}
if (hfilter_fragment_variables[1]) {
gst_gl_filtershader_variables_parse (filtershader->shader0,
hfilter_fragment_variables[1]);
g_free (hfilter_fragment_variables[1]);
hfilter_fragment_variables[1] = 0;
}
gl->Clear (GL_COLOR_BUFFER_BIT);
gst_gl_filter_draw_texture (filter, texture, width, height);
}