/* * Cogl * * An object oriented GL/GLES Abstraction/Utility Layer * * Copyright (C) 2008,2009 Intel Corporation. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * * * Authors: * Robert Bragg */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "cogl.h" #include "cogl-internal.h" #include "cogl-context.h" #include "cogl-handle.h" #include "cogl-material-private.h" #include "cogl-texture-private.h" #include "cogl-blend-string.h" #include "cogl-journal-private.h" #include #include /* * GL/GLES compatability defines for material thingies: */ #ifdef HAVE_COGL_GLES2 #include "../gles/cogl-gles2-wrapper.h" #endif #ifdef HAVE_COGL_GL #define glActiveTexture ctx->drv.pf_glActiveTexture #define glClientActiveTexture ctx->drv.pf_glClientActiveTexture #define glBlendFuncSeparate ctx->drv.pf_glBlendFuncSeparate #define glBlendEquation ctx->drv.pf_glBlendEquation #define glBlendColor ctx->drv.pf_glBlendColor #define glBlendEquationSeparate ctx->drv.pf_glBlendEquationSeparate #endif /* This isn't defined in the GLES headers */ #ifndef GL_CLAMP_TO_BORDER #define GL_CLAMP_TO_BORDER 0x812d #endif static CoglHandle _cogl_material_layer_copy (CoglHandle layer_handle); static void _cogl_material_free (CoglMaterial *tex); static void _cogl_material_layer_free (CoglMaterialLayer *layer); COGL_HANDLE_DEFINE (Material, material); COGL_HANDLE_DEFINE (MaterialLayer, material_layer); /* #define DISABLE_MATERIAL_CACHE 1 */ GQuark _cogl_material_error_quark (void) { return g_quark_from_static_string ("cogl-material-error-quark"); } void _cogl_material_init_default_material (void) { /* Create new - blank - material */ CoglMaterial *material = g_slice_new0 (CoglMaterial); GLubyte *unlit = material->unlit; GLfloat *ambient = material->ambient; GLfloat *diffuse = material->diffuse; GLfloat *specular = material->specular; GLfloat *emission = material->emission; _COGL_GET_CONTEXT (ctx, NO_RETVAL); /* Use the same defaults as the GL spec... */ unlit[0] = 0xff; unlit[1] = 0xff; unlit[2] = 0xff; unlit[3] = 0xff; material->flags |= COGL_MATERIAL_FLAG_DEFAULT_COLOR; /* Use the same defaults as the GL spec... */ ambient[0] = 0.2; ambient[1] = 0.2; ambient[2] = 0.2; ambient[3] = 1.0; diffuse[0] = 0.8; diffuse[1] = 0.8; diffuse[2] = 0.8; diffuse[3] = 1.0; specular[0] = 0; specular[1] = 0; specular[2] = 0; specular[3] = 1.0; emission[0] = 0; emission[1] = 0; emission[2] = 0; emission[3] = 1.0; material->flags |= COGL_MATERIAL_FLAG_DEFAULT_GL_MATERIAL; /* Use the same defaults as the GL spec... */ material->alpha_func = COGL_MATERIAL_ALPHA_FUNC_ALWAYS; material->alpha_func_reference = 0.0; material->flags |= COGL_MATERIAL_FLAG_DEFAULT_ALPHA_FUNC; /* Not the same as the GL default, but seems saner... */ #ifndef HAVE_COGL_GLES material->blend_equation_rgb = GL_FUNC_ADD; material->blend_equation_alpha = GL_FUNC_ADD; material->blend_src_factor_alpha = GL_ONE; material->blend_dst_factor_alpha = GL_ONE_MINUS_SRC_ALPHA; material->blend_constant[0] = 0; material->blend_constant[1] = 0; material->blend_constant[2] = 0; material->blend_constant[3] = 0; #endif material->blend_src_factor_rgb = GL_ONE; material->blend_dst_factor_rgb = GL_ONE_MINUS_SRC_ALPHA; material->flags |= COGL_MATERIAL_FLAG_DEFAULT_BLEND; material->layers = NULL; material->n_layers = 0; ctx->default_material = _cogl_material_handle_new (material); } CoglHandle cogl_material_copy (CoglHandle handle) { CoglMaterial *material = g_slice_new (CoglMaterial); GList *l; _COGL_GET_CONTEXT (ctx, COGL_INVALID_HANDLE); memcpy (material, handle, sizeof (CoglMaterial)); material->layers = g_list_copy (material->layers); for (l = material->layers; l; l = l->next) l->data = _cogl_material_layer_copy (l->data); return _cogl_material_handle_new (material); } CoglHandle cogl_material_new (void) { _COGL_GET_CONTEXT (ctx, COGL_INVALID_HANDLE); return cogl_material_copy (ctx->default_material); } static void _cogl_material_free (CoglMaterial *material) { /* Frees material resources but its handle is not released! Do that separately before this! */ g_list_foreach (material->layers, (GFunc)cogl_handle_unref, NULL); g_list_free (material->layers); g_slice_free (CoglMaterial, material); } static gboolean _cogl_material_needs_blending_enabled (CoglMaterial *material, GLubyte *override_color) { GList *tmp; /* XXX: If we expose manual control over ENABLE_BLEND, we'll add * a flag to know when it's user configured, so we don't trash it */ /* XXX: Uncomment this to disable all blending */ #if 0 return; #endif if ((override_color && override_color[3] != 0xff) || material->unlit[3] != 0xff || material->ambient[3] != 1.0f || material->diffuse[3] != 1.0f || material->specular[3] != 1.0f || material->emission[3] != 1.0f) return TRUE; for (tmp = material->layers; tmp != NULL; tmp = tmp->next) { CoglMaterialLayer *layer = tmp->data; /* NB: A layer may have a combine mode set on it but not yet have an * associated texture. */ if (!layer->texture) continue; if (cogl_texture_get_format (layer->texture) & COGL_A_BIT) return TRUE; } return FALSE; } static void handle_automatic_blend_enable (CoglMaterial *material) { material->flags &= ~COGL_MATERIAL_FLAG_ENABLE_BLEND; if (_cogl_material_needs_blending_enabled (material, NULL)) material->flags |= COGL_MATERIAL_FLAG_ENABLE_BLEND; } /* If primitives have been logged in the journal referencing the current * state of this material we need to flush the journal before we can * modify it... */ static void _cogl_material_pre_change_notify (CoglMaterial *material, gboolean only_color_change, GLubyte *new_color) { /* XXX: We don't usually need to flush the journal just due to color changes * since material colors are logged in the journals vertex buffer. The * exception is when the change in color enables or disables the need for * blending. */ if (only_color_change) { gboolean will_need_blending = _cogl_material_needs_blending_enabled (material, new_color); if (will_need_blending == (material->flags & COGL_MATERIAL_FLAG_ENABLE_BLEND) ? TRUE : FALSE) return; } if (material->journal_ref_count) _cogl_journal_flush (); } void cogl_material_get_color (CoglHandle handle, CoglColor *color) { CoglMaterial *material; g_return_if_fail (cogl_is_material (handle)); material = _cogl_material_pointer_from_handle (handle); cogl_color_set_from_4ub (color, material->unlit[0], material->unlit[1], material->unlit[2], material->unlit[3]); } /* This is used heavily by the cogl journal when logging quads */ void _cogl_material_get_colorubv (CoglHandle handle, guint8 *color) { CoglMaterial *material = _cogl_material_pointer_from_handle (handle); memcpy (color, material->unlit, 4); } void cogl_material_set_color (CoglHandle handle, const CoglColor *unlit_color) { CoglMaterial *material; GLubyte unlit[4]; g_return_if_fail (cogl_is_material (handle)); material = _cogl_material_pointer_from_handle (handle); unlit[0] = cogl_color_get_red_byte (unlit_color); unlit[1] = cogl_color_get_green_byte (unlit_color); unlit[2] = cogl_color_get_blue_byte (unlit_color); unlit[3] = cogl_color_get_alpha_byte (unlit_color); if (memcmp (unlit, material->unlit, sizeof (unlit)) == 0) return; /* possibly flush primitives referencing the current state... */ _cogl_material_pre_change_notify (material, TRUE, unlit); memcpy (material->unlit, unlit, sizeof (unlit)); material->flags &= ~COGL_MATERIAL_FLAG_DEFAULT_COLOR; if (unlit[0] == 0xff && unlit[1] == 0xff && unlit[2] == 0xff && unlit[3] == 0xff) material->flags |= COGL_MATERIAL_FLAG_DEFAULT_COLOR; handle_automatic_blend_enable (material); } void cogl_material_set_color4ub (CoglHandle handle, guint8 red, guint8 green, guint8 blue, guint8 alpha) { CoglColor color; cogl_color_set_from_4ub (&color, red, green, blue, alpha); cogl_material_set_color (handle, &color); } void cogl_material_set_color4f (CoglHandle handle, float red, float green, float blue, float alpha) { CoglColor color; cogl_color_set_from_4f (&color, red, green, blue, alpha); cogl_material_set_color (handle, &color); } void cogl_material_get_ambient (CoglHandle handle, CoglColor *ambient) { CoglMaterial *material; g_return_if_fail (cogl_is_material (handle)); material = _cogl_material_pointer_from_handle (handle); cogl_color_set_from_4f (ambient, material->ambient[0], material->ambient[1], material->ambient[2], material->ambient[3]); } void cogl_material_set_ambient (CoglHandle handle, const CoglColor *ambient_color) { CoglMaterial *material; GLfloat *ambient; g_return_if_fail (cogl_is_material (handle)); material = _cogl_material_pointer_from_handle (handle); /* possibly flush primitives referencing the current state... */ _cogl_material_pre_change_notify (material, FALSE, NULL); ambient = material->ambient; ambient[0] = cogl_color_get_red_float (ambient_color); ambient[1] = cogl_color_get_green_float (ambient_color); ambient[2] = cogl_color_get_blue_float (ambient_color); ambient[3] = cogl_color_get_alpha_float (ambient_color); material->flags &= ~COGL_MATERIAL_FLAG_DEFAULT_GL_MATERIAL; handle_automatic_blend_enable (material); } void cogl_material_get_diffuse (CoglHandle handle, CoglColor *diffuse) { CoglMaterial *material; g_return_if_fail (cogl_is_material (handle)); material = _cogl_material_pointer_from_handle (handle); cogl_color_set_from_4f (diffuse, material->diffuse[0], material->diffuse[1], material->diffuse[2], material->diffuse[3]); } void cogl_material_set_diffuse (CoglHandle handle, const CoglColor *diffuse_color) { CoglMaterial *material; GLfloat *diffuse; g_return_if_fail (cogl_is_material (handle)); material = _cogl_material_pointer_from_handle (handle); /* possibly flush primitives referencing the current state... */ _cogl_material_pre_change_notify (material, FALSE, NULL); diffuse = material->diffuse; diffuse[0] = cogl_color_get_red_float (diffuse_color); diffuse[1] = cogl_color_get_green_float (diffuse_color); diffuse[2] = cogl_color_get_blue_float (diffuse_color); diffuse[3] = cogl_color_get_alpha_float (diffuse_color); material->flags &= ~COGL_MATERIAL_FLAG_DEFAULT_GL_MATERIAL; handle_automatic_blend_enable (material); } void cogl_material_set_ambient_and_diffuse (CoglHandle handle, const CoglColor *color) { cogl_material_set_ambient (handle, color); cogl_material_set_diffuse (handle, color); } void cogl_material_get_specular (CoglHandle handle, CoglColor *specular) { CoglMaterial *material; g_return_if_fail (cogl_is_material (handle)); material = _cogl_material_pointer_from_handle (handle); cogl_color_set_from_4f (specular, material->specular[0], material->specular[1], material->specular[2], material->specular[3]); } void cogl_material_set_specular (CoglHandle handle, const CoglColor *specular_color) { CoglMaterial *material; GLfloat *specular; g_return_if_fail (cogl_is_material (handle)); material = _cogl_material_pointer_from_handle (handle); /* possibly flush primitives referencing the current state... */ _cogl_material_pre_change_notify (material, FALSE, NULL); specular = material->specular; specular[0] = cogl_color_get_red_float (specular_color); specular[1] = cogl_color_get_green_float (specular_color); specular[2] = cogl_color_get_blue_float (specular_color); specular[3] = cogl_color_get_alpha_float (specular_color); material->flags &= ~COGL_MATERIAL_FLAG_DEFAULT_GL_MATERIAL; handle_automatic_blend_enable (material); } float cogl_material_get_shininess (CoglHandle handle) { CoglMaterial *material; g_return_val_if_fail (cogl_is_material (handle), 0); material = _cogl_material_pointer_from_handle (handle); return material->shininess; } void cogl_material_set_shininess (CoglHandle handle, float shininess) { CoglMaterial *material; g_return_if_fail (cogl_is_material (handle)); if (shininess < 0.0 || shininess > 1.0) g_warning ("Out of range shininess %f supplied for material\n", shininess); material = _cogl_material_pointer_from_handle (handle); /* possibly flush primitives referencing the current state... */ _cogl_material_pre_change_notify (material, FALSE, NULL); material->shininess = (GLfloat)shininess * 128.0; material->flags &= ~COGL_MATERIAL_FLAG_DEFAULT_GL_MATERIAL; } void cogl_material_get_emission (CoglHandle handle, CoglColor *emission) { CoglMaterial *material; g_return_if_fail (cogl_is_material (handle)); material = _cogl_material_pointer_from_handle (handle); cogl_color_set_from_4f (emission, material->emission[0], material->emission[1], material->emission[2], material->emission[3]); } void cogl_material_set_emission (CoglHandle handle, const CoglColor *emission_color) { CoglMaterial *material; GLfloat *emission; g_return_if_fail (cogl_is_material (handle)); material = _cogl_material_pointer_from_handle (handle); /* possibly flush primitives referencing the current state... */ _cogl_material_pre_change_notify (material, FALSE, NULL); emission = material->emission; emission[0] = cogl_color_get_red_float (emission_color); emission[1] = cogl_color_get_green_float (emission_color); emission[2] = cogl_color_get_blue_float (emission_color); emission[3] = cogl_color_get_alpha_float (emission_color); material->flags &= ~COGL_MATERIAL_FLAG_DEFAULT_GL_MATERIAL; handle_automatic_blend_enable (material); } void cogl_material_set_alpha_test_function (CoglHandle handle, CoglMaterialAlphaFunc alpha_func, float alpha_reference) { CoglMaterial *material; g_return_if_fail (cogl_is_material (handle)); material = _cogl_material_pointer_from_handle (handle); /* possibly flush primitives referencing the current state... */ _cogl_material_pre_change_notify (material, FALSE, NULL); material->alpha_func = alpha_func; material->alpha_func_reference = (GLfloat)alpha_reference; material->flags &= ~COGL_MATERIAL_FLAG_DEFAULT_ALPHA_FUNC; } GLenum arg_to_gl_blend_factor (CoglBlendStringArgument *arg) { if (arg->source.is_zero) return GL_ZERO; if (arg->factor.is_one) return GL_ONE; else if (arg->factor.is_src_alpha_saturate) return GL_SRC_ALPHA_SATURATE; else if (arg->factor.source.info->type == COGL_BLEND_STRING_COLOR_SOURCE_SRC_COLOR) { if (arg->factor.source.mask == COGL_BLEND_STRING_CHANNEL_MASK_RGB) { if (arg->factor.source.one_minus) return GL_ONE_MINUS_SRC_COLOR; else return GL_SRC_COLOR; } else { if (arg->factor.source.one_minus) return GL_ONE_MINUS_SRC_ALPHA; else return GL_SRC_ALPHA; } } else if (arg->factor.source.info->type == COGL_BLEND_STRING_COLOR_SOURCE_DST_COLOR) { if (arg->factor.source.mask == COGL_BLEND_STRING_CHANNEL_MASK_RGB) { if (arg->factor.source.one_minus) return GL_ONE_MINUS_DST_COLOR; else return GL_DST_COLOR; } else { if (arg->factor.source.one_minus) return GL_ONE_MINUS_DST_ALPHA; else return GL_DST_ALPHA; } } #ifndef HAVE_COGL_GLES else if (arg->factor.source.info->type == COGL_BLEND_STRING_COLOR_SOURCE_CONSTANT) { if (arg->factor.source.mask == COGL_BLEND_STRING_CHANNEL_MASK_RGB) { if (arg->factor.source.one_minus) return GL_ONE_MINUS_CONSTANT_COLOR; else return GL_CONSTANT_COLOR; } else { if (arg->factor.source.one_minus) return GL_ONE_MINUS_CONSTANT_ALPHA; else return GL_CONSTANT_ALPHA; } } #endif g_warning ("Unable to determine valid blend factor from blend string\n"); return GL_ONE; } void setup_blend_state (CoglBlendStringStatement *statement, GLenum *blend_equation, GLint *blend_src_factor, GLint *blend_dst_factor) { #ifndef HAVE_COGL_GLES switch (statement->function->type) { case COGL_BLEND_STRING_FUNCTION_ADD: *blend_equation = GL_FUNC_ADD; break; /* TODO - add more */ default: g_warning ("Unsupported blend function given"); *blend_equation = GL_FUNC_ADD; } #endif *blend_src_factor = arg_to_gl_blend_factor (&statement->args[0]); *blend_dst_factor = arg_to_gl_blend_factor (&statement->args[1]); } gboolean cogl_material_set_blend (CoglHandle handle, const char *blend_description, GError **error) { CoglMaterial *material; CoglBlendStringStatement statements[2]; CoglBlendStringStatement split[2]; CoglBlendStringStatement *rgb; CoglBlendStringStatement *a; GError *internal_error = NULL; int count; g_return_val_if_fail (cogl_is_material (handle), FALSE); material = _cogl_material_pointer_from_handle (handle); count = _cogl_blend_string_compile (blend_description, COGL_BLEND_STRING_CONTEXT_BLENDING, statements, &internal_error); if (!count) { if (error) g_propagate_error (error, internal_error); else { g_warning ("Cannot compile blend description: %s\n", internal_error->message); g_error_free (internal_error); } return FALSE; } if (statements[0].mask == COGL_BLEND_STRING_CHANNEL_MASK_RGBA) { _cogl_blend_string_split_rgba_statement (statements, &split[0], &split[1]); rgb = &split[0]; a = &split[1]; } else { rgb = &statements[0]; a = &statements[1]; } /* possibly flush primitives referencing the current state... */ _cogl_material_pre_change_notify (material, FALSE, NULL); #ifndef HAVE_COGL_GLES setup_blend_state (rgb, &material->blend_equation_rgb, &material->blend_src_factor_rgb, &material->blend_dst_factor_rgb); setup_blend_state (a, &material->blend_equation_alpha, &material->blend_src_factor_alpha, &material->blend_dst_factor_alpha); #else setup_blend_state (rgb, NULL, &material->blend_src_factor_rgb, &material->blend_dst_factor_rgb); #endif material->flags &= ~COGL_MATERIAL_FLAG_DEFAULT_BLEND; return TRUE; } void cogl_material_set_blend_constant (CoglHandle handle, CoglColor *constant_color) { #ifndef HAVE_COGL_GLES CoglMaterial *material; GLfloat *constant; g_return_if_fail (cogl_is_material (handle)); material = _cogl_material_pointer_from_handle (handle); /* possibly flush primitives referencing the current state... */ _cogl_material_pre_change_notify (material, FALSE, NULL); constant = material->blend_constant; constant[0] = cogl_color_get_red_float (constant_color); constant[1] = cogl_color_get_green_float (constant_color); constant[2] = cogl_color_get_blue_float (constant_color); constant[3] = cogl_color_get_alpha_float (constant_color); material->flags &= ~COGL_MATERIAL_FLAG_DEFAULT_BLEND; #endif } /* Asserts that a layer corresponding to the given index exists. If no * match is found, then a new empty layer is added. */ static CoglMaterialLayer * _cogl_material_get_layer (CoglMaterial *material, int index_, gboolean create_if_not_found) { CoglMaterialLayer *layer; GList *tmp; CoglHandle layer_handle; for (tmp = material->layers; tmp != NULL; tmp = tmp->next) { layer = _cogl_material_layer_pointer_from_handle ((CoglHandle)tmp->data); if (layer->index == index_) return layer; /* The layers are always sorted, so at this point we know this layer * doesn't exist */ if (layer->index > index_) break; } /* NB: if we now insert a new layer before tmp, that will maintain order. */ if (!create_if_not_found) return NULL; /* possibly flush primitives referencing the current state... */ _cogl_material_pre_change_notify (material, FALSE, NULL); layer = g_slice_new0 (CoglMaterialLayer); layer_handle = _cogl_material_layer_handle_new (layer); layer->index = index_; layer->flags = COGL_MATERIAL_LAYER_FLAG_DEFAULT_COMBINE; layer->mag_filter = COGL_MATERIAL_FILTER_LINEAR; layer->min_filter = COGL_MATERIAL_FILTER_LINEAR; layer->wrap_mode_s = COGL_MATERIAL_WRAP_MODE_AUTOMATIC; layer->wrap_mode_t = COGL_MATERIAL_WRAP_MODE_AUTOMATIC; layer->wrap_mode_r = COGL_MATERIAL_WRAP_MODE_AUTOMATIC; layer->texture = COGL_INVALID_HANDLE; /* Choose the same default combine mode as OpenGL: * MODULATE(PREVIOUS[RGBA],TEXTURE[RGBA]) */ layer->texture_combine_rgb_func = GL_MODULATE; layer->texture_combine_rgb_src[0] = GL_PREVIOUS; layer->texture_combine_rgb_src[1] = GL_TEXTURE; layer->texture_combine_rgb_op[0] = GL_SRC_COLOR; layer->texture_combine_rgb_op[1] = GL_SRC_COLOR; layer->texture_combine_alpha_func = GL_MODULATE; layer->texture_combine_alpha_src[0] = GL_PREVIOUS; layer->texture_combine_alpha_src[1] = GL_TEXTURE; layer->texture_combine_alpha_op[0] = GL_SRC_ALPHA; layer->texture_combine_alpha_op[1] = GL_SRC_ALPHA; cogl_matrix_init_identity (&layer->matrix); /* Note: see comment after for() loop above */ material->layers = g_list_insert_before (material->layers, tmp, layer_handle); return layer; } void cogl_material_set_layer (CoglHandle material_handle, int layer_index, CoglHandle texture_handle) { CoglMaterial *material; CoglMaterialLayer *layer; g_return_if_fail (cogl_is_material (material_handle)); g_return_if_fail (texture_handle == COGL_INVALID_HANDLE || cogl_is_texture (texture_handle)); material = _cogl_material_pointer_from_handle (material_handle); layer = _cogl_material_get_layer (material, layer_index, TRUE); if (texture_handle == layer->texture) return; /* possibly flush primitives referencing the current state... */ _cogl_material_pre_change_notify (material, FALSE, NULL); material->n_layers = g_list_length (material->layers); if (material->n_layers > _cogl_get_max_texture_image_units ()) { if (!(material->flags & COGL_MATERIAL_FLAG_SHOWN_SAMPLER_WARNING)) { g_warning ("Your hardware does not have enough texture samplers " "to handle this many texture layers"); material->flags |= COGL_MATERIAL_FLAG_SHOWN_SAMPLER_WARNING; } /* Note: We always make a best effort attempt to display as many * layers as possible, so this isn't an _error_ */ /* Note: in the future we may support enabling/disabling layers * too, so it may become valid to add more than * MAX_COMBINED_TEXTURE_IMAGE_UNITS layers. */ } if (texture_handle) cogl_handle_ref (texture_handle); if (layer->texture) cogl_handle_unref (layer->texture); layer->texture = texture_handle; handle_automatic_blend_enable (material); layer->flags |= COGL_MATERIAL_LAYER_FLAG_DIRTY; } static void setup_texture_combine_state (CoglBlendStringStatement *statement, GLint *texture_combine_func, GLint *texture_combine_src, GLint *texture_combine_op) { int i; switch (statement->function->type) { case COGL_BLEND_STRING_FUNCTION_AUTO_COMPOSITE: *texture_combine_func = GL_MODULATE; /* FIXME */ break; case COGL_BLEND_STRING_FUNCTION_REPLACE: *texture_combine_func = GL_REPLACE; break; case COGL_BLEND_STRING_FUNCTION_MODULATE: *texture_combine_func = GL_MODULATE; break; case COGL_BLEND_STRING_FUNCTION_ADD: *texture_combine_func = GL_ADD; break; case COGL_BLEND_STRING_FUNCTION_ADD_SIGNED: *texture_combine_func = GL_ADD_SIGNED; break; case COGL_BLEND_STRING_FUNCTION_INTERPOLATE: *texture_combine_func = GL_INTERPOLATE; break; case COGL_BLEND_STRING_FUNCTION_SUBTRACT: *texture_combine_func = GL_SUBTRACT; break; case COGL_BLEND_STRING_FUNCTION_DOT3_RGB: *texture_combine_func = GL_DOT3_RGB; break; case COGL_BLEND_STRING_FUNCTION_DOT3_RGBA: *texture_combine_func = GL_DOT3_RGBA; break; } for (i = 0; i < statement->function->argc; i++) { CoglBlendStringArgument *arg = &statement->args[i]; switch (arg->source.info->type) { case COGL_BLEND_STRING_COLOR_SOURCE_CONSTANT: texture_combine_src[i] = GL_CONSTANT; break; case COGL_BLEND_STRING_COLOR_SOURCE_TEXTURE: texture_combine_src[i] = GL_TEXTURE; break; case COGL_BLEND_STRING_COLOR_SOURCE_TEXTURE_N: texture_combine_src[i] = GL_TEXTURE0 + arg->source.texture; break; case COGL_BLEND_STRING_COLOR_SOURCE_PRIMARY: texture_combine_src[i] = GL_PRIMARY_COLOR; break; case COGL_BLEND_STRING_COLOR_SOURCE_PREVIOUS: texture_combine_src[i] = GL_PREVIOUS; break; default: g_warning ("Unexpected texture combine source"); texture_combine_src[i] = GL_TEXTURE; } if (arg->source.mask == COGL_BLEND_STRING_CHANNEL_MASK_RGB) { if (statement->args[i].source.one_minus) texture_combine_op[i] = GL_ONE_MINUS_SRC_COLOR; else texture_combine_op[i] = GL_SRC_COLOR; } else { if (statement->args[i].source.one_minus) texture_combine_op[i] = GL_ONE_MINUS_SRC_ALPHA; else texture_combine_op[i] = GL_SRC_ALPHA; } } } gboolean cogl_material_set_layer_combine (CoglHandle handle, int layer_index, const char *combine_description, GError **error) { CoglMaterial *material; CoglMaterialLayer *layer; CoglBlendStringStatement statements[2]; CoglBlendStringStatement split[2]; CoglBlendStringStatement *rgb; CoglBlendStringStatement *a; GError *internal_error = NULL; int count; g_return_val_if_fail (cogl_is_material (handle), FALSE); material = _cogl_material_pointer_from_handle (handle); layer = _cogl_material_get_layer (material, layer_index, TRUE); count = _cogl_blend_string_compile (combine_description, COGL_BLEND_STRING_CONTEXT_TEXTURE_COMBINE, statements, &internal_error); if (!count) { if (error) g_propagate_error (error, internal_error); else { g_warning ("Cannot compile combine description: %s\n", internal_error->message); g_error_free (internal_error); } return FALSE; } if (statements[0].mask == COGL_BLEND_STRING_CHANNEL_MASK_RGBA) { _cogl_blend_string_split_rgba_statement (statements, &split[0], &split[1]); rgb = &split[0]; a = &split[1]; } else { rgb = &statements[0]; a = &statements[1]; } /* possibly flush primitives referencing the current state... */ _cogl_material_pre_change_notify (material, FALSE, NULL); setup_texture_combine_state (rgb, &layer->texture_combine_rgb_func, layer->texture_combine_rgb_src, layer->texture_combine_rgb_op); setup_texture_combine_state (a, &layer->texture_combine_alpha_func, layer->texture_combine_alpha_src, layer->texture_combine_alpha_op); layer->flags |= COGL_MATERIAL_LAYER_FLAG_DIRTY; layer->flags &= ~COGL_MATERIAL_LAYER_FLAG_DEFAULT_COMBINE; return TRUE; } void cogl_material_set_layer_combine_constant (CoglHandle handle, int layer_index, CoglColor *constant_color) { CoglMaterial *material; CoglMaterialLayer *layer; GLfloat *constant; g_return_if_fail (cogl_is_material (handle)); material = _cogl_material_pointer_from_handle (handle); layer = _cogl_material_get_layer (material, layer_index, TRUE); /* possibly flush primitives referencing the current state... */ _cogl_material_pre_change_notify (material, FALSE, NULL); constant = layer->texture_combine_constant; constant[0] = cogl_color_get_red_float (constant_color); constant[1] = cogl_color_get_green_float (constant_color); constant[2] = cogl_color_get_blue_float (constant_color); constant[3] = cogl_color_get_alpha_float (constant_color); layer->flags |= COGL_MATERIAL_LAYER_FLAG_DIRTY; layer->flags &= ~COGL_MATERIAL_LAYER_FLAG_DEFAULT_COMBINE; } void cogl_material_set_layer_matrix (CoglHandle material_handle, int layer_index, CoglMatrix *matrix) { CoglMaterial *material; CoglMaterialLayer *layer; g_return_if_fail (cogl_is_material (material_handle)); material = _cogl_material_pointer_from_handle (material_handle); layer = _cogl_material_get_layer (material, layer_index, TRUE); /* possibly flush primitives referencing the current state... */ _cogl_material_pre_change_notify (material, FALSE, NULL); layer->matrix = *matrix; layer->flags |= COGL_MATERIAL_LAYER_FLAG_DIRTY; layer->flags |= COGL_MATERIAL_LAYER_FLAG_HAS_USER_MATRIX; layer->flags &= ~COGL_MATERIAL_LAYER_FLAG_DEFAULT_COMBINE; } static void _cogl_material_layer_free (CoglMaterialLayer *layer) { if (layer->texture != COGL_INVALID_HANDLE) cogl_handle_unref (layer->texture); g_slice_free (CoglMaterialLayer, layer); } void cogl_material_remove_layer (CoglHandle material_handle, int layer_index) { CoglMaterial *material; CoglMaterialLayer *layer; GList *tmp; gboolean notified_change = FALSE; g_return_if_fail (cogl_is_material (material_handle)); material = _cogl_material_pointer_from_handle (material_handle); for (tmp = material->layers; tmp != NULL; tmp = tmp->next) { layer = tmp->data; if (layer->index == layer_index) { CoglHandle handle = (CoglHandle) layer; /* possibly flush primitives referencing the current state... */ if (!notified_change) { _cogl_material_pre_change_notify (material, FALSE, NULL); notified_change = TRUE; } cogl_handle_unref (handle); material->layers = g_list_remove (material->layers, layer); material->n_layers--; break; } } handle_automatic_blend_enable (material); } /* XXX: This API is hopfully just a stop-gap solution. Ideally _cogl_enable * will be replaced. */ unsigned long _cogl_material_get_cogl_enable_flags (CoglHandle material_handle) { CoglMaterial *material; unsigned long enable_flags = 0; _COGL_GET_CONTEXT (ctx, 0); g_return_val_if_fail (cogl_is_material (material_handle), 0); material = _cogl_material_pointer_from_handle (material_handle); /* Enable blending if the geometry has an associated alpha color, * or the material wants blending enabled. */ if (material->flags & COGL_MATERIAL_FLAG_ENABLE_BLEND) enable_flags |= COGL_ENABLE_BLEND; return enable_flags; } /* It's a bit out of the ordinary to return a const GList *, but it's * probably sensible to try and avoid list manipulation for every * primitive emitted in a scene, every frame. * * Alternatively; we could either add a _foreach function, or maybe * a function that gets a passed a buffer (that may be stack allocated) * by the caller. */ const GList * cogl_material_get_layers (CoglHandle material_handle) { CoglMaterial *material; g_return_val_if_fail (cogl_is_material (material_handle), NULL); material = _cogl_material_pointer_from_handle (material_handle); return material->layers; } int cogl_material_get_n_layers (CoglHandle material_handle) { CoglMaterial *material; g_return_val_if_fail (cogl_is_material (material_handle), 0); material = _cogl_material_pointer_from_handle (material_handle); return material->n_layers; } CoglMaterialLayerType cogl_material_layer_get_type (CoglHandle layer_handle) { return COGL_MATERIAL_LAYER_TYPE_TEXTURE; } CoglHandle cogl_material_layer_get_texture (CoglHandle layer_handle) { CoglMaterialLayer *layer; g_return_val_if_fail (cogl_is_material_layer (layer_handle), COGL_INVALID_HANDLE); layer = _cogl_material_layer_pointer_from_handle (layer_handle); return layer->texture; } unsigned long _cogl_material_layer_get_flags (CoglHandle layer_handle) { CoglMaterialLayer *layer; g_return_val_if_fail (cogl_is_material_layer (layer_handle), 0); layer = _cogl_material_layer_pointer_from_handle (layer_handle); return layer->flags & COGL_MATERIAL_LAYER_FLAG_HAS_USER_MATRIX; } static CoglHandle _cogl_material_layer_copy (CoglHandle layer_handle) { CoglMaterialLayer *layer = _cogl_material_layer_pointer_from_handle (layer_handle); CoglMaterialLayer *layer_copy = g_slice_new (CoglMaterialLayer); memcpy (layer_copy, layer, sizeof (CoglMaterialLayer)); if (layer_copy->texture != COGL_INVALID_HANDLE) cogl_handle_ref (layer_copy->texture); return _cogl_material_layer_handle_new (layer_copy); } static unsigned int get_n_args_for_combine_func (GLint func) { switch (func) { case GL_REPLACE: return 1; case GL_MODULATE: case GL_ADD: case GL_ADD_SIGNED: case GL_SUBTRACT: case GL_DOT3_RGB: case GL_DOT3_RGBA: return 2; case GL_INTERPOLATE: return 3; } return 0; } static gboolean is_mipmap_filter (CoglMaterialFilter filter) { return (filter == COGL_MATERIAL_FILTER_NEAREST_MIPMAP_NEAREST || filter == COGL_MATERIAL_FILTER_LINEAR_MIPMAP_NEAREST || filter == COGL_MATERIAL_FILTER_NEAREST_MIPMAP_LINEAR || filter == COGL_MATERIAL_FILTER_LINEAR_MIPMAP_LINEAR); } /* FIXME: All direct manipulation of GL texture unit state should be dealt with * by extending the CoglTextureUnit abstraction */ static void _cogl_material_layer_flush_gl_sampler_state (CoglMaterialLayer *layer, CoglTextureUnit *unit, CoglLayerInfo *gl_layer_info) { int n_rgb_func_args; int n_alpha_func_args; _COGL_GET_CONTEXT (ctx, NO_RETVAL); #ifndef DISABLE_MATERIAL_CACHE if (!(gl_layer_info && gl_layer_info->flags & COGL_MATERIAL_LAYER_FLAG_DEFAULT_COMBINE && (layer->flags & COGL_MATERIAL_LAYER_FLAG_DEFAULT_COMBINE))) #endif { GE (glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE)); /* Set the combiner functions... */ GE (glTexEnvi (GL_TEXTURE_ENV, GL_COMBINE_RGB, layer->texture_combine_rgb_func)); GE (glTexEnvi (GL_TEXTURE_ENV, GL_COMBINE_ALPHA, layer->texture_combine_alpha_func)); /* * Setup the function arguments... */ /* For the RGB components... */ n_rgb_func_args = get_n_args_for_combine_func (layer->texture_combine_rgb_func); GE (glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_RGB, layer->texture_combine_rgb_src[0])); GE (glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_RGB, layer->texture_combine_rgb_op[0])); if (n_rgb_func_args > 1) { GE (glTexEnvi (GL_TEXTURE_ENV, GL_SRC1_RGB, layer->texture_combine_rgb_src[1])); GE (glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND1_RGB, layer->texture_combine_rgb_op[1])); } if (n_rgb_func_args > 2) { GE (glTexEnvi (GL_TEXTURE_ENV, GL_SRC2_RGB, layer->texture_combine_rgb_src[2])); GE (glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND2_RGB, layer->texture_combine_rgb_op[2])); } /* For the Alpha component */ n_alpha_func_args = get_n_args_for_combine_func (layer->texture_combine_alpha_func); GE (glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_ALPHA, layer->texture_combine_alpha_src[0])); GE (glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, layer->texture_combine_alpha_op[0])); if (n_alpha_func_args > 1) { GE (glTexEnvi (GL_TEXTURE_ENV, GL_SRC1_ALPHA, layer->texture_combine_alpha_src[1])); GE (glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, layer->texture_combine_alpha_op[1])); } if (n_alpha_func_args > 2) { GE (glTexEnvi (GL_TEXTURE_ENV, GL_SRC2_ALPHA, layer->texture_combine_alpha_src[2])); GE (glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND2_ALPHA, layer->texture_combine_alpha_op[2])); } GE (glTexEnvfv (GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, layer->texture_combine_constant)); } if ((gl_layer_info && gl_layer_info->flags & COGL_MATERIAL_LAYER_FLAG_HAS_USER_MATRIX) || (layer->flags & COGL_MATERIAL_LAYER_FLAG_HAS_USER_MATRIX)) _cogl_matrix_stack_set (unit->matrix_stack, &layer->matrix); else _cogl_matrix_stack_load_identity (unit->matrix_stack); /* TODO: Eventually we should just have something like * _cogl_flush_texture_units() that we can call in * _cogl_material_flush_layers_gl_state */ _cogl_matrix_stack_flush_to_gl (unit->matrix_stack, COGL_MATRIX_TEXTURE); } void _cogl_material_layer_ensure_mipmaps (CoglHandle layer_handle) { CoglMaterialLayer *layer; layer = _cogl_material_layer_pointer_from_handle (layer_handle); if (layer->texture && (is_mipmap_filter (layer->min_filter) || is_mipmap_filter (layer->mag_filter))) _cogl_texture_ensure_mipmaps (layer->texture); } static void _cogl_material_set_wrap_modes_for_layer (CoglMaterialLayer *layer, int layer_num, CoglHandle tex_handle, const CoglMaterialWrapModeOverrides * wrap_mode_overrides) { GLenum wrap_mode_s, wrap_mode_t, wrap_mode_r; /* Update the wrap mode on the texture object. The texture backend should cache the value so that it will be a no-op if the object already has the same wrap mode set. The backend is best placed to do this because it knows how many of the coordinates will actually be used (ie, a 1D texture only cares about the 's' coordinate but a 3D texture would use all three). GL uses the wrap mode as part of the texture object state but we are pretending it's part of the per-layer environment state. This will break if the application tries to use different modes in different layers using the same texture. */ if (wrap_mode_overrides && wrap_mode_overrides->values[layer_num].s) wrap_mode_s = (wrap_mode_overrides->values[layer_num].s == COGL_MATERIAL_WRAP_MODE_OVERRIDE_REPEAT ? GL_REPEAT : wrap_mode_overrides->values[layer_num].s == COGL_MATERIAL_WRAP_MODE_OVERRIDE_CLAMP_TO_EDGE ? GL_CLAMP_TO_EDGE : GL_CLAMP_TO_BORDER); else if (layer->wrap_mode_s == COGL_MATERIAL_WRAP_MODE_AUTOMATIC) wrap_mode_s = GL_CLAMP_TO_EDGE; else wrap_mode_s = layer->wrap_mode_s; if (wrap_mode_overrides && wrap_mode_overrides->values[layer_num].t) wrap_mode_t = (wrap_mode_overrides->values[layer_num].t == COGL_MATERIAL_WRAP_MODE_OVERRIDE_REPEAT ? GL_REPEAT : wrap_mode_overrides->values[layer_num].t == COGL_MATERIAL_WRAP_MODE_OVERRIDE_CLAMP_TO_EDGE ? GL_CLAMP_TO_EDGE : GL_CLAMP_TO_BORDER); else if (layer->wrap_mode_t == COGL_MATERIAL_WRAP_MODE_AUTOMATIC) wrap_mode_t = GL_CLAMP_TO_EDGE; else wrap_mode_t = layer->wrap_mode_t; if (wrap_mode_overrides && wrap_mode_overrides->values[layer_num].r) wrap_mode_r = (wrap_mode_overrides->values[layer_num].r == COGL_MATERIAL_WRAP_MODE_OVERRIDE_REPEAT ? GL_REPEAT : wrap_mode_overrides->values[layer_num].r == COGL_MATERIAL_WRAP_MODE_OVERRIDE_CLAMP_TO_EDGE ? GL_CLAMP_TO_EDGE : GL_CLAMP_TO_BORDER); else if (layer->wrap_mode_r == COGL_MATERIAL_WRAP_MODE_AUTOMATIC) wrap_mode_r = GL_CLAMP_TO_EDGE; else wrap_mode_r = layer->wrap_mode_r; _cogl_texture_set_wrap_mode_parameters (tex_handle, wrap_mode_s, wrap_mode_t, wrap_mode_r); } /* * _cogl_material_flush_layers_gl_state: * @fallback_mask: is a bitmask of the material layers that need to be * replaced with the default, fallback textures. The fallback textures are * fully transparent textures so they hopefully wont contribute to the * texture combining. * * The intention of fallbacks is to try and preserve * the number of layers the user is expecting so that texture coordinates * they gave will mostly still correspond to the textures they intended, and * have a fighting chance of looking close to their originally intended * result. * * @disable_mask: is a bitmask of the material layers that will simply have * texturing disabled. It's only really intended for disabling all layers * > X; i.e. we'd expect to see a contiguous run of 0 starting from the LSB * and at some point the remaining bits flip to 1. It might work to disable * arbitrary layers; though I'm not sure a.t.m how OpenGL would take to * that. * * The intention of the disable_mask is for emitting geometry when the user * hasn't supplied enough texture coordinates for all the layers and it's * not possible to auto generate default texture coordinates for those * layers. * * @layer0_override_texture: forcibly tells us to bind this GL texture name for * layer 0 instead of plucking the gl_texture from the CoglTexture of layer * 0. * * The intention of this is for any geometry that supports sliced textures. * The code will can iterate each of the slices and re-flush the material * forcing the GL texture of each slice in turn. * * @wrap_mode_overrides: overrides the wrap modes set on each * layer. This is used to implement the automatic wrap mode. * * XXX: It might also help if we could specify a texture matrix for code * dealing with slicing that would be multiplied with the users own matrix. * * Normaly texture coords in the range [0, 1] refer to the extents of the * texture, but when your GL texture represents a slice of the real texture * (from the users POV) then a texture matrix would be a neat way of * transforming the mapping for each slice. * * Currently for textured rectangles we manually calculate the texture * coords for each slice based on the users given coords, but this solution * isn't ideal, and can't be used with CoglVertexBuffers. */ static void _cogl_material_flush_layers_gl_state (CoglMaterial *material, guint32 fallback_mask, guint32 disable_mask, GLuint layer0_override_texture, const CoglMaterialWrapModeOverrides * wrap_mode_overrides) { GList *tmp; int i; _COGL_GET_CONTEXT (ctx, NO_RETVAL); for (tmp = material->layers, i = 0; tmp != NULL && i < _cogl_get_max_texture_image_units (); tmp = tmp->next, i++) { CoglHandle layer_handle = (CoglHandle)tmp->data; CoglMaterialLayer *layer = _cogl_material_layer_pointer_from_handle (layer_handle); CoglLayerInfo *gl_layer_info = NULL; CoglLayerInfo new_gl_layer_info; CoglHandle tex_handle; GLuint gl_texture; GLenum gl_target; CoglTextureUnit *unit; /* Switch units first so we don't disturb the previous unit if * something needs to bind the texture temporarily */ GE (glActiveTexture (GL_TEXTURE0 + i)); unit = _cogl_get_texture_unit (i); _cogl_material_layer_ensure_mipmaps (layer_handle); new_gl_layer_info.layer0_overridden = layer0_override_texture ? TRUE : FALSE; new_gl_layer_info.fallback = (fallback_mask & (1<texture; if (tex_handle != COGL_INVALID_HANDLE) { _cogl_texture_set_filters (tex_handle, layer->min_filter, layer->mag_filter); _cogl_material_set_wrap_modes_for_layer (layer, i, tex_handle, wrap_mode_overrides); cogl_texture_get_gl_texture (tex_handle, &gl_texture, &gl_target); } else { new_gl_layer_info.fallback = TRUE; gl_target = GL_TEXTURE_2D; } if (new_gl_layer_info.layer0_overridden) gl_texture = layer0_override_texture; else if (new_gl_layer_info.fallback) { if (gl_target == GL_TEXTURE_2D) tex_handle = ctx->default_gl_texture_2d_tex; #ifdef HAVE_COGL_GL else if (gl_target == GL_TEXTURE_RECTANGLE_ARB) tex_handle = ctx->default_gl_texture_rect_tex; #endif else { g_warning ("We don't have a default texture we can use to fill " "in for an invalid material layer, since it was " "using an unsupported texture target "); /* might get away with this... */ tex_handle = ctx->default_gl_texture_2d_tex; } cogl_texture_get_gl_texture (tex_handle, &gl_texture, NULL); } /* FIXME: We could be more clever here and only bind the texture if it is different from gl_layer_info->gl_texture to avoid redundant GL calls. However a few other places in Cogl and Clutter call glBindTexture such as ClutterGLXTexturePixmap so we'd need to ensure they affect the cache. Also deleting a texture should clear it from the cache in case a new texture is generated with the same number */ GE (glBindTexture (gl_target, gl_texture)); /* XXX: Once we add caching for glBindTexture state, these * checks should be moved back up to the top of the loop! */ if (i < ctx->current_layers->len) { gl_layer_info = &g_array_index (ctx->current_layers, CoglLayerInfo, i); #ifndef DISABLE_MATERIAL_CACHE if (gl_layer_info->handle == layer_handle && !(layer->flags & COGL_MATERIAL_LAYER_FLAG_DIRTY) && !(gl_layer_info->layer0_overridden || new_gl_layer_info.layer0_overridden) && (gl_layer_info->fallback == new_gl_layer_info.fallback) && (gl_layer_info->disabled == new_gl_layer_info.disabled)) { continue; } #endif } /* Disable the previous target if it was different */ #ifndef DISABLE_MATERIAL_CACHE if (gl_layer_info && gl_layer_info->gl_target != gl_target && !gl_layer_info->disabled) { GE (glDisable (gl_layer_info->gl_target)); } #else if (gl_layer_info) GE (glDisable (gl_layer_info->gl_target)); #endif /* Enable/Disable the new target */ if (!new_gl_layer_info.disabled) { #ifndef DISABLE_MATERIAL_CACHE if (!(gl_layer_info && gl_layer_info->gl_target == gl_target && !gl_layer_info->disabled)) #endif { /* XXX: Debug: Comment this out to disable all texturing: */ #if 1 GE (glEnable (gl_target)); #endif } } else { #ifndef DISABLE_MATERIAL_CACHE if (!(gl_layer_info && gl_layer_info->gl_target == gl_target && gl_layer_info->disabled)) #endif { GE (glDisable (gl_target)); } } _cogl_material_layer_flush_gl_sampler_state (layer, unit, gl_layer_info); new_gl_layer_info.handle = layer_handle; new_gl_layer_info.flags = layer->flags; new_gl_layer_info.gl_target = gl_target; new_gl_layer_info.gl_texture = gl_texture; if (i < ctx->current_layers->len) *gl_layer_info = new_gl_layer_info; else g_array_append_val (ctx->current_layers, new_gl_layer_info); layer->flags &= ~COGL_MATERIAL_LAYER_FLAG_DIRTY; } /* Disable additional texture units that may have previously been in use.. */ for (; i < ctx->current_layers->len; i++) { CoglLayerInfo *gl_layer_info = &g_array_index (ctx->current_layers, CoglLayerInfo, i); #ifndef DISABLE_MATERIAL_CACHE if (!gl_layer_info->disabled) #endif { GE (glActiveTexture (GL_TEXTURE0 + i)); GE (glDisable (gl_layer_info->gl_target)); gl_layer_info->disabled = TRUE; } } } static void _cogl_material_flush_base_gl_state (CoglMaterial *material, gboolean skip_gl_color) { _COGL_GET_CONTEXT (ctx, NO_RETVAL); /* XXX: * Currently we only don't update state when the flags indicate that the * current material uses the defaults, and the new material also uses the * defaults, but we could do deeper comparisons of state. */ if (!skip_gl_color) { if (!(ctx->current_material_flags & COGL_MATERIAL_FLAG_DEFAULT_COLOR && material->flags & COGL_MATERIAL_FLAG_DEFAULT_COLOR) || /* Assume if we were previously told to skip the color, then * the current color needs updating... */ ctx->current_material_flush_options.flags & COGL_MATERIAL_FLUSH_SKIP_GL_COLOR) { GE (glColor4ub (material->unlit[0], material->unlit[1], material->unlit[2], material->unlit[3])); } } if (!(ctx->current_material_flags & COGL_MATERIAL_FLAG_DEFAULT_GL_MATERIAL && material->flags & COGL_MATERIAL_FLAG_DEFAULT_GL_MATERIAL)) { /* FIXME - we only need to set these if lighting is enabled... */ GE (glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT, material->ambient)); GE (glMaterialfv (GL_FRONT_AND_BACK, GL_DIFFUSE, material->diffuse)); GE (glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, material->specular)); GE (glMaterialfv (GL_FRONT_AND_BACK, GL_EMISSION, material->emission)); GE (glMaterialfv (GL_FRONT_AND_BACK, GL_SHININESS, &material->shininess)); } if (!(ctx->current_material_flags & COGL_MATERIAL_FLAG_DEFAULT_ALPHA_FUNC && material->flags & COGL_MATERIAL_FLAG_DEFAULT_ALPHA_FUNC)) { /* NB: Currently the Cogl defines are compatible with the GL ones: */ GE (glAlphaFunc (material->alpha_func, material->alpha_func_reference)); } if (!(ctx->current_material_flags & COGL_MATERIAL_FLAG_DEFAULT_BLEND && material->flags & COGL_MATERIAL_FLAG_DEFAULT_BLEND)) { #if defined (HAVE_COGL_GLES2) gboolean have_blend_equation_seperate = TRUE; gboolean have_blend_func_separate = TRUE; #elif defined (HAVE_COGL_GL) gboolean have_blend_equation_seperate = FALSE; gboolean have_blend_func_separate = FALSE; if (ctx->drv.pf_glBlendEquationSeparate) /* Only GL 2.0 + */ have_blend_equation_seperate = TRUE; if (ctx->drv.pf_glBlendFuncSeparate) /* Only GL 1.4 + */ have_blend_func_separate = TRUE; #endif #ifndef HAVE_COGL_GLES /* GLES 1 only has glBlendFunc */ if (have_blend_func_separate && (material->blend_src_factor_rgb != material->blend_src_factor_alpha || (material->blend_src_factor_rgb != material->blend_src_factor_alpha))) { if (have_blend_equation_seperate && material->blend_equation_rgb != material->blend_equation_alpha) GE (glBlendEquationSeparate (material->blend_equation_rgb, material->blend_equation_alpha)); else GE (glBlendEquation (material->blend_equation_rgb)); GE (glBlendFuncSeparate (material->blend_src_factor_rgb, material->blend_dst_factor_rgb, material->blend_src_factor_alpha, material->blend_dst_factor_alpha)); GE (glBlendColor (material->blend_constant[0], material->blend_constant[1], material->blend_constant[2], material->blend_constant[3])); } else #endif GE (glBlendFunc (material->blend_src_factor_rgb, material->blend_dst_factor_rgb)); } } void _cogl_material_flush_gl_state (CoglHandle handle, CoglMaterialFlushOptions *options) { CoglMaterial *material; guint32 fallback_layers = 0; guint32 disable_layers = 0; GLuint layer0_override_texture = 0; gboolean skip_gl_color = FALSE; const CoglMaterialWrapModeOverrides *wrap_mode_overrides = NULL; _COGL_GET_CONTEXT (ctx, NO_RETVAL); material = _cogl_material_pointer_from_handle (handle); if (options) { if (options->flags & COGL_MATERIAL_FLUSH_FALLBACK_MASK) fallback_layers = options->fallback_layers; if (options->flags & COGL_MATERIAL_FLUSH_DISABLE_MASK) disable_layers = options->disable_layers; if (options->flags & COGL_MATERIAL_FLUSH_LAYER0_OVERRIDE) layer0_override_texture = options->layer0_override_texture; if (options->flags & COGL_MATERIAL_FLUSH_SKIP_GL_COLOR) skip_gl_color = TRUE; if (options->flags & COGL_MATERIAL_FLUSH_WRAP_MODE_OVERRIDES) wrap_mode_overrides = &options->wrap_mode_overrides; } _cogl_material_flush_base_gl_state (material, skip_gl_color); _cogl_material_flush_layers_gl_state (material, fallback_layers, disable_layers, layer0_override_texture, wrap_mode_overrides); /* NB: we have to take a reference so that next time * cogl_material_flush_gl_state is called, we can compare the incomming * material pointer with ctx->current_material */ cogl_handle_ref (handle); if (ctx->current_material) cogl_handle_unref (ctx->current_material); ctx->current_material = handle; ctx->current_material_flags = material->flags; if (options) ctx->current_material_flush_options = *options; else memset (&ctx->current_material_flush_options, 0, sizeof (CoglMaterialFlushOptions)); } static gboolean _cogl_material_texture_equal (CoglHandle texture0, CoglHandle texture1) { GLenum gl_handle0, gl_handle1, gl_target0, gl_target1; /* If the texture handles are the same then the textures are definitely equal */ if (texture0 == texture1) return TRUE; /* If neither texture is sliced then they could still be the same if the are referring to the same GL texture */ if (cogl_texture_is_sliced (texture0) || cogl_texture_is_sliced (texture1)) return FALSE; cogl_texture_get_gl_texture (texture0, &gl_handle0, &gl_target0); cogl_texture_get_gl_texture (texture1, &gl_handle1, &gl_target1); return gl_handle0 == gl_handle1 && gl_target0 == gl_target1; } static gboolean _cogl_material_layer_equal (CoglMaterialLayer *material0_layer, CoglHandle material0_layer_texture, CoglMaterialLayer *material1_layer, CoglHandle material1_layer_texture) { if (!_cogl_material_texture_equal (material0_layer_texture, material1_layer_texture)) return FALSE; if ((material0_layer->flags & COGL_MATERIAL_LAYER_FLAG_DEFAULT_COMBINE) != (material1_layer->flags & COGL_MATERIAL_LAYER_FLAG_DEFAULT_COMBINE)) return FALSE; #if 0 /* TODO */ if (!_deep_are_layer_combines_equal ()) return FALSE; #else if (!(material0_layer->flags & COGL_MATERIAL_LAYER_FLAG_DEFAULT_COMBINE)) return FALSE; #endif if (material0_layer->mag_filter != material1_layer->mag_filter) return FALSE; if (material0_layer->min_filter != material1_layer->min_filter) return FALSE; if (material0_layer->wrap_mode_s != material1_layer->wrap_mode_s || material0_layer->wrap_mode_t != material1_layer->wrap_mode_t || material0_layer->wrap_mode_r != material1_layer->wrap_mode_r) return FALSE; return TRUE; } /* This is used by the Cogl journal to compare materials so that it * can split up geometry that needs different OpenGL state. * * It is acceptable to have false negatives - although they will result * in redundant OpenGL calls that try and update the state. * * False positives aren't allowed. */ gboolean _cogl_material_equal (CoglHandle material0_handle, CoglMaterialFlushOptions *material0_flush_options, CoglHandle material1_handle, CoglMaterialFlushOptions *material1_flush_options) { CoglMaterial *material0; CoglMaterial *material1; CoglMaterialFlushFlag flush_flags0 = material0_flush_options->flags; CoglMaterialFlushFlag flush_flags1 = material1_flush_options->flags; guint32 fallback_layers0; guint32 fallback_layers1; guint32 disable_layers0; guint32 disable_layers1; GList *l0, *l1; int i; /* Compare the flush options first; if they are equivalent then we * can potentially return quickly if the material handles then match. */ /* The skip color option is used when the color of the material is being * submitted in a vertex array so cogl_material_flush_gl_state doesn't * need to call glColor. * - A skip gl color material following a non skip color material doesn't * need a state change since putting a color in a vertex array (as done * for skip color materials) would simply take precedence over one * previously specified by glColor (as done for non skip color materials) * - A non skip color material following a skip color material also doesn't * need a state change for the same reason. * - The problem is that a non skip color, followed by a skip color, followed * by a non skip color does require a state change. Since we don't have * enough contextual information here we currently return FALSE whenever * the skip color option changes. */ if ((flush_flags0 & COGL_MATERIAL_FLUSH_SKIP_GL_COLOR) != (flush_flags1 & COGL_MATERIAL_FLUSH_SKIP_GL_COLOR)) return FALSE; fallback_layers0 = flush_flags0 & COGL_MATERIAL_FLUSH_FALLBACK_MASK ? material0_flush_options->fallback_layers : 0; fallback_layers1 = flush_flags1 & COGL_MATERIAL_FLUSH_FALLBACK_MASK ? material1_flush_options->fallback_layers : 0; if (fallback_layers0 != fallback_layers1) return FALSE; disable_layers0 = flush_flags0 & COGL_MATERIAL_FLUSH_DISABLE_MASK ? material0_flush_options->disable_layers : 0; disable_layers1 = flush_flags1 & COGL_MATERIAL_FLUSH_DISABLE_MASK ? material1_flush_options->disable_layers : 0; if (disable_layers0 != disable_layers1) return FALSE; /* NB: Some unlikely false negatives are possible here. */ if ((flush_flags0 & COGL_MATERIAL_FLUSH_LAYER0_OVERRIDE) != (flush_flags1 & COGL_MATERIAL_FLUSH_LAYER0_OVERRIDE)) return FALSE; if ((flush_flags0 & COGL_MATERIAL_FLUSH_LAYER0_OVERRIDE) && material0_flush_options->layer0_override_texture != material1_flush_options->layer0_override_texture) return FALSE; /* If one has wrap mode overrides and the other doesn't then the materials are different */ if (((flush_flags0 ^ flush_flags1) & COGL_MATERIAL_FLUSH_WRAP_MODE_OVERRIDES)) return FALSE; /* If they both have overrides then we need to compare them */ if ((flush_flags0 & COGL_MATERIAL_FLUSH_WRAP_MODE_OVERRIDES) && memcmp (&material0_flush_options->wrap_mode_overrides, &material1_flush_options->wrap_mode_overrides, sizeof (CoglMaterialWrapModeOverrides))) return FALSE; /* Since we know the flush options match at this point, if the material * handles match then we know they are equivalent. */ if (material0_handle == material1_handle) return TRUE; /* Now we need to look in more detail... */ material0 = _cogl_material_pointer_from_handle (material0_handle); material1 = _cogl_material_pointer_from_handle (material1_handle); if (!(material0_flush_options->flags & COGL_MATERIAL_FLUSH_SKIP_GL_COLOR) && !memcmp (material0->unlit, material1->unlit, sizeof (material0->unlit))) return FALSE; /* First we simply try and find a difference according to default flags * for each material component to avoid deeper comparison. */ if ((material0->flags & COGL_MATERIAL_FLAG_DEFAULT_GL_MATERIAL) != (material1->flags & COGL_MATERIAL_FLAG_DEFAULT_GL_MATERIAL)) return FALSE; if ((material0->flags & COGL_MATERIAL_FLAG_DEFAULT_ALPHA_FUNC) != (material1->flags & COGL_MATERIAL_FLAG_DEFAULT_ALPHA_FUNC)) return FALSE; /* Potentially blending could be "enabled" but the blend mode * could be equivalent to being disabled, but we accept those false * negatives for now. */ if ((material0->flags & COGL_MATERIAL_FLAG_ENABLE_BLEND) != (material1->flags & COGL_MATERIAL_FLAG_ENABLE_BLEND)) return FALSE; if ((material0->flags & COGL_MATERIAL_FLAG_ENABLE_BLEND) && (material0->flags & COGL_MATERIAL_FLAG_DEFAULT_BLEND) != (material1->flags & COGL_MATERIAL_FLAG_DEFAULT_BLEND)) return FALSE; /* If we still haven't found a difference then do a deeper comparison.. * * Actually we don't currently do this; we simply assume anything * non default is different and accept the false negatives for now. */ #if 0 /* TODO */ if (!_deep_are_gl_materials_equal ()) return FALSE; #else /* Just assume that all non default materials are different */ if (!(material0->flags & COGL_MATERIAL_FLAG_DEFAULT_GL_MATERIAL)) return FALSE; #endif #if 0 /* TODO */ if (!_deep_are_alpha_funcs_equal ()) return FALSE; #else /* Just assume that all non default alpha funcs are different */ if (!(material0->flags & COGL_MATERIAL_FLAG_DEFAULT_ALPHA_FUNC)) return FALSE; #endif if (material0->flags & COGL_MATERIAL_FLAG_ENABLE_BLEND) { #if 0 /* TODO */ if (!_deep_is_blend_equal ()) return FALSE; #else if (!(material0->flags & COGL_MATERIAL_FLAG_DEFAULT_BLEND)) return FALSE; #endif } /* Finally compare each of the material layers ... */ l0 = material0->layers; l1 = material1->layers; i = 0; /* NB: At this point we know if COGL_MATERIAL_FLUSH_LAYER0_OVERRIDE is being * used then both materials are overriding with the same texture. */ if (flush_flags0 & COGL_MATERIAL_FLUSH_LAYER0_OVERRIDE && l0 && l1) { /* We still need to check if the combine modes etc are equal, but we * simply pass COGL_INVALID_HANDLE for both texture handles so they will * be considered equal */ if (!_cogl_material_layer_equal (l0->data, COGL_INVALID_HANDLE, l1->data, COGL_INVALID_HANDLE)) return FALSE; l0 = l0->next; l1 = l1->next; i++; } while (l0 && l1) { CoglMaterialLayer *m0_layer; CoglMaterialLayer *m1_layer; if ((l0 == NULL && l1 != NULL) || (l1 == NULL && l0 != NULL)) return FALSE; /* NB: At this point we know that the fallback and disable masks for * both materials are equal. */ if (disable_layers0 & (1<data; m1_layer = l1->data; /* NB: The use of a fallback texture doesn't imply that the combine * modes etc are the same. */ if ((disable_layers0 & (1<texture, m1_layer, m1_layer->texture)) return FALSE; } next_layer: l0 = l0->next; l1 = l1->next; i++; } if ((l0 == NULL && l1 != NULL) || (l1 == NULL && l0 != NULL)) return FALSE; return TRUE; } /* While a material is referenced by the Cogl journal we can not allow * modifications, so this gives us a mechanism to track journal * references separately */ CoglHandle _cogl_material_journal_ref (CoglHandle material_handle) { CoglMaterial *material = material = _cogl_material_pointer_from_handle (material_handle); material->journal_ref_count++; cogl_handle_ref (material_handle); return material_handle; } void _cogl_material_journal_unref (CoglHandle material_handle) { CoglMaterial *material = material = _cogl_material_pointer_from_handle (material_handle); material->journal_ref_count--; cogl_handle_unref (material_handle); } CoglMaterialFilter cogl_material_layer_get_min_filter (CoglHandle layer_handle) { CoglMaterialLayer *layer; g_return_val_if_fail (cogl_is_material_layer (layer_handle), 0); layer = _cogl_material_layer_pointer_from_handle (layer_handle); return layer->min_filter; } CoglMaterialFilter cogl_material_layer_get_mag_filter (CoglHandle layer_handle) { CoglMaterialLayer *layer; g_return_val_if_fail (cogl_is_material_layer (layer_handle), 0); layer = _cogl_material_layer_pointer_from_handle (layer_handle); return layer->mag_filter; } void cogl_material_set_layer_filters (CoglHandle handle, int layer_index, CoglMaterialFilter min_filter, CoglMaterialFilter mag_filter) { CoglMaterial *material; CoglMaterialLayer *layer; g_return_if_fail (cogl_is_material (handle)); material = _cogl_material_pointer_from_handle (handle); layer = _cogl_material_get_layer (material, layer_index, TRUE); /* possibly flush primitives referencing the current state... */ _cogl_material_pre_change_notify (material, FALSE, NULL); layer->min_filter = min_filter; layer->mag_filter = mag_filter; } void cogl_material_set_layer_wrap_mode_s (CoglHandle handle, int layer_index, CoglMaterialWrapMode mode) { CoglMaterial *material; CoglMaterialLayer *layer; g_return_if_fail (cogl_is_material (handle)); material = _cogl_material_pointer_from_handle (handle); layer = _cogl_material_get_layer (material, layer_index, TRUE); if (layer->wrap_mode_s != mode) { /* possibly flush primitives referencing the current state... */ _cogl_material_pre_change_notify (material, FALSE, NULL); layer->wrap_mode_s = mode; } } void cogl_material_set_layer_wrap_mode_t (CoglHandle handle, int layer_index, CoglMaterialWrapMode mode) { CoglMaterial *material; CoglMaterialLayer *layer; g_return_if_fail (cogl_is_material (handle)); material = _cogl_material_pointer_from_handle (handle); layer = _cogl_material_get_layer (material, layer_index, TRUE); if (layer->wrap_mode_t != mode) { /* possibly flush primitives referencing the current state... */ _cogl_material_pre_change_notify (material, FALSE, NULL); layer->wrap_mode_t = mode; } } /* TODO: this should be made public once we add support for 3D textures in Cogl */ void _cogl_material_set_layer_wrap_mode_r (CoglHandle handle, int layer_index, CoglMaterialWrapMode mode) { CoglMaterial *material; CoglMaterialLayer *layer; g_return_if_fail (cogl_is_material (handle)); material = _cogl_material_pointer_from_handle (handle); layer = _cogl_material_get_layer (material, layer_index, TRUE); if (layer->wrap_mode_r != mode) { /* possibly flush primitives referencing the current state... */ _cogl_material_pre_change_notify (material, FALSE, NULL); layer->wrap_mode_r = mode; } } void cogl_material_set_layer_wrap_mode (CoglHandle material, int layer_index, CoglMaterialWrapMode mode) { cogl_material_set_layer_wrap_mode_s (material, layer_index, mode); cogl_material_set_layer_wrap_mode_t (material, layer_index, mode); _cogl_material_set_layer_wrap_mode_r (material, layer_index, mode); } CoglMaterialWrapMode cogl_material_layer_get_wrap_mode_s (CoglHandle handle) { g_return_val_if_fail (cogl_is_material_layer (handle), FALSE); return _cogl_material_layer_pointer_from_handle (handle)->wrap_mode_s; } CoglMaterialWrapMode cogl_material_layer_get_wrap_mode_t (CoglHandle handle) { g_return_val_if_fail (cogl_is_material_layer (handle), FALSE); return _cogl_material_layer_pointer_from_handle (handle)->wrap_mode_t; } /* TODO: this should be made public once we add support for 3D textures in Cogl */ CoglMaterialWrapMode _cogl_material_layer_get_wrap_mode_r (CoglHandle handle) { g_return_val_if_fail (cogl_is_material_layer (handle), FALSE); return _cogl_material_layer_pointer_from_handle (handle)->wrap_mode_r; }