blob: 60a5af7a5d73bf62cedb33004d1f6ec18e43dbd7 [file] [log] [blame]
/*
* GStreamer
* Copyright (C) 2015 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.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gst/gl/gl.h>
#include "gstglslstage.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: an array of strings concatted together to produce a shader
*
* Returns: (transfer full): 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 full): 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 full): 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 full): 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 full): 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: (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;
}