/* * Cogl * * A Low Level GPU Graphics and Utilities API * * Copyright (C) 2009,2010,2012 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: * Havoc Pennington for litl * Robert Bragg * Neil Roberts */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "cogl-context-private.h" #include "cogl-util-gl-private.h" #include "cogl-matrix-stack.h" #include "cogl-framebuffer-private.h" #include "cogl-object-private.h" #include "cogl-offscreen.h" #include "cogl-matrix-private.h" #include "cogl-magazine-private.h" #include "cogl-gtype-private.h" static void _cogl_matrix_stack_free (CoglMatrixStack *stack); COGL_OBJECT_DEFINE (MatrixStack, matrix_stack); COGL_GTYPE_DEFINE_CLASS (MatrixStack, matrix_stack); COGL_GTYPE_DEFINE_BOXED (MatrixEntry, matrix_entry, cogl_matrix_entry_ref, cogl_matrix_entry_unref); static CoglMagazine *cogl_matrix_stack_magazine; static CoglMagazine *cogl_matrix_stack_matrices_magazine; /* XXX: Note: this leaves entry->parent uninitialized! */ static CoglMatrixEntry * _cogl_matrix_entry_new (CoglMatrixOp operation) { CoglMatrixEntry *entry = _cogl_magazine_chunk_alloc (cogl_matrix_stack_magazine); entry->ref_count = 1; entry->op = operation; #ifdef COGL_DEBUG_ENABLED entry->composite_gets = 0; #endif return entry; } static void * _cogl_matrix_stack_push_entry (CoglMatrixStack *stack, CoglMatrixEntry *entry) { /* NB: The initial reference of the entry is transferred to the * stack here. * * The stack only maintains a reference to the top of the stack (the * last entry pushed) and each entry in-turn maintains a reference * to its parent. * * We don't need to take a reference to the parent from the entry * here because the we are stealing the reference that was held by * the stack while that parent was previously the top of the stack. */ entry->parent = stack->last_entry; stack->last_entry = entry; return entry; } static void * _cogl_matrix_stack_push_operation (CoglMatrixStack *stack, CoglMatrixOp operation) { CoglMatrixEntry *entry = _cogl_matrix_entry_new (operation); _cogl_matrix_stack_push_entry (stack, entry); return entry; } static void * _cogl_matrix_stack_push_replacement_entry (CoglMatrixStack *stack, CoglMatrixOp operation) { CoglMatrixEntry *old_top = stack->last_entry; CoglMatrixEntry *new_top; /* This would only be called for operations that completely replace * the matrix. In that case we don't need to keep a reference to * anything up to the last save entry. This optimisation could be * important for applications that aren't using the stack but * instead just perform their own matrix manipulations and load a * new stack every frame. If this optimisation isn't done then the * stack would just grow endlessly. See the comments * cogl_matrix_stack_pop for a description of how popping works. */ for (new_top = old_top; new_top->op != COGL_MATRIX_OP_SAVE && new_top->parent; new_top = new_top->parent) ; cogl_matrix_entry_ref (new_top); cogl_matrix_entry_unref (old_top); stack->last_entry = new_top; return _cogl_matrix_stack_push_operation (stack, operation); } void _cogl_matrix_entry_identity_init (CoglMatrixEntry *entry) { entry->ref_count = 1; entry->op = COGL_MATRIX_OP_LOAD_IDENTITY; entry->parent = NULL; #ifdef COGL_DEBUG_ENABLED entry->composite_gets = 0; #endif } void cogl_matrix_stack_load_identity (CoglMatrixStack *stack) { _cogl_matrix_stack_push_replacement_entry (stack, COGL_MATRIX_OP_LOAD_IDENTITY); } void cogl_matrix_stack_translate (CoglMatrixStack *stack, float x, float y, float z) { CoglMatrixEntryTranslate *entry; entry = _cogl_matrix_stack_push_operation (stack, COGL_MATRIX_OP_TRANSLATE); entry->x = x; entry->y = y; entry->z = z; } void cogl_matrix_stack_rotate (CoglMatrixStack *stack, float angle, float x, float y, float z) { CoglMatrixEntryRotate *entry; entry = _cogl_matrix_stack_push_operation (stack, COGL_MATRIX_OP_ROTATE); entry->angle = angle; entry->x = x; entry->y = y; entry->z = z; } void cogl_matrix_stack_rotate_quaternion (CoglMatrixStack *stack, const CoglQuaternion *quaternion) { CoglMatrixEntryRotateQuaternion *entry; entry = _cogl_matrix_stack_push_operation (stack, COGL_MATRIX_OP_ROTATE_QUATERNION); entry->values[0] = quaternion->w; entry->values[1] = quaternion->x; entry->values[2] = quaternion->y; entry->values[3] = quaternion->z; } void cogl_matrix_stack_rotate_euler (CoglMatrixStack *stack, const CoglEuler *euler) { CoglMatrixEntryRotateEuler *entry; entry = _cogl_matrix_stack_push_operation (stack, COGL_MATRIX_OP_ROTATE_EULER); entry->heading = euler->heading; entry->pitch = euler->pitch; entry->roll = euler->roll; } void cogl_matrix_stack_scale (CoglMatrixStack *stack, float x, float y, float z) { CoglMatrixEntryScale *entry; entry = _cogl_matrix_stack_push_operation (stack, COGL_MATRIX_OP_SCALE); entry->x = x; entry->y = y; entry->z = z; } void cogl_matrix_stack_multiply (CoglMatrixStack *stack, const CoglMatrix *matrix) { CoglMatrixEntryMultiply *entry; entry = _cogl_matrix_stack_push_operation (stack, COGL_MATRIX_OP_MULTIPLY); entry->matrix = _cogl_magazine_chunk_alloc (cogl_matrix_stack_matrices_magazine); cogl_matrix_init_from_array (entry->matrix, (float *)matrix); } void cogl_matrix_stack_set (CoglMatrixStack *stack, const CoglMatrix *matrix) { CoglMatrixEntryLoad *entry; entry = _cogl_matrix_stack_push_replacement_entry (stack, COGL_MATRIX_OP_LOAD); entry->matrix = _cogl_magazine_chunk_alloc (cogl_matrix_stack_matrices_magazine); cogl_matrix_init_from_array (entry->matrix, (float *)matrix); } void cogl_matrix_stack_frustum (CoglMatrixStack *stack, float left, float right, float bottom, float top, float z_near, float z_far) { CoglMatrixEntryLoad *entry; entry = _cogl_matrix_stack_push_replacement_entry (stack, COGL_MATRIX_OP_LOAD); entry->matrix = _cogl_magazine_chunk_alloc (cogl_matrix_stack_matrices_magazine); cogl_matrix_init_identity (entry->matrix); cogl_matrix_frustum (entry->matrix, left, right, bottom, top, z_near, z_far); } void cogl_matrix_stack_perspective (CoglMatrixStack *stack, float fov_y, float aspect, float z_near, float z_far) { CoglMatrixEntryLoad *entry; entry = _cogl_matrix_stack_push_replacement_entry (stack, COGL_MATRIX_OP_LOAD); entry->matrix = _cogl_magazine_chunk_alloc (cogl_matrix_stack_matrices_magazine); cogl_matrix_init_identity (entry->matrix); cogl_matrix_perspective (entry->matrix, fov_y, aspect, z_near, z_far); } void cogl_matrix_stack_orthographic (CoglMatrixStack *stack, float x_1, float y_1, float x_2, float y_2, float near, float far) { CoglMatrixEntryLoad *entry; entry = _cogl_matrix_stack_push_replacement_entry (stack, COGL_MATRIX_OP_LOAD); entry->matrix = _cogl_magazine_chunk_alloc (cogl_matrix_stack_matrices_magazine); cogl_matrix_init_identity (entry->matrix); cogl_matrix_orthographic (entry->matrix, x_1, y_1, x_2, y_2, near, far); } void cogl_matrix_stack_push (CoglMatrixStack *stack) { CoglMatrixEntrySave *entry; entry = _cogl_matrix_stack_push_operation (stack, COGL_MATRIX_OP_SAVE); entry->cache_valid = FALSE; } CoglMatrixEntry * cogl_matrix_entry_ref (CoglMatrixEntry *entry) { /* A NULL pointer is considered a valid stack so we should accept that as an argument */ if (entry) entry->ref_count++; return entry; } void cogl_matrix_entry_unref (CoglMatrixEntry *entry) { CoglMatrixEntry *parent; for (; entry && --entry->ref_count <= 0; entry = parent) { parent = entry->parent; switch (entry->op) { case COGL_MATRIX_OP_LOAD_IDENTITY: case COGL_MATRIX_OP_TRANSLATE: case COGL_MATRIX_OP_ROTATE: case COGL_MATRIX_OP_ROTATE_QUATERNION: case COGL_MATRIX_OP_ROTATE_EULER: case COGL_MATRIX_OP_SCALE: break; case COGL_MATRIX_OP_MULTIPLY: { CoglMatrixEntryMultiply *multiply = (CoglMatrixEntryMultiply *)entry; _cogl_magazine_chunk_free (cogl_matrix_stack_matrices_magazine, multiply->matrix); break; } case COGL_MATRIX_OP_LOAD: { CoglMatrixEntryLoad *load = (CoglMatrixEntryLoad *)entry; _cogl_magazine_chunk_free (cogl_matrix_stack_matrices_magazine, load->matrix); break; } case COGL_MATRIX_OP_SAVE: { CoglMatrixEntrySave *save = (CoglMatrixEntrySave *)entry; if (save->cache_valid) _cogl_magazine_chunk_free (cogl_matrix_stack_matrices_magazine, save->cache); break; } } _cogl_magazine_chunk_free (cogl_matrix_stack_magazine, entry); } } void cogl_matrix_stack_pop (CoglMatrixStack *stack) { CoglMatrixEntry *old_top; CoglMatrixEntry *new_top; _COGL_RETURN_IF_FAIL (stack != NULL); old_top = stack->last_entry; _COGL_RETURN_IF_FAIL (old_top != NULL); /* To pop we are moving the top of the stack to the old top's parent * node. The stack always needs to have a reference to the top entry * so we must take a reference to the new top. The stack would have * previously had a reference to the old top so we need to decrease * the ref count on that. We need to ref the new head first in case * this stack was the only thing referencing the old top. In that * case the call to cogl_matrix_entry_unref will unref the parent. */ /* Find the last save operation and remove it */ /* XXX: it would be an error to pop to the very beginning of the * stack so we don't need to check for NULL pointer dereferencing. */ for (new_top = old_top; new_top->op != COGL_MATRIX_OP_SAVE; new_top = new_top->parent) ; new_top = new_top->parent; cogl_matrix_entry_ref (new_top); cogl_matrix_entry_unref (old_top); stack->last_entry = new_top; } CoglBool cogl_matrix_stack_get_inverse (CoglMatrixStack *stack, CoglMatrix *inverse) { CoglMatrix matrix; CoglMatrix *internal = cogl_matrix_stack_get (stack, &matrix); if (internal) return cogl_matrix_get_inverse (internal, inverse); else return cogl_matrix_get_inverse (&matrix, inverse); } /* In addition to writing the stack matrix into the give @matrix * argument this function *may* sometimes also return a pointer * to a matrix too so if we are querying the inverse matrix we * should query from the return matrix so that the result can * be cached within the stack. */ CoglMatrix * cogl_matrix_entry_get (CoglMatrixEntry *entry, CoglMatrix *matrix) { int depth; CoglMatrixEntry *current; CoglMatrixEntry **children; int i; for (depth = 0, current = entry; current; current = current->parent, depth++) { switch (current->op) { case COGL_MATRIX_OP_LOAD_IDENTITY: cogl_matrix_init_identity (matrix); goto initialized; case COGL_MATRIX_OP_LOAD: { CoglMatrixEntryLoad *load = (CoglMatrixEntryLoad *)current; _cogl_matrix_init_from_matrix_without_inverse (matrix, load->matrix); goto initialized; } case COGL_MATRIX_OP_SAVE: { CoglMatrixEntrySave *save = (CoglMatrixEntrySave *)current; if (!save->cache_valid) { CoglMagazine *matrices_magazine = cogl_matrix_stack_matrices_magazine; save->cache = _cogl_magazine_chunk_alloc (matrices_magazine); cogl_matrix_entry_get (current->parent, save->cache); save->cache_valid = TRUE; } _cogl_matrix_init_from_matrix_without_inverse (matrix, save->cache); goto initialized; } default: continue; } } initialized: if (depth == 0) { switch (entry->op) { case COGL_MATRIX_OP_LOAD_IDENTITY: case COGL_MATRIX_OP_TRANSLATE: case COGL_MATRIX_OP_ROTATE: case COGL_MATRIX_OP_ROTATE_QUATERNION: case COGL_MATRIX_OP_ROTATE_EULER: case COGL_MATRIX_OP_SCALE: case COGL_MATRIX_OP_MULTIPLY: return NULL; case COGL_MATRIX_OP_LOAD: { CoglMatrixEntryLoad *load = (CoglMatrixEntryLoad *)entry; return load->matrix; } case COGL_MATRIX_OP_SAVE: { CoglMatrixEntrySave *save = (CoglMatrixEntrySave *)entry; return save->cache; } } g_warn_if_reached (); return NULL; } #ifdef COGL_ENABLE_DEBUG if (!current) { g_warning ("Inconsistent matrix stack"); return NULL; } entry->composite_gets++; #endif children = g_alloca (sizeof (CoglMatrixEntry) * depth); /* We need walk the list of entries from the init/load/save entry * back towards the leaf node but the nodes don't link to their * children so we need to re-walk them here to add to a separate * array. */ for (i = depth - 1, current = entry; i >= 0 && current; i--, current = current->parent) { children[i] = current; } #ifdef COGL_ENABLE_DEBUG if (COGL_DEBUG_ENABLED (COGL_DEBUG_PERFORMANCE) && entry->composite_gets >= 2) { COGL_NOTE (PERFORMANCE, "Re-composing a matrix stack entry multiple times"); } #endif for (i = 0; i < depth; i++) { switch (children[i]->op) { case COGL_MATRIX_OP_TRANSLATE: { CoglMatrixEntryTranslate *translate = (CoglMatrixEntryTranslate *)children[i]; cogl_matrix_translate (matrix, translate->x, translate->y, translate->z); continue; } case COGL_MATRIX_OP_ROTATE: { CoglMatrixEntryRotate *rotate= (CoglMatrixEntryRotate *)children[i]; cogl_matrix_rotate (matrix, rotate->angle, rotate->x, rotate->y, rotate->z); continue; } case COGL_MATRIX_OP_ROTATE_EULER: { CoglMatrixEntryRotateEuler *rotate = (CoglMatrixEntryRotateEuler *)children[i]; CoglEuler euler; cogl_euler_init (&euler, rotate->heading, rotate->pitch, rotate->roll); cogl_matrix_rotate_euler (matrix, &euler); continue; } case COGL_MATRIX_OP_ROTATE_QUATERNION: { CoglMatrixEntryRotateQuaternion *rotate = (CoglMatrixEntryRotateQuaternion *)children[i]; CoglQuaternion quaternion; cogl_quaternion_init_from_array (&quaternion, rotate->values); cogl_matrix_rotate_quaternion (matrix, &quaternion); continue; } case COGL_MATRIX_OP_SCALE: { CoglMatrixEntryScale *scale = (CoglMatrixEntryScale *)children[i]; cogl_matrix_scale (matrix, scale->x, scale->y, scale->z); continue; } case COGL_MATRIX_OP_MULTIPLY: { CoglMatrixEntryMultiply *multiply = (CoglMatrixEntryMultiply *)children[i]; cogl_matrix_multiply (matrix, matrix, multiply->matrix); continue; } case COGL_MATRIX_OP_LOAD_IDENTITY: case COGL_MATRIX_OP_LOAD: case COGL_MATRIX_OP_SAVE: g_warn_if_reached (); continue; } } return NULL; } CoglMatrixEntry * cogl_matrix_stack_get_entry (CoglMatrixStack *stack) { return stack->last_entry; } /* In addition to writing the stack matrix into the give @matrix * argument this function *may* sometimes also return a pointer * to a matrix too so if we are querying the inverse matrix we * should query from the return matrix so that the result can * be cached within the stack. */ CoglMatrix * cogl_matrix_stack_get (CoglMatrixStack *stack, CoglMatrix *matrix) { return cogl_matrix_entry_get (stack->last_entry, matrix); } static void _cogl_matrix_stack_free (CoglMatrixStack *stack) { cogl_matrix_entry_unref (stack->last_entry); g_slice_free (CoglMatrixStack, stack); } CoglMatrixStack * cogl_matrix_stack_new (CoglContext *ctx) { CoglMatrixStack *stack = g_slice_new (CoglMatrixStack); if (G_UNLIKELY (cogl_matrix_stack_magazine == NULL)) { cogl_matrix_stack_magazine = _cogl_magazine_new (sizeof (CoglMatrixEntryFull), 20); cogl_matrix_stack_matrices_magazine = _cogl_magazine_new (sizeof (CoglMatrix), 20); } stack->context = ctx; stack->last_entry = NULL; cogl_matrix_entry_ref (&ctx->identity_entry); _cogl_matrix_stack_push_entry (stack, &ctx->identity_entry); return _cogl_matrix_stack_object_new (stack); } static CoglMatrixEntry * _cogl_matrix_entry_skip_saves (CoglMatrixEntry *entry) { /* We currently assume that every stack starts with an * _OP_LOAD_IDENTITY so we don't need to worry about * NULL pointer dereferencing here. */ while (entry->op == COGL_MATRIX_OP_SAVE) entry = entry->parent; return entry; } CoglBool cogl_matrix_entry_calculate_translation (CoglMatrixEntry *entry0, CoglMatrixEntry *entry1, float *x, float *y, float *z) { GSList *head0 = NULL; GSList *head1 = NULL; CoglMatrixEntry *node0; CoglMatrixEntry *node1; int len0 = 0; int len1 = 0; int count; GSList *common_ancestor0; GSList *common_ancestor1; /* Algorithm: * * 1) Ignoring _OP_SAVE entries walk the ancestors of each entry to * the root node or any non-translation node, adding a pointer to * each ancestor node to two linked lists. * * 2) Compare the lists to find the nodes where they start to * differ marking the common_ancestor node for each list. * * 3) For the list corresponding to entry0, start iterating after * the common ancestor applying the negative of all translations * to x, y and z. * * 4) For the list corresponding to entry1, start iterating after * the common ancestor applying the positive of all translations * to x, y and z. * * If we come across any non-translation operations during 3) or 4) * then bail out returning FALSE. */ for (node0 = entry0; node0; node0 = node0->parent) { GSList *link; if (node0->op == COGL_MATRIX_OP_SAVE) continue; link = alloca (sizeof (GSList)); link->next = head0; link->data = node0; head0 = link; len0++; if (node0->op != COGL_MATRIX_OP_TRANSLATE) break; } for (node1 = entry1; node1; node1 = node1->parent) { GSList *link; if (node1->op == COGL_MATRIX_OP_SAVE) continue; link = alloca (sizeof (GSList)); link->next = head1; link->data = node1; head1 = link; len1++; if (node1->op != COGL_MATRIX_OP_TRANSLATE) break; } if (head0->data != head1->data) return FALSE; common_ancestor0 = head0; common_ancestor1 = head1; head0 = head0->next; head1 = head1->next; count = MIN (len0, len1) - 1; while (count--) { if (head0->data != head1->data) break; common_ancestor0 = head0; common_ancestor1 = head1; head0 = head0->next; head1 = head1->next; } *x = 0; *y = 0; *z = 0; for (head0 = common_ancestor0->next; head0; head0 = head0->next) { CoglMatrixEntryTranslate *translate; node0 = head0->data; if (node0->op != COGL_MATRIX_OP_TRANSLATE) return FALSE; translate = (CoglMatrixEntryTranslate *)node0; *x = *x - translate->x; *y = *y - translate->y; *z = *z - translate->z; } for (head1 = common_ancestor1->next; head1; head1 = head1->next) { CoglMatrixEntryTranslate *translate; node1 = head1->data; if (node1->op != COGL_MATRIX_OP_TRANSLATE) return FALSE; translate = (CoglMatrixEntryTranslate *)node1; *x = *x + translate->x; *y = *y + translate->y; *z = *z + translate->z; } return TRUE; } CoglBool cogl_matrix_entry_is_identity (CoglMatrixEntry *entry) { return entry ? entry->op == COGL_MATRIX_OP_LOAD_IDENTITY : FALSE; } static void _cogl_matrix_flush_to_gl_builtin (CoglContext *ctx, CoglBool is_identity, CoglMatrix *matrix, CoglMatrixMode mode) { g_assert (_cogl_has_private_feature (ctx, COGL_PRIVATE_FEATURE_GL_FIXED)); #if defined (HAVE_COGL_GL) || defined (HAVE_COGL_GLES) if (ctx->flushed_matrix_mode != mode) { GLenum gl_mode = 0; switch (mode) { case COGL_MATRIX_MODELVIEW: gl_mode = GL_MODELVIEW; break; case COGL_MATRIX_PROJECTION: gl_mode = GL_PROJECTION; break; case COGL_MATRIX_TEXTURE: gl_mode = GL_TEXTURE; break; } GE (ctx, glMatrixMode (gl_mode)); ctx->flushed_matrix_mode = mode; } if (is_identity) GE (ctx, glLoadIdentity ()); else GE (ctx, glLoadMatrixf (cogl_matrix_get_array (matrix))); #endif } void _cogl_matrix_entry_flush_to_gl_builtins (CoglContext *ctx, CoglMatrixEntry *entry, CoglMatrixMode mode, CoglFramebuffer *framebuffer, CoglBool disable_flip) { g_assert (_cogl_has_private_feature (ctx, COGL_PRIVATE_FEATURE_GL_FIXED)); #if defined (HAVE_COGL_GL) || defined (HAVE_COGL_GLES) { CoglBool needs_flip; CoglMatrixEntryCache *cache; if (mode == COGL_MATRIX_PROJECTION) { /* Because Cogl defines texture coordinates to have a top left * origin and because offscreen framebuffers may be used for * rendering to textures we always render upside down to * offscreen buffers. Also for some backends we need to render * onscreen buffers upside-down too. */ if (disable_flip) needs_flip = FALSE; else needs_flip = cogl_is_offscreen (framebuffer); cache = &ctx->builtin_flushed_projection; } else { needs_flip = FALSE; if (mode == COGL_MATRIX_MODELVIEW) cache = &ctx->builtin_flushed_modelview; else cache = NULL; } /* We don't need to do anything if the state is the same */ if (!cache || _cogl_matrix_entry_cache_maybe_update (cache, entry, needs_flip)) { CoglBool is_identity; CoglMatrix matrix; if (entry->op == COGL_MATRIX_OP_LOAD_IDENTITY) is_identity = TRUE; else { is_identity = FALSE; cogl_matrix_entry_get (entry, &matrix); } if (needs_flip) { CoglMatrix flipped_matrix; cogl_matrix_multiply (&flipped_matrix, &ctx->y_flip_matrix, is_identity ? &ctx->identity_matrix : &matrix); _cogl_matrix_flush_to_gl_builtin (ctx, /* not identity */ FALSE, &flipped_matrix, mode); } else { _cogl_matrix_flush_to_gl_builtin (ctx, is_identity, &matrix, mode); } } } #endif } CoglBool cogl_matrix_entry_equal (CoglMatrixEntry *entry0, CoglMatrixEntry *entry1) { for (; entry0 && entry1; entry0 = entry0->parent, entry1 = entry1->parent) { entry0 = _cogl_matrix_entry_skip_saves (entry0); entry1 = _cogl_matrix_entry_skip_saves (entry1); if (entry0 == entry1) return TRUE; if (entry0->op != entry1->op) return FALSE; switch (entry0->op) { case COGL_MATRIX_OP_LOAD_IDENTITY: return TRUE; case COGL_MATRIX_OP_TRANSLATE: { CoglMatrixEntryTranslate *translate0 = (CoglMatrixEntryTranslate *)entry0; CoglMatrixEntryTranslate *translate1 = (CoglMatrixEntryTranslate *)entry1; /* We could perhaps use an epsilon to compare here? * I expect the false negatives are probaly never going to * be a problem and this is a bit cheaper. */ if (translate0->x != translate1->x || translate0->y != translate1->y || translate0->z != translate1->z) return FALSE; } break; case COGL_MATRIX_OP_ROTATE: { CoglMatrixEntryRotate *rotate0 = (CoglMatrixEntryRotate *)entry0; CoglMatrixEntryRotate *rotate1 = (CoglMatrixEntryRotate *)entry1; if (rotate0->angle != rotate1->angle || rotate0->x != rotate1->x || rotate0->y != rotate1->y || rotate0->z != rotate1->z) return FALSE; } break; case COGL_MATRIX_OP_ROTATE_QUATERNION: { CoglMatrixEntryRotateQuaternion *rotate0 = (CoglMatrixEntryRotateQuaternion *)entry0; CoglMatrixEntryRotateQuaternion *rotate1 = (CoglMatrixEntryRotateQuaternion *)entry1; int i; for (i = 0; i < 4; i++) if (rotate0->values[i] != rotate1->values[i]) return FALSE; } break; case COGL_MATRIX_OP_ROTATE_EULER: { CoglMatrixEntryRotateEuler *rotate0 = (CoglMatrixEntryRotateEuler *)entry0; CoglMatrixEntryRotateEuler *rotate1 = (CoglMatrixEntryRotateEuler *)entry1; if (rotate0->heading != rotate1->heading || rotate0->pitch != rotate1->pitch || rotate0->roll != rotate1->roll) return FALSE; } break; case COGL_MATRIX_OP_SCALE: { CoglMatrixEntryScale *scale0 = (CoglMatrixEntryScale *)entry0; CoglMatrixEntryScale *scale1 = (CoglMatrixEntryScale *)entry1; if (scale0->x != scale1->x || scale0->y != scale1->y || scale0->z != scale1->z) return FALSE; } break; case COGL_MATRIX_OP_MULTIPLY: { CoglMatrixEntryMultiply *mult0 = (CoglMatrixEntryMultiply *)entry0; CoglMatrixEntryMultiply *mult1 = (CoglMatrixEntryMultiply *)entry1; if (!cogl_matrix_equal (mult0->matrix, mult1->matrix)) return FALSE; } break; case COGL_MATRIX_OP_LOAD: { CoglMatrixEntryLoad *load0 = (CoglMatrixEntryLoad *)entry0; CoglMatrixEntryLoad *load1 = (CoglMatrixEntryLoad *)entry1; /* There's no need to check any further since an * _OP_LOAD makes all the ancestors redundant as far as * the final matrix value is concerned. */ return cogl_matrix_equal (load0->matrix, load1->matrix); } case COGL_MATRIX_OP_SAVE: /* We skip over saves above so we shouldn't see save entries */ g_warn_if_reached (); } } return FALSE; } void cogl_debug_matrix_entry_print (CoglMatrixEntry *entry) { int depth; CoglMatrixEntry *e; CoglMatrixEntry **children; int i; for (depth = 0, e = entry; e; e = e->parent) depth++; children = g_alloca (sizeof (CoglMatrixEntry) * depth); for (i = depth - 1, e = entry; i >= 0 && e; i--, e = e->parent) { children[i] = e; } g_print ("MatrixEntry %p =\n", entry); for (i = 0; i < depth; i++) { entry = children[i]; switch (entry->op) { case COGL_MATRIX_OP_LOAD_IDENTITY: g_print (" LOAD IDENTITY\n"); continue; case COGL_MATRIX_OP_TRANSLATE: { CoglMatrixEntryTranslate *translate = (CoglMatrixEntryTranslate *)entry; g_print (" TRANSLATE X=%f Y=%f Z=%f\n", translate->x, translate->y, translate->z); continue; } case COGL_MATRIX_OP_ROTATE: { CoglMatrixEntryRotate *rotate = (CoglMatrixEntryRotate *)entry; g_print (" ROTATE ANGLE=%f X=%f Y=%f Z=%f\n", rotate->angle, rotate->x, rotate->y, rotate->z); continue; } case COGL_MATRIX_OP_ROTATE_QUATERNION: { CoglMatrixEntryRotateQuaternion *rotate = (CoglMatrixEntryRotateQuaternion *)entry; g_print (" ROTATE QUATERNION w=%f x=%f y=%f z=%f\n", rotate->values[0], rotate->values[1], rotate->values[2], rotate->values[3]); continue; } case COGL_MATRIX_OP_ROTATE_EULER: { CoglMatrixEntryRotateEuler *rotate = (CoglMatrixEntryRotateEuler *)entry; g_print (" ROTATE EULER heading=%f pitch=%f roll=%f\n", rotate->heading, rotate->pitch, rotate->roll); continue; } case COGL_MATRIX_OP_SCALE: { CoglMatrixEntryScale *scale = (CoglMatrixEntryScale *)entry; g_print (" SCALE X=%f Y=%f Z=%f\n", scale->x, scale->y, scale->z); continue; } case COGL_MATRIX_OP_MULTIPLY: { CoglMatrixEntryMultiply *mult = (CoglMatrixEntryMultiply *)entry; g_print (" MULT:\n"); _cogl_matrix_prefix_print (" ", mult->matrix); continue; } case COGL_MATRIX_OP_LOAD: { CoglMatrixEntryLoad *load = (CoglMatrixEntryLoad *)entry; g_print (" LOAD:\n"); _cogl_matrix_prefix_print (" ", load->matrix); continue; } case COGL_MATRIX_OP_SAVE: g_print (" SAVE\n"); } } } void _cogl_matrix_entry_cache_init (CoglMatrixEntryCache *cache) { cache->entry = NULL; cache->flushed_identity = FALSE; cache->flipped = FALSE; } /* NB: This function can report false negatives since it never does a * deep comparison of the stack matrices. */ CoglBool _cogl_matrix_entry_cache_maybe_update (CoglMatrixEntryCache *cache, CoglMatrixEntry *entry, CoglBool flip) { CoglBool is_identity; CoglBool updated = FALSE; if (cache->flipped != flip) { cache->flipped = flip; updated = TRUE; } is_identity = (entry->op == COGL_MATRIX_OP_LOAD_IDENTITY); if (cache->flushed_identity != is_identity) { cache->flushed_identity = is_identity; updated = TRUE; } if (cache->entry != entry) { cogl_matrix_entry_ref (entry); if (cache->entry) cogl_matrix_entry_unref (cache->entry); cache->entry = entry; /* We want to make sure here that if the cache->entry and the * given @entry are both identity matrices then even though they * are different entries we don't want to consider this an * update... */ updated |= !is_identity; } return updated; } void _cogl_matrix_entry_cache_destroy (CoglMatrixEntryCache *cache) { if (cache->entry) cogl_matrix_entry_unref (cache->entry); }