/* * * 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 * * * The LwTexture object is inspired by compiz' GLTexture class. * */ /** * SECTION: texture * @Short_description: OpenGL texture wrapper * * #LwTexture is an easy way to work with OpenGL textures. * * There are three ways to create a #LwTexture. The easiest way is * to load an image file (lw_texture_new_from_file()), but you can also * use an existing #GdkPixbuf (lw_texture_new_from_pixbuf()). The last * option is to create a texture directly out of in-memory image data * (lw_texture_new_from_data()). In each case you should free the #LwTexture * if you don't need it anymore by using g_object_unref(). * * * * LwTexture only supports 2D textures at the moment. * * * * Use lw_texture_enable() to bind the texture. This does everything that is * needed to use the texture. If you are finished with drawing, use lw_texture_disable(). * * * Using a LwTexture * * GError *error = NULL; * LwTexture *tex = lw_texture_new_from_file("/path/to/texture.image", &error); * * if(error != NULL) * { * g_warning("Could not load the texture: %s", error->message); * g_error_free(error); * return; * } * * lw_texture_enable(tex); * * // Draw your textured object here, for example a quad * glBegin(GL_QUADS); * glTexCoord2f(...); * glVertex3f(...); * ... * glEnd(); * * lw_texture_disable(tex); * */ #include /** * LW_TEX_COORD_X: * @m: A #LwTextureMatrix * @px: A X coordinate * * Transforms the X coordinate by a 2D texture matrix. * * Returns: A transformed X coordinate */ /** * LW_TEX_COORD_Y: * @m: A #LwTextureMatrix * @py: A Y coordinate * * Transforms the Y coordinate by a 2D texture matrix. * * Returns: A transformed Y coordinate */ /** * LW_TEX_COORD_XY: * @m: A #LwTextureMatrix * @px: A X coordinate * @py: A Y coordinate * * Transforms the X coordinate by a 2D texture matrix. * * Use this macro if the #LwTextureMatrix rotates the texture coordinates, * otherwise use #LW_TEX_COORD_X. * * Returns: A transformed X coordinate */ /** * LW_TEX_COORD_YX: * @m: A #LwTextureMatrix * @px: A X coordinate * @py: A Y coordinate * * Transforms the Y coordinate by a 2D texture matrix. * * Use this macro if the #LwTextureMatrix rotates the texture coordinates, * otherwise use #LW_TEX_COORD_Y. * * Returns: A transformed Y coordinate */ /** * LwTextureMatrix: * @xx: The xx component (scale and rotation) * @yx: The yx component (rotation only) * @xy: The xy component (rotation only) * @yy: The yy component (scale and rotation) * @x0: The offset in x direction * @y0: The offset in y direction * * A 2D matrix to transform texture coordinates. */ static LwTextureMatrix identity_texture_matrix = { 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f }; static gint max_texture_units = 0; struct _LwTexturePrivate { guint name; guint target; guint filter; guint wrap; gint width; gint height; guint bound_to; }; enum { PROP_0, PROP_TARGET, PROP_WIDTH, PROP_HEIGHT, N_PROPERTIES }; /** * lw_texture_get_max_texture_units: * * Returns: The value of GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS * * Since: 0.5 */ gint lw_texture_get_max_texture_units() { if(max_texture_units == 0) glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &max_texture_units); return max_texture_units; } /** * LwTexture: * @matrix: The #LwTextureMatrix of this texture * * A structure for easier OpenGL texture handling. */ G_DEFINE_TYPE(LwTexture, lw_texture, G_TYPE_OBJECT) /** * lw_texture_new_from_file: * @path: Name of the file to load * * Creates a new texture by loading an image from a file. This function supports * all file formats supported by gdk-pixbuf. If %NULL is returned, then @error will * be set. Internally the image file will be loaded into a #GdkPixbuf first before * creating the #LwTexture. * * Returns: A new #LwTexture, or %NULL in case of an error. You should use * g_object_unref() to free the #LwTexture. * * Since: 0.5 */ LwTexture* lw_texture_new_from_file(const gchar *path) { GError *error = NULL; LwTexture *texture; GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(path, &error); if(error != NULL) { g_warning("Could not load the texture: %s", error->message); g_error_free(error); return NULL; } texture = lw_texture_new_from_pixbuf(pixbuf); g_object_unref(pixbuf); return texture; } /** * lw_texture_new_from_resource: * @path: Name of the resource to load * * Creates a new texture by loading an image from a gresource. This function supports * all file formats supported by gdk-pixbuf. If %NULL is returned, then @error will * be set. Internally the image file will be loaded into a #GdkPixbuf first before * creating the #LwTexture. * * Returns: A new #LwTexture, or %NULL in case of an error. You should use * g_object_unref() to free the #LwTexture. * * Since: 0.5 */ LwTexture* lw_texture_new_from_resource (const gchar *path) { GError *error = NULL; LwTexture *texture; GdkPixbuf *pixbuf = gdk_pixbuf_new_from_resource (path, &error); if(error != NULL) { g_warning("Could not load the texture: %s", error->message); g_error_free(error); return NULL; } texture = lw_texture_new_from_pixbuf(pixbuf); g_object_unref(pixbuf); return texture; } /** * lw_texture_new_from_pixbuf: * @pixbuf: A GdkPixbuf holding the image data * * Creates a new texture by using the image data of the #GdkPixbuf. * * * * You have to free the @pixbuf by yourself. You can free it directly after * calling this function, #LwTexture does not need it anymore. * * * * Returns: A new #LwTexture. You should use g_object_unref() to free the #LwTexture. */ LwTexture* lw_texture_new_from_pixbuf(GdkPixbuf *pixbuf) { guint width = gdk_pixbuf_get_width(pixbuf), height = gdk_pixbuf_get_height(pixbuf), format = (gdk_pixbuf_get_has_alpha(pixbuf)) ? GL_RGBA : GL_RGB; guchar *data = gdk_pixbuf_get_pixels(pixbuf); return lw_texture_new_from_data(data, width, height, format, GL_UNSIGNED_BYTE); } /** * lw_texture_new_from_data: * @data: A pointer to the image data * @width: Width of the image in pixels * @height: Height of the image in pixels * @format: Format of the image data * @type: Data type of the image data * * Creates a new texture out of in-memory image data. Internally glTexImage2D * is used to create the texture. You can find a more detailed description of * @format and @type on the documentation page of glTexImage2D. * * * * You have to free the image @data by yourself. You can free it directly after * calling this function, #LwTexture does not need it anymore. * * * * Returns: A new #LwTexture. You should use g_object_unref() to free the #LwTexture. */ LwTexture* lw_texture_new_from_data(const guchar *data, guint width, guint height, guint format, guint type) { LwTexture *texture; texture = g_object_new(LW_TYPE_TEXTURE, "target", GL_TEXTURE_2D, "width", width, "height", height, NULL); glBindTexture(lw_texture_get_target(texture), lw_texture_get_name(texture)); glTexImage2D(lw_texture_get_target(texture), 0, GL_RGBA, width, height, 0, format, type, data); lw_texture_set_filter(texture, GL_NEAREST); lw_texture_set_wrap(texture, GL_CLAMP_TO_EDGE); return texture; } static void lw_texture_set_property(GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { LwTexture *self = LW_TEXTURE(object); switch(property_id) { case PROP_TARGET: self->priv->target = g_value_get_uint(value); break; case PROP_WIDTH: self->priv->width = g_value_get_uint(value); self->matrix.xx = 1.0f / self->priv->width; break; case PROP_HEIGHT: self->priv->height = g_value_get_uint(value); self->matrix.yy = 1.0f / self->priv->height; break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; } } static void lw_texture_get_property(GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { LwTexture *self = LW_TEXTURE(object); switch(property_id) { case PROP_TARGET: g_value_set_uint(value, self->priv->target); break; case PROP_WIDTH: g_value_set_uint(value, self->priv->width); break; case PROP_HEIGHT: g_value_set_uint(value, self->priv->height); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; } } /** * lw_texture_get_name: * @self: A #LwTexture * * Returns: The texture name returned by glGenTextures */ guint lw_texture_get_name(LwTexture *self) { return self->priv->name; } /** * lw_texture_get_target: * @self: A #LwTexture * * Returns the texture's target. At the moment only GL_TEXTURE_2D is supported, so this function * always returns GL_TEXTURE_2D. * * Returns: The texture target */ guint lw_texture_get_target(LwTexture *self) { return self->priv->target; } /** * lw_texture_set_filter: * @self: A #LwTexture * @filter: The new texture filter * * Sets the texture's minifying and magnification function. Possible values are GL_NEAREST and * GL_LINEAR. GL_NEAREST is generally faster than GL_LINEAR, but it produces sharper edges. * #LwTexture uses GL_NEAREST by default. * * To get a more detailed description of all possible filters, take a look at the documentation * page of glTexParameter. * This functions sets the GL_TEXTURE_MIN_FILTER and GL_TEXTURE_MAG_FILTER at the same time, so * only filters supported by both are vaild arguments. */ void lw_texture_set_filter(LwTexture *self, guint filter) { glBindTexture(self->priv->target, self->priv->name); self->priv->filter = filter; glTexParameteri(self->priv->target, GL_TEXTURE_MIN_FILTER, filter); glTexParameteri(self->priv->target, GL_TEXTURE_MAG_FILTER, filter); glBindTexture(self->priv->target, 0); } /** * lw_texture_get_filter: * @self: A #LwTexture * * Returns: The currently applied texture filter */ guint lw_texture_get_filter(LwTexture *self) { return self->priv->filter; } /** * lw_texture_set_wrap: * @self: A #LwTexture * @wrap: The new wrap parameter * * Sets the wrap parameter for texture coordinate s and t. The wrap parameter is set * to GL_CLAMP_TO_EDGE by default. * * This functions calls glTexParameter * for GL_TEXTURE_WRAP_S and GL_TEXTURE_WRAP_T. You can find a complete list of all supported * parameters at the documentation page of glTexParameter. */ void lw_texture_set_wrap(LwTexture *self, guint wrap) { glBindTexture(self->priv->target, self->priv->name); self->priv->wrap = wrap; glTexParameteri(self->priv->target, GL_TEXTURE_WRAP_S, wrap); glTexParameteri(self->priv->target, GL_TEXTURE_WRAP_T, wrap); glBindTexture(self->priv->target, 0); } /** * lw_texture_get_wrap: * @self: A #LwTexture * * Returns: The current wrap parameter */ guint lw_texture_get_wrap(LwTexture *self) { return self->priv->wrap; } /** * lw_texture_get_width: * @self: A #LwTexture * * Returns: The width of the texture */ guint lw_texture_get_width(LwTexture *self) { return self->priv->width; } /** * lw_texture_get_height: * @self: A #LwTexture * * Returns: The height of the texture */ guint lw_texture_get_height(LwTexture *self) { return self->priv->height; } /** * lw_texture_enable: * @self: A #LwTexture * * Enables the texture target. */ void lw_texture_enable(LwTexture *self) { glEnable(self->priv->target); lw_texture_bind(self); } /** * lw_texture_disable: * @self: A #LwTexture * * Disables the texture target. */ void lw_texture_disable(LwTexture *self) { lw_texture_unbind(self); glDisable(self->priv->target); } /** * lw_texture_bind: * @self: A #LwTexture * * Uses lw_texture_bind_to() to bind this texture to the texture unit 0. If you just need one texture * this is the function to use. If you want to use multiple textures with a fragment shader, take a look * at lw_program_set_texture() or lw_texture_bind_to(). * * Since: 0.5 */ void lw_texture_bind(LwTexture *self) { lw_texture_bind_to(self, 0); } /** * lw_texture_bind_to: * @self: A #LwTexture * @unit: The texture unit to use from 0 to lw_texture_get_max_texture_units() * * Binds the texture @self to the specified texture unit. If you just need one texture, the * lw_texture_bind() function might be simpler to use. You usually do not have to use this * function directly if you use lw_program_set_texture(). * * Before binding the specified texture to its target using glBindTexture, * it switches to the specified texture unit using glActiveTexture. * * Returns: %TRUE on success and %FALSE if @unit is not a valid texture unit * * Since: 0.5 */ gboolean lw_texture_bind_to(LwTexture *self, guint unit) { if((int) unit >= max_texture_units) { g_warning("lw_texture_bind_to(): Could not bind to texture unit %d, " "only %d texture units availabe", unit, max_texture_units); return FALSE; } glActiveTexture(GL_TEXTURE0 + unit); glBindTexture(self->priv->target, self->priv->name); self->priv->bound_to = unit; return TRUE; } /** * lw_texture_unbind: * @self: A #LwTexture * * Unbinds the texture by binding 0 to the texture's target. This operation * switches to the texture unit specified by lw_texture_bind() or lw_texture_bind_to() * using glActiveTexture. * This function unbinds every texture currently bound to the same target and texture unit of @self, * even if @self is not the active texture. * * Since: 0.5 */ void lw_texture_unbind(LwTexture *self) { glActiveTexture(GL_TEXTURE0 + self->priv->bound_to); glBindTexture(self->priv->target, 0); } static void lw_texture_init(LwTexture *self) { self->priv = G_TYPE_INSTANCE_GET_PRIVATE(self, LW_TYPE_TEXTURE, LwTexturePrivate); self->priv->target = GL_TEXTURE_2D; self->priv->filter = GL_NEAREST; self->priv->wrap = GL_CLAMP_TO_EDGE; self->matrix = identity_texture_matrix; glGenTextures(1, &(self->priv->name)); lw_texture_get_max_texture_units(); } static void lw_texture_finalize(GObject *object) { LwTexture *self = LW_TEXTURE(object); if(self->priv->name) glDeleteTextures(1, &(self->priv->name)); /* Chain up to the parent class */ G_OBJECT_CLASS(lw_texture_parent_class)->finalize(object); } static void lw_texture_class_init(LwTextureClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS(klass); gobject_class->set_property = lw_texture_set_property; gobject_class->get_property = lw_texture_get_property; gobject_class->finalize = lw_texture_finalize; g_type_class_add_private(klass, sizeof(LwTexturePrivate)); /** * LwTexture:target: * * The texture's target */ g_object_class_install_property(gobject_class, PROP_TARGET, g_param_spec_uint("target", "Target", "The texture's target", 0, G_MAXUINT, GL_TEXTURE_2D, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); /** * LwTexture:width: * * The width of the texture */ g_object_class_install_property(gobject_class, PROP_WIDTH, g_param_spec_uint("width", "Width", "The width of the texture", 0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); /** * LwTexture:height: * * The height of the texture */ g_object_class_install_property(gobject_class, PROP_HEIGHT, g_param_spec_uint("height", "Height", "The height of the texture", 0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); }