/*
* 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;
}