/*
*
* LiveWallpaper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* Copyright (C) 2012-2016 Maximilian Schnarr
*
*/
/**
* SECTION: program
* @Short_description: OpenGL program wrapper
*
* #LwProgram is an easy way to use GLSL shaders in a LiveWallpaper plugin.
*
*
*
* LwProgram is not finished yet. It is possible that parts of this type
* will be changed in a future version of LiveWallpaper.
*
*
*
*
* Using LwProgram
*
* GError *error = NULL;
* LwProgram *prog = g_object_new(LW_TYPE_PROGRAM, NULL);
*
* lw_program_create_and_attach_shader(prog, "/path/to/shader.glsl", GL_VERTEX_SHADER, &error);
* if(error != NULL)
* {
* g_warning("Could not load vertex shader: %s", error->message);
* g_error_free(error);
* return;
* }
*
* lw_program_link(prog);
*
* ...
*
* lw_prog_enable(prog);
*
* // The program is enabled for all following drawing operations
* ...
*
* lw_prog_disable(prog);
*
*
* The noise plugin makes use of the #LwProgram object. Take a look at the source code of that
* plugin to see a full working example for #LwProgram.
*/
#include
#include
struct _LwProgramPrivate
{
guint name;
GArray *tex_units;
};
/**
* LwGLSLType:
* @LW_GLSL_TYPE_FLOAT: Represents the GLSL type float
* @LW_GLSL_TYPE_VEC2: Represents the GLSL type vec2
* @LW_GLSL_TYPE_VEC3: Represents the GLSL type vec3
* @LW_GLSL_TYPE_VEC4: Represents the GLSL type vec4
* @LW_GLSL_TYPE_INT: Represents the GLSL type int
* @LW_GLSL_TYPE_IVEC2: Represents the GLSL type ivec2
* @LW_GLSL_TYPE_IVEC3: Represents the GLSL type ivec3
* @LW_GLSL_TYPE_IVEC4: Represents the GLSL type ivec4
* @LW_GLSL_TYPE_BOOL: Represents the GLSL type bool
* @LW_GLSL_TYPE_BVEC2: Represents the GLSL type bvec2
* @LW_GLSL_TYPE_BVEC3: Represents the GLSL type bvec3
* @LW_GLSL_TYPE_BVEC4: Represents the GLSL type bvec4
*
* Since: 0.5
*/
static void
array_clear_func(gpointer pointer)
{
g_free(*(gchar**)pointer);
}
/**
* LwProgram:
*
* A structure for easier OpenGL program handling.
*
* Since: 0.4
*/
G_DEFINE_TYPE(LwProgram, lw_program, G_TYPE_OBJECT)
/**
* lw_program_get_name:
* @self: A #LwProgram
*
* Returns: The program name returned by glCreateProgram
*
* Since: 0.4
*/
guint
lw_program_get_name(LwProgram *self)
{
return self->priv->name;
}
/**
* lw_program_attach_shader:
* @self: A #LwProgram
* @shader: A #LwShader
*
* Attaches the shader to the program. This function uses glAttachShader
* to attach the shader. The #LwShader can be freed after attaching it to a #LwProgram.
*
* Since: 0.4
*/
void
lw_program_attach_shader(LwProgram *self, LwShader *shader)
{
LW_OPENGL_1_4_HELPER(glAttachShader, glAttachObjectARB, (self->priv->name, lw_shader_get_name(shader)));
}
/**
* lw_program_link:
* @self: A #LwProgram
*
* Links the program using glLinkProgram.
* If an error occurs, this function returns %FALSE and prints a warning.
*
* Returns: %TRUE on success, %FALSE if an error occured
*
* Since: 0.4
*/
gboolean
lw_program_link(LwProgram *self)
{
int status;
LW_OPENGL_1_4_HELPER(glLinkProgram, glLinkProgramARB, (self->priv->name));
/* Reset tex units */
g_array_free(self->priv->tex_units, TRUE);
self->priv->tex_units = g_array_new(FALSE, FALSE, sizeof(gchar*));
g_array_set_clear_func(self->priv->tex_units, array_clear_func);
/* Handle errors */
LW_OPENGL_1_4_HELPER(glGetProgramiv, glGetObjectParameterivARB, (self->priv->name, GL_LINK_STATUS, &status));
if(status == GL_FALSE)
{
int log_length;
gchar *log_buffer;
LW_OPENGL_1_4_HELPER(glGetProgramiv, glGetObjectParameterivARB, (self->priv->name, GL_INFO_LOG_LENGTH, &log_length));
log_buffer = g_malloc(log_length * sizeof(gchar));
LW_OPENGL_1_4_HELPER(glGetProgramInfoLog, glGetInfoLogARB, (self->priv->name, log_length, NULL, log_buffer));
g_warning("%s", log_buffer);
g_free(log_buffer);
return FALSE;
}
return TRUE;
}
/**
* lw_program_create_and_attach_shader:
* @self: A #LwProgram
* @path: The file containing the shader's source code
* @type: GL_VERTEX_SHADER or GL_FRAGMENT_SHADER
*
* This function creates, compiles and attaches a shader to the program.
* It is easier to use this function instead of creating, compiling and
* attaching the shader by yourself.
*
* Returns: %TRUE on success, %FALSE otherwise
*
* Since: 0.4
*/
gboolean
lw_program_create_and_attach_shader(LwProgram *self, const gchar *path, guint type)
{
LwShader *shader = lw_shader_new_from_uri(path, type);
if(shader != NULL)
{
lw_shader_compile(shader);
lw_program_attach_shader(self, shader);
g_object_unref(shader);
return TRUE;
}
return FALSE;
}
/**
* lw_program_create_and_attach_shader_from_resource:
* @self: A #LwProgram
* @path: The file containing the shader's source code
* @type: GL_VERTEX_SHADER or GL_FRAGMENT_SHADER
*
* This function creates, compiles and attaches a shader to the program.
* It is easier to use this function instead of creating, compiling and
* attaching the shader by yourself.
*
* Returns: %TRUE on success, %FALSE otherwise
*
* Since: 0.5
*/
gboolean
lw_program_create_and_attach_shader_from_resource (LwProgram *self, const gchar *path, guint type)
{
LwShader *shader = lw_shader_new_from_uri (path, type);
if(shader != NULL)
{
lw_shader_compile(shader);
lw_program_attach_shader(self, shader);
g_object_unref(shader);
return TRUE;
}
return FALSE;
}
/**
* lw_program_get_attrib_location:
* @self: A #LwProgram
* @name: Name of an attribute variable
*
* Calls glGetAttribLocation
* to get the location of an attribute variable.
*
* Returns: The location of an attribute variable or -1 if attribute is not found
*
* Since: 0.4
*/
gint
lw_program_get_attrib_location(LwProgram *self, const gchar *name)
{
gint location = LW_OPENGL_1_4_HELPER(glGetAttribLocation, glGetAttribLocationARB, (self->priv->name, name));
if(location == -1) g_warning("lw_program_get_attrib_location(): "
"Could not find attribute '%s'", name);
return location;
}
/**
* lw_program_get_uniform_location:
* @self: A #LwProgram
* @name: Name of an uniform variable
*
* Calls glGetUniformLocation
* to get the location of an uniform variable.
*
* Returns: The location of an uniform variable or -1 if uniform is not found
*
* Since: 0.4
*/
gint
lw_program_get_uniform_location(LwProgram *self, const gchar *name)
{
gint location = LW_OPENGL_1_4_HELPER(glGetUniformLocation, glGetUniformLocationARB, (self->priv->name, name));
if(location == -1) g_warning("lw_program_get_uniform_location(): "
"Could not find uniform '%s'", name);
return location;
}
static GLint
lw_glsl_type_get_size(LwGLSLType type)
{
switch(type)
{
case LW_GLSL_TYPE_FLOAT:
case LW_GLSL_TYPE_BOOL:
case LW_GLSL_TYPE_INT:
return 1;
case LW_GLSL_TYPE_VEC2:
case LW_GLSL_TYPE_BVEC2:
case LW_GLSL_TYPE_IVEC2:
return 2;
case LW_GLSL_TYPE_VEC3:
case LW_GLSL_TYPE_BVEC3:
case LW_GLSL_TYPE_IVEC3:
return 3;
case LW_GLSL_TYPE_VEC4:
case LW_GLSL_TYPE_BVEC4:
case LW_GLSL_TYPE_IVEC4:
return 4;
default:
g_critical("lw_glsls_type_get_size(): Unknown GLSLType %d", type);
return 0;
}
}
static GLenum
lw_glsl_type_to_gl_type(LwGLSLType type)
{
switch(type)
{
case LW_GLSL_TYPE_FLOAT:
case LW_GLSL_TYPE_VEC2:
case LW_GLSL_TYPE_VEC3:
case LW_GLSL_TYPE_VEC4:
return GL_FLOAT;
case LW_GLSL_TYPE_INT:
case LW_GLSL_TYPE_IVEC2:
case LW_GLSL_TYPE_IVEC3:
case LW_GLSL_TYPE_IVEC4:
case LW_GLSL_TYPE_BOOL:
case LW_GLSL_TYPE_BVEC2:
case LW_GLSL_TYPE_BVEC3:
case LW_GLSL_TYPE_BVEC4:
return GL_INT;
default:
g_critical("lw_glsl_type_to_gl_type(): Unknown GLSLType %d", type);
return GL_INT;
}
}
/**
* lw_program_set_attribute:
* @self: A #LwProgram
* @name: The name of an attribute variable
* @type: The #LwGLSLType which represents the attribute's type
* @buffer: A #LwBuffer
*
* Binds @buffer to the attribute variable @name. Internally this function uses
* glVertexAttribPointer
* to define the vertex attribute array and
* glEnableVertexAttribArray
* to enable it.
*
* This function assumes that the data stored in the @buffer is tightly packed
* (stride is 0) and there is no offset for the first component (pointer is 0, see parameters
* of glVertexAttribPointer).
*
* Since: 0.5
*/
void
lw_program_set_attribute(LwProgram *self, const gchar *name, LwGLSLType type, LwBuffer *buffer)
{
gint location = lw_program_get_attrib_location(self, name);
g_return_if_fail(lw_buffer_get_target(buffer) == GL_ARRAY_BUFFER);
lw_buffer_bind(buffer);
LW_OPENGL_1_4_HELPER(glVertexAttribPointer,
glVertexAttribPointerARB,
(location,
lw_glsl_type_get_size(type),
lw_glsl_type_to_gl_type(type),
GL_FALSE, 0, 0));
LW_OPENGL_1_4_HELPER(glEnableVertexAttribArray,
glEnableVertexAttribArrayARB,
(location));
}
/**
* lw_program_set_texture:
* @self: A #LwProgram
* @name: The name of an uniform variable of type sampler2D
* @texture: A #LwTexture
*
* Binds the uniform variable @name to @texture. This function automatically binds
* the texture to the next free texture unit and so it is able to handle multiple
* textures for one #LwProgram.
*
* This functions calls lw_texture_bind_to(), so you just have to call lw_texture_unbind()
* or lw_texture_disable() if necessary. If you do not unbind the texture after every paint,
* make sure to unbind it in lw_wallpaper_restore_viewport().
*
* Since: 0.5
*/
void
lw_program_set_texture(LwProgram *self, const gchar *name, LwTexture *texture)
{
guint i;
for(i = 0; i < self->priv->tex_units->len; i++)
if(strcmp(name, g_array_index(self->priv->tex_units, gchar*, i)) == 0)
break;
if(i == self->priv->tex_units->len)
{
/* Get next unused texture unit and set uniform */
gint location = lw_program_get_uniform_location(self, name);
gchar *name_copy = g_strdup(name);
g_array_append_val(self->priv->tex_units, name_copy);
LW_OPENGL_1_4_HELPER(glUniform1i, glUniform1iARB, (location, i));
}
lw_texture_bind_to(texture, i);
}
/**
* lw_program_set_matrix:
* @self: A #LwProgram
* @name: The name of an uniform variable of type mat4
* @matrix: A #LwMatrix
*
* Specifies the value of the matrix uniform variable @name.
*
* Since: 0.5
*/
void
lw_program_set_matrix(LwProgram *self, const gchar *name, LwMatrix *matrix)
{
gint location = lw_program_get_uniform_location(self, name);
LW_OPENGL_1_4_HELPER(glUniformMatrix4fv, glUniformMatrix4fvARB, (location, 1, GL_TRUE, lw_matrix_get_elements(matrix)));
}
/**
* lw_program_enable:
* @self: A #LwProgram
*
* Enables the program.
*
* Since: 0.4
*/
void
lw_program_enable(LwProgram *self)
{
LW_OPENGL_1_4_HELPER(glUseProgram, glUseProgramObjectARB, (self->priv->name));
}
/**
* lw_program_disable:
* @self: A #LwProgram
*
* Disables this program.
*
* Since: 0.4
*/
void
lw_program_disable(LwProgram *self G_GNUC_UNUSED)
{
LW_OPENGL_1_4_HELPER(glUseProgram, glUseProgramObjectARB, (0));
}
static void
lw_program_init(LwProgram *self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE(self, LW_TYPE_PROGRAM,
LwProgramPrivate);
self->priv->name = LW_OPENGL_1_4_HELPER(glCreateProgram, glCreateProgramObjectARB, ());
self->priv->tex_units = g_array_new(FALSE, FALSE, sizeof(gchar*));
g_array_set_clear_func(self->priv->tex_units, array_clear_func);
}
static void
lw_program_finalize(GObject *object)
{
LwProgram *self = LW_PROGRAM(object);
if(self->priv->name)
LW_OPENGL_1_4_HELPER(glDeleteProgram, glDeleteObjectARB, (self->priv->name));
g_array_free(self->priv->tex_units, TRUE);
/* Chain up to the parent class */
G_OBJECT_CLASS(lw_program_parent_class)->finalize(object);
}
static void
lw_program_class_init(LwProgramClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
gobject_class->finalize = lw_program_finalize;
g_type_class_add_private(klass, sizeof(LwProgramPrivate));
}