f5dac3a5d9
Group all the three config files from clutter/cogl/meta into one and also remove unnused configurations and replace duplicated ones This also fixes Cogl usage of HAS_X11/HAS_XLIB to match the expected build options Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3368>
656 lines
21 KiB
C
656 lines
21 KiB
C
/*
|
|
* Cogl
|
|
*
|
|
* A Low Level GPU Graphics and Utilities API
|
|
*
|
|
* Copyright (C) 2010 Intel Corporation.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person
|
|
* obtaining a copy of this software and associated documentation
|
|
* files (the "Software"), to deal in the Software without
|
|
* restriction, including without limitation the rights to use, copy,
|
|
* modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
* of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*
|
|
*
|
|
*
|
|
* Authors:
|
|
* Robert Bragg <robert@linux.intel.com>
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "cogl/cogl-util.h"
|
|
#include "cogl/cogl-context-private.h"
|
|
#include "cogl/cogl-journal-private.h"
|
|
#include "cogl/cogl-attribute.h"
|
|
#include "cogl/cogl-attribute-private.h"
|
|
#include "cogl/cogl-pipeline.h"
|
|
#include "cogl/cogl-pipeline-private.h"
|
|
#include "cogl/cogl-texture-private.h"
|
|
#include "cogl/cogl-framebuffer-private.h"
|
|
#include "cogl/cogl-indices-private.h"
|
|
#include "cogl/cogl-private.h"
|
|
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
G_DEFINE_TYPE (CoglAttribute, cogl_attribute, G_TYPE_OBJECT);
|
|
|
|
static void
|
|
cogl_attribute_dispose (GObject *object)
|
|
{
|
|
CoglAttribute *attribute = COGL_ATTRIBUTE (object);
|
|
|
|
if (attribute->is_buffered)
|
|
g_object_unref (attribute->d.buffered.attribute_buffer);
|
|
else
|
|
_cogl_boxed_value_destroy (&attribute->d.constant.boxed);
|
|
|
|
|
|
G_OBJECT_CLASS (cogl_attribute_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
cogl_attribute_init (CoglAttribute *attribute)
|
|
{
|
|
}
|
|
|
|
static void
|
|
cogl_attribute_class_init (CoglAttributeClass *class)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (class);
|
|
|
|
object_class->dispose = cogl_attribute_dispose;
|
|
}
|
|
|
|
static gboolean
|
|
validate_cogl_attribute_name (const char *name,
|
|
const char **real_attribute_name,
|
|
CoglAttributeNameID *name_id,
|
|
gboolean *normalized,
|
|
int *layer_number)
|
|
{
|
|
name = name + 5; /* skip "cogl_" */
|
|
|
|
*normalized = FALSE;
|
|
*layer_number = 0;
|
|
|
|
if (strcmp (name, "position_in") == 0)
|
|
*name_id = COGL_ATTRIBUTE_NAME_ID_POSITION_ARRAY;
|
|
else if (strcmp (name, "color_in") == 0)
|
|
{
|
|
*name_id = COGL_ATTRIBUTE_NAME_ID_COLOR_ARRAY;
|
|
*normalized = TRUE;
|
|
}
|
|
else if (strcmp (name, "tex_coord_in") == 0)
|
|
{
|
|
*real_attribute_name = "cogl_tex_coord0_in";
|
|
*name_id = COGL_ATTRIBUTE_NAME_ID_TEXTURE_COORD_ARRAY;
|
|
}
|
|
else if (strncmp (name, "tex_coord", strlen ("tex_coord")) == 0)
|
|
{
|
|
char *endptr;
|
|
*layer_number = strtoul (name + 9, &endptr, 10);
|
|
if (strcmp (endptr, "_in") != 0)
|
|
{
|
|
g_warning ("Texture coordinate attributes should either be named "
|
|
"\"cogl_tex_coord_in\" or named with a texture unit index "
|
|
"like \"cogl_tex_coord2_in\"\n");
|
|
return FALSE;
|
|
}
|
|
*name_id = COGL_ATTRIBUTE_NAME_ID_TEXTURE_COORD_ARRAY;
|
|
}
|
|
else if (strcmp (name, "normal_in") == 0)
|
|
{
|
|
*name_id = COGL_ATTRIBUTE_NAME_ID_NORMAL_ARRAY;
|
|
*normalized = TRUE;
|
|
}
|
|
else if (strcmp (name, "point_size_in") == 0)
|
|
*name_id = COGL_ATTRIBUTE_NAME_ID_POINT_SIZE_ARRAY;
|
|
else
|
|
{
|
|
g_warning ("Unknown cogl_* attribute name cogl_%s\n", name);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
CoglAttributeNameState *
|
|
_cogl_attribute_register_attribute_name (CoglContext *context,
|
|
const char *name)
|
|
{
|
|
CoglAttributeNameState *name_state = g_new (CoglAttributeNameState, 1);
|
|
int name_index = context->n_attribute_names++;
|
|
char *name_copy = g_strdup (name);
|
|
|
|
name_state->name = NULL;
|
|
name_state->name_index = name_index;
|
|
if (strncmp (name, "cogl_", 5) == 0)
|
|
{
|
|
if (!validate_cogl_attribute_name (name,
|
|
&name_state->name,
|
|
&name_state->name_id,
|
|
&name_state->normalized_default,
|
|
&name_state->layer_number))
|
|
goto error;
|
|
}
|
|
else
|
|
{
|
|
name_state->name_id = COGL_ATTRIBUTE_NAME_ID_CUSTOM_ARRAY;
|
|
name_state->normalized_default = FALSE;
|
|
name_state->layer_number = 0;
|
|
}
|
|
|
|
if (name_state->name == NULL)
|
|
name_state->name = name_copy;
|
|
|
|
g_hash_table_insert (context->attribute_name_states_hash,
|
|
name_copy, name_state);
|
|
|
|
if (G_UNLIKELY (context->attribute_name_index_map == NULL))
|
|
context->attribute_name_index_map =
|
|
g_array_new (FALSE, FALSE, sizeof (void *));
|
|
|
|
g_array_set_size (context->attribute_name_index_map, name_index + 1);
|
|
|
|
g_array_index (context->attribute_name_index_map,
|
|
CoglAttributeNameState *, name_index) = name_state;
|
|
|
|
return name_state;
|
|
|
|
error:
|
|
g_free (name_state);
|
|
return NULL;
|
|
}
|
|
|
|
static gboolean
|
|
validate_n_components (const CoglAttributeNameState *name_state,
|
|
int n_components)
|
|
{
|
|
switch (name_state->name_id)
|
|
{
|
|
case COGL_ATTRIBUTE_NAME_ID_POINT_SIZE_ARRAY:
|
|
if (G_UNLIKELY (n_components != 1))
|
|
{
|
|
g_critical ("The point size attribute can only have one "
|
|
"component");
|
|
return FALSE;
|
|
}
|
|
break;
|
|
case COGL_ATTRIBUTE_NAME_ID_POSITION_ARRAY:
|
|
case COGL_ATTRIBUTE_NAME_ID_COLOR_ARRAY:
|
|
case COGL_ATTRIBUTE_NAME_ID_TEXTURE_COORD_ARRAY:
|
|
case COGL_ATTRIBUTE_NAME_ID_NORMAL_ARRAY:
|
|
case COGL_ATTRIBUTE_NAME_ID_CUSTOM_ARRAY:
|
|
return TRUE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
CoglAttribute *
|
|
cogl_attribute_new (CoglAttributeBuffer *attribute_buffer,
|
|
const char *name,
|
|
size_t stride,
|
|
size_t offset,
|
|
int n_components,
|
|
CoglAttributeType type)
|
|
{
|
|
CoglAttribute *attribute = g_object_new (COGL_TYPE_ATTRIBUTE, NULL);
|
|
CoglBuffer *buffer = COGL_BUFFER (attribute_buffer);
|
|
CoglContext *ctx = buffer->context;
|
|
|
|
attribute->is_buffered = TRUE;
|
|
|
|
attribute->name_state =
|
|
g_hash_table_lookup (ctx->attribute_name_states_hash, name);
|
|
if (!attribute->name_state)
|
|
{
|
|
CoglAttributeNameState *name_state =
|
|
_cogl_attribute_register_attribute_name (ctx, name);
|
|
if (!name_state)
|
|
goto error;
|
|
attribute->name_state = name_state;
|
|
}
|
|
|
|
attribute->d.buffered.attribute_buffer = g_object_ref (attribute_buffer);
|
|
attribute->d.buffered.stride = stride;
|
|
attribute->d.buffered.offset = offset;
|
|
attribute->d.buffered.n_components = n_components;
|
|
attribute->d.buffered.type = type;
|
|
|
|
attribute->immutable_ref = 0;
|
|
|
|
if (attribute->name_state->name_id != COGL_ATTRIBUTE_NAME_ID_CUSTOM_ARRAY)
|
|
{
|
|
if (!validate_n_components (attribute->name_state, n_components))
|
|
return NULL;
|
|
attribute->normalized =
|
|
attribute->name_state->normalized_default;
|
|
}
|
|
else
|
|
attribute->normalized = FALSE;
|
|
|
|
return attribute;
|
|
|
|
error:
|
|
g_object_unref (attribute);
|
|
return NULL;
|
|
}
|
|
|
|
static CoglAttribute *
|
|
_cogl_attribute_new_const (CoglContext *context,
|
|
const char *name,
|
|
int n_components,
|
|
int n_columns,
|
|
gboolean transpose,
|
|
const float *value)
|
|
{
|
|
CoglAttribute *attribute = g_object_new (COGL_TYPE_ATTRIBUTE, NULL);
|
|
|
|
attribute->name_state =
|
|
g_hash_table_lookup (context->attribute_name_states_hash, name);
|
|
if (!attribute->name_state)
|
|
{
|
|
CoglAttributeNameState *name_state =
|
|
_cogl_attribute_register_attribute_name (context, name);
|
|
if (!name_state)
|
|
goto error;
|
|
attribute->name_state = name_state;
|
|
}
|
|
|
|
if (!validate_n_components (attribute->name_state, n_components))
|
|
goto error;
|
|
|
|
attribute->is_buffered = FALSE;
|
|
attribute->normalized = FALSE;
|
|
|
|
attribute->d.constant.context = g_object_ref (context);
|
|
|
|
attribute->d.constant.boxed.v.array = NULL;
|
|
|
|
if (n_columns == 1)
|
|
{
|
|
_cogl_boxed_value_set_float (&attribute->d.constant.boxed,
|
|
n_components,
|
|
1,
|
|
value);
|
|
}
|
|
else
|
|
{
|
|
/* FIXME: Up until GL[ES] 3 only square matrices were supported
|
|
* and we don't currently expose non-square matrices in Cogl.
|
|
*/
|
|
g_return_val_if_fail (n_columns == n_components, NULL);
|
|
_cogl_boxed_value_set_matrix (&attribute->d.constant.boxed,
|
|
n_columns,
|
|
1,
|
|
transpose,
|
|
value);
|
|
}
|
|
|
|
return attribute;
|
|
|
|
error:
|
|
g_object_unref (attribute);
|
|
return NULL;
|
|
}
|
|
|
|
CoglAttribute *
|
|
cogl_attribute_new_const_1f (CoglContext *context,
|
|
const char *name,
|
|
float value)
|
|
{
|
|
return _cogl_attribute_new_const (context,
|
|
name,
|
|
1, /* n_components */
|
|
1, /* 1 column vector */
|
|
FALSE, /* no transpose */
|
|
&value);
|
|
}
|
|
|
|
CoglAttribute *
|
|
cogl_attribute_new_const_2fv (CoglContext *context,
|
|
const char *name,
|
|
const float *value)
|
|
{
|
|
return _cogl_attribute_new_const (context,
|
|
name,
|
|
2, /* n_components */
|
|
1, /* 1 column vector */
|
|
FALSE, /* no transpose */
|
|
value);
|
|
}
|
|
|
|
CoglAttribute *
|
|
cogl_attribute_new_const_3fv (CoglContext *context,
|
|
const char *name,
|
|
const float *value)
|
|
{
|
|
return _cogl_attribute_new_const (context,
|
|
name,
|
|
3, /* n_components */
|
|
1, /* 1 column vector */
|
|
FALSE, /* no transpose */
|
|
value);
|
|
}
|
|
|
|
CoglAttribute *
|
|
cogl_attribute_new_const_4fv (CoglContext *context,
|
|
const char *name,
|
|
const float *value)
|
|
{
|
|
return _cogl_attribute_new_const (context,
|
|
name,
|
|
4, /* n_components */
|
|
1, /* 1 column vector */
|
|
FALSE, /* no transpose */
|
|
value);
|
|
}
|
|
|
|
CoglAttribute *
|
|
cogl_attribute_new_const_2f (CoglContext *context,
|
|
const char *name,
|
|
float component0,
|
|
float component1)
|
|
{
|
|
float vec2[2] = { component0, component1 };
|
|
return _cogl_attribute_new_const (context,
|
|
name,
|
|
2, /* n_components */
|
|
1, /* 1 column vector */
|
|
FALSE, /* no transpose */
|
|
vec2);
|
|
}
|
|
|
|
CoglAttribute *
|
|
cogl_attribute_new_const_3f (CoglContext *context,
|
|
const char *name,
|
|
float component0,
|
|
float component1,
|
|
float component2)
|
|
{
|
|
float vec3[3] = { component0, component1, component2 };
|
|
return _cogl_attribute_new_const (context,
|
|
name,
|
|
3, /* n_components */
|
|
1, /* 1 column vector */
|
|
FALSE, /* no transpose */
|
|
vec3);
|
|
}
|
|
|
|
CoglAttribute *
|
|
cogl_attribute_new_const_4f (CoglContext *context,
|
|
const char *name,
|
|
float component0,
|
|
float component1,
|
|
float component2,
|
|
float component3)
|
|
{
|
|
float vec4[4] = { component0, component1, component2, component3 };
|
|
return _cogl_attribute_new_const (context,
|
|
name,
|
|
4, /* n_components */
|
|
1, /* 1 column vector */
|
|
FALSE, /* no transpose */
|
|
vec4);
|
|
}
|
|
|
|
CoglAttribute *
|
|
cogl_attribute_new_const_2x2fv (CoglContext *context,
|
|
const char *name,
|
|
const float *matrix2x2,
|
|
gboolean transpose)
|
|
{
|
|
return _cogl_attribute_new_const (context,
|
|
name,
|
|
2, /* n_components */
|
|
2, /* 2 column vector */
|
|
FALSE, /* no transpose */
|
|
matrix2x2);
|
|
}
|
|
|
|
CoglAttribute *
|
|
cogl_attribute_new_const_3x3fv (CoglContext *context,
|
|
const char *name,
|
|
const float *matrix3x3,
|
|
gboolean transpose)
|
|
{
|
|
return _cogl_attribute_new_const (context,
|
|
name,
|
|
3, /* n_components */
|
|
3, /* 3 column vector */
|
|
FALSE, /* no transpose */
|
|
matrix3x3);
|
|
}
|
|
|
|
CoglAttribute *
|
|
cogl_attribute_new_const_4x4fv (CoglContext *context,
|
|
const char *name,
|
|
const float *matrix4x4,
|
|
gboolean transpose)
|
|
{
|
|
return _cogl_attribute_new_const (context,
|
|
name,
|
|
4, /* n_components */
|
|
4, /* 4 column vector */
|
|
FALSE, /* no transpose */
|
|
matrix4x4);
|
|
}
|
|
|
|
gboolean
|
|
cogl_attribute_get_normalized (CoglAttribute *attribute)
|
|
{
|
|
g_return_val_if_fail (COGL_IS_ATTRIBUTE (attribute), FALSE);
|
|
|
|
return attribute->normalized;
|
|
}
|
|
|
|
static void
|
|
warn_about_midscene_changes (void)
|
|
{
|
|
static gboolean seen = FALSE;
|
|
if (!seen)
|
|
{
|
|
g_warning ("Mid-scene modification of attributes has "
|
|
"undefined results\n");
|
|
seen = TRUE;
|
|
}
|
|
}
|
|
|
|
void
|
|
cogl_attribute_set_normalized (CoglAttribute *attribute,
|
|
gboolean normalized)
|
|
{
|
|
g_return_if_fail (COGL_IS_ATTRIBUTE (attribute));
|
|
|
|
if (G_UNLIKELY (attribute->immutable_ref))
|
|
warn_about_midscene_changes ();
|
|
|
|
attribute->normalized = normalized;
|
|
}
|
|
|
|
CoglAttributeBuffer *
|
|
cogl_attribute_get_buffer (CoglAttribute *attribute)
|
|
{
|
|
g_return_val_if_fail (COGL_IS_ATTRIBUTE (attribute), NULL);
|
|
g_return_val_if_fail (attribute->is_buffered, NULL);
|
|
|
|
return attribute->d.buffered.attribute_buffer;
|
|
}
|
|
|
|
void
|
|
cogl_attribute_set_buffer (CoglAttribute *attribute,
|
|
CoglAttributeBuffer *attribute_buffer)
|
|
{
|
|
g_return_if_fail (COGL_IS_ATTRIBUTE (attribute));
|
|
g_return_if_fail (attribute->is_buffered);
|
|
|
|
if (G_UNLIKELY (attribute->immutable_ref))
|
|
warn_about_midscene_changes ();
|
|
|
|
g_object_ref (attribute_buffer);
|
|
|
|
g_object_unref (attribute->d.buffered.attribute_buffer);
|
|
attribute->d.buffered.attribute_buffer = attribute_buffer;
|
|
}
|
|
|
|
CoglAttribute *
|
|
_cogl_attribute_immutable_ref (CoglAttribute *attribute)
|
|
{
|
|
CoglBuffer *buffer = COGL_BUFFER (attribute->d.buffered.attribute_buffer);
|
|
|
|
g_return_val_if_fail (COGL_IS_ATTRIBUTE (attribute), NULL);
|
|
|
|
attribute->immutable_ref++;
|
|
_cogl_buffer_immutable_ref (buffer);
|
|
return attribute;
|
|
}
|
|
|
|
void
|
|
_cogl_attribute_immutable_unref (CoglAttribute *attribute)
|
|
{
|
|
CoglBuffer *buffer = COGL_BUFFER (attribute->d.buffered.attribute_buffer);
|
|
|
|
g_return_if_fail (COGL_IS_ATTRIBUTE (attribute));
|
|
g_return_if_fail (attribute->immutable_ref > 0);
|
|
|
|
attribute->immutable_ref--;
|
|
_cogl_buffer_immutable_unref (buffer);
|
|
}
|
|
|
|
static gboolean
|
|
validate_layer_cb (CoglPipeline *pipeline,
|
|
int layer_index,
|
|
void *user_data)
|
|
{
|
|
CoglTexture *texture =
|
|
cogl_pipeline_get_layer_texture (pipeline, layer_index);
|
|
CoglFlushLayerState *state = user_data;
|
|
gboolean status = TRUE;
|
|
|
|
/* invalid textures will be handled correctly in
|
|
* _cogl_pipeline_flush_layers_gl_state */
|
|
if (texture == NULL)
|
|
goto validated;
|
|
|
|
_cogl_texture_flush_journal_rendering (texture);
|
|
|
|
/* Give the texture a chance to know that we're rendering
|
|
non-quad shaped primitives. If the texture is in an atlas it
|
|
will be migrated */
|
|
_cogl_texture_ensure_non_quad_rendering (texture);
|
|
|
|
/* We need to ensure the mipmaps are ready before deciding
|
|
* anything else about the texture because the texture storate
|
|
* could completely change if it needs to be migrated out of the
|
|
* atlas and will affect how we validate the layer.
|
|
*/
|
|
_cogl_pipeline_pre_paint_for_layer (pipeline, layer_index);
|
|
|
|
if (!_cogl_texture_can_hardware_repeat (texture))
|
|
{
|
|
g_warning ("Disabling layer %d of the current source material, "
|
|
"because texturing with the vertex buffer API is not "
|
|
"currently supported using sliced textures, or textures "
|
|
"with waste\n", layer_index);
|
|
|
|
/* XXX: maybe we can add a mechanism for users to forcibly use
|
|
* textures with waste where it would be their responsibility to use
|
|
* texture coords in the range [0,1] such that sampling outside isn't
|
|
* required. We can then use a texture matrix (or a modification of
|
|
* the users own matrix) to map 1 to the edge of the texture data.
|
|
*
|
|
* Potentially, given the same guarantee as above we could also
|
|
* support a single sliced layer too. We would have to redraw the
|
|
* vertices once for each layer, each time with a fiddled texture
|
|
* matrix.
|
|
*/
|
|
state->fallback_layers |= (1 << state->unit);
|
|
state->options.flags |= COGL_PIPELINE_FLUSH_FALLBACK_MASK;
|
|
}
|
|
|
|
validated:
|
|
state->unit++;
|
|
return status;
|
|
}
|
|
|
|
void
|
|
_cogl_flush_attributes_state (CoglFramebuffer *framebuffer,
|
|
CoglPipeline *pipeline,
|
|
CoglDrawFlags flags,
|
|
CoglAttribute **attributes,
|
|
int n_attributes)
|
|
{
|
|
CoglContext *ctx = cogl_framebuffer_get_context (framebuffer);
|
|
CoglFlushLayerState layers_state;
|
|
CoglPipeline *copy = NULL;
|
|
|
|
if (!(flags & COGL_DRAW_SKIP_JOURNAL_FLUSH))
|
|
_cogl_framebuffer_flush_journal (framebuffer);
|
|
|
|
layers_state.unit = 0;
|
|
layers_state.options.flags = 0;
|
|
layers_state.fallback_layers = 0;
|
|
|
|
if (!(flags & COGL_DRAW_SKIP_PIPELINE_VALIDATION))
|
|
cogl_pipeline_foreach_layer (pipeline,
|
|
validate_layer_cb,
|
|
&layers_state);
|
|
|
|
/* NB: cogl_context_flush_framebuffer_state may disrupt various state (such
|
|
* as the pipeline state) when flushing the clip stack, so should
|
|
* always be done first when preparing to draw. We need to do this
|
|
* before setting up the array pointers because setting up the clip
|
|
* stack can cause some drawing which would change the array
|
|
* pointers. */
|
|
if (!(flags & COGL_DRAW_SKIP_FRAMEBUFFER_FLUSH))
|
|
{
|
|
cogl_context_flush_framebuffer_state (ctx,
|
|
framebuffer,
|
|
framebuffer,
|
|
COGL_FRAMEBUFFER_STATE_ALL);
|
|
}
|
|
|
|
/* In cogl_read_pixels we have a fast-path when reading a single
|
|
* pixel and the scene is just comprised of simple rectangles still
|
|
* in the journal. For this optimization to work we need to track
|
|
* when the framebuffer really does get drawn to. */
|
|
_cogl_framebuffer_mark_clear_clip_dirty (framebuffer);
|
|
|
|
ctx->driver_vtable->flush_attributes_state (framebuffer,
|
|
pipeline,
|
|
&layers_state,
|
|
flags,
|
|
attributes,
|
|
n_attributes);
|
|
|
|
if (copy)
|
|
g_object_unref (copy);
|
|
}
|
|
|
|
int
|
|
_cogl_attribute_get_n_components (CoglAttribute *attribute)
|
|
{
|
|
if (attribute->is_buffered)
|
|
return attribute->d.buffered.n_components;
|
|
else
|
|
return attribute->d.constant.boxed.size;
|
|
}
|