/*
 * Cogl
 *
 * A Low Level GPU Graphics and Utilities API
 *
 * Copyright (C) 2008,2009,2010,2011 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:
 *   Robert Bragg <robert@linux.intel.com>
 */

#pragma once

#include "cogl/cogl-debug.h"
#include "cogl/cogl-pipeline-layer-private.h"
#include "cogl/cogl-pipeline.h"
#include "cogl/cogl-profile.h"
#include "cogl/cogl-list.h"
#include "cogl/cogl-boxed-value.h"
#include "cogl/cogl-pipeline-snippet-private.h"
#include "cogl/cogl-pipeline-state.h"
#include "cogl/cogl-framebuffer.h"
#include "cogl/cogl-bitmask.h"

#include <glib.h>

/* XXX: should I rename these as
 * COGL_PIPELINE_STATE_INDEX_XYZ... ?
 */
typedef enum
{
  /* sparse state */
  COGL_PIPELINE_STATE_COLOR_INDEX,
  COGL_PIPELINE_STATE_LAYERS_INDEX,
  COGL_PIPELINE_STATE_ALPHA_FUNC_INDEX,
  COGL_PIPELINE_STATE_ALPHA_FUNC_REFERENCE_INDEX,
  COGL_PIPELINE_STATE_BLEND_INDEX,
  COGL_PIPELINE_STATE_USER_SHADER_INDEX,
  COGL_PIPELINE_STATE_DEPTH_INDEX,
  COGL_PIPELINE_STATE_NON_ZERO_POINT_SIZE_INDEX,
  COGL_PIPELINE_STATE_POINT_SIZE_INDEX,
  COGL_PIPELINE_STATE_PER_VERTEX_POINT_SIZE_INDEX,
  COGL_PIPELINE_STATE_CULL_FACE_INDEX,
  COGL_PIPELINE_STATE_UNIFORMS_INDEX,
  COGL_PIPELINE_STATE_VERTEX_SNIPPETS_INDEX,
  COGL_PIPELINE_STATE_FRAGMENT_SNIPPETS_INDEX,

  /* non-sparse */
  COGL_PIPELINE_STATE_REAL_BLEND_ENABLE_INDEX,

  COGL_PIPELINE_STATE_COUNT
} CoglPipelineStateIndex;

#define COGL_PIPELINE_STATE_SPARSE_COUNT (COGL_PIPELINE_STATE_COUNT - 1)

/* Used in pipeline->differences masks and for notifying pipeline
 * state changes.
 *
 * XXX: If you add or remove state groups here you may need to update
 * some of the state masks following this enum too!
 *
 * FIXME: perhaps it would be better to rename this enum to
 * CoglPipelineStateGroup to better convey the fact that a single enum
 * here can map to multiple properties.
 */
typedef enum _CoglPipelineState
{
  COGL_PIPELINE_STATE_COLOR =
    1L<<COGL_PIPELINE_STATE_COLOR_INDEX,
  COGL_PIPELINE_STATE_LAYERS =
    1L<<COGL_PIPELINE_STATE_LAYERS_INDEX,

  COGL_PIPELINE_STATE_ALPHA_FUNC =
    1L<<COGL_PIPELINE_STATE_ALPHA_FUNC_INDEX,
  COGL_PIPELINE_STATE_ALPHA_FUNC_REFERENCE =
    1L<<COGL_PIPELINE_STATE_ALPHA_FUNC_REFERENCE_INDEX,
  COGL_PIPELINE_STATE_BLEND =
    1L<<COGL_PIPELINE_STATE_BLEND_INDEX,
  COGL_PIPELINE_STATE_USER_SHADER =
    1L<<COGL_PIPELINE_STATE_USER_SHADER_INDEX,
  COGL_PIPELINE_STATE_DEPTH =
    1L<<COGL_PIPELINE_STATE_DEPTH_INDEX,
  COGL_PIPELINE_STATE_NON_ZERO_POINT_SIZE =
    1L<<COGL_PIPELINE_STATE_NON_ZERO_POINT_SIZE_INDEX,
  COGL_PIPELINE_STATE_POINT_SIZE =
    1L<<COGL_PIPELINE_STATE_POINT_SIZE_INDEX,
  COGL_PIPELINE_STATE_PER_VERTEX_POINT_SIZE =
    1L<<COGL_PIPELINE_STATE_PER_VERTEX_POINT_SIZE_INDEX,
  COGL_PIPELINE_STATE_CULL_FACE =
    1L<<COGL_PIPELINE_STATE_CULL_FACE_INDEX,
  COGL_PIPELINE_STATE_UNIFORMS =
    1L<<COGL_PIPELINE_STATE_UNIFORMS_INDEX,
  COGL_PIPELINE_STATE_VERTEX_SNIPPETS =
    1L<<COGL_PIPELINE_STATE_VERTEX_SNIPPETS_INDEX,
  COGL_PIPELINE_STATE_FRAGMENT_SNIPPETS =
    1L<<COGL_PIPELINE_STATE_FRAGMENT_SNIPPETS_INDEX,

  COGL_PIPELINE_STATE_REAL_BLEND_ENABLE =
    1L<<COGL_PIPELINE_STATE_REAL_BLEND_ENABLE_INDEX,

} CoglPipelineState;

/*
 * Various special masks that tag state-groups in different ways...
 */

#define COGL_PIPELINE_STATE_ALL \
  ((1L<<COGL_PIPELINE_STATE_COUNT) - 1)

#define COGL_PIPELINE_STATE_ALL_SPARSE \
  (COGL_PIPELINE_STATE_ALL \
   & ~COGL_PIPELINE_STATE_REAL_BLEND_ENABLE)

#define COGL_PIPELINE_STATE_AFFECTS_BLENDING \
  (COGL_PIPELINE_STATE_COLOR | \
   COGL_PIPELINE_STATE_LAYERS | \
   COGL_PIPELINE_STATE_BLEND | \
   COGL_PIPELINE_STATE_USER_SHADER | \
   COGL_PIPELINE_STATE_VERTEX_SNIPPETS | \
   COGL_PIPELINE_STATE_FRAGMENT_SNIPPETS)

#define COGL_PIPELINE_STATE_NEEDS_BIG_STATE \
  (COGL_PIPELINE_STATE_ALPHA_FUNC | \
   COGL_PIPELINE_STATE_ALPHA_FUNC_REFERENCE | \
   COGL_PIPELINE_STATE_BLEND | \
   COGL_PIPELINE_STATE_USER_SHADER | \
   COGL_PIPELINE_STATE_DEPTH | \
   COGL_PIPELINE_STATE_NON_ZERO_POINT_SIZE | \
   COGL_PIPELINE_STATE_POINT_SIZE | \
   COGL_PIPELINE_STATE_PER_VERTEX_POINT_SIZE | \
   COGL_PIPELINE_STATE_CULL_FACE | \
   COGL_PIPELINE_STATE_UNIFORMS | \
   COGL_PIPELINE_STATE_VERTEX_SNIPPETS | \
   COGL_PIPELINE_STATE_FRAGMENT_SNIPPETS)

#define COGL_PIPELINE_STATE_MULTI_PROPERTY \
  (COGL_PIPELINE_STATE_LAYERS | \
   COGL_PIPELINE_STATE_BLEND | \
   COGL_PIPELINE_STATE_DEPTH | \
   COGL_PIPELINE_STATE_CULL_FACE | \
   COGL_PIPELINE_STATE_UNIFORMS | \
   COGL_PIPELINE_STATE_VERTEX_SNIPPETS | \
   COGL_PIPELINE_STATE_FRAGMENT_SNIPPETS)

typedef struct
{
  /* Determines what fragments are discarded based on their alpha */
  CoglPipelineAlphaFunc alpha_func;
  float		        alpha_func_reference;
} CoglPipelineAlphaFuncState;

typedef struct
{
  /* Determines how this pipeline is blended with other primitives */
  GLenum    blend_equation_rgb;
  GLenum    blend_equation_alpha;
  GLint     blend_src_factor_alpha;
  GLint     blend_dst_factor_alpha;
  CoglColor blend_constant;
  GLint     blend_src_factor_rgb;
  GLint     blend_dst_factor_rgb;
} CoglPipelineBlendState;

typedef struct
{
  CoglPipelineCullFaceMode mode;
  CoglWinding front_winding;
} CoglPipelineCullFaceState;

typedef struct
{
  CoglBitmask override_mask;

  /* This is an array of values. Only the uniforms that have a bit set
     in override_mask have a corresponding value here. The uniform's
     location is implicit from the order in this array */
  CoglBoxedValue *override_values;

  /* Uniforms that have been modified since this pipeline was last
     flushed */
  CoglBitmask changed_mask;
} CoglPipelineUniformsState;

typedef struct
{
  CoglPipelineAlphaFuncState alpha_state;
  CoglPipelineBlendState blend_state;
  CoglProgram *user_program;
  CoglDepthState depth_state;
  float point_size;
  unsigned int non_zero_point_size : 1;
  unsigned int per_vertex_point_size : 1;
  CoglPipelineCullFaceState cull_face_state;
  CoglPipelineUniformsState uniforms_state;
  CoglPipelineSnippetList vertex_snippets;
  CoglPipelineSnippetList fragment_snippets;
} CoglPipelineBigState;

typedef struct
{
  CoglPipeline *owner;
  CoglPipelineLayer *layer;
} CoglPipelineLayerCacheEntry;

typedef struct _CoglPipelineHashState
{
  unsigned long layer_differences;
  unsigned int hash;
} CoglPipelineHashState;

/*
 * CoglPipelineDestroyCallback
 * @pipeline: The #CoglPipeline that has been destroyed
 * @user_data: The private data associated with the callback
 *
 * Notifies when a weak pipeline has been destroyed because one
 * of its ancestors has been freed or modified.
 */
typedef void (*CoglPipelineDestroyCallback)(CoglPipeline *pipeline,
                                            void *user_data);

struct _CoglPipeline
{
  /* XXX: Please think twice about adding members that *have* be
   * initialized during a cogl_pipeline_copy. We are aiming to have
   * copies be as cheap as possible and copies may be done by the
   * primitives APIs which means they may happen in performance
   * critical code paths.
   *
   * XXX: If you are extending the state we track please consider if
   * the state is expected to vary frequently across many pipelines or
   * if the state can be shared among many derived pipelines instead.
   * This will determine if the state should be added directly to this
   * structure which will increase the memory overhead for *all*
   * pipelines or if instead it can go under ->big_state.
   */

  GObject parent_instance;

  /* Layers represent their state in a tree structure where some of
   * the state relating to a given pipeline or layer may actually be
   * owned by one if is ancestors in the tree. We have a common data
   * type to track the tree hierarchy so we can share code... */
  CoglPipeline *parent;
  CoglPipeline *prev_sibling;
  CoglPipeline *next_sibling;
  CoglPipeline *first_child;
  CoglPipeline *last_child;

  /* TRUE if the node took a strong reference on its parent. Weak
   * pipelines for instance don't take a reference on their parent. */
  gboolean has_parent_reference;

  CoglContext *context;

  /* When weak pipelines are destroyed the user is notified via this
   * callback */
  CoglPipelineDestroyCallback destroy_callback;

  /* When notifying that a weak pipeline has been destroyed this
   * private data is passed to the above callback */
  void *destroy_data;

  /* We need to track if a pipeline is referenced in the journal
   * because we can't allow modification to these pipelines without
   * flushing the journal first */
  unsigned int journal_ref_count;

  /* A mask of which sparse state groups are different in this
   * pipeline in comparison to its parent. */
  unsigned int differences;

  /* Whenever a pipeline is modified we increment the age. There's no
   * guarantee that it won't wrap but it can nevertheless be a
   * convenient mechanism to determine when a pipeline has been
   * changed to you can invalidate some some associated cache that
   * depends on the old state. */
  unsigned int age;

  /* This is the primary color of the pipeline.
   *
   * This is a sparse property, ref COGL_PIPELINE_STATE_COLOR */
  CoglColor color;

  /* A pipeline may be made up with multiple layers used to combine
   * textures together.
   *
   * This is sparse state, ref COGL_PIPELINE_STATE_LAYERS */
  unsigned int     n_layers;
  GList	          *layer_differences;

  /* As a basic way to reduce memory usage we divide the pipeline
   * state into two groups; the minimal state modified in 90% of
   * all pipelines and the rest, so that the second group can
   * be allocated dynamically when required... */
  CoglPipelineBigState *big_state;

  /* Cached state... */

  /* A cached, complete list of the layers this pipeline depends
   * on sorted by layer->unit_index. */
  CoglPipelineLayer   **layers_cache;
  /* To avoid a separate ->layers_cache allocation for common
   * pipelines with only a few layers... */
  CoglPipelineLayer    *short_layers_cache[3];

  /* XXX: consider adding an authorities cache to speed up sparse
   * property value lookups:
   * CoglPipeline *authorities_cache[COGL_PIPELINE_N_SPARSE_PROPERTIES];
   * and corresponding authorities_cache_dirty:1 bitfield
   */

  /* Array of opaque capabilities tagged by owners of pipelines.
   */
  GArray *capabilities;

  /* bitfields */

  /* Weak pipelines don't count as dependants on their parents which
   * means that the parent pipeline can be modified without
   * considering how the modifications may affect the weak pipeline.
   */
  unsigned int          is_weak:1;

  /* Determines if pipeline->big_state is valid */
  unsigned int          has_big_state:1;

  /* There are many factors that can determine if we need to enable
   * blending, this holds our final decision */
  unsigned int          real_blend_enable:1;

  /* Since the code for deciding if blending really needs to be
   * enabled for a particular pipeline is quite expensive we update
   * the real_blend_enable flag lazily when flushing a pipeline if
   * this dirty flag has been set. */
  unsigned int          dirty_real_blend_enable:1;

  /* Whenever a pipeline is flushed we keep track of whether the
   * pipeline was used with a color attribute where we don't know
   * whether the colors are opaque. The real_blend_enable state
   * depends on this, and must be updated whenever this changes (even
   * if dirty_real_blend_enable isn't set) */
  unsigned int          unknown_color_alpha:1;

  unsigned int          layers_cache_dirty:1;

  /* Debugging, only used when defined(COGL_ENABLE_DEBUG) */

  /* For debugging purposes it's possible to associate a static const
   * string with a pipeline which can be an aid when trying to trace
   * where the pipeline originates from */
  unsigned int          has_static_breadcrumb:1;

  /* For debugging purposes it's possible to associate a static const
   * string with a pipeline which can be an aid when trying to trace
   * where the pipeline originates from */
  const char      *static_breadcrumb;

  /* Pointer to a static string with a descriptive name, or NULL. */
  const char *name;
};

typedef struct _CoglPipelineFragend
{
  void (*start) (CoglPipeline *pipeline,
                 int n_layers,
                 unsigned long pipelines_difference);
  gboolean (*add_layer) (CoglPipeline *pipeline,
                         CoglPipelineLayer *layer,
                         unsigned long layers_difference);
  gboolean (*end) (CoglPipeline *pipeline,
                   unsigned long pipelines_difference);

  void (*pipeline_pre_change_notify) (CoglPipeline *pipeline,
                                      CoglPipelineState change,
                                      const CoglColor *new_color);
  void (*layer_pre_change_notify) (CoglPipeline *owner,
                                   CoglPipelineLayer *layer,
                                   CoglPipelineLayerState change);
} CoglPipelineFragend;

typedef struct _CoglPipelineVertend
{
  void (*start) (CoglPipeline *pipeline,
                 int n_layers,
                 unsigned long pipelines_difference);
  gboolean (*add_layer) (CoglPipeline *pipeline,
                         CoglPipelineLayer *layer,
                         unsigned long layers_difference,
                         CoglFramebuffer *framebuffer);
  gboolean (*end) (CoglPipeline *pipeline,
                   unsigned long pipelines_difference);

  void (*pipeline_pre_change_notify) (CoglPipeline *pipeline,
                                      CoglPipelineState change,
                                      const CoglColor *new_color);
  void (*layer_pre_change_notify) (CoglPipeline *owner,
                                   CoglPipelineLayer *layer,
                                   CoglPipelineLayerState change);
} CoglPipelineVertend;

typedef struct
{
  gboolean (*start) (CoglPipeline *pipeline);
  void (*end) (CoglPipeline *pipeline,
               unsigned long pipelines_difference);
  void (*pipeline_pre_change_notify) (CoglPipeline *pipeline,
                                      CoglPipelineState change,
                                      const CoglColor *new_color);
  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, CoglFramebuffer *framebuffer);
} CoglPipelineProgend;

extern const CoglPipelineFragend *_cogl_pipeline_fragend;
extern const CoglPipelineVertend *_cogl_pipeline_vertend;
extern const CoglPipelineProgend *_cogl_pipeline_progend;

void
_cogl_pipeline_init_default_pipeline (CoglContext *ctx);

static inline CoglPipeline *
_cogl_pipeline_get_parent (CoglPipeline *pipeline)
{
  return pipeline->parent;
}

static inline CoglPipeline *
_cogl_pipeline_get_authority (CoglPipeline *pipeline,
                              unsigned long difference)
{
  CoglPipeline *authority = pipeline;
  while (!(authority->differences & difference))
    authority = _cogl_pipeline_get_parent (authority);
  return authority;
}

typedef gboolean (*CoglPipelineStateComparator) (CoglPipeline *authority0,
                                                 CoglPipeline *authority1);

void
_cogl_pipeline_update_authority (CoglPipeline *pipeline,
                                 CoglPipeline *authority,
                                 CoglPipelineState state,
                                 CoglPipelineStateComparator comparator);

void
_cogl_pipeline_pre_change_notify (CoglPipeline     *pipeline,
                                  CoglPipelineState change,
                                  const CoglColor  *new_color,
                                  gboolean          from_layer_change);

void
_cogl_pipeline_prune_redundant_ancestry (CoglPipeline *pipeline);

void
_cogl_pipeline_update_real_blend_enable (CoglPipeline *pipeline,
                                         gboolean unknown_color_alpha);

typedef enum
{
  COGL_PIPELINE_GET_LAYER_NO_CREATE = 1<<0
} CoglPipelineGetLayerFlags;

CoglPipelineLayer *
_cogl_pipeline_get_layer_with_flags (CoglPipeline *pipeline,
                                     int layer_index,
                                     CoglPipelineGetLayerFlags flags);

#define _cogl_pipeline_get_layer(p, l) \
  _cogl_pipeline_get_layer_with_flags (p, l, 0)

void
_cogl_pipeline_prune_empty_layer_difference (CoglPipeline *layers_authority,
                                             CoglPipelineLayer *layer);

gboolean
_cogl_pipeline_get_real_blend_enabled (CoglPipeline *pipeline);

/*
 * Calls the pre_paint method on the layer texture if there is
 * one. This will determine whether mipmaps are needed based on the
 * filter settings.
 */
void
_cogl_pipeline_pre_paint_for_layer (CoglPipeline *pipeline,
                                    int layer_id);

/*
 * CoglPipelineFlushFlag:
 * @COGL_PIPELINE_FLUSH_FALLBACK_MASK: The fallback_layers member is set to
 *      a uint32_t mask of the layers that can't be supported with the user
 *      supplied texture and need to be replaced with fallback textures. (1 =
 *      fallback, and the least significant bit = layer 0)
 * @COGL_PIPELINE_FLUSH_DISABLE_MASK: The disable_layers member is set to
 *      a uint32_t mask of the layers that you want to completely disable
 *      texturing for (1 = fallback, and the least significant bit = layer 0)
 * @COGL_PIPELINE_FLUSH_LAYER0_OVERRIDE: The layer0_override_texture member is
 *      set to a GLuint OpenGL texture name to override the texture used for
 *      layer 0 of the pipeline. This is intended for dealing with sliced
 *      textures where you will need to point to each of the texture slices in
 *      turn when drawing your geometry.  Passing a value of 0 is the same as
 *      not passing the option at all.
 * @COGL_PIPELINE_FLUSH_SKIP_GL_COLOR: When flushing the GL state for the
 *      pipeline don't call glColor.
 */
typedef enum _CoglPipelineFlushFlag
{
  COGL_PIPELINE_FLUSH_FALLBACK_MASK       = 1L<<0,
  COGL_PIPELINE_FLUSH_DISABLE_MASK        = 1L<<1,
  COGL_PIPELINE_FLUSH_LAYER0_OVERRIDE     = 1L<<2,
  COGL_PIPELINE_FLUSH_SKIP_GL_COLOR       = 1L<<3
} CoglPipelineFlushFlag;

/*
 * CoglPipelineFlushOptions:
 *
 */
typedef struct _CoglPipelineFlushOptions
{
  CoglPipelineFlushFlag flags;

  uint32_t fallback_layers;
  uint32_t disable_layers;
  CoglTexture *layer0_override_texture;
} CoglPipelineFlushOptions;

unsigned int
_cogl_get_n_args_for_combine_func (CoglPipelineCombineFunc func);

/*
 * _cogl_pipeline_weak_copy:
 * @pipeline: A #CoglPipeline object
 * @callback: A callback to notify when your weak pipeline is destroyed
 * @user_data: Private data to pass to your given callback.
 *
 * Returns a weak copy of the given source @pipeline. Unlike a normal
 * copy no internal reference is taken on the source @pipeline and you
 * can expect that later modifications of the source pipeline (or in
 * fact any other pipeline) can result in the weak pipeline being
 * destroyed.
 *
 * To understand this better its good to know a bit about the internal
 * design of #CoglPipeline...
 *
 * Internally `CoglPipeline`s are represented as a graph of
 * property diff's, where each node is a diff of properties that gets
 * applied on top of its parent. Copying a pipeline creates an empty
 * diff and a child->parent relationship between the empty diff and
 * the source @pipeline, parent.
 *
 * Because of this internal graph design a single #CoglPipeline may
 * indirectly depend on a chain of ancestors to fully define all of
 * its properties. Because a node depends on its ancestors it normally
 * owns a reference to its parent to stop it from being freed. Also if
 * you try to modify a pipeline with children we internally use a
 * copy-on-write mechanism to ensure that you don't indirectly change
 * the properties those children.
 *
 * Weak pipelines avoid the use of copy-on-write to preserve the
 * integrity of weak dependants and instead weak dependants are
 * simply destroyed allowing the parent to be modified directly. Also
 * because weak pipelines don't own a reference to their parent they
 * won't stop the source @pipeline from being freed when the user
 * releases their reference on it.
 *
 * Because weak pipelines don't own a reference on their parent they
 * are the recommended mechanism for creating derived pipelines that you
 * want to cache as a private property of the original pipeline
 * because they won't result in a circular dependency.
 *
 * An example use case:
 *
 * Consider for example you are implementing a custom primitive that is
 * not compatible with certain source pipelines. To handle this you
 * implement a validation stage that given an arbitrary pipeline as
 * input will create a derived pipeline that is suitable for drawing
 * your primitive.
 *
 * Because you don't want to have to repeat this validation every time
 * the same incompatible pipeline is given as input you want to cache
 * the result as a private property of the original pipeline. If the
 * derived pipeline were created using cogl_pipeline_copy that would
 * create a circular dependency so the original pipeline can never be
 * freed.
 *
 * If you instead create a weak copy you won't stop the original pipeline
 * from being freed if it's no longer needed, and you will instead simply
 * be notified that your weak pipeline has been destroyed.
 *
 * This is the recommended coding pattern for validating an input
 * pipeline and caching a derived result:
 * ```c
 * static GQuark _cogl_my_cache_key = 0;
 *
 * typedef struct {
 *   CoglPipeline *validated_source;
 * } MyValidatedPipelineCache;
 *
 * static void
 * destroy_cache_cb (CoglObject *object, void *user_data)
 * {
 *   g_free (user_data);
 * }
 *
 * static void
 * invalidate_cache_cb (CoglPipeline *destroyed, void *user_data)
 * {
 *   MyValidatedPipelineCache *cache = user_data;
 *   g_object_unref (cache->validated_source);
 *   cache->validated_source = NULL;
 * }
 *
 * static CoglPipeline *
 * get_validated_pipeline (CoglPipeline *source)
 * {
 *   _cogl_my_cache_key = g_quark_from_static_string ("my-cache-key");
 *   MyValidatedPipelineCache *cache =
 *     g_object_get_qdata (G_OBJECT (source),
 *                         _cogl_my_cache_key);
 *   if (G_UNLIKELY (cache == NULL))
 *     {
 *       cache = g_new0 (MyValidatedPipelineCache, 1);
 *
 *       g_object_set_qdata_full (G_OBJECT (source),
 *                                _cogl_my_cache_key,
 *                                cache, destroy_cache_cb);
 *       cache->validated_source = source;
 *     }
 *
 *   if (G_UNLIKELY (cache->validated_source == NULL))
 *     {
 *       cache->validated_source = source;
 *
 *       /&nbsp;* Start validating source... *&nbsp;/
 *
 *       /&nbsp;* If you find you need to change something... *&nbsp;/
 *       if (cache->validated_source == source)
 *         cache->validated_source =
 *           cogl_pipeline_weak_copy (source,
 *                                    invalidate_cache_cb,
 *                                    cache);
 *
 *       /&nbsp;* Modify cache->validated_source *&nbsp;/
 *     }
 *
 *    return cache->validated_source;
 * }
 * ```
 */
CoglPipeline *
_cogl_pipeline_weak_copy (CoglPipeline *pipeline,
                          CoglPipelineDestroyCallback callback,
                          void *user_data);

/* XXX: At some point it could be good for this to accept a mask of
 * the state groups we are interested in comparing since we can
 * probably use that information in a number situations to reduce
 * the work we do. */
unsigned long
_cogl_pipeline_compare_differences (CoglPipeline *pipeline0,
                                    CoglPipeline *pipeline1);

gboolean
_cogl_pipeline_equal (CoglPipeline *pipeline0,
                      CoglPipeline *pipeline1,
                      unsigned int differences,
                      unsigned long layer_differences);

unsigned int
_cogl_pipeline_hash (CoglPipeline *pipeline,
                     unsigned int differences,
                     unsigned long layer_differences);

/* Makes a copy of the given pipeline that is a child of the root
 * pipeline rather than a child of the source pipeline. That way the
 * new pipeline won't hold a reference to the source pipeline. The
 * differences specified in @differences and @layer_differences are
 * copied across and all other state is left with the default
 * values. */
CoglPipeline *
_cogl_pipeline_deep_copy (CoglPipeline *pipeline,
                          unsigned long differences,
                          unsigned long layer_differences);

CoglPipeline *
_cogl_pipeline_journal_ref (CoglPipeline *pipeline);

void
_cogl_pipeline_journal_unref (CoglPipeline *pipeline);

const graphene_matrix_t *
_cogl_pipeline_get_layer_matrix (CoglPipeline *pipeline,
                                 int layer_index);

void
_cogl_pipeline_texture_storage_change_notify (CoglTexture *texture);

void
_cogl_pipeline_apply_overrides (CoglPipeline *pipeline,
                                CoglPipelineFlushOptions *options);

#ifdef COGL_ENABLE_DEBUG
void
_cogl_pipeline_set_static_breadcrumb (CoglPipeline *pipeline,
                                      const char *breadcrumb);
#endif

unsigned long
_cogl_pipeline_get_age (CoglPipeline *pipeline);

void
_cogl_pipeline_add_layer_difference (CoglPipeline *pipeline,
                                     CoglPipelineLayer *layer,
                                     gboolean inc_n_layers);

void
_cogl_pipeline_remove_layer_difference (CoglPipeline *pipeline,
                                        CoglPipelineLayer *layer,
                                        gboolean dec_n_layers);

CoglPipeline *
_cogl_pipeline_find_equivalent_parent (CoglPipeline *pipeline,
                                       CoglPipelineState pipeline_state,
                                       CoglPipelineLayerState layer_state);

void
_cogl_pipeline_get_layer_combine_constant (CoglPipeline *pipeline,
                                           int layer_index,
                                           float *constant);

void
_cogl_pipeline_prune_to_n_layers (CoglPipeline *pipeline, int n);


/*
 * API to support the deprecate cogl_pipeline_layer_xyz functions...
 */

typedef gboolean (*CoglPipelineInternalLayerCallback) (CoglPipelineLayer *layer,
                                                       void *user_data);

void
_cogl_pipeline_foreach_layer_internal (CoglPipeline *pipeline,
                                       CoglPipelineInternalLayerCallback callback,
                                       void *user_data);

gboolean
_cogl_pipeline_layer_numbers_equal (CoglPipeline *pipeline0,
                                    CoglPipeline *pipeline1);

gboolean
_cogl_pipeline_layer_and_unit_numbers_equal (CoglPipeline *pipeline0,
                                             CoglPipeline *pipeline1);

void
_cogl_pipeline_init_state_hash_functions (void);

void
_cogl_pipeline_init_layer_state_hash_functions (void);

CoglPipelineState
_cogl_pipeline_get_state_for_vertex_codegen (CoglContext *context);

CoglPipelineLayerState
_cogl_pipeline_get_layer_state_for_fragment_codegen (CoglContext *context);

CoglPipelineState
_cogl_pipeline_get_state_for_fragment_codegen (CoglContext *context);

void
cogl_pipeline_add_capability_from_snippet (CoglPipeline *pipeline,
                                           CoglSnippet  *snippet);
