diff --git a/clutter/clutter/clutter-context.c b/clutter/clutter/clutter-context.c index 72e99c346..90f68ba1c 100644 --- a/clutter/clutter/clutter-context.c +++ b/clutter/clutter/clutter-context.c @@ -79,6 +79,8 @@ static const GDebugKey clutter_paint_debug_keys[] = { typedef struct _ClutterContextPrivate { ClutterTextDirection text_direction; + + ClutterPipelineCache *pipeline_cache; } ClutterContextPrivate; G_DEFINE_TYPE_WITH_PRIVATE (ClutterContext, clutter_context, G_TYPE_OBJECT) @@ -87,7 +89,9 @@ static void clutter_context_dispose (GObject *object) { ClutterContext *context = CLUTTER_CONTEXT (object); + ClutterContextPrivate *priv = clutter_context_get_instance_private (context); + g_clear_object (&priv->pipeline_cache); g_clear_pointer (&context->events_queue, g_async_queue_unref); g_clear_pointer (&context->backend, clutter_backend_destroy); @@ -252,8 +256,10 @@ clutter_context_new (ClutterContextFlags flags, GError **error) { ClutterContext *context; + ClutterContextPrivate *priv; context = g_object_new (CLUTTER_TYPE_CONTEXT, NULL); + priv = clutter_context_get_instance_private (context); init_clutter_debug (context); context->show_fps = clutter_show_fps; @@ -268,6 +274,8 @@ clutter_context_new (ClutterContextFlags flags, g_async_queue_new_full ((GDestroyNotify) clutter_event_free); context->last_repaint_id = 1; + priv->pipeline_cache = g_object_new (CLUTTER_TYPE_PIPELINE_CACHE, NULL); + if (!clutter_context_init_real (context, flags, error)) return NULL; @@ -317,3 +325,14 @@ clutter_context_get_text_direction (ClutterContext *context) return priv->text_direction; } + +/** + * clutter_context_get_pipeline_cache: (skip) + */ +ClutterPipelineCache * +clutter_context_get_pipeline_cache (ClutterContext *context) +{ + ClutterContextPrivate *priv = clutter_context_get_instance_private (context); + + return priv->pipeline_cache; +} diff --git a/clutter/clutter/clutter-context.h b/clutter/clutter/clutter-context.h index 8bc2aa73a..d4be1f631 100644 --- a/clutter/clutter/clutter-context.h +++ b/clutter/clutter/clutter-context.h @@ -65,3 +65,6 @@ ClutterBackend * clutter_context_get_backend (ClutterContext *context); CoglPangoFontMap * clutter_context_get_pango_fontmap (ClutterContext *context); ClutterTextDirection clutter_context_get_text_direction (ClutterContext *context); + +CLUTTER_EXPORT +ClutterPipelineCache * clutter_context_get_pipeline_cache (ClutterContext *clutter_context); diff --git a/clutter/clutter/clutter-pipeline-cache.c b/clutter/clutter/clutter-pipeline-cache.c new file mode 100644 index 000000000..74dc70c58 --- /dev/null +++ b/clutter/clutter/clutter-pipeline-cache.c @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2023 Red Hat + * + * 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 . + * + */ + +#include "config.h" + +#include "clutter-pipeline-cache.h" + +typedef struct _PipelineGroupEntry +{ + GHashTable **slots; + size_t n_slots; +} PipelineGroupEntry; + +struct _ClutterPipelineCache +{ + GObject parent; + + GHashTable *groups; +}; + +G_DEFINE_TYPE (ClutterPipelineCache, clutter_pipeline_cache, G_TYPE_OBJECT) + +static PipelineGroupEntry * +pipeline_group_entry_new (void) +{ + PipelineGroupEntry *group_entry; + + group_entry = g_new0 (PipelineGroupEntry, 1); + + return group_entry; +} + +static void +pipeline_group_entry_free (PipelineGroupEntry *group_entry) +{ + size_t i; + + for (i = 0; i < group_entry->n_slots; i++) + g_clear_pointer (&group_entry->slots[i], g_hash_table_unref); + g_free (group_entry->slots); + g_free (group_entry); +} + +static void +clutter_pipeline_cache_dispose (GObject *object) +{ + ClutterPipelineCache *pipeline_cache = CLUTTER_PIPELINE_CACHE (object); + + g_clear_pointer (&pipeline_cache->groups, g_hash_table_unref); + + G_OBJECT_CLASS (clutter_pipeline_cache_parent_class)->dispose (object); +} + +static void +clutter_pipeline_cache_class_init (ClutterPipelineCacheClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = clutter_pipeline_cache_dispose; +} + +static void +clutter_pipeline_cache_init (ClutterPipelineCache *pipeline_cache) +{ + pipeline_cache->groups = + g_hash_table_new_full (NULL, + NULL, + NULL, + (GDestroyNotify) pipeline_group_entry_free); +} + +static uint32_t +calculate_color_state_key (ClutterColorState *color_state) +{ + ClutterColorspace colorspace = + clutter_color_state_get_colorspace (color_state); + ClutterTransferFunction transfer_function = + clutter_color_state_get_transfer_function (color_state); + + return (colorspace | + transfer_function << 8); +} + +static uint64_t +calculate_key (ClutterColorState *source_color_state, + ClutterColorState *target_color_state) +{ + uint64_t source_key; + uint64_t target_key; + + source_key = calculate_color_state_key (source_color_state); + target_key = calculate_color_state_key (target_color_state); + + return (source_key | + target_key << 32); +} + +/** + * clutter_pipeline_cache_get_pipeline: (skip) + */ +CoglPipeline * +clutter_pipeline_cache_get_pipeline (ClutterPipelineCache *pipeline_cache, + ClutterPipelineGroup group, + int slot, + ClutterColorState *source_color_state, + ClutterColorState *target_color_state) +{ + PipelineGroupEntry *group_entry; + uint64_t key; + + group_entry = g_hash_table_lookup (pipeline_cache->groups, group); + if (!group_entry) + return NULL; + + if (slot >= group_entry->n_slots) + return NULL; + + if (!group_entry->slots[slot]) + return NULL; + + key = calculate_key (source_color_state, target_color_state); + return g_hash_table_lookup (group_entry->slots[slot], &key); +} + +/** + * clutter_pipeline_cache_sdd_pipeline: (skip) + */ +void +clutter_pipeline_cache_set_pipeline (ClutterPipelineCache *pipeline_cache, + ClutterPipelineGroup group, + int slot, + ClutterColorState *source_color_state, + ClutterColorState *target_color_state, + CoglPipeline *pipeline) +{ + PipelineGroupEntry *group_entry; + uint64_t key; + + group_entry = g_hash_table_lookup (pipeline_cache->groups, group); + if (!group_entry) + { + group_entry = pipeline_group_entry_new (); + g_hash_table_insert (pipeline_cache->groups, group, group_entry); + } + + if (slot >= group_entry->n_slots) + { + size_t new_n_slots; + + new_n_slots = slot + 1; + group_entry->slots = g_realloc_n (group_entry->slots, + new_n_slots, + sizeof (GHashTable *)); + memset (group_entry->slots + group_entry->n_slots, + 0, + (new_n_slots - group_entry->n_slots) * sizeof (GHashTable *)); + group_entry->n_slots = new_n_slots; + } + + if (!group_entry->slots[slot]) + { + group_entry->slots[slot] = g_hash_table_new_full (g_int64_hash, + g_int64_equal, + g_free, + g_object_unref); + } + + key = calculate_key (source_color_state, target_color_state); + g_hash_table_replace (group_entry->slots[slot], + g_memdup2 (&key, sizeof (key)), + g_object_ref (pipeline)); +} + +/** + * clutter_pipeline_cache_unset_pipeline: (skip) + */ +void +clutter_pipeline_cache_unset_pipeline (ClutterPipelineCache *pipeline_cache, + ClutterPipelineGroup group, + int slot, + ClutterColorState *source_color_state, + ClutterColorState *target_color_state) + +{ + PipelineGroupEntry *group_entry; + uint64_t key; + + group_entry = g_hash_table_lookup (pipeline_cache->groups, group); + + if (!group_entry) + return; + + if (slot >= group_entry->n_slots) + return; + + if (!group_entry->slots[slot]) + return; + + key = calculate_key (source_color_state, target_color_state); + g_hash_table_remove (group_entry->slots[slot], &key); +} + +/** + * clutter_pipeline_cache_unset_all_pipeline: (skip) + */ +void +clutter_pipeline_cache_unset_all_pipelines (ClutterPipelineCache *pipeline_cache, + ClutterPipelineGroup group) +{ + g_hash_table_remove (pipeline_cache->groups, group); +} diff --git a/clutter/clutter/clutter-pipeline-cache.h b/clutter/clutter/clutter-pipeline-cache.h new file mode 100644 index 000000000..bc4585211 --- /dev/null +++ b/clutter/clutter/clutter-pipeline-cache.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2023 Red Hat + * + * 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 . + * + */ + +#pragma once + +#if !defined(__CLUTTER_H_INSIDE__) && !defined(CLUTTER_COMPILATION) +#error "Only can be included directly." +#endif + +#include "clutter/clutter-color-state.h" +#include "cogl/cogl.h" + +typedef gpointer ClutterPipelineGroup; + +#define CLUTTER_TYPE_PIPELINE_CACHE (clutter_pipeline_cache_get_type ()) +CLUTTER_EXPORT +G_DECLARE_FINAL_TYPE (ClutterPipelineCache, clutter_pipeline_cache, + CLUTTER, PIPELINE_CACHE, GObject) + +CLUTTER_EXPORT +CoglPipeline * clutter_pipeline_cache_get_pipeline (ClutterPipelineCache *pipeline_cache, + ClutterPipelineGroup group, + int slot, + ClutterColorState *source_color_state, + ClutterColorState *target_color_state); + +CLUTTER_EXPORT +void clutter_pipeline_cache_set_pipeline (ClutterPipelineCache *pipeline_cache, + ClutterPipelineGroup group, + int slot, + ClutterColorState *source_color_state, + ClutterColorState *target_color_state, + CoglPipeline *pipeline); + +CLUTTER_EXPORT +void clutter_pipeline_cache_unset_pipeline (ClutterPipelineCache *pipeline_cache, + ClutterPipelineGroup group, + int slot, + ClutterColorState *source_color_state, + ClutterColorState *target_color_state); + +CLUTTER_EXPORT +void clutter_pipeline_cache_unset_all_pipelines (ClutterPipelineCache *pipeline_cache, + ClutterPipelineGroup group); diff --git a/clutter/clutter/clutter-private.h b/clutter/clutter/clutter-private.h index 172fa8b22..48cfef25d 100644 --- a/clutter/clutter/clutter-private.h +++ b/clutter/clutter/clutter-private.h @@ -36,6 +36,7 @@ #include "clutter/clutter-effect.h" #include "clutter/clutter-event.h" #include "clutter/clutter-layout-manager.h" +#include "clutter/clutter-pipeline-cache.h" #include "clutter/clutter-settings.h" #include "clutter/clutter-stage-manager.h" #include "clutter/clutter-stage.h" diff --git a/clutter/clutter/clutter-types.h b/clutter/clutter/clutter-types.h index 211f1d2d6..7408d0d30 100644 --- a/clutter/clutter/clutter-types.h +++ b/clutter/clutter/clutter-types.h @@ -52,6 +52,7 @@ typedef struct _ClutterLayoutManager ClutterLayoutManager; typedef struct _ClutterActorIter ClutterActorIter; typedef struct _ClutterPaintContext ClutterPaintContext; typedef struct _ClutterPaintNode ClutterPaintNode; +typedef struct _ClutterPipelineCache ClutterPipelineCache; typedef struct _ClutterContent ClutterContent; /* dummy */ typedef struct _ClutterScrollActor ClutterScrollActor; typedef struct _ClutterFrameClock ClutterFrameClock; diff --git a/clutter/clutter/clutter.h b/clutter/clutter/clutter.h index 360333a6c..109358540 100644 --- a/clutter/clutter/clutter.h +++ b/clutter/clutter/clutter.h @@ -80,6 +80,7 @@ #include "clutter/clutter-paint-nodes.h" #include "clutter/clutter-paint-node.h" #include "clutter/clutter-pan-action.h" +#include "clutter/clutter-pipeline-cache.h" #include "clutter/clutter-property-transition.h" #include "clutter/clutter-rotate-action.h" #include "clutter/clutter-scroll-actor.h" diff --git a/clutter/clutter/meson.build b/clutter/clutter/meson.build index 190499930..22af0b2b1 100644 --- a/clutter/clutter/meson.build +++ b/clutter/clutter/meson.build @@ -58,6 +58,7 @@ clutter_headers = [ 'clutter-paint-node.h', 'clutter-pan-action.h', 'clutter-pick-context.h', + 'clutter-pipeline-cache.h', 'clutter-property-transition.h', 'clutter-rotate-action.h', 'clutter-scroll-actor.h', @@ -144,6 +145,7 @@ clutter_sources = [ 'clutter-pan-action.c', 'clutter-pick-context.c', 'clutter-pick-stack.c', + 'clutter-pipeline-cache.c', 'clutter-property-transition.c', 'clutter-rotate-action.c', 'clutter-scroll-actor.c', diff --git a/src/tests/clutter/conform/meson.build b/src/tests/clutter/conform/meson.build index 019c1f223..adcd3b095 100644 --- a/src/tests/clutter/conform/meson.build +++ b/src/tests/clutter/conform/meson.build @@ -39,6 +39,7 @@ clutter_conform_tests_general_tests = [ 'gesture', 'gesture-relationship', 'interval', + 'pipeline-cache', 'timeline', 'timeline-interpolate', 'timeline-progress', diff --git a/src/tests/clutter/conform/pipeline-cache.c b/src/tests/clutter/conform/pipeline-cache.c new file mode 100644 index 000000000..2e53391f4 --- /dev/null +++ b/src/tests/clutter/conform/pipeline-cache.c @@ -0,0 +1,208 @@ +#include +#include + +#include + +#include "tests/clutter-test-utils.h" + +static void +take_snippet (CoglPipeline *pipeline, + CoglSnippet *snippet) +{ + cogl_pipeline_add_snippet (pipeline, snippet); + g_object_unref (snippet); +} + +static void +pipeline_cache_group_pipelines (void) +{ + ClutterContext *context = clutter_test_get_context (); + ClutterBackend *backend = clutter_test_get_backend (); + CoglContext *cogl_context = clutter_backend_get_cogl_context (backend); + ClutterPipelineCache *pipeline_cache = clutter_context_get_pipeline_cache (context); + static ClutterPipelineGroup group1 = &group1; + static ClutterPipelineGroup group2 = &group2; + ClutterColorState *srgb_srgb; + ClutterColorState *srgb_linear; + ClutterColorState *bt2020_pq; + ClutterColorState *bt2020_linear; + /* SDR content with HDR output */ + CoglPipeline *srgb_srgb_to_bt2020_linear; + CoglPipeline *bt2020_linear_to_bt2020_pq; + /* HDR content with HDR output */ + CoglPipeline *bt2020_pq_to_bt2020_linear; + CoglPipeline *srgb_linear_to_srgb_srgb; + /* Copy for group2 */ + CoglPipeline *srgb_srgb_to_bt2020_linear_copy; + + srgb_srgb = clutter_color_state_new (context, + CLUTTER_COLORSPACE_SRGB, + CLUTTER_TRANSFER_FUNCTION_SRGB); + srgb_linear = clutter_color_state_new (context, + CLUTTER_COLORSPACE_SRGB, + CLUTTER_TRANSFER_FUNCTION_LINEAR); + bt2020_pq = clutter_color_state_new (context, + CLUTTER_COLORSPACE_BT2020, + CLUTTER_TRANSFER_FUNCTION_PQ); + bt2020_linear = clutter_color_state_new (context, + CLUTTER_COLORSPACE_BT2020, + CLUTTER_TRANSFER_FUNCTION_LINEAR); + + srgb_srgb_to_bt2020_linear = cogl_pipeline_new (cogl_context); + bt2020_linear_to_bt2020_pq = cogl_pipeline_new (cogl_context); + bt2020_pq_to_bt2020_linear = cogl_pipeline_new (cogl_context); + srgb_linear_to_srgb_srgb = cogl_pipeline_new (cogl_context); + + take_snippet (srgb_srgb_to_bt2020_linear, + clutter_color_state_get_transform_snippet (srgb_srgb, + bt2020_linear)); + take_snippet (bt2020_linear_to_bt2020_pq, + clutter_color_state_get_transform_snippet (bt2020_linear, + bt2020_pq)); + take_snippet (bt2020_pq_to_bt2020_linear, + clutter_color_state_get_transform_snippet (bt2020_pq, + bt2020_linear)); + take_snippet (srgb_linear_to_srgb_srgb, + clutter_color_state_get_transform_snippet (srgb_linear, + srgb_srgb)); + + /* Check that it's all empty. */ + g_assert_null (clutter_pipeline_cache_get_pipeline (pipeline_cache, group1, 0, + srgb_srgb, bt2020_linear)); + g_assert_null (clutter_pipeline_cache_get_pipeline (pipeline_cache, group1, 0, + bt2020_linear, bt2020_pq)); + g_assert_null (clutter_pipeline_cache_get_pipeline (pipeline_cache, group2, 0, + srgb_srgb, bt2020_linear)); + g_assert_null (clutter_pipeline_cache_get_pipeline (pipeline_cache, group2, 0, + bt2020_linear, bt2020_pq)); + + /* Adding sRGB to HDR pipeline to group1 should not effect group2. */ + clutter_pipeline_cache_set_pipeline (pipeline_cache, group1, 0, + srgb_srgb, bt2020_linear, + srgb_srgb_to_bt2020_linear); + clutter_pipeline_cache_set_pipeline (pipeline_cache, group1, 0, + bt2020_linear, bt2020_pq, + bt2020_linear_to_bt2020_pq); + + g_assert_true (clutter_pipeline_cache_get_pipeline (pipeline_cache, group1, 0, + srgb_srgb, bt2020_linear) == + srgb_srgb_to_bt2020_linear); + g_assert_true (clutter_pipeline_cache_get_pipeline (pipeline_cache, group1, 0, + bt2020_linear, bt2020_pq) == + bt2020_linear_to_bt2020_pq); + g_assert_null (clutter_pipeline_cache_get_pipeline (pipeline_cache, group2, 0, + srgb_srgb, bt2020_linear)); + g_assert_null (clutter_pipeline_cache_get_pipeline (pipeline_cache, group2, 0, + bt2020_linear, bt2020_pq)); + + srgb_srgb_to_bt2020_linear_copy = + cogl_pipeline_copy (srgb_srgb_to_bt2020_linear); + g_assert_true (srgb_srgb_to_bt2020_linear_copy != + srgb_srgb_to_bt2020_linear); + + clutter_pipeline_cache_set_pipeline (pipeline_cache, group2, 0, + srgb_srgb, bt2020_linear, + srgb_srgb_to_bt2020_linear_copy); + g_assert_true (clutter_pipeline_cache_get_pipeline (pipeline_cache, group1, 0, + srgb_srgb, bt2020_linear) == + srgb_srgb_to_bt2020_linear); + g_assert_true (clutter_pipeline_cache_get_pipeline (pipeline_cache, group2, 0, + srgb_srgb, bt2020_linear) == + srgb_srgb_to_bt2020_linear_copy); +} + +static void +pipeline_cache_replace_pipeline (void) +{ + ClutterContext *context = clutter_test_get_context (); + ClutterBackend *backend = clutter_test_get_backend (); + CoglContext *cogl_context = clutter_backend_get_cogl_context (backend); + ClutterPipelineCache *pipeline_cache = clutter_context_get_pipeline_cache (context); + static ClutterPipelineGroup group = &group; + ClutterColorState *srgb_srgb; + ClutterColorState *bt2020_linear; + CoglPipeline *srgb_srgb_to_bt2020_linear; + CoglPipeline *srgb_srgb_to_bt2020_linear_copy; + + srgb_srgb = clutter_color_state_new (context, + CLUTTER_COLORSPACE_SRGB, + CLUTTER_TRANSFER_FUNCTION_SRGB); + bt2020_linear = clutter_color_state_new (context, + CLUTTER_COLORSPACE_BT2020, + CLUTTER_TRANSFER_FUNCTION_PQ); + + srgb_srgb_to_bt2020_linear = cogl_pipeline_new (cogl_context); + srgb_srgb_to_bt2020_linear_copy = + cogl_pipeline_copy (srgb_srgb_to_bt2020_linear); + + g_object_add_weak_pointer (G_OBJECT (srgb_srgb_to_bt2020_linear), + (gpointer *) &srgb_srgb_to_bt2020_linear); + + take_snippet (srgb_srgb_to_bt2020_linear, + clutter_color_state_get_transform_snippet (srgb_srgb, + bt2020_linear)); + + clutter_pipeline_cache_set_pipeline (pipeline_cache, group, 0, + srgb_srgb, bt2020_linear, + srgb_srgb_to_bt2020_linear); + + g_object_unref (srgb_srgb_to_bt2020_linear); + g_assert_nonnull (srgb_srgb_to_bt2020_linear); + + take_snippet (srgb_srgb_to_bt2020_linear_copy, + clutter_color_state_get_transform_snippet (srgb_srgb, + bt2020_linear)); + clutter_pipeline_cache_set_pipeline (pipeline_cache, group, 0, + srgb_srgb, bt2020_linear, + srgb_srgb_to_bt2020_linear_copy); + g_assert_null (srgb_srgb_to_bt2020_linear); + + g_assert_true (clutter_pipeline_cache_get_pipeline (pipeline_cache, group, 0, + srgb_srgb, bt2020_linear) == + srgb_srgb_to_bt2020_linear_copy); +} + +static void +pipeline_slots (void) +{ + ClutterContext *context = clutter_test_get_context (); + ClutterBackend *backend = clutter_test_get_backend (); + CoglContext *cogl_context = clutter_backend_get_cogl_context (backend); + ClutterPipelineCache *pipeline_cache = clutter_context_get_pipeline_cache (context); + static ClutterPipelineGroup group = &group; + ClutterColorState *srgb_srgb; + ClutterColorState *bt2020_linear; + CoglPipeline *srgb_srgb_to_bt2020_linear; + CoglPipeline *srgb_srgb_to_bt2020_linear_copy; + + srgb_srgb = clutter_color_state_new (context, + CLUTTER_COLORSPACE_SRGB, + CLUTTER_TRANSFER_FUNCTION_SRGB); + bt2020_linear = clutter_color_state_new (context, + CLUTTER_COLORSPACE_BT2020, + CLUTTER_TRANSFER_FUNCTION_PQ); + + srgb_srgb_to_bt2020_linear = cogl_pipeline_new (cogl_context); + srgb_srgb_to_bt2020_linear_copy = + cogl_pipeline_copy (srgb_srgb_to_bt2020_linear); + + clutter_pipeline_cache_set_pipeline (pipeline_cache, group, 0, + srgb_srgb, bt2020_linear, + srgb_srgb_to_bt2020_linear); + clutter_pipeline_cache_set_pipeline (pipeline_cache, group, 1, + srgb_srgb, bt2020_linear, + srgb_srgb_to_bt2020_linear_copy); + + g_assert_true (clutter_pipeline_cache_get_pipeline (pipeline_cache, group, 0, + srgb_srgb, bt2020_linear) == + srgb_srgb_to_bt2020_linear); + g_assert_true (clutter_pipeline_cache_get_pipeline (pipeline_cache, group, 1, + srgb_srgb, bt2020_linear) == + srgb_srgb_to_bt2020_linear_copy); +} + +CLUTTER_TEST_SUITE ( + CLUTTER_TEST_UNIT ("/pipeline-cache/group-pipelines", pipeline_cache_group_pipelines) + CLUTTER_TEST_UNIT ("/pipeline-cache/replace-pipeline", pipeline_cache_replace_pipeline) + CLUTTER_TEST_UNIT ("/pipeline-cache/pipeline-slots", pipeline_slots) +)