/* * GStreamer * Copyright (C) 2015 Matthew Waters * * 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. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "gstglslstage.h" #include "gl.h" #include "gstglfuncs.h" #include "gstglsl_private.h" #ifndef GL_GEOMETRY_SHADER #define GL_GEOMETRY_SHADER 0x8DD9 #endif #ifndef GL_COMPUTE_SHADER #define GL_COMPUTE_SHADER 0x91B9 #endif #ifndef GL_TESS_CONTROL_SHADER #define GL_TESS_CONTROL_SHADER 0x8E88 #endif #ifndef GL_TESS_EVALUATION_SHADER #define GL_TESS_EVALUATION_SHADER 0x8E87 #endif /** * SECTION:gstglslstage * @short_description: object for dealing with OpenGL shader stages * @title: GstGLSLStage * @see_also: #GstGLShader * * #GstGLSLStage holds and represents a single OpenGL shader stage. */ static const gchar *es2_version_header = "#version 100\n"; GST_DEBUG_CATEGORY_STATIC (gst_glsl_stage_debug); #define GST_CAT_DEFAULT gst_glsl_stage_debug G_DEFINE_TYPE_WITH_CODE (GstGLSLStage, gst_glsl_stage, GST_TYPE_OBJECT, GST_DEBUG_CATEGORY_INIT (gst_glsl_stage_debug, "glslstage", 0, "GLSL Stage"); ); #define GST_GLSL_STAGE_GET_PRIVATE(o) \ (G_TYPE_INSTANCE_GET_PRIVATE((o), GST_TYPE_GLSL_STAGE, GstGLSLStagePrivate)) struct _GstGLSLStagePrivate { GstGLSLFuncs vtable; GLenum type; GLhandleARB handle; GstGLSLVersion version; GstGLSLProfile profile; gchar **strings; gint n_strings; gboolean compiled; }; static void gst_glsl_stage_finalize (GObject * object) { GstGLSLStage *stage = GST_GLSL_STAGE (object); gint i; if (stage->context) { gst_object_unref (stage->context); stage->context = NULL; } for (i = 0; i < stage->priv->n_strings; i++) { g_free (stage->priv->strings[i]); } g_free (stage->priv->strings); stage->priv->strings = NULL; G_OBJECT_CLASS (gst_glsl_stage_parent_class)->finalize (object); } static void gst_glsl_stage_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { switch (prop_id) { default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_glsl_stage_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { switch (prop_id) { default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_glsl_stage_class_init (GstGLSLStageClass * klass) { GObjectClass *obj_class = G_OBJECT_CLASS (klass); g_type_class_add_private (klass, sizeof (GstGLSLStagePrivate)); obj_class->finalize = gst_glsl_stage_finalize; obj_class->set_property = gst_glsl_stage_set_property; obj_class->get_property = gst_glsl_stage_get_property; } static void gst_glsl_stage_init (GstGLSLStage * stage) { stage->priv = GST_GLSL_STAGE_GET_PRIVATE (stage); } static gboolean _is_valid_shader_type (GLenum type) { switch (type) { case GL_VERTEX_SHADER: case GL_FRAGMENT_SHADER: case GL_TESS_CONTROL_SHADER: case GL_TESS_EVALUATION_SHADER: case GL_GEOMETRY_SHADER: case GL_COMPUTE_SHADER: return TRUE; default: return FALSE; } } static const gchar * _shader_type_to_string (GLenum type) { switch (type) { case GL_VERTEX_SHADER: return "vertex"; case GL_FRAGMENT_SHADER: return "fragment"; case GL_TESS_CONTROL_SHADER: return "tesselation control"; case GL_TESS_EVALUATION_SHADER: return "tesselation evaluation"; case GL_GEOMETRY_SHADER: return "geometry"; case GL_COMPUTE_SHADER: return "compute"; default: return "unknown"; } } static gboolean _ensure_shader (GstGLSLStage * stage) { if (stage->priv->handle) return TRUE; if (!(stage->priv->handle = stage->priv->vtable.CreateShader (stage->priv->type))) return FALSE; return stage->priv->handle != 0; } /** * gst_glsl_stage_new_with_strings: * @context: a #GstGLContext * @type: the GL enum shader stage type * @version: the #GstGLSLVersion * @profile: the #GstGLSLProfile * @n_strings: the number of strings in @str * @str: (array length=n_strings): * an array of strings concatted together to produce a shader * * Returns: (transfer floating): a new #GstGLSLStage of the specified @type * * Since: 1.8 */ GstGLSLStage * gst_glsl_stage_new_with_strings (GstGLContext * context, guint type, GstGLSLVersion version, GstGLSLProfile profile, gint n_strings, const gchar ** str) { GstGLSLStage *stage; g_return_val_if_fail (GST_IS_GL_CONTEXT (context), NULL); g_return_val_if_fail (_is_valid_shader_type (type), NULL); stage = g_object_new (GST_TYPE_GLSL_STAGE, NULL); /* FIXME: GInittable */ if (!_gst_glsl_funcs_fill (&stage->priv->vtable, context)) { gst_object_unref (stage); return NULL; } stage->context = gst_object_ref (context); stage->priv->type = type; if (!gst_glsl_stage_set_strings (stage, version, profile, n_strings, str)) { gst_object_unref (stage); return NULL; } return stage; } /** * gst_glsl_stage_new_with_string: * @context: a #GstGLContext * @type: the GL enum shader stage type * @version: the #GstGLSLVersion * @profile: the #GstGLSLProfile * @str: a shader string * * Returns: (transfer floating): a new #GstGLSLStage of the specified @type * * Since: 1.8 */ GstGLSLStage * gst_glsl_stage_new_with_string (GstGLContext * context, guint type, GstGLSLVersion version, GstGLSLProfile profile, const gchar * str) { return gst_glsl_stage_new_with_strings (context, type, version, profile, 1, &str); } /** * gst_glsl_stage_new: * @context: a #GstGLContext * @type: the GL enum shader stage type * * Returns: (transfer floating): a new #GstGLSLStage of the specified @type * * Since: 1.8 */ GstGLSLStage * gst_glsl_stage_new (GstGLContext * context, guint type) { return gst_glsl_stage_new_with_string (context, type, GST_GLSL_VERSION_NONE, GST_GLSL_PROFILE_NONE, NULL); } /** * gst_glsl_stage_new_with_default_vertex: * @context: a #GstGLContext * * Returns: (transfer floating): a new #GstGLSLStage with the default vertex shader * * Since: 1.8 */ GstGLSLStage * gst_glsl_stage_new_default_vertex (GstGLContext * context) { return gst_glsl_stage_new_with_string (context, GL_VERTEX_SHADER, GST_GLSL_VERSION_NONE, GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY, gst_gl_shader_string_vertex_default); } /** * gst_glsl_stage_new_with_default_fragment: * @context: a #GstGLContext * * Returns: (transfer floating): a new #GstGLSLStage with the default fragment shader * * Since: 1.8 */ GstGLSLStage * gst_glsl_stage_new_default_fragment (GstGLContext * context) { return gst_glsl_stage_new_with_string (context, GL_FRAGMENT_SHADER, GST_GLSL_VERSION_NONE, GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY, gst_gl_shader_string_fragment_default); } /** * gst_glsl_stage_set_strings: * @stage: a #GstGLSLStage * @version: a #GstGLSLVersion * @profile: a #GstGLSLProfile * @n_strings: number of strings in @str * @str: (array length=n_strings) (transfer none): a GLSL shader string * * Replaces the current shader string with @str. * * Since: 1.8 */ gboolean gst_glsl_stage_set_strings (GstGLSLStage * stage, GstGLSLVersion version, GstGLSLProfile profile, gint n_strings, const gchar ** str) { gint i; g_return_val_if_fail (GST_IS_GLSL_STAGE (stage), FALSE); g_return_val_if_fail (n_strings > 0, FALSE); g_return_val_if_fail (str != NULL, FALSE); if (!gst_gl_context_supports_glsl_profile_version (stage->context, version, profile)) { const gchar *version_str = gst_glsl_version_to_string (version); const gchar *profile_str = gst_glsl_profile_to_string (profile); GST_ERROR_OBJECT (stage, "GL context does not support version %s and " "profile %s", version_str, profile_str); return FALSE; } stage->priv->version = version; stage->priv->profile = profile; for (i = 0; i < stage->priv->n_strings; i++) { g_free (stage->priv->strings[i]); } if (stage->priv->n_strings < n_strings) { /* only realloc if we need more space */ g_free (stage->priv->strings); stage->priv->strings = g_new0 (gchar *, n_strings); } for (i = 0; i < n_strings; i++) stage->priv->strings[i] = g_strdup (str[i]); stage->priv->n_strings = n_strings; return TRUE; } /** * gst_glsl_stage_get_shader_type: * @stage: a #GstGLSLStage * * Returns: The GL shader type for this shader stage * * Since: 1.8 */ guint gst_glsl_stage_get_shader_type (GstGLSLStage * stage) { g_return_val_if_fail (GST_IS_GLSL_STAGE (stage), 0); return stage->priv->type; } /** * gst_glsl_stage_get_handle: * @stage: a #GstGLSLStage * * Returns: The GL handle for this shader stage * * Since: 1.8 */ guint gst_glsl_stage_get_handle (GstGLSLStage * stage) { g_return_val_if_fail (GST_IS_GLSL_STAGE (stage), 0); g_return_val_if_fail (stage->priv->compiled, 0); return stage->priv->handle; } /** * gst_glsl_stage_get_version: * @stage: a #GstGLSLStage * * Returns: The GLSL version for the current shader stage * * Since: 1.8 */ GstGLSLVersion gst_glsl_stage_get_version (GstGLSLStage * stage) { g_return_val_if_fail (GST_IS_GLSL_STAGE (stage), 0); return stage->priv->version; } /** * gst_glsl_stage_get_profile: * @stage: a #GstGLSLStage * * Returns: The GLSL profile for the current shader stage * * Since: 1.8 */ GstGLSLProfile gst_glsl_stage_get_profile (GstGLSLStage * stage) { g_return_val_if_fail (GST_IS_GLSL_STAGE (stage), 0); return stage->priv->profile; } static void _maybe_prepend_version (GstGLSLStage * stage, gchar ** shader_str, gint * n_vertex_sources, const gchar *** vertex_sources) { gint n = *n_vertex_sources; gboolean add_header = FALSE; gint i, j; /* FIXME: this all an educated guess */ if (gst_gl_context_check_gl_version (stage->context, GST_GL_API_OPENGL3, 3, 0) && (stage->priv->profile & GST_GLSL_PROFILE_ES) != 0 && !_gst_glsl_shader_string_find_version (shader_str[0])) { add_header = TRUE; n++; } *vertex_sources = g_malloc0 (n * sizeof (gchar *)); i = 0; if (add_header) (*vertex_sources)[i++] = es2_version_header; for (j = 0; j < stage->priv->n_strings; i++, j++) (*vertex_sources)[i] = shader_str[j]; *n_vertex_sources = n; } struct compile { GstGLSLStage *stage; GError **error; gboolean result; }; static void _compile_shader (GstGLContext * context, struct compile *data) { GstGLSLStagePrivate *priv = data->stage->priv; GstGLSLFuncs *vtable = &data->stage->priv->vtable; const GstGLFuncs *gl = context->gl_vtable; const gchar **vertex_sources; gchar info_buffer[2048]; gint n_vertex_sources; GLint status; gint len; gint i; if (data->stage->priv->compiled) { data->result = TRUE; return; } if (!_ensure_shader (data->stage)) { g_set_error (data->error, GST_GLSL_ERROR, GST_GLSL_ERROR_COMPILE, "Failed to create shader object"); data->result = FALSE; return; } n_vertex_sources = data->stage->priv->n_strings; _maybe_prepend_version (data->stage, priv->strings, &n_vertex_sources, &vertex_sources); GST_TRACE_OBJECT (data->stage, "compiling shader:"); for (i = 0; i < n_vertex_sources; i++) { GST_TRACE_OBJECT (data->stage, "%s", vertex_sources[i]); } gl->ShaderSource (priv->handle, n_vertex_sources, (const gchar **) vertex_sources, NULL); gl->CompileShader (priv->handle); g_free (vertex_sources); /* FIXME: supported threaded GLSL compilers and don't destroy compilation * performance by getting the compilation result directly after compilation */ status = GL_FALSE; vtable->GetShaderiv (priv->handle, GL_COMPILE_STATUS, &status); len = 0; vtable->GetShaderInfoLog (priv->handle, sizeof (info_buffer) - 1, &len, info_buffer); info_buffer[len] = '\0'; if (status != GL_TRUE) { GST_ERROR_OBJECT (data->stage, "%s shader compilation failed:%s", _shader_type_to_string (priv->type), info_buffer); g_set_error (data->error, GST_GLSL_ERROR, GST_GLSL_ERROR_COMPILE, "%s shader compilation failed:%s", _shader_type_to_string (priv->type), info_buffer); vtable->DeleteShader (priv->handle); data->result = FALSE; return; } else if (len > 1) { GST_FIXME_OBJECT (data->stage, "%s shader info log:%s", _shader_type_to_string (priv->type), info_buffer); } data->result = TRUE; } /** * gst_glsl_stage_compile: * @stage: a #GstGLSLStage * @error: a #GError to use on failure * * Returns: whether the compilation suceeded * * Since: 1.8 */ gboolean gst_glsl_stage_compile (GstGLSLStage * stage, GError ** error) { struct compile data; g_return_val_if_fail (GST_IS_GLSL_STAGE (stage), FALSE); if (!stage->priv->strings) { g_set_error (error, GST_GLSL_ERROR, GST_GLSL_ERROR_COMPILE, "No shader source to compile"); return FALSE; } data.stage = stage; data.error = error; gst_gl_context_thread_add (stage->context, (GstGLContextThreadFunc) _compile_shader, &data); stage->priv->compiled = TRUE; return data.result; }