From a05c701e6b354f699b0886eb16b3863ed735d3c5 Mon Sep 17 00:00:00 2001 From: Neil Roberts Date: Mon, 6 Dec 2010 12:31:51 +0000 Subject: [PATCH] cogl: upload matrices with uniforms on GLES2 Once the GLES2 wrapper is removed we won't be able to upload the matrices with the fixed function API any more. The fixed function API gives a global state for setting the matrix but if a custom shader uniform is used for the matrices then the state is per program. _cogl_matrix_stack_flush_to_gl is called in a few places and it is assumed the current pipeline doesn't need to be flushed before it is called. To allow these semantics to continue to work, on GLES2 the matrix flush now just stores a reference to the matrix stack in the CoglContext. A pre_paint virtual is added to the progend which is called whenever a pipeline is flushed, even if the same pipeline was flushed already. This gives the GLSL progend a chance to upload the matrices to the uniforms. The combined modelview/projection matrix is only calculated if it is used. The generated programs end up never using the modelview or projection matrix so it usually only has to upload the combined matrix. When a matrix stack is flushed a reference is taked to it by the pipeline progend and the age is stored so that if the same state is used with the same program again then we don't need to reupload the uniform. --- cogl/cogl-context.c | 12 ++ cogl/cogl-context.h | 10 ++ cogl/cogl-matrix-stack.c | 127 ++++++++++--- cogl/cogl-matrix-stack.h | 10 ++ cogl/cogl-pipeline-opengl.c | 7 + cogl/cogl-pipeline-private.h | 4 + cogl/cogl-pipeline-progend-glsl.c | 250 +++++++++++++++++++++++++- cogl/cogl-pipeline-vertend-glsl.c | 4 + cogl/driver/gles/cogl-gles2-wrapper.c | 2 + 9 files changed, 396 insertions(+), 30 deletions(-) diff --git a/cogl/cogl-context.c b/cogl/cogl-context.c index 2b03fb05d..0c08c148f 100644 --- a/cogl/cogl-context.c +++ b/cogl/cogl-context.c @@ -251,6 +251,11 @@ cogl_create_context (void) * makes things a bit simpler for us. */ GE (glEnable (GL_ALPHA_TEST)); +#ifdef HAVE_COGL_GLES2 + _context->flushed_modelview_stack = NULL; + _context->flushed_projection_stack = NULL; +#endif + /* Create default textures used for fall backs */ _context->default_gl_texture_2d_tex = cogl_texture_new_from_data (1, /* width */ @@ -363,6 +368,13 @@ _cogl_destroy_context (void) g_slist_free (_context->texture_types); g_slist_free (_context->buffer_types); +#ifdef HAVE_COGL_GLES2 + if (_context->flushed_modelview_stack) + cogl_object_unref (_context->flushed_modelview_stack); + if (_context->flushed_projection_stack) + cogl_object_unref (_context->flushed_projection_stack); +#endif + #ifdef HAVE_COGL_GL g_hash_table_unref (_context->arbfp_cache); #endif diff --git a/cogl/cogl-context.h b/cogl/cogl-context.h index b26dfbcee..38be97197 100644 --- a/cogl/cogl-context.h +++ b/cogl/cogl-context.h @@ -76,6 +76,16 @@ typedef struct /* Client-side matrix stack or NULL if none */ CoglMatrixMode flushed_matrix_mode; + /* On GLES2 we need to track the matrices separately because the are + stored in GLSL uniforms rather than using the fixed function + API. We keep track of the matrix stack that Cogl is trying to + flush so we can flush it later after the program is generated. A + reference is taken on the stacks. */ +#ifdef HAVE_COGL_GLES2 + CoglMatrixStack *flushed_modelview_stack; + CoglMatrixStack *flushed_projection_stack; +#endif /* HAVE_COGL_GLES2 */ + GArray *texture_units; int active_texture_unit; diff --git a/cogl/cogl-matrix-stack.c b/cogl/cogl-matrix-stack.c index d4defd1ce..d0d34ef97 100644 --- a/cogl/cogl-matrix-stack.c +++ b/cogl/cogl-matrix-stack.c @@ -390,6 +390,64 @@ _cogl_matrix_stack_set (CoglMatrixStack *stack, /* mark dirty */ stack->flushed_state = NULL; state->is_identity = FALSE; + stack->age++; +} + +#ifndef HAVE_COGL_GLES2 + +static void +flush_to_fixed_api_gl (gboolean is_identity, + const CoglMatrix *matrix, + void *user_data) +{ + CoglMatrixStack *stack = user_data; + + if (is_identity) + { + if (!stack->flushed_identity) + GE (glLoadIdentity ()); + stack->flushed_identity = TRUE; + } + else + { + GE (glLoadMatrixf (cogl_matrix_get_array (matrix)) ); + stack->flushed_identity = FALSE; + } +} + +#endif /* HAVE_COGL_GLES2 */ + +void +_cogl_matrix_stack_prepare_for_flush (CoglMatrixStack *stack, + CoglMatrixMode mode, + CoglMatrixStackFlushFunc callback, + void *user_data) +{ + CoglMatrixState *state; + + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + + state = _cogl_matrix_stack_top (stack); + + /* 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. + */ + if (mode == COGL_MATRIX_PROJECTION && + cogl_is_offscreen (_cogl_get_framebuffer ())) + { + CoglMatrix flipped_projection; + CoglMatrix *projection = + state->is_identity ? &ctx->identity_matrix : &state->matrix; + + cogl_matrix_multiply (&flipped_projection, + &ctx->y_flip_matrix, projection); + callback (FALSE, &flipped_projection, user_data); + } + else + callback (state->is_identity, + state->is_identity ? &ctx->identity_matrix : &state->matrix, + user_data); } void @@ -402,6 +460,40 @@ _cogl_matrix_stack_flush_to_gl (CoglMatrixStack *stack, state = _cogl_matrix_stack_top (stack); +#ifdef HAVE_COGL_GLES2 + + /* Under GLES2 we need to flush the matrices differently because + they are stored in uniforms attached to the program instead of + the global GL context state. At this point we can't be sure that + the right program will be generated so instead we'll just store a + reference to the matrix stack that is intended to be flushed and + update the uniform once the program is ready. */ + + switch (mode) + { + case COGL_MATRIX_MODELVIEW: + cogl_object_ref (stack); + if (ctx->flushed_modelview_stack) + cogl_object_unref (ctx->flushed_modelview_stack); + ctx->flushed_modelview_stack = stack; + break; + + case COGL_MATRIX_PROJECTION: + cogl_object_ref (stack); + if (ctx->flushed_projection_stack) + cogl_object_unref (ctx->flushed_projection_stack); + ctx->flushed_projection_stack = stack; + break; + + case COGL_MATRIX_TEXTURE: + /* This shouldn't happen because the texture matrices are + handled by the GLSL pipeline backend */ + g_assert_not_reached (); + break; + } + +#else /* HAVE_COGL_GLES2 */ + if (stack->flushed_state == state) return; @@ -428,36 +520,13 @@ _cogl_matrix_stack_flush_to_gl (CoglMatrixStack *stack, ctx->flushed_matrix_mode = mode; } - /* 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. - */ - if (mode == COGL_MATRIX_PROJECTION && - cogl_is_offscreen (_cogl_get_framebuffer ())) - { - CoglMatrix flipped_projection; - CoglMatrix *projection = - state->is_identity ? &ctx->identity_matrix : &state->matrix; + _cogl_matrix_stack_prepare_for_flush (stack, + mode, + flush_to_fixed_api_gl, + stack); + +#endif /* HAVE_COGL_GLES2 */ - cogl_matrix_multiply (&flipped_projection, - &ctx->y_flip_matrix, projection); - GE (glLoadMatrixf (cogl_matrix_get_array (&flipped_projection))); - stack->flushed_identity = FALSE; - } - else - { - if (state->is_identity) - { - if (!stack->flushed_identity) - GE (glLoadIdentity ()); - stack->flushed_identity = TRUE; - } - else - { - GE (glLoadMatrixf (cogl_matrix_get_array (&state->matrix))); - stack->flushed_identity = FALSE; - } - } stack->flushed_state = state; } diff --git a/cogl/cogl-matrix-stack.h b/cogl/cogl-matrix-stack.h index 5c8b954a1..3368f1b69 100644 --- a/cogl/cogl-matrix-stack.h +++ b/cogl/cogl-matrix-stack.h @@ -39,6 +39,10 @@ typedef enum { COGL_MATRIX_TEXTURE } CoglMatrixMode; +typedef void (* CoglMatrixStackFlushFunc) (gboolean is_identity, + const CoglMatrix *matrix, + void *user_data); + CoglMatrixStack * _cogl_matrix_stack_new (void); @@ -117,4 +121,10 @@ _cogl_matrix_stack_get_age (CoglMatrixStack *stack); gboolean _cogl_matrix_stack_has_identity_flag (CoglMatrixStack *stack); +void +_cogl_matrix_stack_prepare_for_flush (CoglMatrixStack *stack, + CoglMatrixMode mode, + CoglMatrixStackFlushFunc callback, + void *user_data); + #endif /* __COGL_MATRIX_STACK_H */ diff --git a/cogl/cogl-pipeline-opengl.c b/cogl/cogl-pipeline-opengl.c index 1700dc887..da37749d1 100644 --- a/cogl/cogl-pipeline-opengl.c +++ b/cogl/cogl-pipeline-opengl.c @@ -1260,6 +1260,13 @@ done: } #endif + /* Give any progends a chance to update any uniforms that might not + depend on the material state. This is used on GLES2 to update the + matrices */ + for (i = 0; i < COGL_PIPELINE_N_PROGENDS; i++) + if (_cogl_pipeline_progends[i]->pre_paint) + _cogl_pipeline_progends[i]->pre_paint (pipeline); + /* Handle the fact that OpenGL associates texture filter and wrap * modes with the texture objects not the texture units... */ foreach_texture_unit_update_filter_and_wrap_modes (); diff --git a/cogl/cogl-pipeline-private.h b/cogl/cogl-pipeline-private.h index 6720a0274..cb81cf3c9 100644 --- a/cogl/cogl-pipeline-private.h +++ b/cogl/cogl-pipeline-private.h @@ -735,6 +735,10 @@ typedef struct void (*layer_pre_change_notify) (CoglPipeline *owner, CoglPipelineLayer *layer, CoglPipelineLayerState change); + /* This is called after all of the other functions whenever the + pipeline is flushed, even if the pipeline hasn't changed since + the last flush */ + void (* pre_paint) (CoglPipeline *pipeline); } CoglPipelineProgend; typedef enum diff --git a/cogl/cogl-pipeline-progend-glsl.c b/cogl/cogl-pipeline-progend-glsl.c index 647e91954..a193f9462 100644 --- a/cogl/cogl-pipeline-progend-glsl.c +++ b/cogl/cogl-pipeline-progend-glsl.c @@ -63,8 +63,11 @@ const CoglPipelineProgend _cogl_pipeline_glsl_progend; typedef struct _UnitState { unsigned int dirty_combine_constant:1; + unsigned int dirty_texture_matrix:1; GLint combine_constant_uniform; + + GLint texture_matrix_uniform; } UnitState; typedef struct @@ -106,6 +109,16 @@ typedef struct /* We only allocate this array if more than one tex coord attribute is requested because most pipelines will only use one layer */ GArray *tex_coord_attribute_locations; + + GLint modelview_uniform; + GLint projection_uniform; + GLint mvp_uniform; + + CoglMatrixStack *flushed_modelview_stack; + unsigned int flushed_modelview_stack_age; + gboolean flushed_modelview_is_identity; + CoglMatrixStack *flushed_projection_stack; + unsigned int flushed_projection_stack_age; #endif /* We need to track the last pipeline that the program was used with @@ -253,6 +266,22 @@ clear_attribute_cache (CoglPipelineProgendPrivate *priv) } } +static void +clear_flushed_matrix_stacks (CoglPipelineProgendPrivate *priv) +{ + if (priv->flushed_modelview_stack) + { + cogl_object_unref (priv->flushed_modelview_stack); + priv->flushed_modelview_stack = NULL; + } + if (priv->flushed_projection_stack) + { + cogl_object_unref (priv->flushed_projection_stack); + priv->flushed_projection_stack = NULL; + } + priv->flushed_modelview_is_identity = FALSE; +} + #endif /* HAVE_COGL_GLES2 */ static void @@ -264,6 +293,7 @@ destroy_glsl_priv (void *user_data) { #ifdef HAVE_COGL_GLES2 clear_attribute_cache (priv); + clear_flushed_matrix_stacks (priv); #endif if (priv->program) @@ -377,6 +407,20 @@ get_uniform_cb (CoglPipeline *pipeline, unit_state->combine_constant_uniform = uniform_location; +#ifdef HAVE_COGL_GLES2 + + g_string_set_size (ctx->codegen_source_buffer, 0); + g_string_append_printf (ctx->codegen_source_buffer, + "cogl_texture_matrix[%i]", state->unit); + + GE_RET( uniform_location, + glGetUniformLocation (state->gl_program, + ctx->codegen_source_buffer->str) ); + + unit_state->texture_matrix_uniform = uniform_location; + +#endif + state->unit++; return TRUE; @@ -404,6 +448,24 @@ update_constants_cb (CoglPipeline *pipeline, 1, constant)); unit_state->dirty_combine_constant = FALSE; } + +#ifdef HAVE_COGL_GLES2 + + if (unit_state->texture_matrix_uniform != -1 && + (state->update_all || unit_state->dirty_texture_matrix)) + { + const CoglMatrix *matrix; + const float *array; + + matrix = _cogl_pipeline_get_layer_matrix (pipeline, layer_index); + array = cogl_matrix_get_array (matrix); + GE (glUniformMatrix4fv (unit_state->texture_matrix_uniform, + 1, FALSE, array)); + unit_state->dirty_texture_matrix = FALSE; + } + +#endif /* HAVE_COGL_GLES2 */ + return TRUE; } @@ -496,6 +558,9 @@ _cogl_pipeline_progend_glsl_end (CoglPipeline *pipeline, cogl_pipeline_get_n_layers (pipeline)); #ifdef HAVE_COGL_GLES2 priv->tex_coord_attribute_locations = NULL; + priv->flushed_modelview_stack = NULL; + priv->flushed_modelview_is_identity = FALSE; + priv->flushed_projection_stack = NULL; #endif set_glsl_priv (authority, priv); } @@ -613,11 +678,24 @@ _cogl_pipeline_progend_glsl_end (CoglPipeline *pipeline, if (program_changed) { clear_attribute_cache (priv); + clear_flushed_matrix_stacks (priv); GE_RET( priv->alpha_test_reference_uniform, glGetUniformLocation (gl_program, "_cogl_alpha_test_ref") ); + GE_RET( priv->modelview_uniform, + glGetUniformLocation (gl_program, + "cogl_modelview_matrix") ); + + GE_RET( priv->projection_uniform, + glGetUniformLocation (gl_program, + "cogl_projection_matrix") ); + + GE_RET( priv->mvp_uniform, + glGetUniformLocation (gl_program, + "cogl_modelview_projection_matrix") ); + GE_RET( priv->point_size_uniform, glGetUniformLocation (gl_program, "cogl_point_size_in") ); @@ -695,13 +773,183 @@ _cogl_pipeline_progend_glsl_layer_pre_change_notify ( priv->unit_state[unit_index].dirty_combine_constant = TRUE; } } + + if (change & COGL_PIPELINE_LAYER_STATE_USER_MATRIX) + { + CoglPipelineProgendPrivate *priv = get_glsl_priv (owner); + if (priv) + { + int unit_index = _cogl_pipeline_layer_get_unit_index (layer); + priv->unit_state[unit_index].dirty_texture_matrix = TRUE; + } + } } +#ifdef HAVE_COGL_GLES2 + +static void +flush_modelview_cb (gboolean is_identity, + const CoglMatrix *matrix, + void *user_data) +{ + CoglPipelineProgendPrivate *priv = user_data; + + GE( glUniformMatrix4fv (priv->modelview_uniform, 1, FALSE, + cogl_matrix_get_array (matrix)) ); +} + +static void +flush_projection_cb (gboolean is_identity, + const CoglMatrix *matrix, + void *user_data) +{ + CoglPipelineProgendPrivate *priv = user_data; + + GE( glUniformMatrix4fv (priv->projection_uniform, 1, FALSE, + cogl_matrix_get_array (matrix)) ); +} + +typedef struct +{ + CoglPipelineProgendPrivate *priv; + const CoglMatrix *projection_matrix; +} FlushCombinedData; + +static void +flush_combined_step_two_cb (gboolean is_identity, + const CoglMatrix *matrix, + void *user_data) +{ + FlushCombinedData *data = user_data; + CoglMatrix mvp_matrix; + + /* If the modelview is the identity then we can bypass the matrix + multiplication */ + if (is_identity) + GE( glUniformMatrix4fv (data->priv->mvp_uniform, 1, FALSE, + cogl_matrix_get_array (data->projection_matrix)) ); + else + { + cogl_matrix_multiply (&mvp_matrix, + data->projection_matrix, + matrix); + + GE( glUniformMatrix4fv (data->priv->mvp_uniform, 1, FALSE, + cogl_matrix_get_array (&mvp_matrix)) ); + } +} + +static void +flush_combined_step_one_cb (gboolean is_identity, + const CoglMatrix *matrix, + void *user_data) +{ + FlushCombinedData data; + + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + + data.priv = user_data; + data.projection_matrix = matrix; + + _cogl_matrix_stack_prepare_for_flush (ctx->flushed_modelview_stack, + COGL_MATRIX_MODELVIEW, + flush_combined_step_two_cb, + &data); +} + +static void +_cogl_pipeline_progend_glsl_pre_paint (CoglPipeline *pipeline) +{ + CoglPipelineProgendPrivate *priv = get_glsl_priv (pipeline); + gboolean modelview_changed; + gboolean projection_changed; + + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + + /* We only need to update the matrices if we're using the the GLSL + vertend, but this is a requirement on GLES2 anyway */ + g_return_if_fail (pipeline->vertend == COGL_PIPELINE_VERTEND_GLSL); + + priv = get_glsl_priv (pipeline); + + /* An initial pipeline is flushed while creating the context. At + this point there are no matrices flushed so we can't do + anything */ + if (ctx->flushed_modelview_stack == NULL || + ctx->flushed_projection_stack == NULL) + return; + + /* When flushing from the journal the modelview matrix is usually + the identity matrix so it makes sense to optimise this case by + specifically checking whether we already have the identity matrix + which will catch a lot of common cases of redundant flushing */ + if (priv->flushed_modelview_is_identity && + _cogl_matrix_stack_has_identity_flag (ctx->flushed_modelview_stack)) + modelview_changed = FALSE; + else + modelview_changed = + priv->flushed_modelview_stack != ctx->flushed_modelview_stack || + priv->flushed_modelview_stack_age != + _cogl_matrix_stack_get_age (priv->flushed_modelview_stack); + + projection_changed = + priv->flushed_projection_stack != ctx->flushed_projection_stack || + priv->flushed_projection_stack_age != + _cogl_matrix_stack_get_age (priv->flushed_projection_stack); + + if (modelview_changed) + { + cogl_object_ref (ctx->flushed_modelview_stack); + if (priv->flushed_modelview_stack) + cogl_object_unref (priv->flushed_modelview_stack); + priv->flushed_modelview_stack = ctx->flushed_modelview_stack; + priv->flushed_modelview_stack_age = + _cogl_matrix_stack_get_age (ctx->flushed_modelview_stack); + priv->flushed_modelview_is_identity = + _cogl_matrix_stack_has_identity_flag (ctx->flushed_modelview_stack); + + if (priv->modelview_uniform != -1) + _cogl_matrix_stack_prepare_for_flush (priv->flushed_modelview_stack, + COGL_MATRIX_MODELVIEW, + flush_modelview_cb, + priv); + } + + if (projection_changed) + { + cogl_object_ref (ctx->flushed_projection_stack); + if (priv->flushed_projection_stack) + cogl_object_unref (priv->flushed_projection_stack); + priv->flushed_projection_stack = ctx->flushed_projection_stack; + priv->flushed_projection_stack_age = + _cogl_matrix_stack_get_age (ctx->flushed_projection_stack); + + if (priv->projection_uniform != -1) + _cogl_matrix_stack_prepare_for_flush (priv->flushed_projection_stack, + COGL_MATRIX_PROJECTION, + flush_projection_cb, + priv); + } + + if (priv->mvp_uniform != -1 && (modelview_changed || projection_changed)) + _cogl_matrix_stack_prepare_for_flush (ctx->flushed_projection_stack, + COGL_MATRIX_PROJECTION, + flush_combined_step_one_cb, + priv); +} + +#endif + const CoglPipelineProgend _cogl_pipeline_glsl_progend = { _cogl_pipeline_progend_glsl_end, _cogl_pipeline_progend_glsl_pre_change_notify, - _cogl_pipeline_progend_glsl_layer_pre_change_notify + _cogl_pipeline_progend_glsl_layer_pre_change_notify, +#ifdef HAVE_COGL_GLES2 + _cogl_pipeline_progend_glsl_pre_paint +#else + NULL +#endif }; #endif /* COGL_PIPELINE_PROGEND_GLSL */ diff --git a/cogl/cogl-pipeline-vertend-glsl.c b/cogl/cogl-pipeline-vertend-glsl.c index f863f1552..6a1f68c93 100644 --- a/cogl/cogl-pipeline-vertend-glsl.c +++ b/cogl/cogl-pipeline-vertend-glsl.c @@ -233,6 +233,8 @@ _cogl_pipeline_vertend_glsl_add_layer (CoglPipeline *pipeline, unit_index = _cogl_pipeline_layer_get_unit_index (layer); +#ifndef HAVE_COGL_GLES2 + /* We are using the fixed function uniforms for the user matrices and the only way to set them is with the fixed function API so we still need to flush them here */ @@ -249,6 +251,8 @@ _cogl_pipeline_vertend_glsl_add_layer (CoglPipeline *pipeline, _cogl_matrix_stack_flush_to_gl (unit->matrix_stack, COGL_MATRIX_TEXTURE); } +#endif /* HAVE_COGL_GLES2 */ + if (priv->source == NULL) return TRUE; diff --git a/cogl/driver/gles/cogl-gles2-wrapper.c b/cogl/driver/gles/cogl-gles2-wrapper.c index 437702fed..0b5d784f9 100644 --- a/cogl/driver/gles/cogl-gles2-wrapper.c +++ b/cogl/driver/gles/cogl-gles2-wrapper.c @@ -590,6 +590,7 @@ _cogl_wrap_prepare_for_draw (void) /* Make sure all of the uniforms are up to date */ if (w->dirty_uniforms) { +#if 0 /* <-- this will all be removed when this wrapper is removed */ if ((w->dirty_uniforms & (COGL_GLES2_DIRTY_MVP_MATRIX | COGL_GLES2_DIRTY_MODELVIEW_MATRIX))) { @@ -626,6 +627,7 @@ _cogl_wrap_prepare_for_draw (void) (float *) &texture_unit->texture_matrix); } } +#endif /* commented out */ if ((w->dirty_uniforms & COGL_GLES2_DIRTY_POINT_SIZE)) glUniform1f (program->uniforms.point_size_uniform,