mirror of
https://github.com/brl/mutter.git
synced 2024-11-29 19:40:43 -05:00
82e63a4753
Instead of the ensure_mipmaps virtual that is only called whenever the texture is about to be rendered with a min filter that needs the mipmap, there is now a pre_paint virtual that is always called when the texture is about to be painted in any way. It has a flags parameter which is used to specify whether the mipmap will be needed. This is useful for CoglTexturePixmapX11 because it needs to do stuff before painting that is unrelated to mipmapping.
7201 lines
231 KiB
C
7201 lines
231 KiB
C
/*
|
|
* Cogl
|
|
*
|
|
* An object oriented GL/GLES Abstraction/Utility Layer
|
|
*
|
|
* Copyright (C) 2008,2009,2010 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
|
|
* <http://www.gnu.org/licenses/>.
|
|
*
|
|
*
|
|
*
|
|
* 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 "cogl-journal-private.h"
|
|
#include "cogl-color-private.h"
|
|
#include "cogl-profile.h"
|
|
#ifndef HAVE_COGL_GLES
|
|
#include "cogl-program.h"
|
|
#endif
|
|
|
|
#include <glib.h>
|
|
#include <glib/gprintf.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
|
|
|
|
#define glProgramString ctx->drv.pf_glProgramString
|
|
#define glBindProgram ctx->drv.pf_glBindProgram
|
|
#define glDeletePrograms ctx->drv.pf_glDeletePrograms
|
|
#define glGenPrograms ctx->drv.pf_glGenPrograms
|
|
#define glProgramLocalParameter4fv ctx->drv.pf_glProgramLocalParameter4fv
|
|
#define glUseProgram ctx->drv.pf_glUseProgram
|
|
#endif
|
|
|
|
/* This isn't defined in the GLES headers */
|
|
#ifndef GL_CLAMP_TO_BORDER
|
|
#define GL_CLAMP_TO_BORDER 0x812d
|
|
#endif
|
|
|
|
#define COGL_MATERIAL(X) ((CoglMaterial *)(X))
|
|
#define COGL_MATERIAL_LAYER(X) ((CoglMaterialLayer *)(X))
|
|
|
|
typedef struct _CoglMaterialBackendARBfpPrivate
|
|
{
|
|
CoglMaterial *authority_cache;
|
|
unsigned long authority_cache_age;
|
|
|
|
GString *source;
|
|
GLuint gl_program;
|
|
gboolean *sampled;
|
|
int next_constant_id;
|
|
} CoglMaterialBackendARBfpPrivate;
|
|
|
|
typedef gboolean (*CoglMaterialStateComparitor) (CoglMaterial *authority0,
|
|
CoglMaterial *authority1);
|
|
|
|
static CoglMaterialLayer *_cogl_material_layer_copy (CoglMaterialLayer *layer);
|
|
|
|
static void _cogl_material_free (CoglMaterial *tex);
|
|
static void _cogl_material_layer_free (CoglMaterialLayer *layer);
|
|
static void _cogl_material_add_layer_difference (CoglMaterial *material,
|
|
CoglMaterialLayer *layer,
|
|
gboolean inc_n_layers);
|
|
static void handle_automatic_blend_enable (CoglMaterial *material,
|
|
CoglMaterialState changes);
|
|
|
|
#if defined (HAVE_COGL_GL)
|
|
|
|
static const CoglMaterialBackend _cogl_material_glsl_backend;
|
|
static const CoglMaterialBackend _cogl_material_arbfp_backend;
|
|
static const CoglMaterialBackend _cogl_material_fixed_backend;
|
|
static const CoglMaterialBackend *backends[] =
|
|
{
|
|
/* The fragment processing backends in order of precedence... */
|
|
&_cogl_material_glsl_backend,
|
|
&_cogl_material_arbfp_backend,
|
|
&_cogl_material_fixed_backend
|
|
};
|
|
/* NB: material->backend is currently a 3bit unsigned int bitfield */
|
|
#define COGL_MATERIAL_BACKEND_GLSL 0
|
|
#define COGL_MATERIAL_BACKEND_GLSL_MASK (1L<<0)
|
|
#define COGL_MATERIAL_BACKEND_ARBFP 1
|
|
#define COGL_MATERIAL_BACKEND_ARBFP_MASK (1L<<1)
|
|
#define COGL_MATERIAL_BACKEND_FIXED 2
|
|
#define COGL_MATERIAL_BACKEND_FIXED_MASK (1L<<2)
|
|
|
|
#elif defined (HAVE_COGL_GLES2)
|
|
|
|
static const CoglMaterialBackend _cogl_material_glsl_backend;
|
|
static const CoglMaterialBackend _cogl_material_fixed_backend;
|
|
static const CoglMaterialBackend *backends[] =
|
|
{
|
|
/* The fragment processing backends in order of precedence... */
|
|
&_cogl_material_glsl_backend,
|
|
&_cogl_material_fixed_backend
|
|
};
|
|
#define COGL_MATERIAL_BACKEND_GLSL 0
|
|
#define COGL_MATERIAL_BACKEND_FIXED 1
|
|
|
|
#else /* HAVE_COGL_GLES */
|
|
|
|
static const CoglMaterialBackend _cogl_material_fixed_backend;
|
|
static const CoglMaterialBackend *backends[] =
|
|
{
|
|
/* The fragment processing backends in order of precedence... */
|
|
&_cogl_material_fixed_backend
|
|
};
|
|
|
|
#define COGL_MATERIAL_BACKEND_FIXED 0
|
|
|
|
#endif
|
|
|
|
#define COGL_MATERIAL_BACKEND_DEFAULT 0
|
|
#define COGL_MATERIAL_BACKEND_UNDEFINED 3
|
|
|
|
COGL_HANDLE_DEFINE (Material, material);
|
|
COGL_HANDLE_DEFINE (MaterialLayer, material_layer);
|
|
|
|
static void
|
|
texture_unit_init (CoglTextureUnit *unit, int index_)
|
|
{
|
|
unit->index = index_;
|
|
unit->enabled = FALSE;
|
|
unit->current_gl_target = 0;
|
|
unit->gl_texture = 0;
|
|
unit->is_foreign = FALSE;
|
|
unit->dirty_gl_texture = FALSE;
|
|
unit->matrix_stack = _cogl_matrix_stack_new ();
|
|
|
|
unit->layer = NULL;
|
|
unit->layer_changes_since_flush = 0;
|
|
unit->texture_storage_changed = FALSE;
|
|
}
|
|
|
|
static void
|
|
texture_unit_free (CoglTextureUnit *unit)
|
|
{
|
|
if (unit->layer)
|
|
cogl_handle_unref (unit->layer);
|
|
_cogl_matrix_stack_destroy (unit->matrix_stack);
|
|
}
|
|
|
|
CoglTextureUnit *
|
|
_cogl_get_texture_unit (int index_)
|
|
{
|
|
_COGL_GET_CONTEXT (ctx, NULL);
|
|
|
|
if (ctx->texture_units->len < (index_ + 1))
|
|
{
|
|
int i;
|
|
int prev_len = ctx->texture_units->len;
|
|
ctx->texture_units = g_array_set_size (ctx->texture_units, index_ + 1);
|
|
for (i = prev_len; i <= index_; i++)
|
|
{
|
|
CoglTextureUnit *unit =
|
|
&g_array_index (ctx->texture_units, CoglTextureUnit, i);
|
|
|
|
texture_unit_init (unit, i);
|
|
}
|
|
}
|
|
|
|
return &g_array_index (ctx->texture_units, CoglTextureUnit, index_);
|
|
}
|
|
|
|
void
|
|
_cogl_destroy_texture_units (void)
|
|
{
|
|
int i;
|
|
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
for (i = 0; i < ctx->texture_units->len; i++)
|
|
{
|
|
CoglTextureUnit *unit =
|
|
&g_array_index (ctx->texture_units, CoglTextureUnit, i);
|
|
texture_unit_free (unit);
|
|
}
|
|
g_array_free (ctx->texture_units, TRUE);
|
|
}
|
|
|
|
static void
|
|
set_active_texture_unit (int unit_index)
|
|
{
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
if (ctx->active_texture_unit != unit_index)
|
|
{
|
|
GE (glActiveTexture (GL_TEXTURE0 + unit_index));
|
|
ctx->active_texture_unit = unit_index;
|
|
}
|
|
}
|
|
|
|
/* Note: _cogl_bind_gl_texture_transient conceptually has slightly
|
|
* different semantics to OpenGL's glBindTexture because Cogl never
|
|
* cares about tracking multiple textures bound to different targets
|
|
* on the same texture unit.
|
|
*
|
|
* glBindTexture lets you bind multiple textures to a single texture
|
|
* unit if they are bound to different targets. So it does something
|
|
* like:
|
|
* unit->current_texture[target] = texture;
|
|
*
|
|
* Cogl only lets you associate one texture with the currently active
|
|
* texture unit, so the target is basically a redundant parameter
|
|
* that's implicitly set on that texture.
|
|
*
|
|
* Technically this is just a thin wrapper around glBindTexture so
|
|
* actually it does have the GL semantics but it seems worth
|
|
* mentioning the conceptual difference in case anyone wonders why we
|
|
* don't associate the gl_texture with a gl_target in the
|
|
* CoglTextureUnit.
|
|
*/
|
|
void
|
|
_cogl_bind_gl_texture_transient (GLenum gl_target,
|
|
GLuint gl_texture,
|
|
gboolean is_foreign)
|
|
{
|
|
CoglTextureUnit *unit;
|
|
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
/* We choose to always make texture unit 1 active for transient
|
|
* binds so that in the common case where multitexturing isn't used
|
|
* we can simply ignore the state of this texture unit. Notably we
|
|
* didn't use a large texture unit (.e.g. (GL_MAX_TEXTURE_UNITS - 1)
|
|
* in case the driver doesn't have a sparse data structure for
|
|
* texture units.
|
|
*/
|
|
set_active_texture_unit (1);
|
|
unit = _cogl_get_texture_unit (1);
|
|
|
|
/* NB: If we have previously bound a foreign texture to this texture
|
|
* unit we don't know if that texture has since been deleted and we
|
|
* are seeing the texture name recycled */
|
|
if (unit->gl_texture == gl_texture &&
|
|
!unit->dirty_gl_texture &&
|
|
!unit->is_foreign)
|
|
return;
|
|
|
|
GE (glBindTexture (gl_target, gl_texture));
|
|
|
|
unit->dirty_gl_texture = TRUE;
|
|
unit->is_foreign = is_foreign;
|
|
}
|
|
|
|
void
|
|
_cogl_delete_gl_texture (GLuint gl_texture)
|
|
{
|
|
int i;
|
|
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
for (i = 0; i < ctx->texture_units->len; i++)
|
|
{
|
|
CoglTextureUnit *unit =
|
|
&g_array_index (ctx->texture_units, CoglTextureUnit, i);
|
|
|
|
if (unit->gl_texture == gl_texture)
|
|
{
|
|
unit->gl_texture = 0;
|
|
unit->dirty_gl_texture = FALSE;
|
|
}
|
|
}
|
|
|
|
GE (glDeleteTextures (1, &gl_texture));
|
|
}
|
|
|
|
/* Whenever the underlying GL texture storage of a CoglTexture is
|
|
* changed (e.g. due to migration out of a texture atlas) then we are
|
|
* notified. This lets us ensure that we reflush that texture's state
|
|
* if it reused again with the same texture unit.
|
|
*/
|
|
void
|
|
_cogl_material_texture_storage_change_notify (CoglHandle texture)
|
|
{
|
|
int i;
|
|
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
for (i = 0; i < ctx->texture_units->len; i++)
|
|
{
|
|
CoglTextureUnit *unit =
|
|
&g_array_index (ctx->texture_units, CoglTextureUnit, i);
|
|
|
|
if (unit->layer &&
|
|
unit->layer->texture == texture)
|
|
unit->texture_storage_changed = TRUE;
|
|
|
|
/* NB: the texture may be bound to multiple texture units so
|
|
* we continue to check the rest */
|
|
}
|
|
}
|
|
|
|
GQuark
|
|
_cogl_material_error_quark (void)
|
|
{
|
|
return g_quark_from_static_string ("cogl-material-error-quark");
|
|
}
|
|
|
|
/*
|
|
* This initializes the first material owned by the Cogl context. All
|
|
* subsequently instantiated materials created via the cogl_material_new()
|
|
* API will initially be a copy of this material.
|
|
*
|
|
* The default material is the topmost ancester for all materials.
|
|
*/
|
|
void
|
|
_cogl_material_init_default_material (void)
|
|
{
|
|
/* Create new - blank - material */
|
|
CoglMaterial *material = g_slice_new0 (CoglMaterial);
|
|
CoglMaterialBigState *big_state = g_slice_new0 (CoglMaterialBigState);
|
|
CoglMaterialLightingState *lighting_state = &big_state->lighting_state;
|
|
CoglMaterialAlphaFuncState *alpha_state = &big_state->alpha_state;
|
|
CoglMaterialBlendState *blend_state = &big_state->blend_state;
|
|
CoglMaterialDepthState *depth_state = &big_state->depth_state;
|
|
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
material->is_weak = FALSE;
|
|
material->journal_ref_count = 0;
|
|
material->parent = NULL;
|
|
material->backend = COGL_MATERIAL_BACKEND_UNDEFINED;
|
|
material->differences = COGL_MATERIAL_STATE_ALL_SPARSE;
|
|
|
|
material->real_blend_enable = FALSE;
|
|
|
|
material->blend_enable = COGL_MATERIAL_BLEND_ENABLE_AUTOMATIC;
|
|
material->layer_differences = NULL;
|
|
material->n_layers = 0;
|
|
|
|
material->big_state = big_state;
|
|
material->has_big_state = TRUE;
|
|
|
|
material->static_breadcrumb = "default material";
|
|
material->has_static_breadcrumb = TRUE;
|
|
|
|
material->age = 0;
|
|
|
|
/* Use the same defaults as the GL spec... */
|
|
cogl_color_init_from_4ub (&material->color, 0xff, 0xff, 0xff, 0xff);
|
|
|
|
/* Use the same defaults as the GL spec... */
|
|
lighting_state->ambient[0] = 0.2;
|
|
lighting_state->ambient[1] = 0.2;
|
|
lighting_state->ambient[2] = 0.2;
|
|
lighting_state->ambient[3] = 1.0;
|
|
|
|
lighting_state->diffuse[0] = 0.8;
|
|
lighting_state->diffuse[1] = 0.8;
|
|
lighting_state->diffuse[2] = 0.8;
|
|
lighting_state->diffuse[3] = 1.0;
|
|
|
|
lighting_state->specular[0] = 0;
|
|
lighting_state->specular[1] = 0;
|
|
lighting_state->specular[2] = 0;
|
|
lighting_state->specular[3] = 1.0;
|
|
|
|
lighting_state->emission[0] = 0;
|
|
lighting_state->emission[1] = 0;
|
|
lighting_state->emission[2] = 0;
|
|
lighting_state->emission[3] = 1.0;
|
|
|
|
/* Use the same defaults as the GL spec... */
|
|
alpha_state->alpha_func = COGL_MATERIAL_ALPHA_FUNC_ALWAYS;
|
|
alpha_state->alpha_func_reference = 0.0;
|
|
|
|
/* Not the same as the GL default, but seems saner... */
|
|
#ifndef HAVE_COGL_GLES
|
|
blend_state->blend_equation_rgb = GL_FUNC_ADD;
|
|
blend_state->blend_equation_alpha = GL_FUNC_ADD;
|
|
blend_state->blend_src_factor_alpha = GL_ONE;
|
|
blend_state->blend_dst_factor_alpha = GL_ONE_MINUS_SRC_ALPHA;
|
|
cogl_color_init_from_4ub (&blend_state->blend_constant,
|
|
0x00, 0x00, 0x00, 0x00);
|
|
#endif
|
|
blend_state->blend_src_factor_rgb = GL_ONE;
|
|
blend_state->blend_dst_factor_rgb = GL_ONE_MINUS_SRC_ALPHA;
|
|
|
|
big_state->user_program = COGL_INVALID_HANDLE;
|
|
|
|
/* The same as the GL defaults */
|
|
depth_state->depth_test_enabled = FALSE;
|
|
depth_state->depth_test_function = COGL_DEPTH_TEST_FUNCTION_LESS;
|
|
depth_state->depth_writing_enabled = TRUE;
|
|
depth_state->depth_range_near = 0;
|
|
depth_state->depth_range_far = 1;
|
|
|
|
ctx->default_material = _cogl_material_handle_new (material);
|
|
}
|
|
|
|
static void
|
|
_cogl_material_unparent (CoglMaterial *material)
|
|
{
|
|
CoglMaterial *parent = material->parent;
|
|
|
|
if (parent == NULL)
|
|
return;
|
|
|
|
g_return_if_fail (parent->has_children);
|
|
|
|
if (parent->first_child == material)
|
|
{
|
|
if (parent->children)
|
|
{
|
|
parent->first_child = parent->children->data;
|
|
parent->children =
|
|
g_list_delete_link (parent->children, parent->children);
|
|
}
|
|
else
|
|
parent->has_children = FALSE;
|
|
}
|
|
else
|
|
parent->children = g_list_remove (parent->children, material);
|
|
|
|
cogl_handle_unref (parent);
|
|
|
|
material->parent = NULL;
|
|
}
|
|
|
|
/* This recursively frees the layers_cache of a material and all of
|
|
* its descendants.
|
|
*
|
|
* For instance if we change a materials ->layer_differences list
|
|
* then that material and all of its descendants may now have
|
|
* incorrect layer caches. */
|
|
static void
|
|
recursively_free_layer_caches (CoglMaterial *material)
|
|
{
|
|
GList *l;
|
|
|
|
/* Note: we maintain the invariable that if a material already has a
|
|
* dirty layers_cache then so do all of its descendants. */
|
|
if (material->layers_cache_dirty)
|
|
return;
|
|
|
|
if (G_UNLIKELY (material->layers_cache != material->short_layers_cache))
|
|
g_slice_free1 (sizeof (CoglMaterialLayer *) * material->n_layers,
|
|
material->layers_cache);
|
|
material->layers_cache_dirty = TRUE;
|
|
|
|
if (material->has_children)
|
|
{
|
|
recursively_free_layer_caches (material->first_child);
|
|
for (l = material->children; l; l = l->next)
|
|
recursively_free_layer_caches (l->data);
|
|
}
|
|
}
|
|
|
|
static void
|
|
_cogl_material_set_parent (CoglMaterial *material, CoglMaterial *parent)
|
|
{
|
|
cogl_handle_ref (parent);
|
|
|
|
if (material->parent)
|
|
_cogl_material_unparent (material);
|
|
|
|
material->parent = parent;
|
|
if (G_UNLIKELY (parent->has_children))
|
|
parent->children = g_list_prepend (parent->children, material);
|
|
else
|
|
{
|
|
parent->has_children = TRUE;
|
|
parent->first_child = material;
|
|
parent->children = NULL;
|
|
}
|
|
|
|
material->parent = parent;
|
|
|
|
/* Since we just changed the ancestry of the material its cache of
|
|
* layers could now be invalid so free it... */
|
|
if (material->differences & COGL_MATERIAL_STATE_LAYERS)
|
|
recursively_free_layer_caches (material);
|
|
|
|
/* If the fragment processing backend is also caching state along
|
|
* with the material that depends on the materials ancestry then it
|
|
* may be notified here...
|
|
*/
|
|
if (material->backend != COGL_MATERIAL_BACKEND_UNDEFINED &&
|
|
backends[material->backend]->material_set_parent_notify)
|
|
backends[material->backend]->material_set_parent_notify (material);
|
|
}
|
|
|
|
/* XXX: Always have an eye out for opportunities to lower the cost of
|
|
* cogl_material_copy. */
|
|
CoglHandle
|
|
cogl_material_copy (CoglHandle handle)
|
|
{
|
|
CoglMaterial *src = COGL_MATERIAL (handle);
|
|
CoglMaterial *material = g_slice_new (CoglMaterial);
|
|
|
|
cogl_handle_ref (handle);
|
|
|
|
material->_parent = src->_parent;
|
|
|
|
material->is_weak = FALSE;
|
|
|
|
material->journal_ref_count = 0;
|
|
|
|
material->parent = NULL;
|
|
|
|
material->has_children = FALSE;
|
|
|
|
material->differences = 0;
|
|
|
|
material->has_big_state = FALSE;
|
|
|
|
/* NB: real_blend_enable isn't a sparse property, it's valid for
|
|
* every material node so we have fast access to it. */
|
|
material->real_blend_enable = src->real_blend_enable;
|
|
|
|
/* XXX:
|
|
* consider generalizing the idea of "cached" properties. These
|
|
* would still have an authority like other sparse properties but
|
|
* you wouldn't have to walk up the ancestry to find the authority
|
|
* because the value would be cached directly in each material.
|
|
*/
|
|
|
|
material->layers_cache_dirty = TRUE;
|
|
material->deprecated_get_layers_list_dirty = TRUE;
|
|
|
|
material->backend = src->backend;
|
|
material->backend_priv_set_mask = 0;
|
|
|
|
material->has_static_breadcrumb = FALSE;
|
|
|
|
material->age = 0;
|
|
|
|
_cogl_material_set_parent (material, src);
|
|
|
|
return _cogl_material_handle_new (material);
|
|
}
|
|
|
|
/* XXX: we should give this more thought before making anything like
|
|
* this API public! */
|
|
CoglHandle
|
|
_cogl_material_weak_copy (CoglHandle handle)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglHandle copy;
|
|
CoglMaterial *copy_material;
|
|
|
|
/* If we make a public API we might want want to allow weak copies
|
|
* of weak material? */
|
|
g_return_val_if_fail (!material->is_weak, COGL_INVALID_HANDLE);
|
|
|
|
copy = cogl_material_copy (handle);
|
|
copy_material = COGL_MATERIAL (copy);
|
|
copy_material->is_weak = TRUE;
|
|
|
|
return copy;
|
|
}
|
|
|
|
CoglHandle
|
|
cogl_material_new (void)
|
|
{
|
|
CoglHandle new;
|
|
|
|
_COGL_GET_CONTEXT (ctx, COGL_INVALID_HANDLE);
|
|
|
|
new = cogl_material_copy (ctx->default_material);
|
|
_cogl_material_set_static_breadcrumb (new, "new");
|
|
return new;
|
|
}
|
|
|
|
static void
|
|
_cogl_material_backend_free_priv (CoglMaterial *material)
|
|
{
|
|
if (material->backend != COGL_MATERIAL_BACKEND_UNDEFINED &&
|
|
backends[material->backend]->free_priv)
|
|
backends[material->backend]->free_priv (material);
|
|
}
|
|
|
|
static void
|
|
_cogl_material_free (CoglMaterial *material)
|
|
{
|
|
_cogl_material_backend_free_priv (material);
|
|
|
|
_cogl_material_unparent (material);
|
|
|
|
if (material->differences & COGL_MATERIAL_STATE_USER_SHADER &&
|
|
material->big_state->user_program)
|
|
cogl_handle_unref (material->big_state->user_program);
|
|
|
|
if (material->differences & COGL_MATERIAL_STATE_NEEDS_BIG_STATE)
|
|
g_slice_free (CoglMaterialBigState, material->big_state);
|
|
|
|
if (material->differences & COGL_MATERIAL_STATE_LAYERS)
|
|
{
|
|
g_list_foreach (material->layer_differences,
|
|
(GFunc)cogl_handle_unref, NULL);
|
|
g_list_free (material->layer_differences);
|
|
}
|
|
|
|
g_slice_free (CoglMaterial, material);
|
|
}
|
|
|
|
gboolean
|
|
_cogl_material_get_real_blend_enabled (CoglHandle handle)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
|
|
g_return_val_if_fail (cogl_is_material (handle), FALSE);
|
|
|
|
return material->real_blend_enable;
|
|
}
|
|
|
|
static CoglMaterial *
|
|
_cogl_material_get_authority (CoglMaterial *material,
|
|
unsigned long difference)
|
|
{
|
|
CoglMaterial *authority = material;
|
|
while (!(authority->differences & difference))
|
|
authority = authority->parent;
|
|
return authority;
|
|
}
|
|
|
|
static CoglMaterialLayer *
|
|
_cogl_material_layer_get_authority (CoglMaterialLayer *layer,
|
|
unsigned long difference)
|
|
{
|
|
CoglMaterialLayer *authority = layer;
|
|
while (!(authority->differences & difference))
|
|
authority = authority->parent;
|
|
return authority;
|
|
}
|
|
|
|
static int
|
|
_cogl_material_layer_get_unit_index (CoglMaterialLayer *layer)
|
|
{
|
|
CoglMaterialLayer *authority =
|
|
_cogl_material_layer_get_authority (layer, COGL_MATERIAL_LAYER_STATE_UNIT);
|
|
return authority->unit_index;
|
|
}
|
|
|
|
static void
|
|
_cogl_material_update_layers_cache (CoglMaterial *material)
|
|
{
|
|
/* Note: we assume this material is a _LAYERS authority */
|
|
int n_layers;
|
|
CoglMaterial *current;
|
|
int layers_found;
|
|
|
|
if (G_LIKELY (!material->layers_cache_dirty) ||
|
|
material->n_layers == 0)
|
|
return;
|
|
|
|
material->layers_cache_dirty = FALSE;
|
|
|
|
n_layers = material->n_layers;
|
|
if (G_LIKELY (n_layers < G_N_ELEMENTS (material->short_layers_cache)))
|
|
{
|
|
material->layers_cache = material->short_layers_cache;
|
|
memset (material->layers_cache, 0,
|
|
sizeof (CoglMaterialLayer *) *
|
|
G_N_ELEMENTS (material->short_layers_cache));
|
|
}
|
|
else
|
|
{
|
|
material->layers_cache =
|
|
g_slice_alloc0 (sizeof (CoglMaterialLayer *) * n_layers);
|
|
}
|
|
|
|
/* Notes:
|
|
*
|
|
* Each material doesn't have to contain a complete list of the layers
|
|
* it depends on, some of them are indirectly referenced through the
|
|
* material's ancestors.
|
|
*
|
|
* material->layer_differences only contains a list of layers that
|
|
* have changed in relation to its parent.
|
|
*
|
|
* material->layer_differences is not maintained sorted, but it
|
|
* won't contain multiple layers corresponding to a particular
|
|
* ->unit_index.
|
|
*
|
|
* Some of the ancestor materials may reference layers with
|
|
* ->unit_index values >= n_layers so we ignore them.
|
|
*
|
|
* As we ascend through the ancestors we are searching for any
|
|
* CoglMaterialLayers corresponding to the texture ->unit_index
|
|
* values in the range [0,n_layers-1]. As soon as a pointer is found
|
|
* we ignore layers of further ancestors with the same ->unit_index
|
|
* values.
|
|
*/
|
|
|
|
layers_found = 0;
|
|
for (current = material; current->parent; current = current->parent)
|
|
{
|
|
GList *l;
|
|
|
|
if (!(current->differences & COGL_MATERIAL_STATE_LAYERS))
|
|
continue;
|
|
|
|
for (l = current->layer_differences; l; l = l->next)
|
|
{
|
|
CoglMaterialLayer *layer = l->data;
|
|
int unit_index = _cogl_material_layer_get_unit_index (layer);
|
|
|
|
if (unit_index < n_layers && !material->layers_cache[unit_index])
|
|
{
|
|
material->layers_cache[unit_index] = layer;
|
|
layers_found++;
|
|
if (layers_found == n_layers)
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
g_warn_if_reached ();
|
|
}
|
|
|
|
typedef gboolean (*CoglMaterialLayerCallback) (CoglMaterialLayer *layer,
|
|
void *user_data);
|
|
|
|
/* TODO: add public cogl_material_foreach_layer but instead of passing
|
|
* a CoglMaterialLayer pointer to the callback we should pass a
|
|
* layer_index instead. */
|
|
|
|
static void
|
|
_cogl_material_foreach_layer (CoglHandle handle,
|
|
CoglMaterialLayerCallback callback,
|
|
void *user_data)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterial *authority =
|
|
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_LAYERS);
|
|
int n_layers;
|
|
int i;
|
|
gboolean cont;
|
|
|
|
n_layers = authority->n_layers;
|
|
if (n_layers == 0)
|
|
return;
|
|
|
|
_cogl_material_update_layers_cache (authority);
|
|
|
|
for (i = 0, cont = TRUE; i < n_layers && cont == TRUE; i++)
|
|
cont = callback (authority->layers_cache[i], user_data);
|
|
}
|
|
|
|
static gboolean
|
|
layer_has_alpha_cb (CoglMaterialLayer *layer, void *data)
|
|
{
|
|
CoglMaterialLayer *combine_authority =
|
|
_cogl_material_layer_get_authority (layer,
|
|
COGL_MATERIAL_LAYER_STATE_COMBINE);
|
|
CoglMaterialLayerBigState *big_state = combine_authority->big_state;
|
|
CoglMaterialLayer *tex_authority;
|
|
gboolean *has_alpha = data;
|
|
|
|
/* has_alpha maintains the alpha status for the GL_PREVIOUS layer */
|
|
|
|
/* For anything but the default texture combine we currently just
|
|
* assume it may result in an alpha value < 1
|
|
*
|
|
* FIXME: we could do better than this. */
|
|
if (big_state->texture_combine_alpha_func != GL_MODULATE ||
|
|
big_state->texture_combine_alpha_src[0] != GL_PREVIOUS ||
|
|
big_state->texture_combine_alpha_op[0] != GL_SRC_ALPHA ||
|
|
big_state->texture_combine_alpha_src[0] != GL_TEXTURE ||
|
|
big_state->texture_combine_alpha_op[0] != GL_SRC_ALPHA)
|
|
{
|
|
*has_alpha = TRUE;
|
|
/* return FALSE to stop iterating layers... */
|
|
return FALSE;
|
|
}
|
|
|
|
/* NB: A layer may have a combine mode set on it but not yet
|
|
* have an associated texture which would mean we'd fallback
|
|
* to the default texture which doesn't have an alpha component
|
|
*/
|
|
tex_authority =
|
|
_cogl_material_layer_get_authority (layer,
|
|
COGL_MATERIAL_LAYER_STATE_TEXTURE);
|
|
if (tex_authority->texture &&
|
|
cogl_texture_get_format (tex_authority->texture) & COGL_A_BIT)
|
|
{
|
|
*has_alpha = TRUE;
|
|
/* return FALSE to stop iterating layers... */
|
|
return FALSE;
|
|
}
|
|
|
|
*has_alpha = FALSE;
|
|
/* return FALSE to continue iterating layers... */
|
|
return TRUE;
|
|
}
|
|
|
|
static CoglHandle
|
|
_cogl_material_get_user_program (CoglHandle handle)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterial *authority;
|
|
|
|
g_return_val_if_fail (cogl_is_material (handle), COGL_INVALID_HANDLE);
|
|
|
|
authority =
|
|
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_USER_SHADER);
|
|
|
|
return authority->big_state->user_program;
|
|
}
|
|
|
|
static gboolean
|
|
_cogl_material_needs_blending_enabled (CoglMaterial *material,
|
|
unsigned long changes,
|
|
const CoglColor *override_color)
|
|
{
|
|
CoglMaterial *enable_authority;
|
|
CoglMaterial *blend_authority;
|
|
CoglMaterialBlendState *blend_state;
|
|
CoglMaterialBlendEnable enabled;
|
|
unsigned long other_state;
|
|
|
|
if (G_UNLIKELY (cogl_debug_flags & COGL_DEBUG_DISABLE_BLENDING))
|
|
return FALSE;
|
|
|
|
enable_authority =
|
|
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_BLEND_ENABLE);
|
|
|
|
enabled = enable_authority->blend_enable;
|
|
if (enabled != COGL_MATERIAL_BLEND_ENABLE_AUTOMATIC)
|
|
return enabled == COGL_MATERIAL_BLEND_ENABLE_ENABLED ? TRUE : FALSE;
|
|
|
|
blend_authority =
|
|
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_BLEND);
|
|
|
|
blend_state = &blend_authority->big_state->blend_state;
|
|
|
|
/* We are trying to identify awkward cases that are equivalent to
|
|
* blending being disable, where the output is simply GL_SRC_COLOR.
|
|
*
|
|
* Note: we assume that all OpenGL drivers will identify the simple
|
|
* case of ADD (ONE, ZERO) as equivalent to blending being disabled.
|
|
*
|
|
* We should update this when we add support for more blend
|
|
* functions...
|
|
*/
|
|
|
|
#ifndef HAVE_COGL_GLES
|
|
/* GLES 1 can't change the function or have separate alpha factors */
|
|
if (blend_state->blend_equation_rgb != GL_FUNC_ADD ||
|
|
blend_state->blend_equation_alpha != GL_FUNC_ADD)
|
|
return TRUE;
|
|
|
|
if (blend_state->blend_src_factor_alpha != GL_ONE ||
|
|
blend_state->blend_dst_factor_alpha != GL_ONE_MINUS_SRC_ALPHA)
|
|
return TRUE;
|
|
#endif
|
|
|
|
if (blend_state->blend_src_factor_rgb != GL_ONE ||
|
|
blend_state->blend_dst_factor_rgb != GL_ONE_MINUS_SRC_ALPHA)
|
|
return TRUE;
|
|
|
|
/* Given the above constraints, it's now a case of finding any
|
|
* SRC_ALPHA that != 1 */
|
|
|
|
/* In the case of a layer state change we need to check everything
|
|
* else first since they contribute to the has_alpha status of the
|
|
* GL_PREVIOUS layer. */
|
|
if (changes & COGL_MATERIAL_STATE_LAYERS)
|
|
changes = COGL_MATERIAL_STATE_AFFECTS_BLENDING;
|
|
|
|
/* XXX: we don't currently handle specific changes in an optimal way*/
|
|
changes = COGL_MATERIAL_STATE_AFFECTS_BLENDING;
|
|
|
|
if ((override_color && cogl_color_get_alpha_byte (override_color) != 0xff))
|
|
return TRUE;
|
|
|
|
if (changes & COGL_MATERIAL_STATE_COLOR)
|
|
{
|
|
CoglColor tmp;
|
|
cogl_material_get_color (material, &tmp);
|
|
if (cogl_color_get_alpha_byte (&tmp) != 0xff)
|
|
return TRUE;
|
|
}
|
|
|
|
/* We can't make any assumptions about the alpha channel if the user
|
|
* is using an unknown fragment shader.
|
|
*
|
|
* TODO: check that it isn't just a vertex shader!
|
|
*/
|
|
if (changes & COGL_MATERIAL_STATE_USER_SHADER)
|
|
{
|
|
if (_cogl_material_get_user_program (material) != COGL_INVALID_HANDLE)
|
|
return TRUE;
|
|
}
|
|
|
|
/* XXX: we should only need to look at these if lighting is enabled
|
|
*/
|
|
if (changes & COGL_MATERIAL_STATE_LIGHTING)
|
|
{
|
|
CoglColor tmp;
|
|
|
|
cogl_material_get_ambient (material, &tmp);
|
|
if (cogl_color_get_alpha_byte (&tmp) != 0xff)
|
|
return TRUE;
|
|
cogl_material_get_diffuse (material, &tmp);
|
|
if (cogl_color_get_alpha_byte (&tmp) != 0xff)
|
|
return TRUE;
|
|
cogl_material_get_specular (material, &tmp);
|
|
if (cogl_color_get_alpha_byte (&tmp) != 0xff)
|
|
return TRUE;
|
|
cogl_material_get_emission (material, &tmp);
|
|
if (cogl_color_get_alpha_byte (&tmp) != 0xff)
|
|
return TRUE;
|
|
}
|
|
|
|
if (changes & COGL_MATERIAL_STATE_LAYERS)
|
|
{
|
|
/* has_alpha tracks the alpha status of the GL_PREVIOUS layer.
|
|
* To start with that's defined by the material color which
|
|
* must be fully opaque if we got this far. */
|
|
gboolean has_alpha = FALSE;
|
|
_cogl_material_foreach_layer (material,
|
|
layer_has_alpha_cb,
|
|
&has_alpha);
|
|
if (has_alpha)
|
|
return TRUE;
|
|
}
|
|
|
|
/* So far we have only checked the property that has been changed so
|
|
* we now need to check all the other properties too. */
|
|
other_state = COGL_MATERIAL_STATE_AFFECTS_BLENDING & ~changes;
|
|
if (other_state &&
|
|
_cogl_material_needs_blending_enabled (material,
|
|
other_state,
|
|
NULL))
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
_cogl_material_set_backend (CoglMaterial *material, int backend)
|
|
{
|
|
_cogl_material_backend_free_priv (material);
|
|
material->backend = backend;
|
|
}
|
|
|
|
static void
|
|
_cogl_material_copy_differences (CoglMaterial *dest,
|
|
CoglMaterial *src,
|
|
unsigned long differences)
|
|
{
|
|
CoglMaterialBigState *big_state;
|
|
|
|
if (differences & COGL_MATERIAL_STATE_COLOR)
|
|
dest->color = src->color;
|
|
|
|
if (differences & COGL_MATERIAL_STATE_BLEND_ENABLE)
|
|
dest->blend_enable = src->blend_enable;
|
|
|
|
if (differences & COGL_MATERIAL_STATE_LAYERS)
|
|
{
|
|
GList *l;
|
|
|
|
if (dest->differences & COGL_MATERIAL_STATE_LAYERS &&
|
|
dest->layer_differences)
|
|
{
|
|
g_list_foreach (dest->layer_differences,
|
|
(GFunc)cogl_handle_unref,
|
|
NULL);
|
|
g_list_free (dest->layer_differences);
|
|
}
|
|
|
|
dest->n_layers = src->n_layers;
|
|
dest->layer_differences = g_list_copy (src->layer_differences);
|
|
|
|
for (l = src->layer_differences; l; l = l->next)
|
|
{
|
|
/* NB: a layer can't have more than one ->owner so we can't
|
|
* simply take a references on each of the original
|
|
* layer_differences, we have to derive new layers from the
|
|
* originals instead. */
|
|
CoglMaterialLayer *copy = _cogl_material_layer_copy (l->data);
|
|
_cogl_material_add_layer_difference (dest, copy, FALSE);
|
|
cogl_handle_unref (copy);
|
|
}
|
|
}
|
|
|
|
if (differences & COGL_MATERIAL_STATE_NEEDS_BIG_STATE)
|
|
{
|
|
if (!dest->has_big_state)
|
|
{
|
|
dest->big_state = g_slice_new (CoglMaterialBigState);
|
|
dest->has_big_state = TRUE;
|
|
}
|
|
big_state = dest->big_state;
|
|
}
|
|
else
|
|
goto check_for_blending_change;
|
|
|
|
if (differences & COGL_MATERIAL_STATE_LIGHTING)
|
|
{
|
|
memcpy (&big_state->lighting_state,
|
|
&src->big_state->lighting_state,
|
|
sizeof (CoglMaterialLightingState));
|
|
}
|
|
|
|
if (differences & COGL_MATERIAL_STATE_ALPHA_FUNC)
|
|
{
|
|
memcpy (&big_state->alpha_state,
|
|
&src->big_state->alpha_state,
|
|
sizeof (CoglMaterialAlphaFuncState));
|
|
}
|
|
|
|
if (differences & COGL_MATERIAL_STATE_BLEND)
|
|
{
|
|
memcpy (&big_state->blend_state,
|
|
&src->big_state->blend_state,
|
|
sizeof (CoglMaterialBlendState));
|
|
}
|
|
|
|
if (differences & COGL_MATERIAL_STATE_USER_SHADER)
|
|
{
|
|
if (src->big_state->user_program)
|
|
big_state->user_program =
|
|
cogl_handle_ref (src->big_state->user_program);
|
|
else
|
|
big_state->user_program = COGL_INVALID_HANDLE;
|
|
}
|
|
|
|
if (differences & COGL_MATERIAL_STATE_DEPTH)
|
|
{
|
|
memcpy (&big_state->depth_state,
|
|
&src->big_state->depth_state,
|
|
sizeof (CoglMaterialDepthState));
|
|
}
|
|
|
|
/* XXX: we shouldn't bother doing this in most cases since
|
|
* _copy_differences is typically used to initialize material state
|
|
* by copying it from the current authority, so it's not actually
|
|
* *changing* anything.
|
|
*/
|
|
check_for_blending_change:
|
|
if (differences & COGL_MATERIAL_STATE_AFFECTS_BLENDING)
|
|
handle_automatic_blend_enable (dest, differences);
|
|
|
|
dest->differences |= differences;
|
|
}
|
|
|
|
static void
|
|
_cogl_material_initialize_state (CoglMaterial *dest,
|
|
CoglMaterial *src,
|
|
CoglMaterialState state)
|
|
{
|
|
if (dest == src)
|
|
return;
|
|
|
|
if (state != COGL_MATERIAL_STATE_LAYERS)
|
|
_cogl_material_copy_differences (dest, src, state);
|
|
else
|
|
{
|
|
dest->n_layers = src->n_layers;
|
|
dest->layer_differences = NULL;
|
|
}
|
|
}
|
|
|
|
typedef gboolean (*CoglMaterialChildCallback) (CoglMaterial *child,
|
|
void *user_data);
|
|
|
|
static void
|
|
_cogl_material_foreach_child (CoglMaterial *material,
|
|
CoglMaterialChildCallback callback,
|
|
void *user_data)
|
|
{
|
|
if (material->has_children)
|
|
{
|
|
callback (material->first_child, user_data);
|
|
g_list_foreach (material->children, (GFunc)callback, user_data);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
check_if_strong_cb (CoglMaterial *material, void *user_data)
|
|
{
|
|
gboolean *has_strong_child = user_data;
|
|
|
|
if (!material->is_weak)
|
|
{
|
|
*has_strong_child = TRUE;
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
has_strong_children (CoglMaterial *material)
|
|
{
|
|
gboolean has_strong_child = FALSE;
|
|
_cogl_material_foreach_child (material,
|
|
check_if_strong_cb,
|
|
&has_strong_child);
|
|
return has_strong_child;
|
|
}
|
|
|
|
static gboolean
|
|
reparent_strong_children_cb (CoglMaterial *material,
|
|
void *user_data)
|
|
{
|
|
CoglMaterial *parent = user_data;
|
|
|
|
if (material->is_weak)
|
|
return TRUE;
|
|
|
|
_cogl_material_set_parent (material, parent);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
_cogl_material_pre_change_notify (CoglMaterial *material,
|
|
CoglMaterialState change,
|
|
const CoglColor *new_color)
|
|
{
|
|
CoglMaterial *authority;
|
|
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
/* 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... */
|
|
if (material->journal_ref_count)
|
|
{
|
|
gboolean skip_journal_flush = FALSE;
|
|
|
|
/* XXX: We don't usually need to flush the journal just due to
|
|
* color changes since material colors are logged in the
|
|
* journal's vertex buffer. The exception is when the change in
|
|
* color enables or disables the need for blending. */
|
|
if (change == COGL_MATERIAL_STATE_COLOR)
|
|
{
|
|
gboolean will_need_blending =
|
|
_cogl_material_needs_blending_enabled (material,
|
|
change,
|
|
new_color);
|
|
gboolean blend_enable = material->real_blend_enable ? TRUE : FALSE;
|
|
|
|
if (will_need_blending == blend_enable)
|
|
skip_journal_flush = TRUE;
|
|
}
|
|
|
|
if (!skip_journal_flush)
|
|
_cogl_journal_flush ();
|
|
}
|
|
|
|
/* The fixed function backend has no private state and can't
|
|
* do anything special to handle small material changes so we may as
|
|
* well try to find a better backend whenever the material changes.
|
|
*
|
|
* The programmable backends may be able to cache a lot of the code
|
|
* they generate and only need to update a small section of that
|
|
* code in response to a material change therefore we don't want to
|
|
* try searching for another backend when the material changes.
|
|
*/
|
|
if (material->backend == COGL_MATERIAL_BACKEND_FIXED)
|
|
_cogl_material_set_backend (material, COGL_MATERIAL_BACKEND_UNDEFINED);
|
|
|
|
if (material->backend != COGL_MATERIAL_BACKEND_UNDEFINED &&
|
|
backends[material->backend]->material_pre_change_notify)
|
|
backends[material->backend]->material_pre_change_notify (material,
|
|
change,
|
|
new_color);
|
|
|
|
/*
|
|
* There is an arbitrary tree of descendants of this material; any of
|
|
* which may indirectly depend on this material as the authority for
|
|
* some set of properties. (Meaning for example that one of its
|
|
* descendants derives its color or blending state from this
|
|
* material.)
|
|
*
|
|
* We can't modify any property that this material is the authority
|
|
* for unless we create another material to take its place first and
|
|
* make sure descendants reference this new material instead.
|
|
*/
|
|
if (has_strong_children (material))
|
|
{
|
|
CoglMaterial *new_authority;
|
|
COGL_STATIC_COUNTER (material_copy_on_write_counter,
|
|
"material copy on write counter",
|
|
"Increments each time a material "
|
|
"must be copied to allow modification",
|
|
0 /* no application private data */);
|
|
|
|
COGL_COUNTER_INC (_cogl_uprof_context, material_copy_on_write_counter);
|
|
|
|
new_authority = cogl_material_copy (material->parent);
|
|
_cogl_material_set_static_breadcrumb (new_authority,
|
|
"pre_change_notify:copy-on-write");
|
|
|
|
/* We could explicitly walk the descendants, OR together the set
|
|
* of differences that we determine this material is the
|
|
* authority on and only copy those differences copied across.
|
|
*
|
|
* Or, if we don't explicitly walk the descendants we at least
|
|
* know that material->differences represents the largest set of
|
|
* differences that this material could possibly be an authority
|
|
* on.
|
|
*
|
|
* We do the later just because it's simplest, but we might need
|
|
* to come back to this later...
|
|
*/
|
|
_cogl_material_copy_differences (new_authority, material,
|
|
material->differences);
|
|
|
|
/* Reparent the strong children of material to be children of
|
|
* new_authority instead... */
|
|
new_authority->has_children = FALSE;
|
|
new_authority->first_child = NULL;
|
|
new_authority->children = NULL;
|
|
_cogl_material_foreach_child (material,
|
|
reparent_strong_children_cb,
|
|
new_authority);
|
|
}
|
|
|
|
/* At this point we know we have a material with no strong
|
|
* dependants (though we may have some weak children) so we are now
|
|
* free to modify the material. */
|
|
|
|
material->age++;
|
|
|
|
/* If the material isn't already an authority for the state group
|
|
* being modified then we need to initialize the corresponding
|
|
* state. */
|
|
if (change & COGL_MATERIAL_STATE_ALL_SPARSE)
|
|
authority = _cogl_material_get_authority (material, change);
|
|
else
|
|
authority = material;
|
|
_cogl_material_initialize_state (material, authority, change);
|
|
|
|
/* Each material has a sorted cache of the layers it depends on
|
|
* which will need updating via _cogl_material_update_layers_cache
|
|
* if a material's layers are changed. */
|
|
if (change == COGL_MATERIAL_STATE_LAYERS)
|
|
recursively_free_layer_caches (material);
|
|
|
|
/* If the material being changed is the same as the last material we
|
|
* flushed then we keep a track of the changes so we can try to
|
|
* minimize redundant OpenGL calls if the same material is flushed
|
|
* again.
|
|
*/
|
|
if (ctx->current_material == material)
|
|
ctx->current_material_changes_since_flush |= change;
|
|
}
|
|
|
|
|
|
static void
|
|
_cogl_material_add_layer_difference (CoglMaterial *material,
|
|
CoglMaterialLayer *layer,
|
|
gboolean inc_n_layers)
|
|
{
|
|
g_return_if_fail (layer->owner == NULL);
|
|
|
|
layer->owner = material;
|
|
cogl_handle_ref ((CoglHandle)layer);
|
|
|
|
/* - Flush journal primitives referencing the current state.
|
|
* - Make sure the material has no dependants so it may be modified.
|
|
* - If the material isn't currently an authority for the state being
|
|
* changed, then initialize that state from the current authority.
|
|
*/
|
|
_cogl_material_pre_change_notify (material,
|
|
COGL_MATERIAL_STATE_LAYERS,
|
|
NULL);
|
|
|
|
material->differences |= COGL_MATERIAL_STATE_LAYERS;
|
|
|
|
material->layer_differences =
|
|
g_list_prepend (material->layer_differences, layer);
|
|
|
|
if (inc_n_layers)
|
|
material->n_layers++;
|
|
}
|
|
|
|
/* NB: If you are calling this it's your responsibility to have
|
|
* already called:
|
|
* _cogl_material_pre_change_notify (m, _CHANGE_LAYERS, NULL);
|
|
*/
|
|
static void
|
|
_cogl_material_remove_layer_difference (CoglMaterial *material,
|
|
CoglMaterialLayer *layer,
|
|
gboolean dec_n_layers)
|
|
{
|
|
g_return_if_fail (layer->owner == material);
|
|
|
|
/* - Flush journal primitives referencing the current state.
|
|
* - Make sure the material has no dependants so it may be modified.
|
|
* - If the material isn't currently an authority for the state being
|
|
* changed, then initialize that state from the current authority.
|
|
*/
|
|
_cogl_material_pre_change_notify (material,
|
|
COGL_MATERIAL_STATE_LAYERS,
|
|
NULL);
|
|
|
|
layer->owner = NULL;
|
|
cogl_handle_unref ((CoglHandle)layer);
|
|
|
|
material->differences |= COGL_MATERIAL_STATE_LAYERS;
|
|
|
|
material->layer_differences =
|
|
g_list_remove (material->layer_differences, layer);
|
|
|
|
if (dec_n_layers)
|
|
material->n_layers--;
|
|
}
|
|
|
|
static void
|
|
_cogl_material_try_reverting_layers_authority (CoglMaterial *authority,
|
|
CoglMaterial *old_authority)
|
|
{
|
|
if (authority->layer_differences == NULL && authority->parent)
|
|
{
|
|
/* If the previous _STATE_LAYERS authority has the same
|
|
* ->n_layers then we can revert to that being the authority
|
|
* again. */
|
|
if (!old_authority)
|
|
{
|
|
old_authority =
|
|
_cogl_material_get_authority (authority->parent,
|
|
COGL_MATERIAL_STATE_LAYERS);
|
|
}
|
|
|
|
if (old_authority->n_layers == authority->n_layers)
|
|
authority->differences &= ~COGL_MATERIAL_STATE_LAYERS;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
handle_automatic_blend_enable (CoglMaterial *material,
|
|
CoglMaterialState change)
|
|
{
|
|
gboolean blend_enable =
|
|
_cogl_material_needs_blending_enabled (material, change, NULL);
|
|
|
|
if (blend_enable != material->real_blend_enable)
|
|
{
|
|
/* - Flush journal primitives referencing the current state.
|
|
* - Make sure the material has no dependants so it may be
|
|
* modified.
|
|
* - If the material isn't currently an authority for the state
|
|
* being changed, then initialize that state from the current
|
|
* authority.
|
|
*/
|
|
_cogl_material_pre_change_notify (material,
|
|
COGL_MATERIAL_STATE_REAL_BLEND_ENABLE,
|
|
NULL);
|
|
material->real_blend_enable = blend_enable;
|
|
}
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
int keep_n;
|
|
int current_pos;
|
|
gboolean needs_pruning;
|
|
int first_index_to_prune;
|
|
} CoglMaterialPruneLayersInfo;
|
|
|
|
static gboolean
|
|
update_prune_layers_info_cb (CoglMaterialLayer *layer, void *user_data)
|
|
{
|
|
CoglMaterialPruneLayersInfo *state = user_data;
|
|
|
|
if (state->current_pos == state->keep_n)
|
|
{
|
|
state->needs_pruning = TRUE;
|
|
state->first_index_to_prune = layer->index;
|
|
return FALSE;
|
|
}
|
|
state->current_pos++;
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
_cogl_material_prune_to_n_layers (CoglMaterial *material, int n)
|
|
{
|
|
CoglMaterialPruneLayersInfo state;
|
|
gboolean notified_change = TRUE;
|
|
GList *l;
|
|
GList *next;
|
|
|
|
state.keep_n = n;
|
|
state.current_pos = 0;
|
|
state.needs_pruning = FALSE;
|
|
_cogl_material_foreach_layer (material,
|
|
update_prune_layers_info_cb,
|
|
&state);
|
|
|
|
material->n_layers = n;
|
|
|
|
if (!state.needs_pruning)
|
|
return;
|
|
|
|
if (!(material->differences & COGL_MATERIAL_STATE_LAYERS))
|
|
return;
|
|
|
|
/* It's possible that this material owns some of the layers being
|
|
* discarded, so we'll need to unlink them... */
|
|
for (l = material->layer_differences; l; l = next)
|
|
{
|
|
CoglMaterialLayer *layer = l->data;
|
|
next = l->next; /* we're modifying the list we're iterating */
|
|
|
|
if (layer->index > state.first_index_to_prune)
|
|
{
|
|
if (!notified_change)
|
|
{
|
|
/* - Flush journal primitives referencing the current
|
|
* state.
|
|
* - Make sure the material has no dependants so it may
|
|
* be modified.
|
|
* - If the material isn't currently an authority for
|
|
* the state being changed, then initialize that state
|
|
* from the current authority.
|
|
*/
|
|
_cogl_material_pre_change_notify (material,
|
|
COGL_MATERIAL_STATE_LAYERS,
|
|
NULL);
|
|
notified_change = TRUE;
|
|
}
|
|
|
|
material->layer_differences =
|
|
g_list_delete_link (material->layer_differences, l);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
_cogl_material_backend_layer_change_notify (CoglMaterialLayer *layer,
|
|
CoglMaterialLayerState change)
|
|
{
|
|
int i;
|
|
|
|
/* NB: layers may be used by multiple materials which may be using
|
|
* different backends, therefore we determine which backends to
|
|
* notify based on the private state pointers for each backend...
|
|
*/
|
|
for (i = 0; i < COGL_MATERIAL_N_BACKENDS; i++)
|
|
{
|
|
if (layer->backend_priv[i] && backends[i]->layer_pre_change_notify)
|
|
backends[i]->layer_pre_change_notify (layer, change);
|
|
}
|
|
}
|
|
|
|
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 void
|
|
_cogl_material_layer_initialize_state (CoglMaterialLayer *dest,
|
|
CoglMaterialLayer *src,
|
|
unsigned long differences)
|
|
{
|
|
CoglMaterialLayerBigState *big_state;
|
|
|
|
dest->differences |= differences;
|
|
|
|
if (differences & COGL_MATERIAL_LAYER_STATE_UNIT)
|
|
dest->unit_index = src->unit_index;
|
|
|
|
if (differences & COGL_MATERIAL_LAYER_STATE_TEXTURE)
|
|
dest->texture = src->texture;
|
|
|
|
if (differences & COGL_MATERIAL_LAYER_STATE_FILTERS)
|
|
{
|
|
dest->min_filter = src->min_filter;
|
|
dest->mag_filter = src->mag_filter;
|
|
}
|
|
|
|
if (differences & COGL_MATERIAL_LAYER_STATE_WRAP_MODES)
|
|
{
|
|
dest->wrap_mode_s = src->wrap_mode_s;
|
|
dest->wrap_mode_t = src->wrap_mode_t;
|
|
dest->wrap_mode_r = src->wrap_mode_r;
|
|
}
|
|
|
|
if (differences & COGL_MATERIAL_LAYER_STATE_NEEDS_BIG_STATE)
|
|
{
|
|
if (!dest->has_big_state)
|
|
{
|
|
dest->big_state = g_slice_new (CoglMaterialLayerBigState);
|
|
dest->has_big_state = TRUE;
|
|
}
|
|
big_state = dest->big_state;
|
|
}
|
|
else
|
|
return;
|
|
|
|
if (differences & COGL_MATERIAL_LAYER_STATE_COMBINE)
|
|
{
|
|
int n_args;
|
|
int i;
|
|
GLint func = src->big_state->texture_combine_rgb_func;
|
|
big_state->texture_combine_rgb_func = func;
|
|
n_args = get_n_args_for_combine_func (func);
|
|
for (i = 0; i < n_args; i++)
|
|
{
|
|
big_state->texture_combine_rgb_src[i] =
|
|
src->big_state->texture_combine_rgb_src[i];
|
|
big_state->texture_combine_rgb_op[i] =
|
|
src->big_state->texture_combine_rgb_op[i];
|
|
}
|
|
|
|
func = src->big_state->texture_combine_alpha_func;
|
|
big_state->texture_combine_alpha_func = func;
|
|
n_args = get_n_args_for_combine_func (func);
|
|
for (i = 0; i < n_args; i++)
|
|
{
|
|
big_state->texture_combine_alpha_src[i] =
|
|
src->big_state->texture_combine_alpha_src[i];
|
|
big_state->texture_combine_alpha_op[i] =
|
|
src->big_state->texture_combine_alpha_op[i];
|
|
}
|
|
}
|
|
|
|
if (differences & COGL_MATERIAL_LAYER_STATE_COMBINE_CONSTANT)
|
|
memcpy (dest->big_state->texture_combine_constant,
|
|
src->big_state->texture_combine_constant,
|
|
sizeof (float) * 4);
|
|
|
|
if (differences & COGL_MATERIAL_LAYER_STATE_USER_MATRIX)
|
|
dest->big_state->matrix = src->big_state->matrix;
|
|
}
|
|
|
|
/* NB: This function will allocate a new derived layer if you are
|
|
* trying to change the state of a layer with dependants so you must
|
|
* always check the return value.
|
|
*
|
|
* If a new layer is returned it will be owned by required_owner.
|
|
*
|
|
* required_owner can only by NULL for new, currently unowned layers
|
|
* with no dependants.
|
|
*/
|
|
static CoglMaterialLayer *
|
|
_cogl_material_layer_pre_change_notify (CoglMaterial *required_owner,
|
|
CoglMaterialLayer *layer,
|
|
CoglMaterialLayerState change)
|
|
{
|
|
CoglTextureUnit *unit;
|
|
CoglMaterialLayer *authority;
|
|
|
|
/* Identify the case where the layer is new with no owner or
|
|
* dependants and so we don't need to do anything. */
|
|
if (layer->has_children == FALSE && layer->owner == NULL)
|
|
goto init_layer_state;
|
|
|
|
/* We only allow a NULL required_owner for new layers */
|
|
g_return_val_if_fail (required_owner != NULL, layer);
|
|
|
|
/* Unlike materials; layers are simply considered immutable once
|
|
* they have dependants - either children or another material owner.
|
|
*/
|
|
if (layer->has_children || layer->owner != required_owner)
|
|
{
|
|
CoglMaterialLayer *new = _cogl_material_layer_copy (layer);
|
|
_cogl_material_add_layer_difference (required_owner, new, FALSE);
|
|
cogl_handle_unref (new);
|
|
layer = new;
|
|
goto init_layer_state;
|
|
}
|
|
|
|
/* Note: At this point we know there is only one material dependant on
|
|
* this layer (required_owner), and there are no other layers
|
|
* dependant on this layer so it's ok to modify it. */
|
|
|
|
if (required_owner->journal_ref_count)
|
|
_cogl_journal_flush ();
|
|
|
|
_cogl_material_backend_layer_change_notify (layer, change);
|
|
|
|
/* If the layer being changed is the same as the last layer we
|
|
* flushed to the corresponding texture unit then we keep a track of
|
|
* the changes so we can try to minimize redundant OpenGL calls if
|
|
* the same layer is flushed again.
|
|
*/
|
|
unit = _cogl_get_texture_unit (_cogl_material_layer_get_unit_index (layer));
|
|
if (unit->layer == layer)
|
|
unit->layer_changes_since_flush |= change;
|
|
|
|
init_layer_state:
|
|
|
|
if (required_owner)
|
|
required_owner->age++;
|
|
|
|
/* If the material isn't already an authority for the state group
|
|
* being modified then we need to initialize the corresponding
|
|
* state. */
|
|
authority = _cogl_material_layer_get_authority (layer, change);
|
|
_cogl_material_layer_initialize_state (layer, authority, change);
|
|
|
|
return layer;
|
|
}
|
|
|
|
/* XXX: This is duplicated logic; the same as for
|
|
* _cogl_material_prune_redundant_ancestry it would be nice to find a
|
|
* way to consolidate these functions! */
|
|
static void
|
|
_cogl_material_layer_prune_redundant_ancestry (CoglMaterialLayer *layer)
|
|
{
|
|
CoglMaterialLayer *new_parent = layer->parent;
|
|
|
|
/* walk up past ancestors that are now redundant and potentially
|
|
* reparent the layer. */
|
|
while (new_parent->parent &&
|
|
(new_parent->differences | layer->differences) ==
|
|
layer->differences)
|
|
new_parent = new_parent->parent;
|
|
|
|
if (new_parent != layer->parent)
|
|
{
|
|
CoglMaterialLayer *old_parent = layer->parent;
|
|
layer->parent = cogl_handle_ref (new_parent);
|
|
/* Note: the old parent may indirectly be keeping
|
|
the new parent alive so we have to ref the new
|
|
parent before unrefing the old */
|
|
cogl_handle_unref (old_parent);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* XXX: consider special casing layer->unit_index so it's not a sparse
|
|
* property so instead we can assume it's valid for all layer
|
|
* instances.
|
|
* - We would need to initialize ->unit_index in
|
|
* _cogl_material_layer_copy ().
|
|
*
|
|
* XXX: If you use this API you should consider that the given layer
|
|
* might not be writeable and so a new derived layer will be allocated
|
|
* and modified instead. The layer modified will be returned so you
|
|
* can identify when this happens.
|
|
*/
|
|
static CoglMaterialLayer *
|
|
_cogl_material_set_layer_unit (CoglMaterial *required_owner,
|
|
CoglMaterialLayer *layer,
|
|
int unit_index)
|
|
{
|
|
CoglMaterialLayerState change = COGL_MATERIAL_LAYER_STATE_UNIT;
|
|
CoglMaterialLayer *authority =
|
|
_cogl_material_layer_get_authority (layer, change);
|
|
CoglMaterialLayer *new;
|
|
|
|
if (authority->unit_index == unit_index)
|
|
return layer;
|
|
|
|
new =
|
|
_cogl_material_layer_pre_change_notify (required_owner,
|
|
layer,
|
|
change);
|
|
if (new != layer)
|
|
layer = new;
|
|
else
|
|
{
|
|
/* If the layer we found is currently the authority on the state
|
|
* we are changing see if we can revert to one of our ancestors
|
|
* being the authority. */
|
|
if (layer == authority && authority->parent != NULL)
|
|
{
|
|
CoglMaterialLayer *old_authority =
|
|
_cogl_material_layer_get_authority (authority->parent, change);
|
|
|
|
if (old_authority->unit_index == unit_index)
|
|
{
|
|
layer->differences &= ~change;
|
|
return layer;
|
|
}
|
|
}
|
|
}
|
|
|
|
layer->unit_index = unit_index;
|
|
|
|
/* If we weren't previously the authority on this state then we need
|
|
* to extended our differences mask and so it's possible that some
|
|
* of our ancestry will now become redundant, so we aim to reparent
|
|
* ourselves if that's true... */
|
|
if (layer != authority)
|
|
{
|
|
layer->differences |= change;
|
|
_cogl_material_layer_prune_redundant_ancestry (layer);
|
|
}
|
|
|
|
return layer;
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
/* The layer we are trying to find */
|
|
int layer_index;
|
|
|
|
/* The layer we find or untouched if not found */
|
|
CoglMaterialLayer *layer;
|
|
|
|
/* If the layer can't be found then a new layer should be
|
|
* inserted after this texture unit index... */
|
|
int insert_after;
|
|
|
|
/* When adding a layer we need the list of layers to shift up
|
|
* to a new texture unit. When removing we need the list of
|
|
* layers to shift down.
|
|
*
|
|
* Note: the list isn't sorted */
|
|
CoglMaterialLayer **layers_to_shift;
|
|
int n_layers_to_shift;
|
|
|
|
/* When adding a layer we don't need a complete list of
|
|
* layers_to_shift if we find a layer already corresponding to the
|
|
* layer_index. */
|
|
gboolean ignore_shift_layers_if_found;
|
|
|
|
} CoglMaterialLayerInfo;
|
|
|
|
/* Returns TRUE once we know there is nothing more to update */
|
|
static gboolean
|
|
update_layer_info (CoglMaterialLayer *layer,
|
|
CoglMaterialLayerInfo *layer_info)
|
|
{
|
|
if (layer->index == layer_info->layer_index)
|
|
{
|
|
layer_info->layer = layer;
|
|
if (layer_info->ignore_shift_layers_if_found)
|
|
return TRUE;
|
|
}
|
|
else if (layer->index < layer_info->layer_index)
|
|
{
|
|
int unit_index = _cogl_material_layer_get_unit_index (layer);
|
|
layer_info->insert_after = unit_index;
|
|
}
|
|
else
|
|
layer_info->layers_to_shift[layer_info->n_layers_to_shift++] =
|
|
layer;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/* Returns FALSE to break out of a _foreach_layer () iteration */
|
|
static gboolean
|
|
update_layer_info_cb (CoglMaterialLayer *layer,
|
|
void *user_data)
|
|
{
|
|
CoglMaterialLayerInfo *layer_info = user_data;
|
|
|
|
if (update_layer_info (layer, layer_info))
|
|
return FALSE; /* break */
|
|
else
|
|
return TRUE; /* continue */
|
|
}
|
|
|
|
static void
|
|
_cogl_material_get_layer_info (CoglMaterial *material,
|
|
CoglMaterialLayerInfo *layer_info)
|
|
{
|
|
/* Note: we are assuming this material is a _STATE_LAYERS authority */
|
|
int n_layers = material->n_layers;
|
|
int i;
|
|
|
|
/* FIXME: _cogl_material_foreach_layer now calls
|
|
* _cogl_material_update_layers_cache anyway so this codepath is
|
|
* pointless! */
|
|
if (layer_info->ignore_shift_layers_if_found &&
|
|
material->layers_cache_dirty)
|
|
{
|
|
/* The expectation is that callers of
|
|
* _cogl_material_get_layer_info are likely to be modifying the
|
|
* list of layers associated with a material so in this case
|
|
* where we don't have a cache of the layers and we don't
|
|
* necessarily have to iterate all the layers of the material we
|
|
* use a foreach_layer callback instead of updating the cache
|
|
* and iterating that as below. */
|
|
_cogl_material_foreach_layer (material,
|
|
update_layer_info_cb,
|
|
layer_info);
|
|
return;
|
|
}
|
|
|
|
_cogl_material_update_layers_cache (material);
|
|
for (i = 0; i < n_layers; i++)
|
|
{
|
|
CoglMaterialLayer *layer = material->layers_cache[i];
|
|
|
|
if (update_layer_info (layer, layer_info))
|
|
return;
|
|
}
|
|
}
|
|
|
|
static CoglMaterialLayer *
|
|
_cogl_material_get_layer (CoglMaterial *material,
|
|
int layer_index)
|
|
{
|
|
CoglMaterial *authority =
|
|
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_LAYERS);
|
|
CoglMaterialLayerInfo layer_info;
|
|
CoglMaterialLayer *layer;
|
|
int unit_index;
|
|
int i;
|
|
|
|
_COGL_GET_CONTEXT (ctx, NULL);
|
|
|
|
/* The layer index of the layer we want info about */
|
|
layer_info.layer_index = layer_index;
|
|
|
|
/* If a layer already exists with the given index this will be
|
|
* updated. */
|
|
layer_info.layer = NULL;
|
|
|
|
/* If a layer isn't found for the given index we'll need to know
|
|
* where to insert a new layer. */
|
|
layer_info.insert_after = -1;
|
|
|
|
/* If a layer can't be found then we'll need to insert a new layer
|
|
* and bump up the texture unit for all layers with an index
|
|
* > layer_index. */
|
|
layer_info.layers_to_shift =
|
|
g_alloca (sizeof (CoglMaterialLayer *) * authority->n_layers);
|
|
layer_info.n_layers_to_shift = 0;
|
|
|
|
/* If an exact match is found though we don't need a complete
|
|
* list of layers with indices > layer_index... */
|
|
layer_info.ignore_shift_layers_if_found = TRUE;
|
|
|
|
_cogl_material_get_layer_info (authority, &layer_info);
|
|
|
|
if (layer_info.layer)
|
|
return layer_info.layer;
|
|
|
|
unit_index = layer_info.insert_after + 1;
|
|
if (unit_index == 0)
|
|
layer = _cogl_material_layer_copy (ctx->default_layer_0);
|
|
else
|
|
{
|
|
CoglMaterialLayer *new;
|
|
layer = _cogl_material_layer_copy (ctx->default_layer_n);
|
|
new = _cogl_material_set_layer_unit (NULL, layer, unit_index);
|
|
/* Since we passed a newly allocated layer we wouldn't expect
|
|
* _set_layer_unit() to have to allocate *another* layer. */
|
|
g_assert (new == layer);
|
|
layer->index = layer_index;
|
|
}
|
|
|
|
for (i = 0; i < layer_info.n_layers_to_shift; i++)
|
|
{
|
|
CoglMaterialLayer *shift_layer = layer_info.layers_to_shift[i];
|
|
|
|
unit_index = _cogl_material_layer_get_unit_index (shift_layer);
|
|
_cogl_material_set_layer_unit (material, shift_layer, unit_index + 1);
|
|
/* NB: shift_layer may not be writeable so _set_layer_unit()
|
|
* will allocate a derived layer internally which will become
|
|
* owned by material. Check the return value if we need to do
|
|
* anything else with this layer. */
|
|
}
|
|
|
|
_cogl_material_add_layer_difference (material, layer, TRUE);
|
|
|
|
cogl_handle_unref (layer);
|
|
|
|
return layer;
|
|
}
|
|
|
|
static CoglHandle
|
|
_cogl_material_layer_get_texture (CoglMaterialLayer *layer)
|
|
{
|
|
CoglMaterialLayer *authority =
|
|
_cogl_material_layer_get_authority (layer,
|
|
COGL_MATERIAL_LAYER_STATE_TEXTURE);
|
|
|
|
return authority->texture;
|
|
}
|
|
|
|
static void
|
|
_cogl_material_prune_empty_layer_difference (CoglMaterial *layers_authority,
|
|
CoglMaterialLayer *layer)
|
|
{
|
|
/* Find the GList link that references the empty layer */
|
|
GList *link = g_list_find (layers_authority->layer_differences, layer);
|
|
/* No material directly owns the root node layer so this is safe... */
|
|
CoglMaterialLayer *layer_parent = layer->parent;
|
|
CoglMaterialLayerInfo layer_info;
|
|
CoglMaterial *old_layers_authority;
|
|
|
|
g_return_if_fail (link != NULL);
|
|
|
|
/* If the layer's parent doesn't have an owner then we can simply
|
|
* take ownership ourselves and drop our reference on the empty
|
|
* layer.
|
|
*/
|
|
if (layer_parent->index == layer->index && layer_parent->owner == NULL)
|
|
{
|
|
cogl_handle_ref (layer_parent);
|
|
cogl_handle_unref (layer);
|
|
link->data = layer->parent;
|
|
recursively_free_layer_caches (layers_authority);
|
|
return;
|
|
}
|
|
|
|
/* Now we want to find the layer that would become the authority for
|
|
* layer->index if we were to remove layer from
|
|
* layers_authority->layer_differences
|
|
*/
|
|
|
|
/* The layer index of the layer we want info about */
|
|
layer_info.layer_index = layer->index;
|
|
|
|
/* If a layer already exists with the given index this will be
|
|
* updated. */
|
|
layer_info.layer = NULL;
|
|
|
|
/* If a layer can't be found then we'll need to insert a new layer
|
|
* and bump up the texture unit for all layers with an index
|
|
* > layer_index. */
|
|
layer_info.layers_to_shift =
|
|
g_alloca (sizeof (CoglMaterialLayer *) * layers_authority->n_layers);
|
|
layer_info.n_layers_to_shift = 0;
|
|
|
|
/* If an exact match is found though we don't need a complete
|
|
* list of layers with indices > layer_index... */
|
|
layer_info.ignore_shift_layers_if_found = TRUE;
|
|
|
|
/* We know the default/root material isn't a LAYERS authority so
|
|
* it's safe to dereference layers_authority->parent. */
|
|
old_layers_authority =
|
|
_cogl_material_get_authority (layers_authority->parent,
|
|
COGL_MATERIAL_STATE_LAYERS);
|
|
|
|
_cogl_material_get_layer_info (old_layers_authority, &layer_info);
|
|
|
|
/* If layer is the defining layer for the corresponding ->index then
|
|
* we can't get rid of it. */
|
|
if (!layer_info.layer)
|
|
return;
|
|
|
|
/* If the layer that would become the authority for layer->index
|
|
* is layer->parent then we can simply remove the layer difference. */
|
|
if (layer_info.layer == layer->parent)
|
|
{
|
|
_cogl_material_remove_layer_difference (layers_authority, layer, FALSE);
|
|
_cogl_material_try_reverting_layers_authority (layers_authority,
|
|
old_layers_authority);
|
|
}
|
|
}
|
|
|
|
static void
|
|
_cogl_material_set_layer_texture (CoglMaterial *material,
|
|
int layer_index,
|
|
CoglHandle texture,
|
|
gboolean overriden,
|
|
GLuint slice_gl_texture,
|
|
GLenum slice_gl_target)
|
|
{
|
|
CoglMaterialLayerState change = COGL_MATERIAL_LAYER_STATE_TEXTURE;
|
|
CoglMaterialLayer *layer;
|
|
CoglMaterialLayer *authority;
|
|
CoglMaterialLayer *new;
|
|
|
|
/* Note: this will ensure that the layer exists, creating one if it
|
|
* doesn't already.
|
|
*
|
|
* Note: If the layer already existed it's possibly owned by another
|
|
* material. If the layer is created then it will be owned by
|
|
* material. */
|
|
layer = _cogl_material_get_layer (material, layer_index);
|
|
|
|
/* Now find the ancestor of the layer that is the authority for the
|
|
* state we want to change */
|
|
authority = _cogl_material_layer_get_authority (layer, change);
|
|
|
|
if (authority->texture_overridden == overriden &&
|
|
authority->texture == texture &&
|
|
(authority->texture_overridden == FALSE ||
|
|
(authority->slice_gl_texture == slice_gl_texture &&
|
|
authority->slice_gl_target == slice_gl_target)))
|
|
return;
|
|
|
|
new = _cogl_material_layer_pre_change_notify (material, layer, change);
|
|
if (new != layer)
|
|
layer = new;
|
|
else
|
|
{
|
|
/* If the original layer we found is currently the authority on
|
|
* the state we are changing see if we can revert to one of our
|
|
* ancestors being the authority. */
|
|
if (layer == authority && authority->parent != NULL)
|
|
{
|
|
CoglMaterialLayer *old_authority =
|
|
_cogl_material_layer_get_authority (authority->parent, change);
|
|
|
|
if (old_authority->texture_overridden == overriden &&
|
|
old_authority->texture == texture &&
|
|
(old_authority->texture_overridden == FALSE ||
|
|
(old_authority->slice_gl_texture == slice_gl_texture &&
|
|
old_authority->slice_gl_target == slice_gl_target)))
|
|
{
|
|
layer->differences &= ~change;
|
|
|
|
g_assert (layer->owner == material);
|
|
if (layer->differences == 0)
|
|
_cogl_material_prune_empty_layer_difference (material,
|
|
layer);
|
|
goto changed;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (texture != COGL_INVALID_HANDLE)
|
|
cogl_handle_ref (texture);
|
|
if (layer == authority &&
|
|
layer->texture != COGL_INVALID_HANDLE)
|
|
cogl_handle_unref (layer->texture);
|
|
layer->texture = texture;
|
|
layer->texture_overridden = FALSE;
|
|
layer->slice_gl_texture = slice_gl_texture;
|
|
layer->slice_gl_target = slice_gl_target;
|
|
|
|
/* If we weren't previously the authority on this state then we need
|
|
* to extended our differences mask and so it's possible that some
|
|
* of our ancestry will now become redundant, so we aim to reparent
|
|
* ourselves if that's true... */
|
|
if (layer != authority)
|
|
{
|
|
layer->differences |= change;
|
|
_cogl_material_layer_prune_redundant_ancestry (layer);
|
|
}
|
|
|
|
changed:
|
|
|
|
handle_automatic_blend_enable (material, COGL_MATERIAL_STATE_LAYERS);
|
|
}
|
|
|
|
static void
|
|
_cogl_material_set_layer_gl_texture_slice (CoglHandle handle,
|
|
int layer_index,
|
|
CoglHandle texture,
|
|
GLuint slice_gl_texture,
|
|
GLenum slice_gl_target)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
|
|
g_return_if_fail (cogl_is_material (handle));
|
|
/* GL texture overrides can only be set in association with a parent
|
|
* CoglTexture */
|
|
g_return_if_fail (cogl_is_texture (texture));
|
|
|
|
_cogl_material_set_layer_texture (material,
|
|
layer_index,
|
|
texture,
|
|
TRUE, /* slice override */
|
|
slice_gl_texture,
|
|
slice_gl_target);
|
|
}
|
|
|
|
/* XXX: deprecate and replace with cogl_material_set_layer_texture?
|
|
*
|
|
* Originally I was planning on allowing users to set shaders somehow
|
|
* on layers (thus the ambiguous name), but now I wonder if we will do
|
|
* that with a more explicit "snippets" API and materials will have
|
|
* hooks defined to receive these snippets.
|
|
*/
|
|
void
|
|
cogl_material_set_layer (CoglHandle handle,
|
|
int layer_index,
|
|
CoglHandle texture)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
|
|
g_return_if_fail (cogl_is_material (handle));
|
|
g_return_if_fail (texture == COGL_INVALID_HANDLE ||
|
|
cogl_is_texture (texture));
|
|
|
|
_cogl_material_set_layer_texture (material,
|
|
layer_index,
|
|
texture,
|
|
FALSE, /* slice override */
|
|
0, /* slice_gl_texture */
|
|
0); /* slice_gl_target */
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
int i;
|
|
CoglMaterial *material;
|
|
unsigned long fallback_layers;
|
|
} CoglMaterialFallbackState;
|
|
|
|
static gboolean
|
|
fallback_layer_cb (CoglMaterialLayer *layer, void *user_data)
|
|
{
|
|
CoglMaterialFallbackState *state = user_data;
|
|
CoglMaterial *material = state->material;
|
|
CoglHandle texture = _cogl_material_layer_get_texture (layer);
|
|
GLenum gl_target;
|
|
COGL_STATIC_COUNTER (layer_fallback_counter,
|
|
"layer fallback counter",
|
|
"Increments each time a layer's texture is "
|
|
"forced to a fallback texture",
|
|
0 /* no application private data */);
|
|
|
|
_COGL_GET_CONTEXT (ctx, FALSE);
|
|
|
|
if (!(state->fallback_layers & 1<<state->i))
|
|
return TRUE;
|
|
|
|
COGL_COUNTER_INC (_cogl_uprof_context, layer_fallback_counter);
|
|
|
|
if (G_LIKELY (texture != COGL_INVALID_HANDLE))
|
|
cogl_texture_get_gl_texture (texture, NULL, &gl_target);
|
|
else
|
|
gl_target = GL_TEXTURE_2D;
|
|
|
|
if (gl_target == GL_TEXTURE_2D)
|
|
texture = ctx->default_gl_texture_2d_tex;
|
|
#ifdef HAVE_COGL_GL
|
|
else if (gl_target == GL_TEXTURE_RECTANGLE_ARB)
|
|
texture = ctx->default_gl_texture_rect_tex;
|
|
#endif
|
|
else
|
|
{
|
|
g_warning ("We don't have a fallback 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... */
|
|
texture = ctx->default_gl_texture_2d_tex;
|
|
}
|
|
|
|
cogl_material_set_layer (material, layer->index, texture);
|
|
|
|
state->i++;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
_cogl_material_set_layer_wrap_modes (CoglMaterial *material,
|
|
CoglMaterialLayer *layer,
|
|
CoglMaterialLayer *authority,
|
|
CoglMaterialWrapModeInternal wrap_mode_s,
|
|
CoglMaterialWrapModeInternal wrap_mode_t,
|
|
CoglMaterialWrapModeInternal wrap_mode_r)
|
|
{
|
|
CoglMaterialLayer *new;
|
|
CoglMaterialLayerState change = COGL_MATERIAL_LAYER_STATE_WRAP_MODES;
|
|
|
|
if (authority->wrap_mode_s == wrap_mode_s &&
|
|
authority->wrap_mode_t == wrap_mode_t &&
|
|
authority->wrap_mode_r == wrap_mode_r)
|
|
return;
|
|
|
|
new = _cogl_material_layer_pre_change_notify (material, layer, change);
|
|
if (new != layer)
|
|
layer = new;
|
|
else
|
|
{
|
|
/* If the original layer we found is currently the authority on
|
|
* the state we are changing see if we can revert to one of our
|
|
* ancestors being the authority. */
|
|
if (layer == authority && authority->parent != NULL)
|
|
{
|
|
CoglMaterialLayer *old_authority =
|
|
_cogl_material_layer_get_authority (authority->parent, change);
|
|
|
|
if (old_authority->wrap_mode_s == wrap_mode_s &&
|
|
old_authority->wrap_mode_t == wrap_mode_t &&
|
|
old_authority->wrap_mode_r == wrap_mode_r)
|
|
{
|
|
layer->differences &= ~change;
|
|
|
|
g_assert (layer->owner == material);
|
|
if (layer->differences == 0)
|
|
_cogl_material_prune_empty_layer_difference (material,
|
|
layer);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
layer->wrap_mode_s = wrap_mode_s;
|
|
layer->wrap_mode_t = wrap_mode_t;
|
|
layer->wrap_mode_r = wrap_mode_r;
|
|
|
|
/* If we weren't previously the authority on this state then we need
|
|
* to extended our differences mask and so it's possible that some
|
|
* of our ancestry will now become redundant, so we aim to reparent
|
|
* ourselves if that's true... */
|
|
if (layer != authority)
|
|
{
|
|
layer->differences |= change;
|
|
_cogl_material_layer_prune_redundant_ancestry (layer);
|
|
}
|
|
}
|
|
|
|
static CoglMaterialWrapModeInternal
|
|
public_to_internal_wrap_mode (CoglMaterialWrapMode mode)
|
|
{
|
|
return (CoglMaterialWrapModeInternal)mode;
|
|
}
|
|
|
|
static CoglMaterialWrapMode
|
|
internal_to_public_wrap_mode (CoglMaterialWrapModeInternal internal_mode)
|
|
{
|
|
g_return_val_if_fail (internal_mode !=
|
|
COGL_MATERIAL_WRAP_MODE_INTERNAL_CLAMP_TO_BORDER,
|
|
COGL_MATERIAL_WRAP_MODE_AUTOMATIC);
|
|
return (CoglMaterialWrapMode)internal_mode;
|
|
}
|
|
|
|
void
|
|
cogl_material_set_layer_wrap_mode_s (CoglHandle handle,
|
|
int layer_index,
|
|
CoglMaterialWrapMode mode)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterialLayerState change = COGL_MATERIAL_LAYER_STATE_WRAP_MODES;
|
|
CoglMaterialLayer *layer;
|
|
CoglMaterialLayer *authority;
|
|
CoglMaterialWrapModeInternal internal_mode =
|
|
public_to_internal_wrap_mode (mode);
|
|
|
|
g_return_if_fail (cogl_is_material (handle));
|
|
|
|
/* Note: this will ensure that the layer exists, creating one if it
|
|
* doesn't already.
|
|
*
|
|
* Note: If the layer already existed it's possibly owned by another
|
|
* material. If the layer is created then it will be owned by
|
|
* material. */
|
|
layer = _cogl_material_get_layer (material, layer_index);
|
|
|
|
/* Now find the ancestor of the layer that is the authority for the
|
|
* state we want to change */
|
|
authority = _cogl_material_layer_get_authority (layer, change);
|
|
|
|
_cogl_material_set_layer_wrap_modes (material, layer, authority,
|
|
internal_mode,
|
|
authority->wrap_mode_t,
|
|
authority->wrap_mode_r);
|
|
}
|
|
|
|
void
|
|
cogl_material_set_layer_wrap_mode_t (CoglHandle handle,
|
|
int layer_index,
|
|
CoglMaterialWrapMode mode)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterialLayerState change = COGL_MATERIAL_LAYER_STATE_WRAP_MODES;
|
|
CoglMaterialLayer *layer;
|
|
CoglMaterialLayer *authority;
|
|
CoglMaterialWrapModeInternal internal_mode =
|
|
public_to_internal_wrap_mode (mode);
|
|
|
|
g_return_if_fail (cogl_is_material (handle));
|
|
|
|
/* Note: this will ensure that the layer exists, creating one if it
|
|
* doesn't already.
|
|
*
|
|
* Note: If the layer already existed it's possibly owned by another
|
|
* material. If the layer is created then it will be owned by
|
|
* material. */
|
|
layer = _cogl_material_get_layer (material, layer_index);
|
|
|
|
/* Now find the ancestor of the layer that is the authority for the
|
|
* state we want to change */
|
|
authority = _cogl_material_layer_get_authority (layer, change);
|
|
|
|
_cogl_material_set_layer_wrap_modes (material, layer, authority,
|
|
authority->wrap_mode_s,
|
|
internal_mode,
|
|
authority->wrap_mode_r);
|
|
}
|
|
|
|
/* 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 = COGL_MATERIAL (handle);
|
|
CoglMaterialLayerState change = COGL_MATERIAL_LAYER_STATE_WRAP_MODES;
|
|
CoglMaterialLayer *layer;
|
|
CoglMaterialLayer *authority;
|
|
CoglMaterialWrapModeInternal internal_mode =
|
|
public_to_internal_wrap_mode (mode);
|
|
|
|
g_return_if_fail (cogl_is_material (handle));
|
|
|
|
/* Note: this will ensure that the layer exists, creating one if it
|
|
* doesn't already.
|
|
*
|
|
* Note: If the layer already existed it's possibly owned by another
|
|
* material. If the layer is created then it will be owned by
|
|
* material. */
|
|
layer = _cogl_material_get_layer (material, layer_index);
|
|
|
|
/* Now find the ancestor of the layer that is the authority for the
|
|
* state we want to change */
|
|
authority = _cogl_material_layer_get_authority (layer, change);
|
|
|
|
_cogl_material_set_layer_wrap_modes (material, layer, authority,
|
|
authority->wrap_mode_s,
|
|
authority->wrap_mode_t,
|
|
internal_mode);
|
|
}
|
|
|
|
void
|
|
cogl_material_set_layer_wrap_mode (CoglHandle handle,
|
|
int layer_index,
|
|
CoglMaterialWrapMode mode)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterialLayerState change = COGL_MATERIAL_LAYER_STATE_WRAP_MODES;
|
|
CoglMaterialLayer *layer;
|
|
CoglMaterialLayer *authority;
|
|
CoglMaterialWrapModeInternal internal_mode =
|
|
public_to_internal_wrap_mode (mode);
|
|
|
|
g_return_if_fail (cogl_is_material (handle));
|
|
|
|
/* Note: this will ensure that the layer exists, creating one if it
|
|
* doesn't already.
|
|
*
|
|
* Note: If the layer already existed it's possibly owned by another
|
|
* material. If the layer is created then it will be owned by
|
|
* material. */
|
|
layer = _cogl_material_get_layer (material, layer_index);
|
|
|
|
/* Now find the ancestor of the layer that is the authority for the
|
|
* state we want to change */
|
|
authority = _cogl_material_layer_get_authority (layer, change);
|
|
|
|
_cogl_material_set_layer_wrap_modes (material, layer, authority,
|
|
internal_mode,
|
|
internal_mode,
|
|
internal_mode);
|
|
/* XXX: I wonder if we should really be duplicating the mode into
|
|
* the 'r' wrap mode too? */
|
|
}
|
|
|
|
/* FIXME: deprecate this API */
|
|
CoglMaterialWrapMode
|
|
cogl_material_layer_get_wrap_mode_s (CoglHandle handle)
|
|
{
|
|
CoglMaterialLayer *layer = COGL_MATERIAL_LAYER (handle);
|
|
CoglMaterialLayerState change = COGL_MATERIAL_LAYER_STATE_WRAP_MODES;
|
|
CoglMaterialLayer *authority;
|
|
|
|
g_return_val_if_fail (cogl_is_material_layer (handle), FALSE);
|
|
|
|
/* Now find the ancestor of the layer that is the authority for the
|
|
* state we want to change */
|
|
authority = _cogl_material_layer_get_authority (layer, change);
|
|
|
|
return internal_to_public_wrap_mode (authority->wrap_mode_s);
|
|
}
|
|
|
|
CoglMaterialWrapMode
|
|
cogl_material_get_layer_wrap_mode_s (CoglHandle handle, int layer_index)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterialLayer *layer;
|
|
|
|
g_return_val_if_fail (cogl_is_material (handle), FALSE);
|
|
|
|
/* Note: this will ensure that the layer exists, creating one if it
|
|
* doesn't already.
|
|
*
|
|
* Note: If the layer already existed it's possibly owned by another
|
|
* material. If the layer is created then it will be owned by
|
|
* material. */
|
|
layer = _cogl_material_get_layer (material, layer_index);
|
|
/* FIXME: we shouldn't ever construct a layer in a getter function */
|
|
|
|
return cogl_material_layer_get_wrap_mode_s ((CoglHandle)layer);
|
|
}
|
|
|
|
/* FIXME: deprecate this API */
|
|
CoglMaterialWrapMode
|
|
cogl_material_layer_get_wrap_mode_t (CoglHandle handle)
|
|
{
|
|
CoglMaterialLayer *layer = COGL_MATERIAL_LAYER (handle);
|
|
CoglMaterialLayerState change = COGL_MATERIAL_LAYER_STATE_WRAP_MODES;
|
|
CoglMaterialLayer *authority;
|
|
|
|
g_return_val_if_fail (cogl_is_material_layer (handle), FALSE);
|
|
|
|
/* Now find the ancestor of the layer that is the authority for the
|
|
* state we want to change */
|
|
authority = _cogl_material_layer_get_authority (layer, change);
|
|
|
|
return internal_to_public_wrap_mode (authority->wrap_mode_t);
|
|
}
|
|
|
|
CoglMaterialWrapMode
|
|
cogl_material_get_layer_wrap_mode_t (CoglHandle handle, int layer_index)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterialLayer *layer;
|
|
|
|
g_return_val_if_fail (cogl_is_material (handle), FALSE);
|
|
|
|
/* Note: this will ensure that the layer exists, creating one if it
|
|
* doesn't already.
|
|
*
|
|
* Note: If the layer already existed it's possibly owned by another
|
|
* material. If the layer is created then it will be owned by
|
|
* material. */
|
|
layer = _cogl_material_get_layer (material, layer_index);
|
|
/* FIXME: we shouldn't ever construct a layer in a getter function */
|
|
|
|
return cogl_material_layer_get_wrap_mode_t ((CoglHandle)layer);
|
|
}
|
|
|
|
CoglMaterialWrapMode
|
|
_cogl_material_layer_get_wrap_mode_r (CoglHandle handle)
|
|
{
|
|
CoglMaterialLayer *layer = COGL_MATERIAL_LAYER (handle);
|
|
CoglMaterialLayerState change = COGL_MATERIAL_LAYER_STATE_WRAP_MODES;
|
|
CoglMaterialLayer *authority =
|
|
_cogl_material_layer_get_authority (layer, change);
|
|
|
|
return internal_to_public_wrap_mode (authority->wrap_mode_r);
|
|
}
|
|
|
|
/* TODO: make this public when we expose 3D textures. */
|
|
CoglMaterialWrapMode
|
|
_cogl_material_get_layer_wrap_mode_r (CoglHandle handle, int layer_index)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterialLayer *layer;
|
|
|
|
g_return_val_if_fail (cogl_is_material (handle), FALSE);
|
|
|
|
/* Note: this will ensure that the layer exists, creating one if it
|
|
* doesn't already.
|
|
*
|
|
* Note: If the layer already existed it's possibly owned by another
|
|
* material. If the layer is created then it will be owned by
|
|
* material. */
|
|
layer = _cogl_material_get_layer (material, layer_index);
|
|
|
|
return _cogl_material_layer_get_wrap_mode_r (layer);
|
|
}
|
|
|
|
static void
|
|
_cogl_material_layer_get_wrap_modes (CoglMaterialLayer *layer,
|
|
CoglMaterialWrapModeInternal *wrap_mode_s,
|
|
CoglMaterialWrapModeInternal *wrap_mode_t,
|
|
CoglMaterialWrapModeInternal *wrap_mode_r)
|
|
{
|
|
CoglMaterialLayer *authority =
|
|
_cogl_material_layer_get_authority (layer,
|
|
COGL_MATERIAL_LAYER_STATE_WRAP_MODES);
|
|
|
|
*wrap_mode_s = authority->wrap_mode_s;
|
|
*wrap_mode_t = authority->wrap_mode_t;
|
|
*wrap_mode_r = authority->wrap_mode_r;
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
CoglMaterial *material;
|
|
CoglMaterialWrapModeOverrides *wrap_mode_overrides;
|
|
int i;
|
|
} CoglMaterialWrapModeOverridesState;
|
|
|
|
static gboolean
|
|
apply_wrap_mode_overrides_cb (CoglMaterialLayer *layer,
|
|
void *user_data)
|
|
{
|
|
CoglMaterialWrapModeOverridesState *state = user_data;
|
|
CoglMaterialLayer *authority =
|
|
_cogl_material_layer_get_authority (layer,
|
|
COGL_MATERIAL_LAYER_STATE_WRAP_MODES);
|
|
CoglMaterialWrapModeInternal wrap_mode_s;
|
|
CoglMaterialWrapModeInternal wrap_mode_t;
|
|
CoglMaterialWrapModeInternal wrap_mode_r;
|
|
|
|
g_return_val_if_fail (state->i < 32, FALSE);
|
|
|
|
wrap_mode_s = state->wrap_mode_overrides->values[state->i].s;
|
|
if (wrap_mode_s == COGL_MATERIAL_WRAP_MODE_OVERRIDE_NONE)
|
|
wrap_mode_s = (CoglMaterialWrapModeInternal)authority->wrap_mode_s;
|
|
wrap_mode_t = state->wrap_mode_overrides->values[state->i].t;
|
|
if (wrap_mode_t == COGL_MATERIAL_WRAP_MODE_OVERRIDE_NONE)
|
|
wrap_mode_t = (CoglMaterialWrapModeInternal)authority->wrap_mode_t;
|
|
wrap_mode_r = state->wrap_mode_overrides->values[state->i].r;
|
|
if (wrap_mode_r == COGL_MATERIAL_WRAP_MODE_OVERRIDE_NONE)
|
|
wrap_mode_r = (CoglMaterialWrapModeInternal)authority->wrap_mode_r;
|
|
|
|
_cogl_material_set_layer_wrap_modes (state->material,
|
|
layer,
|
|
authority,
|
|
wrap_mode_s,
|
|
wrap_mode_t,
|
|
wrap_mode_r);
|
|
|
|
state->i++;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
CoglMaterial *material;
|
|
GLuint gl_texture;
|
|
} CoglMaterialOverrideLayerState;
|
|
|
|
static gboolean
|
|
override_layer_texture_cb (CoglMaterialLayer *layer, void *user_data)
|
|
{
|
|
CoglMaterialOverrideLayerState *state = user_data;
|
|
CoglHandle texture;
|
|
GLenum gl_target;
|
|
|
|
texture = _cogl_material_layer_get_texture (layer);
|
|
|
|
if (texture != COGL_INVALID_HANDLE)
|
|
gl_target = cogl_texture_get_gl_texture (texture, NULL, &gl_target);
|
|
else
|
|
gl_target = GL_TEXTURE_2D;
|
|
|
|
_cogl_material_set_layer_gl_texture_slice (state->material,
|
|
layer->index,
|
|
texture,
|
|
state->gl_texture,
|
|
gl_target);
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
_cogl_material_apply_overrides (CoglMaterial *material,
|
|
CoglMaterialFlushOptions *options)
|
|
{
|
|
COGL_STATIC_COUNTER (apply_overrides_counter,
|
|
"material overrides counter",
|
|
"Increments each time we have to apply "
|
|
"override options to a material",
|
|
0 /* no application private data */);
|
|
|
|
COGL_COUNTER_INC (_cogl_uprof_context, apply_overrides_counter);
|
|
|
|
if (options->flags & COGL_MATERIAL_FLUSH_DISABLE_MASK)
|
|
{
|
|
int i;
|
|
|
|
/* NB: we can assume that once we see one bit to disable
|
|
* a layer, all subsequent layers are also disabled. */
|
|
for (i = 0; i < 32 && options->disable_layers & (1<<i); i++)
|
|
;
|
|
|
|
_cogl_material_prune_to_n_layers (material, i);
|
|
}
|
|
|
|
if (options->flags & COGL_MATERIAL_FLUSH_FALLBACK_MASK)
|
|
{
|
|
CoglMaterialFallbackState state;
|
|
|
|
state.i = 0;
|
|
state.material = material;
|
|
state.fallback_layers = options->fallback_layers;
|
|
|
|
_cogl_material_foreach_layer (material,
|
|
fallback_layer_cb,
|
|
&state);
|
|
}
|
|
|
|
if (options->flags & COGL_MATERIAL_FLUSH_LAYER0_OVERRIDE)
|
|
{
|
|
CoglMaterialOverrideLayerState state;
|
|
|
|
_cogl_material_prune_to_n_layers (material, 1);
|
|
|
|
/* NB: we are overriding the first layer, but we don't know
|
|
* the user's given layer_index, which is why we use
|
|
* _cogl_material_foreach_layer() here even though we know
|
|
* there's only one layer. */
|
|
state.material = material;
|
|
state.gl_texture = options->layer0_override_texture;
|
|
_cogl_material_foreach_layer (material,
|
|
override_layer_texture_cb,
|
|
&state);
|
|
}
|
|
|
|
if (options->flags & COGL_MATERIAL_FLUSH_WRAP_MODE_OVERRIDES)
|
|
{
|
|
CoglMaterialWrapModeOverridesState state;
|
|
|
|
state.material = material;
|
|
state.wrap_mode_overrides = &options->wrap_mode_overrides;
|
|
state.i = 0;
|
|
_cogl_material_foreach_layer (material,
|
|
apply_wrap_mode_overrides_cb,
|
|
&state);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
_cogl_material_layer_texture_equal (CoglMaterialLayer *authority0,
|
|
CoglMaterialLayer *authority1)
|
|
{
|
|
if (authority0->texture != authority1->texture)
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
/* Determine the mask of differences between two layers.
|
|
*
|
|
* XXX: If layers and materials could both be cast to a common Tree
|
|
* type of some kind then we could have a unified
|
|
* compare_differences() function.
|
|
*/
|
|
static unsigned long
|
|
_cogl_material_layer_compare_differences (CoglMaterialLayer *layer0,
|
|
CoglMaterialLayer *layer1)
|
|
{
|
|
CoglMaterialLayer *node0;
|
|
CoglMaterialLayer *node1;
|
|
int len0;
|
|
int len1;
|
|
int len0_index;
|
|
int len1_index;
|
|
int count;
|
|
int i;
|
|
CoglMaterialLayer *common_ancestor = NULL;
|
|
unsigned long layers_difference = 0;
|
|
|
|
_COGL_GET_CONTEXT (ctx, 0);
|
|
|
|
/* Algorithm:
|
|
*
|
|
* 1) Walk the ancestors of each layer to the root node, adding a
|
|
* pointer to each ancester node to two GArrays:
|
|
* ctx->material0_nodes, and ctx->material1_nodes.
|
|
*
|
|
* 2) Compare the arrays to find the nodes where they stop to
|
|
* differ.
|
|
*
|
|
* 3) For each array now iterate from index 0 to the first node of
|
|
* difference ORing that nodes ->difference mask into the final
|
|
* material_differences mask.
|
|
*/
|
|
|
|
g_array_set_size (ctx->material0_nodes, 0);
|
|
g_array_set_size (ctx->material1_nodes, 0);
|
|
for (node0 = layer0; node0; node0 = node0->parent)
|
|
g_array_append_vals (ctx->material0_nodes, &node0, 1);
|
|
for (node1 = layer1; node1; node1 = node1->parent)
|
|
g_array_append_vals (ctx->material1_nodes, &node1, 1);
|
|
|
|
len0 = ctx->material0_nodes->len;
|
|
len1 = ctx->material1_nodes->len;
|
|
/* There's no point looking at the last entries since we know both
|
|
* layers must have the same default layer as their root node. */
|
|
len0_index = len0 - 2;
|
|
len1_index = len1 - 2;
|
|
count = MIN (len0, len1) - 1;
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
node0 = g_array_index (ctx->material0_nodes,
|
|
CoglMaterialLayer *, len0_index--);
|
|
node1 = g_array_index (ctx->material1_nodes,
|
|
CoglMaterialLayer *, len1_index--);
|
|
if (node0 != node1)
|
|
{
|
|
common_ancestor = node0->parent;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If we didn't already find the first the common_ancestor ancestor
|
|
* that's because one material is a direct descendant of the other
|
|
* and in this case the first common ancestor is the last node we
|
|
* looked at. */
|
|
if (!common_ancestor)
|
|
common_ancestor = node0;
|
|
|
|
count = len0 - 1;
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
node0 = g_array_index (ctx->material0_nodes, CoglMaterialLayer *, i);
|
|
if (node0 == common_ancestor)
|
|
break;
|
|
layers_difference |= node0->differences;
|
|
}
|
|
|
|
count = len1 - 1;
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
node1 = g_array_index (ctx->material1_nodes, CoglMaterialLayer *, i);
|
|
if (node1 == common_ancestor)
|
|
break;
|
|
layers_difference |= node1->differences;
|
|
}
|
|
|
|
return layers_difference;
|
|
}
|
|
|
|
static gboolean
|
|
_cogl_material_layer_combine_state_equal (CoglMaterialLayer *authority0,
|
|
CoglMaterialLayer *authority1)
|
|
{
|
|
CoglMaterialLayerBigState *big_state0 = authority0->big_state;
|
|
CoglMaterialLayerBigState *big_state1 = authority1->big_state;
|
|
int n_args;
|
|
int i;
|
|
|
|
if (big_state0->texture_combine_rgb_func !=
|
|
big_state1->texture_combine_rgb_func)
|
|
return FALSE;
|
|
|
|
if (big_state0->texture_combine_alpha_func !=
|
|
big_state1->texture_combine_alpha_func)
|
|
return FALSE;
|
|
|
|
n_args =
|
|
get_n_args_for_combine_func (big_state0->texture_combine_rgb_func);
|
|
for (i = 0; i < n_args; i++)
|
|
{
|
|
if ((big_state0->texture_combine_rgb_src[i] !=
|
|
big_state1->texture_combine_rgb_src[i]) ||
|
|
(big_state0->texture_combine_rgb_op[i] !=
|
|
big_state1->texture_combine_rgb_op[i]))
|
|
return FALSE;
|
|
}
|
|
|
|
n_args =
|
|
get_n_args_for_combine_func (big_state0->texture_combine_alpha_func);
|
|
for (i = 0; i < n_args; i++)
|
|
{
|
|
if ((big_state0->texture_combine_alpha_src[i] !=
|
|
big_state1->texture_combine_alpha_src[i]) ||
|
|
(big_state0->texture_combine_alpha_op[i] !=
|
|
big_state1->texture_combine_alpha_op[i]))
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
_cogl_material_layer_combine_constant_equal (CoglMaterialLayer *authority0,
|
|
CoglMaterialLayer *authority1)
|
|
{
|
|
return memcmp (authority0->big_state->texture_combine_constant,
|
|
authority1->big_state->texture_combine_constant,
|
|
sizeof (float) * 4) == 0 ? TRUE : FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
_cogl_material_layer_filters_equal (CoglMaterialLayer *authority0,
|
|
CoglMaterialLayer *authority1)
|
|
{
|
|
if (authority0->mag_filter != authority1->mag_filter)
|
|
return FALSE;
|
|
if (authority0->min_filter != authority1->min_filter)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
_cogl_material_layer_wrap_modes_equal (CoglMaterialLayer *authority0,
|
|
CoglMaterialLayer *authority1)
|
|
{
|
|
if (authority0->wrap_mode_s != authority1->wrap_mode_s ||
|
|
authority0->wrap_mode_t != authority1->wrap_mode_t ||
|
|
authority0->wrap_mode_r != authority1->wrap_mode_r)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
_cogl_material_layer_user_matrix_equal (CoglMaterialLayer *authority0,
|
|
CoglMaterialLayer *authority1)
|
|
{
|
|
CoglMaterialLayerBigState *big_state0 = authority0->big_state;
|
|
CoglMaterialLayerBigState *big_state1 = authority1->big_state;
|
|
|
|
if (!cogl_matrix_equal (&big_state0->matrix, &big_state1->matrix))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
typedef gboolean
|
|
(*CoglMaterialLayerStateComparitor) (CoglMaterialLayer *authority0,
|
|
CoglMaterialLayer *authority1);
|
|
|
|
static gboolean
|
|
layer_state_equal (CoglMaterialLayerState state,
|
|
CoglMaterialLayer *layer0,
|
|
CoglMaterialLayer *layer1,
|
|
CoglMaterialLayerStateComparitor comparitor)
|
|
{
|
|
CoglMaterialLayer *authority0 =
|
|
_cogl_material_layer_get_authority (layer0, state);
|
|
CoglMaterialLayer *authority1 =
|
|
_cogl_material_layer_get_authority (layer1, state);
|
|
|
|
return comparitor (authority0, authority1);
|
|
}
|
|
|
|
static gboolean
|
|
_cogl_material_layer_equal (CoglMaterialLayer *layer0,
|
|
CoglMaterialLayer *layer1)
|
|
{
|
|
unsigned long layers_difference;
|
|
|
|
if (layer0 == layer1)
|
|
return TRUE;
|
|
|
|
layers_difference =
|
|
_cogl_material_layer_compare_differences (layer0, layer1);
|
|
|
|
if (layers_difference & COGL_MATERIAL_LAYER_STATE_TEXTURE &&
|
|
!layer_state_equal (COGL_MATERIAL_LAYER_STATE_TEXTURE,
|
|
layer0, layer1,
|
|
_cogl_material_layer_texture_equal))
|
|
return FALSE;
|
|
|
|
if (layers_difference & COGL_MATERIAL_LAYER_STATE_COMBINE &&
|
|
!layer_state_equal (COGL_MATERIAL_LAYER_STATE_COMBINE,
|
|
layer0, layer1,
|
|
_cogl_material_layer_combine_state_equal))
|
|
return FALSE;
|
|
|
|
if (layers_difference & COGL_MATERIAL_LAYER_STATE_COMBINE_CONSTANT &&
|
|
!layer_state_equal (COGL_MATERIAL_LAYER_STATE_COMBINE_CONSTANT,
|
|
layer0, layer1,
|
|
_cogl_material_layer_combine_constant_equal))
|
|
return FALSE;
|
|
|
|
if (layers_difference & COGL_MATERIAL_LAYER_STATE_FILTERS &&
|
|
!layer_state_equal (COGL_MATERIAL_LAYER_STATE_FILTERS,
|
|
layer0, layer1,
|
|
_cogl_material_layer_filters_equal))
|
|
return FALSE;
|
|
|
|
if (layers_difference & COGL_MATERIAL_LAYER_STATE_WRAP_MODES &&
|
|
!layer_state_equal (COGL_MATERIAL_LAYER_STATE_WRAP_MODES,
|
|
layer0, layer1,
|
|
_cogl_material_layer_wrap_modes_equal))
|
|
return FALSE;
|
|
|
|
if (layers_difference & COGL_MATERIAL_LAYER_STATE_USER_MATRIX &&
|
|
!layer_state_equal (COGL_MATERIAL_LAYER_STATE_USER_MATRIX,
|
|
layer0, layer1,
|
|
_cogl_material_layer_user_matrix_equal))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
_cogl_material_color_equal (CoglMaterial *authority0,
|
|
CoglMaterial *authority1)
|
|
{
|
|
return cogl_color_equal (&authority0->color, &authority1->color);
|
|
}
|
|
|
|
static gboolean
|
|
_cogl_material_lighting_state_equal (CoglMaterial *authority0,
|
|
CoglMaterial *authority1)
|
|
{
|
|
CoglMaterialLightingState *state0 = &authority0->big_state->lighting_state;
|
|
CoglMaterialLightingState *state1 = &authority1->big_state->lighting_state;
|
|
|
|
if (memcmp (state0->ambient, state1->ambient, sizeof (float) * 4) != 0)
|
|
return FALSE;
|
|
if (memcmp (state0->diffuse, state1->diffuse, sizeof (float) * 4) != 0)
|
|
return FALSE;
|
|
if (memcmp (state0->specular, state1->specular, sizeof (float) * 4) != 0)
|
|
return FALSE;
|
|
if (memcmp (state0->emission, state1->emission, sizeof (float) * 4) != 0)
|
|
return FALSE;
|
|
if (state0->shininess != state1->shininess)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
_cogl_material_alpha_state_equal (CoglMaterial *authority0,
|
|
CoglMaterial *authority1)
|
|
{
|
|
CoglMaterialAlphaFuncState *alpha_state0 =
|
|
&authority0->big_state->alpha_state;
|
|
CoglMaterialAlphaFuncState *alpha_state1 =
|
|
&authority1->big_state->alpha_state;
|
|
|
|
if (alpha_state0->alpha_func != alpha_state1->alpha_func ||
|
|
alpha_state0->alpha_func_reference != alpha_state1->alpha_func_reference)
|
|
return FALSE;
|
|
else
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
_cogl_material_blend_state_equal (CoglMaterial *authority0,
|
|
CoglMaterial *authority1)
|
|
{
|
|
CoglMaterialBlendState *blend_state0 = &authority0->big_state->blend_state;
|
|
CoglMaterialBlendState *blend_state1 = &authority1->big_state->blend_state;
|
|
|
|
#ifndef HAVE_COGL_GLES
|
|
if (blend_state0->blend_equation_rgb != blend_state1->blend_equation_rgb)
|
|
return FALSE;
|
|
if (blend_state0->blend_equation_alpha !=
|
|
blend_state1->blend_equation_alpha)
|
|
return FALSE;
|
|
if (blend_state0->blend_src_factor_alpha !=
|
|
blend_state1->blend_src_factor_alpha)
|
|
return FALSE;
|
|
if (blend_state0->blend_dst_factor_alpha !=
|
|
blend_state1->blend_dst_factor_alpha)
|
|
return FALSE;
|
|
#endif
|
|
if (blend_state0->blend_src_factor_rgb !=
|
|
blend_state1->blend_src_factor_rgb)
|
|
return FALSE;
|
|
if (blend_state0->blend_dst_factor_rgb !=
|
|
blend_state1->blend_dst_factor_rgb)
|
|
return FALSE;
|
|
#ifndef HAVE_COGL_GLES
|
|
if (!cogl_color_equal (&blend_state0->blend_constant,
|
|
&blend_state1->blend_constant))
|
|
return FALSE;
|
|
#endif
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
_cogl_material_depth_state_equal (CoglMaterial *authority0,
|
|
CoglMaterial *authority1)
|
|
{
|
|
if (authority0->big_state->depth_state.depth_test_enabled == FALSE &&
|
|
authority1->big_state->depth_state.depth_test_enabled == FALSE)
|
|
return TRUE;
|
|
else
|
|
return memcmp (&authority0->big_state->depth_state,
|
|
&authority1->big_state->depth_state,
|
|
sizeof (CoglMaterialDepthState)) == 0;
|
|
}
|
|
|
|
static gboolean
|
|
_cogl_material_layers_equal (CoglMaterial *authority0,
|
|
CoglMaterial *authority1)
|
|
{
|
|
int i;
|
|
|
|
if (authority0->n_layers != authority1->n_layers)
|
|
return FALSE;
|
|
|
|
_cogl_material_update_layers_cache (authority0);
|
|
_cogl_material_update_layers_cache (authority1);
|
|
|
|
for (i = 0; i < authority0->n_layers; i++)
|
|
{
|
|
if (!_cogl_material_layer_equal (authority0->layers_cache[i],
|
|
authority1->layers_cache[i]))
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/* Determine the mask of differences between two materials */
|
|
static unsigned long
|
|
_cogl_material_compare_differences (CoglMaterial *material0,
|
|
CoglMaterial *material1)
|
|
{
|
|
CoglMaterial *node0;
|
|
CoglMaterial *node1;
|
|
int len0;
|
|
int len1;
|
|
int len0_index;
|
|
int len1_index;
|
|
int count;
|
|
int i;
|
|
CoglMaterial *common_ancestor = NULL;
|
|
unsigned long materials_difference = 0;
|
|
|
|
_COGL_GET_CONTEXT (ctx, 0);
|
|
|
|
/* Algorithm:
|
|
*
|
|
* 1) Walk the ancestors of each layer to the root node, adding a
|
|
* pointer to each ancester node to two GArrays:
|
|
* ctx->material0_nodes, and ctx->material1_nodes.
|
|
*
|
|
* 2) Compare the arrays to find the nodes where they stop to
|
|
* differ.
|
|
*
|
|
* 3) For each array now iterate from index 0 to the first node of
|
|
* difference ORing that nodes ->difference mask into the final
|
|
* material_differences mask.
|
|
*/
|
|
|
|
g_array_set_size (ctx->material0_nodes, 0);
|
|
g_array_set_size (ctx->material1_nodes, 0);
|
|
for (node0 = material0; node0; node0 = node0->parent)
|
|
g_array_append_vals (ctx->material0_nodes, &node0, 1);
|
|
for (node1 = material1; node1; node1 = node1->parent)
|
|
g_array_append_vals (ctx->material1_nodes, &node1, 1);
|
|
|
|
len0 = ctx->material0_nodes->len;
|
|
len1 = ctx->material1_nodes->len;
|
|
/* There's no point looking at the last entries since we know both
|
|
* layers must have the same default layer as their root node. */
|
|
len0_index = len0 - 2;
|
|
len1_index = len1 - 2;
|
|
count = MIN (len0, len1) - 1;
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
node0 = g_array_index (ctx->material0_nodes,
|
|
CoglMaterial *, len0_index--);
|
|
node1 = g_array_index (ctx->material1_nodes,
|
|
CoglMaterial *, len1_index--);
|
|
if (node0 != node1)
|
|
{
|
|
common_ancestor = node0->parent;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If we didn't already find the first the common_ancestor ancestor
|
|
* that's because one material is a direct descendant of the other
|
|
* and in this case the first common ancestor is the last node we
|
|
* looked at. */
|
|
if (!common_ancestor)
|
|
common_ancestor = node0;
|
|
|
|
count = len0 - 1;
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
node0 = g_array_index (ctx->material0_nodes, CoglMaterial *, i);
|
|
if (node0 == common_ancestor)
|
|
break;
|
|
materials_difference |= node0->differences;
|
|
}
|
|
|
|
count = len1 - 1;
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
node1 = g_array_index (ctx->material1_nodes, CoglMaterial *, i);
|
|
if (node1 == common_ancestor)
|
|
break;
|
|
materials_difference |= node1->differences;
|
|
}
|
|
|
|
return materials_difference;
|
|
|
|
}
|
|
|
|
static gboolean
|
|
simple_property_equal (CoglMaterial *material0,
|
|
CoglMaterial *material1,
|
|
unsigned long materials_difference,
|
|
CoglMaterialState state,
|
|
CoglMaterialStateComparitor comparitor)
|
|
{
|
|
if (materials_difference & state)
|
|
{
|
|
if (!comparitor (_cogl_material_get_authority (material0, state),
|
|
_cogl_material_get_authority (material1, state)))
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/* Comparison of two arbitrary materials is done by:
|
|
* 1) walking up the parents of each material until a common
|
|
* ancestor is found, and at each step ORing together the
|
|
* difference masks.
|
|
*
|
|
* 2) using the final difference mask to determine which state
|
|
* groups to compare.
|
|
*
|
|
* 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 handle0,
|
|
CoglHandle handle1,
|
|
gboolean skip_gl_color)
|
|
{
|
|
CoglMaterial *material0 = COGL_MATERIAL (handle0);
|
|
CoglMaterial *material1 = COGL_MATERIAL (handle1);
|
|
unsigned long materials_difference;
|
|
|
|
if (material0 == material1)
|
|
return TRUE;
|
|
|
|
/* First check non-sparse properties */
|
|
|
|
if (material0->real_blend_enable != material1->real_blend_enable)
|
|
return FALSE;
|
|
|
|
/* Then check sparse properties */
|
|
|
|
materials_difference =
|
|
_cogl_material_compare_differences (material0, material1);
|
|
|
|
if (materials_difference & COGL_MATERIAL_STATE_COLOR &&
|
|
!skip_gl_color)
|
|
{
|
|
CoglMaterialState state = COGL_MATERIAL_STATE_COLOR;
|
|
CoglMaterial *authority0 =
|
|
_cogl_material_get_authority (material0, state);
|
|
CoglMaterial *authority1 =
|
|
_cogl_material_get_authority (material1, state);
|
|
|
|
if (!cogl_color_equal (&authority0->color, &authority1->color))
|
|
return FALSE;
|
|
}
|
|
|
|
if (!simple_property_equal (material0, material1,
|
|
materials_difference,
|
|
COGL_MATERIAL_STATE_LIGHTING,
|
|
_cogl_material_lighting_state_equal))
|
|
return FALSE;
|
|
|
|
if (!simple_property_equal (material0, material1,
|
|
materials_difference,
|
|
COGL_MATERIAL_STATE_ALPHA_FUNC,
|
|
_cogl_material_alpha_state_equal))
|
|
return FALSE;
|
|
|
|
/* We don't need to compare the detailed blending state if we know
|
|
* blending is disabled for both materials. */
|
|
if (material0->real_blend_enable &&
|
|
materials_difference & COGL_MATERIAL_STATE_BLEND)
|
|
{
|
|
CoglMaterialState state = COGL_MATERIAL_STATE_BLEND;
|
|
CoglMaterial *authority0 =
|
|
_cogl_material_get_authority (material0, state);
|
|
CoglMaterial *authority1 =
|
|
_cogl_material_get_authority (material1, state);
|
|
|
|
if (!_cogl_material_blend_state_equal (authority0, authority1))
|
|
return FALSE;
|
|
}
|
|
|
|
/* XXX: we don't need to compare the BLEND_ENABLE state because it's
|
|
* already reflected in ->real_blend_enable */
|
|
#if 0
|
|
if (!simple_property_equal (material0, material1,
|
|
materials_difference,
|
|
COGL_MATERIAL_STATE_BLEND,
|
|
_cogl_material_blend_enable_equal))
|
|
return FALSE;
|
|
#endif
|
|
|
|
if (!simple_property_equal (material0, material1,
|
|
materials_difference,
|
|
COGL_MATERIAL_STATE_DEPTH,
|
|
_cogl_material_depth_state_equal))
|
|
return FALSE;
|
|
|
|
if (!simple_property_equal (material0, material1,
|
|
materials_difference,
|
|
COGL_MATERIAL_STATE_LAYERS,
|
|
_cogl_material_layers_equal))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
cogl_material_get_color (CoglHandle handle,
|
|
CoglColor *color)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterial *authority;
|
|
|
|
g_return_if_fail (cogl_is_material (handle));
|
|
|
|
authority =
|
|
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_COLOR);
|
|
|
|
*color = authority->color;
|
|
}
|
|
|
|
/* This is used heavily by the cogl journal when logging quads */
|
|
void
|
|
_cogl_material_get_colorubv (CoglHandle handle,
|
|
guint8 *color)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterial *authority =
|
|
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_COLOR);
|
|
|
|
_cogl_color_get_rgba_4ubv (&authority->color, color);
|
|
}
|
|
|
|
static void
|
|
_cogl_material_prune_redundant_ancestry (CoglMaterial *material)
|
|
{
|
|
CoglMaterial *new_parent = material->parent;
|
|
|
|
/* walk up past ancestors that are now redundant and potentially
|
|
* reparent the material. */
|
|
while (new_parent->parent &&
|
|
(new_parent->differences | material->differences) ==
|
|
material->differences)
|
|
new_parent = new_parent->parent;
|
|
|
|
if (new_parent != material->parent)
|
|
{
|
|
CoglMaterial *old_parent = material->parent;
|
|
material->parent = cogl_handle_ref (new_parent);
|
|
/* Note: the old parent may indirectly be keeping
|
|
the new parent alive so we have to ref the new
|
|
parent before unrefing the old */
|
|
cogl_handle_unref (old_parent);
|
|
}
|
|
}
|
|
|
|
static void
|
|
_cogl_material_update_authority (CoglMaterial *material,
|
|
CoglMaterial *authority,
|
|
CoglMaterialState state,
|
|
CoglMaterialStateComparitor comparitor)
|
|
{
|
|
/* If we are the current authority see if we can revert to one of
|
|
* our ancestors being the authority */
|
|
if (material == authority && authority->parent != NULL)
|
|
{
|
|
CoglMaterial *old_authority =
|
|
_cogl_material_get_authority (authority->parent, state);
|
|
|
|
if (comparitor (authority, old_authority))
|
|
material->differences &= ~state;
|
|
}
|
|
else if (material != authority)
|
|
{
|
|
/* If we weren't previously the authority on this state then we
|
|
* need to extended our differences mask and so it's possible
|
|
* that some of our ancestry will now become redundant, so we
|
|
* aim to reparent ourselves if that's true... */
|
|
material->differences |= state;
|
|
_cogl_material_prune_redundant_ancestry (material);
|
|
}
|
|
}
|
|
|
|
void
|
|
cogl_material_set_color (CoglHandle handle,
|
|
const CoglColor *color)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterialState state = COGL_MATERIAL_STATE_COLOR;
|
|
CoglMaterial *authority;
|
|
|
|
g_return_if_fail (cogl_is_material (handle));
|
|
|
|
authority = _cogl_material_get_authority (material, state);
|
|
|
|
if (cogl_color_equal (color, &authority->color))
|
|
return;
|
|
|
|
/* - Flush journal primitives referencing the current state.
|
|
* - Make sure the material has no dependants so it may be modified.
|
|
* - If the material isn't currently an authority for the state being
|
|
* changed, then initialize that state from the current authority.
|
|
*/
|
|
_cogl_material_pre_change_notify (material, state, color);
|
|
|
|
material->color = *color;
|
|
|
|
_cogl_material_update_authority (material, authority, state,
|
|
_cogl_material_color_equal);
|
|
|
|
handle_automatic_blend_enable (material, state);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
CoglMaterialBlendEnable
|
|
_cogl_material_get_blend_enabled (CoglHandle handle)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);;
|
|
CoglMaterial *authority;
|
|
|
|
g_return_val_if_fail (cogl_is_material (handle), FALSE);
|
|
|
|
authority =
|
|
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_BLEND_ENABLE);
|
|
return authority->blend_enable;
|
|
}
|
|
|
|
static gboolean
|
|
_cogl_material_blend_enable_equal (CoglMaterial *authority0,
|
|
CoglMaterial *authority1)
|
|
{
|
|
return authority0->blend_enable == authority1->blend_enable ? TRUE : FALSE;
|
|
}
|
|
|
|
void
|
|
_cogl_material_set_blend_enabled (CoglHandle handle,
|
|
CoglMaterialBlendEnable enable)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterialState state = COGL_MATERIAL_STATE_BLEND_ENABLE;
|
|
CoglMaterial *authority;
|
|
|
|
g_return_if_fail (cogl_is_material (handle));
|
|
g_return_if_fail (enable > 1 &&
|
|
"don't pass TRUE or FALSE to _set_blend_enabled!");
|
|
|
|
authority = _cogl_material_get_authority (material, state);
|
|
|
|
if (authority->blend_enable == enable)
|
|
return;
|
|
|
|
/* - Flush journal primitives referencing the current state.
|
|
* - Make sure the material has no dependants so it may be modified.
|
|
* - If the material isn't currently an authority for the state being
|
|
* changed, then initialize that state from the current authority.
|
|
*/
|
|
_cogl_material_pre_change_notify (material, state, NULL);
|
|
|
|
material->blend_enable = enable;
|
|
|
|
_cogl_material_update_authority (material, authority, state,
|
|
_cogl_material_blend_enable_equal);
|
|
|
|
handle_automatic_blend_enable (material, state);
|
|
}
|
|
|
|
void
|
|
cogl_material_get_ambient (CoglHandle handle,
|
|
CoglColor *ambient)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterial *authority;
|
|
|
|
g_return_if_fail (cogl_is_material (handle));
|
|
|
|
authority =
|
|
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_LIGHTING);
|
|
|
|
cogl_color_init_from_4fv (ambient,
|
|
authority->big_state->lighting_state.ambient);
|
|
}
|
|
|
|
void
|
|
cogl_material_set_ambient (CoglHandle handle,
|
|
const CoglColor *ambient)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterialState state = COGL_MATERIAL_STATE_LIGHTING;
|
|
CoglMaterial *authority;
|
|
CoglMaterialLightingState *lighting_state;
|
|
|
|
g_return_if_fail (cogl_is_material (handle));
|
|
|
|
authority = _cogl_material_get_authority (material, state);
|
|
|
|
lighting_state = &authority->big_state->lighting_state;
|
|
if (cogl_color_equal (ambient, &lighting_state->ambient))
|
|
return;
|
|
|
|
/* - Flush journal primitives referencing the current state.
|
|
* - Make sure the material has no dependants so it may be modified.
|
|
* - If the material isn't currently an authority for the state being
|
|
* changed, then initialize that state from the current authority.
|
|
*/
|
|
_cogl_material_pre_change_notify (material, state, NULL);
|
|
|
|
lighting_state = &material->big_state->lighting_state;
|
|
lighting_state->ambient[0] = cogl_color_get_red_float (ambient);
|
|
lighting_state->ambient[1] = cogl_color_get_green_float (ambient);
|
|
lighting_state->ambient[2] = cogl_color_get_blue_float (ambient);
|
|
lighting_state->ambient[3] = cogl_color_get_alpha_float (ambient);
|
|
|
|
_cogl_material_update_authority (material, authority, state,
|
|
_cogl_material_lighting_state_equal);
|
|
|
|
handle_automatic_blend_enable (material, state);
|
|
}
|
|
|
|
void
|
|
cogl_material_get_diffuse (CoglHandle handle,
|
|
CoglColor *diffuse)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterial *authority;
|
|
|
|
g_return_if_fail (cogl_is_material (handle));
|
|
|
|
authority =
|
|
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_LIGHTING);
|
|
|
|
cogl_color_init_from_4fv (diffuse,
|
|
authority->big_state->lighting_state.diffuse);
|
|
}
|
|
|
|
void
|
|
cogl_material_set_diffuse (CoglHandle handle,
|
|
const CoglColor *diffuse)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterialState state = COGL_MATERIAL_STATE_LIGHTING;
|
|
CoglMaterial *authority;
|
|
CoglMaterialLightingState *lighting_state;
|
|
|
|
g_return_if_fail (cogl_is_material (handle));
|
|
|
|
authority = _cogl_material_get_authority (material, state);
|
|
|
|
lighting_state = &authority->big_state->lighting_state;
|
|
if (cogl_color_equal (diffuse, &lighting_state->diffuse))
|
|
return;
|
|
|
|
/* - Flush journal primitives referencing the current state.
|
|
* - Make sure the material has no dependants so it may be modified.
|
|
* - If the material isn't currently an authority for the state being
|
|
* changed, then initialize that state from the current authority.
|
|
*/
|
|
_cogl_material_pre_change_notify (material, state, NULL);
|
|
|
|
lighting_state = &material->big_state->lighting_state;
|
|
lighting_state->diffuse[0] = cogl_color_get_red_float (diffuse);
|
|
lighting_state->diffuse[1] = cogl_color_get_green_float (diffuse);
|
|
lighting_state->diffuse[2] = cogl_color_get_blue_float (diffuse);
|
|
lighting_state->diffuse[3] = cogl_color_get_alpha_float (diffuse);
|
|
|
|
|
|
_cogl_material_update_authority (material, authority, state,
|
|
_cogl_material_lighting_state_equal);
|
|
|
|
handle_automatic_blend_enable (material, state);
|
|
}
|
|
|
|
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 *authority = COGL_MATERIAL (handle);
|
|
|
|
g_return_if_fail (cogl_is_material (handle));
|
|
|
|
while (!(authority->differences & COGL_MATERIAL_STATE_LIGHTING))
|
|
authority = authority->parent;
|
|
|
|
cogl_color_init_from_4fv (specular,
|
|
authority->big_state->lighting_state.specular);
|
|
}
|
|
|
|
void
|
|
cogl_material_set_specular (CoglHandle handle, const CoglColor *specular)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterial *authority;
|
|
CoglMaterialState state = COGL_MATERIAL_STATE_LIGHTING;
|
|
CoglMaterialLightingState *lighting_state;
|
|
|
|
g_return_if_fail (cogl_is_material (handle));
|
|
|
|
authority = _cogl_material_get_authority (material, state);
|
|
|
|
lighting_state = &authority->big_state->lighting_state;
|
|
if (cogl_color_equal (specular, &lighting_state->specular))
|
|
return;
|
|
|
|
/* - Flush journal primitives referencing the current state.
|
|
* - Make sure the material has no dependants so it may be modified.
|
|
* - If the material isn't currently an authority for the state being
|
|
* changed, then initialize that state from the current authority.
|
|
*/
|
|
_cogl_material_pre_change_notify (material, state, NULL);
|
|
|
|
lighting_state = &material->big_state->lighting_state;
|
|
lighting_state->specular[0] = cogl_color_get_red_float (specular);
|
|
lighting_state->specular[1] = cogl_color_get_green_float (specular);
|
|
lighting_state->specular[2] = cogl_color_get_blue_float (specular);
|
|
lighting_state->specular[3] = cogl_color_get_alpha_float (specular);
|
|
|
|
_cogl_material_update_authority (material, authority, state,
|
|
_cogl_material_lighting_state_equal);
|
|
|
|
handle_automatic_blend_enable (material, state);
|
|
}
|
|
|
|
float
|
|
cogl_material_get_shininess (CoglHandle handle)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterial *authority;
|
|
|
|
g_return_val_if_fail (cogl_is_material (handle), 0);
|
|
|
|
authority =
|
|
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_LIGHTING);
|
|
|
|
return authority->big_state->lighting_state.shininess;
|
|
}
|
|
|
|
void
|
|
cogl_material_set_shininess (CoglHandle handle,
|
|
float shininess)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterial *authority;
|
|
CoglMaterialState state = COGL_MATERIAL_STATE_LIGHTING;
|
|
CoglMaterialLightingState *lighting_state;
|
|
|
|
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);
|
|
return;
|
|
}
|
|
|
|
authority = _cogl_material_get_authority (material, state);
|
|
|
|
lighting_state = &authority->big_state->lighting_state;
|
|
|
|
if (lighting_state->shininess == shininess)
|
|
return;
|
|
|
|
/* - Flush journal primitives referencing the current state.
|
|
* - Make sure the material has no dependants so it may be modified.
|
|
* - If the material isn't currently an authority for the state being
|
|
* changed, then initialize that state from the current authority.
|
|
*/
|
|
_cogl_material_pre_change_notify (material, state, NULL);
|
|
|
|
lighting_state = &material->big_state->lighting_state;
|
|
lighting_state->shininess = shininess;
|
|
|
|
_cogl_material_update_authority (material, authority, state,
|
|
_cogl_material_lighting_state_equal);
|
|
}
|
|
|
|
void
|
|
cogl_material_get_emission (CoglHandle handle,
|
|
CoglColor *emission)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterial *authority;
|
|
|
|
g_return_if_fail (cogl_is_material (handle));
|
|
|
|
authority =
|
|
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_LIGHTING);
|
|
|
|
cogl_color_init_from_4fv (emission,
|
|
authority->big_state->lighting_state.emission);
|
|
}
|
|
|
|
void
|
|
cogl_material_set_emission (CoglHandle handle, const CoglColor *emission)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterial *authority;
|
|
CoglMaterialState state = COGL_MATERIAL_STATE_LIGHTING;
|
|
CoglMaterialLightingState *lighting_state;
|
|
|
|
g_return_if_fail (cogl_is_material (handle));
|
|
|
|
authority = _cogl_material_get_authority (material, state);
|
|
|
|
lighting_state = &authority->big_state->lighting_state;
|
|
if (cogl_color_equal (emission, &lighting_state->emission))
|
|
return;
|
|
|
|
/* - Flush journal primitives referencing the current state.
|
|
* - Make sure the material has no dependants so it may be modified.
|
|
* - If the material isn't currently an authority for the state being
|
|
* changed, then initialize that state from the current authority.
|
|
*/
|
|
_cogl_material_pre_change_notify (material, state, NULL);
|
|
|
|
lighting_state = &material->big_state->lighting_state;
|
|
lighting_state->emission[0] = cogl_color_get_red_float (emission);
|
|
lighting_state->emission[1] = cogl_color_get_green_float (emission);
|
|
lighting_state->emission[2] = cogl_color_get_blue_float (emission);
|
|
lighting_state->emission[3] = cogl_color_get_alpha_float (emission);
|
|
|
|
_cogl_material_update_authority (material, authority, state,
|
|
_cogl_material_lighting_state_equal);
|
|
|
|
handle_automatic_blend_enable (material, state);
|
|
}
|
|
|
|
void
|
|
cogl_material_set_alpha_test_function (CoglHandle handle,
|
|
CoglMaterialAlphaFunc alpha_func,
|
|
float alpha_reference)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterialState state = COGL_MATERIAL_STATE_ALPHA_FUNC;
|
|
CoglMaterial *authority;
|
|
CoglMaterialAlphaFuncState *alpha_state;
|
|
|
|
g_return_if_fail (cogl_is_material (handle));
|
|
|
|
authority = _cogl_material_get_authority (material, state);
|
|
|
|
alpha_state = &authority->big_state->alpha_state;
|
|
if (alpha_state->alpha_func == alpha_func &&
|
|
alpha_state->alpha_func_reference == alpha_reference)
|
|
return;
|
|
|
|
/* - Flush journal primitives referencing the current state.
|
|
* - Make sure the material has no dependants so it may be modified.
|
|
* - If the material isn't currently an authority for the state being
|
|
* changed, then initialize that state from the current authority.
|
|
*/
|
|
_cogl_material_pre_change_notify (material, state, NULL);
|
|
|
|
alpha_state = &material->big_state->alpha_state;
|
|
alpha_state->alpha_func = alpha_func;
|
|
alpha_state->alpha_func_reference = alpha_reference;
|
|
|
|
_cogl_material_update_authority (material, authority, state,
|
|
_cogl_material_alpha_state_equal);
|
|
}
|
|
|
|
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_ALPHA)
|
|
{
|
|
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_ALPHA)
|
|
{
|
|
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_ALPHA)
|
|
{
|
|
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 = COGL_MATERIAL (handle);
|
|
CoglMaterialState state = COGL_MATERIAL_STATE_BLEND;
|
|
CoglMaterial *authority;
|
|
CoglBlendStringStatement statements[2];
|
|
CoglBlendStringStatement *rgb;
|
|
CoglBlendStringStatement *a;
|
|
GError *internal_error = NULL;
|
|
int count;
|
|
CoglMaterialBlendState *blend_state;
|
|
|
|
g_return_val_if_fail (cogl_is_material (handle), FALSE);
|
|
|
|
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 (count == 1)
|
|
rgb = a = statements;
|
|
else
|
|
{
|
|
rgb = &statements[0];
|
|
a = &statements[1];
|
|
}
|
|
|
|
authority =
|
|
_cogl_material_get_authority (material, state);
|
|
|
|
/* - Flush journal primitives referencing the current state.
|
|
* - Make sure the material has no dependants so it may be modified.
|
|
* - If the material isn't currently an authority for the state being
|
|
* changed, then initialize that state from the current authority.
|
|
*/
|
|
_cogl_material_pre_change_notify (material, state, NULL);
|
|
|
|
blend_state = &material->big_state->blend_state;
|
|
#ifndef HAVE_COGL_GLES
|
|
setup_blend_state (rgb,
|
|
&blend_state->blend_equation_rgb,
|
|
&blend_state->blend_src_factor_rgb,
|
|
&blend_state->blend_dst_factor_rgb);
|
|
setup_blend_state (a,
|
|
&blend_state->blend_equation_alpha,
|
|
&blend_state->blend_src_factor_alpha,
|
|
&blend_state->blend_dst_factor_alpha);
|
|
#else
|
|
setup_blend_state (rgb,
|
|
NULL,
|
|
&blend_state->blend_src_factor_rgb,
|
|
&blend_state->blend_dst_factor_rgb);
|
|
#endif
|
|
|
|
/* If we are the current authority see if we can revert to one of our
|
|
* ancestors being the authority */
|
|
if (material == authority && authority->parent != NULL)
|
|
{
|
|
CoglMaterial *old_authority =
|
|
_cogl_material_get_authority (authority->parent, state);
|
|
|
|
if (_cogl_material_blend_state_equal (authority, old_authority))
|
|
material->differences &= ~state;
|
|
}
|
|
|
|
/* If we weren't previously the authority on this state then we need
|
|
* to extended our differences mask and so it's possible that some
|
|
* of our ancestry will now become redundant, so we aim to reparent
|
|
* ourselves if that's true... */
|
|
if (material != authority)
|
|
{
|
|
material->differences |= state;
|
|
_cogl_material_prune_redundant_ancestry (material);
|
|
}
|
|
|
|
handle_automatic_blend_enable (material, state);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
cogl_material_set_blend_constant (CoglHandle handle,
|
|
CoglColor *constant_color)
|
|
{
|
|
#ifndef HAVE_COGL_GLES
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterialState state = COGL_MATERIAL_STATE_BLEND;
|
|
CoglMaterial *authority;
|
|
CoglMaterialBlendState *blend_state;
|
|
|
|
g_return_if_fail (cogl_is_material (handle));
|
|
|
|
authority = _cogl_material_get_authority (material, state);
|
|
|
|
blend_state = &authority->big_state->blend_state;
|
|
if (cogl_color_equal (constant_color, &blend_state->blend_constant))
|
|
return;
|
|
|
|
/* - Flush journal primitives referencing the current state.
|
|
* - Make sure the material has no dependants so it may be modified.
|
|
* - If the material isn't currently an authority for the state being
|
|
* changed, then initialize that state from the current authority.
|
|
*/
|
|
_cogl_material_pre_change_notify (material, state, NULL);
|
|
|
|
blend_state = &material->big_state->blend_state;
|
|
blend_state->blend_constant = *constant_color;
|
|
|
|
_cogl_material_update_authority (material, authority, state,
|
|
_cogl_material_blend_state_equal);
|
|
|
|
handle_automatic_blend_enable (material, state);
|
|
#endif
|
|
}
|
|
|
|
/* XXX: for now we don't mind if the program has vertex shaders
|
|
* attached but if we ever make a similar API public we should only
|
|
* allow attaching of programs containing fragment shaders. Eventually
|
|
* we will have a CoglPipeline abstraction to also cover vertex
|
|
* processing.
|
|
*/
|
|
void
|
|
_cogl_material_set_user_program (CoglHandle handle,
|
|
CoglHandle program)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterialState state = COGL_MATERIAL_STATE_USER_SHADER;
|
|
CoglMaterial *authority;
|
|
|
|
g_return_if_fail (cogl_is_material (handle));
|
|
|
|
authority = _cogl_material_get_authority (material, state);
|
|
|
|
if (authority->big_state->user_program == program)
|
|
return;
|
|
|
|
/* - Flush journal primitives referencing the current state.
|
|
* - Make sure the material has no dependants so it may be modified.
|
|
* - If the material isn't currently an authority for the state being
|
|
* changed, then initialize that state from the current authority.
|
|
*/
|
|
_cogl_material_pre_change_notify (material, state, NULL);
|
|
|
|
if (program != COGL_INVALID_HANDLE)
|
|
_cogl_material_set_backend (material, COGL_MATERIAL_BACKEND_DEFAULT);
|
|
|
|
/* If we are the current authority see if we can revert to one of our
|
|
* ancestors being the authority */
|
|
if (material == authority && authority->parent != NULL)
|
|
{
|
|
CoglMaterial *old_authority =
|
|
_cogl_material_get_authority (authority->parent, state);
|
|
|
|
if (old_authority->big_state->user_program == program)
|
|
material->differences &= ~state;
|
|
}
|
|
else if (material != authority)
|
|
{
|
|
/* If we weren't previously the authority on this state then we
|
|
* need to extended our differences mask and so it's possible
|
|
* that some of our ancestry will now become redundant, so we
|
|
* aim to reparent ourselves if that's true... */
|
|
material->differences |= state;
|
|
_cogl_material_prune_redundant_ancestry (material);
|
|
}
|
|
|
|
if (program != COGL_INVALID_HANDLE)
|
|
cogl_handle_ref (program);
|
|
if (authority == material &&
|
|
material->big_state->user_program != COGL_INVALID_HANDLE)
|
|
cogl_handle_unref (material->big_state->user_program);
|
|
material->big_state->user_program = program;
|
|
|
|
handle_automatic_blend_enable (material, state);
|
|
}
|
|
|
|
void
|
|
cogl_material_set_depth_test_enabled (CoglHandle handle,
|
|
gboolean enable)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterialState state = COGL_MATERIAL_STATE_DEPTH;
|
|
CoglMaterial *authority;
|
|
CoglMaterialDepthState *depth_state;
|
|
|
|
g_return_if_fail (cogl_is_material (handle));
|
|
|
|
authority = _cogl_material_get_authority (material, state);
|
|
|
|
depth_state = &authority->big_state->depth_state;
|
|
if (depth_state->depth_test_enabled == enable)
|
|
return;
|
|
|
|
/* - Flush journal primitives referencing the current state.
|
|
* - Make sure the material has no dependants so it may be modified.
|
|
* - If the material isn't currently an authority for the state being
|
|
* changed, then initialize that state from the current authority.
|
|
*/
|
|
_cogl_material_pre_change_notify (material, state, NULL);
|
|
|
|
material->big_state->depth_state.depth_test_enabled = enable;
|
|
|
|
_cogl_material_update_authority (material, authority, state,
|
|
_cogl_material_depth_state_equal);
|
|
}
|
|
|
|
gboolean
|
|
cogl_material_get_depth_test_enabled (CoglHandle handle)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterial *authority;
|
|
|
|
g_return_val_if_fail (cogl_is_material (handle), FALSE);
|
|
|
|
authority =
|
|
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_DEPTH);
|
|
|
|
return authority->big_state->depth_state.depth_test_enabled;
|
|
}
|
|
|
|
void
|
|
cogl_material_set_depth_writing_enabled (CoglHandle handle,
|
|
gboolean enable)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterialState state = COGL_MATERIAL_STATE_DEPTH;
|
|
CoglMaterial *authority;
|
|
CoglMaterialDepthState *depth_state;
|
|
|
|
g_return_if_fail (cogl_is_material (handle));
|
|
|
|
authority = _cogl_material_get_authority (material, state);
|
|
|
|
depth_state = &authority->big_state->depth_state;
|
|
if (depth_state->depth_writing_enabled == enable)
|
|
return;
|
|
|
|
/* - Flush journal primitives referencing the current state.
|
|
* - Make sure the material has no dependants so it may be modified.
|
|
* - If the material isn't currently an authority for the state being
|
|
* changed, then initialize that state from the current authority.
|
|
*/
|
|
_cogl_material_pre_change_notify (material, state, NULL);
|
|
|
|
material->big_state->depth_state.depth_writing_enabled = enable;
|
|
|
|
_cogl_material_update_authority (material, authority, state,
|
|
_cogl_material_depth_state_equal);
|
|
}
|
|
|
|
gboolean
|
|
cogl_material_get_depth_writing_enabled (CoglHandle handle)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterial *authority;
|
|
|
|
g_return_val_if_fail (cogl_is_material (handle), TRUE);
|
|
|
|
authority =
|
|
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_DEPTH);
|
|
|
|
return authority->big_state->depth_state.depth_writing_enabled;
|
|
}
|
|
|
|
void
|
|
cogl_material_set_depth_test_function (CoglHandle handle,
|
|
CoglDepthTestFunction function)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterialState state = COGL_MATERIAL_STATE_DEPTH;
|
|
CoglMaterial *authority;
|
|
CoglMaterialDepthState *depth_state;
|
|
|
|
g_return_if_fail (cogl_is_material (handle));
|
|
|
|
authority = _cogl_material_get_authority (material, state);
|
|
|
|
depth_state = &authority->big_state->depth_state;
|
|
if (depth_state->depth_test_function == function)
|
|
return;
|
|
|
|
/* - Flush journal primitives referencing the current state.
|
|
* - Make sure the material has no dependants so it may be modified.
|
|
* - If the material isn't currently an authority for the state being
|
|
* changed, then initialize that state from the current authority.
|
|
*/
|
|
_cogl_material_pre_change_notify (material, state, NULL);
|
|
|
|
material->big_state->depth_state.depth_test_function = function;
|
|
|
|
_cogl_material_update_authority (material, authority, state,
|
|
_cogl_material_depth_state_equal);
|
|
}
|
|
|
|
CoglDepthTestFunction
|
|
cogl_material_get_depth_test_function (CoglHandle handle)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterial *authority;
|
|
|
|
g_return_val_if_fail (cogl_is_material (handle),
|
|
COGL_DEPTH_TEST_FUNCTION_LESS);
|
|
|
|
authority =
|
|
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_DEPTH);
|
|
|
|
return authority->big_state->depth_state.depth_test_function;
|
|
}
|
|
|
|
|
|
gboolean
|
|
cogl_material_set_depth_range (CoglHandle handle,
|
|
float near_val,
|
|
float far_val,
|
|
GError **error)
|
|
{
|
|
#ifndef COGL_HAS_GLES
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterialState state = COGL_MATERIAL_STATE_DEPTH;
|
|
CoglMaterial *authority;
|
|
CoglMaterialDepthState *depth_state;
|
|
|
|
g_return_val_if_fail (cogl_is_material (handle), FALSE);
|
|
|
|
authority = _cogl_material_get_authority (material, state);
|
|
|
|
depth_state = &authority->big_state->depth_state;
|
|
if (depth_state->depth_range_near == near_val &&
|
|
depth_state->depth_range_far == far_val)
|
|
return TRUE;
|
|
|
|
/* - Flush journal primitives referencing the current state.
|
|
* - Make sure the material has no dependants so it may be modified.
|
|
* - If the material isn't currently an authority for the state being
|
|
* changed, then initialize that state from the current authority.
|
|
*/
|
|
_cogl_material_pre_change_notify (material, state, NULL);
|
|
|
|
material->big_state->depth_state.depth_range_near = near_val;
|
|
material->big_state->depth_state.depth_range_far = far_val;
|
|
|
|
_cogl_material_update_authority (material, authority, state,
|
|
_cogl_material_depth_state_equal);
|
|
return TRUE;
|
|
#else
|
|
g_set_error (error,
|
|
COGL_ERROR,
|
|
COGL_ERROR_MISSING_FEATURE,
|
|
"glDepthRange not available on GLES 1");
|
|
return FALSE;
|
|
#endif
|
|
}
|
|
|
|
void
|
|
cogl_material_get_depth_range (CoglHandle handle,
|
|
float *near_val,
|
|
float *far_val)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterial *authority;
|
|
|
|
g_return_if_fail (cogl_is_material (handle));
|
|
|
|
authority =
|
|
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_DEPTH);
|
|
|
|
*near_val = authority->big_state->depth_state.depth_range_near;
|
|
*far_val = authority->big_state->depth_state.depth_range_far;
|
|
}
|
|
|
|
unsigned long
|
|
_cogl_material_get_age (CoglHandle handle)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
|
|
g_return_val_if_fail (cogl_is_material (handle), 0);
|
|
|
|
return material->age;
|
|
}
|
|
|
|
static CoglMaterialLayer *
|
|
_cogl_material_layer_copy (CoglMaterialLayer *src)
|
|
{
|
|
CoglMaterialLayer *layer = g_slice_new (CoglMaterialLayer);
|
|
int i;
|
|
|
|
cogl_handle_ref ((CoglHandle)src);
|
|
|
|
layer->_parent = src->_parent;
|
|
layer->owner = NULL;
|
|
layer->parent = src;
|
|
|
|
if (src->has_children)
|
|
src->children = g_list_prepend (src->children, layer);
|
|
else
|
|
{
|
|
src->has_children = TRUE;
|
|
src->first_child = layer;
|
|
src->children = NULL;
|
|
}
|
|
|
|
layer->has_children = FALSE;
|
|
layer->index = src->index;
|
|
layer->differences = 0;
|
|
layer->has_big_state = FALSE;
|
|
|
|
for (i = 0; i < COGL_MATERIAL_N_BACKENDS; i++)
|
|
layer->backend_priv[i] = NULL;
|
|
|
|
return _cogl_material_layer_handle_new (layer);
|
|
}
|
|
|
|
static void
|
|
_cogl_material_layer_unparent (CoglMaterialLayer *layer)
|
|
{
|
|
CoglMaterialLayer *parent = layer->parent;
|
|
|
|
if (parent == NULL)
|
|
return;
|
|
|
|
g_return_if_fail (parent->has_children);
|
|
|
|
if (parent->first_child == layer)
|
|
{
|
|
if (parent->children)
|
|
{
|
|
parent->first_child = parent->children->data;
|
|
parent->children =
|
|
g_list_delete_link (parent->children, parent->children);
|
|
}
|
|
else
|
|
parent->has_children = FALSE;
|
|
}
|
|
else
|
|
parent->children = g_list_remove (parent->children, layer);
|
|
|
|
cogl_handle_unref (parent);
|
|
}
|
|
|
|
static void
|
|
_cogl_material_layer_free (CoglMaterialLayer *layer)
|
|
{
|
|
int i;
|
|
|
|
_cogl_material_layer_unparent (layer);
|
|
|
|
/* NB: layers may be used by multiple materials which may be using
|
|
* different backends, therefore we determine which backends to
|
|
* notify based on the private state pointers for each backend...
|
|
*/
|
|
for (i = 0; i < COGL_MATERIAL_N_BACKENDS; i++)
|
|
{
|
|
if (layer->backend_priv[i] && backends[i]->free_layer_priv)
|
|
backends[i]->free_layer_priv (layer);
|
|
}
|
|
|
|
if (layer->differences & COGL_MATERIAL_LAYER_STATE_TEXTURE)
|
|
cogl_handle_unref (layer->texture);
|
|
|
|
if (layer->differences & COGL_MATERIAL_LAYER_STATE_NEEDS_BIG_STATE)
|
|
g_slice_free (CoglMaterialLayerBigState, layer->big_state);
|
|
|
|
g_slice_free (CoglMaterialLayer, layer);
|
|
}
|
|
|
|
/* If a layer has descendants we can't modify it freely
|
|
*
|
|
* If the layer is owned and the owner has descendants we can't
|
|
* modify it freely.
|
|
*
|
|
* In both cases when we can't freely modify a layer we can either:
|
|
* - create a new layer; splice it in to replace the layer so it can
|
|
* be directly modified.
|
|
* XXX: disadvantage is that we have to invalidate the layers_cache
|
|
* for the owner and its descendants.
|
|
* - create a new derived layer and modify that.
|
|
*/
|
|
|
|
/* XXX: how is the caller expected to deal with ref-counting?
|
|
*
|
|
* If the layer can't be freely modified and we return a new layer
|
|
* then that will effectively make the caller own a new reference
|
|
* which doesn't happen if we simply modify the given layer.
|
|
*
|
|
* We could make it consistent by taking a reference on the layer if
|
|
* we don't create a new one. At least this way the caller could
|
|
* deal with it consistently, though the semantics are a bit
|
|
* strange.
|
|
*
|
|
* Alternatively we could leave it to the caller to check
|
|
* ...?
|
|
*/
|
|
|
|
void
|
|
_cogl_material_init_default_layers (void)
|
|
{
|
|
CoglMaterialLayer *layer = g_slice_new0 (CoglMaterialLayer);
|
|
CoglMaterialLayerBigState *big_state =
|
|
g_slice_new0 (CoglMaterialLayerBigState);
|
|
CoglMaterialLayer *new;
|
|
int i;
|
|
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
layer->has_children = FALSE;
|
|
layer->index = 0;
|
|
|
|
for (i = 0; i < COGL_MATERIAL_N_BACKENDS; i++)
|
|
layer->backend_priv[i] = NULL;
|
|
|
|
layer->differences = COGL_MATERIAL_LAYER_STATE_ALL_SPARSE;
|
|
|
|
layer->unit_index = 0;
|
|
|
|
layer->texture = COGL_INVALID_HANDLE;
|
|
layer->texture_overridden = FALSE;
|
|
|
|
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->big_state = big_state;
|
|
layer->has_big_state = TRUE;
|
|
|
|
/* Choose the same default combine mode as OpenGL:
|
|
* RGBA = MODULATE(PREVIOUS[RGBA],TEXTURE[RGBA]) */
|
|
big_state->texture_combine_rgb_func = GL_MODULATE;
|
|
big_state->texture_combine_rgb_src[0] = GL_PREVIOUS;
|
|
big_state->texture_combine_rgb_src[1] = GL_TEXTURE;
|
|
big_state->texture_combine_rgb_op[0] = GL_SRC_COLOR;
|
|
big_state->texture_combine_rgb_op[1] = GL_SRC_COLOR;
|
|
big_state->texture_combine_alpha_func = GL_MODULATE;
|
|
big_state->texture_combine_alpha_src[0] = GL_PREVIOUS;
|
|
big_state->texture_combine_alpha_src[1] = GL_TEXTURE;
|
|
big_state->texture_combine_alpha_op[0] = GL_SRC_ALPHA;
|
|
big_state->texture_combine_alpha_op[1] = GL_SRC_ALPHA;
|
|
|
|
cogl_matrix_init_identity (&big_state->matrix);
|
|
|
|
ctx->default_layer_0 = _cogl_material_layer_handle_new (layer);
|
|
|
|
/* TODO: we should make default_layer_n comprise of two
|
|
* descendants of default_layer_0:
|
|
* - the first descendant should change the texture combine
|
|
* to what we expect is most commonly used for multitexturing
|
|
* - the second should revert the above change.
|
|
*
|
|
* why? the documentation for how a new layer is initialized
|
|
* doesn't say that layers > 0 have different defaults so unless
|
|
* we change the documentation we can't use different defaults,
|
|
* but if the user does what we expect and changes the
|
|
* texture combine then we can revert the authority to the
|
|
* first descendant which means we can maximize the number
|
|
* of layers with a common ancestor.
|
|
*
|
|
* The main problem will be that we'll need to disable the
|
|
* optimizations for flattening the ancestry when we make
|
|
* the second descendant which reverts the state.
|
|
*/
|
|
ctx->default_layer_n = _cogl_material_layer_copy (layer);
|
|
new = _cogl_material_set_layer_unit (NULL, ctx->default_layer_n, 1);
|
|
g_assert (new == ctx->default_layer_n);
|
|
/* Since we passed a newly allocated layer we don't expect that
|
|
* _set_layer_unit() will have to allocate *another* layer. */
|
|
|
|
/* Finally we create a dummy dependant for ->default_layer_n which
|
|
* effectively ensures that ->default_layer_n and ->default_layer_0
|
|
* remain immutable.
|
|
*/
|
|
ctx->dummy_layer_dependant =
|
|
_cogl_material_layer_copy (ctx->default_layer_n);
|
|
}
|
|
|
|
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_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 = COGL_MATERIAL (handle);
|
|
CoglMaterialLayerState state = COGL_MATERIAL_LAYER_STATE_COMBINE;
|
|
CoglMaterialLayer *authority;
|
|
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);
|
|
|
|
/* Note: this will ensure that the layer exists, creating one if it
|
|
* doesn't already.
|
|
*
|
|
* Note: If the layer already existed it's possibly owned by another
|
|
* material. If the layer is created then it will be owned by
|
|
* material. */
|
|
layer = _cogl_material_get_layer (material, layer_index);
|
|
|
|
/* Now find the ancestor of the layer that is the authority for the
|
|
* state we want to change */
|
|
authority = _cogl_material_layer_get_authority (layer, state);
|
|
|
|
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];
|
|
}
|
|
|
|
/* FIXME: compare the new state with the current state! */
|
|
|
|
/* possibly flush primitives referencing the current state... */
|
|
layer = _cogl_material_layer_pre_change_notify (material, layer, state);
|
|
|
|
setup_texture_combine_state (rgb,
|
|
&layer->big_state->texture_combine_rgb_func,
|
|
layer->big_state->texture_combine_rgb_src,
|
|
layer->big_state->texture_combine_rgb_op);
|
|
|
|
setup_texture_combine_state (a,
|
|
&layer->big_state->texture_combine_alpha_func,
|
|
layer->big_state->texture_combine_alpha_src,
|
|
layer->big_state->texture_combine_alpha_op);
|
|
|
|
/* If the original layer we found is currently the authority on
|
|
* the state we are changing see if we can revert to one of our
|
|
* ancestors being the authority. */
|
|
if (layer == authority && authority->parent != NULL)
|
|
{
|
|
CoglMaterialLayer *old_authority =
|
|
_cogl_material_layer_get_authority (authority->parent, state);
|
|
|
|
if (_cogl_material_layer_combine_state_equal (authority,
|
|
old_authority))
|
|
{
|
|
layer->differences &= ~state;
|
|
|
|
g_assert (layer->owner == material);
|
|
if (layer->differences == 0)
|
|
_cogl_material_prune_empty_layer_difference (material,
|
|
layer);
|
|
goto changed;
|
|
}
|
|
}
|
|
|
|
/* If we weren't previously the authority on this state then we need
|
|
* to extended our differences mask and so it's possible that some
|
|
* of our ancestry will now become redundant, so we aim to reparent
|
|
* ourselves if that's true... */
|
|
if (layer != authority)
|
|
{
|
|
layer->differences |= state;
|
|
_cogl_material_layer_prune_redundant_ancestry (layer);
|
|
}
|
|
|
|
changed:
|
|
|
|
handle_automatic_blend_enable (material, COGL_MATERIAL_STATE_LAYERS);
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
cogl_material_set_layer_combine_constant (CoglHandle handle,
|
|
int layer_index,
|
|
CoglColor *constant_color)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterialLayerState state = COGL_MATERIAL_LAYER_STATE_COMBINE_CONSTANT;
|
|
CoglMaterialLayer *layer;
|
|
CoglMaterialLayer *authority;
|
|
CoglMaterialLayer *new;
|
|
|
|
g_return_if_fail (cogl_is_material (handle));
|
|
|
|
/* Note: this will ensure that the layer exists, creating one if it
|
|
* doesn't already.
|
|
*
|
|
* Note: If the layer already existed it's possibly owned by another
|
|
* material. If the layer is created then it will be owned by
|
|
* material. */
|
|
layer = _cogl_material_get_layer (material, layer_index);
|
|
|
|
/* Now find the ancestor of the layer that is the authority for the
|
|
* state we want to change */
|
|
authority = _cogl_material_layer_get_authority (layer, state);
|
|
|
|
if (memcmp (authority->big_state->texture_combine_constant,
|
|
constant_color, sizeof (float) * 4) == 0)
|
|
return;
|
|
|
|
new = _cogl_material_layer_pre_change_notify (material, layer, state);
|
|
if (new != layer)
|
|
layer = new;
|
|
else
|
|
{
|
|
/* If the original layer we found is currently the authority on
|
|
* the state we are changing see if we can revert to one of our
|
|
* ancestors being the authority. */
|
|
if (layer == authority && authority->parent != NULL)
|
|
{
|
|
CoglMaterialLayer *old_authority =
|
|
_cogl_material_layer_get_authority (authority->parent, state);
|
|
CoglMaterialLayerBigState *old_big_state = old_authority->big_state;
|
|
|
|
if (memcmp (old_big_state->texture_combine_constant,
|
|
constant_color, sizeof (float) * 4) == 0)
|
|
{
|
|
layer->differences &= ~state;
|
|
|
|
g_assert (layer->owner == material);
|
|
if (layer->differences == 0)
|
|
_cogl_material_prune_empty_layer_difference (material,
|
|
layer);
|
|
goto changed;
|
|
}
|
|
}
|
|
}
|
|
|
|
layer->big_state->texture_combine_constant[0] =
|
|
cogl_color_get_red_float (constant_color);
|
|
layer->big_state->texture_combine_constant[1] =
|
|
cogl_color_get_green_float (constant_color);
|
|
layer->big_state->texture_combine_constant[2] =
|
|
cogl_color_get_blue_float (constant_color);
|
|
layer->big_state->texture_combine_constant[3] =
|
|
cogl_color_get_alpha_float (constant_color);
|
|
|
|
/* If we weren't previously the authority on this state then we need
|
|
* to extended our differences mask and so it's possible that some
|
|
* of our ancestry will now become redundant, so we aim to reparent
|
|
* ourselves if that's true... */
|
|
if (layer != authority)
|
|
{
|
|
layer->differences |= state;
|
|
_cogl_material_layer_prune_redundant_ancestry (layer);
|
|
}
|
|
|
|
changed:
|
|
|
|
handle_automatic_blend_enable (material, COGL_MATERIAL_STATE_LAYERS);
|
|
}
|
|
|
|
void
|
|
cogl_material_set_layer_matrix (CoglHandle handle,
|
|
int layer_index,
|
|
CoglMatrix *matrix)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterialLayerState state = COGL_MATERIAL_LAYER_STATE_USER_MATRIX;
|
|
CoglMaterialLayer *layer;
|
|
CoglMaterialLayer *authority;
|
|
CoglMaterialLayer *new;
|
|
|
|
g_return_if_fail (cogl_is_material (handle));
|
|
|
|
/* Note: this will ensure that the layer exists, creating one if it
|
|
* doesn't already.
|
|
*
|
|
* Note: If the layer already existed it's possibly owned by another
|
|
* material. If the layer is created then it will be owned by
|
|
* material. */
|
|
layer = _cogl_material_get_layer (material, layer_index);
|
|
|
|
/* Now find the ancestor of the layer that is the authority for the
|
|
* state we want to change */
|
|
authority = _cogl_material_layer_get_authority (layer, state);
|
|
|
|
if (cogl_matrix_equal (matrix, &authority->big_state->matrix))
|
|
return;
|
|
|
|
new = _cogl_material_layer_pre_change_notify (material, layer, state);
|
|
if (new != layer)
|
|
layer = new;
|
|
else
|
|
{
|
|
/* If the original layer we found is currently the authority on
|
|
* the state we are changing see if we can revert to one of our
|
|
* ancestors being the authority. */
|
|
if (layer == authority && authority->parent != NULL)
|
|
{
|
|
CoglMaterialLayer *old_authority =
|
|
_cogl_material_layer_get_authority (authority->parent, state);
|
|
|
|
if (cogl_matrix_equal (matrix, &old_authority->big_state->matrix))
|
|
{
|
|
layer->differences &= ~state;
|
|
|
|
g_assert (layer->owner == material);
|
|
if (layer->differences == 0)
|
|
_cogl_material_prune_empty_layer_difference (material,
|
|
layer);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
layer->big_state->matrix = *matrix;
|
|
|
|
/* If we weren't previously the authority on this state then we need
|
|
* to extended our differences mask and so it's possible that some
|
|
* of our ancestry will now become redundant, so we aim to reparent
|
|
* ourselves if that's true... */
|
|
if (layer != authority)
|
|
{
|
|
layer->differences |= state;
|
|
_cogl_material_layer_prune_redundant_ancestry (layer);
|
|
}
|
|
}
|
|
|
|
void
|
|
cogl_material_remove_layer (CoglHandle handle, int layer_index)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterial *authority;
|
|
CoglMaterialLayerInfo layer_info;
|
|
int i;
|
|
|
|
g_return_if_fail (cogl_is_material (handle));
|
|
|
|
authority =
|
|
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_LAYERS);
|
|
|
|
/* The layer index of the layer we want info about */
|
|
layer_info.layer_index = layer_index;
|
|
|
|
/* This will be updated with a reference to the layer being removed
|
|
* if it can be found. */
|
|
layer_info.layer = NULL;
|
|
|
|
/* This will be filled in with a list of layers that need to be
|
|
* dropped down to a lower texture unit to fill the gap of the
|
|
* removed layer. */
|
|
layer_info.layers_to_shift =
|
|
g_alloca (sizeof (CoglMaterialLayer *) * authority->n_layers);
|
|
layer_info.n_layers_to_shift = 0;
|
|
|
|
/* Unlike when we query layer info when adding a layer we must
|
|
* always have a complete layers_to_shift list... */
|
|
layer_info.ignore_shift_layers_if_found = FALSE;
|
|
|
|
_cogl_material_get_layer_info (authority, &layer_info);
|
|
|
|
if (layer_info.layer == NULL)
|
|
return;
|
|
|
|
for (i = 0; i < layer_info.n_layers_to_shift; i++)
|
|
{
|
|
CoglMaterialLayer *shift_layer = layer_info.layers_to_shift[i];
|
|
int unit_index = _cogl_material_layer_get_unit_index (shift_layer);
|
|
_cogl_material_set_layer_unit (material, shift_layer, unit_index - 1);
|
|
/* NB: shift_layer may not be writeable so _set_layer_unit()
|
|
* will allocate a derived layer internally which will become
|
|
* owned by material. Check the return value if we need to do
|
|
* anything else with this layer. */
|
|
}
|
|
|
|
_cogl_material_remove_layer_difference (material, layer_info.layer, TRUE);
|
|
_cogl_material_try_reverting_layers_authority (material, NULL);
|
|
|
|
handle_automatic_blend_enable (material, COGL_MATERIAL_STATE_LAYERS);
|
|
}
|
|
|
|
static gboolean
|
|
prepend_layer_to_list_cb (CoglMaterialLayer *layer,
|
|
void *user_data)
|
|
{
|
|
GList **layers = user_data;
|
|
|
|
*layers = g_list_prepend (*layers, layer);
|
|
return TRUE;
|
|
}
|
|
|
|
/* TODO: deprecate this API and replace it with
|
|
* cogl_material_foreach_layer
|
|
* TODO: update the docs to note that if the user modifies any layers
|
|
* then the list may become invalid.
|
|
*/
|
|
const GList *
|
|
cogl_material_get_layers (CoglHandle handle)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
|
|
g_return_val_if_fail (cogl_is_material (handle), NULL);
|
|
|
|
if (!material->deprecated_get_layers_list_dirty)
|
|
g_list_free (material->deprecated_get_layers_list);
|
|
|
|
material->deprecated_get_layers_list = NULL;
|
|
|
|
_cogl_material_foreach_layer (material,
|
|
prepend_layer_to_list_cb,
|
|
&material->deprecated_get_layers_list);
|
|
material->deprecated_get_layers_list =
|
|
g_list_reverse (material->deprecated_get_layers_list);
|
|
|
|
material->deprecated_get_layers_list_dirty = 0;
|
|
|
|
return material->deprecated_get_layers_list;
|
|
}
|
|
|
|
int
|
|
cogl_material_get_n_layers (CoglHandle handle)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterial *authority;
|
|
|
|
g_return_val_if_fail (cogl_is_material (handle), 0);
|
|
|
|
authority =
|
|
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_LAYERS);
|
|
|
|
return authority->n_layers;
|
|
}
|
|
|
|
/* FIXME: deprecate and replace with
|
|
* cogl_material_get_layer_type() instead. */
|
|
CoglMaterialLayerType
|
|
cogl_material_layer_get_type (CoglHandle layer_handle)
|
|
{
|
|
return COGL_MATERIAL_LAYER_TYPE_TEXTURE;
|
|
}
|
|
|
|
/* FIXME: deprecate and replace with
|
|
* cogl_material_get_layer_texture() instead. */
|
|
CoglHandle
|
|
cogl_material_layer_get_texture (CoglHandle handle)
|
|
{
|
|
CoglMaterialLayer *layer = COGL_MATERIAL_LAYER (handle);
|
|
|
|
g_return_val_if_fail (cogl_is_material_layer (handle),
|
|
COGL_INVALID_HANDLE);
|
|
|
|
return _cogl_material_layer_get_texture (layer);
|
|
}
|
|
|
|
gboolean
|
|
_cogl_material_layer_has_user_matrix (CoglHandle handle)
|
|
{
|
|
CoglMaterialLayer *layer = COGL_MATERIAL_LAYER (handle);
|
|
CoglMaterialLayer *authority;
|
|
|
|
g_return_val_if_fail (cogl_is_material_layer (handle), FALSE);
|
|
|
|
authority =
|
|
_cogl_material_layer_get_authority (layer,
|
|
COGL_MATERIAL_LAYER_STATE_USER_MATRIX);
|
|
|
|
/* If the authority is the default material then no, otherwise yes */
|
|
return authority->parent ? TRUE : FALSE;
|
|
}
|
|
|
|
static void
|
|
_cogl_material_layer_get_filters (CoglMaterialLayer *layer,
|
|
CoglMaterialFilter *min_filter,
|
|
CoglMaterialFilter *mag_filter)
|
|
{
|
|
CoglMaterialLayer *authority =
|
|
_cogl_material_layer_get_authority (layer,
|
|
COGL_MATERIAL_LAYER_STATE_FILTERS);
|
|
|
|
*min_filter = authority->min_filter;
|
|
*mag_filter = authority->mag_filter;
|
|
}
|
|
|
|
void
|
|
_cogl_material_layer_pre_paint (CoglHandle handle)
|
|
{
|
|
CoglMaterialLayer *layer = COGL_MATERIAL_LAYER (handle);
|
|
CoglMaterialLayer *texture_authority;
|
|
|
|
texture_authority =
|
|
_cogl_material_layer_get_authority (layer,
|
|
COGL_MATERIAL_LAYER_STATE_TEXTURE);
|
|
|
|
if (texture_authority->texture != COGL_INVALID_HANDLE)
|
|
{
|
|
CoglTexturePrePaintFlags flags = 0;
|
|
CoglMaterialFilter min_filter;
|
|
CoglMaterialFilter mag_filter;
|
|
|
|
_cogl_material_layer_get_filters (layer, &min_filter, &mag_filter);
|
|
|
|
if (min_filter == COGL_MATERIAL_FILTER_NEAREST_MIPMAP_NEAREST
|
|
|| min_filter == COGL_MATERIAL_FILTER_LINEAR_MIPMAP_NEAREST
|
|
|| min_filter == COGL_MATERIAL_FILTER_NEAREST_MIPMAP_LINEAR
|
|
|| min_filter == COGL_MATERIAL_FILTER_LINEAR_MIPMAP_LINEAR)
|
|
flags |= COGL_TEXTURE_NEEDS_MIPMAP;
|
|
|
|
_cogl_texture_pre_paint (layer->texture, flags);
|
|
}
|
|
}
|
|
|
|
CoglMaterialFilter
|
|
cogl_material_layer_get_min_filter (CoglHandle handle)
|
|
{
|
|
CoglMaterialLayer *layer = COGL_MATERIAL_LAYER (handle);
|
|
CoglMaterialLayer *authority;
|
|
|
|
g_return_val_if_fail (cogl_is_material_layer (handle), 0);
|
|
|
|
authority =
|
|
_cogl_material_layer_get_authority (layer,
|
|
COGL_MATERIAL_LAYER_STATE_FILTERS);
|
|
|
|
return authority->min_filter;
|
|
}
|
|
|
|
CoglMaterialFilter
|
|
cogl_material_layer_get_mag_filter (CoglHandle handle)
|
|
{
|
|
CoglMaterialLayer *layer = COGL_MATERIAL_LAYER (handle);
|
|
CoglMaterialLayer *authority;
|
|
|
|
g_return_val_if_fail (cogl_is_material_layer (handle), 0);
|
|
|
|
authority =
|
|
_cogl_material_layer_get_authority (layer,
|
|
COGL_MATERIAL_LAYER_STATE_FILTERS);
|
|
|
|
return authority->mag_filter;
|
|
}
|
|
|
|
void
|
|
cogl_material_set_layer_filters (CoglHandle handle,
|
|
int layer_index,
|
|
CoglMaterialFilter min_filter,
|
|
CoglMaterialFilter mag_filter)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
CoglMaterialLayerState change = COGL_MATERIAL_LAYER_STATE_FILTERS;
|
|
CoglMaterialLayer *layer;
|
|
CoglMaterialLayer *authority;
|
|
CoglMaterialLayer *new;
|
|
|
|
g_return_if_fail (cogl_is_material (handle));
|
|
|
|
/* Note: this will ensure that the layer exists, creating one if it
|
|
* doesn't already.
|
|
*
|
|
* Note: If the layer already existed it's possibly owned by another
|
|
* material. If the layer is created then it will be owned by
|
|
* material. */
|
|
layer = _cogl_material_get_layer (material, layer_index);
|
|
|
|
/* Now find the ancestor of the layer that is the authority for the
|
|
* state we want to change */
|
|
authority = _cogl_material_layer_get_authority (layer, change);
|
|
|
|
if (authority->min_filter == min_filter &&
|
|
authority->mag_filter == mag_filter)
|
|
return;
|
|
|
|
new = _cogl_material_layer_pre_change_notify (material, layer, change);
|
|
if (new != layer)
|
|
layer = new;
|
|
else
|
|
{
|
|
/* If the original layer we found is currently the authority on
|
|
* the state we are changing see if we can revert to one of our
|
|
* ancestors being the authority. */
|
|
if (layer == authority && authority->parent != NULL)
|
|
{
|
|
CoglMaterialLayer *old_authority =
|
|
_cogl_material_layer_get_authority (authority->parent, change);
|
|
|
|
if (old_authority->min_filter == min_filter &&
|
|
old_authority->mag_filter == mag_filter)
|
|
{
|
|
layer->differences &= ~change;
|
|
|
|
g_assert (layer->owner == material);
|
|
if (layer->differences == 0)
|
|
_cogl_material_prune_empty_layer_difference (material,
|
|
layer);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
layer->min_filter = min_filter;
|
|
layer->mag_filter = mag_filter;
|
|
|
|
/* If we weren't previously the authority on this state then we need
|
|
* to extended our differences mask and so it's possible that some
|
|
* of our ancestry will now become redundant, so we aim to reparent
|
|
* ourselves if that's true... */
|
|
if (layer != authority)
|
|
{
|
|
layer->differences |= change;
|
|
_cogl_material_layer_prune_redundant_ancestry (layer);
|
|
}
|
|
}
|
|
|
|
static void
|
|
disable_texture_unit (int unit_index)
|
|
{
|
|
CoglTextureUnit *unit;
|
|
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
unit = &g_array_index (ctx->texture_units, CoglTextureUnit, unit_index);
|
|
|
|
if (unit->enabled)
|
|
{
|
|
set_active_texture_unit (unit_index);
|
|
GE (glDisable (unit->current_gl_target));
|
|
unit->enabled = FALSE;
|
|
}
|
|
}
|
|
|
|
void
|
|
_cogl_gl_use_program_wrapper (GLuint program)
|
|
{
|
|
#ifdef COGL_MATERIAL_BACKEND_GLSL
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
if (ctx->current_gl_program == program)
|
|
return;
|
|
|
|
if (program)
|
|
{
|
|
GLenum gl_error;
|
|
|
|
while ((gl_error = glGetError ()) != GL_NO_ERROR)
|
|
;
|
|
glUseProgram (program);
|
|
if (glGetError () != GL_NO_ERROR)
|
|
{
|
|
GE (glUseProgram (0));
|
|
ctx->current_gl_program = 0;
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
GE (glUseProgram (0));
|
|
|
|
ctx->current_gl_program = program;
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
disable_glsl (void)
|
|
{
|
|
#ifdef COGL_MATERIAL_BACKEND_GLSL
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
if (ctx->current_use_program_type == COGL_MATERIAL_PROGRAM_TYPE_GLSL)
|
|
_cogl_gl_use_program_wrapper (0);
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
disable_arbfp (void)
|
|
{
|
|
#ifdef COGL_MATERIAL_BACKEND_ARBFP
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
if (ctx->current_use_program_type == COGL_MATERIAL_PROGRAM_TYPE_ARBFP)
|
|
GE (glDisable (GL_FRAGMENT_PROGRAM_ARB));
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
use_program (CoglHandle program_handle, CoglMaterialProgramType type)
|
|
{
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
switch (type)
|
|
{
|
|
#ifdef COGL_MATERIAL_BACKEND_GLSL
|
|
case COGL_MATERIAL_PROGRAM_TYPE_GLSL:
|
|
{
|
|
/* The GLES2 backend currently manages its own codegen for
|
|
* fixed function API fallbacks and manages its own shader
|
|
* state. */
|
|
#ifndef HAVE_COGL_GLES2
|
|
CoglProgram *program =
|
|
_cogl_program_pointer_from_handle (program_handle);
|
|
|
|
_cogl_gl_use_program_wrapper (program->gl_handle);
|
|
disable_arbfp ();
|
|
#endif
|
|
|
|
ctx->current_use_program_type = type;
|
|
break;
|
|
}
|
|
#else
|
|
case COGL_MATERIAL_PROGRAM_TYPE_GLSL:
|
|
g_warning ("Unexpected use of GLSL backend!");
|
|
break;
|
|
#endif
|
|
#ifdef COGL_MATERIAL_BACKEND_ARBFP
|
|
case COGL_MATERIAL_PROGRAM_TYPE_ARBFP:
|
|
|
|
/* _cogl_gl_use_program_wrapper can be called by cogl-program.c
|
|
* so we can't bailout without making sure we glUseProgram (0)
|
|
* first. */
|
|
disable_glsl ();
|
|
|
|
if (ctx->current_use_program_type == COGL_MATERIAL_PROGRAM_TYPE_ARBFP)
|
|
break;
|
|
|
|
GE (glEnable (GL_FRAGMENT_PROGRAM_ARB));
|
|
|
|
ctx->current_use_program_type = type;
|
|
break;
|
|
#else
|
|
case COGL_MATERIAL_PROGRAM_TYPE_ARBFP:
|
|
g_warning ("Unexpected use of GLSL backend!");
|
|
break;
|
|
#endif
|
|
#ifdef COGL_MATERIAL_BACKEND_FIXED
|
|
case COGL_MATERIAL_PROGRAM_TYPE_FIXED:
|
|
|
|
/* _cogl_gl_use_program_wrapper can be called by cogl-program.c
|
|
* so we can't bailout without making sure we glUseProgram (0)
|
|
* first. */
|
|
disable_glsl ();
|
|
|
|
if (ctx->current_use_program_type == COGL_MATERIAL_PROGRAM_TYPE_FIXED)
|
|
break;
|
|
|
|
disable_arbfp ();
|
|
|
|
ctx->current_use_program_type = type;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#if defined (COGL_MATERIAL_BACKEND_GLSL) || \
|
|
defined (COGL_MATERIAL_BACKEND_ARBFP)
|
|
static int
|
|
get_max_texture_image_units (void)
|
|
{
|
|
_COGL_GET_CONTEXT (ctx, 0);
|
|
|
|
/* This function is called quite often so we cache the value to
|
|
avoid too many GL calls */
|
|
if (G_UNLIKELY (ctx->max_texture_image_units == -1))
|
|
{
|
|
ctx->max_texture_image_units = 1;
|
|
GE (glGetIntegerv (GL_MAX_TEXTURE_IMAGE_UNITS,
|
|
&ctx->max_texture_image_units));
|
|
}
|
|
|
|
return ctx->max_texture_image_units;
|
|
}
|
|
#endif
|
|
|
|
#ifdef COGL_MATERIAL_BACKEND_GLSL
|
|
|
|
static int
|
|
_cogl_material_backend_glsl_get_max_texture_units (void)
|
|
{
|
|
return get_max_texture_image_units ();
|
|
}
|
|
|
|
static gboolean
|
|
_cogl_material_backend_glsl_start (CoglMaterial *material,
|
|
int n_layers,
|
|
unsigned long materials_difference)
|
|
{
|
|
_COGL_GET_CONTEXT (ctx, FALSE);
|
|
|
|
if (!cogl_features_available (COGL_FEATURE_SHADERS_GLSL))
|
|
return FALSE;
|
|
|
|
/* FIXME: This will likely conflict with the GLES 2 backends use of
|
|
* glUseProgram.
|
|
*/
|
|
if (materials_difference & COGL_MATERIAL_STATE_USER_SHADER)
|
|
{
|
|
CoglMaterial *authority =
|
|
_cogl_material_get_authority (material,
|
|
COGL_MATERIAL_STATE_USER_SHADER);
|
|
CoglHandle program = authority->big_state->user_program;
|
|
|
|
if (program == COGL_INVALID_HANDLE)
|
|
return FALSE; /* XXX: change me when we support code generation here */
|
|
|
|
use_program (program, COGL_MATERIAL_PROGRAM_TYPE_GLSL);
|
|
return TRUE;
|
|
}
|
|
|
|
/* TODO: also support code generation */
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
gboolean
|
|
_cogl_material_backend_glsl_add_layer (CoglMaterial *material,
|
|
CoglMaterialLayer *layer,
|
|
unsigned long layers_difference)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
_cogl_material_backend_glsl_passthrough (CoglMaterial *material)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
_cogl_material_backend_glsl_end (CoglMaterial *material,
|
|
unsigned long materials_difference)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
static const CoglMaterialBackend _cogl_material_glsl_backend =
|
|
{
|
|
_cogl_material_backend_glsl_get_max_texture_units,
|
|
_cogl_material_backend_glsl_start,
|
|
_cogl_material_backend_glsl_add_layer,
|
|
_cogl_material_backend_glsl_passthrough,
|
|
_cogl_material_backend_glsl_end,
|
|
NULL, /* material_state_change_notify */
|
|
NULL, /* material_set_parent_notify */
|
|
NULL, /* layer_state_change_notify */
|
|
NULL, /* free_priv */
|
|
};
|
|
|
|
#endif /* COGL_MATERIAL_BACKEND_GLSL */
|
|
|
|
#ifdef COGL_MATERIAL_BACKEND_ARBFP
|
|
|
|
static int
|
|
_cogl_material_backend_arbfp_get_max_texture_units (void)
|
|
{
|
|
return get_max_texture_image_units ();
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
int i;
|
|
CoglMaterialLayer **layers;
|
|
} AddLayersToArrayState;
|
|
|
|
static gboolean
|
|
add_layer_to_array_cb (CoglMaterialLayer *layer,
|
|
void *user_data)
|
|
{
|
|
AddLayersToArrayState *state = user_data;
|
|
state->layers[state->i++] = layer;
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
layers_arbfp_would_differ (CoglMaterialLayer **material0_layers,
|
|
CoglMaterialLayer **material1_layers,
|
|
int n_layers)
|
|
{
|
|
int i;
|
|
/* The layer state that affects arbfp codegen... */
|
|
unsigned long arbfp_codegen_modifiers =
|
|
COGL_MATERIAL_LAYER_STATE_COMBINE |
|
|
COGL_MATERIAL_LAYER_STATE_COMBINE_CONSTANT |
|
|
COGL_MATERIAL_LAYER_STATE_UNIT |
|
|
COGL_MATERIAL_LAYER_STATE_TEXTURE;
|
|
|
|
for (i = 0; i < n_layers; i++)
|
|
{
|
|
CoglMaterialLayer *layer0 = material0_layers[i];
|
|
CoglMaterialLayer *layer1 = material1_layers[i];
|
|
unsigned long layer_differences;
|
|
|
|
if (layer0 == layer1)
|
|
continue;
|
|
|
|
layer_differences =
|
|
_cogl_material_layer_compare_differences (layer0, layer1);
|
|
|
|
if (layer_differences & arbfp_codegen_modifiers)
|
|
{
|
|
/* When it comes to texture differences the only thing that
|
|
* affects the arbfp is the target enum... */
|
|
if (layer_differences == COGL_MATERIAL_LAYER_STATE_TEXTURE)
|
|
{
|
|
CoglHandle tex0 = _cogl_material_layer_get_texture (layer0);
|
|
CoglHandle tex1 = _cogl_material_layer_get_texture (layer1);
|
|
GLenum gl_target0;
|
|
GLenum gl_target1;
|
|
|
|
cogl_texture_get_gl_texture (tex0, NULL, &gl_target0);
|
|
cogl_texture_get_gl_texture (tex1, NULL, &gl_target1);
|
|
if (gl_target0 == gl_target1)
|
|
continue;
|
|
}
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/* This tries to find the oldest ancestor whos state would generate
|
|
* the same arbfp program as the current material. This is a simple
|
|
* mechanism for reducing the number of arbfp programs we have to
|
|
* generate.
|
|
*/
|
|
static CoglMaterial *
|
|
find_arbfp_authority (CoglMaterial *material)
|
|
{
|
|
CoglMaterial *authority0;
|
|
CoglMaterial *authority1;
|
|
int n_layers;
|
|
CoglMaterialLayer **authority0_layers;
|
|
CoglMaterialLayer **authority1_layers;
|
|
|
|
/* XXX: we'll need to update this when we add fog support to the
|
|
* arbfp codegen */
|
|
|
|
/* Find the first material that modifies state that affects the
|
|
* arbfp codegen... */
|
|
authority0 = _cogl_material_get_authority (material,
|
|
COGL_MATERIAL_STATE_LAYERS);
|
|
|
|
/* Find the next ancestor after that, that also modifies state
|
|
* affecting arbfp codegen... */
|
|
if (authority0->parent)
|
|
authority1 = _cogl_material_get_authority (authority0->parent,
|
|
COGL_MATERIAL_STATE_LAYERS);
|
|
else
|
|
return authority0;
|
|
|
|
n_layers = authority0->n_layers;
|
|
|
|
for (;;)
|
|
{
|
|
AddLayersToArrayState state;
|
|
|
|
if (authority0->n_layers != authority1->n_layers)
|
|
return authority0;
|
|
|
|
authority0_layers =
|
|
g_alloca (sizeof (CoglMaterialLayer *) * n_layers);
|
|
state.i = 0;
|
|
state.layers = authority0_layers;
|
|
_cogl_material_foreach_layer (authority0,
|
|
add_layer_to_array_cb,
|
|
&state);
|
|
|
|
authority1_layers =
|
|
g_alloca (sizeof (CoglMaterialLayer *) * n_layers);
|
|
state.i = 0;
|
|
state.layers = authority1_layers;
|
|
_cogl_material_foreach_layer (authority1,
|
|
add_layer_to_array_cb,
|
|
&state);
|
|
|
|
if (layers_arbfp_would_differ (authority0_layers, authority1_layers,
|
|
n_layers))
|
|
return authority0;
|
|
|
|
/* Find the next ancestor after that, that also modifies state
|
|
* affecting arbfp codegen... */
|
|
|
|
if (!authority1->parent)
|
|
break;
|
|
|
|
authority0 = authority1;
|
|
authority1 = _cogl_material_get_authority (authority1->parent,
|
|
COGL_MATERIAL_STATE_LAYERS);
|
|
if (authority1 == authority0)
|
|
break;
|
|
}
|
|
|
|
return authority1;
|
|
}
|
|
|
|
static void
|
|
invalidate_arbfp_authority_cache (CoglMaterial *material)
|
|
{
|
|
if (material->backend_priv_set_mask & COGL_MATERIAL_BACKEND_ARBFP_MASK)
|
|
{
|
|
CoglMaterialBackendARBfpPrivate *priv =
|
|
material->backend_privs[COGL_MATERIAL_BACKEND_ARBFP];
|
|
priv->authority_cache = NULL;
|
|
priv->authority_cache_age = 0;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
_cogl_material_backend_arbfp_start (CoglMaterial *material,
|
|
int n_layers,
|
|
unsigned long materials_difference)
|
|
{
|
|
CoglMaterial *authority;
|
|
CoglMaterialBackendARBfpPrivate *priv;
|
|
CoglMaterialBackendARBfpPrivate *authority_priv;
|
|
|
|
_COGL_GET_CONTEXT (ctx, FALSE);
|
|
|
|
if (!_cogl_features_available_private (COGL_FEATURE_PRIVATE_ARB_FP))
|
|
return FALSE;
|
|
|
|
/* TODO: support fog */
|
|
if (ctx->fog_enabled)
|
|
return FALSE;
|
|
|
|
/* Note: we allocate ARBfp private state for both the given material
|
|
* and the authority. (The oldest ancestor whos state will result in
|
|
* the same program being generated) The former will simply cache a
|
|
* pointer to the authority and the later will track the arbfp
|
|
* program that we will generate.
|
|
*/
|
|
|
|
if (!(material->backend_priv_set_mask & COGL_MATERIAL_BACKEND_ARBFP_MASK))
|
|
{
|
|
material->backend_privs[COGL_MATERIAL_BACKEND_ARBFP] =
|
|
g_slice_new0 (CoglMaterialBackendARBfpPrivate);
|
|
material->backend_priv_set_mask |= COGL_MATERIAL_BACKEND_ARBFP_MASK;
|
|
}
|
|
priv = material->backend_privs[COGL_MATERIAL_BACKEND_ARBFP];
|
|
|
|
/* XXX: We are making assumptions that we don't yet support
|
|
* modification of ancestors to optimize the sharing of state in the
|
|
* material graph. When we start to support this then the arbfp
|
|
* backend will somehow need to be notified of graph changes that
|
|
* may invalidate authority_cache pointers.
|
|
*/
|
|
|
|
if (priv->authority_cache &&
|
|
priv->authority_cache_age != _cogl_material_get_age (material))
|
|
invalidate_arbfp_authority_cache (material);
|
|
|
|
if (!priv->authority_cache)
|
|
{
|
|
priv->authority_cache = find_arbfp_authority (material);
|
|
priv->authority_cache_age = _cogl_material_get_age (material);
|
|
}
|
|
|
|
authority = priv->authority_cache;
|
|
if (!(authority->backend_priv_set_mask & COGL_MATERIAL_BACKEND_ARBFP_MASK))
|
|
{
|
|
authority->backend_privs[COGL_MATERIAL_BACKEND_ARBFP] =
|
|
g_slice_new0 (CoglMaterialBackendARBfpPrivate);
|
|
authority->backend_priv_set_mask |= COGL_MATERIAL_BACKEND_ARBFP_MASK;
|
|
}
|
|
authority_priv = authority->backend_privs[COGL_MATERIAL_BACKEND_ARBFP];
|
|
|
|
if (authority_priv->gl_program == 0)
|
|
{
|
|
/* We reuse a single grow-only GString for ARBfp code-gen */
|
|
g_string_set_size (ctx->arbfp_source_buffer, 0);
|
|
authority_priv->source = ctx->arbfp_source_buffer;
|
|
g_string_append (authority_priv->source,
|
|
"!!ARBfp1.0\n"
|
|
"TEMP output;\n"
|
|
"TEMP tmp0, tmp1, tmp2, tmp3, tmp4;\n"
|
|
"PARAM half = {.5, .5, .5, .5};\n"
|
|
"PARAM one = {1, 1, 1, 1};\n"
|
|
"PARAM two = {2, 2, 2, 2};\n"
|
|
"PARAM minus_one = {-1, -1, -1, -1};\n");
|
|
authority_priv->sampled = g_new0 (gboolean, n_layers);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static CoglMaterial *
|
|
get_arbfp_authority (CoglMaterial *material)
|
|
{
|
|
CoglMaterialBackendARBfpPrivate *priv =
|
|
material->backend_privs[COGL_MATERIAL_BACKEND_ARBFP];
|
|
|
|
g_return_val_if_fail (priv != NULL, NULL);
|
|
|
|
return priv->authority_cache;
|
|
}
|
|
|
|
/* Determines if we need to handle the RGB and A texture combining
|
|
* separately or is the same function used for both channel masks and
|
|
* with the same arguments...
|
|
*/
|
|
static gboolean
|
|
need_texture_combine_separate (CoglMaterialLayer *combine_authority)
|
|
{
|
|
CoglMaterialLayerBigState *big_state = combine_authority->big_state;
|
|
int n_args;
|
|
int i;
|
|
|
|
if (big_state->texture_combine_rgb_func !=
|
|
big_state->texture_combine_alpha_func)
|
|
return TRUE;
|
|
|
|
n_args = get_n_args_for_combine_func (big_state->texture_combine_rgb_func);
|
|
|
|
for (i = 0; i < n_args; i++)
|
|
{
|
|
if (big_state->texture_combine_rgb_src[i] !=
|
|
big_state->texture_combine_alpha_src[i])
|
|
return TRUE;
|
|
|
|
/*
|
|
* We can allow some variation of the source operands without
|
|
* needing a separation...
|
|
*
|
|
* "A = REPLACE (CONSTANT[A])" + either of the following...
|
|
* "RGB = REPLACE (CONSTANT[RGB])"
|
|
* "RGB = REPLACE (CONSTANT[A])"
|
|
*
|
|
* can be combined as:
|
|
* "RGBA = REPLACE (CONSTANT)" or
|
|
* "RGBA = REPLACE (CONSTANT[A])" or
|
|
*
|
|
* And "A = REPLACE (1-CONSTANT[A])" + either of the following...
|
|
* "RGB = REPLACE (1-CONSTANT)" or
|
|
* "RGB = REPLACE (1-CONSTANT[A])"
|
|
*
|
|
* can be combined as:
|
|
* "RGBA = REPLACE (1-CONSTANT)" or
|
|
* "RGBA = REPLACE (1-CONSTANT[A])"
|
|
*/
|
|
switch (big_state->texture_combine_alpha_op[i])
|
|
{
|
|
case GL_SRC_ALPHA:
|
|
switch (big_state->texture_combine_rgb_op[i])
|
|
{
|
|
case GL_SRC_COLOR:
|
|
case GL_SRC_ALPHA:
|
|
break;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
break;
|
|
case GL_ONE_MINUS_SRC_ALPHA:
|
|
switch (big_state->texture_combine_rgb_op[i])
|
|
{
|
|
case GL_ONE_MINUS_SRC_COLOR:
|
|
case GL_ONE_MINUS_SRC_ALPHA:
|
|
break;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
break;
|
|
default:
|
|
return FALSE; /* impossible */
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static const char *
|
|
gl_target_to_arbfp_string (GLenum gl_target)
|
|
{
|
|
#ifndef HAVE_COGL_GLES2
|
|
if (gl_target == GL_TEXTURE_1D)
|
|
return "1D";
|
|
else
|
|
#endif
|
|
if (gl_target == GL_TEXTURE_2D)
|
|
return "2D";
|
|
#ifdef GL_ARB_texture_rectangle
|
|
else if (gl_target == GL_TEXTURE_RECTANGLE_ARB)
|
|
return "RECT";
|
|
#endif
|
|
else
|
|
return "2D";
|
|
}
|
|
|
|
static void
|
|
setup_texture_source (CoglMaterialBackendARBfpPrivate *priv,
|
|
int unit_index,
|
|
GLenum gl_target)
|
|
{
|
|
if (!priv->sampled[unit_index])
|
|
{
|
|
g_string_append_printf (priv->source,
|
|
"TEMP texel%d;\n"
|
|
"TEX texel%d,fragment.texcoord[%d],"
|
|
"texture[%d],%s;\n",
|
|
unit_index,
|
|
unit_index,
|
|
unit_index,
|
|
unit_index,
|
|
gl_target_to_arbfp_string (gl_target));
|
|
priv->sampled[unit_index] = TRUE;
|
|
}
|
|
}
|
|
|
|
typedef enum _CoglMaterialBackendARBfpArgType
|
|
{
|
|
COGL_MATERIAL_BACKEND_ARBFP_ARG_TYPE_SIMPLE,
|
|
COGL_MATERIAL_BACKEND_ARBFP_ARG_TYPE_CONSTANT,
|
|
COGL_MATERIAL_BACKEND_ARBFP_ARG_TYPE_TEXTURE
|
|
} CoglMaterialBackendARBfpArgType;
|
|
|
|
typedef struct _CoglMaterialBackendARBfpArg
|
|
{
|
|
const char *name;
|
|
|
|
CoglMaterialBackendARBfpArgType type;
|
|
|
|
/* for type = TEXTURE */
|
|
int texture_unit;
|
|
GLenum texture_target;
|
|
|
|
/* for type = CONSTANT */
|
|
int constant_id;
|
|
|
|
const char *swizzle;
|
|
|
|
} CoglMaterialBackendARBfpArg;
|
|
|
|
static void
|
|
append_arg (GString *source, const CoglMaterialBackendARBfpArg *arg)
|
|
{
|
|
switch (arg->type)
|
|
{
|
|
case COGL_MATERIAL_BACKEND_ARBFP_ARG_TYPE_TEXTURE:
|
|
g_string_append_printf (source, "texel%d%s",
|
|
arg->texture_unit, arg->swizzle);
|
|
break;
|
|
case COGL_MATERIAL_BACKEND_ARBFP_ARG_TYPE_CONSTANT:
|
|
g_string_append_printf (source, "constant%d%s",
|
|
arg->constant_id, arg->swizzle);
|
|
break;
|
|
case COGL_MATERIAL_BACKEND_ARBFP_ARG_TYPE_SIMPLE:
|
|
g_string_append_printf (source, "%s%s",
|
|
arg->name, arg->swizzle);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Note: we are trying to avoid duplicating strings during codegen
|
|
* which is why we have the slightly awkward
|
|
* CoglMaterialBackendARBfpArg mechanism. */
|
|
static void
|
|
setup_arg (CoglMaterial *material,
|
|
CoglMaterialLayer *layer,
|
|
CoglBlendStringChannelMask mask,
|
|
int arg_index,
|
|
GLint src,
|
|
GLint op,
|
|
CoglMaterialBackendARBfpArg *arg)
|
|
{
|
|
CoglMaterial *arbfp_authority = get_arbfp_authority (material);
|
|
CoglMaterialBackendARBfpPrivate *priv =
|
|
arbfp_authority->backend_privs[COGL_MATERIAL_BACKEND_ARBFP];
|
|
static const char *tmp_name[3] = { "tmp0", "tmp1", "tmp2" };
|
|
GLenum gl_target;
|
|
CoglHandle texture;
|
|
|
|
switch (src)
|
|
{
|
|
case GL_TEXTURE:
|
|
arg->type = COGL_MATERIAL_BACKEND_ARBFP_ARG_TYPE_TEXTURE;
|
|
arg->name = "texel%d";
|
|
arg->texture_unit = _cogl_material_layer_get_unit_index (layer);
|
|
texture = _cogl_material_layer_get_texture (layer);
|
|
cogl_texture_get_gl_texture (texture, NULL, &gl_target);
|
|
setup_texture_source (priv, arg->texture_unit, gl_target);
|
|
break;
|
|
case GL_CONSTANT:
|
|
{
|
|
unsigned long state = COGL_MATERIAL_LAYER_STATE_COMBINE_CONSTANT;
|
|
CoglMaterialLayer *authority =
|
|
_cogl_material_layer_get_authority (layer, state);
|
|
CoglMaterialLayerBigState *big_state = authority->big_state;
|
|
|
|
arg->type = COGL_MATERIAL_BACKEND_ARBFP_ARG_TYPE_CONSTANT;
|
|
arg->name = "constant%d";
|
|
arg->constant_id = priv->next_constant_id++;
|
|
g_string_append_printf (priv->source,
|
|
"PARAM constant%d = "
|
|
" {%f, %f, %f, %f};\n",
|
|
arg->constant_id,
|
|
big_state->texture_combine_constant[0],
|
|
big_state->texture_combine_constant[1],
|
|
big_state->texture_combine_constant[2],
|
|
big_state->texture_combine_constant[3]);
|
|
break;
|
|
}
|
|
case GL_PRIMARY_COLOR:
|
|
arg->type = COGL_MATERIAL_BACKEND_ARBFP_ARG_TYPE_SIMPLE;
|
|
arg->name = "fragment.color.primary";
|
|
break;
|
|
case GL_PREVIOUS:
|
|
arg->type = COGL_MATERIAL_BACKEND_ARBFP_ARG_TYPE_SIMPLE;
|
|
if (_cogl_material_layer_get_unit_index (layer) == 0)
|
|
arg->name = "fragment.color.primary";
|
|
else
|
|
arg->name = "output";
|
|
break;
|
|
default: /* GL_TEXTURE0..N */
|
|
arg->type = COGL_MATERIAL_BACKEND_ARBFP_ARG_TYPE_TEXTURE;
|
|
arg->name = "texture[%d]";
|
|
arg->texture_unit = src - GL_TEXTURE0;
|
|
texture = _cogl_material_layer_get_texture (layer);
|
|
cogl_texture_get_gl_texture (texture, NULL, &gl_target);
|
|
setup_texture_source (priv, arg->texture_unit, gl_target);
|
|
}
|
|
|
|
arg->swizzle = "";
|
|
|
|
switch (op)
|
|
{
|
|
case GL_SRC_COLOR:
|
|
break;
|
|
case GL_ONE_MINUS_SRC_COLOR:
|
|
g_string_append_printf (priv->source,
|
|
"SUB tmp%d, one, ",
|
|
arg_index);
|
|
append_arg (priv->source, arg);
|
|
g_string_append_printf (priv->source, ";\n");
|
|
arg->type = COGL_MATERIAL_BACKEND_ARBFP_ARG_TYPE_SIMPLE;
|
|
arg->name = tmp_name[arg_index];
|
|
arg->swizzle = "";
|
|
break;
|
|
case GL_SRC_ALPHA:
|
|
/* avoid a swizzle if we know RGB are going to be masked
|
|
* in the end anyway */
|
|
if (mask != COGL_BLEND_STRING_CHANNEL_MASK_ALPHA)
|
|
arg->swizzle = ".a";
|
|
break;
|
|
case GL_ONE_MINUS_SRC_ALPHA:
|
|
g_string_append_printf (priv->source,
|
|
"SUB tmp%d, one, ",
|
|
arg_index);
|
|
append_arg (priv->source, arg);
|
|
/* avoid a swizzle if we know RGB are going to be masked
|
|
* in the end anyway */
|
|
if (mask != COGL_BLEND_STRING_CHANNEL_MASK_ALPHA)
|
|
g_string_append_printf (priv->source, ".a;\n");
|
|
else
|
|
g_string_append_printf (priv->source, ";\n");
|
|
arg->type = COGL_MATERIAL_BACKEND_ARBFP_ARG_TYPE_SIMPLE;
|
|
arg->name = tmp_name[arg_index];
|
|
break;
|
|
default:
|
|
g_error ("Unknown texture combine operator %d", op);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
backend_arbfp_args_equal (CoglMaterialBackendARBfpArg *arg0,
|
|
CoglMaterialBackendARBfpArg *arg1)
|
|
{
|
|
if (arg0->type != arg1->type)
|
|
return FALSE;
|
|
|
|
if (arg0->name != arg1->name &&
|
|
strcmp (arg0->name, arg1->name) != 0)
|
|
return FALSE;
|
|
|
|
if (arg0->type == COGL_MATERIAL_BACKEND_ARBFP_ARG_TYPE_TEXTURE &&
|
|
arg0->texture_unit != arg1->texture_unit)
|
|
return FALSE;
|
|
/* Note we don't have to check the target; a texture unit can only
|
|
* have one target enabled at a time. */
|
|
|
|
if (arg0->type == COGL_MATERIAL_BACKEND_ARBFP_ARG_TYPE_CONSTANT &&
|
|
arg0->constant_id != arg0->constant_id)
|
|
return FALSE;
|
|
|
|
if (arg0->swizzle != arg1->swizzle &&
|
|
strcmp (arg0->swizzle, arg1->swizzle) != 0)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
append_function (CoglMaterial *material,
|
|
CoglBlendStringChannelMask mask,
|
|
GLint function,
|
|
CoglMaterialBackendARBfpArg *args,
|
|
int n_args)
|
|
{
|
|
CoglMaterial *arbfp_authority = get_arbfp_authority (material);
|
|
CoglMaterialBackendARBfpPrivate *priv =
|
|
arbfp_authority->backend_privs[COGL_MATERIAL_BACKEND_ARBFP];
|
|
const char *mask_name;
|
|
|
|
switch (mask)
|
|
{
|
|
case COGL_BLEND_STRING_CHANNEL_MASK_RGB:
|
|
mask_name = ".rgb";
|
|
break;
|
|
case COGL_BLEND_STRING_CHANNEL_MASK_ALPHA:
|
|
mask_name = ".a";
|
|
break;
|
|
case COGL_BLEND_STRING_CHANNEL_MASK_RGBA:
|
|
mask_name = "";
|
|
break;
|
|
default:
|
|
g_error ("Unknown channel mask %d", mask);
|
|
mask_name = "";
|
|
}
|
|
|
|
switch (function)
|
|
{
|
|
case GL_ADD:
|
|
g_string_append_printf (priv->source, "ADD_SAT output%s, ",
|
|
mask_name);
|
|
break;
|
|
case GL_MODULATE:
|
|
/* Note: no need to saturate since we can assume operands
|
|
* have values in the range [0,1] */
|
|
g_string_append_printf (priv->source, "MUL output%s, ",
|
|
mask_name);
|
|
break;
|
|
case GL_REPLACE:
|
|
/* Note: no need to saturate since we can assume operand
|
|
* has a value in the range [0,1] */
|
|
g_string_append_printf (priv->source, "MOV output%s, ",
|
|
mask_name);
|
|
break;
|
|
case GL_SUBTRACT:
|
|
g_string_append_printf (priv->source, "SUB_SAT output%s, ",
|
|
mask_name);
|
|
break;
|
|
case GL_ADD_SIGNED:
|
|
g_string_append_printf (priv->source, "ADD tmp3%s, ",
|
|
mask_name);
|
|
append_arg (priv->source, &args[0]);
|
|
g_string_append (priv->source, ", ");
|
|
append_arg (priv->source, &args[1]);
|
|
g_string_append (priv->source, ";\n");
|
|
g_string_append_printf (priv->source, "SUB_SAT output%s, tmp3, half",
|
|
mask_name);
|
|
n_args = 0;
|
|
break;
|
|
case GL_DOT3_RGB:
|
|
/* These functions are the same except that GL_DOT3_RGB never
|
|
* updates the alpha channel.
|
|
*
|
|
* NB: GL_DOT3_RGBA is a bit special because it effectively forces
|
|
* an RGBA mask and we end up ignoring any separate alpha channel
|
|
* function.
|
|
*/
|
|
case GL_DOT3_RGBA:
|
|
{
|
|
const char *tmp4 = "tmp4";
|
|
|
|
/* The maths for this was taken from Mesa;
|
|
* apparently:
|
|
*
|
|
* tmp3 = 2*src0 - 1
|
|
* tmp4 = 2*src1 - 1
|
|
* output = DP3 (tmp3, tmp4)
|
|
*
|
|
* is the same as:
|
|
*
|
|
* output = 4 * DP3 (src0 - 0.5, src1 - 0.5)
|
|
*/
|
|
|
|
g_string_append (priv->source, "MAD tmp3, two, ");
|
|
append_arg (priv->source, &args[0]);
|
|
g_string_append (priv->source, ", minus_one;\n");
|
|
|
|
if (!backend_arbfp_args_equal (&args[0], &args[1]))
|
|
{
|
|
g_string_append (priv->source, "MAD tmp4, two, ");
|
|
append_arg (priv->source, &args[1]);
|
|
g_string_append (priv->source, ", minus_one;\n");
|
|
}
|
|
else
|
|
tmp4 = "tmp3";
|
|
|
|
g_string_append_printf (priv->source,
|
|
"DP3_SAT output%s, tmp3, %s",
|
|
mask_name, tmp4);
|
|
n_args = 0;
|
|
}
|
|
break;
|
|
case GL_INTERPOLATE:
|
|
/* Note: no need to saturate since we can assume operands
|
|
* have values in the range [0,1] */
|
|
|
|
/* NB: GL_INTERPOLATE = arg0*arg2 + arg1*(1-arg2)
|
|
* but LRP dst, a, b, c = b*a + c*(1-a) */
|
|
g_string_append_printf (priv->source, "LRP output%s, ",
|
|
mask_name);
|
|
append_arg (priv->source, &args[2]);
|
|
g_string_append (priv->source, ", ");
|
|
append_arg (priv->source, &args[0]);
|
|
g_string_append (priv->source, ", ");
|
|
append_arg (priv->source, &args[1]);
|
|
n_args = 0;
|
|
break;
|
|
default:
|
|
g_error ("Unknown texture combine function %d", function);
|
|
g_string_append_printf (priv->source, "MUL_SAT output%s, ",
|
|
mask_name);
|
|
n_args = 2;
|
|
break;
|
|
}
|
|
|
|
if (n_args > 0)
|
|
append_arg (priv->source, &args[0]);
|
|
if (n_args > 1)
|
|
{
|
|
g_string_append (priv->source, ", ");
|
|
append_arg (priv->source, &args[1]);
|
|
}
|
|
g_string_append (priv->source, ";\n");
|
|
}
|
|
|
|
static void
|
|
append_masked_combine (CoglMaterial *arbfp_authority,
|
|
CoglMaterialLayer *layer,
|
|
CoglBlendStringChannelMask mask,
|
|
GLint function,
|
|
GLint *src,
|
|
GLint *op)
|
|
{
|
|
int i;
|
|
int n_args;
|
|
CoglMaterialBackendARBfpArg args[3];
|
|
|
|
n_args = get_n_args_for_combine_func (function);
|
|
|
|
for (i = 0; i < n_args; i++)
|
|
{
|
|
setup_arg (arbfp_authority,
|
|
layer,
|
|
mask,
|
|
i,
|
|
src[i],
|
|
op[i],
|
|
&args[i]);
|
|
}
|
|
|
|
append_function (arbfp_authority,
|
|
mask,
|
|
function,
|
|
args,
|
|
n_args);
|
|
}
|
|
|
|
static gboolean
|
|
_cogl_material_backend_arbfp_add_layer (CoglMaterial *material,
|
|
CoglMaterialLayer *layer,
|
|
unsigned long layers_difference)
|
|
{
|
|
CoglMaterial *arbfp_authority = get_arbfp_authority (material);
|
|
CoglMaterialBackendARBfpPrivate *priv =
|
|
arbfp_authority->backend_privs[COGL_MATERIAL_BACKEND_ARBFP];
|
|
CoglMaterialLayer *combine_authority =
|
|
_cogl_material_layer_get_authority (layer,
|
|
COGL_MATERIAL_LAYER_STATE_COMBINE);
|
|
CoglMaterialLayerBigState *big_state = combine_authority->big_state;
|
|
|
|
/* Notes...
|
|
*
|
|
* We are ignoring the issue of texture indirection limits until
|
|
* someone complains (Ref Section 3.11.6 in the ARB_fragment_program
|
|
* spec)
|
|
*
|
|
* There always five TEMPs named tmp0, tmp1 and tmp2, tmp3 and tmp4
|
|
* available and these constants: 'one' = {1, 1, 1, 1}, 'half'
|
|
* {.5, .5, .5, .5}, 'two' = {2, 2, 2, 2}, 'minus_one' = {-1, -1,
|
|
* -1, -1}
|
|
*
|
|
* tmp0-2 are intended for dealing with some of the texture combine
|
|
* operands (e.g. GL_ONE_MINUS_SRC_COLOR) tmp3/4 are for dealing
|
|
* with the GL_ADD_SIGNED texture combine and the GL_DOT3_RGB[A]
|
|
* functions.
|
|
*
|
|
* Each layer outputs to the TEMP called "output", and reads from
|
|
* output if it needs to refer to GL_PREVIOUS. (we detect if we are
|
|
* layer0 so we will read fragment.color for GL_PREVIOUS in that
|
|
* case)
|
|
*
|
|
* We aim to do all the channels together if the same function is
|
|
* used for RGB as for A.
|
|
*
|
|
* We aim to avoid string duplication / allocations during codegen.
|
|
*
|
|
* We are careful to only saturate when writing to output.
|
|
*/
|
|
|
|
if (!priv->source)
|
|
return TRUE;
|
|
|
|
if (!need_texture_combine_separate (combine_authority))
|
|
{
|
|
append_masked_combine (material,
|
|
layer,
|
|
COGL_BLEND_STRING_CHANNEL_MASK_RGBA,
|
|
big_state->texture_combine_rgb_func,
|
|
big_state->texture_combine_rgb_src,
|
|
big_state->texture_combine_rgb_op);
|
|
}
|
|
else if (big_state->texture_combine_rgb_func == GL_DOT3_RGBA)
|
|
{
|
|
/* GL_DOT3_RGBA Is a bit weird as a GL_COMBINE_RGB function
|
|
* since if you use it, it overrides your ALPHA function...
|
|
*/
|
|
append_masked_combine (material,
|
|
layer,
|
|
COGL_BLEND_STRING_CHANNEL_MASK_RGBA,
|
|
big_state->texture_combine_rgb_func,
|
|
big_state->texture_combine_rgb_src,
|
|
big_state->texture_combine_rgb_op);
|
|
}
|
|
else
|
|
{
|
|
append_masked_combine (material,
|
|
layer,
|
|
COGL_BLEND_STRING_CHANNEL_MASK_RGB,
|
|
big_state->texture_combine_rgb_func,
|
|
big_state->texture_combine_rgb_src,
|
|
big_state->texture_combine_rgb_op);
|
|
append_masked_combine (material,
|
|
layer,
|
|
COGL_BLEND_STRING_CHANNEL_MASK_ALPHA,
|
|
big_state->texture_combine_alpha_func,
|
|
big_state->texture_combine_alpha_src,
|
|
big_state->texture_combine_alpha_op);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
_cogl_material_backend_arbfp_passthrough (CoglMaterial *material)
|
|
{
|
|
CoglMaterial *arbfp_authority = get_arbfp_authority (material);
|
|
CoglMaterialBackendARBfpPrivate *priv =
|
|
arbfp_authority->backend_privs[COGL_MATERIAL_BACKEND_ARBFP];
|
|
|
|
if (!priv->source)
|
|
return TRUE;
|
|
|
|
g_string_append (priv->source, "MOV output, fragment.color.primary;\n");
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
_cogl_material_backend_arbfp_end (CoglMaterial *material,
|
|
unsigned long materials_difference)
|
|
{
|
|
CoglMaterial *arbfp_authority = get_arbfp_authority (material);
|
|
CoglMaterialBackendARBfpPrivate *priv =
|
|
arbfp_authority->backend_privs[COGL_MATERIAL_BACKEND_ARBFP];
|
|
|
|
_COGL_GET_CONTEXT (ctx, FALSE);
|
|
|
|
if (priv->source)
|
|
{
|
|
GLenum gl_error;
|
|
COGL_STATIC_COUNTER (backend_arbfp_compile_counter,
|
|
"arbfp compile counter",
|
|
"Increments each time a new ARBfp "
|
|
"program is compiled",
|
|
0 /* no application private data */);
|
|
|
|
COGL_COUNTER_INC (_cogl_uprof_context, backend_arbfp_compile_counter);
|
|
|
|
g_string_append (priv->source, "MOV result.color,output;\n");
|
|
g_string_append (priv->source, "END\n");
|
|
|
|
if (G_UNLIKELY (cogl_debug_flags & COGL_DEBUG_SHOW_SOURCE))
|
|
g_message ("material program:\n%s", priv->source->str);
|
|
|
|
GE (glGenPrograms (1, &priv->gl_program));
|
|
|
|
GE (glBindProgram (GL_FRAGMENT_PROGRAM_ARB, priv->gl_program));
|
|
|
|
while ((gl_error = glGetError ()) != GL_NO_ERROR)
|
|
;
|
|
glProgramString (GL_FRAGMENT_PROGRAM_ARB,
|
|
GL_PROGRAM_FORMAT_ASCII_ARB,
|
|
priv->source->len,
|
|
priv->source->str);
|
|
if (glGetError () != GL_NO_ERROR)
|
|
{
|
|
g_warning ("\n%s\n%s",
|
|
priv->source->str,
|
|
glGetString (GL_PROGRAM_ERROR_STRING_ARB));
|
|
}
|
|
|
|
priv->source = NULL;
|
|
|
|
g_free (priv->sampled);
|
|
priv->sampled = NULL;
|
|
}
|
|
else
|
|
GE (glBindProgram (GL_FRAGMENT_PROGRAM_ARB, priv->gl_program));
|
|
|
|
use_program (COGL_INVALID_HANDLE, COGL_MATERIAL_PROGRAM_TYPE_ARBFP);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
_cogl_material_backend_arbfp_material_pre_change_notify (
|
|
CoglMaterial *material,
|
|
CoglMaterialState change,
|
|
const CoglColor *new_color)
|
|
{
|
|
CoglMaterialBackendARBfpPrivate *priv =
|
|
material->backend_privs[COGL_MATERIAL_BACKEND_ARBFP];
|
|
static const unsigned long fragment_op_changes =
|
|
COGL_MATERIAL_STATE_LAYERS;
|
|
/* TODO: COGL_MATERIAL_STATE_FOG */
|
|
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
if (material->backend_priv_set_mask & COGL_MATERIAL_BACKEND_ARBFP_MASK &&
|
|
priv->gl_program &&
|
|
change & fragment_op_changes)
|
|
{
|
|
GE (glDeletePrograms (1, &priv->gl_program));
|
|
priv->gl_program = 0;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
invalidate_arbfp_authority_cache_cb (CoglMaterial *material,
|
|
void *user_data)
|
|
{
|
|
invalidate_arbfp_authority_cache (material);
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
_cogl_material_backend_arbfp_material_set_parent_notify (
|
|
CoglMaterial *material)
|
|
{
|
|
/* Any arbfp authority cache associated with this material or
|
|
* any of its descendants will now be invalid. */
|
|
invalidate_arbfp_authority_cache (material);
|
|
|
|
_cogl_material_foreach_child (material,
|
|
invalidate_arbfp_authority_cache_cb,
|
|
NULL);
|
|
}
|
|
|
|
static void
|
|
_cogl_material_backend_arbfp_layer_pre_change_notify (
|
|
CoglMaterialLayer *layer,
|
|
CoglMaterialLayerState changes)
|
|
{
|
|
/* TODO: we could be saving snippets of texture combine code along
|
|
* with each layer and then when a layer changes we would just free
|
|
* the snippet. */
|
|
return;
|
|
}
|
|
|
|
static void
|
|
_cogl_material_backend_arbfp_free_priv (CoglMaterial *material)
|
|
{
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
if (material->backend_priv_set_mask & COGL_MATERIAL_BACKEND_ARBFP_MASK)
|
|
{
|
|
CoglMaterialBackendARBfpPrivate *priv =
|
|
material->backend_privs[COGL_MATERIAL_BACKEND_ARBFP];
|
|
|
|
glDeletePrograms (1, &priv->gl_program);
|
|
if (priv->sampled)
|
|
g_free (priv->sampled);
|
|
g_slice_free (CoglMaterialBackendARBfpPrivate, priv);
|
|
material->backend_priv_set_mask &= ~COGL_MATERIAL_BACKEND_ARBFP_MASK;
|
|
}
|
|
}
|
|
|
|
static const CoglMaterialBackend _cogl_material_arbfp_backend =
|
|
{
|
|
_cogl_material_backend_arbfp_get_max_texture_units,
|
|
_cogl_material_backend_arbfp_start,
|
|
_cogl_material_backend_arbfp_add_layer,
|
|
_cogl_material_backend_arbfp_passthrough,
|
|
_cogl_material_backend_arbfp_end,
|
|
_cogl_material_backend_arbfp_material_pre_change_notify,
|
|
_cogl_material_backend_arbfp_material_set_parent_notify,
|
|
_cogl_material_backend_arbfp_layer_pre_change_notify,
|
|
_cogl_material_backend_arbfp_free_priv,
|
|
NULL
|
|
};
|
|
|
|
#endif /* COGL_MATERIAL_BACKEND_ARBFP */
|
|
|
|
static int
|
|
_cogl_material_backend_fixed_get_max_texture_units (void)
|
|
{
|
|
_COGL_GET_CONTEXT (ctx, 0);
|
|
|
|
/* This function is called quite often so we cache the value to
|
|
avoid too many GL calls */
|
|
if (ctx->max_texture_units == -1)
|
|
{
|
|
ctx->max_texture_units = 1;
|
|
GE (glGetIntegerv (GL_MAX_TEXTURE_UNITS,
|
|
&ctx->max_texture_units));
|
|
}
|
|
|
|
return ctx->max_texture_units;
|
|
}
|
|
|
|
static gboolean
|
|
_cogl_material_backend_fixed_start (CoglMaterial *material,
|
|
int n_layers,
|
|
unsigned long materials_difference)
|
|
{
|
|
use_program (COGL_INVALID_HANDLE, COGL_MATERIAL_PROGRAM_TYPE_FIXED);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
_cogl_material_backend_fixed_add_layer (CoglMaterial *material,
|
|
CoglMaterialLayer *layer,
|
|
unsigned long layers_difference)
|
|
{
|
|
CoglTextureUnit *unit =
|
|
_cogl_get_texture_unit (_cogl_material_layer_get_unit_index (layer));
|
|
int unit_index = unit->index;
|
|
int n_rgb_func_args;
|
|
int n_alpha_func_args;
|
|
|
|
_COGL_GET_CONTEXT (ctx, FALSE);
|
|
|
|
/* XXX: Beware that since we are changing the active texture unit we
|
|
* must make sure we don't call into other Cogl components that may
|
|
* temporarily bind texture objects to query/modify parameters since
|
|
* they will end up binding texture unit 1. See
|
|
* _cogl_bind_gl_texture_transient for more details.
|
|
*/
|
|
set_active_texture_unit (unit_index);
|
|
|
|
if (layers_difference & COGL_MATERIAL_LAYER_STATE_COMBINE)
|
|
{
|
|
CoglMaterialLayer *authority =
|
|
_cogl_material_layer_get_authority (layer,
|
|
COGL_MATERIAL_LAYER_STATE_COMBINE);
|
|
CoglMaterialLayerBigState *big_state = authority->big_state;
|
|
|
|
GE (glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE));
|
|
|
|
/* Set the combiner functions... */
|
|
GE (glTexEnvi (GL_TEXTURE_ENV,
|
|
GL_COMBINE_RGB,
|
|
big_state->texture_combine_rgb_func));
|
|
GE (glTexEnvi (GL_TEXTURE_ENV,
|
|
GL_COMBINE_ALPHA,
|
|
big_state->texture_combine_alpha_func));
|
|
|
|
/*
|
|
* Setup the function arguments...
|
|
*/
|
|
|
|
/* For the RGB components... */
|
|
n_rgb_func_args =
|
|
get_n_args_for_combine_func (big_state->texture_combine_rgb_func);
|
|
|
|
GE (glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_RGB,
|
|
big_state->texture_combine_rgb_src[0]));
|
|
GE (glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_RGB,
|
|
big_state->texture_combine_rgb_op[0]));
|
|
if (n_rgb_func_args > 1)
|
|
{
|
|
GE (glTexEnvi (GL_TEXTURE_ENV, GL_SRC1_RGB,
|
|
big_state->texture_combine_rgb_src[1]));
|
|
GE (glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND1_RGB,
|
|
big_state->texture_combine_rgb_op[1]));
|
|
}
|
|
if (n_rgb_func_args > 2)
|
|
{
|
|
GE (glTexEnvi (GL_TEXTURE_ENV, GL_SRC2_RGB,
|
|
big_state->texture_combine_rgb_src[2]));
|
|
GE (glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND2_RGB,
|
|
big_state->texture_combine_rgb_op[2]));
|
|
}
|
|
|
|
/* For the Alpha component */
|
|
n_alpha_func_args =
|
|
get_n_args_for_combine_func (big_state->texture_combine_alpha_func);
|
|
|
|
GE (glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_ALPHA,
|
|
big_state->texture_combine_alpha_src[0]));
|
|
GE (glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_ALPHA,
|
|
big_state->texture_combine_alpha_op[0]));
|
|
if (n_alpha_func_args > 1)
|
|
{
|
|
GE (glTexEnvi (GL_TEXTURE_ENV, GL_SRC1_ALPHA,
|
|
big_state->texture_combine_alpha_src[1]));
|
|
GE (glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND1_ALPHA,
|
|
big_state->texture_combine_alpha_op[1]));
|
|
}
|
|
if (n_alpha_func_args > 2)
|
|
{
|
|
GE (glTexEnvi (GL_TEXTURE_ENV, GL_SRC2_ALPHA,
|
|
big_state->texture_combine_alpha_src[2]));
|
|
GE (glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND2_ALPHA,
|
|
big_state->texture_combine_alpha_op[2]));
|
|
}
|
|
}
|
|
|
|
if (layers_difference & COGL_MATERIAL_LAYER_STATE_COMBINE)
|
|
{
|
|
CoglMaterialLayer *authority =
|
|
_cogl_material_layer_get_authority (layer,
|
|
COGL_MATERIAL_LAYER_STATE_COMBINE);
|
|
CoglMaterialLayerBigState *big_state = authority->big_state;
|
|
|
|
GE (glTexEnvfv (GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR,
|
|
big_state->texture_combine_constant));
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
_cogl_material_backend_fixed_end (CoglMaterial *material,
|
|
unsigned long materials_difference)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
static const CoglMaterialBackend _cogl_material_fixed_backend =
|
|
{
|
|
_cogl_material_backend_fixed_get_max_texture_units,
|
|
_cogl_material_backend_fixed_start,
|
|
_cogl_material_backend_fixed_add_layer,
|
|
NULL, /* passthrough */
|
|
_cogl_material_backend_fixed_end,
|
|
NULL, /* material_change_notify */
|
|
NULL, /* material_set_parent_notify */
|
|
NULL, /* layer_change_notify */
|
|
NULL /* free_priv */
|
|
};
|
|
|
|
static void
|
|
_cogl_material_layer_get_texture_info (CoglMaterialLayer *layer,
|
|
CoglHandle *texture,
|
|
GLuint *gl_texture,
|
|
GLuint *gl_target)
|
|
{
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
*texture = layer->texture;
|
|
if (G_UNLIKELY (*texture == COGL_INVALID_HANDLE))
|
|
*texture = ctx->default_gl_texture_2d_tex;
|
|
cogl_texture_get_gl_texture (*texture, gl_texture, gl_target);
|
|
return;
|
|
}
|
|
|
|
#ifndef HAVE_COGL_GLES
|
|
|
|
static gboolean
|
|
blend_factor_uses_constant (GLenum blend_factor)
|
|
{
|
|
return (blend_factor == GL_CONSTANT_COLOR ||
|
|
blend_factor == GL_ONE_MINUS_CONSTANT_COLOR ||
|
|
blend_factor == GL_CONSTANT_ALPHA ||
|
|
blend_factor == GL_ONE_MINUS_CONSTANT_ALPHA);
|
|
}
|
|
|
|
#endif
|
|
|
|
static void
|
|
flush_depth_state (CoglMaterialDepthState *depth_state)
|
|
{
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
if (ctx->depth_test_function_cache != depth_state->depth_test_function)
|
|
{
|
|
GE (glDepthFunc (depth_state->depth_test_function));
|
|
ctx->depth_test_function_cache = depth_state->depth_test_function;
|
|
}
|
|
|
|
if (ctx->depth_writing_enabled_cache != depth_state->depth_writing_enabled)
|
|
{
|
|
GE (glDepthMask (depth_state->depth_writing_enabled ?
|
|
GL_TRUE : GL_FALSE));
|
|
ctx->depth_writing_enabled_cache = depth_state->depth_writing_enabled;
|
|
}
|
|
|
|
#ifndef COGL_HAS_GLES
|
|
if (ctx->depth_range_near_cache != depth_state->depth_range_near ||
|
|
ctx->depth_range_far_cache != depth_state->depth_range_far)
|
|
{
|
|
#ifdef COGL_HAS_GLES2
|
|
GE (glDepthRangef (depth_state->depth_range_near,
|
|
depth_state->depth_range_far));
|
|
#else
|
|
GE (glDepthRange (depth_state->depth_range_near,
|
|
depth_state->depth_range_far));
|
|
#endif
|
|
ctx->depth_range_near_cache = depth_state->depth_range_near;
|
|
ctx->depth_range_far_cache = depth_state->depth_range_far;
|
|
}
|
|
#endif /* COGL_HAS_GLES */
|
|
}
|
|
|
|
static void
|
|
_cogl_material_flush_color_blend_alpha_depth_state (
|
|
CoglMaterial *material,
|
|
unsigned long materials_difference,
|
|
gboolean skip_gl_color)
|
|
{
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
if (!skip_gl_color)
|
|
{
|
|
if ((materials_difference & COGL_MATERIAL_STATE_COLOR) ||
|
|
/* Assume if we were previously told to skip the color, then
|
|
* the current color needs updating... */
|
|
ctx->current_material_skip_gl_color)
|
|
{
|
|
CoglMaterial *authority =
|
|
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_COLOR);
|
|
GE (glColor4ub (cogl_color_get_red_byte (&authority->color),
|
|
cogl_color_get_green_byte (&authority->color),
|
|
cogl_color_get_blue_byte (&authority->color),
|
|
cogl_color_get_alpha_byte (&authority->color)));
|
|
}
|
|
}
|
|
|
|
if (materials_difference & COGL_MATERIAL_STATE_LIGHTING)
|
|
{
|
|
CoglMaterial *authority =
|
|
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_LIGHTING);
|
|
CoglMaterialLightingState *lighting_state =
|
|
&authority->big_state->lighting_state;
|
|
|
|
/* FIXME - we only need to set these if lighting is enabled... */
|
|
GLfloat shininess = lighting_state->shininess * 128.0f;
|
|
|
|
GE (glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT, lighting_state->ambient));
|
|
GE (glMaterialfv (GL_FRONT_AND_BACK, GL_DIFFUSE, lighting_state->diffuse));
|
|
GE (glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, lighting_state->specular));
|
|
GE (glMaterialfv (GL_FRONT_AND_BACK, GL_EMISSION, lighting_state->emission));
|
|
GE (glMaterialfv (GL_FRONT_AND_BACK, GL_SHININESS, &shininess));
|
|
}
|
|
|
|
if (materials_difference & COGL_MATERIAL_STATE_BLEND)
|
|
{
|
|
CoglMaterial *authority =
|
|
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_BLEND);
|
|
CoglMaterialBlendState *blend_state =
|
|
&authority->big_state->blend_state;
|
|
|
|
#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 (blend_factor_uses_constant (blend_state->blend_src_factor_rgb) ||
|
|
blend_factor_uses_constant (blend_state->blend_src_factor_alpha) ||
|
|
blend_factor_uses_constant (blend_state->blend_dst_factor_rgb) ||
|
|
blend_factor_uses_constant (blend_state->blend_dst_factor_alpha))
|
|
{
|
|
float red =
|
|
cogl_color_get_red_float (&blend_state->blend_constant);
|
|
float green =
|
|
cogl_color_get_green_float (&blend_state->blend_constant);
|
|
float blue =
|
|
cogl_color_get_blue_float (&blend_state->blend_constant);
|
|
float alpha =
|
|
cogl_color_get_alpha_float (&blend_state->blend_constant);
|
|
|
|
|
|
GE (glBlendColor (red, green, blue, alpha));
|
|
}
|
|
|
|
if (have_blend_equation_seperate &&
|
|
blend_state->blend_equation_rgb != blend_state->blend_equation_alpha)
|
|
GE (glBlendEquationSeparate (blend_state->blend_equation_rgb,
|
|
blend_state->blend_equation_alpha));
|
|
else
|
|
GE (glBlendEquation (blend_state->blend_equation_rgb));
|
|
|
|
if (have_blend_func_separate &&
|
|
(blend_state->blend_src_factor_rgb != blend_state->blend_src_factor_alpha ||
|
|
(blend_state->blend_src_factor_rgb !=
|
|
blend_state->blend_src_factor_alpha)))
|
|
GE (glBlendFuncSeparate (blend_state->blend_src_factor_rgb,
|
|
blend_state->blend_dst_factor_rgb,
|
|
blend_state->blend_src_factor_alpha,
|
|
blend_state->blend_dst_factor_alpha));
|
|
else
|
|
#endif
|
|
GE (glBlendFunc (blend_state->blend_src_factor_rgb,
|
|
blend_state->blend_dst_factor_rgb));
|
|
}
|
|
|
|
if (materials_difference & COGL_MATERIAL_STATE_ALPHA_FUNC)
|
|
{
|
|
CoglMaterial *authority =
|
|
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_ALPHA_FUNC);
|
|
CoglMaterialAlphaFuncState *alpha_state =
|
|
&authority->big_state->alpha_state;
|
|
|
|
/* NB: Currently the Cogl defines are compatible with the GL ones: */
|
|
GE (glAlphaFunc (alpha_state->alpha_func,
|
|
alpha_state->alpha_func_reference));
|
|
}
|
|
|
|
if (materials_difference & COGL_MATERIAL_STATE_DEPTH)
|
|
{
|
|
CoglMaterial *authority =
|
|
_cogl_material_get_authority (material, COGL_MATERIAL_STATE_DEPTH);
|
|
CoglMaterialDepthState *depth_state = &authority->big_state->depth_state;
|
|
|
|
if (depth_state->depth_test_enabled)
|
|
{
|
|
if (ctx->depth_test_enabled_cache != TRUE)
|
|
{
|
|
GE (glEnable (GL_DEPTH_TEST));
|
|
ctx->depth_test_enabled_cache = depth_state->depth_test_enabled;
|
|
}
|
|
flush_depth_state (depth_state);
|
|
}
|
|
else if (ctx->depth_test_enabled_cache != FALSE)
|
|
{
|
|
GE (glDisable (GL_DEPTH_TEST));
|
|
ctx->depth_test_enabled_cache = depth_state->depth_test_enabled;
|
|
}
|
|
}
|
|
|
|
if (material->real_blend_enable != ctx->gl_blend_enable_cache)
|
|
{
|
|
if (material->real_blend_enable)
|
|
GE (glEnable (GL_BLEND));
|
|
else
|
|
GE (glDisable (GL_BLEND));
|
|
/* XXX: we shouldn't update any other blend state if blending
|
|
* is disabled! */
|
|
ctx->gl_blend_enable_cache = material->real_blend_enable;
|
|
}
|
|
}
|
|
|
|
static int
|
|
get_max_activateable_texture_units (void)
|
|
{
|
|
_COGL_GET_CONTEXT (ctx, 0);
|
|
|
|
if (G_UNLIKELY (ctx->max_activateable_texture_units == -1))
|
|
{
|
|
#ifdef HAVE_COGL_GL
|
|
GLint max_tex_coords;
|
|
GLint max_combined_tex_units;
|
|
GE (glGetIntegerv (GL_MAX_TEXTURE_COORDS, &max_tex_coords));
|
|
GE (glGetIntegerv (GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS,
|
|
&max_combined_tex_units));
|
|
ctx->max_activateable_texture_units =
|
|
MAX (max_tex_coords - 1, max_combined_tex_units);
|
|
#else
|
|
GE (glGetIntegerv (GL_MAX_TEXTURE_UNITS,
|
|
&ctx->max_activateable_texture_units));
|
|
#endif
|
|
}
|
|
|
|
return ctx->max_activateable_texture_units;
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
int i;
|
|
unsigned long *layer_differences;
|
|
} CoglMaterialFlushLayerState;
|
|
|
|
static gboolean
|
|
flush_layers_common_gl_state_cb (CoglMaterialLayer *layer, void *user_data)
|
|
{
|
|
CoglMaterialFlushLayerState *flush_state = user_data;
|
|
int unit_index = flush_state->i;
|
|
CoglTextureUnit *unit = _cogl_get_texture_unit (unit_index);
|
|
unsigned long layers_difference =
|
|
flush_state->layer_differences[unit_index];
|
|
|
|
/* There may not be enough texture units so we can bail out if
|
|
* that's the case...
|
|
*/
|
|
if (G_UNLIKELY (unit_index >= get_max_activateable_texture_units ()))
|
|
{
|
|
static gboolean shown_warning = FALSE;
|
|
|
|
if (!shown_warning)
|
|
{
|
|
g_warning ("Your hardware does not have enough texture units"
|
|
"to handle this many texture layers");
|
|
shown_warning = TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
if (layers_difference & COGL_MATERIAL_LAYER_STATE_TEXTURE)
|
|
{
|
|
CoglMaterialLayer *authority =
|
|
_cogl_material_layer_get_authority (layer,
|
|
COGL_MATERIAL_LAYER_STATE_TEXTURE);
|
|
CoglHandle texture;
|
|
GLuint gl_texture;
|
|
GLenum gl_target;
|
|
|
|
_cogl_material_layer_get_texture_info (authority,
|
|
&texture,
|
|
&gl_texture,
|
|
&gl_target);
|
|
|
|
set_active_texture_unit (unit_index);
|
|
|
|
/* NB: There are several Cogl components and some code in
|
|
* Clutter that will temporarily bind arbitrary GL textures to
|
|
* query and modify texture object parameters. If you look at
|
|
* _cogl_bind_gl_texture_transient() you can see we make sure
|
|
* that such code always binds to texture unit 1 which means we
|
|
* can't rely on the unit->gl_texture state if unit->index == 1.
|
|
*
|
|
* Because texture unit 1 is a bit special we actually defer any
|
|
* necessary glBindTexture for it until the end of
|
|
* _cogl_material_flush_gl_state().
|
|
*
|
|
* NB: we get notified whenever glDeleteTextures is used (see
|
|
* _cogl_delete_gl_texture()) where we invalidate
|
|
* unit->gl_texture references to deleted textures so it's safe
|
|
* to compare unit->gl_texture with gl_texture. (Without the
|
|
* hook it would be possible to delete a GL texture and create a
|
|
* new one with the same name and comparing unit->gl_texture and
|
|
* gl_texture wouldn't detect that.)
|
|
*
|
|
* NB: for foreign textures we don't know how the deletion of
|
|
* the GL texture objects correspond to the deletion of the
|
|
* CoglTextures so if there was previously a foreign texture
|
|
* associated with the texture unit then we can't assume that we
|
|
* aren't seeing a recycled texture name so we have to bind.
|
|
*/
|
|
if (unit->gl_texture != gl_texture || unit->is_foreign)
|
|
{
|
|
if (unit_index != 1)
|
|
GE (glBindTexture (gl_target, gl_texture));
|
|
unit->gl_texture = gl_texture;
|
|
}
|
|
unit->is_foreign = _cogl_texture_is_foreign (texture);
|
|
|
|
/* Disable the previous target if it was different and it's
|
|
* still enabled */
|
|
if (unit->enabled && unit->current_gl_target != gl_target)
|
|
GE (glDisable (unit->current_gl_target));
|
|
|
|
if (!G_UNLIKELY (cogl_debug_flags & COGL_DEBUG_DISABLE_TEXTURING) &&
|
|
(!unit->enabled || unit->current_gl_target != gl_target))
|
|
{
|
|
GE (glEnable (gl_target));
|
|
unit->enabled = TRUE;
|
|
unit->current_gl_target = gl_target;
|
|
}
|
|
|
|
/* The texture_storage_changed boolean indicates if the
|
|
* CoglTexture's underlying GL texture storage has changed since
|
|
* it was flushed to the texture unit. We've just flushed the
|
|
* latest state so we can reset this. */
|
|
unit->texture_storage_changed = FALSE;
|
|
}
|
|
else
|
|
{
|
|
/* Even though there may be no difference between the last flushed
|
|
* texture state and the current layers texture state it may be that the
|
|
* texture unit has been disabled for some time so we need to assert that
|
|
* it's enabled now.
|
|
*/
|
|
if (!G_UNLIKELY (cogl_debug_flags & COGL_DEBUG_DISABLE_TEXTURING) &&
|
|
!unit->enabled)
|
|
{
|
|
GE (glEnable (unit->current_gl_target));
|
|
unit->enabled = TRUE;
|
|
}
|
|
}
|
|
|
|
if (layers_difference & COGL_MATERIAL_LAYER_STATE_USER_MATRIX)
|
|
{
|
|
CoglMaterialLayerState state = COGL_MATERIAL_LAYER_STATE_USER_MATRIX;
|
|
CoglMaterialLayer *authority =
|
|
_cogl_material_layer_get_authority (layer, state);
|
|
|
|
_cogl_matrix_stack_set (unit->matrix_stack,
|
|
&authority->big_state->matrix);
|
|
|
|
_cogl_matrix_stack_flush_to_gl (unit->matrix_stack, COGL_MATRIX_TEXTURE);
|
|
}
|
|
|
|
cogl_handle_ref (layer);
|
|
if (unit->layer != COGL_INVALID_HANDLE)
|
|
cogl_handle_unref (unit->layer);
|
|
unit->layer = layer;
|
|
unit->layer_changes_since_flush = 0;
|
|
|
|
flush_state->i++;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
_cogl_material_flush_common_gl_state (CoglMaterial *material,
|
|
unsigned long materials_difference,
|
|
unsigned long *layer_differences,
|
|
gboolean skip_gl_color)
|
|
{
|
|
CoglMaterialFlushLayerState state;
|
|
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
_cogl_material_flush_color_blend_alpha_depth_state (material,
|
|
materials_difference,
|
|
skip_gl_color);
|
|
|
|
state.i = 0;
|
|
state.layer_differences = layer_differences;
|
|
_cogl_material_foreach_layer (material,
|
|
flush_layers_common_gl_state_cb,
|
|
&state);
|
|
|
|
/* Disable additional texture units that may have previously been in use.. */
|
|
for (; state.i < ctx->texture_units->len; state.i++)
|
|
disable_texture_unit (state.i);
|
|
}
|
|
|
|
/* Re-assert the layer's wrap modes on the given CoglTexture.
|
|
*
|
|
* Note: we don't simply forward the wrap modes to layer->texture
|
|
* since the actual texture being used may have been overridden.
|
|
*/
|
|
static void
|
|
_cogl_material_layer_forward_wrap_modes (CoglMaterialLayer *layer,
|
|
CoglHandle texture)
|
|
{
|
|
CoglMaterialWrapModeInternal wrap_mode_s, wrap_mode_t, wrap_mode_r;
|
|
GLenum gl_wrap_mode_s, gl_wrap_mode_t, gl_wrap_mode_r;
|
|
|
|
if (texture == COGL_INVALID_HANDLE)
|
|
return;
|
|
|
|
_cogl_material_layer_get_wrap_modes (layer,
|
|
&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_s == COGL_MATERIAL_WRAP_MODE_INTERNAL_AUTOMATIC)
|
|
gl_wrap_mode_s = GL_CLAMP_TO_EDGE;
|
|
else
|
|
gl_wrap_mode_s = wrap_mode_s;
|
|
|
|
if (wrap_mode_t == COGL_MATERIAL_WRAP_MODE_INTERNAL_AUTOMATIC)
|
|
gl_wrap_mode_t = GL_CLAMP_TO_EDGE;
|
|
else
|
|
gl_wrap_mode_t = wrap_mode_t;
|
|
|
|
if (wrap_mode_r == COGL_MATERIAL_WRAP_MODE_INTERNAL_AUTOMATIC)
|
|
gl_wrap_mode_r = GL_CLAMP_TO_EDGE;
|
|
else
|
|
gl_wrap_mode_r = wrap_mode_r;
|
|
|
|
_cogl_texture_set_wrap_mode_parameters (texture,
|
|
gl_wrap_mode_s,
|
|
gl_wrap_mode_t,
|
|
gl_wrap_mode_r);
|
|
}
|
|
|
|
/* OpenGL associates the min/mag filters and repeat modes with the
|
|
* texture object not the texture unit so we always have to re-assert
|
|
* the filter and repeat modes whenever we use a texture since it may
|
|
* be referenced by multiple materials with different modes.
|
|
*
|
|
* XXX: GL_ARB_sampler_objects fixes this in OpenGL so we should
|
|
* eventually look at using this extension when available.
|
|
*/
|
|
static void
|
|
foreach_texture_unit_update_filter_and_wrap_modes (void)
|
|
{
|
|
int i;
|
|
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
for (i = 0; i < ctx->texture_units->len; i++)
|
|
{
|
|
CoglTextureUnit *unit =
|
|
&g_array_index (ctx->texture_units, CoglTextureUnit, i);
|
|
|
|
if (!unit->enabled)
|
|
break;
|
|
|
|
if (unit->layer)
|
|
{
|
|
CoglHandle texture = _cogl_material_layer_get_texture (unit->layer);
|
|
CoglMaterialFilter min;
|
|
CoglMaterialFilter mag;
|
|
|
|
_cogl_material_layer_get_filters (unit->layer, &min, &mag);
|
|
_cogl_texture_set_filters (texture, min, mag);
|
|
|
|
_cogl_material_layer_forward_wrap_modes (unit->layer, texture);
|
|
}
|
|
}
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
int i;
|
|
unsigned long *layer_differences;
|
|
} CoglMaterialCompareLayersState;
|
|
|
|
static gboolean
|
|
compare_layer_differences_cb (CoglMaterialLayer *layer, void *user_data)
|
|
{
|
|
CoglMaterialCompareLayersState *state = user_data;
|
|
CoglTextureUnit *unit = _cogl_get_texture_unit (state->i);
|
|
|
|
if (unit->layer == layer)
|
|
state->layer_differences[state->i] = unit->layer_changes_since_flush;
|
|
else if (unit->layer)
|
|
{
|
|
state->layer_differences[state->i] = unit->layer_changes_since_flush;
|
|
state->layer_differences[state->i] |=
|
|
_cogl_material_layer_compare_differences (layer, unit->layer);
|
|
}
|
|
else
|
|
state->layer_differences[state->i] = COGL_MATERIAL_LAYER_STATE_ALL_SPARSE;
|
|
|
|
/* XXX: There is always a possibility that a CoglTexture's
|
|
* underlying GL texture storage has been changed since it was last
|
|
* bound to a texture unit which is why we have a callback into
|
|
* _cogl_material_texture_storage_change_notify whenever a textures
|
|
* underlying GL texture storage changes which will set the
|
|
* unit->texture_intern_changed flag. If we see that's been set here
|
|
* then we force an update of the texture state...
|
|
*/
|
|
if (unit->texture_storage_changed)
|
|
state->layer_differences[state->i] |= COGL_MATERIAL_LAYER_STATE_TEXTURE;
|
|
|
|
state->i++;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
const CoglMaterialBackend *backend;
|
|
CoglMaterial *material;
|
|
unsigned long *layer_differences;
|
|
gboolean error_adding_layer;
|
|
gboolean added_layer;
|
|
} CoglMaterialBackendAddLayerState;
|
|
|
|
|
|
static gboolean
|
|
backend_add_layer_cb (CoglMaterialLayer *layer,
|
|
void *user_data)
|
|
{
|
|
CoglMaterialBackendAddLayerState *state = user_data;
|
|
const CoglMaterialBackend *backend = state->backend;
|
|
CoglMaterial *material = state->material;
|
|
int unit_index = _cogl_material_layer_get_unit_index (layer);
|
|
CoglTextureUnit *unit = _cogl_get_texture_unit (unit_index);
|
|
|
|
_COGL_GET_CONTEXT (ctx, FALSE);
|
|
|
|
/* NB: We don't support the random disabling of texture
|
|
* units, so as soon as we hit a disabled unit we know all
|
|
* subsequent units are also disabled */
|
|
if (!unit->enabled)
|
|
return FALSE;
|
|
|
|
if (G_UNLIKELY (unit_index >= backend->get_max_texture_units ()))
|
|
{
|
|
int j;
|
|
for (j = unit_index; j < ctx->texture_units->len; j++)
|
|
disable_texture_unit (j);
|
|
/* TODO: although this isn't considered an error that
|
|
* warrants falling back to a different backend we
|
|
* should print a warning here. */
|
|
return FALSE;
|
|
}
|
|
|
|
/* Either generate per layer code snippets or setup the
|
|
* fixed function glTexEnv for each layer... */
|
|
if (G_LIKELY (backend->add_layer (material,
|
|
layer,
|
|
state->layer_differences[unit_index])))
|
|
state->added_layer = TRUE;
|
|
else
|
|
{
|
|
state->error_adding_layer = TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* _cogl_material_flush_gl_state:
|
|
*
|
|
* Details of override options:
|
|
* ->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 primitives 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.
|
|
*/
|
|
void
|
|
_cogl_material_flush_gl_state (CoglHandle handle,
|
|
gboolean skip_gl_color)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
unsigned long materials_difference;
|
|
int n_layers;
|
|
unsigned long *layer_differences;
|
|
int i;
|
|
CoglTextureUnit *unit1;
|
|
|
|
COGL_STATIC_TIMER (material_flush_timer,
|
|
"Mainloop", /* parent */
|
|
"Material Flush",
|
|
"The time spent flushing material state",
|
|
0 /* no application private data */);
|
|
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
COGL_TIMER_START (_cogl_uprof_context, material_flush_timer);
|
|
|
|
if (ctx->current_material == material)
|
|
materials_difference = ctx->current_material_changes_since_flush;
|
|
else if (ctx->current_material)
|
|
{
|
|
materials_difference = ctx->current_material_changes_since_flush;
|
|
materials_difference |=
|
|
_cogl_material_compare_differences (ctx->current_material,
|
|
material);
|
|
}
|
|
else
|
|
materials_difference = COGL_MATERIAL_STATE_ALL_SPARSE;
|
|
|
|
/* Get a layer_differences mask for each layer to be flushed */
|
|
n_layers = cogl_material_get_n_layers (material);
|
|
if (n_layers)
|
|
{
|
|
CoglMaterialCompareLayersState state;
|
|
layer_differences = g_alloca (sizeof (unsigned long *) * n_layers);
|
|
memset (layer_differences, 0, sizeof (layer_differences));
|
|
state.i = 0;
|
|
state.layer_differences = layer_differences;
|
|
_cogl_material_foreach_layer (material,
|
|
compare_layer_differences_cb,
|
|
&state);
|
|
}
|
|
|
|
/* First flush everything that's the same regardless of which
|
|
* material backend is being used...
|
|
*
|
|
* 1) top level state:
|
|
* glColor (or skip if a vertex attribute is being used for color)
|
|
* blend state
|
|
* alpha test state (except for GLES 2.0)
|
|
*
|
|
* 2) then foreach layer:
|
|
* determine gl_target/gl_texture
|
|
* bind texture
|
|
* enable/disable target
|
|
* flush user matrix
|
|
*
|
|
* Note: After _cogl_material_flush_common_gl_state you can expect
|
|
* all state of the layers corresponding texture unit to be
|
|
* updated.
|
|
*/
|
|
_cogl_material_flush_common_gl_state (material,
|
|
materials_difference,
|
|
layer_differences,
|
|
skip_gl_color);
|
|
|
|
/* Now flush the fragment processing state according to the current
|
|
* fragment processing backend.
|
|
*
|
|
* Note: Some of the backends may not support the current material
|
|
* configuration and in that case it will report an error and we
|
|
* will fallback to a different backend.
|
|
*
|
|
* NB: if material->backend != COGL_MATERIAL_BACKEND_UNDEFINED then
|
|
* we have previously managed to successfully flush this material
|
|
* with the given backend so we will simply use that to avoid
|
|
* fallback code paths.
|
|
*/
|
|
|
|
if (material->backend == COGL_MATERIAL_BACKEND_UNDEFINED)
|
|
_cogl_material_set_backend (material, COGL_MATERIAL_BACKEND_DEFAULT);
|
|
|
|
for (i = material->backend;
|
|
i < G_N_ELEMENTS (backends);
|
|
i++, _cogl_material_set_backend (material, i))
|
|
{
|
|
const CoglMaterialBackend *backend = backends[i];
|
|
CoglMaterialBackendAddLayerState state;
|
|
|
|
/* E.g. For backends generating code they can setup their
|
|
* scratch buffers here... */
|
|
if (G_UNLIKELY (!backend->start (material,
|
|
n_layers,
|
|
materials_difference)))
|
|
continue;
|
|
|
|
state.backend = backend;
|
|
state.material = material;
|
|
state.layer_differences = layer_differences;
|
|
state.error_adding_layer = FALSE;
|
|
state.added_layer = FALSE;
|
|
_cogl_material_foreach_layer (material,
|
|
backend_add_layer_cb,
|
|
&state);
|
|
|
|
if (G_UNLIKELY (state.error_adding_layer))
|
|
continue;
|
|
|
|
if (!state.added_layer &&
|
|
backend->passthrough &&
|
|
G_UNLIKELY (!backend->passthrough (material)))
|
|
continue;
|
|
|
|
/* For backends generating code they may compile and link their
|
|
* programs here, update any uniforms and tell OpenGL to use
|
|
* that program.
|
|
*/
|
|
if (G_UNLIKELY (!backend->end (material, materials_difference)))
|
|
continue;
|
|
|
|
break;
|
|
}
|
|
|
|
/* FIXME: This reference is actually resulting in lots of
|
|
* copy-on-write reparenting because one-shot materials end up
|
|
* living for longer than necessary and so any later modification of
|
|
* the parent will cause a copy-on-write.
|
|
*
|
|
* XXX: The issue should largely go away when we switch to using
|
|
* weak materials for overrides.
|
|
*/
|
|
cogl_handle_ref (handle);
|
|
if (ctx->current_material != COGL_INVALID_HANDLE)
|
|
cogl_handle_unref (ctx->current_material);
|
|
ctx->current_material = handle;
|
|
ctx->current_material_changes_since_flush = 0;
|
|
ctx->current_material_skip_gl_color = skip_gl_color;
|
|
|
|
/* Handle the fact that OpenGL associates texture filter and wrap
|
|
* modes with the texture objects not the texture units... */
|
|
foreach_texture_unit_update_filter_and_wrap_modes ();
|
|
|
|
/* If this material has more than one layer then we always need
|
|
* to make sure we rebind the texture for unit 1.
|
|
*
|
|
* NB: various components of Cogl may temporarily bind arbitrary
|
|
* textures to texture unit 1 so they can query and modify texture
|
|
* object parameters. cogl-material.c (See
|
|
* _cogl_bind_gl_texture_transient)
|
|
*/
|
|
unit1 = _cogl_get_texture_unit (1);
|
|
if (unit1->enabled && unit1->dirty_gl_texture)
|
|
{
|
|
set_active_texture_unit (1);
|
|
GE (glBindTexture (unit1->current_gl_target, unit1->gl_texture));
|
|
unit1->dirty_gl_texture = FALSE;
|
|
}
|
|
|
|
COGL_TIMER_STOP (_cogl_uprof_context, material_flush_timer);
|
|
}
|
|
|
|
/* 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 = COGL_MATERIAL (material_handle);
|
|
|
|
material->journal_ref_count++;
|
|
cogl_handle_ref (material_handle);
|
|
return material_handle;
|
|
}
|
|
|
|
void
|
|
_cogl_material_journal_unref (CoglHandle material_handle)
|
|
{
|
|
CoglMaterial *material =
|
|
_cogl_material_pointer_from_handle (material_handle);
|
|
material->journal_ref_count--;
|
|
cogl_handle_unref (material_handle);
|
|
}
|
|
|
|
void
|
|
_cogl_material_apply_legacy_state (CoglHandle handle)
|
|
{
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
/* It was a mistake that we ever copied the OpenGL style API for
|
|
* associating these things directly with the context when we
|
|
* originally wrote Cogl. Until the corresponding deprecated APIs
|
|
* can be removed though we now shoehorn the state changes through
|
|
* the cogl_material API instead.
|
|
*/
|
|
|
|
if (ctx->current_program)
|
|
_cogl_material_set_user_program (handle, ctx->current_program);
|
|
|
|
if (ctx->legacy_depth_test_enabled)
|
|
cogl_material_set_depth_test_enabled (handle, TRUE);
|
|
}
|
|
|
|
void
|
|
_cogl_material_set_static_breadcrumb (CoglHandle handle,
|
|
const char *breadcrumb)
|
|
{
|
|
CoglMaterial *material = COGL_MATERIAL (handle);
|
|
|
|
material->has_static_breadcrumb = TRUE;
|
|
material->static_breadcrumb = breadcrumb;
|
|
}
|
|
|