mutter/cogl/cogl-material.c
Neil Roberts a8b563b622 cogl: Enable blending if a lighting colour is semi-transparent
We currently enable blending if the material colour has
transparency. This patch makes it also enable blending if any of the
lighting colours have transparency. Arguably this isn't neccessary
because we don't expose any API to enable lighting so there is no
bug. However it is currently possible to enable lighting with a direct
call to glEnable and this otherwise works so it is a shame not to have
it.

http://bugzilla.openedhand.com/show_bug.cgi?id=1907
2009-11-30 19:08:38 +00:00

2035 lines
64 KiB
C

/*
* 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, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
* Authors:
* Robert Bragg <robert@linux.intel.com>
*/
#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 <glib.h>
#include <string.h>
/*
* 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
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_SRC_ALPHA;
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,
gint 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->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,
gint 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 >= CGL_MAX_COMBINED_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,
gint 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,
gint 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,
gint 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,
gint 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. */
gulong
_cogl_material_get_cogl_enable_flags (CoglHandle material_handle)
{
CoglMaterial *material;
gulong 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;
}
gulong
_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 guint
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);
}
/*
* _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.
*
* 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)
{
GList *tmp;
int i;
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
for (tmp = material->layers, i = 0; tmp != NULL; 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;
#ifdef HAVE_COGL_GLES2
GLenum gl_internal_format;
#endif
CoglTextureUnit *unit;
new_gl_layer_info.layer0_overridden =
layer0_override_texture ? TRUE : FALSE;
new_gl_layer_info.fallback =
(fallback_mask & (1<<i)) ? TRUE : FALSE;
new_gl_layer_info.disabled =
(disable_mask & (1<<i)) ? TRUE : FALSE;
tex_handle = layer->texture;
if (tex_handle != COGL_INVALID_HANDLE)
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);
}
GE (glActiveTexture (GL_TEXTURE0 + i));
unit = _cogl_get_texture_unit (i);
_cogl_texture_set_filters (layer->texture,
layer->min_filter,
layer->mag_filter);
if (is_mipmap_filter (layer->min_filter)
|| is_mipmap_filter (layer->mag_filter))
_cogl_texture_ensure_mipmaps (layer->texture);
/* 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 */
#ifdef HAVE_COGL_GLES2
gl_internal_format = _cogl_texture_get_internal_gl_format (tex_handle);
cogl_gles2_wrapper_bind_texture (gl_target,
gl_texture,
gl_internal_format);
#else
GE (glBindTexture (gl_target, gl_texture));
#endif
/* 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;
if ((i+1) >= CGL_MAX_COMBINED_TEXTURE_IMAGE_UNITS)
break;
}
/* 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;
_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;
}
_cogl_material_flush_base_gl_state (material,
skip_gl_color);
_cogl_material_flush_layers_gl_state (material,
fallback_layers,
disable_layers,
layer0_override_texture);
/* 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_layer_equal (CoglMaterialLayer *material0_layer,
CoglHandle material0_layer_texture,
CoglMaterialLayer *material1_layer,
CoglHandle material1_layer_texture)
{
if (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
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;
/* 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<<i))
goto next_layer;
m0_layer = l0->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<<i)) || (fallback_layers0 & (1<<i)))
{
/* As with layer0 overrides, we simply pass COGL_INVALID_HANDLEs for
* both texture handles here so they will be considered equal. */
if (!_cogl_material_layer_equal (m0_layer, COGL_INVALID_HANDLE,
m1_layer, COGL_INVALID_HANDLE))
return FALSE;
}
else
{
if (!_cogl_material_layer_equal (m0_layer, m0_layer->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);
}
/* TODO: Should go in cogl.c, but that implies duplication which is also
* not ideal. */
void
cogl_set_source (CoglHandle material_handle)
{
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
g_return_if_fail (cogl_is_material (material_handle));
if (ctx->source_material == material_handle)
return;
cogl_handle_ref (material_handle);
if (ctx->source_material)
cogl_handle_unref (ctx->source_material);
ctx->source_material = material_handle;
}
/* TODO: add cogl_set_front_source (), and cogl_set_back_source () */
void
cogl_set_source_texture (CoglHandle texture_handle)
{
CoglColor white;
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
g_return_if_fail (texture_handle != COGL_INVALID_HANDLE);
cogl_material_set_layer (ctx->simple_material, 0, texture_handle);
cogl_color_set_from_4ub (&white, 0xff, 0xff, 0xff, 0xff);
cogl_material_set_color (ctx->simple_material, &white);
cogl_set_source (ctx->simple_material);
}
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,
gint 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;
}