From 40b0bb4e95b7f332b2b48e1d072eb2b4c936b4bf Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Thu, 11 Dec 2008 20:11:30 +0000 Subject: [PATCH] Adds a CoglMaterial abstraction, which includes support for multi-texturing My previous work to provide muti-texturing support has been extended into a CoglMaterial abstraction that adds control over the texture combine functions (controlling how multiple texture layers are blended together), the gl blend function (used for blending the final primitive with the framebuffer), the alpha function (used to discard fragments based on their alpha channel), describing attributes such as a diffuse, ambient and specular color (for use with the standard OpenGL lighting model), and per layer rotations. (utilizing the new CoglMatrix utility API) For now the only way this abstraction is exposed is via a new cogl_material_rectangle function, that is similar to cogl_texture_rectangle but doesn't take a texture handle (the source material is pulled from the context), and the array of texture coordinates is extended to be able to supply coordinates for each layer. Note: this function doesn't support sliced textures; supporting sliced textures is a non trivial problem, considering the ability to rotate layers. Note: cogl_material_rectangle, has quite a few workarounds, for a number of other limitations within Cogl a.t.m. Note: The GLES1/2 multi-texturing support has yet to be updated to use the material abstraction. --- clutter/cogl/cogl-material.h | 481 ++++++++++++++ clutter/cogl/cogl.h.in | 2 + clutter/cogl/common/Makefile.am | 3 +- clutter/cogl/common/cogl-material-private.h | 72 +++ clutter/cogl/common/cogl-material.c | 680 ++++++++++++++++++++ clutter/cogl/common/cogl-matrix.h | 57 -- clutter/cogl/gl/cogl-context.c | 5 +- clutter/cogl/gl/cogl-context.h | 9 +- clutter/cogl/gl/cogl-texture-private.h | 8 +- clutter/cogl/gl/cogl-texture.c | 305 +++------ tests/data/Makefile.am | 1 - tests/data/light0.png | Bin 24410 -> 5674 bytes tests/data/light1.png | Bin 28430 -> 0 bytes tests/interactive/Makefile.am | 2 +- tests/interactive/test-cogl-material.c | 161 +++++ tests/interactive/test-cogl-multi-texture.c | 250 ------- 16 files changed, 1515 insertions(+), 521 deletions(-) create mode 100644 clutter/cogl/cogl-material.h create mode 100644 clutter/cogl/common/cogl-material-private.h create mode 100644 clutter/cogl/common/cogl-material.c delete mode 100644 clutter/cogl/common/cogl-matrix.h delete mode 100644 tests/data/light1.png create mode 100644 tests/interactive/test-cogl-material.c delete mode 100644 tests/interactive/test-cogl-multi-texture.c diff --git a/clutter/cogl/cogl-material.h b/clutter/cogl/cogl-material.h new file mode 100644 index 000000000..9a6f0eb10 --- /dev/null +++ b/clutter/cogl/cogl-material.h @@ -0,0 +1,481 @@ +#if !defined(__COGL_H_INSIDE__) && !defined(CLUTTER_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __COGL_MATERIAL_H__ +#define __COGL_MATERIAL_H__ + +G_BEGIN_DECLS + +#include +#include + +/** + * SECTION:cogl-material + * @short_description: Fuctions for creating and manipulating materials + * + * COGL allows creating and manipulating materials used to fill in + * geometry. Materials may simply be lighting attributes (such as an + * ambient and diffuse colour) or might represent one or more textures + * blended together. + */ + +G_END_DECLS + +typedef enum _CoglMaterialAlphaFunc +{ + COGL_MATERIAL_ALPHA_FUNC_NEVER = GL_NEVER, + COGL_MATERIAL_ALPHA_FUNC_LESS = GL_LESS, + COGL_MATERIAL_ALPHA_FUNC_EQUAL = GL_EQUAL, + COGL_MATERIAL_ALPHA_FUNC_LEQUAL = GL_LEQUAL, + COGL_MATERIAL_ALPHA_FUNC_GREATER = GL_GREATER, + COGL_MATERIAL_ALPHA_FUNC_NOTEQUAL = GL_NOTEQUAL, + COGL_MATERIAL_ALPHA_FUNC_GEQUAL = GL_GEQUAL, + COGL_MATERIAL_ALPHA_FUNC_ALWAYS = GL_ALWAYS +} CoglMaterialAlphaFunc; + +typedef enum _CoglMaterialBlendFactor +{ + COGL_MATERIAL_BLEND_FACTOR_ZERO = GL_ZERO, + COGL_MATERIAL_BLEND_FACTOR_ONE = GL_ONE, + COGL_MATERIAL_BLEND_FACTOR_DST_COLOR = GL_DST_COLOR, + COGL_MATERIAL_BLEND_FACTOR_SRC_COLOR = GL_SRC_COLOR, + COGL_MATERIAL_BLEND_FACTOR_ONE_MINUS_DST_COLOR = GL_ONE_MINUS_DST_COLOR, + COGL_MATERIAL_BLEND_FACTOR_ONE_MINUS_SRC_COLOR = GL_ONE_MINUS_SRC_COLOR, + COGL_MATERIAL_BLEND_FACTOR_SRC_ALPHA = GL_SRC_ALPHA, + COGL_MATERIAL_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA = GL_ONE_MINUS_SRC_ALPHA, + COGL_MATERIAL_BLEND_FACTOR_DST_ALPHA = GL_DST_ALPHA, + COGL_MATERIAL_BLEND_FACTOR_ONE_MINUS_DST_ALPHA = GL_ONE_MINUS_DST_ALPHA, + COGL_MATERIAL_BLEND_FACTOR_SRC_ALPHA_SATURATE = GL_SRC_ALPHA_SATURATE, + COGL_MATERIAL_BLEND_FACTOR_CONSTANT_COLOR = GL_CONSTANT_COLOR, + COGL_MATERIAL_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR = + GL_ONE_MINUS_CONSTANT_COLOR, + COGL_MATERIAL_BLEND_FACTOR_CONSTANT_ALPHA = GL_CONSTANT_ALPHA, + COGL_MATERIAL_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA = + GL_ONE_MINUS_CONSTANT_ALPHA +} CoglMaterialBlendFactor; + +typedef enum _CoglMaterialLayerType +{ + COGL_MATERIAL_LAYER_TYPE_TEXTURE +} CoglMaterialLayerType; + +typedef enum _CoglMaterialLayerCombineFunc +{ + COGL_MATERIAL_LAYER_COMBINE_FUNC_REPLACE = GL_REPLACE, + COGL_MATERIAL_LAYER_COMBINE_FUNC_MODULATE = GL_MODULATE, + COGL_MATERIAL_LAYER_COMBINE_FUNC_ADD = GL_ADD, + COGL_MATERIAL_LAYER_COMBINE_FUNC_ADD_SIGNED = GL_ADD_SIGNED, + COGL_MATERIAL_LAYER_COMBINE_FUNC_INTERPOLATE = GL_INTERPOLATE, + COGL_MATERIAL_LAYER_COMBINE_FUNC_SUBTRACT = GL_SUBTRACT, + COGL_MATERIAL_LAYER_COMBINE_FUNC_DOT3_RGB = GL_DOT3_RGB, + COGL_MATERIAL_LAYER_COMBINE_FUNC_DOT3_RGBA = GL_DOT3_RGBA +} CoglMaterialLayerCombineFunc; + +typedef enum _CoglMaterialLayerCombineChannels +{ + COGL_MATERIAL_LAYER_COMBINE_CHANNELS_RGB, + COGL_MATERIAL_LAYER_COMBINE_CHANNELS_ALPHA, + COGL_MATERIAL_LAYER_COMBINE_CHANNELS_RGBA +} CoglMaterialLayerCombineChannels; + +typedef enum _CoglMaterialLayerCombineSrc +{ + COGL_MATERIAL_LAYER_COMBINE_SRC_TEXTURE = GL_TEXTURE, + + /* Can we find a nicer way... */ + COGL_MATERIAL_LAYER_COMBINE_SRC_TEXTURE0 = GL_TEXTURE0, + COGL_MATERIAL_LAYER_COMBINE_SRC_TEXTURE1 = GL_TEXTURE1, + COGL_MATERIAL_LAYER_COMBINE_SRC_TEXTURE2 = GL_TEXTURE2, + COGL_MATERIAL_LAYER_COMBINE_SRC_TEXTURE3 = GL_TEXTURE3, + COGL_MATERIAL_LAYER_COMBINE_SRC_TEXTURE4 = GL_TEXTURE4, + COGL_MATERIAL_LAYER_COMBINE_SRC_TEXTURE5 = GL_TEXTURE5, + COGL_MATERIAL_LAYER_COMBINE_SRC_TEXTURE6 = GL_TEXTURE6, + COGL_MATERIAL_LAYER_COMBINE_SRC_TEXTURE7 = GL_TEXTURE7, + /* .. who would ever need more than 8 texture layers.. :-) */ + + COGL_MATERIAL_LAYER_COMBINE_SRC_CONSTANT = GL_CONSTANT, + COGL_MATERIAL_LAYER_COMBINE_SRC_PRIMARY_COLOR = GL_PRIMARY_COLOR, + COGL_MATERIAL_LAYER_COMBINE_SRC_PREVIOUS = GL_PREVIOUS +} CoglMaterialLayerCombineSrc; + +typedef enum _CoglMaterialLayerCombineOp +{ + COGL_MATERIAL_LAYER_COMBINE_OP_SRC_COLOR = GL_SRC_COLOR, + COGL_MATERIAL_LAYER_COMBINE_OP_ONE_MINUS_SRC_COLOR = GL_ONE_MINUS_SRC_COLOR, + + COGL_MATERIAL_LAYER_COMBINE_OP_SRC_ALPHA = GL_SRC_ALPHA, + COGL_MATERIAL_LAYER_COMBINE_OP_ONE_MINUS_SRC_ALPHA = GL_ONE_MINUS_SRC_ALPHA +} CoglMaterialLayerCombineOp; + +/** + * cogl_material_new: + * + * Allocates and initializes blank white material + */ +CoglHandle cogl_material_new (void); + +/** + * cogl_material_ref: + * @handle: a @CoglHandle. + * + * Increment the reference count for a cogl material. + * + * Returns: the @handle. + */ +CoglHandle cogl_material_ref (CoglHandle handle); + +/** + * cogl_material_unref: + * @handle: a @CoglHandle. + * + * Deccrement the reference count for a cogl material. + */ +void cogl_material_unref (CoglHandle handle); + +/** + * cogl_material_set_diffuse: + * @material: A CoglMaterial object + * @diffuse: The components of the desired diffuse color + * + * Exposing the standard OpenGL lighting model; this function sets + * the material's diffuse color. The diffuse color is most intense + * where the light hits the surface directly; perpendicular to the + * surface. + */ +void cogl_material_set_diffuse (CoglHandle material, + const CoglColor *diffuse); + +/** + * cogl_material_set_ambient: + * @material: A CoglMaterial object + * @ambient: The components of the desired ambient color + * + * Exposing the standard OpenGL lighting model; this function sets + * the material's ambient color. The ambient color affects the overall + * color of the object. Since the diffuse color will be intense when + * the light hits the surface directly, the ambient will most aparent + * where the light hits at a slant. + */ +void cogl_material_set_ambient (CoglHandle material, + const CoglColor *ambient); + +/** + * cogl_material_set_ambient_and_diffuse: + * @material: A CoglMaterial object + * @color: The components of the desired ambient and diffuse colors + * + * This is a convenience for setting the diffuse and ambient color + * of the material at the same time. + */ +void cogl_material_set_ambient_and_diffuse (CoglHandle material, + const CoglColor *color); + +/** + * cogl_material_set_specular: + * @material: A CoglMaterial object + * @specular: The components of the desired specular color + * + * Exposing the standard OpenGL lighting model; this function sets + * the material's specular color. The intensity of the specular color + * depends on the viewport position, and is brightest along the lines + * of reflection. + */ +void cogl_material_set_specular (CoglHandle material, + const CoglColor *specular); + +/** + * cogl_material_set_shininess: + * @material: A CoglMaterial object + * shininess: The desired shininess; range: [0.0, 1.0] + * + * This function sets the materials shininess which determines how + * specular highlights are calculated. A higher shininess will produce + * smaller brigher highlights. + */ +void cogl_material_set_shininess (CoglHandle material, + float shininess); + +/** + * cogl_material_set_emission: + * @material: A CoglMaterial object + * @emission: The components of the desired emissive color + * + * Exposing the standard OpenGL lighting model; this function sets + * the material's emissive color. It will look like the surface is + * a light source emitting this color. + */ +void cogl_material_set_emission (CoglHandle material, + const CoglColor *emission); + +/** + * cogl_set_source: + * @material: A CoglMaterial object + * + * This function sets the source material that will be used to fill + * subsequent geometry emitted via the cogl API. + * + * XXX: This doesn't really belong to the cogl-material API, it should + * move to cogl.h + */ +void cogl_set_source (CoglHandle material); + +/** + * cogl_material_set_alpha_test_func: + * @material: A CoglMaterial object + * + * Before a primitive is blended with the framebuffer, it goes through an + * alpha test stage which lets you discard fragments based on the current + * alpha value. This function lets you change the function used to evaluate + * the alpha channel, and thus determine which fragments are discarded + * and which continue on to the blending stage. + * TODO: expand on this + */ +void cogl_material_set_alpha_test_func (CoglHandle material, + CoglMaterialAlphaFunc alpha_func, + float alpha_reference); + +/** + * cogl_material_set_blend_function: + * @material: A CoglMaterial object + * @src_factor: Chooses the source factor you want plugged in to the blend + * equation. + * @dst_factor: Chooses the source factor you want plugged in to the blend + * equation. + * + * This function lets you control how primitives using this material will get + * blended with the contents of your framebuffer. The blended RGBA components + * are calculated like this: + * + * (RsSr+RdDr, GsSg+GdDg, BsSb+BsSb, AsSa+AdDa) + * + * Where (Rs,Gs,Bs,As) represents your source - material- color, + * (Rd,Gd,Bd,Ad) represents your destination - framebuffer - color, + * (Sr,Sg,Sb,Sa) represents your source blend factor and + * (Dr,Dg,Db,Da) represents you destination blend factor. + * + * All factors lie in the range [0,1] and incoming color components are also + * normalized to the range [0,1] + * + * The factors are selected with the following constants: + * + * COGL_MATERIAL_BLEND_FACTOR_ZERO: (0,0,0,0) + * COGL_MATERIAL_BLEND_FACTOR_ONE: (1,1,1,1) + * COGL_MATERIAL_BLEND_FACTOR_DST_COLOR: (Rd,Gd,Bd,Ad) + * COGL_MATERIAL_BLEND_FACTOR_SRC_COLOR: (Rs,Gs,Bs,As) + * COGL_MATERIAL_BLEND_FACTOR_ONE_MINUS_DST_COLOR: + * (1,1,1,1)-(Rd,Gd,Bd,Ad) [Only valid for src_factor] + * COGL_MATERIAL_BLEND_FACTOR_ONE_MINUS_SRC_COLOR: + * (1,1,1,1)-(Rs,Gs,Bs,As) + * COGL_MATERIAL_BLEND_FACTOR_SRC_ALPHA: (As,As,As,As) + * COGL_MATERIAL_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA: + * (1,1,1,1)-(As,As,As,As) [Only valid for dst_factor] + * COGL_MATERIAL_BLEND_FACTOR_DST_ALPHA: (Ad,Ad,Ad,Ad) + * COGL_MATERIAL_BLEND_FACTOR_ONE_MINUS_DST_ALPHA: + * (1,1,1,1)-(Ad,Ad,Ad,Ad) + * COGL_MATERIAL_BLEND_FACTOR_SRC_ALPHA_SATURATE: + * (f,f,f,1) where f=MIN(As,1-Ad) + * + */ +void cogl_material_set_blend_function (CoglHandle material, + CoglMaterialBlendFactor src_factor, + CoglMaterialBlendFactor dst_factor); + +/** + * cogl_material_set_layer: + * @material: A CoglMaterial object + * + * In addition to the standard OpenGL lighting model a Cogl material may have + * one or more layers comprised of textures that can be blended together in + * order with a number of different texture combine modes. This function + * defines a new texture layer. + * + * The index values of multiple layers do not have to be consecutive; it is + * only their relative order that is important. + * + * XXX: In the future, we may define other types of material layers, such + * as purely GLSL based layers. + */ +void cogl_material_set_layer (CoglHandle material, + gint layer_index, + CoglHandle texture); + +/** + * cogl_material_add_texture: + * @material: A CoglMaterial object + * + * + */ +void cogl_material_remove_layer (CoglHandle material, + gint layer_index); + +/** + * cogl_material_set_layer_alpha_combine_func: + * @material: A CoglMaterial object + * + * TODO: Brew, a nice hot cup of tea, and document these functions... + */ +void cogl_material_set_layer_combine_func ( + CoglHandle material, + gint layer_index, + CoglMaterialLayerCombineChannels channels, + CoglMaterialLayerCombineFunc func); + +void cogl_material_set_layer_combine_arg_src ( + CoglHandle material, + gint layer_index, + gint argument, + CoglMaterialLayerCombineChannels channels, + CoglMaterialLayerCombineSrc src); + +void cogl_material_set_layer_combine_arg_op ( + CoglHandle material, + gint layer_index, + gint argument, + CoglMaterialLayerCombineChannels channels, + CoglMaterialLayerCombineOp op); + +/* TODO: */ +#if 0 + I think it would be be really neat to support a simple string description + of the fixed function texture combine modes exposed above. I think we can + consider this stuff to be set in stone from the POV that more advanced + texture combine functions are catered for with GLSL, so it seems reasonable + to find a concise string representation that can represent all the above + modes in a *much* more readable/useable fashion. I think somthing like + this would be quite nice: + + "MODULATE(TEXTURE[RGB], PREVIOUS[A])" + "ADD(TEXTURE[A],PREVIOUS[RGB])" + "INTERPOLATE(TEXTURE[1-A], PREVIOUS[RGB])" + +void cogl_material_set_layer_rgb_combine (CoglHandle material + gint layer_index, + const char *combine_description); +void cogl_material_set_layer_alpha_combine (CoglHandle material + gint layer_index, + const char *combine_description); +#endif + +/** + * cogl_material_set_layer_matrix: + * @material: A CoglMaterial object + * + * This function lets you set a matrix that can be used to e.g. translate + * and rotate a single layer of a material used to fill your geometry. + */ +void cogl_material_set_layer_matrix (CoglHandle material, + gint layer_index, + CoglMatrix *matrix); + +/** + * cogl_material_get_cogl_enable_flags: + * @material: A CoglMaterial object + * + * This determines what flags need to be passed to cogl_enable before + * this material can be used. Normally you shouldn't need to use this + * function directly since Cogl will do this internally, but if you are + * developing custom primitives directly with OpenGL you may want to use + * this. + * + * Note: This API is hopfully just a stop-gap solution. Ideally + * cogl_enable will be replaced. + */ +gulong +cogl_material_get_cogl_enable_flags (CoglHandle handle); + +/** + * cogl_material_flush_gl_material_state: + * @material: A CoglMaterial object + * + * This commits the glMaterial state to the OpenGL driver. Normally you + * shouldn't need to use this function directly, since Cogl will do this + * internally, but if you are developing custom primitives directly with + * OpenGL you may want to use this. + */ +void cogl_material_flush_gl_material_state (CoglHandle material_handle); + +/** + * cogl_material_flush_gl_alpha_func: + * @material: A CoglMaterial object + * + */ +void cogl_material_flush_gl_alpha_func (CoglHandle material_handle); + +/** + * cogl_material_flush_gl_blend_func: + * @material: A CoglMaterial object + * + */ +void cogl_material_flush_gl_blend_func (CoglHandle material_handle); + +/** + * cogl_material_get_layers: + * @material: A CoglMaterial object + * + * This function lets you access a materials internal list of layers + * for iteration. + * + * Note: Normally you shouldn't need to use this function directly since + * Cogl will do this internally, but if you are developing custom primitives + * directly with OpenGL, you will need to iterate the layers that you want + * to texture with. + * + * Note: This function may return more layers than OpenGL can use at once + * so it's your responsability limit yourself to + * CGL_MAX_COMBINED_TEXTURE_IMAGE_UNITS. + * + * Note: It's a bit out of the ordinary to return a const GList *, but it + * was considered sensible to try and avoid list manipulation for every + * primitive emitted in a scene, every frame. + */ +const GList *cogl_material_get_layers (CoglHandle material_handle); + +/** + * cogl_material_layer_get_type: + * @material: A CoglMaterial object + * + * Currently there is only one type of layer defined: + * COGL_MATERIAL_LAYER_TYPE_TEXTURE, but considering we may add purely GLSL + * based layers in the future, you should write code that checks the type + * first. + * + * Note: Normally you shouldn't need to use this function directly since + * Cogl will do this internally, but if you are developing custom primitives + * directly with OpenGL, you will need to iterate the layers that you want + * to texture with, and thus should be checking the layer types. + */ +CoglMaterialLayerType cogl_material_layer_get_type (CoglHandle layer_handle); + +/** + * cogl_material_layer_get_texture: + * @material: A CoglMaterial object + * + * This lets you extract a CoglTexture handle for a specific layer. Normally + * you shouldn't need to use this function directly since Cogl will do this + * internally, but if you are developing custom primitives directly with + * OpenGL you may need this. + * + * Note: In the future, we may support purely GLSL based layers which will + * likley return COGL_INVALID_HANDLE if you try to get the texture. + * Considering this, you should always call cogl_material_layer_get_type + * first, to check it is of type COGL_MATERIAL_LAYER_TYPE_TEXTURE. + */ +CoglHandle cogl_material_layer_get_texture (CoglHandle layer_handle); + +/** + * cogl_material_layer_flush_gl_sampler_state: + * @material: A CoglMaterial object + * + * This commits the sampler state for a single material layer to the OpenGL + * driver. Normally you shouldn't need to use this function directly since + * Cogl will do this internally, but if you are developing custom primitives + * directly with OpenGL you may want to use this. + * + * Note: It assumes you have already activated the appropriate sampler + * by calling glActiveTexture (); + */ +void cogl_material_layer_flush_gl_sampler_state (CoglHandle layer_handle); + +#endif /* __COGL_MATERIAL_H__ */ + diff --git a/clutter/cogl/cogl.h.in b/clutter/cogl/cogl.h.in index ea81c7b2c..091770227 100644 --- a/clutter/cogl/cogl.h.in +++ b/clutter/cogl/cogl.h.in @@ -33,9 +33,11 @@ #include #include +#include #include #include #include +#include #include #include #include diff --git a/clutter/cogl/common/Makefile.am b/clutter/cogl/common/Makefile.am index 755001e1f..f78a2c645 100644 --- a/clutter/cogl/common/Makefile.am +++ b/clutter/cogl/common/Makefile.am @@ -30,4 +30,5 @@ libclutter_cogl_common_la_SOURCES = \ cogl-fixed.c \ cogl-color.c \ cogl-mesh.c \ - cogl-matrix.c + cogl-matrix.c \ + cogl-material.c diff --git a/clutter/cogl/common/cogl-material-private.h b/clutter/cogl/common/cogl-material-private.h new file mode 100644 index 000000000..d64d8485b --- /dev/null +++ b/clutter/cogl/common/cogl-material-private.h @@ -0,0 +1,72 @@ +#ifndef __COGL_MATERIAL_PRIVATE_H +#define __COGL_MATERIAL_PRIVATE_H + +#include "cogl-material.h" +#include "cogl-matrix.h" + +#include + +typedef struct _CoglMaterial CoglMaterial; +typedef struct _CoglMaterialLayer CoglMaterialLayer; + +typedef enum _CoglMaterialLayerFlags +{ + COGL_MATERIAL_LAYER_FLAG_USER_MATRIX = 1L<<0, +} CoglMaterialLayerFlags; + +struct _CoglMaterialLayer +{ + guint ref_count; + guint index; /*!< lowest index is blended first then others + on top */ + gulong flags; + CoglHandle texture; /*!< The texture for this layer, or COGL_INVALID_HANDLE + for an empty layer */ + + /* Determines how the color of individual texture fragments + * are calculated. */ + CoglMaterialLayerCombineFunc texture_combine_rgb_func; + CoglMaterialLayerCombineSrc texture_combine_rgb_src[3]; + CoglMaterialLayerCombineOp texture_combine_rgb_op[3]; + + CoglMaterialLayerCombineFunc texture_combine_alpha_func; + CoglMaterialLayerCombineSrc texture_combine_alpha_src[3]; + CoglMaterialLayerCombineOp texture_combine_alpha_op[3]; + + /* TODO: Support purely GLSL based material layers */ + + CoglMatrix matrix; +}; + +typedef enum _CoglMaterialFlags +{ + COGL_MATERIAL_FLAG_ENABLE_BLEND = 1L<<0, + COGL_MATERIAL_FLAG_SHOWN_SAMPLER_WARNING = 1L<<1 +} CoglMaterialFlags; + +struct _CoglMaterial +{ + guint ref_count; + + gulong flags; + + /* Standard OpenGL lighting model attributes */ + GLfloat ambient[4]; + GLfloat diffuse[4]; + GLfloat specular[4]; + GLfloat emission[4]; + GLfloat shininess; + + /* Determines what fragments are discarded based on their alpha */ + CoglMaterialAlphaFunc alpha_func; + GLfloat alpha_func_reference; + + /* Determines how this material is blended with other primitives */ + CoglMaterialBlendFactor blend_src_factor; + CoglMaterialBlendFactor blend_dst_factor; + + GList *layers; +}; + +#endif /* __COGL_MATERIAL_PRIVATE_H */ + diff --git a/clutter/cogl/common/cogl-material.c b/clutter/cogl/common/cogl-material.c new file mode 100644 index 000000000..d720c120f --- /dev/null +++ b/clutter/cogl/common/cogl-material.c @@ -0,0 +1,680 @@ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "cogl.h" +#include "cogl-internal.h" +#include "cogl-context.h" +#include "cogl-handle.h" + +#include "cogl-material-private.h" + +#include + +static void _cogl_material_free (CoglMaterial *tex); +static void _cogl_material_layer_free (CoglMaterialLayer *layer); + +COGL_HANDLE_DEFINE (Material, material, material_handles); +COGL_HANDLE_DEFINE (MaterialLayer, + material_layer, + material_layer_handles); + +CoglHandle +cogl_material_new (void) +{ + /* Create new - blank - material */ + CoglMaterial *material = g_new0 (CoglMaterial, 1); + GLfloat *ambient = material->ambient; + GLfloat *diffuse = material->diffuse; + GLfloat *specular = material->specular; + GLfloat *emission = material->emission; + + material->ref_count = 1; + COGL_HANDLE_DEBUG_NEW (material, material); + + /* Use the same defaults as the GL spec... */ + ambient[0] = 0.2; ambient[1] = 0.2; ambient[2] = 0.2; ambient[3] = 1.0; + diffuse[0] = 0.8; diffuse[1] = 0.8; diffuse[2] = 0.8; diffuse[3] = 1.0; + specular[0] = 0; specular[1] = 0; specular[2] = 0; specular[3] = 1.0; + emission[0] = 0; emission[1] = 0; emission[2] = 0; emission[3] = 1.0; + + /* Use the same defaults as the GL spec... */ + material->alpha_func = COGL_MATERIAL_ALPHA_FUNC_ALWAYS; + material->alpha_func_reference = 0.0; + + /* Not the same as the GL default, but seems saner... */ + material->blend_src_factor = COGL_MATERIAL_BLEND_FACTOR_SRC_ALPHA; + material->blend_dst_factor = COGL_MATERIAL_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + + material->layers = NULL; + + return _cogl_material_handle_new (material); +} + +static void +_cogl_material_free (CoglMaterial *material) +{ + /* Frees material resources but its handle is not + released! Do that separately before this! */ + + g_list_foreach (material->layers, + (GFunc)cogl_material_layer_unref, NULL); + g_free (material); +} + +void +cogl_material_set_ambient (CoglHandle handle, + const CoglColor *ambient_color) +{ + CoglMaterial *material; + GLfloat *ambient; + + g_return_if_fail (cogl_is_material (handle)); + + material = _cogl_material_pointer_from_handle (handle); + + ambient = material->ambient; + ambient[0] = cogl_color_get_red_float (ambient_color); + ambient[1] = cogl_color_get_green_float (ambient_color); + ambient[2] = cogl_color_get_blue_float (ambient_color); + ambient[3] = cogl_color_get_alpha_float (ambient_color); + /* material->ambient = *ambient_color; */ +} + +void +cogl_material_set_diffuse (CoglHandle handle, + const CoglColor *diffuse_color) +{ + CoglMaterial *material; + GLfloat *diffuse; + + g_return_if_fail (cogl_is_material (handle)); + + material = _cogl_material_pointer_from_handle (handle); + + diffuse = material->diffuse; + diffuse[0] = cogl_color_get_red_float (diffuse_color); + diffuse[1] = cogl_color_get_green_float (diffuse_color); + diffuse[2] = cogl_color_get_blue_float (diffuse_color); + diffuse[3] = cogl_color_get_alpha_float (diffuse_color); + /* material->diffuse = *diffuse_color; */ +} + +void +cogl_material_set_ambient_and_diffuse (CoglHandle handle, + const CoglColor *color) +{ + cogl_material_set_ambient (handle, color); + cogl_material_set_diffuse (handle, color); +} + +void +cogl_material_set_specular (CoglHandle handle, + const CoglColor *specular_color) +{ + CoglMaterial *material; + GLfloat *specular; + + g_return_if_fail (cogl_is_material (handle)); + + material = _cogl_material_pointer_from_handle (handle); + + specular = material->specular; + specular[0] = cogl_color_get_red_float (specular_color); + specular[1] = cogl_color_get_green_float (specular_color); + specular[2] = cogl_color_get_blue_float (specular_color); + specular[3] = cogl_color_get_alpha_float (specular_color); + /* material->specular = *specular_color; */ +} + +void +cogl_material_set_shininess (CoglHandle handle, + float shininess) +{ + CoglMaterial *material; + + g_return_if_fail (cogl_is_material (handle)); + + if (shininess < 0.0 || shininess > 1.0) + g_warning ("Out of range shininess %f supplied for material\n", + shininess); + + material = _cogl_material_pointer_from_handle (handle); + + material->shininess = (GLfloat)shininess * 128.0; +} + +void +cogl_material_set_emission (CoglHandle handle, + const CoglColor *emission_color) +{ + CoglMaterial *material; + GLfloat *emission; + + g_return_if_fail (cogl_is_material (handle)); + + material = _cogl_material_pointer_from_handle (handle); + + emission = material->emission; + emission[0] = cogl_color_get_red_float (emission_color); + emission[1] = cogl_color_get_green_float (emission_color); + emission[2] = cogl_color_get_blue_float (emission_color); + emission[3] = cogl_color_get_alpha_float (emission_color); + /* material->emission = *emission_color; */ +} + +/* TODO: Should go in cogl.c */ +void +cogl_set_source (CoglHandle material_handle) +{ + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + + g_return_if_fail (cogl_is_material (material_handle)); + + if (ctx->source_material) + cogl_material_unref (ctx->source_material); + + cogl_material_ref (material_handle); + ctx->source_material = material_handle; +} + +void +cogl_material_set_alpha_test_func (CoglHandle handle, + CoglMaterialAlphaFunc alpha_func, + float alpha_reference) +{ + CoglMaterial *material; + + g_return_if_fail (cogl_is_material (handle)); + + material = _cogl_material_pointer_from_handle (handle); + material->alpha_func = alpha_func; + material->alpha_func_reference = (GLfloat)alpha_reference; +} + +void +cogl_material_set_blend_function (CoglHandle handle, + CoglMaterialBlendFactor src_factor, + CoglMaterialBlendFactor dst_factor) +{ + CoglMaterial *material; + + g_return_if_fail (cogl_is_material (handle)); + + material = _cogl_material_pointer_from_handle (handle); + material->blend_src_factor = src_factor; + material->blend_dst_factor = dst_factor; +} + +/* Asserts that a layer corresponding to the given index exists. If no + * match is found, then a new empty layer is added. + */ +static CoglMaterialLayer * +_cogl_material_get_layer (CoglMaterial *material, + gint index, + gboolean create_if_not_found) +{ + CoglMaterialLayer *layer; + GList *tmp; + CoglHandle layer_handle; + + for (tmp = material->layers; tmp != NULL; tmp = tmp->next) + { + layer = + _cogl_material_layer_pointer_from_handle ((CoglHandle)tmp->data); + if (layer->index == index) + return layer; + + /* The layers are always sorted, so at this point we know this layer + * doesn't exist */ + if (layer->index > index) + break; + } + /* NB: if we now insert a new layer before tmp, that will maintain order. + */ + + if (!create_if_not_found) + return NULL; + + layer = g_new0 (CoglMaterialLayer, 1); + + layer->ref_count = 1; + layer->index = index; + layer->texture = COGL_INVALID_HANDLE; + + /* Choose the same default combine mode as OpenGL: + * MODULATE(PREVIOUS[RGBA],TEXTURE[RGBA]) */ + layer->texture_combine_rgb_func = COGL_MATERIAL_LAYER_COMBINE_FUNC_MODULATE; + layer->texture_combine_rgb_src[0] = COGL_MATERIAL_LAYER_COMBINE_SRC_PREVIOUS; + layer->texture_combine_rgb_src[1] = COGL_MATERIAL_LAYER_COMBINE_SRC_TEXTURE; + layer->texture_combine_rgb_op[0] = COGL_MATERIAL_LAYER_COMBINE_OP_SRC_COLOR; + layer->texture_combine_rgb_op[1] = COGL_MATERIAL_LAYER_COMBINE_OP_SRC_COLOR; + layer->texture_combine_alpha_func = + COGL_MATERIAL_LAYER_COMBINE_FUNC_MODULATE; + layer->texture_combine_alpha_src[0] = + COGL_MATERIAL_LAYER_COMBINE_SRC_PREVIOUS; + layer->texture_combine_alpha_src[1] = + COGL_MATERIAL_LAYER_COMBINE_SRC_TEXTURE; + layer->texture_combine_alpha_op[0] = + COGL_MATERIAL_LAYER_COMBINE_OP_SRC_ALPHA; + layer->texture_combine_alpha_op[1] = + COGL_MATERIAL_LAYER_COMBINE_OP_SRC_ALPHA; + + layer_handle = _cogl_material_layer_handle_new (layer); + /* Note: see comment after for() loop above */ + material->layers = + g_list_insert_before (material->layers, tmp, layer_handle); + + return layer; +} + +void +cogl_material_set_layer (CoglHandle material_handle, + gint layer_index, + CoglHandle texture_handle) +{ + CoglMaterial *material; + CoglMaterialLayer *layer; + int n_layers; + + g_return_if_fail (cogl_is_material (material_handle)); + g_return_if_fail (cogl_is_texture (texture_handle)); + + material = _cogl_material_pointer_from_handle (material_handle); + layer = _cogl_material_get_layer (material_handle, layer_index, TRUE); + + /* XXX: If we expose manual control over ENABLE_BLEND, we'll add + * a flag to know when it's user configured, so we don't trash it */ + if (cogl_texture_get_format (texture_handle) & COGL_A_BIT) + material->flags |= COGL_MATERIAL_FLAG_ENABLE_BLEND; + + n_layers = g_list_length (material->layers); + if (n_layers >= CGL_MAX_COMBINED_TEXTURE_IMAGE_UNITS) + { + if (!(material->flags & COGL_MATERIAL_FLAG_SHOWN_SAMPLER_WARNING)) + { + g_warning ("Your hardware doesnot have enough texture samplers" + "to handle this many texture layers"); + material->flags |= COGL_MATERIAL_FLAG_SHOWN_SAMPLER_WARNING; + } + /* Note: We always make a best effort attempt to display as many + * layers as possible, so this isn't an _error_ */ + /* Note: in the future we may support enabling/disabling layers + * too, so it may become valid to add more than + * MAX_COMBINED_TEXTURE_IMAGE_UNITS layers. */ + } + + if (layer->texture) + cogl_texture_unref (layer->texture); + + cogl_texture_ref (texture_handle); + layer->texture = texture_handle; +} + +void +cogl_material_set_layer_combine_func ( + CoglHandle handle, + gint layer_index, + CoglMaterialLayerCombineChannels channels, + CoglMaterialLayerCombineFunc func) +{ + CoglMaterial *material; + CoglMaterialLayer *layer; + gboolean set_alpha_func = FALSE; + gboolean set_rgb_func = FALSE; + + g_return_if_fail (cogl_is_material (handle)); + + material = _cogl_material_pointer_from_handle (handle); + layer = _cogl_material_get_layer (material, layer_index, FALSE); + if (!layer) + return; + + if (channels == COGL_MATERIAL_LAYER_COMBINE_CHANNELS_RGBA) + set_alpha_func = set_rgb_func = TRUE; + else if (channels == COGL_MATERIAL_LAYER_COMBINE_CHANNELS_RGB) + set_rgb_func = TRUE; + else if (channels == COGL_MATERIAL_LAYER_COMBINE_CHANNELS_ALPHA) + set_alpha_func = TRUE; + + if (set_rgb_func) + layer->texture_combine_rgb_func = func; + if (set_alpha_func) + layer->texture_combine_alpha_func = func; +} + +void +cogl_material_set_layer_combine_arg_src ( + CoglHandle handle, + gint layer_index, + gint argument, + CoglMaterialLayerCombineChannels channels, + CoglMaterialLayerCombineSrc src) +{ + CoglMaterial *material; + CoglMaterialLayer *layer; + gboolean set_arg_alpha_src = FALSE; + gboolean set_arg_rgb_src = FALSE; + + g_return_if_fail (cogl_is_material (handle)); + g_return_if_fail (argument >=0 && argument <= 3); + + material = _cogl_material_pointer_from_handle (handle); + layer = _cogl_material_get_layer (material, layer_index, FALSE); + if (!layer) + return; + + if (channels == COGL_MATERIAL_LAYER_COMBINE_CHANNELS_RGBA) + set_arg_alpha_src = set_arg_rgb_src = TRUE; + else if (channels == COGL_MATERIAL_LAYER_COMBINE_CHANNELS_RGB) + set_arg_rgb_src = TRUE; + else if (channels == COGL_MATERIAL_LAYER_COMBINE_CHANNELS_ALPHA) + set_arg_alpha_src = TRUE; + + if (set_arg_rgb_src) + layer->texture_combine_rgb_src[argument] = src; + if (set_arg_alpha_src) + layer->texture_combine_alpha_src[argument] = src; +} + +void +cogl_material_set_layer_combine_arg_op ( + CoglHandle material_handle, + gint layer_index, + gint argument, + CoglMaterialLayerCombineChannels channels, + CoglMaterialLayerCombineOp op) +{ + CoglMaterial *material; + CoglMaterialLayer *layer; + gboolean set_arg_alpha_op = FALSE; + gboolean set_arg_rgb_op = FALSE; + + g_return_if_fail (cogl_is_material (material_handle)); + g_return_if_fail (argument >=0 && argument <= 3); + + material = _cogl_material_pointer_from_handle (material_handle); + layer = _cogl_material_get_layer (material, layer_index, FALSE); + if (!layer) + return; + + if (channels == COGL_MATERIAL_LAYER_COMBINE_CHANNELS_RGBA) + set_arg_alpha_op = set_arg_rgb_op = TRUE; + else if (channels == COGL_MATERIAL_LAYER_COMBINE_CHANNELS_RGB) + set_arg_rgb_op = TRUE; + else if (channels == COGL_MATERIAL_LAYER_COMBINE_CHANNELS_ALPHA) + set_arg_alpha_op = TRUE; + + if (set_arg_rgb_op) + layer->texture_combine_rgb_op[argument] = op; + if (set_arg_alpha_op) + layer->texture_combine_alpha_op[argument] = op; +} + +void +cogl_material_set_layer_matrix (CoglHandle material_handle, + gint layer_index, + CoglMatrix *matrix) +{ + CoglMaterial *material; + CoglMaterialLayer *layer; + + g_return_if_fail (cogl_is_material (material_handle)); + + material = _cogl_material_pointer_from_handle (material_handle); + layer = _cogl_material_get_layer (material, layer_index, FALSE); + if (!layer) + return; + + layer->matrix = *matrix; + layer->flags |= COGL_MATERIAL_LAYER_FLAG_USER_MATRIX; +} + +static void +_cogl_material_layer_free (CoglMaterialLayer *layer) +{ + cogl_texture_unref (layer->texture); + g_free (layer); +} + +void +cogl_material_remove_layer (CoglHandle material_handle, + gint layer_index) +{ + CoglMaterial *material; + CoglMaterialLayer *layer; + GList *tmp; + + g_return_if_fail (cogl_is_material (material_handle)); + + material = _cogl_material_pointer_from_handle (material_handle); + material->flags &= ~COGL_MATERIAL_FLAG_ENABLE_BLEND; + for (tmp = material->layers; tmp != NULL; tmp = tmp->next) + { + layer = tmp->data; + if (layer->index == layer_index) + { + CoglHandle handle = + _cogl_material_layer_handle_from_pointer (layer); + cogl_material_layer_unref (handle); + material->layers = g_list_remove (material->layers, layer); + continue; + } + + /* XXX: If we expose manual control over ENABLE_BLEND, we'll add + * a flag to know when it's user configured, so we don't trash it */ + if (cogl_texture_get_format (layer->texture) & COGL_A_BIT) + material->flags |= COGL_MATERIAL_FLAG_ENABLE_BLEND; + } +} + +/* XXX: This API is hopfully just a stop-gap solution. Ideally cogl_enable + * will be replaced. */ +gulong +cogl_material_get_cogl_enable_flags (CoglHandle material_handle) +{ + CoglMaterial *material; + gulong enable_flags = 0; + + _COGL_GET_CONTEXT (ctx, 0); + + g_return_val_if_fail (cogl_is_material (material_handle), 0); + + material = _cogl_material_pointer_from_handle (material_handle); + + /* Enable blending if the geometry has an associated alpha color, + * or the material wants blending enabled. */ + if (material->flags & COGL_MATERIAL_FLAG_ENABLE_BLEND + || ctx->color_alpha < 255) + enable_flags |= COGL_ENABLE_BLEND; + + return enable_flags; +} + +void +cogl_material_flush_gl_material_state (CoglHandle material_handle) +{ + CoglMaterial *material; + + g_return_if_fail (cogl_is_material (material_handle)); + + material = _cogl_material_pointer_from_handle (material_handle); + + GE (glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT, material->ambient)); + GE (glMaterialfv (GL_FRONT_AND_BACK, GL_DIFFUSE, material->diffuse)); + GE (glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, material->specular)); + GE (glMaterialfv (GL_FRONT_AND_BACK, GL_EMISSION, material->emission)); + GE (glMaterialfv (GL_FRONT_AND_BACK, GL_SHININESS, &material->shininess)); +} + +void +cogl_material_flush_gl_alpha_func (CoglHandle material_handle) +{ + CoglMaterial *material; + + g_return_if_fail (cogl_is_material (material_handle)); + + material = _cogl_material_pointer_from_handle (material_handle); + + /* NB: Currently the Cogl defines are compatible with the GL ones: */ + GE (glAlphaFunc (material->alpha_func, material->alpha_func_reference)); +} + +void +cogl_material_flush_gl_blend_func (CoglHandle material_handle) +{ + CoglMaterial *material; + + g_return_if_fail (cogl_is_material (material_handle)); + GE (glBlendFunc (material->blend_src_factor, material->blend_dst_factor)); +} + +/* It's a bit out of the ordinary to return a const GList *, but it's + * probably sensible to try and avoid list manipulation for every + * primitive emitted in a scene, every frame. + * + * Alternativly; we could either add a _foreach function, or maybe + * a function that gets a passed a buffer (that may be stack allocated) + * by the caller. + */ +const GList * +cogl_material_get_layers (CoglHandle material_handle) +{ + CoglMaterial *material; + + g_return_val_if_fail (cogl_is_material (material_handle), NULL); + + material = _cogl_material_pointer_from_handle (material_handle); + + return material->layers; +} + +CoglMaterialLayerType +cogl_material_layer_get_type (CoglHandle layer_handle) +{ + return COGL_MATERIAL_LAYER_TYPE_TEXTURE; +} + +CoglHandle +cogl_material_layer_get_texture (CoglHandle layer_handle) +{ + CoglMaterialLayer *layer; + + g_return_val_if_fail (cogl_is_material_layer (layer_handle), + COGL_INVALID_HANDLE); + + layer = _cogl_material_layer_pointer_from_handle (layer_handle); + return layer->texture; +} + +static guint +get_n_args_for_combine_func (CoglMaterialLayerCombineFunc func) +{ + switch (func) + { + case COGL_MATERIAL_LAYER_COMBINE_FUNC_REPLACE: + return 1; + case COGL_MATERIAL_LAYER_COMBINE_FUNC_MODULATE: + case COGL_MATERIAL_LAYER_COMBINE_FUNC_ADD: + case COGL_MATERIAL_LAYER_COMBINE_FUNC_ADD_SIGNED: + case COGL_MATERIAL_LAYER_COMBINE_FUNC_SUBTRACT: + case COGL_MATERIAL_LAYER_COMBINE_FUNC_DOT3_RGB: + case COGL_MATERIAL_LAYER_COMBINE_FUNC_DOT3_RGBA: + return 2; + case COGL_MATERIAL_LAYER_COMBINE_FUNC_INTERPOLATE: + return 3; + } + return 0; +} + +void +cogl_material_layer_flush_gl_sampler_state (CoglHandle layer_handle) +{ + CoglMaterialLayer *layer; + int n_rgb_func_args; + int n_alpha_func_args; + + g_return_if_fail (cogl_is_material_layer (layer_handle)); + + layer = _cogl_material_layer_pointer_from_handle (layer_handle); + + /* XXX: We really want some kind of cache/dirty flag mechanism + * somewhere here so we can avoid as much mucking about with + * the texture units per primitive as possible! + * + * E.g. some recent profiling of clutter-actor suggested that + * validating/updating the texture environment may currently + * be a significant bottleneck. Given that all the actors should + * have the same texture environment, that implies we could do a + * much better job of avoiding redundant glTexEnv calls. + */ + + GE (glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE)); + + /* Set the combiner functions... */ + GE (glTexEnvi (GL_TEXTURE_ENV, + GL_COMBINE_RGB, + layer->texture_combine_rgb_func)); + GE (glTexEnvi (GL_TEXTURE_ENV, + GL_COMBINE_ALPHA, + layer->texture_combine_alpha_func)); + + /* + * Setup the function arguments... + */ + + /* For the RGB components... */ + n_rgb_func_args = + get_n_args_for_combine_func (layer->texture_combine_rgb_func); + + GE (glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_RGB, + layer->texture_combine_rgb_src[0])); + GE (glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_RGB, + layer->texture_combine_rgb_op[0])); + if (n_rgb_func_args > 1) + { + GE (glTexEnvi (GL_TEXTURE_ENV, GL_SRC1_RGB, + layer->texture_combine_rgb_src[1])); + GE (glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND1_RGB, + layer->texture_combine_rgb_op[1])); + } + if (n_rgb_func_args > 2) + { + GE (glTexEnvi (GL_TEXTURE_ENV, GL_SRC2_RGB, + layer->texture_combine_rgb_src[2])); + GE (glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND2_RGB, + layer->texture_combine_rgb_op[2])); + } + + /* For the Alpha component */ + n_alpha_func_args = + get_n_args_for_combine_func (layer->texture_combine_alpha_func); + + GE (glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_ALPHA, + layer->texture_combine_alpha_src[0])); + GE (glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, + layer->texture_combine_alpha_op[0])); + if (n_alpha_func_args > 1) + { + GE (glTexEnvi (GL_TEXTURE_ENV, GL_SRC1_ALPHA, + layer->texture_combine_alpha_src[1])); + GE (glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, + layer->texture_combine_alpha_op[1])); + } + if (n_alpha_func_args > 2) + { + GE (glTexEnvi (GL_TEXTURE_ENV, GL_SRC2_ALPHA, + layer->texture_combine_alpha_src[2])); + GE (glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND2_ALPHA, + layer->texture_combine_alpha_op[2])); + } + + if (layer->flags & COGL_MATERIAL_LAYER_FLAG_USER_MATRIX) + { + GE (glMatrixMode (GL_TEXTURE)); + GE (glLoadMatrixf ((GLfloat *)&layer->matrix)); + GE (glMatrixMode (GL_MODELVIEW)); + } +} + diff --git a/clutter/cogl/common/cogl-matrix.h b/clutter/cogl/common/cogl-matrix.h deleted file mode 100644 index 2f6874fae..000000000 --- a/clutter/cogl/common/cogl-matrix.h +++ /dev/null @@ -1,57 +0,0 @@ -#ifndef __COGL_MATRIX_H -#define __COGL_MATRIX_H - -/* Note: This is ordered according to how OpenGL expects to get 4x4 matrices */ -typedef struct { - /* column 0 */ - float xx; - float yx; - float zx; - float wx; - - /* column 1 */ - float xy; - float yy; - float zy; - float wy; - - /* column 2 */ - float xz; - float yz; - float zz; - float wz; - - /* column 3 */ - float xw; - float yw; - float zw; - float ww; - - /* Note: we may want to extend this later with private flags - * and a cache of the inverse transform matrix. */ -} CoglMatrix; - -void cogl_matrix_init_identity (CoglMatrix *matrix); - -void cogl_matrix_multiply (CoglMatrix *result, - const CoglMatrix *a, - const CoglMatrix *b); - -void cogl_matrix_rotate (CoglMatrix *matrix, - float angle, - float x, - float y, - float z); - -void cogl_matrix_translate (CoglMatrix *matrix, - float x, - float y, - float z); - -void cogl_matrix_scale (CoglMatrix *matrix, - float sx, - float sy, - float sz); - -#endif /* __COGL_MATRIX_H */ - diff --git a/clutter/cogl/gl/cogl-context.c b/clutter/cogl/gl/cogl-context.c index 86a3d425a..84e46bc92 100644 --- a/clutter/cogl/gl/cogl-context.c +++ b/clutter/cogl/gl/cogl-context.c @@ -59,8 +59,9 @@ cogl_create_context () _context->texture_vertices_size = 0; _context->texture_vertices = NULL; - _context->multi_texture_handles = NULL; - _context->multi_texture_layer_handles = NULL; + _context->material_handles = NULL; + _context->material_layer_handles = NULL; + _context->source_material = NULL; _context->fbo_handles = NULL; _context->draw_buffer = COGL_WINDOW_BUFFER; diff --git a/clutter/cogl/gl/cogl-context.h b/clutter/cogl/gl/cogl-context.h index afcf9c347..81ec93219 100644 --- a/clutter/cogl/gl/cogl-context.h +++ b/clutter/cogl/gl/cogl-context.h @@ -66,11 +66,10 @@ typedef struct CoglTextureGLVertex *texture_vertices; gulong texture_vertices_size; - /* Multi Textures */ - GArray *multi_texture_handles; - - /* Multi Texture Layers */ - GArray *multi_texture_layer_handles; + /* Materials */ + GArray *material_handles; + GArray *material_layer_handles; + CoglHandle source_material; /* Framebuffer objects */ GArray *fbo_handles; diff --git a/clutter/cogl/gl/cogl-texture-private.h b/clutter/cogl/gl/cogl-texture-private.h index b6c5af5ec..44fd7712e 100644 --- a/clutter/cogl/gl/cogl-texture-private.h +++ b/clutter/cogl/gl/cogl-texture-private.h @@ -31,8 +31,8 @@ typedef struct _CoglTexture CoglTexture; typedef struct _CoglTexSliceSpan CoglTexSliceSpan; typedef struct _CoglSpanIter CoglSpanIter; -typedef struct _CoglMultiTexture CoglMultiTexture; -typedef struct _CoglMultiTextureLayer CoglMultiTextureLayer; +typedef struct _CoglCompositeTexture CoglCompositeTexture; +typedef struct _CoglCompositeTextureLayer CoglCompositeTextureLayer; struct _CoglTexSliceSpan { @@ -61,7 +61,7 @@ struct _CoglTexture gboolean auto_mipmap; }; -struct _CoglMultiTextureLayer +struct _CoglCompositeTextureLayer { guint ref_count; @@ -74,7 +74,7 @@ struct _CoglMultiTextureLayer * unit. For example we should support dot3 normal mapping. */ }; -struct _CoglMultiTexture +struct _CoglCompositeTexture { guint ref_count; GList *layers; diff --git a/clutter/cogl/gl/cogl-texture.c b/clutter/cogl/gl/cogl-texture.c index 73478b8db..c1a8ab682 100644 --- a/clutter/cogl/gl/cogl-texture.c +++ b/clutter/cogl/gl/cogl-texture.c @@ -32,6 +32,7 @@ #include "cogl-util.h" #include "cogl-bitmap.h" #include "cogl-texture-private.h" +#include "cogl-material.h" #include "cogl-context.h" #include "cogl-handle.h" @@ -50,14 +51,8 @@ } */ static void _cogl_texture_free (CoglTexture *tex); -static void _cogl_multi_texture_free (CoglMultiTexture *multi_texture); -static void _cogl_multi_texture_layer_free (CoglMultiTextureLayer *layer); COGL_HANDLE_DEFINE (Texture, texture, texture_handles); -COGL_HANDLE_DEFINE (MultiTexture, multi_texture, multi_texture_handles); -COGL_HANDLE_DEFINE (MultiTextureLayer, - multi_texture_layer, - multi_texture_layer_handles); struct _CoglSpanIter { @@ -2411,210 +2406,79 @@ cogl_texture_polygon (CoglHandle handle, } } -CoglHandle -cogl_multi_texture_new (void) -{ - CoglMultiTexture *multi_tex = g_new0 (CoglMultiTexture, 1); - return _cogl_multi_texture_handle_new (multi_tex); -} - -static void -_cogl_multi_texture_free (CoglMultiTexture *multi_tex) -{ - g_list_foreach (multi_tex->layers, - (GFunc)cogl_multi_texture_layer_unref, NULL); - g_free (multi_tex); -} - -static CoglMultiTextureLayer * -_cogl_multi_texture_get_layer (CoglMultiTexture *multi_tex, guint index) -{ - CoglMultiTextureLayer *layer; - GList *tmp; - - for (tmp = multi_tex->layers; tmp != NULL; tmp = tmp->next) - { - layer = tmp->data; - if (layer->index == index) - return layer; - - /* The layers are always sorted, so we know this layer doesn't exists */ - if (layer->index > index) - break; - } - /* NB: if we now insert a new layer before tmp, that will maintain order. - */ - - layer = g_new (CoglMultiTextureLayer, 1); - - layer->ref_count = 1; - layer->index = index; - /* Note: comment after for() loop above */ - multi_tex->layers = g_list_insert_before (multi_tex->layers, tmp, layer); - - return layer; -} - void -cogl_multi_texture_layer_set_texture (CoglHandle multi_texture_handle, - guint layer_index, - CoglHandle tex_handle) +cogl_material_rectangle (CoglFixed x1, + CoglFixed y1, + CoglFixed x2, + CoglFixed y2, + CoglFixed *user_tex_coords) { - CoglMultiTexture *multi_tex; - CoglMultiTextureLayer *layer; - CoglTexture *tex; + CoglHandle material; + const GList *layers; + int n_layers; + const GList *tmp; + CoglHandle *valid_layers = NULL; + int n_valid_layers = 0; + gboolean handle_slicing = FALSE; + int i; + GLfloat *tex_coords_buff; + GLfloat quad_coords[8]; + gulong enable_flags = 0; + GLfloat values[4]; - if (!cogl_is_multi_texture (multi_texture_handle) - || !cogl_is_texture (tex_handle)) - return; - - multi_tex = _cogl_multi_texture_pointer_from_handle (multi_texture_handle); - layer = _cogl_multi_texture_get_layer (multi_tex, layer_index); - tex = _cogl_texture_pointer_from_handle (tex_handle); - - cogl_texture_ref (tex_handle); - - layer->tex = tex; -} - -static void -_cogl_multi_texture_layer_free (CoglMultiTextureLayer *layer) -{ - cogl_texture_unref (layer->tex); - g_free (layer); -} - -void -cogl_multi_texture_layer_remove (CoglHandle multi_texture_handle, - guint layer_index) -{ - CoglMultiTexture *multi_tex; - CoglMultiTextureLayer *layer; - GList *tmp; - - /* Check if valid multi texture */ - if (!cogl_is_multi_texture (multi_texture_handle)) - return; - - multi_tex = _cogl_multi_texture_pointer_from_handle (multi_texture_handle); - for (tmp = multi_tex->layers; tmp != NULL; tmp = tmp->next) - { - layer = tmp->data; - if (layer->index == layer_index) - { - CoglHandle handle = - _cogl_multi_texture_layer_handle_from_pointer (layer); - cogl_multi_texture_layer_unref (handle); - multi_tex->layers = g_list_remove (multi_tex->layers, layer); - return; - } - } -} - -void -cogl_multi_texture_rectangle (CoglHandle multi_texture_handle, - CoglFixed x1, - CoglFixed y1, - CoglFixed x2, - CoglFixed y2, - CoglFixed *user_tex_coords) -{ - CoglMultiTexture *multi_tex; - GLfloat quad_coords[8]; - GList *tmp; - GList *valid_layers = NULL; - int count; - GLfloat *tex_coords_buff; - gulong enable_flags = 0; - /* FIXME - currently cogl deals with enabling texturing - * via enable flags, but that can't scale to n texture - * units. Currently we have to be carefull how we leave the - * environment so we don't break things. See the cleanup + /* FIXME - currently cogl deals with enabling texturing via enable flags, + * but that can't scale to n texture units. Currently we have to be carefull + * how we leave the environment so we don't break things. See the cleanup * notes at the end of this function */ _COGL_GET_CONTEXT (ctx, NO_RETVAL); - /* Check if valid multi texture */ - if (!cogl_is_multi_texture (multi_texture_handle)) - return; + material = ctx->source_material; - multi_tex = _cogl_multi_texture_pointer_from_handle (multi_texture_handle); + layers = cogl_material_get_layers (material); + n_layers = g_list_length ((GList *)layers); + valid_layers = alloca (sizeof (CoglHandle) * n_layers); -#define CFX_F COGL_FIXED_TO_FLOAT - quad_coords[0] = CFX_F (x1); - quad_coords[1] = CFX_F (y1); - quad_coords[2] = CFX_F (x2); - quad_coords[3] = CFX_F (y1); - quad_coords[4] = CFX_F (x1); - quad_coords[5] = CFX_F (y2); - quad_coords[6] = CFX_F (x2); - quad_coords[7] = CFX_F (y2); -#undef CFX_F - - enable_flags |= COGL_ENABLE_VERTEX_ARRAY; - GE( glVertexPointer (2, GL_FLOAT, 0, quad_coords)); - - for (count = 0, tmp = multi_tex->layers; - tmp != NULL; - count++, tmp = tmp->next) + for (tmp = layers; tmp != NULL; tmp = tmp->next) { - CoglMultiTextureLayer *layer = tmp->data; - - /* Skip empty layers */ - if (!layer->tex) - { - count--; - continue; - } - - /* FIXME - currently we don't support sliced textures */ - if (layer->tex->slice_gl_handles == NULL - || layer->tex->slice_gl_handles->len < 1) + CoglHandle layer = tmp->data; + CoglHandle texture = cogl_material_layer_get_texture (layer); + + if (cogl_material_layer_get_type (layer) + != COGL_MATERIAL_LAYER_TYPE_TEXTURE) continue; - if (count >= CGL_MAX_COMBINED_TEXTURE_IMAGE_UNITS) + /* FIXME: support sliced textures. For now if the first layer is + * sliced then all other layers are ignored, or if the first layer + * is not sliced, we ignore sliced textures in other layers. */ + if (cogl_texture_is_sliced (texture)) { - static gboolean shown_warning = FALSE; - - if (!shown_warning) + if (n_valid_layers == 0) { - g_warning ("Your driver does not support enough texture layers" - "to correctly handle this multi texturing"); - shown_warning = TRUE; + valid_layers[n_valid_layers++] = layer; + handle_slicing = TRUE; + break; } - /* NB: We make a best effort attempt to display as many layers as - * possible. */ - break; + continue; } + valid_layers[n_valid_layers++] = tmp->data; - if (layer->tex->bitmap.format & COGL_A_BIT) - enable_flags |= COGL_ENABLE_BLEND; - - valid_layers = g_list_prepend (valid_layers, layer); + if (n_valid_layers >= CGL_MAX_COMBINED_TEXTURE_IMAGE_UNITS) + break; } - valid_layers = g_list_reverse (valid_layers); - - /* Enable blending if the geometry has an associated alpha color, - * or - see above - we also check each layer texture and if any has - * an alpha channel also enable blending. */ - if (ctx->color_alpha < 255) - enable_flags |= COGL_ENABLE_BLEND; - cogl_enable (enable_flags); - + /* NB: It could be that no valid texture layers were found, but * we will still submit a non-textured rectangle in that case. */ - if (count) - tex_coords_buff = alloca (sizeof(GLfloat) * 8 * count); + if (n_valid_layers) + tex_coords_buff = alloca (sizeof(GLfloat) * 8 * n_valid_layers); - /* NB: valid_layers is in order, sorted by index */ - for (count = 0, tmp = valid_layers; - tmp != NULL; - count++, tmp = tmp->next) + for (i = 0; i < n_valid_layers; i++) { - CoglMultiTextureLayer *layer = tmp->data; - CoglFixed *in_tex_coords = &user_tex_coords[count * 4]; - GLfloat *out_tex_coords = &tex_coords_buff[count * 8]; - GLenum gl_tex_handle; + CoglHandle layer = valid_layers[i]; + CoglHandle texture = cogl_material_layer_get_texture (layer); + CoglFixed *in_tex_coords = &user_tex_coords[i * 4]; + GLfloat *out_tex_coords = &tex_coords_buff[i * 8]; + GLuint gl_tex_handle; #define CFX_F COGL_FIXED_TO_FLOAT /* IN LAYOUT: [ tx1:0, ty1:1, tx2:2, ty2:3 ] */ @@ -2629,24 +2493,22 @@ cogl_multi_texture_rectangle (CoglHandle multi_texture_handle, #undef CFX_F /* TODO - support sliced textures */ - gl_tex_handle = g_array_index (layer->tex->slice_gl_handles, GLuint, 0); + cogl_texture_get_gl_texture (texture, &gl_tex_handle, NULL); + //gl_tex_handle = g_array_index (layer->tex->slice_gl_handles, GLuint, 0); - GE (glActiveTexture (GL_TEXTURE0 + count)); - GE (glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE)); + GE (glActiveTexture (GL_TEXTURE0 + i)); + cogl_material_layer_flush_gl_sampler_state (layer); GE (glBindTexture (GL_TEXTURE_2D, gl_tex_handle)); /* GE (glEnable (GL_TEXTURE_2D)); */ - GE (glClientActiveTexture (GL_TEXTURE0 + count)); + GE (glClientActiveTexture (GL_TEXTURE0 + i)); GE (glTexCoordPointer (2, GL_FLOAT, 0, out_tex_coords)); /* GE (glEnableClientState (GL_TEXTURE_COORD_ARRAY)); */ /* FIXME - cogl only knows about one texture unit a.t.m * (Also see cleanup note below) */ - if (count == 0) - { - enable_flags |= COGL_ENABLE_TEXTURE_2D | COGL_ENABLE_TEXCOORD_ARRAY; - cogl_enable (enable_flags); - } + if (i == 0) + enable_flags |= COGL_ENABLE_TEXTURE_2D | COGL_ENABLE_TEXCOORD_ARRAY; else { GE (glEnable (GL_TEXTURE_2D)); @@ -2654,19 +2516,62 @@ cogl_multi_texture_rectangle (CoglHandle multi_texture_handle, } } +#define CFX_F COGL_FIXED_TO_FLOAT + quad_coords[0] = CFX_F (x1); + quad_coords[1] = CFX_F (y1); + quad_coords[2] = CFX_F (x2); + quad_coords[3] = CFX_F (y1); + quad_coords[4] = CFX_F (x1); + quad_coords[5] = CFX_F (y2); + quad_coords[6] = CFX_F (x2); + quad_coords[7] = CFX_F (y2); +#undef CFX_F + + enable_flags |= COGL_ENABLE_VERTEX_ARRAY; + GE( glVertexPointer (2, GL_FLOAT, 0, quad_coords)); + + /* Setup the remaining GL state according to this material... */ + cogl_material_flush_gl_material_state (material); + cogl_material_flush_gl_alpha_func (material); + cogl_material_flush_gl_blend_func (material); + /* FIXME: This api is a bit yukky, ideally it will be removed if we + * re-work the cogl_enable mechanism */ + enable_flags |= cogl_material_get_cogl_enable_flags (material); + + /* FIXME - cogl only knows about one texture unit so assumes that unit 0 + * is always active...*/ + GE (glActiveTexture (GL_TEXTURE0)); + GE (glClientActiveTexture (GL_TEXTURE0)); + cogl_enable (enable_flags); glDrawArrays (GL_TRIANGLE_STRIP, 0, 4); /* FIXME - cogl doesn't currently have a way of caching the * enable states for more than one texture unit so for now, * we just disable anything relating to additional units once * we are done with them. */ - while (--count > 0) + for (i = 1; i < n_valid_layers; i++) { - GE (glActiveTexture (GL_TEXTURE0 + count)); - GE (glClientActiveTexture (GL_TEXTURE0 + count)); + GE (glActiveTexture (GL_TEXTURE0 + i)); + GE (glClientActiveTexture (GL_TEXTURE0 + i)); GE (glDisable (GL_TEXTURE_2D)); GE (glDisableClientState (GL_TEXTURE_COORD_ARRAY)); } + + /* FIXME - CoglMaterials aren't yet used pervasively throughout + * the cogl API, so we currently need to cleanup material state + * that will confuse other parts of the API. + * Other places to tweak, include the primitives API and lite + * GL wrappers like cogl_rectangle */ + values[0] = 0.2; values[1] = 0.2; values[2] = 0.2; values[3] = 1.0; + GE (glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT, values)); + values[0] = 0.8; values[1] = 0.8; values[2] = 0.8; values[3] = 1.0; + GE (glMaterialfv (GL_FRONT_AND_BACK, GL_DIFFUSE, values)); + values[0] = 0; values[1] = 0; values[2] = 0; values[3] = 1.0; + GE (glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, values)); + values[0] = 0; values[1] = 0; values[2] = 0; values[3] = 1.0; + GE (glMaterialfv (GL_FRONT_AND_BACK, GL_EMISSION, values)); + values[0] = 0; + GE (glMaterialfv (GL_FRONT_AND_BACK, GL_SHININESS, values)); } diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 061acaaa1..58efa73c5 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -3,6 +3,5 @@ EXTRA_DIST = \ redhand.png \ redhand_alpha.png \ light0.png \ - light1.png \ test-script.json diff --git a/tests/data/light0.png b/tests/data/light0.png index fdb84250ad2ea0871e96a591b3315d6e6a7cd1f0..66871ddb35bf15c68f8db660aba3422eeeb9844c 100644 GIT binary patch literal 5674 zcmai22T)VpwvH02AXTIjNGPFq2~A3XgepNmnh4U7-bLUC{C`M9gwTtiND+|Uq>F&` zM4B`~{3)SFy7U+Boq03&-S_UBGw1BttL?Sc*?X<;9P~XST{;>L8UO%5heW_pq_K~5 zPf$^io=glKv!vmgKT1~7fAVMC8U zY2o3$H|m(0Iay?H1Fj@%0ManV_y-wRbmaEs#ZjcpHGmYb3kU#20{~Rl0U#>CAE1Aq z{AVsn??34Nzg*Iq|JS5L$)bG0dQxt6kOglJ-1^|K!k69XJx2lp{>GGQ_J{0CHe$g02!)%>k>Vpe_E1V zJA_GVLcd0*{G`UNa}pcoxUAiqoat+y&SY-pzA42SAWP*YDzs0 z+NEafo|i5d7uX*7j2NKyb#^$6D{09Sl;!X1uIH)ly5iUmX*z3P;FYC%l*h~r37K^4 zG11xAF@NC3{rLd;maXt6T+1iLzhZifB6>Kj^KM$fAK^v@41pIW=KFIS;8^8GC$0Ry zzso}vh>e{KHrdkIPml?5Jdbk$SN>MO>}2x~56i)gPu!ehJg$Fs_oTz9vE_)e%NB+= zJbMnhGL~qKo17eX%OZchkuz*wobHW9__i}7dBpG&u)lM+=D%+drA z4DQ4pSAeQ4N5eX908{DTfxZGJK)=q;UvvIrf##|pD?b-l50Cc_VcO(t>68ba14(=3i{)dUUsR&w=$QsU~?kI^eme8v?? z%r%I2jRY98az1fabu)$rRl6X+Cgm4FWtz-bzQ|27hJd2dY<`HMl%y3NyEC{J1!THA zJL(QEj8(rAJGM@@{-h=(5Z53+k!X0k2SLg0bC~J7bS7mec>0%y&P|rGhJp z6)P74tEo=X04=9g-2662(^7^@Cs_*5#)ZNv#%!SsUH~eAm+2u+(2B4 zLwGs;a}$QuPjzRUnz#?9S?RuMXQc^rq|juB65Jno7z|^EY`Cpzmp>h6L8lvIM%+?w z57x-B%YJa)hEzn_VMhcC;&qQ#oJ}{449yD$OCX~I)4Cj7dYO2j- z$eiS2j2vZsHqC4(K=mZ3-V%#g|547J9Z~cVIM|WSSVb?=XLkIRbHcgzq-tzj@lvLZ zF{~n?(l$rF#f_o+!0fI*aWn&=b-r1NaIZ%`#e*N#Om7eXFjcV z9QL)3=@R5+4{^ama)!5Q+JTw|H^c{RB70RLzdAjA7kVt_X_CrR!xeu+59Cx-W^_sO zn(k_&zatymyjTsxdHZd;>lU3Ebx?eTk1dxg%~yk09?fJhw?&@iGZbOP-2^4yI(K^l zh61reIDTFYp`J&1#74Xu<=W=Vc6^7-V%ckst>!8hnxClaj{+~2w%5h>I!;QtP~31FIQIQB*B^Ef=1bkyo|X}D zDVA&S`ebmCwZH$9s+;gY6)DN%5**{e7XFO<9HCn#@b@1ix1cZ{|KQZ_;fDQlvD2y>QXlH!%(KvR-i`0O2WV>MdaxozxJKt!hyI~ zuk2<)auRi7dDQNu9PcuhX3WgDE!XrS7}&hSj$LD*ue8$ZVOw%^ZCx~y_f z7@l`|Lj($c?tqCEA7XOU#QLj#KNjYT=$T?WjOrGl6wogccz8F>?)5x9Jn*N@DokE>D$^9e8oilaCSAQTZbCt?6hJpa1AoiHZ77? zwT`Jw-6^iTK&W=UQwN{xjpubv4;RngZNA^NPhm{Y(03+nB;y%EArtVio-tP$AqDma|}$ z#xfAN7EKX#Q`5{}G^1&VjO)o}^-T-iBb+4eWlABiRLMprbJ9PVyJK-kXo|28yNj5pLBC3bFKa$*je|=Dd*B_L0=qP;4!BSD2l zs7iT`pLRfPn}Z4i+f(V*pNVo`IJ!_AcB&v!nT~)_{_v&2PXW1){Waow(4m#>L~h9q z&k8W%jx9$r_a&Sp34>|e*Be*fxf!kgYA(L|%z$t$t|0ffZ$QrjQQpxLVnagXowkIW zOVd#j+JI>Up+3KiQKu~8^9j`Bq=Lgyk|lMNoRAi{bpW6Vc4ltA`TDZBkZjZ6FPb5B zaVMsO2MA>zS>IG+i2DYOE=XVs?1PCxvaBpjerx1|Hy)R zDeSibppQs2XHb9y&wQ=#S;Q29_7EnZ5g{q{hwNJ0xnQRJ0M zzWM#-2#R^mZnv~{6GY{M0Jf;@xK{Ji&%fxwd>UlZBAa9SyJ8E3n^@k)c|Py-3r1kl z!P=KoM_w5Er5eS-#Kc1&U+f(~)$$aGrD)TZ0iu7;wH}5^jP&-xMl#zokV!`QdRdVvXip^T`2zPrg7Uusqzi|$YOKWhx9M} zaVEd;8dy`trA;h<$fU~Ydxwz6?=BWTdc>Kyo6&9jscK~_X7WUyI@iaC3)1r#K_k#> zGVzOq6*Uzb&2Ho$PMMrW=(7@LFr)XGMF{??eq-PqG?z;%%VghMq&nvb;dm;Hiztb#kQ*jVVKI_WM_oikvVU zKlDlCb{to_TN%fE%gKjBng$Q7f9(rV3vGhI{OJl!!_0fB33y*>S2aB%n&vukXA4rj zSTRqLJ6GkdSo9R_l3FwroYZa3NIXvvc7MT$95;67#M6%-hqYaULH;yk0&~Xa-UU|R zi*I#4BnziM@p#jA)IC(!jfmJeK4eSeqn( z_=jDdFlB1K5zuj%-h9)rGf%g*E6j|R)M7wj!U+}L2CfI=O_|n9_pGIUbW~PAUfvVM zJOqbl3TCDMsnYaCp$3txvT_9xbu%3!siW0V@nya8%JF@SpU3ah>eKdmxu&P^JM|y5 z@oSsUVDcd&dVkAI40ImI9gF)q_S4sLEjx*j60blprtSfGTODJLpQix(J$I8q3)5mv z-#lQ}hA6ar)P?K}rD!9}HTQ!;KQ5h$Opsw6Y|u1(R?5QYFcMmx#QdDT^W46T(+Zi$ zWau?harYP8*D?#^fXp{*N&Jj3aw!v4HP97pMI9*C7F2PoWD93GowZJPn$r@sWGTj6 z=tbScpdSoTn1lLUVKCM;Rs8a=MA_tuJ(&vryK^H?-AR#suX^Pzq$NMKf1KHL(KaDL z=I1J4|HJ&7Tm3|hn4vbc$Abfi8=`4OyDY9ezX5YrxBgExTwQqnYFVsW$Hh6=PnaF+#Zy8ZJna zL-NpMWyVFnZ{HXb5Sw3Kq}!k0IAL#QZ?Wuj{5s-XStv`wJC!YgTbfcpwu6VWY?)Yl?>)3Sg1K!KM1zM-ct zGE`xrUDkM}(5@coOT(@kg)hoM47?PMKNKmT5t>MM0I71mB+iXayo~m#995 zgb>3E&(1S7eYyZ#0%);J2Mp<>s2Z7?D2jBj68>Fcp6ZlYHXi>^e5hA9uK?{qWqM8Z zZ>L|=06{P63;JNhlP;JG#zYuclrw10tmI~i9x-sN_>sMkE0{k;SHhZWf%1G@6?!3T zXK@-w)x$#H`?$ya3LTd0u1?F&v?j9H@j)T!%Od)%5nrmX`IV*3G@t-`X+!v44hR3* zUR*&ILsX7d)Gc+|ul8TCN8aG{Jia&HY@J7{my_>3UW|Q+-E+k7m zsN*iR0P?^eOOr%`htn?w1?si)#2mP0g$eO=rim;+z)(522ib`pcS7B6l?3q>!Nafx zEaxZe<34ap_KIc}kbE=?@j|s;Encx}(XFBj#p}+vZ|~Yc00?CTr6cjP)A)YoZFn3ZTAQyc&71S-da?fNoBS@!n@NILF zMN0)4$Ys-uzI2K#H)y2~w?R2!dcSlBP|u3-5}89AO(i^vmOD3`SRsE3Jdid}SdwO>wSDcrJfSn>nV0%kq77%)pSEiOT3LK4U{t-m48&(p&U4!38P(r&~26 zyXP4YRp<%EZ&+g4L&kG^OYvJSR~z?zXEnWp5VZ7FFFYe!2;G zv52?1I$a>PJ__44^D{}wLQ;oh+9Rkb7J_3;k~9k?*s1bXuO9X$)fhaQWRS%F4Rn;D;yLzOtM%Y0;!`Lcczg z54v6Tf-@Oj_Jqpjaz0I%1P{Hx*R;qb+jRnK8& zhtOZ;f>x?|QCyPRq*zXonsw#&;2L=UK>(X&GLkN(o-?E_Xoyq`D^3ZGOQDW_tz4vI zSr;Hz&v<5t-6-TGKoYjEt(tZz)h^V&Hf5Q?0o1B3BBqz#wiOMePw(! zj3X+9vOzD7e&{0gSC=HyVle6DoTDA@o3K1{c{KQ_^1PO5QSPd{y9AXEW8|g3`+K)~ zK2(u~8tIY(igYgCc8iVui?<_BJ<)|Hn^b{r`99<@~ literal 24410 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

>jNtEp3Sr}Mp4tG6qr+bqb???-IMGqu{g9}GD zwJ-T{gELo@*>3CCy_>7fSm;>emWiaD+9cz$9eq#p6+xbK>(nJ%?0B!u@kE2Oq4vh1 zpksI19Cwbmq6jDjydKJTl0V)lyX=wXior(*3N4Id(fKKDZ9QK7!1;4*s7zT4*nh%yGoLY1~LQM%ni9_ov)ny$;YU1Fs zHs(O#3)}P%<1B04e9qh&28t_uxxA--SOu^~-DOw#YUt5haQoymr$S0`z6qQz3pI==Z)W z6nrO0`0tJv2Hw%+rh))z;qQ&N4+uu$Rx+Pbq+kgquPN-AYZ!uBstm*w?m+MMb6!|2 zSWHGB@&(sJlh)*KPnQgt=X{C6D$JU@$;Pe?_73erptnNJ9y_0X_@ z3Q5s>r&Q?gz`Z-QQW*H}O|3Rpa#F6lMS-OAVmy;_Q#83tbV(^GGb)x4!L3HVMZl_3 zc01;PVjCJmdM*br2B|#byViAU^hvSMhGaH|Q^`En3o^oNkO<=nG}T~DVHGd5h<$ur zNhZM%X+^vaA@`bf!3(u;zgM)_i}l?&JOyk#8z=75@_NrA@3=NcvkUU=a>#gVj^aV( z5WUDXRzA&HG+LVrxnJ^d!@+n9_Ooex1j!r=3yQ^qm_=0+7j?02usZ`Os7$-Aa12UB zw+<$x${7tRZP3Js@RBH0HSw>V-$A2rbb2Z{eZONl-3TuEzM^R{)I>-OZyeMqa~ls~ zhaw2w)0ghQ)fc{^!oA!DcWSlgkz@ahwISa$YW2k5No;vb-MFap4c*oeXKRp8UbZe`0=HVc0<^{Dt%@DSwE zI-n^Hjgq$=$fzzmPS(Va3;=o1fT)_x||7^^cwAeV>sCo6p#TYVO6+|HU)r;4RXx%>*7k<274b zmwm08Bd(KX?bmvg&WDl{U=Zsj;Tog*;r#{iah$#re9Y ze|+n_JHh66As|s9;$64oUUv)&w?zjX&}91A`Jo$Bb#8=-AWrz0wOF>9#I+|n;*j&C zTdRc`>Ug-m5X0t6X|&u;8^!i#dXAiWD!FCEyIDtZA5l|Pa3P;2HT#Iv=i^w$$F%n zI&lxFS?-5#VScBL@{U1^;whzP(mTYHnJ~=qktArmQR;!edlm9G--Z8@jWAWoLw~jS@+az-zGb4j)Ilt(2J->Y@i40iR z1k`ZlQ-cXD7{{+sWD&ciDgO#IHX*mcR@jy!QDJ86i! zSVF{hQheK8mytDjtVMA>Z|=&(Fnn+=&oe1<@Aq_;O(H_Yt206zrwrr$?V24t^zLWzS=NWyP$bak!k7}hxY5H6#sBy#Gnam&hp0$;@1jSG@n!d z8ud9HhL7~fWAZ+y?zO|@yVD9cDz!WIin4a~VBDx0sa$7VC)8Z8Y<*3j{X3W~7GoY@ z6wyRtfVYqBwPtf{MesY=X30#Dqf{}iKHgg|B>%GdRi9-Ms2l}0!~VDV5uS!CN}>K2 z9qrms@>}$u%~1%+&5`IPx=-z72i54Ii}0>Eh39Z%GbT{S)G%sq>a>H}$Vb9WTQj(EiiQ|lJ8Qy zKQ+RxBX0fvy}w_zTn`GymvPV1`HX&@v}~nUNy_pT@tBHKlVLzy7lS?($1uKG(|k9N zkkdwzET(o84xx!({U4?$ox_Gun6LE#NUcJ{kgEAGwxj{Ca`sM9nj4;6sngGOa^`)A z+_|=RSAHB?+`M1x(F04bb)_^AZ|7qg*t5o?ZU3k&FF|&bPiP?%tLvJ!4kvMVil8js z8wCPBjCw}6keKt+gvGyIyl-j!i6$@Sv?30ZvN-M<-Qr7;Zcl#Cd)25iIKR8nz%lI# z7h)1QGS5jzS55+z>nNHlGosCLA{^!xI#MuOOU6)u(Wp6BT7*{;vgTWCnDR}l4r+A2 zXjL*>SkQ2!HmrBp_n#1f3DSLmg6j;<+%w66J>z`AyAapW)bw*^KCwXCEP_EA@g)+| z&6oW?5rGZXEt_OVi@yZV-Y63Lzmz(i8YRKQTP?f~rrY$9NA&f?kF+SFPgstaJ$Cnocu{env+zygp1khHTf)@t^#^^lwBS zGlqBF=daz{-TY1pf=k2tL;n47sd4vptor9-n)tLf<7JxRq9AHGbq$WRKP#7OdL$ZY1iP6O=5^AKwrJ5i3^fvzp*!K>)b;O+zL9ipof0iV82aLJgAcW1YA|*4l@Vcv?{xK1m-IW0C)f(<^4xnwN$*h zE9tKH>`s?|Eg8ih8P`Itt|CC+IA;2ckh%+!+t@}T$N7LW^HHp62{hZ&7O|1r{q~@e9qBK1iqX7wYNLy-NO5>X; zQj&Lb+=c*r52MU`q|L%qe+c}JHR$G@TXKD9`45=l1(#;yO)AuV8B ze-Xu0>}_^J{P6j`Yc)q9*BE%Xna2!{bpYt=e|74PJsFB*nYBTAU)da+GJavH z>sEsBulBQNsr;W7^M6UH%VetkS0QCuuK~_J1rsl(dOb%gw9Zvs_#ZtYPB`iKm&1b$ zx|Zf}^3f(4x`;U|Ehlkf3B!pbwAMtb0O08?eCv_bBfgqPiKF`6t|YskLy@Z2+3m`{ zz<7sk?io%O*QD1quN+(6-iCbkA)|YQH9-lqRlk%idP>`?@FB0KFHaF3%&J|KH0i*^ zEX6xtiApxCOVDm6BU7|#>~@o>o6#Ar{%FXP<35yOXIEv!*M@ZNpxRfGr`(o2Ec*Cm z6?8SYR$$*YU%d~1^u&?5VJ*xWjwZe5JD$4Y&J$dh41z~4`@WUyFXH}L4_Qu;&75Cv z3oMV;YHd1fh8!E-ed>uVKRKHNXTT*aj$O1_R143jTB+^se!2}a>>EkJi?bo_%(f%` z{+KLp1Vlamtnbo~)ZYPoLN8hvx#|5R?0i{}l2?qWX z%OKHamB0UYoKx;Q`~st78FtJczS|<&j1ZHhIAurZMhY|6#&j7L{%)gK%7#zw52Md% z%(K*WLp8tk&&G1v8W^ph~J>#LqaF7VXB1_qd-j@^EQGy+@+UV91 zKOSe_MMc2%q1b?xaSze ziEcK#5w5lrJU)ge8w*`dk*zM;UvEI&tNB(Ne9j~sXmM5g%DAh#QC;3h->wT9$@F2K z_uqObjylDaxx=J;@GS7^k&s*$&U}xxQFbHNi&$}YDxQD25vxocrPKngxJ`2wvjhEh zluQxi-YH-v{=}_>xA^<+$f6RI?6H+G*;S~(Y@x?913ImIfnkbpVi5IpAak1yixemK)@U4NCun|Zb12U}|hAv3+2ICk>$J>{l z$HTFhGQ?udW3?Dp4VAG7;xT^M(s(8U@nzfp^dbi}cIyNTKd(}O9Eyk);_>W|Q=8hL zr(0vq?7X!ip;&IbE{yX;>&!C6yfd*bYaAU4o_-%6gXI-m0D98CRlk2}W?>+Hg7LRf zC*avIIlbBgZmz$hPUQ7OlXz`>k_q+ed9_h3zBAO1kP>ejFN9M=lC_jM>0^-xuFuU|S;cGEF z46oL8(IhJ2_}eG5s&+C&lhKz*AZ9b zf$=F~B*pI90QaUw-iD|rg}T;^k;8jH{I4EvBqGv0kXjI)yMc23OerdpKI=}yJF{@G zrc%d*x~EyE?#M?FeIGS$bAx|HhC))h|C2r-; z<`f)KqAnHpHnV-?^w@9Ig4W-nqe*j)mnbI$W~vtMQyy3CUgxS!&QRQ$il*r zTzACZ9u>ANr`MvpUfZ5U1g`sVukT%uHFIrGU@S|Ycu}ma*xjiXLQl~HK$ID%t@cQR zmFfFJn)6|Jg9n=4^CLt|Bi-&8kS3KIap24h;^^jfuoYz;l`<(%)p89fTzKEzW|eD% zl)q=5^w9g!A`B>x$QW zdUG7HI|a8~K2F!*DbA193hD5-Bq7wS>W;rRB4 zBm^t9*V={GrXEK(Qc$!i9Df1d9&0z(KfJS?g{T|R(X4&6@1*|C-#)l~fNkT$9@3Cr ztqH1aB3HoYC-N|wI}y6P)&4V&Hb7PQGC7z~5U!;|&i zo8hR2uNDi{0k(Dsw@%GQz_1}vco6v1%8AB^u5lT?vIeQa7jp1lU|z++H3W}tkP{g3 zeEo0frmk0vi1&cGxMzfHG7zsWt89>6n7=+-If|V0xz-^_BS_;NHP@9r$u!RxIJ|C? zcclLHGh^gfiKbzmY4TJyf%tR)`8vk6)*Oc>aaLa712me57MgzbTzgo>htnGNHT2t{ zuSrV6=#GgFQnC(r^srkDJs_jg{E!Q0Gi^sCT12C7o-IwexYWIW%fe^-X05v7dOHS} zKv6~-5C@$?5=&}<4y5_KGDxe;xMu`#pNUOL0+0*y&9>jdhn!)W$(E<^At({4I%aDi03KFmy~3wd{3uf6sgI6PrInUgqVf9 zPo#EGTAk9gKNkO#B${JX+ zlYC)zZWx~}ZGLGXkrtpTIJ+IVBXto5#H3lt^r-l_cQDO0KlY~A+9lVuJRvS5csH42 zF9*l1=q(;#tELGk6@;qbPRP~1ms!pGB3ebLh~(pof^^jsh99u2Q^`7DCw#v}^BVY= zIoKkUfGkbz&6#9wnaz-fxgL~sqW;cuav@*JjKS#9#mV38Xf=faW8Qo*=zc!HjVf|9 zdM^&5$cA)OE`|5gm5aAQYPptRn>Ap)#GYKsw~H{6%sp?2bB0i&xIn!;%HG3)phl2D zYMx?a*KZyZZ+pOnk%UlOFA?7$h-#}`G;rL8JxEP49Wud|WiS@I!C@*-*m|mc5D8Vm^slRCI%V25rOWuyPEu{}LL7?T5A1yAh{IUraj$ zP${`D&{e$eC8pW%QDxtZ>iSMMsyWR-?Y{C^*zi!YerEFJatMmskS!l}P%u1> zIXQ611IqB~TX*cipUd+KYXsI+f^Tl`M2hU)>xn5XJ-%x!4mowmP8wa^6bmu)O8Wj( zcNe0b2Io3}(GN1M17Q?G@elVJ3m^?Vi?bbah^QNPE-LZgv4^~+O3wMjgf}ggOMdS> zi`calVRBBm8e&QX&HR!WI2xA{juk$P056L3`VP8RIw-P9cAhAO6h%t0o(_Z8Sut@S zk-oMaPxgqmf6o+mDRe%4;mKlMJZL!Yr+luwuYW2cJZa~-E~he|Os?5M2KP47n*FVe zH@tAMxEf;4>8%Szn0DR|O->l?55mLHEghsxyo!GPPMYBQ=biBYKKz^VOhLpljmQz@ zT<4abF!!l%Ki{$u>H}s;u~tnnvDnToNpo{|L*clnFeZTLsgjeHQ`#V=kr^R1MY}x2 zr`K*o9r2dBGrLC?hm5Ib6TjUFB6@>6IPCiH);}=hQ5(z{^T;>GhuAT^3%#4M9Uq0L zvstx0DLPKhC<#8IRMtv0>3*En+dUhk-(L33GAgPaiN~JWJ6bPA6qzorIu89-2)vkwQ`X47A|OI4xaDfH~K1Ed%*s8HY9=zcg{j}k1`^BU)GluKjou+w5 z<3l8v-#|HaR6l|8A}#%ie1c%GvHuwU?j$8JCwsdl$K?#nJB_XO^>h7b5s|E{D~Svi@X!!$A=*JPTiz_hYP^t4I!Gj8##*Rd{Grfgx_e@v2IT%}G^~#DSA*)Pqngd>q8cX{G{Jdm zsmZLBsF)}bB2J`XzC$G|m=QSbQPE9|sFA6in8sN$-(Z?l#Y!0*$79I5R*PVEcZvA6 zdevV5dP=Kez_PH+so+vcYSNF2M&^_k;xIlPE*J=2PKA5;nvh}IKiGRyw)1RB4RTsi zU|NGVM0}5khUl9d1KzOp@YpvDG%aDx?LkkRIRK$R+rw!z-4o53 zUHbB`f)(hCI6pJGjR3Rh%rnvqfrD&i`W~a#Lyc22@Od4i5>?a~zEdy^3I_O|A;**I zU>#r=>F|Yc3FmTp6=rlQia+2+wH73%cPcqCJbbG24PT#L!=qr}30}6}`3{>-IE_%P zpGF=M4g20s;X!htd{BXEX2M1rqa*0_Xg-mf?WCOc7$o#CmOvAXtjHJ4 zd!)>CXH@qF(x)?)L^SClU=ADB=nPJ7}{kBcDtMtYog+N^MNM3Y8EW+~vh(4OlVbPXAi;p6If`~E8;$8U6 zO*F;eE(RpZEb_)9pr!*kt-(K-lUC#>9DQI_ zQ{OFn0Uiz&aC4}`G&TuY)5MBqvEw7BpkwAvA8z3pH}2mRB>mYi@N`)=xmPT| z95(a^0;B7k!fk#*^k7ocv4!9tpXYFjlW^ahmkmrLs;o;wFPZ-m@Nj%_(o- zPNJetpJXGAwG<6ToHp~-;MX<@7lHzq38O)u!)GQN=4R(z3l0{ey2H_&C)$Q8)-Q-Q zuIn`+WB;ANkWLxK8T8kseG?herzl`4IR8WpQu1$nvDR|08D5MCbfS!=b{M7qAL;Cr zYCF=9rkfGNdJ2Pj<_J!;In|WXjqwZL--KPHx03@h`+6`lfMJP=xrrrcWZ^MOD->Dy za&xp277Dx0Ig;rB7CPe0ov^atXhd{Hw?_e8--xRowV* z8l=mq8=@1b5D1)#mZ3wiqpRabN*>`&?R4Zgg@Z9&jK7`{9UK%#i2^>tu3jKJ_T8Kh zF$~W6usL%e*sMs(P3dbPY_)WGr3|qU$d{4hBi75xd!Ah2Mv%tHRV(b`EY%@(1?S>6Jap+I2_W*4-U1ZMk6Dx7OX?V66c21<}mnyP^6n`^sy*tWH8RrBnvw^ zz@E94H?!{w8kD{glRWrY}Bq#aUNVZ8Z+lQX9je zVw9{3*~-lKBCl_z5XvK=BX$g*E|I=XSlKy)P z+@0O5=l)$b?QrfU`Lt<04CswiEq})cL8H@A4Lad)e#d@tB8msMjVU5FCrBatw7^!} zA12x~lL(E1LM)cW;q*cjG;BbotY^hQSca?r!q+-_eKEBia~{MxAv!SpQ!&2dK&^=D zdmFgqtf-9IVm*t?nyo<_l~@ITc1)J)XV7*1Wv0WR<{2=MlX3w->+uV7FD6xoKL zDeVMAtyy43iADUw7y$+vMkih413W4BX(-}bueD>JbSA@e455deg=}>wUZD7Xb^ru(d5RN zbW&t34~anxrxX93Hk6rpa>JY{b9-3qqDnfO&=6A!2@P_t8Xr-_7nl```pp6`KDZ-a^kLs0RIt``hR%wF+ z4T{twx*u$q&Euw_nsHmra>j<>=txKyG|oXa*W+wD4QC+pmvIj1~i!0E-4qRAH!uaW(r}WS}B_RcTATC zsX;M9a4ozBow6Y_Oll>rea)CIjC^}~AM_}cwrN@esln8Gk|i}F&;bE4AVpt8i&Pyk zgk&wA3%ks2J)6-p%dxnzcVSu#j1fgcq@sgDG=q8+I^@S_2US0)Id;76?xusxh1RJ_ zp7MRd^l`XGd~?EEF2>vh4lG?WILN1+2rR8}H2i3;1{Je&FwIB;T_dC$$q{BW>FmF3 zRki;gI)uo`HMHfl@dz@Qhz8E4qq7Y+34Tm22mZ=#orE#Zs zf7fw7gm?U)qz{mSTA^@-Wpggih;iDni$KmV00O65;J|?drY~R3LE3m}^(B@A10N>m zAu^qz_DC}dQT6wBiV~{JH%y&j$E=C=iu8opgd{jZjp4fq_4HMQBVwNO;RwqZm~C!G4R|;brL@(&gBbv-5X7|U4`S1!kL?R3d#t#=2 zBLU#hb*IX4T^}^5XNpdjEnXuA`x?V|e@&28f7Yfe`cOy0bcB&c|JT=+}gF6@)B?HeJIxMf5H`e%bAWb@#Nu%NLQV{pF5iZPM`gMutj#Xe1s=-nqd$ z6VIdvr6QfB{>PG^`Cu{CxXxbzP3j~q#f~Yq^^N-A4s3pygHh`U%hiNYKo7%i z^xo@6Y^bUM0cdI)P0^=A!#cV2*X|?OFf6RmU_g<8$)F5o(v#8e(4G9wW>Zrh4$^}G z@c!(X&|i`MZdc^FXyJUpv>)5q%|w{dZMtB@wZrMzoXK3sfJ$ zU7)6K?5w%7U!}Mq(oMtCOvOomjJx}ksz27F&b$oXLER0*0ez>R9r`GRvCiBs@@;k& zb({fWQW0rIG-JwJKCMzdN zNnMJg&hUZoh7>WVtEU4?qdAOJhI0>kxKaSdm&-u@35>aVXP%5bZtfxjcR+zQU2l^M z+wCD4zosuza%$9SQP0Sxn=6Oo0ywwdT9bp1XbYn-ZmAnP3WhF}HMxoRiVVr6p z6MWSSQ0q^`Y+Ym}M8?P9UoFh8|K8~Un*=C2#NESl*m;*kd+nKSSj$R1oYiendXU7aC=imVVb zpX`S4EvG)c7ncB~CMjyVwVbaqvibD{uFq5;w_wc}^khUhN;>fGLX^Rdi8OMKw zGnW&h_dYmBl<_nP<1kK>V$th+tWlZ^>+#WtVaTWWK$seID+{r0R0E5;C_H3em6(t$=QV;Tp~G8pnOjc8glnxrf3q zU2MbA+@g~#ZZ#$N(m-K=qA?7nY^KUFn>Ab6e#AfLPFq5Ym7P`j`6QlmwmU$z3Bmw@ zMtJ+4vZ*o5H;L|G?`C#I{QUJrNe5G@>-kis8x=`NT0)_fHo zbE`u}5Dd;JImKyRZqbV^wPbNkM0v(|lSj~xv(^H2Jki2Ge7Xs*;bkuWn)N-suF7`w zfWctspcXu%3ysP|IP{6r6tOVwi5hVvdV*aGr>So#_NS545n~?aHh1$z{2jP^k70)GMwE08^h8fn`x=5Q40;8@^4r5S5upJRYP`(>MvmHK*81_K zGcnh7V{lR{*K~qYdu+@-)`6BP7qWBbhe1Rfj2hgbs2z>&xv8?7G$gYbpAAp7c+f^0 z&|)xZh(-)wp3q{Kr-vdBlM}aM$%D=Q7tE;aDF!oX72So*EO&^GIvK(^B8ItfFotQN z%(xkmjfrQWQ79NoB$$-OiW2qOq~38vr-CGUu;}}2gz;dE0TVTyYbL{kEfbWVYc-uv zR%}e2DR<{M`b2`x_gtOe86Zpy12&5*yi){oNNLbhi{d;;&k(!Wa&Fvk8>b)>t(-HW zs{^Pmkesbio^~5R +#include +#include + +#include +#include +#include + +#include +#include + +#define TIMELINE_FRAME_COUNT 200 + +typedef struct _TestMultiLayerMaterialState +{ + ClutterActor *group; + CoglHandle material; + CoglHandle alpha_tex; + CoglHandle redhand_tex; + CoglHandle light_tex0; + ClutterFixed *tex_coords; + + CoglMatrix tex_matrix; + CoglMatrix rot_matrix; + +} TestMultiLayerMaterialState; + + +static void +frame_cb (ClutterTimeline *timeline, + gint frame_no, + gpointer data) +{ + TestMultiLayerMaterialState *state = data; + + cogl_matrix_multiply (&state->tex_matrix, + &state->tex_matrix, + &state->rot_matrix); + cogl_material_set_layer_matrix (state->material, 2, &state->tex_matrix); +} + +static gboolean +material_rectangle_paint (ClutterActor *actor, gpointer data) +{ + TestMultiLayerMaterialState *state = data; + + cogl_set_source (state->material); + cogl_material_rectangle (CLUTTER_INT_TO_FIXED(0), + CLUTTER_INT_TO_FIXED(0), + CLUTTER_INT_TO_FIXED(TIMELINE_FRAME_COUNT), + CLUTTER_INT_TO_FIXED(TIMELINE_FRAME_COUNT), + state->tex_coords); +} + +G_MODULE_EXPORT int +test_cogl_material_main (int argc, char *argv[]) +{ + ClutterTimeline *timeline; + ClutterAlpha *alpha; + ClutterBehaviour *r_behave; + ClutterActor *stage; + ClutterColor stage_color = { 0x61, 0x56, 0x56, 0xff }; + TestMultiLayerMaterialState *state = g_new0 (TestMultiLayerMaterialState, 1); + ClutterGeometry geom; + ClutterFixed tex_coords[] = + { + /* tx1 ty1 tx2 ty2 */ + 0, 0, CLUTTER_INT_TO_FIXED (1), CLUTTER_INT_TO_FIXED (1), + 0, 0, CLUTTER_INT_TO_FIXED (1), CLUTTER_INT_TO_FIXED (1), + 0, 0, CLUTTER_INT_TO_FIXED (1), CLUTTER_INT_TO_FIXED (1) + }; + + clutter_init (&argc, &argv); + + stage = clutter_stage_get_default (); + clutter_actor_get_geometry (stage, &geom); + + clutter_stage_set_color (CLUTTER_STAGE (stage), + &stage_color); + + /* We create a non-descript actor that we know doesn't have a + * default paint handler, so that we can easily control + * painting in a paint signal handler, without having to + * sub-class anything etc. */ + state->group = clutter_group_new (); + clutter_actor_set_position (state->group, geom.width/2, geom.height/2); + g_signal_connect (state->group, "paint", + G_CALLBACK(material_rectangle_paint), state); + + state->alpha_tex = + cogl_texture_new_from_file ("./redhand_alpha.png", + -1, /* disable slicing */ + TRUE, + COGL_PIXEL_FORMAT_ANY, + NULL); + state->redhand_tex = + cogl_texture_new_from_file ("./redhand.png", + -1, /* disable slicing */ + TRUE, + COGL_PIXEL_FORMAT_ANY, + NULL); + state->light_tex0 = + cogl_texture_new_from_file ("./light0.png", + -1, /* disable slicing */ + TRUE, + COGL_PIXEL_FORMAT_ANY, + NULL); + + state->material = cogl_material_new (); + cogl_material_set_layer (state->material, 0, state->alpha_tex); + cogl_material_set_layer (state->material, 1, state->redhand_tex); + cogl_material_set_layer (state->material, 2, state->light_tex0); + + state->tex_coords = tex_coords; + + cogl_matrix_init_identity (&state->tex_matrix); + cogl_matrix_init_identity (&state->rot_matrix); + + cogl_matrix_translate (&state->rot_matrix, 0.5, 0.5, 0); + cogl_matrix_rotate (&state->rot_matrix, 10.0, 0, 0, 1.0); + cogl_matrix_translate (&state->rot_matrix, -0.5, -0.5, 0); + + clutter_actor_set_anchor_point (state->group, 86, 125); + clutter_container_add_actor (CLUTTER_CONTAINER(stage), + state->group); + + timeline = clutter_timeline_new (TIMELINE_FRAME_COUNT, 26 /* fps */); + g_object_set (timeline, "loop", TRUE, NULL); + + g_signal_connect (timeline, "new-frame", G_CALLBACK (frame_cb), state); + + /* Set an alpha func to power behaviour - ramp is constant rise/fall */ + alpha = clutter_alpha_new_for_mode (CLUTTER_LINEAR); + clutter_alpha_set_timeline (alpha, timeline); + + /* Create a behaviour for that alpha */ + r_behave = clutter_behaviour_rotate_new (alpha, + CLUTTER_Y_AXIS, + CLUTTER_ROTATE_CW, + 0.0, 360.0); + + /* Apply it to our actor */ + clutter_behaviour_apply (r_behave, state->group); + + /* start the timeline and thus the animations */ + clutter_timeline_start (timeline); + + clutter_actor_show_all (stage); + + clutter_main(); + + cogl_material_unref (state->material); + cogl_texture_unref (state->alpha_tex); + cogl_texture_unref (state->redhand_tex); + cogl_texture_unref (state->light_tex0); + g_free (state); + + g_object_unref (r_behave); + + return 0; +} diff --git a/tests/interactive/test-cogl-multi-texture.c b/tests/interactive/test-cogl-multi-texture.c deleted file mode 100644 index 123de7c7c..000000000 --- a/tests/interactive/test-cogl-multi-texture.c +++ /dev/null @@ -1,250 +0,0 @@ -#include -#include -#include - -#include -#include -#include - -#include -#include - -#define TIMELINE_FRAME_COUNT 200 - -typedef struct _MultiTextureState -{ - ClutterActor *group; - CoglHandle multi_tex; - CoglHandle alpha_tex; - CoglHandle redhand_tex; - CoglHandle light_tex0; - CoglHandle light_tex1; - ClutterFixed *tex_coords; - - /* For handling light switching */ - guint last_light_change; - gboolean light_on; - - /* For handling texture coord sliding */ - guint last_frame_no; - gint light_x_dir; - gint light_y_dir; - gint light_x_pos; - gint light_y_pos; - -} MultiTextureState; - - -static void -frame_cb (ClutterTimeline *timeline, - gint frame_no, - gpointer data) -{ - MultiTextureState *state = data; - ClutterFixed *tex_coords = &state->tex_coords[8]; - int prev_frame_delta; - int light_duration; - - light_duration = frame_no - state->last_light_change; - if (light_duration < 0) - light_duration += TIMELINE_FRAME_COUNT; - - if (light_duration > 10) - { - if (state->light_on) - { - cogl_multi_texture_layer_set_texture (state->multi_tex, - 2, - state->light_tex1); - state->light_on = FALSE; - } - else - { - cogl_multi_texture_layer_set_texture (state->multi_tex, - 2, - state->light_tex0); - state->light_on = TRUE; - } - state->last_light_change = frame_no; - } - - /* slide the texture coordinates */ - - /* This is worked out as if the texture has a virtual resolution - * of TIMELINE_FRAME_COUNT x TIMELINE_FRAME_COUNT. - * - * We are always showing an apature of the texture that is - * (TIMELINE_FRAME_COUNT/2) x (TIMELINE_FRAME_COUNT/2) - * - * To remain within the texture we don't let the tx1, ty1 positions - * go past ((TIMELINE_FRAME_COUNT/2), (TIMELINE_FRAME_COUNT/2)) - */ - prev_frame_delta = frame_no - state->last_frame_no; - if (prev_frame_delta < 0) - prev_frame_delta += TIMELINE_FRAME_COUNT; - - state->light_x_pos += prev_frame_delta * state->light_x_dir; - if (state->light_x_pos > TIMELINE_FRAME_COUNT/2) - { - state->light_x_pos = TIMELINE_FRAME_COUNT/2; - state->light_x_dir = -state->light_x_dir; - } - else if (state->light_x_pos < 0) - { - state->light_x_pos = 0; - state->light_x_dir = 1; - } - - state->light_y_pos += prev_frame_delta * state->light_y_dir; - if (state->light_y_pos > TIMELINE_FRAME_COUNT/2) - { - state->light_y_pos = TIMELINE_FRAME_COUNT/2; - state->light_y_dir = -state->light_y_dir; - } - else if (state->light_y_pos < 0) - { - state->light_y_pos = 0; - state->light_y_dir = 1; - } - -#define CI_F CLUTTER_INT_TO_FIXED - tex_coords[0] = (CI_F(1)/TIMELINE_FRAME_COUNT) * state->light_x_pos; - tex_coords[1] = (CI_F(1)/TIMELINE_FRAME_COUNT) * state->light_y_pos; - tex_coords[2] = tex_coords[0] + CI_F(1)/2; - tex_coords[3] = tex_coords[1] + CI_F(1)/2; -#undef CI_F - - state->last_frame_no = frame_no; -} - -static gboolean -multi_texture_paint (ClutterActor *actor, gpointer data) -{ - MultiTextureState *state = data; - - cogl_multi_texture_rectangle (state->multi_tex, - CLUTTER_INT_TO_FIXED(0), - CLUTTER_INT_TO_FIXED(0), - CLUTTER_INT_TO_FIXED(TIMELINE_FRAME_COUNT), - CLUTTER_INT_TO_FIXED(TIMELINE_FRAME_COUNT), - state->tex_coords); -} - -G_MODULE_EXPORT int -test_cogl_multi_texture_main (int argc, char *argv[]) -{ - ClutterTimeline *timeline; - ClutterAlpha *alpha; - ClutterBehaviour *r_behave; - ClutterActor *stage; - ClutterColor stage_color = { 0x15, 0x93, 0x15, 0xff }; - MultiTextureState *state = g_new0 (MultiTextureState, 1); - CoglHandle layer; - ClutterGeometry geom; - ClutterFixed tex_coords[] = - { - /* tx1 ty1 tx2 ty2 */ - 0, 0, CLUTTER_INT_TO_FIXED (1), CLUTTER_INT_TO_FIXED (1), - 0, 0, CLUTTER_INT_TO_FIXED (1), CLUTTER_INT_TO_FIXED (1), - 0, 0, CLUTTER_INT_TO_FIXED (1), CLUTTER_INT_TO_FIXED (1) - }; - - - clutter_init (&argc, &argv); - - stage = clutter_stage_get_default (); - clutter_actor_get_geometry (stage, &geom); - - clutter_stage_set_color (CLUTTER_STAGE (stage), - &stage_color); - - /* We create a non-descript actor that we know doesn't have a - * default paint handler, so that we can easily control - * painting in a paint signal handler, without having to - * sub-class anything etc. */ - state->group = clutter_group_new (); - clutter_actor_set_position (state->group, geom.width/2, geom.height/2); - g_signal_connect (state->group, "paint", - G_CALLBACK(multi_texture_paint), state); - - state->alpha_tex = - cogl_texture_new_from_file ("./redhand_alpha.png", - -1, /* disable slicing */ - TRUE, - COGL_PIXEL_FORMAT_ANY, - NULL); - state->redhand_tex = - cogl_texture_new_from_file ("./redhand.png", - -1, /* disable slicing */ - TRUE, - COGL_PIXEL_FORMAT_ANY, - NULL); - state->light_tex0 = - cogl_texture_new_from_file ("./light0.png", - -1, /* disable slicing */ - TRUE, - COGL_PIXEL_FORMAT_ANY, - NULL); - state->light_tex1 = - cogl_texture_new_from_file ("./light1.png", - -1, /* disable slicing */ - TRUE, - COGL_PIXEL_FORMAT_ANY, - NULL); - - state->multi_tex = cogl_multi_texture_new (); - - cogl_multi_texture_layer_set_texture (state->multi_tex, 0, - state->alpha_tex); - cogl_multi_texture_layer_set_texture (state->multi_tex, 1, - state->redhand_tex); - cogl_multi_texture_layer_set_texture (state->multi_tex, 2, - state->light_tex0); - - state->tex_coords = tex_coords; - - /* pick a random starting direction */ - state->light_x_dir = rand() % 5; - state->light_y_dir = rand() % 5; - - clutter_actor_set_anchor_point (state->group, 86, 125); - clutter_container_add_actor (CLUTTER_CONTAINER(stage), - state->group); - - - timeline = clutter_timeline_new (TIMELINE_FRAME_COUNT, 26); /* num frames, fps */ - g_object_set (timeline, "loop", TRUE, NULL); - - g_signal_connect (timeline, "new-frame", G_CALLBACK (frame_cb), state); - - /* Set an alpha func to power behaviour - ramp is constant rise/fall */ - alpha = clutter_alpha_new_for_mode (CLUTTER_LINEAR); - clutter_alpha_set_timeline (alpha, timeline); - - /* Create a behaviour for that alpha */ - r_behave = clutter_behaviour_rotate_new (alpha, - CLUTTER_Y_AXIS, - CLUTTER_ROTATE_CW, - 0.0, 360.0); - - /* Apply it to our actor */ - clutter_behaviour_apply (r_behave, state->group); - - /* start the timeline and thus the animations */ - clutter_timeline_start (timeline); - - clutter_actor_show_all (stage); - - clutter_main(); - - cogl_multi_texture_unref (state->multi_tex); - cogl_texture_unref (state->alpha_tex); - cogl_texture_unref (state->redhand_tex); - cogl_texture_unref (state->light_tex0); - cogl_texture_unref (state->light_tex1); - g_free (state); - - g_object_unref (r_behave); - - return 0; -}