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.
This commit is contained in:
Neil Roberts 2010-12-06 12:31:51 +00:00
parent 35b07f6b83
commit a05c701e6b
9 changed files with 396 additions and 30 deletions

View File

@ -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

View File

@ -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;

View File

@ -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;
}

View File

@ -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 */

View File

@ -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 ();

View File

@ -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

View File

@ -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 */

View File

@ -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;

View File

@ -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,