/* * 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 . * * * * Authors: * Robert Bragg */ /* XXX: For an overview of the functionality implemented here, please * see cogl-vertex-buffer.h, which contains the gtk-doc section overview * for the Vertex Buffers API. */ /* * TODO: We need to do a better job of minimizing when we call glVertexPointer * and pals in enable_state_for_drawing_buffer * * We should have an internal 2-tuple cache of (VBO, offset) for each of them * so we can avoid some GL calls. We could have cogl wrappers for the * gl*Pointer funcs that look like this: * * cogl_vertex_pointer (n_components, gl_type, stride, vbo, offset); * cogl_color_pointer (n_components, gl_type, stride, vbo, offset); * * They would also accept NULL for the VBO handle to support old style vertex * arrays. * * TODO: * Actually hook this up to the cogl shaders infrastructure. The vertex * buffer API has been designed to allow adding of arbitrary attributes for use * with shaders, but this has yet to be actually plumbed together and tested. * The bits we are missing: * - cogl_program_use doesn't currently record within ctx-> which program * is currently in use so a.t.m only Clutter knows the current shader. * - We don't query the current shader program for the generic vertex indices * (using glGetAttribLocation) so that we can call glEnableVertexAttribArray * with those indices. * (currently we just make up consecutive indices) * - some dirty flag mechanims to know when the shader program has changed * so we don't need to re-query it each time we draw a buffer. * * TODO * Expose API that lets developers get back a buffer handle for a particular * polygon so they may add custom attributes to them. * - It should be possible to query/modify attributes efficiently, in place, * avoiding copies. It would not be acceptable to simply require that * developers must query back the n_vertices of a buffer and then the * n_components, type and stride etc of each attribute since there * would be too many combinations to realistically handle. * * - In practice, some cases might be best solved with a higher level * EditableMesh API, (see futher below) but for many cases I think an * API like this might be appropriate: * * cogl_vertex_buffer_foreach_vertex (buffer_handle, * (AttributesBufferIteratorFunc)callback, * "gl_Vertex", "gl_Color", NULL); * static void callback (CoglVertexBufferVertex *vert) * { * GLfloat *pos = vert->attrib[0]; * GLubyte *color = vert->attrib[1]; * GLfloat *new_attrib = buf[vert->index]; * * new_attrib = pos*color; * } * * TODO * Think about a higher level Mesh API for building/modifying attribute buffers * - E.g. look at Blender for inspiration here. They can build a mesh from * "MVert", "MFace" and "MEdge" primitives. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include "cogl.h" #include "cogl-internal.h" #include "cogl-util.h" #include "cogl-context.h" #include "cogl-handle.h" #include "cogl-vertex-buffer-private.h" #include "cogl-texture-private.h" #include "cogl-material.h" #include "cogl-material-private.h" #include "cogl-primitives.h" #include "cogl-framebuffer-private.h" #include "cogl-journal-private.h" #define PAD_FOR_ALIGNMENT(VAR, TYPE_SIZE) \ (VAR = TYPE_SIZE + ((VAR - 1) & ~(TYPE_SIZE - 1))) static void _cogl_vertex_buffer_free (CoglVertexBuffer *buffer); static void _cogl_vertex_buffer_indices_free (CoglVertexBufferIndices *buffer_indices); static CoglUserDataKey _cogl_vertex_buffer_material_priv_key; COGL_HANDLE_DEFINE (VertexBuffer, vertex_buffer); COGL_OBJECT_DEFINE_DEPRECATED_REF_COUNTING (vertex_buffer); COGL_HANDLE_DEFINE (VertexBufferIndices, vertex_buffer_indices); CoglHandle cogl_vertex_buffer_new (unsigned int n_vertices) { CoglVertexBuffer *buffer = g_slice_alloc (sizeof (CoglVertexBuffer)); buffer->n_vertices = n_vertices; buffer->submitted_vbos = NULL; buffer->new_attributes = NULL; buffer->primitive = cogl_primitive_new (COGL_VERTICES_MODE_TRIANGLES, n_vertices, NULL); /* return COGL_INVALID_HANDLE; */ return _cogl_vertex_buffer_handle_new (buffer); } unsigned int cogl_vertex_buffer_get_n_vertices (CoglHandle handle) { CoglVertexBuffer *buffer; if (!cogl_is_vertex_buffer (handle)) return 0; buffer = _cogl_vertex_buffer_pointer_from_handle (handle); return buffer->n_vertices; } /* There are a number of standard OpenGL attributes that we deal with * specially. These attributes are all namespaced with a "gl_" prefix * so we should catch any typos instead of silently adding a custom * attribute. */ static CoglVertexBufferAttribFlags validate_gl_attribute (const char *gl_attribute, guint8 n_components, guint8 *texture_unit) { CoglVertexBufferAttribFlags type; char *detail_seperator = NULL; int name_len; detail_seperator = strstr (gl_attribute, "::"); if (detail_seperator) name_len = detail_seperator - gl_attribute; else name_len = strlen (gl_attribute); if (strncmp (gl_attribute, "Vertex", name_len) == 0) { if (G_UNLIKELY (n_components == 1)) g_critical ("glVertexPointer doesn't allow 1 component vertex " "positions so we currently only support \"gl_Vertex\" " "attributes where n_components == 2, 3 or 4"); type = COGL_VERTEX_BUFFER_ATTRIB_FLAG_VERTEX_ARRAY; } else if (strncmp (gl_attribute, "Color", name_len) == 0) { if (G_UNLIKELY (n_components != 3 && n_components != 4)) g_critical ("glColorPointer expects 3 or 4 component colors so we " "currently only support \"gl_Color\" attributes where " "n_components == 3 or 4"); type = COGL_VERTEX_BUFFER_ATTRIB_FLAG_COLOR_ARRAY; } else if (strncmp (gl_attribute, "MultiTexCoord", strlen ("MultiTexCoord")) == 0) { unsigned int unit; if (sscanf (gl_attribute, "MultiTexCoord%u", &unit) != 1) { g_warning ("gl_MultiTexCoord attributes should include a\n" "texture unit number, E.g. gl_MultiTexCoord0\n"); unit = 0; } /* FIXME: validate any '::' delimiter for this case */ *texture_unit = unit; type = COGL_VERTEX_BUFFER_ATTRIB_FLAG_TEXTURE_COORD_ARRAY; } else if (strncmp (gl_attribute, "Normal", name_len) == 0) { if (G_UNLIKELY (n_components != 3)) g_critical ("glNormalPointer expects 3 component normals so we " "currently only support \"gl_Normal\" attributes where " "n_components == 3"); type = COGL_VERTEX_BUFFER_ATTRIB_FLAG_NORMAL_ARRAY; } else { g_warning ("Unknown gl_* attribute name gl_%s\n", gl_attribute); type = COGL_VERTEX_BUFFER_ATTRIB_FLAG_INVALID; } return type; } /* This validates that a custom attribute name is a valid GLSL variable name * * NB: attribute names may have a detail component delimited using '::' E.g. * custom_attrib::foo or custom_attrib::bar * * maybe I should hang a compiled regex somewhere to handle this */ static gboolean validate_custom_attribute_name (const char *attribute_name) { char *detail_seperator = NULL; int name_len; int i; detail_seperator = strstr (attribute_name, "::"); if (detail_seperator) name_len = detail_seperator - attribute_name; else name_len = strlen (attribute_name); if (name_len == 0 || !g_ascii_isalpha (attribute_name[0]) || attribute_name[0] != '_') return FALSE; for (i = 1; i < name_len; i++) if (!g_ascii_isalnum (attribute_name[i]) || attribute_name[i] != '_') return FALSE; return TRUE; } /* Iterates the CoglVertexBufferVBOs of a buffer and creates a flat list * of all the submitted attributes * * Note: The CoglVertexBufferAttrib structs are deep copied, except the * internal CoglVertexAttribute pointer is set to NULL. */ static GList * copy_submitted_attributes_list (CoglVertexBuffer *buffer) { GList *tmp; GList *submitted_attributes = NULL; for (tmp = buffer->submitted_vbos; tmp != NULL; tmp = tmp->next) { CoglVertexBufferVBO *cogl_vbo = tmp->data; GList *tmp2; for (tmp2 = cogl_vbo->attributes; tmp2 != NULL; tmp2 = tmp2->next) { CoglVertexBufferAttrib *attribute = tmp2->data; CoglVertexBufferAttrib *copy = g_slice_alloc (sizeof (CoglVertexBufferAttrib)); *copy = *attribute; copy->name_without_detail = g_strdup (attribute->name_without_detail); copy->attribute = NULL; submitted_attributes = g_list_prepend (submitted_attributes, copy); } } return submitted_attributes; } static size_t sizeof_attribute_type (CoglAttributeType type) { switch (type) { case COGL_ATTRIBUTE_TYPE_BYTE: return 1; case COGL_ATTRIBUTE_TYPE_UNSIGNED_BYTE: return 1; case COGL_ATTRIBUTE_TYPE_SHORT: return 2; case COGL_ATTRIBUTE_TYPE_UNSIGNED_SHORT: return 2; case COGL_ATTRIBUTE_TYPE_FLOAT: return 4; } g_return_val_if_reached (0); } static size_t strideof (CoglAttributeType type, int n_components) { return sizeof_attribute_type (type) * n_components; } static char * canonize_attribute_name (const char *attribute_name) { char *detail_seperator = NULL; int name_len; if (strncmp (attribute_name, "gl_", 3) != 0) return g_strdup (attribute_name); /* skip past the "gl_" */ attribute_name += 3; detail_seperator = strstr (attribute_name, "::"); if (detail_seperator) name_len = detail_seperator - attribute_name; else { name_len = strlen (attribute_name); detail_seperator = ""; } if (strncmp (attribute_name, "Vertex", name_len) == 0) return g_strconcat ("cogl_position_in", detail_seperator, NULL); else if (strncmp (attribute_name, "Color", name_len) == 0) return g_strconcat ("cogl_color_in", detail_seperator, NULL); else if (strncmp (attribute_name, "MultiTexCoord", strlen ("MultiTexCoord")) == 0) { unsigned int unit; if (sscanf (attribute_name, "MultiTexCoord%u", &unit) != 1) { g_warning ("gl_MultiTexCoord attributes should include a\n" "texture unit number, E.g. gl_MultiTexCoord0\n"); unit = 0; } return g_strdup_printf ("cogl_tex_coord%u_in%s", unit, detail_seperator); } else if (strncmp (attribute_name, "Normal", name_len) == 0) return g_strconcat ("cogl_normal_in", detail_seperator, NULL); else { g_warning ("Unknown gl_* attribute name gl_%s\n", attribute_name); return g_strdup (attribute_name); } } void cogl_vertex_buffer_add (CoglHandle handle, const char *attribute_name, guint8 n_components, CoglAttributeType type, gboolean normalized, guint16 stride, const void *pointer) { CoglVertexBuffer *buffer; char *cogl_attribute_name; GQuark name_quark; gboolean modifying_an_attrib = FALSE; CoglVertexBufferAttrib *attribute; CoglVertexBufferAttribFlags flags = 0; guint8 texture_unit = 0; GList *tmp; char *detail; if (!cogl_is_vertex_buffer (handle)) return; buffer = _cogl_vertex_buffer_pointer_from_handle (handle); buffer->dirty_attributes = TRUE; cogl_attribute_name = canonize_attribute_name (attribute_name); name_quark = g_quark_from_string (cogl_attribute_name); /* The submit function works by diffing between submitted_attributes * and new_attributes to minimize the upload bandwidth + cost of * allocating new VBOs, so if there isn't already a list of new_attributes * we create one: */ if (!buffer->new_attributes) buffer->new_attributes = copy_submitted_attributes_list (buffer); /* Note: we first look for an existing attribute that we are modifying * so we may skip needing to validate the name */ for (tmp = buffer->new_attributes; tmp != NULL; tmp = tmp->next) { CoglVertexBufferAttrib *submitted_attribute = tmp->data; if (submitted_attribute->name == name_quark) { modifying_an_attrib = TRUE; attribute = submitted_attribute; /* since we will skip validate_gl_attribute in this case, we need * to pluck out the attribute type before overwriting the flags: */ flags |= attribute->flags & COGL_VERTEX_BUFFER_ATTRIB_FLAG_TYPE_MASK; break; } } if (!modifying_an_attrib) { /* Validate the attribute name, is suitable as a variable name */ if (strncmp (attribute_name, "gl_", 3) == 0) { /* Note: we pass the original attribute name here so that * any warning messages correspond to the users original * attribute name... */ flags |= validate_gl_attribute (attribute_name + 3, n_components, &texture_unit); if (flags & COGL_VERTEX_BUFFER_ATTRIB_FLAG_INVALID) return; } else { flags |= COGL_VERTEX_BUFFER_ATTRIB_FLAG_CUSTOM_ARRAY; if (validate_custom_attribute_name (attribute_name)) return; } attribute = g_slice_alloc0 (sizeof (CoglVertexBufferAttrib)); } attribute->name = name_quark; detail = strstr (cogl_attribute_name, "::"); if (detail) attribute->name_without_detail = g_strndup (cogl_attribute_name, detail - cogl_attribute_name); else attribute->name_without_detail = g_strdup (cogl_attribute_name); attribute->type = type; attribute->n_components = n_components; if (stride == 0) stride = strideof (type, n_components); attribute->stride = stride; attribute->u.pointer = pointer; attribute->texture_unit = texture_unit; attribute->attribute = NULL; flags |= COGL_VERTEX_BUFFER_ATTRIB_FLAG_ENABLED; /* Note: We currently just assume, if an attribute is *ever* updated * then it should be taged as frequently changing. */ if (modifying_an_attrib) flags |= COGL_VERTEX_BUFFER_ATTRIB_FLAG_FREQUENT_RESUBMIT; else flags |= COGL_VERTEX_BUFFER_ATTRIB_FLAG_INFREQUENT_RESUBMIT; if (normalized) flags |= COGL_VERTEX_BUFFER_ATTRIB_FLAG_NORMALIZED; attribute->flags = flags; attribute->span_bytes = buffer->n_vertices * attribute->stride; if (!modifying_an_attrib) buffer->new_attributes = g_list_prepend (buffer->new_attributes, attribute); g_free (cogl_attribute_name); } static void _cogl_vertex_buffer_attrib_free (CoglVertexBufferAttrib *attribute) { if (attribute->attribute) cogl_object_unref (attribute->attribute); g_free (attribute->name_without_detail); g_slice_free (CoglVertexBufferAttrib, attribute); } void cogl_vertex_buffer_delete (CoglHandle handle, const char *attribute_name) { CoglVertexBuffer *buffer; char *cogl_attribute_name = canonize_attribute_name (attribute_name); GQuark name = g_quark_from_string (cogl_attribute_name); GList *tmp; g_free (cogl_attribute_name); if (!cogl_is_vertex_buffer (handle)) return; buffer = _cogl_vertex_buffer_pointer_from_handle (handle); buffer->dirty_attributes = TRUE; /* The submit function works by diffing between submitted_attributes * and new_attributes to minimize the upload bandwidth + cost of * allocating new VBOs, so if there isn't already a list of new_attributes * we create one: */ if (!buffer->new_attributes) buffer->new_attributes = copy_submitted_attributes_list (buffer); for (tmp = buffer->new_attributes; tmp != NULL; tmp = tmp->next) { CoglVertexBufferAttrib *submitted_attribute = tmp->data; if (submitted_attribute->name == name) { buffer->new_attributes = g_list_delete_link (buffer->new_attributes, tmp); _cogl_vertex_buffer_attrib_free (submitted_attribute); return; } } g_warning ("Failed to find an attribute named %s to delete\n", attribute_name); } static void set_attribute_enable (CoglHandle handle, const char *attribute_name, gboolean state) { CoglVertexBuffer *buffer; char *cogl_attribute_name = canonize_attribute_name (attribute_name); GQuark name_quark = g_quark_from_string (cogl_attribute_name); GList *tmp; g_free (cogl_attribute_name); if (!cogl_is_vertex_buffer (handle)) return; buffer = _cogl_vertex_buffer_pointer_from_handle (handle); buffer->dirty_attributes = TRUE; /* NB: If a buffer is currently being edited, then there can be two seperate * lists of attributes; those that are currently submitted and a new list yet * to be submitted, we need to modify both. */ for (tmp = buffer->new_attributes; tmp != NULL; tmp = tmp->next) { CoglVertexBufferAttrib *attribute = tmp->data; if (attribute->name == name_quark) { if (state) attribute->flags |= COGL_VERTEX_BUFFER_ATTRIB_FLAG_ENABLED; else attribute->flags &= ~COGL_VERTEX_BUFFER_ATTRIB_FLAG_ENABLED; break; } } for (tmp = buffer->submitted_vbos; tmp != NULL; tmp = tmp->next) { CoglVertexBufferVBO *cogl_vbo = tmp->data; GList *tmp2; for (tmp2 = cogl_vbo->attributes; tmp2 != NULL; tmp2 = tmp2->next) { CoglVertexBufferAttrib *attribute = tmp2->data; if (attribute->name == name_quark) { if (state) attribute->flags |= COGL_VERTEX_BUFFER_ATTRIB_FLAG_ENABLED; else attribute->flags &= ~COGL_VERTEX_BUFFER_ATTRIB_FLAG_ENABLED; return; } } } g_warning ("Failed to %s attribute named %s/%s\n", state == TRUE ? "enable" : "disable", attribute_name, cogl_attribute_name); } void cogl_vertex_buffer_enable (CoglHandle handle, const char *attribute_name) { set_attribute_enable (handle, attribute_name, TRUE); } void cogl_vertex_buffer_disable (CoglHandle handle, const char *attribute_name) { set_attribute_enable (handle, attribute_name, FALSE); } /* Given an attribute that we know has already been submitted before, this * function looks for the existing VBO that contains it. * * Note: It will free redundant attribute struct once the corresponding * VBO has been found. */ static void filter_already_submitted_attribute (CoglVertexBufferAttrib *attribute, GList **reuse_vbos, GList **submitted_vbos) { GList *tmp; /* First check the cogl_vbos we already know are being reused since we * are more likley to get a match here */ for (tmp = *reuse_vbos; tmp != NULL; tmp = tmp->next) { CoglVertexBufferVBO *cogl_vbo = tmp->data; GList *tmp2; for (tmp2 = cogl_vbo->attributes; tmp2 != NULL; tmp2 = tmp2->next) { CoglVertexBufferAttrib *vbo_attribute = tmp2->data; if (vbo_attribute->name == attribute->name) { vbo_attribute->flags &= ~COGL_VERTEX_BUFFER_ATTRIB_FLAG_UNUSED; /* Note: we don't free the redundant attribute here, since it * will be freed after all filtering in * cogl_vertex_buffer_submit */ return; } } } for (tmp = *submitted_vbos; tmp != NULL; tmp = tmp->next) { CoglVertexBufferVBO *cogl_vbo = tmp->data; CoglVertexBufferAttrib *reuse_attribute = NULL; GList *tmp2; for (tmp2 = cogl_vbo->attributes; tmp2 != NULL; tmp2 = tmp2->next) { CoglVertexBufferAttrib *vbo_attribute = tmp2->data; if (vbo_attribute->name == attribute->name) { reuse_attribute = vbo_attribute; /* Note: we don't free the redundant attribute here, since it * will be freed after all filtering in * cogl_vertex_buffer_submit */ *submitted_vbos = g_list_remove_link (*submitted_vbos, tmp); tmp->next = *reuse_vbos; *reuse_vbos = tmp; break; } } if (!reuse_attribute) continue; /* Mark all but the matched attribute as UNUSED, so that when we * finish filtering all our attributes any attrributes still * marked as UNUSED can be removed from the their cogl_vbo */ for (tmp2 = cogl_vbo->attributes; tmp2 != NULL; tmp2 = tmp2->next) { CoglVertexBufferAttrib *vbo_attribute = tmp2->data; if (vbo_attribute != reuse_attribute) vbo_attribute->flags |= COGL_VERTEX_BUFFER_ATTRIB_FLAG_UNUSED; } return; } g_critical ("Failed to find the cogl vbo that corresponds to an\n" "attribute that had apparently already been submitted!"); } /* When we first mark a CoglVertexBufferVBO to be reused, we mark the * attributes as unsed, so that when filtering of attributes into VBOs is done * we can then prune the now unsed attributes. */ static void remove_unused_attributes (CoglVertexBufferVBO *cogl_vbo) { GList *tmp; GList *next; for (tmp = cogl_vbo->attributes; tmp != NULL; tmp = next) { CoglVertexBufferAttrib *attribute = tmp->data; next = tmp->next; if (attribute->flags & COGL_VERTEX_BUFFER_ATTRIB_FLAG_UNUSED) { cogl_vbo->attributes = g_list_delete_link (cogl_vbo->attributes, tmp); g_slice_free (CoglVertexBufferAttrib, attribute); } } } /* Give a newly added, strided, attribute, this function looks for a * CoglVertexBufferVBO that the attribute is interleved with. If it can't * find one then a new CoglVertexBufferVBO is allocated and added to the * list of new_strided_vbos. */ static void filter_strided_attribute (CoglVertexBufferAttrib *attribute, GList **new_vbos) { GList *tmp; CoglVertexBufferVBO *new_cogl_vbo; for (tmp = *new_vbos; tmp != NULL; tmp = tmp->next) { CoglVertexBufferVBO *cogl_vbo = tmp->data; GList *tmp2; if (!(cogl_vbo->flags & COGL_VERTEX_BUFFER_VBO_FLAG_STRIDED)) continue; for (tmp2 = cogl_vbo->attributes; tmp2 != NULL; tmp2 = tmp2->next) { CoglVertexBufferAttrib *vbo_attribute = tmp2->data; const char *attribute_start = attribute->u.pointer; const char *vbo_attribute_start = vbo_attribute->u.pointer; /* NB: All attributes have buffer->n_vertices values which * simplifies determining which attributes are interleved * since we assume they will start no farther than +- a * stride away from each other: */ if (attribute_start <= (vbo_attribute_start - vbo_attribute->stride) || attribute_start >= (vbo_attribute_start + vbo_attribute->stride)) continue; /* Not interleved */ cogl_vbo->attributes = g_list_prepend (cogl_vbo->attributes, attribute); if (attribute->flags & COGL_VERTEX_BUFFER_ATTRIB_FLAG_FREQUENT_RESUBMIT) { cogl_vbo->flags &= ~COGL_VERTEX_BUFFER_VBO_FLAG_INFREQUENT_RESUBMIT; cogl_vbo->flags |= COGL_VERTEX_BUFFER_VBO_FLAG_FREQUENT_RESUBMIT; } return; } } new_cogl_vbo = g_slice_alloc (sizeof (CoglVertexBufferVBO)); new_cogl_vbo->attributes = NULL; new_cogl_vbo->attributes = g_list_prepend (new_cogl_vbo->attributes, attribute); /* Any one of the interleved attributes will have the same span_bytes */ new_cogl_vbo->array = NULL; new_cogl_vbo->array_bytes = attribute->span_bytes; new_cogl_vbo->flags = COGL_VERTEX_BUFFER_VBO_FLAG_STRIDED; if (attribute->flags & COGL_VERTEX_BUFFER_ATTRIB_FLAG_INFREQUENT_RESUBMIT) new_cogl_vbo->flags |= COGL_VERTEX_BUFFER_VBO_FLAG_INFREQUENT_RESUBMIT; else new_cogl_vbo->flags |= COGL_VERTEX_BUFFER_VBO_FLAG_FREQUENT_RESUBMIT; *new_vbos = g_list_prepend (*new_vbos, new_cogl_vbo); return; } /* This iterates through the list of submitted VBOs looking for one that * contains attribute. If found the list *link* is removed and returned */ static GList * unlink_submitted_vbo_containing_attribute (GList **submitted_vbos, CoglVertexBufferAttrib *attribute) { GList *tmp; GList *next = NULL; for (tmp = *submitted_vbos; tmp != NULL; tmp = next) { CoglVertexBufferVBO *submitted_vbo = tmp->data; GList *tmp2; next = tmp->next; for (tmp2 = submitted_vbo->attributes; tmp2 != NULL; tmp2 = tmp2->next) { CoglVertexBufferAttrib *submitted_attribute = tmp2->data; if (submitted_attribute->name == attribute->name) { *submitted_vbos = g_list_remove_link (*submitted_vbos, tmp); return tmp; } } } return NULL; } /* Unlinks all the submitted VBOs that conflict with the new cogl_vbo and * returns them as a list. */ static GList * get_submitted_vbo_conflicts (GList **submitted_vbos, CoglVertexBufferVBO *cogl_vbo) { GList *tmp; GList *conflicts = NULL; for (tmp = cogl_vbo->attributes; tmp != NULL; tmp = tmp->next) { GList *link = unlink_submitted_vbo_containing_attribute (submitted_vbos, tmp->data); if (link) { /* prepend the link to the list of conflicts: */ link->next = conflicts; conflicts = link; } } return conflicts; } /* Any attributes in cogl_vbo gets removed from conflict_vbo */ static void disassociate_conflicting_attributes (CoglVertexBufferVBO *conflict_vbo, CoglVertexBufferVBO *cogl_vbo) { GList *tmp; /* NB: The attributes list in conflict_vbo will be shrinking so * we iterate those in the inner loop. */ for (tmp = cogl_vbo->attributes; tmp != NULL; tmp = tmp->next) { CoglVertexBufferAttrib *attribute = tmp->data; GList *tmp2; for (tmp2 = conflict_vbo->attributes; tmp2 != NULL; tmp2 = tmp2->next) { CoglVertexBufferAttrib *conflict_attribute = tmp2->data; if (conflict_attribute->name == attribute->name) { _cogl_vertex_buffer_attrib_free (conflict_attribute); conflict_vbo->attributes = g_list_delete_link (conflict_vbo->attributes, tmp2); break; } } } } static void cogl_vertex_buffer_vbo_free (CoglVertexBufferVBO *cogl_vbo) { GList *tmp; _COGL_GET_CONTEXT (ctx, NO_RETVAL); for (tmp = cogl_vbo->attributes; tmp != NULL; tmp = tmp->next) _cogl_vertex_buffer_attrib_free (tmp->data); g_list_free (cogl_vbo->attributes); if (cogl_vbo->flags & COGL_VERTEX_BUFFER_VBO_FLAG_SUBMITTED) cogl_object_unref (cogl_vbo->array); g_slice_free (CoglVertexBufferVBO, cogl_vbo); } /* This figures out the lowest attribute client pointer. (This pointer is used * to upload all the interleved attributes). * * In the process it also replaces the client pointer with the attributes * offset, and marks the attribute as submitted. */ static const void * prep_strided_vbo_for_upload (CoglVertexBufferVBO *cogl_vbo) { GList *tmp; const char *lowest_pointer = NULL; for (tmp = cogl_vbo->attributes; tmp != NULL; tmp = tmp->next) { CoglVertexBufferAttrib *attribute = tmp->data; const char *client_pointer = attribute->u.pointer; if (!lowest_pointer || client_pointer < lowest_pointer) lowest_pointer = client_pointer; } for (tmp = cogl_vbo->attributes; tmp != NULL; tmp = tmp->next) { CoglVertexBufferAttrib *attribute = tmp->data; const char *client_pointer = attribute->u.pointer; attribute->u.vbo_offset = client_pointer - lowest_pointer; attribute->flags |= COGL_VERTEX_BUFFER_ATTRIB_FLAG_SUBMITTED; } return lowest_pointer; } static gboolean upload_multipack_vbo_via_map_buffer (CoglVertexBufferVBO *cogl_vbo) { GList *tmp; unsigned int offset = 0; guint8 *buf; _COGL_GET_CONTEXT (ctx, FALSE); buf = cogl_buffer_map (COGL_BUFFER (cogl_vbo->array), COGL_BUFFER_ACCESS_WRITE, COGL_BUFFER_MAP_HINT_DISCARD); if (!buf) return FALSE; for (tmp = cogl_vbo->attributes; tmp != NULL; tmp = tmp->next) { CoglVertexBufferAttrib *attribute = tmp->data; gsize attribute_size = attribute->span_bytes; gsize type_size = sizeof_attribute_type (attribute->type); PAD_FOR_ALIGNMENT (offset, type_size); memcpy (buf + offset, attribute->u.pointer, attribute_size); attribute->u.vbo_offset = offset; attribute->flags |= COGL_VERTEX_BUFFER_ATTRIB_FLAG_SUBMITTED; offset += attribute_size; } cogl_buffer_unmap (COGL_BUFFER (cogl_vbo->array)); return TRUE; } static void upload_multipack_vbo_via_buffer_sub_data (CoglVertexBufferVBO *cogl_vbo) { GList *l; unsigned int offset = 0; for (l = cogl_vbo->attributes; l != NULL; l = l->next) { CoglVertexBufferAttrib *attribute = l->data; gsize attribute_size = attribute->span_bytes; gsize type_size = sizeof_attribute_type (attribute->type); PAD_FOR_ALIGNMENT (offset, type_size); cogl_buffer_set_data (COGL_BUFFER (cogl_vbo->array), offset, attribute->u.pointer, attribute_size); attribute->u.vbo_offset = offset; attribute->flags |= COGL_VERTEX_BUFFER_ATTRIB_FLAG_SUBMITTED; offset += attribute_size; } } static void upload_attributes (CoglVertexBufferVBO *cogl_vbo) { CoglBufferUpdateHint usage; _COGL_GET_CONTEXT (ctx, NO_RETVAL); if (cogl_vbo->flags & COGL_VERTEX_BUFFER_VBO_FLAG_FREQUENT_RESUBMIT) usage = COGL_BUFFER_UPDATE_HINT_DYNAMIC; else usage = COGL_BUFFER_UPDATE_HINT_STATIC; cogl_buffer_set_update_hint (COGL_BUFFER (cogl_vbo->array), usage); if (cogl_vbo->flags & COGL_VERTEX_BUFFER_VBO_FLAG_STRIDED) { const void *pointer = prep_strided_vbo_for_upload (cogl_vbo); cogl_buffer_set_data (COGL_BUFFER (cogl_vbo->array), 0, /* offset */ pointer, cogl_vbo->array_bytes); } else /* MULTIPACK */ { /* I think it might depend on the specific driver/HW whether its better * to use glMapBuffer here or glBufferSubData here. There is even a good * thread about this topic here: * http://www.mail-archive.com/dri-devel@lists.sourceforge.net/msg35004.html * For now I have gone with glMapBuffer, but the jury is still out. */ if (!upload_multipack_vbo_via_map_buffer (cogl_vbo)) upload_multipack_vbo_via_buffer_sub_data (cogl_vbo); } cogl_vbo->flags |= COGL_VERTEX_BUFFER_VBO_FLAG_SUBMITTED; } /* Note: although there ends up being quite a few inner loops involved with * resolving buffers, the number of attributes will be low so I don't expect * them to cause a problem. */ static void cogl_vertex_buffer_vbo_resolve (CoglVertexBuffer *buffer, CoglVertexBufferVBO *new_cogl_vbo, GList **final_vbos) { GList *conflicts; GList *tmp; GList *next; gboolean found_target_vbo = FALSE; _COGL_GET_CONTEXT (ctx, NO_RETVAL); conflicts = get_submitted_vbo_conflicts (&buffer->submitted_vbos, new_cogl_vbo); for (tmp = conflicts; tmp != NULL; tmp = next) { CoglVertexBufferVBO *conflict_vbo = tmp->data; next = tmp->next; disassociate_conflicting_attributes (conflict_vbo, new_cogl_vbo); if (!conflict_vbo->attributes) { /* See if we can re-use this now empty VBO: */ if (!found_target_vbo && conflict_vbo->array_bytes == new_cogl_vbo->array_bytes) { found_target_vbo = TRUE; new_cogl_vbo->array = cogl_object_ref (conflict_vbo->array); cogl_vertex_buffer_vbo_free (conflict_vbo); upload_attributes (new_cogl_vbo); *final_vbos = g_list_prepend (*final_vbos, new_cogl_vbo); } else cogl_vertex_buffer_vbo_free (conflict_vbo); } else { /* Relink the VBO back into buffer->submitted_vbos since it may * be involved in other conflicts later */ tmp->next = buffer->submitted_vbos; tmp->prev = NULL; buffer->submitted_vbos = tmp; } } if (!found_target_vbo) { new_cogl_vbo->array = cogl_vertex_array_new (new_cogl_vbo->array_bytes); upload_attributes (new_cogl_vbo); *final_vbos = g_list_prepend (*final_vbos, new_cogl_vbo); } } static void update_primitive_attributes (CoglVertexBuffer *buffer) { GList *l; int n_attributes = 0; CoglVertexAttribute **attributes; int i; if (!buffer->dirty_attributes) return; buffer->dirty_attributes = FALSE; for (l = buffer->submitted_vbos; l; l = l->next) { CoglVertexBufferVBO *cogl_vbo = l->data; GList *l2; for (l2 = cogl_vbo->attributes; l2; l2 = l2->next, n_attributes++) ; } g_return_if_fail (n_attributes > 0); attributes = g_alloca (sizeof (CoglVertexAttribute *) * n_attributes + 1); i = 0; for (l = buffer->submitted_vbos; l; l = l->next) { CoglVertexBufferVBO *cogl_vbo = l->data; GList *l2; for (l2 = cogl_vbo->attributes; l2; l2 = l2->next) { CoglVertexBufferAttrib *attribute = l2->data; if (G_LIKELY (attribute->flags & COGL_VERTEX_BUFFER_ATTRIB_FLAG_ENABLED)) { if (G_UNLIKELY (!attribute->attribute)) { attribute->attribute = cogl_vertex_attribute_new (cogl_vbo->array, attribute->name_without_detail, attribute->stride, attribute->u.vbo_offset, attribute->n_components, attribute->type); } attributes[i++] = attribute->attribute; } } } attributes[i] = NULL; cogl_primitive_set_attributes (buffer->primitive, attributes); } static void cogl_vertex_buffer_submit_real (CoglVertexBuffer *buffer) { GList *tmp; CoglVertexBufferVBO *new_multipack_vbo; GList *new_multipack_vbo_link; GList *new_vbos = NULL; GList *reuse_vbos = NULL; GList *final_vbos = NULL; if (!buffer->new_attributes) goto done; /* The objective now is to copy the attribute data supplied by the client * into buffer objects, but it's important to minimize the number of * redundant data uploads. * * We obviously aim to group together the attributes that are interleved so * that they can be delivered in one go to the driver. * All BOs for interleved data are created as STATIC_DRAW_ARB. * * Non interleved attributes tagged as INFREQUENT_RESUBMIT will be grouped * together back to back in a single BO created as STATIC_DRAW_ARB * * Non interleved attributes tagged as FREQUENT_RESUBMIT will be copied into * individual buffer objects, and the BO itself created DYNAMIC_DRAW_ARB * * If we are modifying a previously submitted CoglVertexBuffer then we are * carefull not to needlesly delete OpenGL buffer objects and replace with * new ones, instead we upload new data to the existing buffers. */ /* NB: We must forget attribute->pointer after submitting since the user * is free to re-use that memory for other purposes now. */ /* Pseudo code: * * Broadly speaking we start with a list of unsorted attributes, and filter * those into 'new' and 're-use' CoglVertexBufferVBO (CBO) lists. We then * take the list of new CBO structs and compare with the CBOs that have * already been submitted to the GPU (but ignoring those we already know will * be re-used) to determine what other CBOs can be re-used, due to being * superseded, and what new GL VBOs need to be created. * * We have two kinds of CBOs: * - Multi Pack CBOs * These contain multiple attributes tightly packed back to back) * - Strided CBOs * These typically contain multiple interleved sets of attributes, * though they can contain just one attribute with a stride * * First create a new-CBOs entry "new-multipack-CBO" * Tag "new-multipack-CBO" as MULTIPACK + INFREQUENT_RESUBMIT * For each unsorted attrib: * if already marked as submitted: * iterate reuse-CBOs: * if we find one that contains this attribute: * free redundant unsorted attrib struct * remove the UNUSED flag from the attrib found in the reuse-CBO * continue to next unsorted attrib * iterate submitted VBOs: * if we find one that contains this attribute: * free redundant unsorted attrib struct * unlink the vbo and move it to the list of reuse-CBOs * mark all attributes except the one just matched as UNUSED * assert (found) * continue to next unsorted attrib * if strided: * iterate the new, strided, CBOs, to see if the attribute is * interleved with one of them, if found: * add to the matched CBO * else if not found: * create a new-CBOs entry tagged STRIDED + INFREQUENT_RESUBMIT * else if unstrided && tagged with FREQUENT_RESUBMIT: * create a new-CBOs entry tagged MULTIPACK + FREQUENT_RESUBMIT * else * add to the new-multipack-CBO * free list of unsorted-attribs * * Next compare the new list of CBOs with the submitted set and try to * minimize the memory bandwidth required to upload the attributes and the * overhead of creating new GL-BOs. * * We deal with four sets of CBOs: * - The "new" CBOs * (as determined above during filtering) * - The "re-use" CBOs * (as determined above during filtering) * - The "submitted" CBOs * (I.e. ones currently submitted to the GPU) * - The "final" CBOs * (The result of resolving the differences between the above sets) * * The re-use CBOs are dealt with first, and we simply delete any remaining * attributes in these that are still marked as UNUSED, and move them * to the list of final CBOs. * * Next we iterate through the "new" CBOs, searching for conflicts * with the "submitted" CBOs and commit our decision to the "final" CBOs * * When searching for submitted entries we always unlink items from the * submitted list once we make matches (before we make descisions * based on the matches). If the CBO node is superseded it is freed, * if it is modified but may be needed for more descisions later it is * relinked back into the submitted list and if it's identical to a new * CBO it will be linked into the final list. * * At the end the list of submitted CBOs represents the attributes that were * deleted from the buffer. * * Iterate re-use-CBOs: * Iterate attribs for each: * if attrib UNUSED: * remove the attrib from the CBO + free * |Note: we could potentially mark this as a re-useable gap * |if needs be later. * add re-use CBO to the final-CBOs * Iterate new-CBOs: * List submitted CBOs conflicting with the this CBO (Unlinked items) * found-target-BO=FALSE * Iterate conflicting CBOs: * Disassociate conflicting attribs from conflicting CBO struct * If no attribs remain: * If found-target-BO!=TRUE * _AND_ If the total size of the conflicting CBO is compatible: * |Note: We don't currently consider re-using oversized buffers * found-target-BO=TRUE * upload replacement data * free submitted CBO struct * add new CBO struct to final-CBOs * else: * delete conflict GL-BO * delete conflict CBO struct * else: * relink CBO back into submitted-CBOs * * if found-target-BO == FALSE: * create a new GL-BO * upload data * add new CBO struct to final-BOs * * Iterate through the remaining "submitted" CBOs: * delete the submitted GL-BO * free the submitted CBO struct */ new_multipack_vbo = g_slice_alloc (sizeof (CoglVertexBufferVBO)); new_multipack_vbo->array = NULL; new_multipack_vbo->array_bytes = 0; new_multipack_vbo->flags = COGL_VERTEX_BUFFER_VBO_FLAG_MULTIPACK | COGL_VERTEX_BUFFER_VBO_FLAG_INFREQUENT_RESUBMIT; new_multipack_vbo->attributes = NULL; new_vbos = g_list_prepend (new_vbos, new_multipack_vbo); /* We save the link pointer here, just so we can do a fast removal later if * no attributes get added to this vbo. */ new_multipack_vbo_link = new_vbos; /* Start with a list of unsorted attributes, and filter those into * potential new Cogl BO structs */ for (tmp = buffer->new_attributes; tmp != NULL; tmp = tmp->next) { CoglVertexBufferAttrib *attribute = tmp->data; if (attribute->flags & COGL_VERTEX_BUFFER_ATTRIB_FLAG_SUBMITTED) { /* If the attribute is already marked as submitted, then we need * to find the existing VBO that contains it so we dont delete it. * * NB: this also frees the attribute struct since it's implicitly * redundant in this case. */ filter_already_submitted_attribute (attribute, &reuse_vbos, &buffer->submitted_vbos); } else if (attribute->stride) { /* look for a CoglVertexBufferVBO that the attribute is * interleved with. If one can't be found then a new * CoglVertexBufferVBO is allocated and added to the list of * new_vbos: */ filter_strided_attribute (attribute, &new_vbos); } else if (attribute->flags & COGL_VERTEX_BUFFER_ATTRIB_FLAG_FREQUENT_RESUBMIT) { CoglVertexBufferVBO *cogl_vbo = g_slice_alloc (sizeof (CoglVertexBufferVBO)); /* attributes we expect will be frequently resubmitted are placed * in their own VBO so that updates don't impact other attributes */ cogl_vbo->flags = COGL_VERTEX_BUFFER_VBO_FLAG_MULTIPACK | COGL_VERTEX_BUFFER_VBO_FLAG_FREQUENT_RESUBMIT; cogl_vbo->attributes = NULL; cogl_vbo->attributes = g_list_prepend (cogl_vbo->attributes, attribute); cogl_vbo->array = NULL; cogl_vbo->array_bytes = attribute->span_bytes; new_vbos = g_list_prepend (new_vbos, cogl_vbo); } else { gsize type_size = sizeof_attribute_type (attribute->flags); /* Infrequently updated attributes just get packed back to back * in a single VBO: */ new_multipack_vbo->attributes = g_list_prepend (new_multipack_vbo->attributes, attribute); /* Note: we have to ensure that each run of attributes is * naturally aligned according to its data type, which may * require some padding bytes: */ /* XXX: We also have to be sure that the attributes aren't * reorderd before being uploaded because the alignment padding * is based on the adjacent attribute. */ PAD_FOR_ALIGNMENT (new_multipack_vbo->array_bytes, type_size); new_multipack_vbo->array_bytes += attribute->span_bytes; } } /* At this point all buffer->new_attributes have been filtered into * CoglVertexBufferVBOs... */ g_list_free (buffer->new_attributes); buffer->new_attributes = NULL; /* If the multipack vbo wasn't needed: */ if (new_multipack_vbo->attributes == NULL) { new_vbos = g_list_delete_link (new_vbos, new_multipack_vbo_link); g_slice_free (CoglVertexBufferVBO, new_multipack_vbo); } for (tmp = reuse_vbos; tmp != NULL; tmp = tmp->next) remove_unused_attributes (tmp->data); final_vbos = g_list_concat (final_vbos, reuse_vbos); for (tmp = new_vbos; tmp != NULL; tmp = tmp->next) cogl_vertex_buffer_vbo_resolve (buffer, tmp->data, &final_vbos); /* Anything left corresponds to deleted attributes: */ for (tmp = buffer->submitted_vbos; tmp != NULL; tmp = tmp->next) cogl_vertex_buffer_vbo_free (tmp->data); g_list_free (buffer->submitted_vbos); g_list_free (new_vbos); buffer->submitted_vbos = final_vbos; done: update_primitive_attributes (buffer); } void cogl_vertex_buffer_submit (CoglHandle handle) { CoglVertexBuffer *buffer; if (!cogl_is_vertex_buffer (handle)) return; buffer = _cogl_vertex_buffer_pointer_from_handle (handle); cogl_vertex_buffer_submit_real (buffer); } typedef struct { CoglMaterial *real_source; } VertexBufferMaterialPrivate; static void weak_override_source_destroyed_cb (CoglMaterial *material, void *user_data) { VertexBufferMaterialPrivate *material_priv = user_data; material_priv->real_source = NULL; } static gboolean validate_layer_cb (CoglMaterial *material, int layer_index, void *user_data) { VertexBufferMaterialPrivate *material_priv = user_data; CoglMaterial *source = material_priv->real_source; if (!cogl_material_get_layer_point_sprite_coords_enabled (source, layer_index)) { CoglMaterialWrapMode wrap_s; CoglMaterialWrapMode wrap_t; CoglMaterialWrapMode wrap_p; gboolean need_override_source = FALSE; /* By default COGL_MATERIAL_WRAP_MODE_AUTOMATIC becomes * GL_CLAMP_TO_EDGE but we want GL_REPEAT to maintain * compatibility with older versions of Cogl so we'll override * it. We don't want to do this for point sprites because in * that case the whole texture is drawn so you would usually * want clamp-to-edge. */ wrap_s = cogl_material_get_layer_wrap_mode_s (source, layer_index); if (wrap_s == COGL_MATERIAL_WRAP_MODE_AUTOMATIC) { need_override_source = TRUE; wrap_s = COGL_MATERIAL_WRAP_MODE_REPEAT; } wrap_t = cogl_material_get_layer_wrap_mode_t (source, layer_index); if (wrap_t == COGL_MATERIAL_WRAP_MODE_AUTOMATIC) { need_override_source = TRUE; wrap_t = COGL_MATERIAL_WRAP_MODE_REPEAT; } wrap_p = cogl_material_get_layer_wrap_mode_p (source, layer_index); if (wrap_p == COGL_MATERIAL_WRAP_MODE_AUTOMATIC) { need_override_source = TRUE; wrap_p = COGL_MATERIAL_WRAP_MODE_REPEAT; } if (need_override_source) { if (material_priv->real_source == material) material_priv->real_source = source = _cogl_material_weak_copy (material, weak_override_source_destroyed_cb, material_priv); cogl_material_set_layer_wrap_mode_s (source, layer_index, wrap_s); cogl_material_set_layer_wrap_mode_t (source, layer_index, wrap_t); cogl_material_set_layer_wrap_mode_p (source, layer_index, wrap_p); } } return TRUE; } static void destroy_material_priv_cb (void *user_data) { g_slice_free (VertexBufferMaterialPrivate, user_data); } static void update_primitive_and_draw (CoglVertexBuffer *buffer, CoglVerticesMode mode, int first, int count, CoglVertexBufferIndices *buffer_indices) { VertexBufferMaterialPrivate *material_priv; CoglMaterial *users_source; _COGL_GET_CONTEXT (ctx, NO_RETVAL); cogl_primitive_set_mode (buffer->primitive, mode); cogl_primitive_set_first_vertex (buffer->primitive, first); cogl_primitive_set_n_vertices (buffer->primitive, count); if (buffer_indices) cogl_primitive_set_indices (buffer->primitive, buffer_indices->indices); else cogl_primitive_set_indices (buffer->primitive, NULL); cogl_vertex_buffer_submit_real (buffer); users_source = cogl_get_source (); material_priv = cogl_object_get_user_data (COGL_OBJECT (users_source), &_cogl_vertex_buffer_material_priv_key); if (G_UNLIKELY (!material_priv)) { material_priv = g_slice_new0 (VertexBufferMaterialPrivate); cogl_object_set_user_data (COGL_OBJECT (users_source), &_cogl_vertex_buffer_material_priv_key, material_priv, destroy_material_priv_cb); } if (G_UNLIKELY (!material_priv->real_source)) { material_priv->real_source = users_source; cogl_material_foreach_layer (material_priv->real_source, validate_layer_cb, material_priv); } cogl_push_source (material_priv->real_source); cogl_primitive_draw (buffer->primitive); cogl_pop_source (); } void cogl_vertex_buffer_draw (CoglHandle handle, CoglVerticesMode mode, int first, int count) { CoglVertexBuffer *buffer; if (!cogl_is_vertex_buffer (handle)) return; buffer = _cogl_vertex_buffer_pointer_from_handle (handle); update_primitive_and_draw (buffer, mode, first, count, NULL); } CoglHandle cogl_vertex_buffer_indices_new (CoglIndicesType indices_type, const void *indices_array, int indices_len) { CoglVertexBufferIndices *buffer_indices = g_slice_alloc (sizeof (CoglVertexBufferIndices)); buffer_indices->indices = cogl_indices_new (indices_type, indices_array, indices_len); return _cogl_vertex_buffer_indices_handle_new (buffer_indices); } CoglIndicesType cogl_vertex_buffer_indices_get_type (CoglHandle indices_handle) { CoglVertexBufferIndices *buffer_indices = NULL; if (!cogl_is_vertex_buffer_indices (indices_handle)) return COGL_INDICES_TYPE_UNSIGNED_SHORT; buffer_indices = _cogl_vertex_buffer_indices_pointer_from_handle (indices_handle); return cogl_indices_get_type (buffer_indices->indices); } void _cogl_vertex_buffer_indices_free (CoglVertexBufferIndices *buffer_indices) { cogl_object_unref (buffer_indices->indices); g_slice_free (CoglVertexBufferIndices, buffer_indices); } void cogl_vertex_buffer_draw_elements (CoglHandle handle, CoglVerticesMode mode, CoglHandle indices_handle, int min_index, int max_index, int indices_offset, int count) { CoglVertexBuffer *buffer; CoglVertexBufferIndices *buffer_indices; if (!cogl_is_vertex_buffer (handle)) return; buffer = _cogl_vertex_buffer_pointer_from_handle (handle); if (!cogl_is_vertex_buffer_indices (indices_handle)) return; buffer_indices = _cogl_vertex_buffer_indices_pointer_from_handle (indices_handle); update_primitive_and_draw (buffer, mode, indices_offset, count, buffer_indices); } static void _cogl_vertex_buffer_free (CoglVertexBuffer *buffer) { GList *tmp; for (tmp = buffer->submitted_vbos; tmp != NULL; tmp = tmp->next) cogl_vertex_buffer_vbo_free (tmp->data); g_list_free (buffer->submitted_vbos); for (tmp = buffer->new_attributes; tmp != NULL; tmp = tmp->next) _cogl_vertex_buffer_attrib_free (tmp->data); g_list_free (buffer->new_attributes); if (buffer->primitive) cogl_object_unref (buffer->primitive); g_slice_free (CoglVertexBuffer, buffer); } CoglHandle cogl_vertex_buffer_indices_get_for_quads (unsigned int n_indices) { _COGL_GET_CONTEXT (ctx, COGL_INVALID_HANDLE); /* Check if the indices would fit in a byte array */ if (n_indices <= 256 / 4 * 6) { /* Generate the byte array if we haven't already */ if (ctx->quad_indices_byte == COGL_INVALID_HANDLE) { guint8 *byte_array = g_malloc (256 / 4 * 6 * sizeof (guint8)); guint8 *p = byte_array; int i, vert_num = 0; for (i = 0; i < 256 / 4; i++) { *(p++) = vert_num + 0; *(p++) = vert_num + 1; *(p++) = vert_num + 2; *(p++) = vert_num + 0; *(p++) = vert_num + 2; *(p++) = vert_num + 3; vert_num += 4; } ctx->quad_indices_byte = cogl_vertex_buffer_indices_new (COGL_INDICES_TYPE_UNSIGNED_BYTE, byte_array, 256 / 4 * 6); g_free (byte_array); } return ctx->quad_indices_byte; } else { if (ctx->quad_indices_short_len < n_indices) { guint16 *short_array; guint16 *p; int i, vert_num = 0; if (ctx->quad_indices_short != COGL_INVALID_HANDLE) cogl_handle_unref (ctx->quad_indices_short); /* Pick a power of two >= MAX (512, n_indices) */ if (ctx->quad_indices_short_len == 0) ctx->quad_indices_short_len = 512; while (ctx->quad_indices_short_len < n_indices) ctx->quad_indices_short_len *= 2; /* Over-allocate to generate a whole number of quads */ p = short_array = g_malloc ((ctx->quad_indices_short_len + 5) / 6 * 6 * sizeof (guint16)); /* Fill in the complete quads */ for (i = 0; i < ctx->quad_indices_short_len; i += 6) { *(p++) = vert_num + 0; *(p++) = vert_num + 1; *(p++) = vert_num + 2; *(p++) = vert_num + 0; *(p++) = vert_num + 2; *(p++) = vert_num + 3; vert_num += 4; } ctx->quad_indices_short = cogl_vertex_buffer_indices_new (COGL_INDICES_TYPE_UNSIGNED_SHORT, short_array, ctx->quad_indices_short_len); g_free (short_array); } return ctx->quad_indices_short; } }