From 68b3643b2543c0f5f787f95663c8e9195e291227 Mon Sep 17 00:00:00 2001 From: Neil Roberts Date: Mon, 28 Nov 2011 19:58:15 +0000 Subject: [PATCH] snippet: Add a hook for the layer texture coordinate transformation This adds a hook called COGL_SNIPPET_HOOK_TEXTURE_COORD_TRANSFORM. This can be used to alter the application of the layer user matrix to a texture coordinate or it can bypass it altogether. This is the first per-layer hook that affects the vertex shader state so the patch includes the boilerplate needed to get that to work. Reviewed-by: Robert Bragg --- cogl/cogl-pipeline-layer-private.h | 9 +++- cogl/cogl-pipeline-layer-state-private.h | 9 ++++ cogl/cogl-pipeline-layer-state.c | 60 +++++++++++++++++++++++- cogl/cogl-pipeline-layer.c | 18 +++++++ cogl/cogl-pipeline-snippet-private.h | 6 +++ cogl/cogl-pipeline-snippet.c | 2 +- cogl/cogl-pipeline-state.c | 29 +++++++++++- cogl/cogl-pipeline-vertend-glsl.c | 51 +++++++++++++++++++- cogl/cogl-pipeline.c | 5 +- cogl/cogl-snippet.h | 45 +++++++++++++++++- tests/conform/test-snippets.c | 46 +++++++++++++++++- 11 files changed, 268 insertions(+), 12 deletions(-) diff --git a/cogl/cogl-pipeline-layer-private.h b/cogl/cogl-pipeline-layer-private.h index e42859938..0bd703483 100644 --- a/cogl/cogl-pipeline-layer-private.h +++ b/cogl/cogl-pipeline-layer-private.h @@ -79,6 +79,7 @@ typedef enum COGL_PIPELINE_LAYER_STATE_COMBINE_CONSTANT_INDEX, COGL_PIPELINE_LAYER_STATE_USER_MATRIX_INDEX, COGL_PIPELINE_LAYER_STATE_POINT_SPRITE_COORDS_INDEX, + COGL_PIPELINE_LAYER_STATE_VERTEX_SNIPPETS_INDEX, COGL_PIPELINE_LAYER_STATE_FRAGMENT_SNIPPETS_INDEX, /* note: layers don't currently have any non-sparse state */ @@ -117,6 +118,8 @@ typedef enum COGL_PIPELINE_LAYER_STATE_POINT_SPRITE_COORDS = 1L<big_state->point_sprite_coords; } +static void +_cogl_pipeline_layer_add_vertex_snippet (CoglPipeline *pipeline, + int layer_index, + CoglSnippet *snippet) +{ + CoglPipelineLayerState change = COGL_PIPELINE_LAYER_STATE_VERTEX_SNIPPETS; + CoglPipelineLayer *layer, *authority; + + /* Note: this will ensure that the layer exists, creating one if it + * doesn't already. + * + * Note: If the layer already existed it's possibly owned by another + * pipeline. If the layer is created then it will be owned by + * pipeline. */ + layer = _cogl_pipeline_get_layer (pipeline, layer_index); + + /* Now find the ancestor of the layer that is the authority for the + * state we want to change */ + authority = _cogl_pipeline_layer_get_authority (layer, change); + + layer = _cogl_pipeline_layer_pre_change_notify (pipeline, layer, change); + + _cogl_pipeline_snippet_list_add (&layer->big_state->vertex_snippets, + snippet); + + /* If we weren't previously the authority on this state then we need + * to extended our differences mask and so it's possible that some + * of our ancestry will now become redundant, so we aim to reparent + * ourselves if that's true... */ + if (layer != authority) + { + layer->differences |= change; + _cogl_pipeline_layer_prune_redundant_ancestry (layer); + } +} + static void _cogl_pipeline_layer_add_fragment_snippet (CoglPipeline *pipeline, int layer_index, @@ -821,8 +857,9 @@ cogl_pipeline_add_layer_snippet (CoglPipeline *pipeline, _COGL_RETURN_IF_FAIL (snippet->hook >= COGL_SNIPPET_FIRST_LAYER_HOOK); if (snippet->hook < COGL_SNIPPET_FIRST_LAYER_FRAGMENT_HOOK) - /* TODO */ - g_assert_not_reached (); + _cogl_pipeline_layer_add_vertex_snippet (pipeline, + layer_index, + snippet); else _cogl_pipeline_layer_add_fragment_snippet (pipeline, layer_index, @@ -966,6 +1003,16 @@ _cogl_pipeline_layer_point_sprite_coords_equal (CoglPipelineLayer *authority0, return big_state0->point_sprite_coords == big_state1->point_sprite_coords; } +gboolean +_cogl_pipeline_layer_vertex_snippets_equal (CoglPipelineLayer *authority0, + CoglPipelineLayer *authority1) +{ + return _cogl_pipeline_snippet_list_equal (&authority0->big_state-> + vertex_snippets, + &authority1->big_state-> + vertex_snippets); +} + gboolean _cogl_pipeline_layer_fragment_snippets_equal (CoglPipelineLayer *authority0, CoglPipelineLayer *authority1) @@ -1729,6 +1776,15 @@ _cogl_pipeline_layer_hash_point_sprite_state (CoglPipelineLayer *authority, sizeof (big_state->point_sprite_coords)); } +void +_cogl_pipeline_layer_hash_vertex_snippets_state (CoglPipelineLayer *authority, + CoglPipelineLayer **authorities, + CoglPipelineHashState *state) +{ + _cogl_pipeline_snippet_list_hash (&authority->big_state->vertex_snippets, + &state->hash); +} + void _cogl_pipeline_layer_hash_fragment_snippets_state (CoglPipelineLayer *authority, CoglPipelineLayer **authorities, diff --git a/cogl/cogl-pipeline-layer.c b/cogl/cogl-pipeline-layer.c index 76d17e743..d2d799d50 100644 --- a/cogl/cogl-pipeline-layer.c +++ b/cogl/cogl-pipeline-layer.c @@ -114,6 +114,10 @@ _cogl_pipeline_layer_has_alpha (CoglPipelineLayer *layer) } /* All bets are off if the layer contains any snippets */ + snippets_authority = _cogl_pipeline_layer_get_authority + (layer, COGL_PIPELINE_LAYER_STATE_VERTEX_SNIPPETS); + if (!COGL_LIST_EMPTY (&snippets_authority->big_state->vertex_snippets)) + return TRUE; snippets_authority = _cogl_pipeline_layer_get_authority (layer, COGL_PIPELINE_LAYER_STATE_FRAGMENT_SNIPPETS); if (!COGL_LIST_EMPTY (&snippets_authority->big_state->fragment_snippets)) @@ -211,6 +215,11 @@ _cogl_pipeline_layer_init_multi_property_sparse_state ( } break; } + case COGL_PIPELINE_LAYER_STATE_VERTEX_SNIPPETS: + _cogl_pipeline_snippet_list_copy (&layer->big_state->vertex_snippets, + &authority->big_state-> + vertex_snippets); + break; case COGL_PIPELINE_LAYER_STATE_FRAGMENT_SNIPPETS: _cogl_pipeline_snippet_list_copy (&layer->big_state->fragment_snippets, &authority->big_state-> @@ -591,6 +600,12 @@ _cogl_pipeline_layer_equal (CoglPipelineLayer *layer0, _cogl_pipeline_layer_point_sprite_coords_equal)) return FALSE; + if (layers_difference & COGL_PIPELINE_LAYER_STATE_VERTEX_SNIPPETS && + !layer_state_equal (COGL_PIPELINE_LAYER_STATE_VERTEX_SNIPPETS_INDEX, + authorities0, authorities1, + _cogl_pipeline_layer_vertex_snippets_equal)) + return FALSE; + if (layers_difference & COGL_PIPELINE_LAYER_STATE_FRAGMENT_SNIPPETS && !layer_state_equal (COGL_PIPELINE_LAYER_STATE_FRAGMENT_SNIPPETS_INDEX, authorities0, authorities1, @@ -609,6 +624,9 @@ _cogl_pipeline_layer_free (CoglPipelineLayer *layer) layer->texture != NULL) cogl_object_unref (layer->texture); + if (layer->differences & COGL_PIPELINE_LAYER_STATE_VERTEX_SNIPPETS) + _cogl_pipeline_snippet_list_free (&layer->big_state->vertex_snippets); + if (layer->differences & COGL_PIPELINE_LAYER_STATE_FRAGMENT_SNIPPETS) _cogl_pipeline_snippet_list_free (&layer->big_state->fragment_snippets); diff --git a/cogl/cogl-pipeline-snippet-private.h b/cogl/cogl-pipeline-snippet-private.h index 24ff610bc..823193e84 100644 --- a/cogl/cogl-pipeline-snippet-private.h +++ b/cogl/cogl-pipeline-snippet-private.h @@ -68,6 +68,12 @@ typedef struct NULL */ const char *return_variable; + /* If this is TRUE then it won't allocate a separate variable for + the return value. Instead it is expected that the snippet will + modify one of the argument variables directly and that will be + returned */ + gboolean return_variable_is_argument; + /* The argument names or NULL if there are none */ const char *arguments; diff --git a/cogl/cogl-pipeline-snippet.c b/cogl/cogl-pipeline-snippet.c index 33bf22346..2e37741b3 100644 --- a/cogl/cogl-pipeline-snippet.c +++ b/cogl/cogl-pipeline-snippet.c @@ -131,7 +131,7 @@ _cogl_pipeline_snippet_generate_code (const CoglPipelineSnippetData *data) ")\n" "{\n"); - if (data->return_type) + if (data->return_type && !data->return_variable_is_argument) g_string_append_printf (data->source_buf, " %s %s;\n" "\n", diff --git a/cogl/cogl-pipeline-state.c b/cogl/cogl-pipeline-state.c index 44384ed5c..3e010f2b3 100644 --- a/cogl/cogl-pipeline-state.c +++ b/cogl/cogl-pipeline-state.c @@ -1627,10 +1627,37 @@ _cogl_pipeline_has_non_layer_vertex_snippets (CoglPipeline *pipeline) return !COGL_LIST_EMPTY (&authority->big_state->vertex_snippets); } +static gboolean +check_layer_has_vertex_snippet (CoglPipelineLayer *layer, + void *user_data) +{ + unsigned long state = COGL_PIPELINE_LAYER_STATE_VERTEX_SNIPPETS; + CoglPipelineLayer *authority = + _cogl_pipeline_layer_get_authority (layer, state); + gboolean *found_vertex_snippet = user_data; + + if (!COGL_LIST_EMPTY (&authority->big_state->vertex_snippets)) + { + *found_vertex_snippet = TRUE; + return FALSE; + } + + return TRUE; +} + gboolean _cogl_pipeline_has_vertex_snippets (CoglPipeline *pipeline) { - return _cogl_pipeline_has_non_layer_vertex_snippets (pipeline); + gboolean found_vertex_snippet = FALSE; + + if (_cogl_pipeline_has_non_layer_vertex_snippets (pipeline)) + return TRUE; + + _cogl_pipeline_foreach_layer_internal (pipeline, + check_layer_has_vertex_snippet, + &found_vertex_snippet); + + return found_vertex_snippet; } gboolean diff --git a/cogl/cogl-pipeline-vertend-glsl.c b/cogl/cogl-pipeline-vertend-glsl.c index a6f04ca6d..da0958c3f 100644 --- a/cogl/cogl-pipeline-vertend-glsl.c +++ b/cogl/cogl-pipeline-vertend-glsl.c @@ -140,6 +140,15 @@ get_vertex_snippets (CoglPipeline *pipeline) return &pipeline->big_state->vertex_snippets; } +static CoglPipelineSnippetList * +get_layer_vertex_snippets (CoglPipelineLayer *layer) +{ + unsigned long state = COGL_PIPELINE_LAYER_STATE_VERTEX_SNIPPETS; + layer = _cogl_pipeline_layer_get_authority (layer, state); + + return &layer->big_state->vertex_snippets; +} + static gboolean _cogl_pipeline_vertend_glsl_start (CoglPipeline *pipeline, int n_layers, @@ -293,6 +302,7 @@ _cogl_pipeline_vertend_glsl_add_layer (CoglPipeline *pipeline, unsigned long layers_difference) { CoglPipelineShaderState *shader_state; + CoglPipelineSnippetData snippet_data; int unit_index; _COGL_GET_CONTEXT (ctx, FALSE); @@ -338,10 +348,47 @@ _cogl_pipeline_vertend_glsl_add_layer (CoglPipeline *pipeline, * avoid setting them if not */ + g_string_append_printf (shader_state->header, + "vec4\n" + "cogl_real_transform_layer%i (mat4 matrix, " + "vec4 tex_coord)\n" + "{\n" + " return matrix * tex_coord;\n" + "}\n", + unit_index); + + /* Wrap the layer code in any snippets that have been hooked */ + memset (&snippet_data, 0, sizeof (snippet_data)); + snippet_data.snippets = get_layer_vertex_snippets (layer); + snippet_data.hook = COGL_SNIPPET_HOOK_TEXTURE_COORD_TRANSFORM; + snippet_data.chain_function = g_strdup_printf ("cogl_real_transform_layer%i", + unit_index); + snippet_data.final_name = g_strdup_printf ("cogl_transform_layer%i", + unit_index); + snippet_data.function_prefix = g_strdup_printf ("cogl_transform_layer%i", + unit_index); + snippet_data.return_type = "vec4"; + snippet_data.return_variable = "cogl_tex_coord"; + snippet_data.return_variable_is_argument = TRUE; + snippet_data.arguments = "cogl_matrix, cogl_tex_coord"; + snippet_data.argument_declarations = "mat4 cogl_matrix, vec4 cogl_tex_coord"; + snippet_data.source_buf = shader_state->header; + + _cogl_pipeline_snippet_generate_code (&snippet_data); + + g_free ((char *) snippet_data.chain_function); + g_free ((char *) snippet_data.final_name); + g_free ((char *) snippet_data.function_prefix); + g_string_append_printf (shader_state->source, " cogl_tex_coord_out[%i] = " - "cogl_texture_matrix[%i] * cogl_tex_coord%i_in;\n", - unit_index, unit_index, unit_index); + "cogl_transform_layer%i (cogl_texture_matrix[%i],\n" + " " + " cogl_tex_coord%i_in);\n", + unit_index, + unit_index, + unit_index, + unit_index); return TRUE; } diff --git a/cogl/cogl-pipeline.c b/cogl/cogl-pipeline.c index 026c9c65b..72777822d 100644 --- a/cogl/cogl-pipeline.c +++ b/cogl/cogl-pipeline.c @@ -2612,6 +2612,9 @@ _cogl_pipeline_init_layer_state_hash_functions (void) layer_state_hash_functions[COGL_PIPELINE_LAYER_STATE_USER_MATRIX_INDEX] = _cogl_pipeline_layer_hash_user_matrix_state; _index = COGL_PIPELINE_LAYER_STATE_POINT_SPRITE_COORDS_INDEX; + layer_state_hash_functions[_index] = + _cogl_pipeline_layer_hash_point_sprite_state; + _index = COGL_PIPELINE_LAYER_STATE_VERTEX_SNIPPETS_INDEX; layer_state_hash_functions[_index] = _cogl_pipeline_layer_hash_point_sprite_state; _index = COGL_PIPELINE_LAYER_STATE_FRAGMENT_SNIPPETS_INDEX; @@ -2619,7 +2622,7 @@ _cogl_pipeline_init_layer_state_hash_functions (void) _cogl_pipeline_layer_hash_fragment_snippets_state; /* So we get a big error if we forget to update this code! */ - g_assert (COGL_PIPELINE_LAYER_STATE_SPARSE_COUNT == 10); + g_assert (COGL_PIPELINE_LAYER_STATE_SPARSE_COUNT == 11); } static gboolean diff --git a/cogl/cogl-snippet.h b/cogl/cogl-snippet.h index 0fe61e529..db8fe5808 100644 --- a/cogl/cogl-snippet.h +++ b/cogl/cogl-snippet.h @@ -54,6 +54,8 @@ typedef struct _CoglSnippet CoglSnippet; * stage of the pipeline. * @COGL_SNIPPET_HOOK_FRAGMENT: A hook for the entire fragment * processing stage of the pipeline. + * @COGL_SNIPPET_HOOK_TEXTURE_COORD_TRANSFORM: A hook for applying the + * layer matrix to a texture coordinate for a layer. * @COGL_SNIPPET_HOOK_LAYER_FRAGMENT: A hook for the fragment * processing of a particular layer. * @COGL_SNIPPET_HOOK_TEXTURE_LOOKUP: A hook for the texture lookup @@ -129,6 +131,46 @@ typedef struct _CoglSnippet CoglSnippet; * * * + * %COGL_SNIPPET_HOOK_TEXTURE_COORD_TRANSFORM + * Adds a shader snippet that will hook on to the texture coordinate + * transformation of a particular layer. This can be used to replace + * the processing for a layer or to modify the results. + * + * + * Within the snippet code for this hook there are two extra + * variables. The first is a mat4 called cogl_matrix which represents + * the user matrix for this layer. The second is called cogl_tex_coord + * and represents the incoming and outgoing texture coordinate. On + * entry to the hook, cogl_tex_coord contains the value of the + * corresponding texture coordinate attribute for this layer. The hook + * is expected to modify this variable. The output will be passed as a + * varying to the fragment processing stage. The default code will + * just multiply cogl_matrix by cogl_tex_coord and store the result in + * cogl_tex_coord. + * + * + * The ‘declarations’ string in @snippet will be inserted in the + * global scope of the shader. Use this to declare any uniforms, + * attributes or functions that the snippet requires. + * + * + * The ‘pre’ string in @snippet will be inserted just before the + * fragment processing for this layer. At this point cogl_tex_coord + * still contains the value of the texture coordinate attribute. + * + * + * If a ‘replace’ string is given then this will be used instead of + * the default fragment processing for this layer. The snippet can + * modify cogl_tex_coord or leave it as is to apply no transformation. + * + * + * The ‘post’ string in @snippet will be inserted just after the + * transformation. At this point cogl_tex_coord will contain the + * results of the transformation but it can be further modified by the + * snippet. + * + * + * * %COGL_SNIPPET_HOOK_LAYER_FRAGMENT * Adds a shader snippet that will hook on to the fragment processing * of a particular layer. This can be used to replace the processing @@ -209,8 +251,7 @@ typedef enum { COGL_SNIPPET_HOOK_FRAGMENT = 2048, /* Per layer vertex hooks */ - /* TODO */ - /* ... = 4096 */ + COGL_SNIPPET_HOOK_TEXTURE_COORD_TRANSFORM = 4096, /* Per layer fragment hooks */ COGL_SNIPPET_HOOK_LAYER_FRAGMENT = 6144, diff --git a/tests/conform/test-snippets.c b/tests/conform/test-snippets.c index 5533f2ebd..148d3f0d8 100644 --- a/tests/conform/test-snippets.c +++ b/tests/conform/test-snippets.c @@ -45,6 +45,7 @@ paint (TestState *state) { CoglPipeline *pipeline; CoglSnippet *snippet; + CoglMatrix matrix; CoglColor color; int location; int i; @@ -268,7 +269,7 @@ paint (TestState *state) cogl_pop_source (); cogl_object_unref (pipeline); - /* Test replacing the layer code */ + /* Test replacing the fragment layer code */ pipeline = create_texture_pipeline (); snippet = cogl_snippet_new (COGL_SNIPPET_HOOK_LAYER_FRAGMENT, NULL, NULL); @@ -291,7 +292,7 @@ paint (TestState *state) cogl_pop_source (); cogl_object_unref (pipeline); - /* Test modifying the layer code */ + /* Test modifying the fragment layer code */ pipeline = cogl_pipeline_new (); cogl_pipeline_set_uniform_1f (pipeline, @@ -311,6 +312,45 @@ paint (TestState *state) cogl_pop_source (); cogl_object_unref (pipeline); + /* Test modifying the vertex layer code */ + pipeline = create_texture_pipeline (); + + cogl_matrix_init_identity (&matrix); + cogl_matrix_translate (&matrix, 0.0f, 1.0f, 0.0f); + cogl_pipeline_set_layer_matrix (pipeline, 0, &matrix); + + snippet = cogl_snippet_new (COGL_SNIPPET_HOOK_TEXTURE_COORD_TRANSFORM, + NULL, + "cogl_tex_coord.x = 1.0;"); + cogl_pipeline_add_layer_snippet (pipeline, 0, snippet); + cogl_object_unref (snippet); + + cogl_push_source (pipeline); + cogl_rectangle_with_texture_coords (130, 0, 140, 10, + 0, 0, 0, 0); + cogl_pop_source (); + cogl_object_unref (pipeline); + + /* Test replacing the vertex layer code */ + pipeline = create_texture_pipeline (); + + cogl_matrix_init_identity (&matrix); + cogl_matrix_translate (&matrix, 0.0f, 1.0f, 0.0f); + cogl_pipeline_set_layer_matrix (pipeline, 0, &matrix); + + snippet = cogl_snippet_new (COGL_SNIPPET_HOOK_TEXTURE_COORD_TRANSFORM, + NULL, + NULL); + cogl_snippet_set_replace (snippet, "cogl_tex_coord.x = 1.0;\n"); + cogl_pipeline_add_layer_snippet (pipeline, 0, snippet); + cogl_object_unref (snippet); + + cogl_push_source (pipeline); + cogl_rectangle_with_texture_coords (140, 0, 150, 10, + 0, 0, 0, 0); + cogl_pop_source (); + cogl_object_unref (pipeline); + /* Sanity check modifying the snippet */ snippet = cogl_snippet_new (COGL_SNIPPET_HOOK_FRAGMENT, "foo", "bar"); g_assert_cmpstr (cogl_snippet_get_declarations (snippet), ==, "foo"); @@ -363,6 +403,8 @@ validate_result (void) test_utils_check_pixel (105, 5, 0xff0000ff); test_utils_check_pixel (115, 5, 0xff00ffff); test_utils_check_pixel (125, 5, 0xff80ffff); + test_utils_check_pixel (135, 5, 0xffff00ff); + test_utils_check_pixel (145, 5, 0x00ff00ff); } void