From ce6e80315e17f2e671e2709268f580add757f596 Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Mon, 26 Apr 2010 10:01:43 +0100 Subject: [PATCH] material: Adds backend abstraction for fragment processing As part of an effort to improve the architecture of CoglMaterial internally this overhauls how we flush layer state to OpenGL by adding a formal backend abstraction for fragment processing and further formalizing the CoglTextureUnit abstraction. There are three backends: "glsl", "arbfp" and "fixed". The fixed backend uses the OpenGL fixed function APIs to setup the fragment processing, the arbfp backend uses code generation to handle fragment processing using an ARBfp program, and the GLSL backend is currently only there as a formality to handle user programs associated with a material. (i.e. the glsl backend doesn't yet support code generation) The GLSL backend has highest precedence, then arbfp and finally the fixed. If a backend can't support some particular CoglMaterial feature then it will fallback to the next backend. This adds three new COGL_DEBUG options: * "disable-texturing" as expected should disable all texturing * "disable-arbfp" always make the arbfp backend fallback * "disable-glsl" always make the glsl backend fallback * "show-source" show code generated by the arbfp/glsl backends --- clutter/cogl/cogl/cogl-buffer.c | 2 - clutter/cogl/cogl/cogl-context.c | 38 +- clutter/cogl/cogl/cogl-context.h | 22 +- clutter/cogl/cogl/cogl-debug.c | 10 +- clutter/cogl/cogl/cogl-debug.h | 6 +- clutter/cogl/cogl/cogl-internal.h | 15 - clutter/cogl/cogl/cogl-journal.c | 11 +- clutter/cogl/cogl/cogl-material-private.h | 234 +- clutter/cogl/cogl/cogl-material.c | 2674 ++++++++++++++------ clutter/cogl/cogl/cogl-path.c | 27 +- clutter/cogl/cogl/cogl-pixel-buffer.c | 2 - clutter/cogl/cogl/cogl-primitives.c | 33 +- clutter/cogl/cogl/cogl-vertex-buffer.c | 36 +- clutter/cogl/cogl/cogl.c | 109 +- clutter/cogl/cogl/driver/gl/cogl-context-driver.h | 2 + .../cogl/cogl/driver/gl/cogl-feature-functions.h | 57 +- clutter/cogl/cogl/driver/gl/cogl-program.c | 106 +- clutter/cogl/cogl/driver/gl/cogl-program.h | 2 +- clutter/cogl/cogl/driver/gl/cogl-shader.c | 26 +- clutter/cogl/cogl/driver/gles/cogl-program.c | 15 +- 20 files changed, 2398 insertions(+), 1029 deletions(-) diff --git a/clutter/cogl/cogl/cogl-buffer.c b/clutter/cogl/cogl/cogl-buffer.c index c54d52c..a3ede8f 100644 --- a/clutter/cogl/cogl/cogl-buffer.c +++ b/clutter/cogl/cogl/cogl-buffer.c @@ -58,8 +58,6 @@ #define glDeleteBuffers ctx->drv.pf_glDeleteBuffers #define glMapBuffer ctx->drv.pf_glMapBuffer #define glUnmapBuffer ctx->drv.pf_glUnmapBuffer -#define glActiveTexture ctx->drv.pf_glActiveTexture -#define glClientActiveTexture ctx->drv.pf_glClientActiveTexture #ifndef GL_ARRAY_BUFFER #define GL_ARRAY_BUFFER GL_ARRAY_BUFFER_ARB #endif diff --git a/clutter/cogl/cogl/cogl-context.c b/clutter/cogl/cogl/cogl-context.c index d9b2642..830c69e 100644 --- a/clutter/cogl/cogl/cogl-context.c +++ b/clutter/cogl/cogl/cogl-context.c @@ -70,6 +70,7 @@ cogl_create_context (void) _context->enable_flags = 0; _context->color_alpha = 0; + _context->fog_enabled = FALSE; _context->enable_backface_culling = FALSE; _context->flushed_front_winding = COGL_FRONT_WINDING_COUNTER_CLOCKWISE; @@ -81,10 +82,20 @@ cogl_create_context (void) cogl_matrix_scale (&_context->y_flip_matrix, 1, -1, 1); _context->flushed_matrix_mode = COGL_MATRIX_MODELVIEW; - _context->texture_units = NULL; + + _context->texture_units = + g_array_new (FALSE, FALSE, sizeof (CoglTextureUnit)); + + /* See cogl-material.c for more details about why we leave texture unit 1 + * active by default... */ + _context->active_texture_unit = 1; + GE (glActiveTexture (GL_TEXTURE1)); _context->simple_material = cogl_material_new (); _context->source_material = NULL; + _context->arbfp_source_buffer = g_string_new (""); + + _context->legacy_state_set = 0; _context->default_gl_texture_2d_tex = COGL_INVALID_HANDLE; _context->default_gl_texture_rect_tex = COGL_INVALID_HANDLE; @@ -93,15 +104,25 @@ cogl_create_context (void) _context->logged_vertices = g_array_new (FALSE, FALSE, sizeof (GLfloat)); _context->current_material = NULL; - _context->current_material_flags = 0; - memset (&_context->current_material_flush_options, - 0, sizeof (CoglMaterialFlushOptions)); - _context->current_layers = g_array_new (FALSE, FALSE, - sizeof (CoglLayerInfo)); + _context->current_material_flags = COGL_MATERIAL_FLAGS_INIT; + _context->current_material_fallback_layers = 0; + _context->current_material_disable_layers = 0; + _context->current_material_layer0_override = 0; + _context->current_material_skip_gl_color = FALSE; + _cogl_bitmask_init (&_context->texcoord_arrays_enabled); _cogl_bitmask_init (&_context->temp_bitmask); _cogl_bitmask_init (&_context->texcoord_arrays_to_disable); + _context->max_texture_units = -1; + _context->max_texture_image_units = -1; + _context->max_activateable_texture_units = -1; + + _context->current_program = COGL_INVALID_HANDLE; + + _context->current_use_program_type = COGL_MATERIAL_PROGRAM_TYPE_FIXED; + _context->current_gl_program = 0; + _context->framebuffer_stack = _cogl_create_framebuffer_stack (); window_buffer = _cogl_onscreen_new (); @@ -162,8 +183,6 @@ cogl_create_context (void) _context->current_pbo = NULL; - _context->max_texture_units = -1; - return TRUE; } @@ -194,9 +213,6 @@ _cogl_destroy_context (void) if (_context->logged_vertices) g_array_free (_context->logged_vertices, TRUE); - if (_context->current_layers) - g_array_free (_context->current_layers, TRUE); - if (_context->quad_indices_byte) cogl_handle_unref (_context->quad_indices_byte); if (_context->quad_indices_short) diff --git a/clutter/cogl/cogl/cogl-context.h b/clutter/cogl/cogl/cogl-context.h index e032827..148bbaa 100644 --- a/clutter/cogl/cogl/cogl-context.h +++ b/clutter/cogl/cogl/cogl-context.h @@ -53,6 +53,7 @@ typedef struct /* Enable cache */ unsigned long enable_flags; guint8 color_alpha; + gboolean fog_enabled; gboolean enable_backface_culling; CoglFrontWinding flushed_front_winding; @@ -65,11 +66,16 @@ typedef struct /* Client-side matrix stack or NULL if none */ CoglMatrixMode flushed_matrix_mode; - GList *texture_units; + + GArray *texture_units; + int active_texture_unit; /* Materials */ CoglHandle simple_material; CoglHandle source_material; + GString *arbfp_source_buffer; + + int legacy_state_set; /* Textures */ CoglHandle default_gl_texture_2d_tex; @@ -86,8 +92,10 @@ typedef struct /* Some simple caching, to minimize state changes... */ CoglHandle current_material; unsigned long current_material_flags; - CoglMaterialFlushOptions current_material_flush_options; - GArray *current_layers; + gboolean current_material_fallback_layers; + gboolean current_material_disable_layers; + GLuint current_material_layer0_override; + gboolean current_material_skip_gl_color; /* Bitmask of texture coordinates arrays that are enabled */ CoglBitmask texcoord_arrays_enabled; /* These are temporary bitmasks that are used when disabling @@ -129,9 +137,15 @@ typedef struct chances of getting the same colour during an animation */ guint8 journal_rectangles_color; - /* Cached value for GL_MAX_TEXTURE_UNITS to avoid calling + /* Cached values for GL_MAX_TEXTURE_[IMAGE_]UNITS to avoid calling glGetInteger too often */ GLint max_texture_units; + GLint max_texture_image_units; + GLint max_activateable_texture_units; + + CoglHandle current_program; + CoglMaterialProgramType current_use_program_type; + GLuint current_gl_program; CoglContextDriver drv; } CoglContext; diff --git a/clutter/cogl/cogl/cogl-debug.c b/clutter/cogl/cogl/cogl-debug.c index 46ab04c..9304919 100644 --- a/clutter/cogl/cogl/cogl-debug.c +++ b/clutter/cogl/cogl/cogl-debug.c @@ -50,6 +50,7 @@ static const GDebugKey cogl_log_debug_keys[] = { { "draw", COGL_DEBUG_DRAW }, { "opengl", COGL_DEBUG_OPENGL }, { "pango", COGL_DEBUG_PANGO }, + { "show-source", COGL_DEBUG_SHOW_SOURCE} }; static const int n_cogl_log_debug_keys = G_N_ELEMENTS (cogl_log_debug_keys); @@ -61,7 +62,10 @@ static const GDebugKey cogl_behavioural_debug_keys[] = { { "disable-software-transform", COGL_DEBUG_DISABLE_SOFTWARE_TRANSFORM }, { "force-scanline-paths", COGL_DEBUG_FORCE_SCANLINE_PATHS }, { "dump-atlas-image", COGL_DEBUG_DUMP_ATLAS_IMAGE }, - { "disable-atlas", COGL_DEBUG_DISABLE_ATLAS } + { "disable-atlas", COGL_DEBUG_DISABLE_ATLAS }, + { "disable-texturing", COGL_DEBUG_DISABLE_TEXTURING}, + { "disable-arbfp", COGL_DEBUG_DISABLE_ARBFP}, + { "disable-glsl", COGL_DEBUG_DISABLE_GLSL} }; static const int n_cogl_behavioural_debug_keys = G_N_ELEMENTS (cogl_behavioural_debug_keys); @@ -115,6 +119,10 @@ _cogl_parse_debug_string (const char *value, OPT ("force-scanline-paths:", "use a scanline based path rasterizer"); OPT ("dump-atlas-image:", "dump atlas changes to an image file"); OPT ("disable-atlas:", "disable texture atlasing"); + OPT ("disable-texturing:", "disable texturing primitives"); + OPT ("disable-arbfp:", "disable use of ARBfp"); + OPT ("disable-glsl:", "disable use of GLSL"); + OPT ("show-source:", "show generated ARBfp/GLSL"); OPT ("opengl:", "traces some select OpenGL calls"); g_printerr ("\n%28s\n", "Special debug values:"); OPT ("all:", "Enables all non-behavioural debug options"); diff --git a/clutter/cogl/cogl/cogl-debug.h b/clutter/cogl/cogl/cogl-debug.h index 2537b26..3933737 100644 --- a/clutter/cogl/cogl/cogl-debug.h +++ b/clutter/cogl/cogl/cogl-debug.h @@ -46,7 +46,11 @@ typedef enum { COGL_DEBUG_ATLAS = 1 << 15, COGL_DEBUG_DUMP_ATLAS_IMAGE = 1 << 16, COGL_DEBUG_DISABLE_ATLAS = 1 << 17, - COGL_DEBUG_OPENGL = 1 << 18 + COGL_DEBUG_OPENGL = 1 << 18, + COGL_DEBUG_DISABLE_TEXTURING = 1 << 19, + COGL_DEBUG_DISABLE_ARBFP = 1 << 20, + COGL_DEBUG_DISABLE_GLSL = 1 << 21, + COGL_DEBUG_SHOW_SOURCE = 1 << 22 } CoglDebugFlags; #ifdef COGL_ENABLE_DEBUG diff --git a/clutter/cogl/cogl/cogl-internal.h b/clutter/cogl/cogl/cogl-internal.h index 511c9d5..b459c57 100644 --- a/clutter/cogl/cogl/cogl-internal.h +++ b/clutter/cogl/cogl/cogl-internal.h @@ -115,21 +115,6 @@ _cogl_enable (unsigned long flags); unsigned long _cogl_get_enable (void); -typedef struct _CoglTextureUnit -{ - int index; - CoglMatrixStack *matrix_stack; -} CoglTextureUnit; - -CoglTextureUnit * -_cogl_get_texture_unit (int index_); - -void -_cogl_destroy_texture_units (void); - -unsigned int -_cogl_get_max_texture_image_units (void); - void _cogl_flush_face_winding (void); diff --git a/clutter/cogl/cogl/cogl-journal.c b/clutter/cogl/cogl/cogl-journal.c index 9c185c8..9bedf6b 100644 --- a/clutter/cogl/cogl/cogl-journal.c +++ b/clutter/cogl/cogl/cogl-journal.c @@ -792,7 +792,16 @@ _cogl_journal_log_quad (const float *position, disable_layers = (1 << n_layers) - 1; disable_layers = ~disable_layers; - entry->material = _cogl_material_journal_ref (material); + if (G_UNLIKELY (ctx->legacy_state_set)) + { + material = cogl_material_copy (material); + _cogl_material_apply_legacy_state (material); + entry->material = _cogl_material_journal_ref (material); + cogl_handle_unref (material); + } + else + entry->material = _cogl_material_journal_ref (material); + entry->n_layers = n_layers; entry->flush_options.flags = COGL_MATERIAL_FLUSH_FALLBACK_MASK | diff --git a/clutter/cogl/cogl/cogl-material-private.h b/clutter/cogl/cogl/cogl-material-private.h index 9edd792..ed98393 100644 --- a/clutter/cogl/cogl/cogl-material-private.h +++ b/clutter/cogl/cogl/cogl-material-private.h @@ -29,6 +29,7 @@ #include "cogl-material.h" #include "cogl-matrix.h" +#include "cogl-matrix-stack.h" #include "cogl-handle.h" #include @@ -36,6 +37,92 @@ typedef struct _CoglMaterial CoglMaterial; typedef struct _CoglMaterialLayer CoglMaterialLayer; + +/* + * cogl-material.c owns the GPU's texture unit state so we have some + * private structures for describing the current state of a texture + * unit that we track in a per context array (ctx->texture_units) that + * grows according to the largest texture unit used so far... + * + * Roughly speaking the members in this structure are of two kinds: + * either they are a low level reflection of the state we send to + * OpenGL or they are for high level meta data assoicated with the + * texture unit when flushing CoglMaterialLayers that is typically + * used to optimize subsequent re-flushing of the same layer. + * + * The low level members are at the top, and the high level members + * start with the .layer member. + */ +typedef struct _CoglTextureUnit +{ + /* The base 0 texture unit index which can be used with + * glActiveTexture () */ + int index; + + /* Whether or not the corresponding gl_target has been glEnabled */ + gboolean enabled; + + /* The GL target currently glEnabled or 0 if .enabled == FALSE */ + GLenum enabled_gl_target; + + /* The raw GL texture object name for which we called glBindTexture when + * we flushed the last layer. (NB: The CoglTexture associated + * with a layer may represent more than one GL texture) */ + GLuint gl_texture; + + /* A matrix stack giving us the means to associate a texture + * transform matrix with the texture unit. */ + CoglMatrixStack *matrix_stack; + + /* + * Higher level layer state associated with the unit... + */ + + /* The CoglMaterialLayer whos state was flushed to update this + * texture unit last. + * + * This will be set to NULL if the layer is modified or freed which + * means when we come to flush a layer; if this pointer is still + * valid and == to the layer being flushed we don't need to update + * any texture unit state. */ + CoglMaterialLayer *layer; + + /* To help minimize the state changes required we track the + * difference flags associated with the layer whos state was last + * flushed to update this texture unit. + * + * Note: we track this explicitly because .layer may get invalidated + * if that layer is modified or deleted. Even if the layer is + * invalidated though these flags can be used to optimize the state + * flush of the next layer + */ + unsigned long layer_differences; + + /* The options that may have affected how the layer state updated + * this texture unit. */ + gboolean fallback; + gboolean layer0_overridden; + + /* When flushing a layers state, fallback options may mean that a + * different CoglTexture is used than layer->texture. + * + * Once a layers state has been flushed we have to keep track of + * changes to that layer so if we are asked to re-flush the same + * layer later we will know what work is required. This also means + * we need to keep track of changes to the CoglTexture of that layer + * so we need to explicitly keep a reference to the final texture + * chosen. + */ + CoglHandle texture; + +} CoglTextureUnit; + +CoglTextureUnit * +_cogl_get_texture_unit (int index_); + +void +_cogl_destroy_texture_units (void); + typedef enum _CoglMaterialEqualFlags { /* Return FALSE if any component of either material isn't set to its @@ -46,35 +133,41 @@ typedef enum _CoglMaterialEqualFlags } CoglMaterialEqualFlags; -/* XXX: I don't think gtk-doc supports having private enums so these aren't - * bundled in with CoglMaterialLayerFlags */ -typedef enum _CoglMaterialLayerPrivFlags +typedef enum _CoglMaterialLayerDifferenceFlags { - /* Ref: CoglMaterialLayerFlags - COGL_MATERIAL_LAYER_FLAG_HAS_USER_MATRIX = 1L<<0 - */ - COGL_MATERIAL_LAYER_FLAG_DIRTY = 1L<<1, - COGL_MATERIAL_LAYER_FLAG_DEFAULT_COMBINE = 1L<<2 -} CoglMaterialLayerPrivFlags; - -/* For tracking the state of a layer that's been flushed to OpenGL */ -typedef struct _CoglLayerInfo + COGL_MATERIAL_LAYER_DIFFERENCE_TEXTURE = 1L<<0, + COGL_MATERIAL_LAYER_DIFFERENCE_COMBINE = 1L<<1, + COGL_MATERIAL_LAYER_DIFFERENCE_COMBINE_CONSTANT = 1L<<2, + COGL_MATERIAL_LAYER_DIFFERENCE_USER_MATRIX = 1L<<3, + COGL_MATERIAL_LAYER_DIFFERENCE_FILTERS = 1L<<4 +} CoglMaterialLayerDifferenceFlags; + +typedef enum _CoglMaterialLayerChangeFlags { - CoglHandle handle; - unsigned long flags; - GLenum gl_target; - GLuint gl_texture; - gboolean fallback; - gboolean disabled; - gboolean layer0_overridden; -} CoglLayerInfo; + COGL_MATERIAL_LAYER_CHANGE_TEXTURE = 1L<<0, + COGL_MATERIAL_LAYER_CHANGE_COMBINE = 1L<<1, + COGL_MATERIAL_LAYER_CHANGE_COMBINE_CONSTANT = 1L<<2, + COGL_MATERIAL_LAYER_CHANGE_USER_MATRIX = 1L<<3, + COGL_MATERIAL_LAYER_CHANGE_FILTERS = 1L<<4, + + COGL_MATERIAL_LAYER_CHANGE_TEXTURE_INTERN = 1L<<5, + COGL_MATERIAL_LAYER_CHANGE_UNIT = 1L<<6 +} CoglMaterialLayerChangeFlags; struct _CoglMaterialLayer { CoglHandleObject _parent; + + /* Parent material */ + CoglMaterial *material; + unsigned int index; /*!< lowest index is blended first then others on top */ - unsigned long flags; + + int unit_index; + + unsigned long differences; + CoglHandle texture; /*!< The texture for this layer, or COGL_INVALID_HANDLE for an empty layer */ @@ -100,33 +193,48 @@ struct _CoglMaterialLayer /* TODO: Support purely GLSL based material layers */ CoglMatrix matrix; + + /* Different material backends (GLSL/ARBfp/Fixed Function) may + * want to associate private data with a layer... */ + void *backend_priv; }; typedef enum _CoglMaterialFlags { - COGL_MATERIAL_FLAG_SHOWN_SAMPLER_WARNING = 1L<<0, - COGL_MATERIAL_FLAG_DEFAULT_COLOR = 1L<<1, COGL_MATERIAL_FLAG_DEFAULT_GL_MATERIAL = 1L<<2, COGL_MATERIAL_FLAG_DEFAULT_ALPHA_FUNC = 1L<<3, COGL_MATERIAL_FLAG_ENABLE_BLEND = 1L<<4, - COGL_MATERIAL_FLAG_DEFAULT_BLEND = 1L<<5 + COGL_MATERIAL_FLAG_DEFAULT_BLEND = 1L<<5, + COGL_MATERIAL_FLAG_DEFAULT_USER_SHADER = 1L<<6, + COGL_MATERIAL_FLAG_DEFAULT_LAYERS = 1L<<7 } CoglMaterialFlags; -/* ARBfp1.0 program (Fog + ARB_texture_env_combine) */ -typedef struct _CoglMaterialProgram -{ - GString *source; - GLuint gl_program; +/* This defines the initialization state for + * ctx->current_material_flags which should result in the first + * material flush explicitly initializing everything + */ +#define COGL_MATERIAL_FLAGS_INIT \ + COGL_MATERIAL_FLAG_DEFAULT_USER_SHADER - gboolean *sampled; -} CoglMaterialProgram; +typedef enum _CoglMaterialChangeFlag +{ + COGL_MATERIAL_CHANGE_COLOR = 1L<<1, + COGL_MATERIAL_CHANGE_GL_MATERIAL = 1L<<2, + COGL_MATERIAL_CHANGE_ALPHA_FUNC = 1L<<3, + COGL_MATERIAL_CHANGE_ENABLE_BLEND = 1L<<4, + COGL_MATERIAL_CHANGE_BLEND = 1L<<5, + COGL_MATERIAL_CHANGE_USER_SHADER = 1L<<6, + COGL_MATERIAL_CHANGE_LAYERS = 1L<<7 +} CoglMaterialChangeFlag; struct _CoglMaterial { CoglHandleObject _parent; unsigned long journal_ref_count; + int backend; + unsigned long flags; /* If no lighting is enabled; this is the basic material color */ @@ -154,12 +262,39 @@ struct _CoglMaterial GLint blend_src_factor_rgb; GLint blend_dst_factor_rgb; - CoglMaterialProgram program; + CoglHandle user_program; GList *layers; unsigned int n_layers; + + void *backend_priv; }; +typedef struct _CoglMaterialBackend +{ + int (*get_max_texture_units) (void); + + gboolean (*start) (CoglMaterial *material); + gboolean (*add_layer) (CoglMaterialLayer *layer); + gboolean (*passthrough) (CoglMaterial *material); + gboolean (*end) (CoglMaterial *material); + + void (*material_change_notify) (CoglMaterial *material, + unsigned long changes, + GLubyte *new_color); + void (*layer_change_notify) (CoglMaterialLayer *layer, + unsigned long changes); + + void (*free_priv) (CoglMaterial *material); +} CoglMaterialBackend; + +typedef enum +{ + COGL_MATERIAL_PROGRAM_TYPE_GLSL = 1, + COGL_MATERIAL_PROGRAM_TYPE_ARBFP, + COGL_MATERIAL_PROGRAM_TYPE_FIXED +} CoglMaterialProgramType; + /* * SECTION:cogl-material-internals * @short_description: Functions for creating custom primitives that make use @@ -197,29 +332,8 @@ _cogl_material_init_default_material (void); unsigned long _cogl_material_get_cogl_enable_flags (CoglHandle handle); -/* - * CoglMaterialLayerFlags: - * @COGL_MATERIAL_LAYER_FLAG_USER_MATRIX: Means the user has supplied a - * custom texture matrix. - */ -typedef enum _CoglMaterialLayerFlags -{ - COGL_MATERIAL_LAYER_FLAG_HAS_USER_MATRIX = 1L<<0 -} CoglMaterialLayerFlags; -/* XXX: NB: if you add flags here you will need to update - * CoglMaterialLayerPrivFlags!!! */ - -/* - * cogl_material_layer_get_flags: - * @layer_handle: A CoglMaterialLayer layer handle - * - * This lets you get a number of flag attributes about the 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. - */ -unsigned long -_cogl_material_layer_get_flags (CoglHandle layer_handle); +gboolean +_cogl_material_layer_has_user_matrix (CoglHandle layer_handle); /* * Ensures the mipmaps are available for the texture in the layer if @@ -322,5 +436,15 @@ _cogl_material_set_layer_wrap_mode_r (CoglHandle material, CoglMaterialWrapMode _cogl_material_layer_get_wrap_mode_r (CoglHandle layer); +void +_cogl_material_set_user_program (CoglHandle handle, + CoglHandle program); + +void +_cogl_material_apply_legacy_state (CoglHandle handle); + +void +_cogl_gl_use_program_wrapper (GLuint program); + #endif /* __COGL_MATERIAL_PRIVATE_H */ diff --git a/clutter/cogl/cogl/cogl-material.c b/clutter/cogl/cogl/cogl-material.c index d8d8ee1..fd08dd9 100644 --- a/clutter/cogl/cogl/cogl-material.c +++ b/clutter/cogl/cogl/cogl-material.c @@ -3,7 +3,7 @@ * * An object oriented GL/GLES Abstraction/Utility Layer * - * Copyright (C) 2008,2009 Intel Corporation. + * Copyright (C) 2008,2009,2010 Intel Corporation. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -37,6 +37,9 @@ #include "cogl-texture-private.h" #include "cogl-blend-string.h" #include "cogl-journal-private.h" +#ifndef HAVE_COGL_GLES +#include "cogl-program.h" +#endif #include #include @@ -60,8 +63,10 @@ #define glProgramString ctx->drv.pf_glProgramString #define glBindProgram ctx->drv.pf_glBindProgram -#define glDeleteProgram ctx->drv.pf_glDeletePrograms +#define glDeletePrograms ctx->drv.pf_glDeletePrograms #define glGenPrograms ctx->drv.pf_glGenPrograms +#define glProgramLocalParameter4fv ctx->drv.pf_glProgramLocalParameter4fv +#define glUseProgram ctx->drv.pf_glUseProgram #endif /* This isn't defined in the GLES headers */ @@ -69,11 +74,64 @@ #define GL_CLAMP_TO_BORDER 0x812d #endif +typedef struct _CoglMaterialBackendARBfpPrivate +{ + GString *source; + GLuint gl_program; + gboolean *sampled; + int next_constant_id; +} CoglMaterialBackendARBfpPrivate; + static CoglHandle _cogl_material_layer_copy (CoglHandle layer_handle); static void _cogl_material_free (CoglMaterial *tex); static void _cogl_material_layer_free (CoglMaterialLayer *layer); +#if defined (HAVE_COGL_GL) + +static const CoglMaterialBackend _cogl_material_glsl_backend; +static const CoglMaterialBackend _cogl_material_arbfp_backend; +static const CoglMaterialBackend _cogl_material_fixed_backend; +static const CoglMaterialBackend *backends[] = +{ + /* The fragment processing backends in order of precedence... */ + &_cogl_material_glsl_backend, + &_cogl_material_arbfp_backend, + &_cogl_material_fixed_backend +}; +#define COGL_MATERIAL_BACKEND_GLSL 0 +#define COGL_MATERIAL_BACKEND_ARBFP 1 +#define COGL_MATERIAL_BACKEND_FIXED 2 + +#elif defined (HAVE_COGL_GLES2) + +static const CoglMaterialBackend _cogl_material_glsl_backend; +static const CoglMaterialBackend _cogl_material_fixed_backend; +static const CoglMaterialBackend *backends[] = +{ + /* The fragment processing backends in order of precedence... */ + &_cogl_material_glsl_backend, + &_cogl_material_fixed_backend +}; +#define COGL_MATERIAL_BACKEND_GLSL 0 +#define COGL_MATERIAL_BACKEND_FIXED 1 + +#else /* HAVE_COGL_GLES */ + +static const CoglMaterialBackend _cogl_material_fixed_backend; +static const CoglMaterialBackend *backends[] = +{ + /* The fragment processing backends in order of precedence... */ + &_cogl_material_fixed_backend +}; + +#define COGL_MATERIAL_BACKEND_FIXED 0 + +#endif + +#define COGL_MATERIAL_BACKEND_DEFAULT 0 +#define COGL_MATERIAL_BACKEND_UNDEFINED -1 + COGL_HANDLE_DEFINE (Material, material); COGL_HANDLE_DEFINE (MaterialLayer, material_layer); @@ -98,6 +156,8 @@ _cogl_material_init_default_material (void) _COGL_GET_CONTEXT (ctx, NO_RETVAL); + material->backend = COGL_MATERIAL_BACKEND_UNDEFINED; + /* Use the same defaults as the GL spec... */ unlit[0] = 0xff; unlit[1] = 0xff; unlit[2] = 0xff; unlit[3] = 0xff; material->flags |= COGL_MATERIAL_FLAG_DEFAULT_COLOR; @@ -129,11 +189,12 @@ _cogl_material_init_default_material (void) material->blend_dst_factor_rgb = GL_ONE_MINUS_SRC_ALPHA; material->flags |= COGL_MATERIAL_FLAG_DEFAULT_BLEND; - material->program.source = NULL; - material->program.gl_program = -1; + material->user_program = COGL_INVALID_HANDLE; + material->flags |= COGL_MATERIAL_FLAG_DEFAULT_USER_SHADER; material->layers = NULL; material->n_layers = 0; + material->flags |= COGL_MATERIAL_FLAG_DEFAULT_LAYERS; ctx->default_material = _cogl_material_handle_new (material); } @@ -164,10 +225,27 @@ cogl_material_new (void) } static void +_cogl_material_backend_free_priv (CoglMaterial *material) +{ + if (material->backend != COGL_MATERIAL_BACKEND_UNDEFINED && + backends[material->backend]->free_priv) + backends[material->backend]->free_priv (material); +} + +static void _cogl_material_free (CoglMaterial *material) { - /* Frees material resources but its handle is not - released! Do that separately before this! */ + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + + _cogl_material_backend_free_priv (material); + + /* Invalidate the ->current_material reference to this material since + * it will no longer represent the current state. + * + * NB: we would also invalidate this if the material we being + * modified. + */ + ctx->current_material = COGL_INVALID_HANDLE; g_list_foreach (material->layers, (GFunc)cogl_handle_unref, NULL); @@ -214,12 +292,12 @@ _cogl_material_needs_blending_enabled (CoglMaterial *material, } static void -handle_automatic_blend_enable (CoglMaterial *material) +_cogl_material_set_backend (CoglMaterial *material, int backend) { - material->flags &= ~COGL_MATERIAL_FLAG_ENABLE_BLEND; - - if (_cogl_material_needs_blending_enabled (material, NULL)) - material->flags |= COGL_MATERIAL_FLAG_ENABLE_BLEND; + if (material->backend != COGL_MATERIAL_BACKEND_UNDEFINED && + backends[material->backend]->free_priv) + backends[material->backend]->free_priv (material); + material->backend = backend; } /* If primitives have been logged in the journal referencing the current @@ -227,24 +305,102 @@ handle_automatic_blend_enable (CoglMaterial *material) * modify it... */ static void _cogl_material_pre_change_notify (CoglMaterial *material, - gboolean only_color_change, + unsigned long changes, GLubyte *new_color) { - /* XXX: We don't usually need to flush the journal just due to color changes - * since material colors are logged in the journals vertex buffer. The - * exception is when the change in color enables or disables the need for - * blending. */ - if (only_color_change) + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + + if (material->journal_ref_count) + { + /* XXX: We don't usually need to flush the journal just due to + * color changes since material colors are logged in the + * journals vertex buffer. The exception is when the change in + * color enables or disables the need for blending. */ + if (changes == COGL_MATERIAL_CHANGE_COLOR) + { + gboolean will_need_blending = + _cogl_material_needs_blending_enabled (material, new_color); + if (will_need_blending != + ((material->flags & COGL_MATERIAL_FLAG_ENABLE_BLEND) ? + TRUE : FALSE)) + _cogl_journal_flush (); + } + else + _cogl_journal_flush (); + } + + /* The fixed function backend has no private state and can't + * do anything special to handle small material changes so we may as + * well try to find a better backend whenever the material changes. + * + * The programmable backends may be able to cache a lot of the code + * they generate and only need to update a small section of that + * code in response to a material change therefore we don't want to + * try searching for another backend when the material changes. + */ + if (material->backend == COGL_MATERIAL_BACKEND_FIXED) + _cogl_material_set_backend (material, COGL_MATERIAL_BACKEND_UNDEFINED); + + if (material->backend != COGL_MATERIAL_BACKEND_UNDEFINED && + backends[material->backend]->material_change_notify) + backends[material->backend]->material_change_notify (material, + changes, + new_color); + + /* Invalidate any ->current_material reference to this material since + * it will no longer represent the current state. + * + * NB: we also invalidate this if the material is freed + */ + if (ctx->current_material == material) + ctx->current_material = COGL_INVALID_HANDLE; +} + +static void +handle_automatic_blend_enable (CoglMaterial *material) +{ + material->flags &= ~COGL_MATERIAL_FLAG_ENABLE_BLEND; + + if (_cogl_material_needs_blending_enabled (material, NULL)) { - gboolean will_need_blending = - _cogl_material_needs_blending_enabled (material, new_color); - if (will_need_blending == - (material->flags & COGL_MATERIAL_FLAG_ENABLE_BLEND) ? TRUE : FALSE) - return; + _cogl_material_pre_change_notify (material, + COGL_MATERIAL_CHANGE_ENABLE_BLEND, + NULL); + material->flags |= COGL_MATERIAL_FLAG_ENABLE_BLEND; } +} - if (material->journal_ref_count) - _cogl_journal_flush (); +static void +_cogl_material_backend_layer_change_notify (CoglMaterialLayer *layer, + unsigned long changes) +{ + int backend = layer->material->backend; + if (backend == COGL_MATERIAL_BACKEND_UNDEFINED) + return; + + if (backends[backend]->layer_change_notify) + backends[backend]->layer_change_notify (layer, changes); +} + +static void +_cogl_material_layer_pre_change_notify (CoglMaterialLayer *layer, + CoglMaterialLayerChangeFlags changes) +{ + CoglTextureUnit *unit = _cogl_get_texture_unit (layer->unit_index); + + /* Look at the texture unit corresponding to this layer, if it + * currently has a back reference to this layer then invalidate it + * so that next time we come to flush this layer we'll see that the + * texture unit no longer corresponds to this layer's state. + */ + if (unit->layer == layer) + unit->layer = NULL; + + _cogl_material_backend_layer_change_notify (layer, changes); + + _cogl_material_pre_change_notify (layer->material, + COGL_MATERIAL_CHANGE_LAYERS, + NULL); } void @@ -292,7 +448,8 @@ cogl_material_set_color (CoglHandle handle, return; /* possibly flush primitives referencing the current state... */ - _cogl_material_pre_change_notify (material, TRUE, unlit); + _cogl_material_pre_change_notify (material, COGL_MATERIAL_CHANGE_COLOR, + unlit); memcpy (material->unlit, unlit, sizeof (unlit)); @@ -359,7 +516,9 @@ cogl_material_set_ambient (CoglHandle handle, material = _cogl_material_pointer_from_handle (handle); /* possibly flush primitives referencing the current state... */ - _cogl_material_pre_change_notify (material, FALSE, NULL); + _cogl_material_pre_change_notify (material, + COGL_MATERIAL_CHANGE_GL_MATERIAL, + NULL); ambient = material->ambient; ambient[0] = cogl_color_get_red_float (ambient_color); @@ -401,7 +560,9 @@ cogl_material_set_diffuse (CoglHandle handle, material = _cogl_material_pointer_from_handle (handle); /* possibly flush primitives referencing the current state... */ - _cogl_material_pre_change_notify (material, FALSE, NULL); + _cogl_material_pre_change_notify (material, + COGL_MATERIAL_CHANGE_GL_MATERIAL, + NULL); diffuse = material->diffuse; diffuse[0] = cogl_color_get_red_float (diffuse_color); @@ -451,7 +612,9 @@ cogl_material_set_specular (CoglHandle handle, material = _cogl_material_pointer_from_handle (handle); /* possibly flush primitives referencing the current state... */ - _cogl_material_pre_change_notify (material, FALSE, NULL); + _cogl_material_pre_change_notify (material, + COGL_MATERIAL_CHANGE_GL_MATERIAL, + NULL); specular = material->specular; specular[0] = cogl_color_get_red_float (specular_color); @@ -491,7 +654,9 @@ cogl_material_set_shininess (CoglHandle handle, material = _cogl_material_pointer_from_handle (handle); /* possibly flush primitives referencing the current state... */ - _cogl_material_pre_change_notify (material, FALSE, NULL); + _cogl_material_pre_change_notify (material, + COGL_MATERIAL_CHANGE_GL_MATERIAL, + NULL); material->shininess = (GLfloat)shininess * 128.0; @@ -527,7 +692,9 @@ cogl_material_set_emission (CoglHandle handle, material = _cogl_material_pointer_from_handle (handle); /* possibly flush primitives referencing the current state... */ - _cogl_material_pre_change_notify (material, FALSE, NULL); + _cogl_material_pre_change_notify (material, + COGL_MATERIAL_CHANGE_GL_MATERIAL, + NULL); emission = material->emission; emission[0] = cogl_color_get_red_float (emission_color); @@ -552,7 +719,9 @@ cogl_material_set_alpha_test_function (CoglHandle handle, material = _cogl_material_pointer_from_handle (handle); /* possibly flush primitives referencing the current state... */ - _cogl_material_pre_change_notify (material, FALSE, NULL); + _cogl_material_pre_change_notify (material, + COGL_MATERIAL_CHANGE_ALPHA_FUNC, + NULL); material->alpha_func = alpha_func; material->alpha_func_reference = (GLfloat)alpha_reference; @@ -696,7 +865,9 @@ cogl_material_set_blend (CoglHandle handle, } /* possibly flush primitives referencing the current state... */ - _cogl_material_pre_change_notify (material, FALSE, NULL); + _cogl_material_pre_change_notify (material, + COGL_MATERIAL_CHANGE_BLEND, + NULL); #ifndef HAVE_COGL_GLES setup_blend_state (rgb, @@ -732,7 +903,9 @@ cogl_material_set_blend_constant (CoglHandle handle, material = _cogl_material_pointer_from_handle (handle); /* possibly flush primitives referencing the current state... */ - _cogl_material_pre_change_notify (material, FALSE, NULL); + _cogl_material_pre_change_notify (material, + COGL_MATERIAL_CHANGE_BLEND, + NULL); constant = material->blend_constant; constant[0] = cogl_color_get_red_float (constant_color); @@ -744,6 +917,116 @@ cogl_material_set_blend_constant (CoglHandle handle, #endif } +/* XXX: for now we don't mind if the program has vertex shaders + * attached but if we ever make a similar API public we should only + * allow attaching of programs containing fragment shaders. Eventually + * we will have a CoglPipeline abstraction to also cover vertex + * processing. + */ +void +_cogl_material_set_user_program (CoglHandle handle, + CoglHandle program) +{ + CoglMaterial *material; + + g_return_if_fail (cogl_is_material (handle)); + + material = _cogl_material_pointer_from_handle (handle); + + if (material->user_program == program) + return; + + /* possibly flush primitives referencing the current state... */ + _cogl_material_pre_change_notify (material, + COGL_MATERIAL_CHANGE_USER_SHADER, + NULL); + + _cogl_material_set_backend (material, COGL_MATERIAL_BACKEND_DEFAULT); + + if (program != COGL_INVALID_HANDLE) + cogl_handle_ref (program); + if (material->user_program != COGL_INVALID_HANDLE) + cogl_handle_unref (material->user_program); + material->user_program = program; + + if (program == COGL_INVALID_HANDLE) + material->flags |= COGL_MATERIAL_FLAG_DEFAULT_USER_SHADER; + else + material->flags &= ~COGL_MATERIAL_FLAG_DEFAULT_USER_SHADER; +} + +static void +texture_unit_init (CoglTextureUnit *unit, int index_) +{ + unit->index = index_; + unit->enabled = FALSE; + unit->enabled_gl_target = 0; + unit->gl_texture = 0; + unit->matrix_stack = _cogl_matrix_stack_new (); + + unit->layer = NULL; + unit->layer_differences = COGL_MATERIAL_LAYER_DIFFERENCE_COMBINE; + unit->fallback = FALSE; + unit->layer0_overridden = FALSE; + unit->texture = COGL_INVALID_HANDLE; +} + +static void +texture_unit_free (CoglTextureUnit *unit) +{ + _cogl_matrix_stack_destroy (unit->matrix_stack); +} + +CoglTextureUnit * +_cogl_get_texture_unit (int index_) +{ + _COGL_GET_CONTEXT (ctx, NULL); + + if (ctx->texture_units->len < (index_ + 1)) + { + int i; + int prev_len = ctx->texture_units->len; + ctx->texture_units = g_array_set_size (ctx->texture_units, index_ + 1); + for (i = prev_len; i <= index_; i++) + { + CoglTextureUnit *unit = + &g_array_index (ctx->texture_units, CoglTextureUnit, i); + + texture_unit_init (unit, i); + } + } + + return &g_array_index (ctx->texture_units, CoglTextureUnit, index_); +} + +void +_cogl_destroy_texture_units (void) +{ + int i; + + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + + for (i = 0; i < ctx->texture_units->len; i++) + { + CoglTextureUnit *unit = + &g_array_index (ctx->texture_units, CoglTextureUnit, i); + texture_unit_free (unit); + } + g_array_free (ctx->texture_units, TRUE); +} + +static void +set_active_texture_unit (int unit_index) +{ + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + + if (ctx->active_texture_unit != unit_index) + { + GE (glActiveTexture (GL_TEXTURE0 + unit_index)); + ctx->active_texture_unit = unit_index; + } +} + /* Asserts that a layer corresponding to the given index exists. If no * match is found, then a new empty layer is added. */ @@ -753,13 +1036,13 @@ _cogl_material_get_layer (CoglMaterial *material, gboolean create_if_not_found) { CoglMaterialLayer *layer; - GList *tmp; + GList *l; CoglHandle layer_handle; + int i; - for (tmp = material->layers; tmp != NULL; tmp = tmp->next) + for (l = material->layers, i = 0; l != NULL; l = l->next, i++) { - layer = - _cogl_material_layer_pointer_from_handle ((CoglHandle)tmp->data); + layer = l->data; if (layer->index == index_) return layer; @@ -768,20 +1051,23 @@ _cogl_material_get_layer (CoglMaterial *material, if (layer->index > index_) break; } - /* NB: if we now insert a new layer before tmp, that will maintain order. + /* NB: if we now insert a new layer before l, that will maintain order. */ if (!create_if_not_found) return NULL; /* possibly flush primitives referencing the current state... */ - _cogl_material_pre_change_notify (material, FALSE, NULL); + _cogl_material_pre_change_notify (material, + COGL_MATERIAL_CHANGE_LAYERS, + NULL); layer = g_slice_new0 (CoglMaterialLayer); layer_handle = _cogl_material_layer_handle_new (layer); + layer->material = material; layer->index = index_; - layer->flags = COGL_MATERIAL_LAYER_FLAG_DEFAULT_COMBINE; + layer->differences = 0; layer->mag_filter = COGL_MATERIAL_FILTER_LINEAR; layer->min_filter = COGL_MATERIAL_FILTER_LINEAR; layer->wrap_mode_s = COGL_MATERIAL_WRAP_MODE_AUTOMATIC; @@ -789,6 +1075,8 @@ _cogl_material_get_layer (CoglMaterial *material, layer->wrap_mode_r = COGL_MATERIAL_WRAP_MODE_AUTOMATIC; layer->texture = COGL_INVALID_HANDLE; + layer->unit_index = i; + /* Choose the same default combine mode as OpenGL: * MODULATE(PREVIOUS[RGBA],TEXTURE[RGBA]) */ layer->texture_combine_rgb_func = GL_MODULATE; @@ -806,7 +1094,9 @@ _cogl_material_get_layer (CoglMaterial *material, /* Note: see comment after for() loop above */ material->layers = - g_list_insert_before (material->layers, tmp, layer_handle); + g_list_insert_before (material->layers, l, layer_handle); + + material->flags &= ~COGL_MATERIAL_FLAG_DEFAULT_LAYERS; material->n_layers++; @@ -833,22 +1123,9 @@ cogl_material_set_layer (CoglHandle material_handle, return; /* possibly flush primitives referencing the current state... */ - _cogl_material_pre_change_notify (material, FALSE, NULL); - - if (material->n_layers > _cogl_get_max_texture_image_units ()) - { - if (!(material->flags & COGL_MATERIAL_FLAG_SHOWN_SAMPLER_WARNING)) - { - g_warning ("Your hardware does not 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. */ - } + _cogl_material_pre_change_notify (material, + COGL_MATERIAL_CHANGE_LAYERS, + NULL); if (texture_handle) cogl_handle_ref (texture_handle); @@ -859,7 +1136,8 @@ cogl_material_set_layer (CoglHandle material_handle, layer->texture = texture_handle; handle_automatic_blend_enable (material); - layer->flags |= COGL_MATERIAL_LAYER_FLAG_DIRTY; + + layer->differences |= COGL_MATERIAL_LAYER_DIFFERENCE_TEXTURE; } static void @@ -997,7 +1275,9 @@ cogl_material_set_layer_combine (CoglHandle handle, } /* possibly flush primitives referencing the current state... */ - _cogl_material_pre_change_notify (material, FALSE, NULL); + _cogl_material_layer_pre_change_notify ( + layer, + COGL_MATERIAL_LAYER_CHANGE_COMBINE); setup_texture_combine_state (rgb, &layer->texture_combine_rgb_func, @@ -1010,8 +1290,7 @@ cogl_material_set_layer_combine (CoglHandle handle, layer->texture_combine_alpha_op); - layer->flags |= COGL_MATERIAL_LAYER_FLAG_DIRTY; - layer->flags &= ~COGL_MATERIAL_LAYER_FLAG_DEFAULT_COMBINE; + layer->differences |= COGL_MATERIAL_LAYER_DIFFERENCE_COMBINE; return TRUE; } @@ -1030,7 +1309,9 @@ cogl_material_set_layer_combine_constant (CoglHandle handle, layer = _cogl_material_get_layer (material, layer_index, TRUE); /* possibly flush primitives referencing the current state... */ - _cogl_material_pre_change_notify (material, FALSE, NULL); + _cogl_material_layer_pre_change_notify ( + layer, + COGL_MATERIAL_LAYER_CHANGE_COMBINE_CONSTANT); constant = layer->texture_combine_constant; constant[0] = cogl_color_get_red_float (constant_color); @@ -1038,8 +1319,7 @@ cogl_material_set_layer_combine_constant (CoglHandle handle, constant[2] = cogl_color_get_blue_float (constant_color); constant[3] = cogl_color_get_alpha_float (constant_color); - layer->flags |= COGL_MATERIAL_LAYER_FLAG_DIRTY; - layer->flags &= ~COGL_MATERIAL_LAYER_FLAG_DEFAULT_COMBINE; + layer->differences |= COGL_MATERIAL_LAYER_DIFFERENCE_COMBINE_CONSTANT; } void @@ -1050,24 +1330,42 @@ cogl_material_set_layer_matrix (CoglHandle material_handle, CoglMaterial *material; CoglMaterialLayer *layer; + static gboolean initialized_identity_matrix = FALSE; + static CoglMatrix identity_matrix; + 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, TRUE); - /* possibly flush primitives referencing the current state... */ - _cogl_material_pre_change_notify (material, FALSE, NULL); + if (cogl_matrix_equal (matrix, &layer->matrix)) + return; + /* possibly flush primitives referencing the current state... */ + _cogl_material_layer_pre_change_notify ( + layer, + COGL_MATERIAL_LAYER_CHANGE_USER_MATRIX); layer->matrix = *matrix; - layer->flags |= COGL_MATERIAL_LAYER_FLAG_DIRTY; - layer->flags |= COGL_MATERIAL_LAYER_FLAG_HAS_USER_MATRIX; - layer->flags &= ~COGL_MATERIAL_LAYER_FLAG_DEFAULT_COMBINE; + if (G_UNLIKELY (!initialized_identity_matrix)) + cogl_matrix_init_identity (&identity_matrix); + + if (cogl_matrix_equal (matrix, &identity_matrix)) + layer->differences &= ~COGL_MATERIAL_LAYER_DIFFERENCE_USER_MATRIX; + else + layer->differences |= COGL_MATERIAL_LAYER_DIFFERENCE_USER_MATRIX; } static void _cogl_material_layer_free (CoglMaterialLayer *layer) { + CoglTextureUnit *unit = _cogl_get_texture_unit (layer->unit_index); + + /* Since we're freeing the layer make sure the texture unit no + * longer keeps a back reference to it */ + if (unit->layer == layer) + unit->layer = NULL; + if (layer->texture != COGL_INVALID_HANDLE) cogl_handle_unref (layer->texture); g_slice_free (CoglMaterialLayer, layer); @@ -1079,32 +1377,51 @@ cogl_material_remove_layer (CoglHandle material_handle, { CoglMaterial *material; CoglMaterialLayer *layer; - GList *tmp; - gboolean notified_change = FALSE; + GList *l; + GList *l2; + gboolean found = FALSE; + int i; g_return_if_fail (cogl_is_material (material_handle)); material = _cogl_material_pointer_from_handle (material_handle); - for (tmp = material->layers; tmp != NULL; tmp = tmp->next) + for (l = material->layers, i = 0; l != NULL; l = l2, i++) { - layer = tmp->data; + /* were going to be modifying the list and continuing to iterate + * it so we get the pointer to the next link now... */ + l2 = l->next; + + layer = l->data; if (layer->index == layer_index) { CoglHandle handle = (CoglHandle) layer; + found = TRUE; + /* possibly flush primitives referencing the current state... */ - if (!notified_change) - { - _cogl_material_pre_change_notify (material, FALSE, NULL); - notified_change = TRUE; - } + _cogl_material_pre_change_notify (material, + COGL_MATERIAL_CHANGE_LAYERS, + NULL); cogl_handle_unref (handle); - material->layers = g_list_remove (material->layers, layer); + material->layers = g_list_delete_link (material->layers, l); material->n_layers--; - break; + + /* We need to iterate through the rest of the layers + * updating the texture unit that they reference. */ + continue; } + + /* All layers following a removed layer need to have their + * associated texture unit updated... */ + if (found) + { + _cogl_material_layer_pre_change_notify ( + layer, + COGL_MATERIAL_LAYER_CHANGE_UNIT); + layer->unit_index = i; + } } handle_automatic_blend_enable (material); @@ -1182,16 +1499,13 @@ cogl_material_layer_get_texture (CoglHandle layer_handle) return layer->texture; } -unsigned long -_cogl_material_layer_get_flags (CoglHandle layer_handle) +gboolean +_cogl_material_layer_has_user_matrix (CoglHandle layer_handle) { - CoglMaterialLayer *layer; - - g_return_val_if_fail (cogl_is_material_layer (layer_handle), 0); - - layer = _cogl_material_layer_pointer_from_handle (layer_handle); - - return layer->flags & COGL_MATERIAL_LAYER_FLAG_HAS_USER_MATRIX; + CoglMaterialLayer *layer = + _cogl_material_layer_pointer_from_handle (layer_handle); + return layer->differences & COGL_MATERIAL_LAYER_CHANGE_USER_MATRIX ? + TRUE : FALSE; } static CoglHandle @@ -1209,6 +1523,74 @@ _cogl_material_layer_copy (CoglHandle layer_handle) return _cogl_material_layer_handle_new (layer_copy); } +static gboolean +is_mipmap_filter (CoglMaterialFilter filter) +{ + return (filter == COGL_MATERIAL_FILTER_NEAREST_MIPMAP_NEAREST + || filter == COGL_MATERIAL_FILTER_LINEAR_MIPMAP_NEAREST + || filter == COGL_MATERIAL_FILTER_NEAREST_MIPMAP_LINEAR + || filter == COGL_MATERIAL_FILTER_LINEAR_MIPMAP_LINEAR); +} + +#ifndef HAVE_COGL_GLES +static int +get_max_texture_image_units (void) +{ + _COGL_GET_CONTEXT (ctx, 0); + + /* This function is called quite often so we cache the value to + avoid too many GL calls */ + if (G_UNLIKELY (ctx->max_texture_image_units == -1)) + { + ctx->max_texture_image_units = 1; + GE (glGetIntegerv (GL_MAX_TEXTURE_IMAGE_UNITS, + &ctx->max_texture_image_units)); + } + + return ctx->max_texture_image_units; +} +#endif + +static void +disable_texture_unit (int unit_index) +{ + CoglTextureUnit *unit; + + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + + unit = &g_array_index (ctx->texture_units, CoglTextureUnit, unit_index); + +#ifndef DISABLE_MATERIAL_CACHE + if (unit->enabled) +#endif + { + set_active_texture_unit (unit_index); + GE (glDisable (unit->enabled_gl_target)); + unit->enabled_gl_target = 0; + unit->enabled = FALSE; + + /* XXX: This implies that a lot of unneeded work will happen + * if a given layer somehow simply gets disabled and enabled + * without changing. Currently the public CoglMaterial API + * doesn't give a way to disable layers but one day we may + * want to avoid doing this if that changes... */ + unit->layer = NULL; + } +} + +void +_cogl_material_layer_ensure_mipmaps (CoglHandle layer_handle) +{ + CoglMaterialLayer *layer; + + layer = _cogl_material_layer_pointer_from_handle (layer_handle); + + if (layer->texture && + (is_mipmap_filter (layer->min_filter) || + is_mipmap_filter (layer->mag_filter))) + _cogl_texture_ensure_mipmaps (layer->texture); +} + static unsigned int get_n_args_for_combine_func (GLint func) { @@ -1229,512 +1611,1210 @@ get_n_args_for_combine_func (GLint func) return 0; } -static gboolean -is_mipmap_filter (CoglMaterialFilter filter) +void +_cogl_gl_use_program_wrapper (GLuint program) { - return (filter == COGL_MATERIAL_FILTER_NEAREST_MIPMAP_NEAREST - || filter == COGL_MATERIAL_FILTER_LINEAR_MIPMAP_NEAREST - || filter == COGL_MATERIAL_FILTER_NEAREST_MIPMAP_LINEAR - || filter == COGL_MATERIAL_FILTER_LINEAR_MIPMAP_LINEAR); +#ifdef COGL_MATERIAL_BACKEND_GLSL + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + + if (ctx->current_gl_program == program) + return; + + if (program) + { + GLenum gl_error; + + while ((gl_error = glGetError ()) != GL_NO_ERROR) + ; + glUseProgram (program); + if (glGetError () != GL_NO_ERROR) + { + GE (glUseProgram (0)); + ctx->current_gl_program = 0; + return; + } + } + else + GE (glUseProgram (0)); + + ctx->current_gl_program = program; +#endif } -/* FIXME: All direct manipulation of GL texture unit state should be dealt with - * by extending the CoglTextureUnit abstraction */ static void -_cogl_material_layer_flush_gl_sampler_state (CoglMaterialLayer *layer, - CoglTextureUnit *unit, - CoglLayerInfo *gl_layer_info) +disable_glsl (void) { - int n_rgb_func_args; - int n_alpha_func_args; +#ifdef COGL_MATERIAL_BACKEND_GLSL + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + + if (ctx->current_use_program_type == COGL_MATERIAL_PROGRAM_TYPE_GLSL) + _cogl_gl_use_program_wrapper (0); +#endif +} +static void +disable_arbfp (void) +{ +#ifdef COGL_MATERIAL_BACKEND_ARBFP _COGL_GET_CONTEXT (ctx, NO_RETVAL); -#ifndef DISABLE_MATERIAL_CACHE - if (!(gl_layer_info && - gl_layer_info->flags & COGL_MATERIAL_LAYER_FLAG_DEFAULT_COMBINE && - (layer->flags & COGL_MATERIAL_LAYER_FLAG_DEFAULT_COMBINE))) + if (ctx->current_use_program_type == COGL_MATERIAL_PROGRAM_TYPE_ARBFP) + GE (glDisable (GL_FRAGMENT_PROGRAM_ARB)); #endif +} + +static void +use_program (CoglHandle program_handle, CoglMaterialProgramType type) +{ + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + + switch (type) { - GE (glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE)); +#ifdef COGL_MATERIAL_BACKEND_GLSL + case COGL_MATERIAL_PROGRAM_TYPE_GLSL: + { + /* The GLES2 backend currently manages its own codegen for + * fixed function API fallbacks and manages its own shader + * state. */ +#ifndef HAVE_COGL_GLES2 + CoglProgram *program = + _cogl_program_pointer_from_handle (program_handle); + + _cogl_gl_use_program_wrapper (program->gl_handle); + disable_arbfp (); +#endif - /* 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)); + ctx->current_use_program_type = type; + break; + } +#else + case COGL_MATERIAL_PROGRAM_TYPE_GLSL: + g_warning ("Unexpected use of GLSL backend!"); + break; +#endif +#ifdef COGL_MATERIAL_BACKEND_ARBFP + case COGL_MATERIAL_PROGRAM_TYPE_ARBFP: - /* - * Setup the function arguments... - */ + /* _cogl_gl_use_program_wrapper can be called by cogl-program.c + * so we can't bailout without making sure we glUseProgram (0) + * first. */ + disable_glsl (); - /* For the RGB components... */ - n_rgb_func_args = - get_n_args_for_combine_func (layer->texture_combine_rgb_func); + if (ctx->current_use_program_type == COGL_MATERIAL_PROGRAM_TYPE_ARBFP) + break; - 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])); - } + GE (glEnable (GL_FRAGMENT_PROGRAM_ARB)); - /* For the Alpha component */ - n_alpha_func_args = - get_n_args_for_combine_func (layer->texture_combine_alpha_func); + ctx->current_use_program_type = type; + break; +#else + case COGL_MATERIAL_PROGRAM_TYPE_ARBFP: + g_warning ("Unexpected use of GLSL backend!"); + break; +#endif +#ifdef COGL_MATERIAL_BACKEND_FIXED + case COGL_MATERIAL_PROGRAM_TYPE_FIXED: - 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])); - } + /* _cogl_gl_use_program_wrapper can be called by cogl-program.c + * so we can't bailout without making sure we glUseProgram (0) + * first. */ + disable_glsl (); - GE (glTexEnvfv (GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, - layer->texture_combine_constant)); + if (ctx->current_use_program_type == COGL_MATERIAL_PROGRAM_TYPE_FIXED) + break; + + disable_arbfp (); + + ctx->current_use_program_type = type; +#endif } +} - if ((gl_layer_info && - gl_layer_info->flags & COGL_MATERIAL_LAYER_FLAG_HAS_USER_MATRIX) || - (layer->flags & COGL_MATERIAL_LAYER_FLAG_HAS_USER_MATRIX)) - _cogl_matrix_stack_set (unit->matrix_stack, &layer->matrix); - else - _cogl_matrix_stack_load_identity (unit->matrix_stack); +#ifdef COGL_MATERIAL_BACKEND_GLSL - /* TODO: Eventually we should just have something like - * _cogl_flush_texture_units() that we can call in - * _cogl_material_flush_layers_gl_state */ - _cogl_matrix_stack_flush_to_gl (unit->matrix_stack, COGL_MATRIX_TEXTURE); +static int +_cogl_material_backend_glsl_get_max_texture_units (void) +{ + return get_max_texture_image_units (); } -void -_cogl_material_layer_ensure_mipmaps (CoglHandle layer_handle) +static gboolean +_cogl_material_backend_glsl_start (CoglMaterial *material) { - CoglMaterialLayer *layer; + _COGL_GET_CONTEXT (ctx, FALSE); - layer = _cogl_material_layer_pointer_from_handle (layer_handle); + if (!cogl_features_available (COGL_FEATURE_SHADERS_GLSL)) + return FALSE; - if (layer->texture && - (is_mipmap_filter (layer->min_filter) || - is_mipmap_filter (layer->mag_filter))) - _cogl_texture_ensure_mipmaps (layer->texture); + /* FIXME: This will likely conflict with the GLES 2 backends use of + * glUseProgram. + */ + if (!(ctx->current_material_flags & COGL_MATERIAL_FLAG_DEFAULT_USER_SHADER + && material->flags & COGL_MATERIAL_FLAG_DEFAULT_USER_SHADER)) + { + CoglHandle program = material->user_program; + if (program == COGL_INVALID_HANDLE) + return FALSE; /* XXX: change me when we support code generation here */ + + use_program (program, COGL_MATERIAL_PROGRAM_TYPE_GLSL); + return TRUE; + } + + /* TODO: also support code generation */ + + return FALSE; } -static void -_cogl_material_set_wrap_modes_for_layer (CoglMaterialLayer *layer, - int layer_num, - CoglHandle tex_handle, - const CoglMaterialWrapModeOverrides * - wrap_mode_overrides) +gboolean +_cogl_material_backend_glsl_add_layer (CoglMaterialLayer *layer) { - GLenum wrap_mode_s, wrap_mode_t, wrap_mode_r; + return TRUE; +} - /* Update the wrap mode on the texture object. The texture backend - should cache the value so that it will be a no-op if the object - already has the same wrap mode set. The backend is best placed to - do this because it knows how many of the coordinates will - actually be used (ie, a 1D texture only cares about the 's' - coordinate but a 3D texture would use all three). GL uses the - wrap mode as part of the texture object state but we are - pretending it's part of the per-layer environment state. This - will break if the application tries to use different modes in - different layers using the same texture. */ +gboolean +_cogl_material_backend_glsl_passthrough (CoglMaterial *material) +{ + return TRUE; +} - if (wrap_mode_overrides && wrap_mode_overrides->values[layer_num].s) - wrap_mode_s = (wrap_mode_overrides->values[layer_num].s == - COGL_MATERIAL_WRAP_MODE_OVERRIDE_REPEAT ? - GL_REPEAT : - wrap_mode_overrides->values[layer_num].s == - COGL_MATERIAL_WRAP_MODE_OVERRIDE_CLAMP_TO_EDGE ? - GL_CLAMP_TO_EDGE : - GL_CLAMP_TO_BORDER); - else if (layer->wrap_mode_s == COGL_MATERIAL_WRAP_MODE_AUTOMATIC) - wrap_mode_s = GL_CLAMP_TO_EDGE; - else - wrap_mode_s = layer->wrap_mode_s; +gboolean +_cogl_material_backend_glsl_end (CoglMaterial *material) +{ + return TRUE; +} - if (wrap_mode_overrides && wrap_mode_overrides->values[layer_num].t) - wrap_mode_t = (wrap_mode_overrides->values[layer_num].t == - COGL_MATERIAL_WRAP_MODE_OVERRIDE_REPEAT ? - GL_REPEAT : - wrap_mode_overrides->values[layer_num].t == - COGL_MATERIAL_WRAP_MODE_OVERRIDE_CLAMP_TO_EDGE ? - GL_CLAMP_TO_EDGE : - GL_CLAMP_TO_BORDER); - else if (layer->wrap_mode_t == COGL_MATERIAL_WRAP_MODE_AUTOMATIC) - wrap_mode_t = GL_CLAMP_TO_EDGE; - else - wrap_mode_t = layer->wrap_mode_t; +static const CoglMaterialBackend _cogl_material_glsl_backend = +{ + _cogl_material_backend_glsl_get_max_texture_units, + _cogl_material_backend_glsl_start, + _cogl_material_backend_glsl_add_layer, + _cogl_material_backend_glsl_passthrough, + _cogl_material_backend_glsl_end, + NULL, /* material_state_change_notify */ + NULL, /* layer_state_change_notify */ + NULL, /* free_priv */ +}; - if (wrap_mode_overrides && wrap_mode_overrides->values[layer_num].r) - wrap_mode_r = (wrap_mode_overrides->values[layer_num].r == - COGL_MATERIAL_WRAP_MODE_OVERRIDE_REPEAT ? - GL_REPEAT : - wrap_mode_overrides->values[layer_num].r == - COGL_MATERIAL_WRAP_MODE_OVERRIDE_CLAMP_TO_EDGE ? - GL_CLAMP_TO_EDGE : - GL_CLAMP_TO_BORDER); - else if (layer->wrap_mode_r == COGL_MATERIAL_WRAP_MODE_AUTOMATIC) - wrap_mode_r = GL_CLAMP_TO_EDGE; - else - wrap_mode_r = layer->wrap_mode_r; +#endif /* COGL_MATERIAL_BACKEND_GLSL */ - _cogl_texture_set_wrap_mode_parameters (tex_handle, - wrap_mode_s, - wrap_mode_t, - wrap_mode_r); +#ifdef COGL_MATERIAL_BACKEND_ARBFP + +static int +_cogl_material_backend_arbfp_get_max_texture_units (void) +{ + return get_max_texture_image_units (); +} + +static gboolean +_cogl_material_backend_arbfp_start (CoglMaterial *material) +{ + CoglMaterialBackendARBfpPrivate *priv; + + _COGL_GET_CONTEXT (ctx, FALSE); + + if (!_cogl_features_available_private (COGL_FEATURE_PRIVATE_ARB_FP)) + return FALSE; + + /* TODO: support fog */ + if (ctx->fog_enabled) + return FALSE; + + if (!material->backend_priv) + material->backend_priv = g_slice_new0 (CoglMaterialBackendARBfpPrivate); + priv = material->backend_priv; + + if (priv->gl_program == 0) + { + /* Se reuse a single grow-only GString for ARBfp code-gen */ + g_string_set_size (ctx->arbfp_source_buffer, 0); + priv->source = ctx->arbfp_source_buffer; + g_string_append (priv->source, + "!!ARBfp1.0\n" + "TEMP output;\n" + "TEMP tmp0, tmp1, tmp2, tmp3, tmp4;\n" + "PARAM half = {.5, .5, .5, .5};\n" + "PARAM one = {1, 1, 1, 1};\n" + "PARAM two = {2, 2, 2, 2};\n" + "PARAM minus_one = {-1, -1, -1, -1};\n"); + priv->sampled = g_new0 (gboolean, material->n_layers); + } + + return TRUE; } +/* Determines if we need to handle the RGB and A texture combining + * separately or is the same function used for both channel masks and + * with the same arguments... + */ static gboolean -cogl_material_program_is_used (void) +need_texture_combine_separate (CoglMaterialLayer *layer) { - static gint use_arbfp = -1; + int n_args; + int i; + + if (layer->texture_combine_rgb_func != layer->texture_combine_alpha_func) + return TRUE; - if (G_UNLIKELY (use_arbfp == -1)) + n_args = get_n_args_for_combine_func (layer->texture_combine_rgb_func); + + for (i = 0; i < n_args; i++) { - const gchar *env_var = g_getenv ("COGL_PIPELINE"); - use_arbfp = FALSE; + if (layer->texture_combine_rgb_src[i] != + layer->texture_combine_alpha_src[i]) + return TRUE; - if (g_strcmp0 (env_var, "arbfp") == 0) + /* + * We can allow some variation of the source operands without + * needing a separation... + * + * "A = REPLACE (CONSTANT[A])" + either of the following... + * "RGB = REPLACE (CONSTANT[RGB])" + * "RGB = REPLACE (CONSTANT[A])" + * + * can be combined as: + * "RGBA = REPLACE (CONSTANT)" or + * "RGBA = REPLACE (CONSTANT[A])" or + * + * And "A = REPLACE (1-CONSTANT[A])" + either of the following... + * "RGB = REPLACE (1-CONSTANT)" or + * "RGB = REPLACE (1-CONSTANT[A])" + * + * can be combined as: + * "RGBA = REPLACE (1-CONSTANT)" or + * "RGBA = REPLACE (1-CONSTANT[A])" + */ + switch (layer->texture_combine_alpha_op[i]) { - if (_cogl_features_available_private (COGL_FEATURE_PRIVATE_ARB_FP)) + case GL_SRC_ALPHA: + switch (layer->texture_combine_rgb_op[i]) { - g_message ("Using an ARBfp pipeline"); - use_arbfp = TRUE; + case GL_SRC_COLOR: + case GL_SRC_ALPHA: + break; + default: + return FALSE; } - else + break; + case GL_ONE_MINUS_SRC_ALPHA: + switch (layer->texture_combine_rgb_op[i]) { - g_warning ("ARB_fragment_program is not available for your " - "platform"); + case GL_ONE_MINUS_SRC_COLOR: + case GL_ONE_MINUS_SRC_ALPHA: + break; + default: + return FALSE; } + break; + default: + return FALSE; /* impossible */ + } + } + + return FALSE; +} + +static const char * +gl_target_to_arbfp_string (GLenum gl_target) +{ +#ifndef HAVE_COGL_GLES2 + if (gl_target == GL_TEXTURE_1D) + return "1D"; + else +#endif + if (gl_target == GL_TEXTURE_2D) + return "2D"; +#ifdef ARB_texture_rectangle + else if (gl_target == GL_TEXTURE_RECTANGLE_ARB) + return "RECT"; +#endif + else + return "2D"; +} + +static void +setup_texture_source (CoglMaterialBackendARBfpPrivate *priv, + int unit_index, + GLenum gl_target) +{ + if (!priv->sampled[unit_index]) + { + g_string_append_printf (priv->source, + "TEMP texel%d;\n" + "TEX texel%d,fragment.texcoord[%d]," + "texture[%d],%s;\n", + unit_index, + unit_index, + unit_index, + unit_index, + gl_target_to_arbfp_string (gl_target)); + priv->sampled[unit_index] = TRUE; + } +} + +typedef enum _CoglMaterialBackendARBfpArgType +{ + COGL_MATERIAL_BACKEND_ARBFP_ARG_TYPE_SIMPLE, + COGL_MATERIAL_BACKEND_ARBFP_ARG_TYPE_CONSTANT, + COGL_MATERIAL_BACKEND_ARBFP_ARG_TYPE_TEXTURE +} CoglMaterialBackendARBfpArgType; + +typedef struct _CoglMaterialBackendARBfpArg +{ + const char *name; + + CoglMaterialBackendARBfpArgType type; + + /* for type = TEXTURE */ + int texture_unit; + GLenum texture_target; + + /* for type = CONSTANT */ + int constant_id; + + const char *swizzle; + +} CoglMaterialBackendARBfpArg; + +static void +append_arg (GString *source, const CoglMaterialBackendARBfpArg *arg) +{ + switch (arg->type) + { + case COGL_MATERIAL_BACKEND_ARBFP_ARG_TYPE_TEXTURE: + g_string_append_printf (source, "texel%d%s", + arg->texture_unit, arg->swizzle); + break; + case COGL_MATERIAL_BACKEND_ARBFP_ARG_TYPE_CONSTANT: + g_string_append_printf (source, "constant%d%s", + arg->constant_id, arg->swizzle); + break; + case COGL_MATERIAL_BACKEND_ARBFP_ARG_TYPE_SIMPLE: + g_string_append_printf (source, "%s%s", + arg->name, arg->swizzle); + break; + } +} + +/* Note: we are trying to avoid duplicating strings during codegen + * which is why we have the slightly awkward + * CoglMaterialBackendARBfpArg mechanism. */ +static void +setup_arg (CoglMaterial *material, + CoglMaterialLayer *layer, + CoglBlendStringChannelMask mask, + int arg_index, + GLint src, + GLint op, + CoglMaterialBackendARBfpArg *arg) +{ + CoglMaterialBackendARBfpPrivate *priv = material->backend_priv; + static const char *tmp_name[3] = { "tmp0", "tmp1", "tmp2" }; + GLenum gl_target; + + switch (src) + { + case GL_TEXTURE: + arg->type = COGL_MATERIAL_BACKEND_ARBFP_ARG_TYPE_TEXTURE; + arg->name = "texel%d"; + arg->texture_unit = layer->unit_index; + cogl_texture_get_gl_texture (layer->texture, NULL, &gl_target); + setup_texture_source (priv, arg->texture_unit, gl_target); + break; + case GL_CONSTANT: + arg->type = COGL_MATERIAL_BACKEND_ARBFP_ARG_TYPE_CONSTANT; + arg->name = "constant%d"; + arg->constant_id = priv->next_constant_id++; + g_string_append_printf (priv->source, + "PARAM constant%d = " + " {%f, %f, %f, %f};\n", + arg->constant_id, + layer->texture_combine_constant[0], + layer->texture_combine_constant[1], + layer->texture_combine_constant[2], + layer->texture_combine_constant[3]); + break; + case GL_PRIMARY_COLOR: + arg->type = COGL_MATERIAL_BACKEND_ARBFP_ARG_TYPE_SIMPLE; + arg->name = "fragment.color.primary"; + break; + case GL_PREVIOUS: + arg->type = COGL_MATERIAL_BACKEND_ARBFP_ARG_TYPE_SIMPLE; + if (layer->unit_index == 0) + arg->name = "fragment.color.primary"; + else + arg->name = "output"; + break; + default: /* GL_TEXTURE0..N */ + arg->type = COGL_MATERIAL_BACKEND_ARBFP_ARG_TYPE_TEXTURE; + arg->name = "texture[%d]"; + arg->texture_unit = src - GL_TEXTURE0; + cogl_texture_get_gl_texture (layer->texture, NULL, &gl_target); + setup_texture_source (priv, arg->texture_unit, gl_target); + } + + arg->swizzle = ""; + + switch (op) + { + case GL_SRC_COLOR: + break; + case GL_ONE_MINUS_SRC_COLOR: + g_string_append_printf (priv->source, + "SUB tmp%d, one, ", + arg_index); + append_arg (priv->source, arg); + g_string_append_printf (priv->source, ";\n"); + arg->type = COGL_MATERIAL_BACKEND_ARBFP_ARG_TYPE_SIMPLE; + arg->name = tmp_name[arg_index]; + arg->swizzle = ""; + break; + case GL_SRC_ALPHA: + /* avoid a swizzle if we know RGB are going to be masked + * in the end anyway */ + if (mask != COGL_BLEND_STRING_CHANNEL_MASK_ALPHA) + arg->swizzle = ".a"; + break; + case GL_ONE_MINUS_SRC_ALPHA: + g_string_append_printf (priv->source, + "SUB tmp%d, one, ", + arg_index); + append_arg (priv->source, arg); + /* avoid a swizzle if we know RGB are going to be masked + * in the end anyway */ + if (mask != COGL_BLEND_STRING_CHANNEL_MASK_ALPHA) + g_string_append_printf (priv->source, ".a;\n"); + else + g_string_append_printf (priv->source, ";\n"); + arg->type = COGL_MATERIAL_BACKEND_ARBFP_ARG_TYPE_SIMPLE; + arg->name = tmp_name[arg_index]; + break; + default: + g_error ("Unknown texture combine operator %d", op); + break; + } +} + +static gboolean +backend_arbfp_args_equal (CoglMaterialBackendARBfpArg *arg0, + CoglMaterialBackendARBfpArg *arg1) +{ + if (arg0->type != arg1->type) + return FALSE; + + if (arg0->name != arg1->name && + strcmp (arg0->name, arg1->name) != 0) + return FALSE; + + if (arg0->type == COGL_MATERIAL_BACKEND_ARBFP_ARG_TYPE_TEXTURE && + arg0->texture_unit != arg1->texture_unit) + return FALSE; + /* Note we don't have to check the target; a texture unit can only + * have one target enabled at a time. */ + + if (arg0->type == COGL_MATERIAL_BACKEND_ARBFP_ARG_TYPE_CONSTANT && + arg0->constant_id != arg0->constant_id) + return FALSE; + + if (arg0->swizzle != arg1->swizzle && + strcmp (arg0->swizzle, arg1->swizzle) != 0) + return FALSE; + + return TRUE; +} + +static void +append_function (CoglMaterial *material, + CoglMaterialLayer *layer, + CoglBlendStringChannelMask mask, + GLint function, + CoglMaterialBackendARBfpArg *args, + int n_args) +{ + CoglMaterialBackendARBfpPrivate *priv = material->backend_priv; + const char *mask_name; + + switch (mask) + { + case COGL_BLEND_STRING_CHANNEL_MASK_RGB: + mask_name = ".rgb"; + break; + case COGL_BLEND_STRING_CHANNEL_MASK_ALPHA: + mask_name = ".a"; + break; + case COGL_BLEND_STRING_CHANNEL_MASK_RGBA: + mask_name = ""; + break; + default: + g_error ("Unknown channel mask %d", mask); + mask_name = ""; + } + + switch (function) + { + case GL_ADD: + g_string_append_printf (priv->source, "ADD_SAT output%s, ", + mask_name); + break; + case GL_MODULATE: + /* Note: no need to saturate since we can assume operands + * have values in the range [0,1] */ + g_string_append_printf (priv->source, "MUL output%s, ", + mask_name); + break; + case GL_REPLACE: + /* Note: no need to saturate since we can assume operand + * has a value in the range [0,1] */ + g_string_append_printf (priv->source, "MOV output%s, ", + mask_name); + break; + case GL_SUBTRACT: + g_string_append_printf (priv->source, "SUB_SAT output%s, ", + mask_name); + break; + case GL_ADD_SIGNED: + g_string_append_printf (priv->source, "ADD tmp3%s, ", + mask_name); + append_arg (priv->source, &args[0]); + g_string_append (priv->source, ", "); + append_arg (priv->source, &args[1]); + g_string_append (priv->source, ";\n"); + g_string_append_printf (priv->source, "SUB_SAT output%s, tmp3, half", + mask_name); + n_args = 0; + break; + case GL_DOT3_RGB: + /* These functions are the same except that GL_DOT3_RGB never + * updates the alpha channel. + * + * NB: GL_DOT3_RGBA is a bit special because it effectively forces + * an RGBA mask and we end up ignoring any separate alpha channel + * function. + */ + case GL_DOT3_RGBA: + { + const char *tmp4 = "tmp4"; + + /* The maths for this was taken from Mesa; + * apparently: + * + * tmp3 = 2*src0 - 1 + * tmp4 = 2*src1 - 1 + * output = DP3 (tmp3, tmp4) + * + * is the same as: + * + * output = 4 * DP3 (src0 - 0.5, src1 - 0.5) + */ + + g_string_append (priv->source, "MAD tmp3, two, "); + append_arg (priv->source, &args[0]); + g_string_append (priv->source, ", minus_one;\n"); + + if (!backend_arbfp_args_equal (&args[0], &args[1])) + { + g_string_append (priv->source, "MAD tmp4, two, "); + append_arg (priv->source, &args[1]); + g_string_append (priv->source, ", minus_one;\n"); + } + else + tmp4 = "tmp3"; + + g_string_append_printf (priv->source, + "DP3_SAT output%s, tmp3, %s", + mask_name, tmp4); + n_args = 0; + } + break; + case GL_INTERPOLATE: + /* Note: no need to saturate since we can assume operands + * have values in the range [0,1] */ + + /* NB: GL_INTERPOLATE = arg0*arg2 + arg1*(1-arg2) + * but LRP dst, a, b, c = b*a + c*(1-a) */ + g_string_append_printf (priv->source, "LRP output%s, ", + mask_name); + append_arg (priv->source, &args[2]); + g_string_append (priv->source, ", "); + append_arg (priv->source, &args[0]); + g_string_append (priv->source, ", "); + append_arg (priv->source, &args[1]); + n_args = 0; + break; + default: + g_error ("Unknown texture combine function %d", function); + g_string_append_printf (priv->source, "MUL_SAT output%s, ", + mask_name); + n_args = 2; + break; + } + + if (n_args > 0) + append_arg (priv->source, &args[0]); + if (n_args > 1) + { + g_string_append (priv->source, ", "); + append_arg (priv->source, &args[1]); + } + g_string_append (priv->source, ";\n"); +} + +static void +append_masked_combine (CoglMaterial *material, + CoglMaterialLayer *layer, + CoglBlendStringChannelMask mask, + GLint function, + GLint *src, + GLint *op) +{ + int i; + int n_args; + CoglMaterialBackendARBfpArg args[3]; + + n_args = get_n_args_for_combine_func (function); + + for (i = 0; i < n_args; i++) + { + setup_arg (material, + layer, + mask, + i, + src[i], + op[i], + &args[i]); + } + + append_function (material, + layer, + mask, + function, + args, + n_args); +} + +static gboolean +_cogl_material_backend_arbfp_add_layer (CoglMaterialLayer *layer) +{ + CoglMaterial *material = layer->material; + CoglMaterialBackendARBfpPrivate *priv = material->backend_priv; + + /* Notes... + * + * We are ignoring the issue of texture indirection limits until + * someone complains (Ref Section 3.11.6 in the ARB_fragment_program + * spec) + * + * There always five TEMPs named tmp0, tmp1 and tmp2, tmp3 and tmp4 + * available and these constants: 'one' = {1, 1, 1, 1}, 'half' + * {.5, .5, .5, .5}, 'two' = {2, 2, 2, 2}, 'minus_one' = {-1, -1, + * -1, -1} + * + * tmp0-2 are intended for dealing with some of the texture combine + * operands (e.g. GL_ONE_MINUS_SRC_COLOR) tmp3/4 are for dealing + * with the GL_ADD_SIGNED texture combine and the GL_DOT3_RGB[A] + * functions. + * + * Each layer outputs to the TEMP called "output", and reads from + * output if it needs to refer to GL_PREVIOUS. (we detect if we are + * layer0 so we will read fragment.color for GL_PREVIOUS in that + * case) + * + * We aim to do all the channels together if the same function is + * used for RGB as for A. + * + * We aim to avoid string duplication / allocations during codegen. + * + * We are careful to only saturate when writing to output. + */ + + if (!priv->source) + return TRUE; + + if (!need_texture_combine_separate (layer)) + { + append_masked_combine (material, + layer, + COGL_BLEND_STRING_CHANNEL_MASK_RGBA, + layer->texture_combine_rgb_func, + layer->texture_combine_rgb_src, + layer->texture_combine_rgb_op); + } + else if (layer->texture_combine_rgb_func == GL_DOT3_RGBA) + { + /* GL_DOT3_RGBA Is a bit weird as a GL_COMBINE_RGB function + * since if you use it, it overrides your ALPHA function... + */ + append_masked_combine (material, + layer, + COGL_BLEND_STRING_CHANNEL_MASK_RGBA, + layer->texture_combine_rgb_func, + layer->texture_combine_rgb_src, + layer->texture_combine_rgb_op); + } + else + { + append_masked_combine (material, + layer, + COGL_BLEND_STRING_CHANNEL_MASK_RGB, + layer->texture_combine_rgb_func, + layer->texture_combine_rgb_src, + layer->texture_combine_rgb_op); + append_masked_combine (material, + layer, + COGL_BLEND_STRING_CHANNEL_MASK_ALPHA, + layer->texture_combine_alpha_func, + layer->texture_combine_alpha_src, + layer->texture_combine_alpha_op); + } + + return TRUE; +} + +gboolean +_cogl_material_backend_arbfp_passthrough (CoglMaterial *material) +{ + CoglMaterialBackendARBfpPrivate *priv = material->backend_priv; + + if (!priv->source) + return TRUE; + + g_string_append (priv->source, "MOV output, fragment.color.primary;\n"); + return TRUE; +} + +static gboolean +_cogl_material_backend_arbfp_end (CoglMaterial *material) +{ + CoglMaterialBackendARBfpPrivate *priv = material->backend_priv; + + _COGL_GET_CONTEXT (ctx, FALSE); + + if (priv->source) + { + GLenum gl_error; + + g_string_append (priv->source, "MOV result.color,output;\n"); + g_string_append (priv->source, "END\n"); + + if (G_UNLIKELY (cogl_debug_flags & COGL_DEBUG_SHOW_SOURCE)) + g_message ("material program:\n%s", priv->source->str); + + GE (glGenPrograms (1, &priv->gl_program)); + + GE (glBindProgram (GL_FRAGMENT_PROGRAM_ARB, priv->gl_program)); + + while ((gl_error = glGetError ()) != GL_NO_ERROR) + ; + glProgramString (GL_FRAGMENT_PROGRAM_ARB, + GL_PROGRAM_FORMAT_ASCII_ARB, + priv->source->len, + priv->source->str); + if (glGetError () != GL_NO_ERROR) + { + g_warning ("\n%s\n%s", + priv->source->str, + glGetString (GL_PROGRAM_ERROR_STRING_ARB)); } + + priv->source = NULL; + + g_free (priv->sampled); + priv->sampled = NULL; } + else + GE (glBindProgram (GL_FRAGMENT_PROGRAM_ARB, priv->gl_program)); + + use_program (COGL_INVALID_HANDLE, COGL_MATERIAL_PROGRAM_TYPE_ARBFP); - return use_arbfp; + return TRUE; } -static gboolean -cogl_material_program_show_source (void) +static void +_cogl_material_backend_arbfp_material_change_notify (CoglMaterial *material, + unsigned long changes, + GLubyte *new_color) { - static gint show_source = -1; + CoglMaterialBackendARBfpPrivate *priv = material->backend_priv; + static const unsigned long fragment_op_changes = + COGL_MATERIAL_CHANGE_LAYERS; + /* TODO: COGL_MATERIAL_CHANGE_FOG */ - if (G_UNLIKELY (show_source == -1)) - { - const gchar *env_var = g_getenv ("COGL_SHOW_FP_SOURCE"); + _COGL_GET_CONTEXT (ctx, NO_RETVAL); - if (env_var != NULL && env_var[0] != '\0') - show_source = TRUE; - else - show_source = FALSE; + if (priv && + priv->gl_program && + changes & fragment_op_changes) + { + GE (glDeletePrograms (1, &priv->gl_program)); + priv->gl_program = 0; } +} - return show_source; +static void +_cogl_material_backend_arbfp_layer_change_notify (CoglMaterialLayer *layer, + unsigned long changes) +{ + /* TODO: we could be saving snippets of texture combine code along + * with each layer and then when a layer changes we would just free + * the snippet. */ + return; } -static const char * -combine_func_to_arbfp_instruction (GLint op) +static void +_cogl_material_backend_arbfp_free_priv (CoglMaterial *material) { - switch (op) + CoglMaterialBackendARBfpPrivate *priv = material->backend_priv; + + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + + if (priv) { - case GL_ADD: - return "ADD_SAT"; - case GL_MODULATE: - return "MUL"; - case GL_REPLACE: - return "MOV"; - case GL_SUBTRACT: - return "SUB_SAT"; - case GL_ADD_SIGNED: - case GL_DOT3_RGB: - case GL_DOT3_RGBA: - case GL_INTERPOLATE: - default: - /* get away with it now */ - g_message ("*** oops got %d", op); - return "MUL"; + glDeletePrograms (1, &priv->gl_program); + if (priv->sampled) + g_free (priv->sampled); + g_slice_free (CoglMaterialBackendARBfpPrivate, material->backend_priv); } } -static const char * -gl_target_to_arbfp_string (GLenum gl_target) +static const CoglMaterialBackend _cogl_material_arbfp_backend = { - if (gl_target == GL_TEXTURE_1D) - return "1D"; - else if (gl_target == GL_TEXTURE_2D) - return "2D"; - else if (gl_target == GL_TEXTURE_RECTANGLE_ARB) - return "RECT"; - else - return "2D"; + _cogl_material_backend_arbfp_get_max_texture_units, + _cogl_material_backend_arbfp_start, + _cogl_material_backend_arbfp_add_layer, + _cogl_material_backend_arbfp_passthrough, + _cogl_material_backend_arbfp_end, + _cogl_material_backend_arbfp_material_change_notify, + _cogl_material_backend_arbfp_layer_change_notify, + _cogl_material_backend_arbfp_free_priv +}; + +#endif /* COGL_MATERIAL_BACKEND_ARBFP */ + +static int +_cogl_material_backend_fixed_get_max_texture_units (void) +{ + _COGL_GET_CONTEXT (ctx, 0); + + /* This function is called quite often so we cache the value to + avoid too many GL calls */ + if (ctx->max_texture_units == -1) + { + ctx->max_texture_units = 1; + GE (glGetIntegerv (GL_MAX_TEXTURE_UNITS, + &ctx->max_texture_units)); + } + + return ctx->max_texture_units; } static gboolean -cogl_material_program_is_unit_sampled (CoglMaterialProgram *program, - unsigned int sampler_nb) +_cogl_material_backend_fixed_start (CoglMaterial *material) { - return program->sampled[sampler_nb]; + use_program (COGL_INVALID_HANDLE, COGL_MATERIAL_PROGRAM_TYPE_FIXED); + return TRUE; } -static char * -cogl_material_layer_get_arbfp_arg (CoglMaterialLayer *layer, - CoglTextureUnit *unit, - CoglBlendStringChannelMask mask, - int arg, - gboolean *need_sampler, - unsigned int *sampler_nb) +static gboolean +_cogl_material_backend_fixed_add_layer (CoglMaterialLayer *layer) { - char buffer[32]; - unsigned int texture_unit; - GLint *src; + CoglTextureUnit *unit = _cogl_get_texture_unit (layer->unit_index); + int n_rgb_func_args; + int n_alpha_func_args; - if (mask == COGL_BLEND_STRING_CHANNEL_MASK_ALPHA) - src = layer->texture_combine_alpha_src; - else - src = layer->texture_combine_rgb_src; + _COGL_GET_CONTEXT (ctx, FALSE); - if (src[arg] == GL_PRIMARY_COLOR) - { - g_sprintf (buffer, "fragment.color.primary"); - *need_sampler = FALSE; - } - else if (src[arg] == GL_PREVIOUS) + /* XXX: Beware that since we are changing the active texture unit we + * must make sure we don't call into other Cogl components that may + * temporarily bind texture objects to query/modify parameters until + * we restore texture unit 1 as the active unit. For more details + * about this see the end of _cogl_material_flush_gl_state + */ + set_active_texture_unit (unit->index); + +#ifndef DISABLE_MATERIAL_CACHE + if (unit->layer_differences & COGL_MATERIAL_LAYER_DIFFERENCE_COMBINE || + layer->differences & COGL_MATERIAL_LAYER_DIFFERENCE_COMBINE) +#endif { - int unit_nb = unit->index - 1; + 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); - if (unit_nb >= 0) + 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) { - *need_sampler = TRUE; - *sampler_nb = unit->index - 1; - g_sprintf (buffer, "texture[%d]", *sampler_nb); + 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])); } - else + if (n_rgb_func_args > 2) { - g_sprintf (buffer, "fragment.color.primary"); - *need_sampler = FALSE; + 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])); } - } - else if (src[arg] == GL_TEXTURE) - { - *need_sampler = TRUE; - *sampler_nb = unit->index; - g_sprintf (buffer, "texture[%d]", *sampler_nb); - } - else - { - texture_unit = src[arg] - GL_TEXTURE0; - if (texture_unit < _cogl_get_max_texture_image_units ()) + + /* 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) { - *need_sampler = TRUE; - *sampler_nb = texture_unit; - g_sprintf (buffer, "texture[%d]", *sampler_nb); + 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])); } - else + if (n_alpha_func_args > 2) { - /* Note that this should not happend */ - g_warning ("Cannot figure out ARBfp1.0 argument defaulting to " - "primary color"); - g_sprintf (buffer, "fragment.color.primary"); - *need_sampler = FALSE; + 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])); } + + GE (glTexEnvfv (GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, + layer->texture_combine_constant)); } - return g_strdup (buffer); + return TRUE; } -static void -cogl_material_program_gen (CoglMaterialProgram *program, - CoglMaterialLayer *layer, - CoglTextureUnit *unit, - CoglBlendStringChannelMask mask, - GLenum gl_target) +static gboolean +_cogl_material_backend_fixed_end (CoglMaterial *material) { - gboolean need_sampler; - unsigned int sampler_nb; - GLint func; - unsigned int nb_args, i; - char *arg[3]; - const gchar *swizzle[2][3] = - { - /* RGB ALPHA RGBA*/ - { ".xyz", ".a", "" }, - { "" , ".a", "" } - }; + /* There is a convention to always leave texture unit 1 active and + * since we modify the active unit in + * _cogl_material_backend_fixed_add_layer we need to restore it + * here... + * + * (See the end of _cogl_material_flush_gl_state for more + * details) */ + set_active_texture_unit (1); + return TRUE; +} - if (mask == COGL_BLEND_STRING_CHANNEL_MASK_ALPHA) - { - func = layer->texture_combine_alpha_func; - } - else - func = layer->texture_combine_rgb_func; +static const CoglMaterialBackend _cogl_material_fixed_backend = +{ + _cogl_material_backend_fixed_get_max_texture_units, + _cogl_material_backend_fixed_start, + _cogl_material_backend_fixed_add_layer, + NULL, + _cogl_material_backend_fixed_end, + NULL, /* material_change_notify */ + NULL, /* layer_change_notify */ + NULL /* free_priv */ +}; + +/* Here we resolve what low level GL texture we are *actually* going + * to use. This can either be a layer0 override texture, it can be a + * fallback texture or we can query the CoglTexture for the GL + * texture. + */ +static void +_cogl_material_layer_get_texture_info (CoglMaterialLayer *layer, + GLuint layer0_override_texture, + gboolean fallback, + CoglHandle *texture, + GLuint *gl_texture, + GLuint *gl_target) +{ + gboolean layer0_overridden = layer0_override_texture ? TRUE : FALSE; - nb_args = get_n_args_for_combine_func (func); + _COGL_GET_CONTEXT (ctx, NO_RETVAL); - for (i = 0; i < nb_args; i++) + *texture = layer->texture; + if (G_LIKELY (*texture != COGL_INVALID_HANDLE)) + cogl_texture_get_gl_texture (*texture, gl_texture, gl_target); + else { - arg[i] = cogl_material_layer_get_arbfp_arg (layer, - unit, - mask, - i, - &need_sampler, - &sampler_nb); - if (need_sampler) - { - if (!cogl_material_program_is_unit_sampled (program, sampler_nb)) - { - g_string_append_printf (program->source, - "TEMP texel%d;\n", - sampler_nb); - g_string_append_printf (program->source, - "TEX texel%d,fragment.texcoord[%d]," - "%s,%s;\n", - sampler_nb, - unit->index, - arg[i], - gl_target_to_arbfp_string (gl_target)); - program->sampled[sampler_nb] = TRUE; - } - arg[i] = g_strdup_printf ("texel%d", sampler_nb); - } + fallback = TRUE; + *gl_target = GL_TEXTURE_2D; } - if (nb_args == 1) + if (layer0_overridden && layer->unit_index == 0) { - g_string_append_printf (program->source, - "%s output%s,%s%s;\n", - combine_func_to_arbfp_instruction (func), - swizzle[0][mask], - arg[0], swizzle[1][mask]); + /* We assume that layer0 overrides are only used for sliced + * textures where the GL texture is actually a sub component + * of the layer->texture... */ + *texture = layer->texture; + *gl_texture = layer0_override_texture; } - else if (nb_args == 2) + else if (fallback) { - g_string_append_printf (program->source, - "%s output%s,%s%s,%s%s;\n", - combine_func_to_arbfp_instruction (func), - swizzle[0][mask], - arg[0], swizzle[1][mask], - arg[1], swizzle[1][mask]); + if (*gl_target == GL_TEXTURE_2D) + *texture = ctx->default_gl_texture_2d_tex; +#ifdef HAVE_COGL_GL + else if (*gl_target == GL_TEXTURE_RECTANGLE_ARB) + *texture = ctx->default_gl_texture_rect_tex; +#endif + else + { + g_warning ("We don't have a default texture we can use to fill " + "in for an invalid material layer, since it was " + "using an unsupported texture target "); + /* might get away with this... */ + *texture = ctx->default_gl_texture_2d_tex; + } + cogl_texture_get_gl_texture (*texture, gl_texture, NULL); } +} - for (i = 0; i < nb_args; i++) - g_free (arg[i]); +#ifndef HAVE_COGL_GLES +static gboolean +blend_factor_uses_constant (GLenum blend_factor) +{ + return (blend_factor == GL_CONSTANT_COLOR || + blend_factor == GL_ONE_MINUS_CONSTANT_COLOR || + blend_factor == GL_CONSTANT_ALPHA || + blend_factor == GL_ONE_MINUS_CONSTANT_ALPHA); } +#endif + static void -cogl_material_program_update (CoglMaterialProgram *program, - CoglMaterialLayer *layer, - CoglTextureUnit *unit, - CoglLayerInfo *gl_layer_info, - GLenum gl_target) +_cogl_material_flush_color_blend_alpha_state (CoglMaterial *material, + gboolean skip_gl_color) { - CoglBlendStringChannelMask mask; - - if (!(layer->flags & COGL_MATERIAL_LAYER_FLAG_DIRTY)) - return; + _COGL_GET_CONTEXT (ctx, NO_RETVAL); -#ifndef DISABLE_MATERIAL_CACHE - if (!(gl_layer_info && - gl_layer_info->flags & COGL_MATERIAL_LAYER_FLAG_DEFAULT_COMBINE && - (layer->flags & COGL_MATERIAL_LAYER_FLAG_DEFAULT_COMBINE))) -#endif + if (!skip_gl_color) { - if (program->source == NULL) + if (!(ctx->current_material_flags & COGL_MATERIAL_FLAG_DEFAULT_COLOR) || + !(material->flags & COGL_MATERIAL_FLAG_DEFAULT_COLOR) || + /* Assume if we were previously told to skip the color, then + * the current color needs updating... */ + ctx->current_material_skip_gl_color) { - program->source = g_string_new ("!!ARBfp1.0\n"); - g_string_append (program->source, "TEMP output;\n"); - program->sampled = g_new0 (gboolean, - _cogl_get_max_texture_image_units ()); + GE (glColor4ub (material->unlit[0], + material->unlit[1], + material->unlit[2], + material->unlit[3])); } + } - if (layer->texture_combine_rgb_func == layer->texture_combine_alpha_func) - { - mask = COGL_BLEND_STRING_CHANNEL_MASK_RGBA; + /* XXX: + * Currently we only don't update state when the flags indicate that the + * current material uses the defaults, and the new material also uses the + * defaults, but we could do deeper comparisons of state. + */ - cogl_material_program_gen (program, - layer, - unit, - mask, - gl_target); - } - else - { - mask = COGL_BLEND_STRING_CHANNEL_MASK_RGB; - cogl_material_program_gen (program, - layer, - unit, - mask, - gl_target); - mask = COGL_BLEND_STRING_CHANNEL_MASK_ALPHA; - cogl_material_program_gen (program, - layer, - unit, - mask, - gl_target); - } + if (!(ctx->current_material_flags & + COGL_MATERIAL_FLAG_DEFAULT_GL_MATERIAL) || + !(material->flags & COGL_MATERIAL_FLAG_DEFAULT_GL_MATERIAL)) + { + /* FIXME - we only need to set these if lighting is enabled... */ + 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)); } -} - -static void -cogl_material_program_flush (CoglMaterialProgram *program) -{ - _COGL_GET_CONTEXT (ctx, NO_RETVAL); - if (program->source) + if (!(ctx->current_material_flags & COGL_MATERIAL_FLAG_DEFAULT_BLEND) || + !(material->flags & COGL_MATERIAL_FLAG_DEFAULT_BLEND)) { - g_string_append (program->source, "MOV result.color,output;\n"); - g_string_append (program->source, "END\n"); +#if defined (HAVE_COGL_GLES2) + gboolean have_blend_equation_seperate = TRUE; + gboolean have_blend_func_separate = TRUE; +#elif defined (HAVE_COGL_GL) + gboolean have_blend_equation_seperate = FALSE; + gboolean have_blend_func_separate = FALSE; + if (ctx->drv.pf_glBlendEquationSeparate) /* Only GL 2.0 + */ + have_blend_equation_seperate = TRUE; + if (ctx->drv.pf_glBlendFuncSeparate) /* Only GL 1.4 + */ + have_blend_func_separate = TRUE; +#endif - if (cogl_material_program_show_source ()) - g_message ("material program:\n%s", program->source->str); +#ifndef HAVE_COGL_GLES /* GLES 1 only has glBlendFunc */ + if (have_blend_equation_seperate && + material->blend_equation_rgb != material->blend_equation_alpha) + GE (glBlendEquationSeparate (material->blend_equation_rgb, + material->blend_equation_alpha)); + else + GE (glBlendEquation (material->blend_equation_rgb)); - GE (glGenPrograms (1, &program->gl_program)); + if (blend_factor_uses_constant (material->blend_src_factor_rgb) || + blend_factor_uses_constant (material->blend_src_factor_alpha) || + blend_factor_uses_constant (material->blend_dst_factor_rgb) || + blend_factor_uses_constant (material->blend_dst_factor_alpha)) + GE (glBlendColor (material->blend_constant[0], + material->blend_constant[1], + material->blend_constant[2], + material->blend_constant[3])); - GE (glBindProgram (GL_FRAGMENT_PROGRAM_ARB, program->gl_program)); - GE (glProgramString (GL_FRAGMENT_PROGRAM_ARB, - GL_PROGRAM_FORMAT_ASCII_ARB, - program->source->len, - program->source->str)); - GE (glEnable (GL_FRAGMENT_PROGRAM_ARB)); + if (have_blend_func_separate && + (material->blend_src_factor_rgb != material->blend_src_factor_alpha || + (material->blend_src_factor_rgb != + material->blend_src_factor_alpha))) + GE (glBlendFuncSeparate (material->blend_src_factor_rgb, + material->blend_dst_factor_rgb, + material->blend_src_factor_alpha, + material->blend_dst_factor_alpha)); + else +#endif + GE (glBlendFunc (material->blend_src_factor_rgb, + material->blend_dst_factor_rgb)); + } - g_string_free (program->source, TRUE); - program->source = NULL; - g_free (program->sampled); - program->sampled = NULL; + if (!(ctx->current_material_flags & COGL_MATERIAL_FLAG_DEFAULT_ALPHA_FUNC) || + !(material->flags & COGL_MATERIAL_FLAG_DEFAULT_ALPHA_FUNC)) + { + /* NB: Currently the Cogl defines are compatible with the GL ones: */ + GE (glAlphaFunc (material->alpha_func, material->alpha_func_reference)); } - else if (program->gl_program != -1) +} + +static int +get_max_activateable_texture_units (void) +{ + _COGL_GET_CONTEXT (ctx, 0); + + if (G_UNLIKELY (ctx->max_activateable_texture_units == -1)) { - GE (glEnable (GL_FRAGMENT_PROGRAM_ARB)); +#ifdef HAVE_COGL_GL + GLint max_tex_coords; + GLint max_combined_tex_units; + GE (glGetIntegerv (GL_MAX_TEXTURE_COORDS, &max_tex_coords)); + GE (glGetIntegerv (GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, + &max_combined_tex_units)); + ctx->max_activateable_texture_units = + MAX (max_tex_coords - 1, max_combined_tex_units); +#else + GE (glGetIntegerv (GL_MAX_TEXTURE_UNITS, + &ctx->max_activateable_texture_units)); +#endif } + + return ctx->max_activateable_texture_units; } /* - * _cogl_material_flush_layers_gl_state: + * _cogl_material_flush_common_gl_state: * @fallback_mask: is a bitmask of the material layers that need to be * replaced with the default, fallback textures. The fallback textures are * fully transparent textures so they hopefully wont contribute to the @@ -1762,7 +2842,7 @@ cogl_material_program_flush (CoglMaterialProgram *program) * layer 0 instead of plucking the gl_texture from the CoglTexture of layer * 0. * - * The intention of this is for any geometry that supports sliced textures. + * The intention of this is for any primitives that supports sliced textures. * The code will can iterate each of the slices and re-flush the material * forcing the GL texture of each slice in turn. * @@ -1782,88 +2862,70 @@ cogl_material_program_flush (CoglMaterialProgram *program) * isn't ideal, and can't be used with CoglVertexBuffers. */ static void -_cogl_material_flush_layers_gl_state (CoglMaterial *material, +_cogl_material_flush_common_gl_state (CoglMaterial *material, + gboolean skip_gl_color, guint32 fallback_mask, guint32 disable_mask, GLuint layer0_override_texture, const CoglMaterialWrapModeOverrides * wrap_mode_overrides) { - GList *tmp; - int i; - - _COGL_GET_CONTEXT (ctx, NO_RETVAL); - - if (cogl_material_program_is_used ()) - glDisable (GL_FRAGMENT_PROGRAM_ARB); - - for (tmp = material->layers, i = 0; - tmp != NULL && i < _cogl_get_max_texture_image_units (); - tmp = tmp->next, i++) - { - CoglHandle layer_handle = (CoglHandle)tmp->data; - CoglMaterialLayer *layer = - _cogl_material_layer_pointer_from_handle (layer_handle); - CoglLayerInfo *gl_layer_info = NULL; - CoglLayerInfo new_gl_layer_info; - CoglHandle tex_handle; - GLuint gl_texture; - GLenum gl_target; - CoglTextureUnit *unit; - - /* Switch units first so we don't disturb the previous unit if - * something needs to bind the texture temporarily */ - GE (glActiveTexture (GL_TEXTURE0 + i)); - - unit = _cogl_get_texture_unit (i); + GList *l; + int i; - new_gl_layer_info.layer0_overridden = - layer0_override_texture ? TRUE : FALSE; - new_gl_layer_info.fallback = - (fallback_mask & (1<texture; - if (tex_handle != COGL_INVALID_HANDLE) - { + _cogl_material_flush_color_blend_alpha_state (material, skip_gl_color); - _cogl_texture_set_filters (tex_handle, - layer->min_filter, - layer->mag_filter); + for (l = material->layers, i = 0; l != NULL; l = l->next, i++) + { + CoglMaterialLayer *layer = l->data; + CoglTextureUnit *unit; + gboolean fallback; + CoglHandle texture; + GLuint gl_texture; + GLenum gl_target; - _cogl_material_set_wrap_modes_for_layer (layer, i, tex_handle, - wrap_mode_overrides); + unit = _cogl_get_texture_unit (layer->unit_index); - cogl_texture_get_gl_texture (tex_handle, &gl_texture, &gl_target); - } - else + /* There may not be enough texture units so we can bail out if + * that's the case... + */ + if (G_UNLIKELY (unit->index >= get_max_activateable_texture_units ())) { - new_gl_layer_info.fallback = TRUE; - gl_target = GL_TEXTURE_2D; - } + static gboolean shown_warning = FALSE; - if (new_gl_layer_info.layer0_overridden) - gl_texture = layer0_override_texture; - else if (new_gl_layer_info.fallback) - { - if (gl_target == GL_TEXTURE_2D) - tex_handle = ctx->default_gl_texture_2d_tex; -#ifdef HAVE_COGL_GL - else if (gl_target == GL_TEXTURE_RECTANGLE_ARB) - tex_handle = ctx->default_gl_texture_rect_tex; -#endif - else + if (!shown_warning) { - g_warning ("We don't have a default texture we can use to fill " - "in for an invalid material layer, since it was " - "using an unsupported texture target "); - /* might get away with this... */ - tex_handle = ctx->default_gl_texture_2d_tex; + g_warning ("Your hardware does not have enough texture units" + "to handle this many texture layers"); + shown_warning = TRUE; } - cogl_texture_get_gl_texture (tex_handle, &gl_texture, NULL); + break; } + /* Bail out as soon as we hit a bit set in the disable mask */ + if (G_UNLIKELY (disable_mask & (1<index))) + break; + + fallback = (fallback_mask & (1<index); + + _cogl_material_layer_get_texture_info (layer, + layer0_override_texture, + fallback, + &texture, + &gl_texture, + &gl_target); + + /* NB: Due to fallbacks texture may not == layer->texture */ + unit->texture = texture; + unit->layer0_overridden = layer0_override_texture ? TRUE : FALSE; + unit->fallback = fallback; + /* FIXME: We could be more clever here and only bind the texture if it is different from gl_layer_info->gl_texture to avoid redundant GL calls. However a few other places in Cogl and @@ -1873,221 +2935,154 @@ _cogl_material_flush_layers_gl_state (CoglMaterial *material, is generated with the same number */ GE (glBindTexture (gl_target, gl_texture)); - /* XXX: Once we add caching for glBindTexture state, these - * checks should be moved back up to the top of the loop! - */ - if (i < ctx->current_layers->len) - { - gl_layer_info = - &g_array_index (ctx->current_layers, CoglLayerInfo, i); - -#ifndef DISABLE_MATERIAL_CACHE - if (gl_layer_info->handle == layer_handle && - !(layer->flags & COGL_MATERIAL_LAYER_FLAG_DIRTY) && - !(gl_layer_info->layer0_overridden || - new_gl_layer_info.layer0_overridden) && - (gl_layer_info->fallback - == new_gl_layer_info.fallback) && - (gl_layer_info->disabled - == new_gl_layer_info.disabled)) - { - continue; - } -#endif - } - - /* Disable the previous target if it was different */ + /* Disable the previous target if it was different and it's + * still enabled */ + if (unit->enabled #ifndef DISABLE_MATERIAL_CACHE - if (gl_layer_info && - gl_layer_info->gl_target != gl_target && - !gl_layer_info->disabled) - { - GE (glDisable (gl_layer_info->gl_target)); - } -#else - if (gl_layer_info) - GE (glDisable (gl_layer_info->gl_target)); + && unit->enabled_gl_target != gl_target #endif + ) + GE (glDisable (unit->enabled_gl_target)); - /* Enable/Disable the new target */ - if (!new_gl_layer_info.disabled) - { + if (!G_UNLIKELY (cogl_debug_flags & COGL_DEBUG_DISABLE_TEXTURING) #ifndef DISABLE_MATERIAL_CACHE - if (!(gl_layer_info && - gl_layer_info->gl_target == gl_target && - !gl_layer_info->disabled)) + && !(unit->enabled && unit->enabled_gl_target == gl_target) #endif - { - /* XXX: Debug: Comment this out to disable all texturing: */ -#if 1 - GE (glEnable (gl_target)); -#endif - } - } - else + ) { -#ifndef DISABLE_MATERIAL_CACHE - if (!(gl_layer_info && - gl_layer_info->gl_target == gl_target && - gl_layer_info->disabled)) -#endif - { - GE (glDisable (gl_target)); - } + GE (glEnable (gl_target)); + unit->enabled = TRUE; + unit->enabled_gl_target = gl_target; } - if (cogl_material_program_is_used ()) - { - cogl_material_program_update (&material->program, - layer, - unit, - gl_layer_info, - gl_target); - } - else + if (unit->layer_differences & COGL_MATERIAL_LAYER_DIFFERENCE_USER_MATRIX || + layer->differences & COGL_MATERIAL_LAYER_DIFFERENCE_USER_MATRIX) { - _cogl_material_layer_flush_gl_sampler_state (layer, - unit, - gl_layer_info); - } - - new_gl_layer_info.handle = layer_handle; - new_gl_layer_info.flags = layer->flags; - new_gl_layer_info.gl_target = gl_target; - new_gl_layer_info.gl_texture = gl_texture; - - if (i < ctx->current_layers->len) - *gl_layer_info = new_gl_layer_info; - else - g_array_append_val (ctx->current_layers, new_gl_layer_info); + if (layer->differences & COGL_MATERIAL_LAYER_DIFFERENCE_USER_MATRIX) + _cogl_matrix_stack_set (unit->matrix_stack, &layer->matrix); + else + _cogl_matrix_stack_load_identity (unit->matrix_stack); - layer->flags &= ~COGL_MATERIAL_LAYER_FLAG_DIRTY; + _cogl_matrix_stack_flush_to_gl (unit->matrix_stack, + COGL_MATRIX_TEXTURE); + } } /* Disable additional texture units that may have previously been in use.. */ - for (; i < ctx->current_layers->len; i++) - { - CoglLayerInfo *gl_layer_info = - &g_array_index (ctx->current_layers, CoglLayerInfo, i); + for (; i < ctx->texture_units->len; i++) + disable_texture_unit (i); -#ifndef DISABLE_MATERIAL_CACHE - if (!gl_layer_info->disabled) -#endif - { - GE (glActiveTexture (GL_TEXTURE0 + i)); - GE (glDisable (gl_layer_info->gl_target)); - gl_layer_info->disabled = TRUE; - } - } + /* There is a convention to always leave texture unit 1 active.. + * (See the end of _cogl_material_flush_gl_state for more + * details) */ + set_active_texture_unit (1); +} + +/* Re-assert the layer's wrap modes on the given CoglTexture. + * + * Note: we don't simply forward the wrap modes to layer->texture + * since the actual texture being used may have been overridden. + */ +static void +_cogl_material_layer_forward_wrap_modes ( + CoglMaterialLayer *layer, + const CoglMaterialWrapModeOverrides *wrap_mode_overrides, + CoglHandle texture) +{ + GLenum wrap_mode_s, wrap_mode_t, wrap_mode_r; + int unit_index = layer->unit_index; - if (cogl_material_program_is_used ()) - cogl_material_program_flush (&material->program); + /* Update the wrap mode on the texture object. The texture backend + should cache the value so that it will be a no-op if the object + already has the same wrap mode set. The backend is best placed to + do this because it knows how many of the coordinates will + actually be used (ie, a 1D texture only cares about the 's' + coordinate but a 3D texture would use all three). GL uses the + wrap mode as part of the texture object state but we are + pretending it's part of the per-layer environment state. This + will break if the application tries to use different modes in + different layers using the same texture. */ -} + if (wrap_mode_overrides && wrap_mode_overrides->values[unit_index].s) + wrap_mode_s = (wrap_mode_overrides->values[unit_index].s == + COGL_MATERIAL_WRAP_MODE_OVERRIDE_REPEAT ? + GL_REPEAT : + wrap_mode_overrides->values[unit_index].s == + COGL_MATERIAL_WRAP_MODE_OVERRIDE_CLAMP_TO_EDGE ? + GL_CLAMP_TO_EDGE : + GL_CLAMP_TO_BORDER); + else if (layer->wrap_mode_s == COGL_MATERIAL_WRAP_MODE_AUTOMATIC) + wrap_mode_s = GL_CLAMP_TO_EDGE; + else + wrap_mode_s = layer->wrap_mode_s; -#ifndef HAVE_COGL_GLES + if (wrap_mode_overrides && wrap_mode_overrides->values[unit_index].t) + wrap_mode_t = (wrap_mode_overrides->values[unit_index].t == + COGL_MATERIAL_WRAP_MODE_OVERRIDE_REPEAT ? + GL_REPEAT : + wrap_mode_overrides->values[unit_index].t == + COGL_MATERIAL_WRAP_MODE_OVERRIDE_CLAMP_TO_EDGE ? + GL_CLAMP_TO_EDGE : + GL_CLAMP_TO_BORDER); + else if (layer->wrap_mode_t == COGL_MATERIAL_WRAP_MODE_AUTOMATIC) + wrap_mode_t = GL_CLAMP_TO_EDGE; + else + wrap_mode_t = layer->wrap_mode_t; -static gboolean -blend_factor_uses_constant (GLenum blend_factor) -{ - return (blend_factor == GL_CONSTANT_COLOR || - blend_factor == GL_ONE_MINUS_CONSTANT_COLOR || - blend_factor == GL_CONSTANT_ALPHA || - blend_factor == GL_ONE_MINUS_CONSTANT_ALPHA); -} + if (wrap_mode_overrides && wrap_mode_overrides->values[unit_index].r) + wrap_mode_r = (wrap_mode_overrides->values[unit_index].r == + COGL_MATERIAL_WRAP_MODE_OVERRIDE_REPEAT ? + GL_REPEAT : + wrap_mode_overrides->values[unit_index].r == + COGL_MATERIAL_WRAP_MODE_OVERRIDE_CLAMP_TO_EDGE ? + GL_CLAMP_TO_EDGE : + GL_CLAMP_TO_BORDER); + else if (layer->wrap_mode_r == COGL_MATERIAL_WRAP_MODE_AUTOMATIC) + wrap_mode_r = GL_CLAMP_TO_EDGE; + else + wrap_mode_r = layer->wrap_mode_r; -#endif + _cogl_texture_set_wrap_mode_parameters (texture, + wrap_mode_s, + wrap_mode_t, + wrap_mode_r); +} +/* OpenGL associates the min/mag filters and repeat modes with the + * texture object not the texture unit so we always have to re-assert + * the filter and repeat modes whenever we use a texture since it may + * be referenced by multiple materials with different modes. + * + * XXX: GL_ARB_sampler_objects fixes this in OpenGL so we should + * eventually look at using this extension when available. + */ static void -_cogl_material_flush_base_gl_state (CoglMaterial *material, - gboolean skip_gl_color) +foreach_texture_unit_update_filter_and_wrap_modes ( + const CoglMaterialWrapModeOverrides *wrap_mode_overrides) { - _COGL_GET_CONTEXT (ctx, NO_RETVAL); + int i; - /* XXX: - * Currently we only don't update state when the flags indicate that the - * current material uses the defaults, and the new material also uses the - * defaults, but we could do deeper comparisons of state. */ + _COGL_GET_CONTEXT (ctx, NO_RETVAL); - if (!skip_gl_color) + for (i = 0; i < ctx->texture_units->len; i++) { - if (!(ctx->current_material_flags & COGL_MATERIAL_FLAG_DEFAULT_COLOR - && material->flags & COGL_MATERIAL_FLAG_DEFAULT_COLOR) || - /* Assume if we were previously told to skip the color, then - * the current color needs updating... */ - ctx->current_material_flush_options.flags & - COGL_MATERIAL_FLUSH_SKIP_GL_COLOR) + CoglTextureUnit *unit = + &g_array_index (ctx->texture_units, CoglTextureUnit, i); + + if (unit->enabled) { - GE (glColor4ub (material->unlit[0], - material->unlit[1], - material->unlit[2], - material->unlit[3])); + /* NB: we can't just look at unit->layer->texture because + * _cogl_material_flush_gl_state may have chosen to flush a + * different texture due to fallbacks. */ + _cogl_texture_set_filters (unit->texture, + unit->layer->min_filter, + unit->layer->mag_filter); + + _cogl_material_layer_forward_wrap_modes (unit->layer, + wrap_mode_overrides, + unit->texture); } } - - if (!(ctx->current_material_flags & COGL_MATERIAL_FLAG_DEFAULT_GL_MATERIAL - && material->flags & COGL_MATERIAL_FLAG_DEFAULT_GL_MATERIAL)) - { - /* FIXME - we only need to set these if lighting is enabled... */ - 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)); - } - - if (!(ctx->current_material_flags & COGL_MATERIAL_FLAG_DEFAULT_ALPHA_FUNC - && material->flags & COGL_MATERIAL_FLAG_DEFAULT_ALPHA_FUNC)) - { - /* NB: Currently the Cogl defines are compatible with the GL ones: */ - GE (glAlphaFunc (material->alpha_func, material->alpha_func_reference)); - } - - if (!(ctx->current_material_flags & COGL_MATERIAL_FLAG_DEFAULT_BLEND - && material->flags & COGL_MATERIAL_FLAG_DEFAULT_BLEND)) - { -#if defined (HAVE_COGL_GLES2) - gboolean have_blend_equation_seperate = TRUE; - gboolean have_blend_func_separate = TRUE; -#elif defined (HAVE_COGL_GL) - gboolean have_blend_equation_seperate = FALSE; - gboolean have_blend_func_separate = FALSE; - if (ctx->drv.pf_glBlendEquationSeparate) /* Only GL 2.0 + */ - have_blend_equation_seperate = TRUE; - if (ctx->drv.pf_glBlendFuncSeparate) /* Only GL 1.4 + */ - have_blend_func_separate = TRUE; -#endif - -#ifndef HAVE_COGL_GLES /* GLES 1 only has glBlendFunc */ - if (have_blend_equation_seperate && - material->blend_equation_rgb != material->blend_equation_alpha) - GE (glBlendEquationSeparate (material->blend_equation_rgb, - material->blend_equation_alpha)); - else - GE (glBlendEquation (material->blend_equation_rgb)); - - if (blend_factor_uses_constant (material->blend_src_factor_rgb) || - blend_factor_uses_constant (material->blend_src_factor_alpha) || - blend_factor_uses_constant (material->blend_dst_factor_rgb) || - blend_factor_uses_constant (material->blend_dst_factor_alpha)) - GE (glBlendColor (material->blend_constant[0], - material->blend_constant[1], - material->blend_constant[2], - material->blend_constant[3])); - - if (have_blend_func_separate && - (material->blend_src_factor_rgb != material->blend_src_factor_alpha || - (material->blend_src_factor_rgb != - material->blend_src_factor_alpha))) - GE (glBlendFuncSeparate (material->blend_src_factor_rgb, - material->blend_dst_factor_rgb, - material->blend_src_factor_alpha, - material->blend_dst_factor_alpha)); - else -#endif - GE (glBlendFunc (material->blend_src_factor_rgb, - material->blend_dst_factor_rgb)); - } } void @@ -2095,11 +3090,14 @@ _cogl_material_flush_gl_state (CoglHandle handle, CoglMaterialFlushOptions *options) { CoglMaterial *material; - guint32 fallback_layers = 0; - guint32 disable_layers = 0; + guint32 fallback_layers = 0; + guint32 disable_layers = 0; GLuint layer0_override_texture = 0; - gboolean skip_gl_color = FALSE; - const CoglMaterialWrapModeOverrides *wrap_mode_overrides = NULL; + gboolean skip_gl_color = FALSE; + const CoglMaterialWrapModeOverrides *wrap_mode_overrides = NULL; + int i; + CoglTextureUnit *unit1; + GList *tmp; _COGL_GET_CONTEXT (ctx, NO_RETVAL); @@ -2119,30 +3117,182 @@ _cogl_material_flush_gl_state (CoglHandle handle, wrap_mode_overrides = &options->wrap_mode_overrides; } - _cogl_material_flush_base_gl_state (material, - skip_gl_color); - - _cogl_material_flush_layers_gl_state (material, + /* If the material we are flushing and the override options are the + * same then try to bail out as quickly as possible. + * + * XXX: the more overrides we add the slower "quickly" will get; I + * think we need to move towards cheap copy-on-write materials so + * that exceptional fallbacks/overrides can be implemented simply by + * copying a material and modifying it before flushing. + */ + if (ctx->current_material == material && + ctx->current_material_fallback_layers == fallback_layers && + ctx->current_material_disable_layers == disable_layers && + ctx->current_material_layer0_override == layer0_override_texture && + ctx->current_material_skip_gl_color == skip_gl_color) + goto done; + + /* First flush everything that's the same regardless of which + * material backend is being used... + * + * 1) top level state: + * glColor (or skip if a vertex attribute is being used for color) + * blend state + * alpha test state (except for GLES 2.0) + * + * 2) then foreach layer: + * determine gl_target/gl_texture + * bind texture + * enable/disable target + * flush user matrix + * + * Note: After _cogl_material_flush_common_gl_state you can expect + * all state of the layers corresponding texture unit to be + * updated. + */ + _cogl_material_flush_common_gl_state (material, + skip_gl_color, fallback_layers, disable_layers, layer0_override_texture, wrap_mode_overrides); - /* NB: we have to take a reference so that next time - * cogl_material_flush_gl_state is called, we can compare the incomming - * material pointer with ctx->current_material + /* Now flush the fragment processing state according to the current + * fragment processing backend. + * + * Note: Some of the backends may not support the current material + * configuration and in that case it will report an error and we + * will fallback to a different backend. + * + * NB: if material->backend != COGL_MATERIAL_BACKEND_UNDEFINED then + * we have previously managed to successfully flush this material + * with the given backend so we will simply use that to avoid + * fallback code paths. */ - cogl_handle_ref (handle); - if (ctx->current_material) - cogl_handle_unref (ctx->current_material); + if (material->backend == COGL_MATERIAL_BACKEND_UNDEFINED) + _cogl_material_set_backend (material, COGL_MATERIAL_BACKEND_DEFAULT); + + for (i = material->backend; + i < G_N_ELEMENTS (backends); + i++, _cogl_material_set_backend (material, i)) + { + const GList *l; + const CoglMaterialBackend *backend = backends[i]; + gboolean added_layer = FALSE; + gboolean error_adding_layer = FALSE; + + /* E.g. For backends generating code they can setup their + * scratch buffers here... */ + if (G_UNLIKELY (!backend->start (material))) + continue; + + for (l = cogl_material_get_layers (material); l; l = l->next) + { + CoglMaterialLayer *layer = l->data; + CoglTextureUnit *unit = _cogl_get_texture_unit (layer->unit_index); + + /* NB: We don't support the random disabling of texture + * units, so as soon as we hit a disabled unit we know all + * subsequent units are also disabled */ + if (!unit->enabled) + break; + + if (G_UNLIKELY (layer->unit_index >= + backend->get_max_texture_units ())) + { + int j; + for (j = layer->unit_index; j < ctx->texture_units->len; j++) + disable_texture_unit (j); + /* TODO: although this isn't considered an error that + * warrants falling back to a different backend we + * should print a warning here. */ + break; + } + + /* Either generate per layer code snippets or setup the + * fixed function glTexEnv for each layer... */ + if (G_LIKELY (backend->add_layer (layer))) + added_layer = TRUE; + else + { + error_adding_layer = TRUE; + break; + } + } + + if (G_UNLIKELY (error_adding_layer)) + continue; + + if (!added_layer && + backend->passthrough && + G_UNLIKELY (!backend->passthrough (material))) + continue; + + /* For backends generating code they may compile and link their + * programs here, update any uniforms and tell OpenGL to use + * that program. + */ + if (G_UNLIKELY (!backend->end (material))) + continue; + + break; + } + + for (tmp = material->layers; tmp != NULL; tmp = tmp->next) + { + CoglMaterialLayer *layer = tmp->data; + CoglTextureUnit *unit = _cogl_get_texture_unit (layer->unit_index); + + unit->layer = layer; + unit->layer_differences = layer->differences; + } + + /* NB: _cogl_material_pre_change_notify and _cogl_material_free will + * invalidate ctx->current_material (set it to COGL_INVALID_HANDLE) + * if the material is changed/freed. + */ ctx->current_material = handle; ctx->current_material_flags = material->flags; - if (options) - ctx->current_material_flush_options = *options; - else - memset (&ctx->current_material_flush_options, - 0, sizeof (CoglMaterialFlushOptions)); + ctx->current_material_fallback_layers = fallback_layers; + ctx->current_material_disable_layers = disable_layers; + ctx->current_material_layer0_override = layer0_override_texture; + ctx->current_material_skip_gl_color = skip_gl_color; + +done: /* well, almost... */ + + /* Handle the fact that OpenGL associates texture filter and wrap + * modes with the texture objects not the texture units... */ + foreach_texture_unit_update_filter_and_wrap_modes (wrap_mode_overrides); + + /* If this material has more than one layer then we always need + * to make sure we rebind the texture for unit 1. + * + * NB: various components of Cogl may temporarily bind arbitrary + * textures to the current texture unit so they can query and modify + * texture object parameters. cogl-material.c will always leave + * texture unit 1 active so we can ignore these temporary binds + * unless multitexturing is being used. + */ + unit1 = _cogl_get_texture_unit (1); + if (unit1->enabled) + { + set_active_texture_unit (1); + GE (glBindTexture (unit1->enabled_gl_target, unit1->gl_texture)); + } + + /* Since there are several places where Cogl will temporarily bind a + * GL texture so that it can query or modify texture objects we want + * to make sure we know which texture unit state is being changed by + * such code. + * + * We choose to always end up with texture unit 1 active so that in + * the common case where multitexturing isn't used we can simply + * ignore the state of this texture unit. Notably we didn't use a + * large texture unit (.e.g. (GL_MAX_TEXTURE_UNITS - 1) in case the + * driver doesn't have a sparse data structure for texture units. + */ + set_active_texture_unit (1); } static gboolean @@ -2177,15 +3327,17 @@ _cogl_material_layer_equal (CoglMaterialLayer *material0_layer, material1_layer_texture)) return FALSE; - if ((material0_layer->flags & COGL_MATERIAL_LAYER_FLAG_DEFAULT_COMBINE) != - (material1_layer->flags & COGL_MATERIAL_LAYER_FLAG_DEFAULT_COMBINE)) + if ((material0_layer->differences & + COGL_MATERIAL_LAYER_DIFFERENCE_COMBINE) != + (material1_layer->differences & + COGL_MATERIAL_LAYER_DIFFERENCE_COMBINE)) return FALSE; #if 0 /* TODO */ if (!_deep_are_layer_combines_equal ()) return FALSE; #else - if (!(material0_layer->flags & COGL_MATERIAL_LAYER_FLAG_DEFAULT_COMBINE)) + if (!(material0_layer->differences & COGL_MATERIAL_LAYER_DIFFERENCE_COMBINE)) return FALSE; #endif @@ -2488,10 +3640,16 @@ cogl_material_set_layer_filters (CoglHandle handle, layer = _cogl_material_get_layer (material, layer_index, TRUE); /* possibly flush primitives referencing the current state... */ - _cogl_material_pre_change_notify (material, FALSE, NULL); + _cogl_material_layer_pre_change_notify ( + layer, + COGL_MATERIAL_LAYER_CHANGE_FILTERS); layer->min_filter = min_filter; layer->mag_filter = mag_filter; + + /* Note we don't have a layer->difference flag for the min/mag + * filters since in GL terms this state is owned by the texture + * object so they are dealt with slightly differently. */ } void @@ -2597,3 +3755,21 @@ _cogl_material_layer_get_wrap_mode_r (CoglHandle handle) return _cogl_material_layer_pointer_from_handle (handle)->wrap_mode_r; } + +void +_cogl_material_apply_legacy_state (CoglHandle handle) +{ + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + + if (ctx->current_program) + { + /* It was a mistake that we ever copied the OpenGL style API for + * making a program current (cogl_program_use) on the context. + * Until cogl_program_use is removed we will transparently set + * the program on the material because the cogl-material code is + * in the best position to juggle the corresponding GL state. */ + _cogl_material_set_user_program (handle, + ctx->current_program); + } +} + diff --git a/clutter/cogl/cogl/cogl-path.c b/clutter/cogl/cogl/cogl-path.c index 9c1475a..606dfe5 100644 --- a/clutter/cogl/cogl/cogl-path.c +++ b/clutter/cogl/cogl/cogl-path.c @@ -132,6 +132,7 @@ _cogl_path_stroke_nodes (void) unsigned long enable_flags = COGL_ENABLE_VERTEX_ARRAY; CoglPathData *data; CoglMaterialFlushOptions options; + CoglHandle source; _COGL_GET_CONTEXT (ctx, NO_RETVAL); @@ -151,7 +152,15 @@ _cogl_path_stroke_nodes (void) /* disable all texture layers */ options.disable_layers = (guint32)~0; - _cogl_material_flush_gl_state (ctx->source_material, &options); + if (G_UNLIKELY (ctx->legacy_state_set)) + { + source = cogl_material_copy (ctx->source_material); + _cogl_material_apply_legacy_state (source); + } + else + source = ctx->source_material; + + _cogl_material_flush_gl_state (source, &options); while (path_start < data->path_nodes->len) { @@ -163,6 +172,9 @@ _cogl_path_stroke_nodes (void) path_start += node->path_size; } + + if (G_UNLIKELY (source != ctx->source_material)) + cogl_handle_unref (source); } static void @@ -332,6 +344,8 @@ _cogl_path_fill_nodes_scanlines (CoglPathNode *path, unsigned int bounds_w, unsigned int bounds_h) { + CoglHandle source; + /* This is our edge list it stores intersections between our * curve and scanlines, it should probably be implemented with a * data structure that has smaller overhead for inserting the @@ -359,6 +373,14 @@ _cogl_path_fill_nodes_scanlines (CoglPathNode *path, * always be done first when preparing to draw. */ _cogl_framebuffer_flush_state (_cogl_get_framebuffer (), 0); + if (G_UNLIKELY (ctx->legacy_state_set)) + { + source = cogl_material_copy (ctx->source_material); + _cogl_material_apply_legacy_state (source); + } + else + source = ctx->source_material; + _cogl_material_flush_gl_state (ctx->source_material, NULL); _cogl_enable (COGL_ENABLE_VERTEX_ARRAY @@ -502,6 +524,9 @@ _cogl_path_fill_nodes_scanlines (CoglPathNode *path, GE (glDrawArrays (GL_TRIANGLES, 0, spans * 2 * 3)); g_free (coords); } + + if (G_UNLIKELY (source != ctx->source_material)) + cogl_handle_unref (source); } static void diff --git a/clutter/cogl/cogl/cogl-pixel-buffer.c b/clutter/cogl/cogl/cogl-pixel-buffer.c index a819f1e..2ad748d 100644 --- a/clutter/cogl/cogl/cogl-pixel-buffer.c +++ b/clutter/cogl/cogl/cogl-pixel-buffer.c @@ -59,8 +59,6 @@ #define glDeleteBuffers ctx->drv.pf_glDeleteBuffers #define glMapBuffer ctx->drv.pf_glMapBuffer #define glUnmapBuffer ctx->drv.pf_glUnmapBuffer -#define glActiveTexture ctx->drv.pf_glActiveTexture -#define glClientActiveTexture ctx->drv.pf_glClientActiveTexture #ifndef GL_PIXEL_UNPACK_BUFFER #define GL_PIXEL_UNPACK_BUFFER GL_PIXEL_UNPACK_BUFFER_ARB diff --git a/clutter/cogl/cogl/cogl-primitives.c b/clutter/cogl/cogl/cogl-primitives.c index 03caecb..84a4985 100644 --- a/clutter/cogl/cogl/cogl-primitives.c +++ b/clutter/cogl/cogl/cogl-primitives.c @@ -516,7 +516,6 @@ _cogl_rectangles_with_multitexture_coords ( { CoglHandle layer = tmp->data; CoglHandle tex_handle; - unsigned long flags; if (cogl_material_layer_get_type (layer) != COGL_MATERIAL_LAYER_TYPE_TEXTURE) @@ -619,8 +618,7 @@ _cogl_rectangles_with_multitexture_coords ( * waste or if using GL_TEXTURE_RECTANGLE_ARB) then we don't support * multi texturing since we don't know if the result will end up trying * to texture from the waste area. */ - flags = _cogl_material_layer_get_flags (layer); - if (flags & COGL_MATERIAL_LAYER_FLAG_HAS_USER_MATRIX + if (_cogl_material_layer_has_user_matrix (layer) && !_cogl_texture_can_hardware_repeat (tex_handle)) { static gboolean warning_seen = FALSE; @@ -838,6 +836,7 @@ draw_polygon_sub_texture_cb (CoglHandle tex_handle, float virtual_origin_y; float v_to_s_scale_x; float v_to_s_scale_y; + CoglHandle source; _COGL_GET_CONTEXT (ctx, NO_RETVAL); @@ -888,9 +887,20 @@ draw_polygon_sub_texture_cb (CoglHandle tex_handle, options.wrap_mode_overrides.values[0].t = COGL_MATERIAL_WRAP_MODE_OVERRIDE_CLAMP_TO_BORDER; - _cogl_material_flush_gl_state (ctx->source_material, &options); + if (G_UNLIKELY (ctx->legacy_state_set)) + { + source = cogl_material_copy (ctx->source_material); + _cogl_material_apply_legacy_state (source); + } + else + source = ctx->source_material; + + _cogl_material_flush_gl_state (source, &options); GE (glDrawArrays (GL_TRIANGLE_FAN, 0, state->n_vertices)); + + if (G_UNLIKELY (source != ctx->source_material)) + cogl_handle_unref (source); } /* handles 2d-sliced textures with > 1 slice */ @@ -963,6 +973,7 @@ _cogl_multitexture_polygon_single_primitive (const CoglTextureVertex *vertices, GList *tmp; GLfloat *v; CoglMaterialFlushOptions options; + CoglHandle source; _COGL_GET_CONTEXT (ctx, NO_RETVAL); @@ -1028,9 +1039,21 @@ _cogl_multitexture_polygon_single_primitive (const CoglTextureVertex *vertices, options.flags |= COGL_MATERIAL_FLUSH_WRAP_MODE_OVERRIDES; options.wrap_mode_overrides = *wrap_mode_overrides; } - _cogl_material_flush_gl_state (ctx->source_material, &options); + + if (G_UNLIKELY (ctx->legacy_state_set)) + { + source = cogl_material_copy (ctx->source_material); + _cogl_material_apply_legacy_state (source); + } + else + source = ctx->source_material; + + _cogl_material_flush_gl_state (source, &options); GE (glDrawArrays (GL_TRIANGLE_FAN, 0, n_vertices)); + + if (G_UNLIKELY (source != ctx->source_material)) + cogl_handle_unref (source); } void diff --git a/clutter/cogl/cogl/cogl-vertex-buffer.c b/clutter/cogl/cogl/cogl-vertex-buffer.c index 0684cba..0c9b756 100644 --- a/clutter/cogl/cogl/cogl-vertex-buffer.c +++ b/clutter/cogl/cogl/cogl-vertex-buffer.c @@ -159,7 +159,6 @@ #define glDeleteBuffers ctx->drv.pf_glDeleteBuffers #define glMapBuffer ctx->drv.pf_glMapBuffer #define glUnmapBuffer ctx->drv.pf_glUnmapBuffer -#define glActiveTexture ctx->drv.pf_glActiveTexture #define glClientActiveTexture ctx->drv.pf_glClientActiveTexture #ifndef GL_ARRAY_BUFFER #define GL_ARRAY_BUFFER GL_ARRAY_BUFFER_ARB @@ -1509,7 +1508,7 @@ get_gl_type_from_attribute_flags (CoglVertexBufferAttribFlags flags) } } -static void +static CoglHandle enable_state_for_drawing_buffer (CoglVertexBuffer *buffer) { GList *tmp; @@ -1522,8 +1521,9 @@ enable_state_for_drawing_buffer (CoglVertexBuffer *buffer) guint32 fallback_layers = 0; int i; CoglMaterialFlushOptions options; + CoglHandle source; - _COGL_GET_CONTEXT (ctx, NO_RETVAL); + _COGL_GET_CONTEXT (ctx, COGL_INVALID_HANDLE); if (buffer->new_attributes) cogl_vertex_buffer_submit_real (buffer); @@ -1717,18 +1717,29 @@ enable_state_for_drawing_buffer (CoglVertexBuffer *buffer) options.fallback_layers = fallback_layers; - _cogl_material_flush_gl_state (ctx->source_material, &options); - enable_flags |= _cogl_material_get_cogl_enable_flags (ctx->source_material); + if (G_UNLIKELY (ctx->legacy_state_set)) + { + source = cogl_material_copy (ctx->source_material); + _cogl_material_apply_legacy_state (source); + } + else + source = ctx->source_material; + + _cogl_material_flush_gl_state (source, &options); + enable_flags |= _cogl_material_get_cogl_enable_flags (source); if (ctx->enable_backface_culling) enable_flags |= COGL_ENABLE_BACKFACE_CULLING; _cogl_enable (enable_flags); _cogl_flush_face_winding (); + + return source; } static void -disable_state_for_drawing_buffer (CoglVertexBuffer *buffer) +disable_state_for_drawing_buffer (CoglVertexBuffer *buffer, + CoglHandle source) { GList *tmp; GLenum gl_type; @@ -1738,6 +1749,9 @@ disable_state_for_drawing_buffer (CoglVertexBuffer *buffer) _COGL_GET_CONTEXT (ctx, NO_RETVAL); + if (G_UNLIKELY (source != ctx->source_material)) + cogl_handle_unref (source); + /* Disable all the client state that cogl doesn't currently know * about: */ @@ -1798,6 +1812,7 @@ cogl_vertex_buffer_draw (CoglHandle handle, int count) { CoglVertexBuffer *buffer; + CoglHandle source; if (!cogl_is_vertex_buffer (handle)) return; @@ -1806,11 +1821,11 @@ cogl_vertex_buffer_draw (CoglHandle handle, buffer = _cogl_vertex_buffer_pointer_from_handle (handle); - enable_state_for_drawing_buffer (buffer); + source = enable_state_for_drawing_buffer (buffer); GE (glDrawArrays (mode, first, count)); - disable_state_for_drawing_buffer (buffer); + disable_state_for_drawing_buffer (buffer, source); } static int @@ -1934,6 +1949,7 @@ cogl_vertex_buffer_draw_elements (CoglHandle handle, (cogl_get_features () & COGL_FEATURE_VBOS) ? FALSE : TRUE; gsize byte_offset; CoglVertexBufferIndices *indices = NULL; + CoglHandle source; _COGL_GET_CONTEXT (ctx, NO_RETVAL); @@ -1949,7 +1965,7 @@ cogl_vertex_buffer_draw_elements (CoglHandle handle, indices = _cogl_vertex_buffer_indices_pointer_from_handle (indices_handle); - enable_state_for_drawing_buffer (buffer); + source = enable_state_for_drawing_buffer (buffer); byte_offset = indices_offset * get_indices_type_size (indices->type); if (fallback) @@ -1961,7 +1977,7 @@ cogl_vertex_buffer_draw_elements (CoglHandle handle, GE (glDrawRangeElements (mode, min_index, max_index, count, indices->type, (void *)byte_offset)); - disable_state_for_drawing_buffer (buffer); + disable_state_for_drawing_buffer (buffer, source); GE (glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0)); } diff --git a/clutter/cogl/cogl/cogl.c b/clutter/cogl/cogl/cogl.c index b6331fd..12ba17b 100644 --- a/clutter/cogl/cogl/cogl.c +++ b/clutter/cogl/cogl/cogl.c @@ -493,6 +493,9 @@ cogl_get_features (void) if (G_UNLIKELY (cogl_debug_flags & COGL_DEBUG_DISABLE_VBOS)) ctx->feature_flags &= ~COGL_FEATURE_VBOS; + if (G_UNLIKELY (cogl_debug_flags & COGL_DEBUG_DISABLE_GLSL)) + ctx->feature_flags &= ~COGL_FEATURE_SHADERS_GLSL; + return ctx->feature_flags; } @@ -515,6 +518,9 @@ _cogl_features_available_private (CoglFeatureFlagsPrivate features) if (!ctx->features_cached) _cogl_features_init (); + if (G_UNLIKELY (cogl_debug_flags & COGL_DEBUG_DISABLE_ARBFP)) + ctx->feature_flags_private &= ~COGL_FEATURE_PRIVATE_ARB_FP; + return (ctx->feature_flags_private & features) == features; } @@ -571,6 +577,8 @@ cogl_set_fog (const CoglColor *fog_color, GLfloat fogColor[4]; GLenum gl_mode = GL_LINEAR; + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + /* The cogl journal doesn't currently track fog state changes */ _cogl_journal_flush (); @@ -606,15 +614,20 @@ cogl_set_fog (const CoglColor *fog_color, glFogf (GL_FOG_DENSITY, (GLfloat) density); glFogf (GL_FOG_START, (GLfloat) z_near); glFogf (GL_FOG_END, (GLfloat) z_far); + + ctx->fog_enabled = TRUE; } void cogl_disable_fog (void) { + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + /* Currently the journal can't track changes to fog state... */ _cogl_journal_flush (); glDisable (GL_FOG); + ctx->fog_enabled = FALSE; } void @@ -876,102 +889,6 @@ cogl_end_gl (void) ctx->in_begin_gl_block = FALSE; } -static CoglTextureUnit * -_cogl_texture_unit_new (int index_) -{ - CoglTextureUnit *unit = g_new0 (CoglTextureUnit, 1); - unit->matrix_stack = _cogl_matrix_stack_new (); - unit->index = index_; - return unit; -} - -static void -_cogl_texture_unit_free (CoglTextureUnit *unit) -{ - _cogl_matrix_stack_destroy (unit->matrix_stack); - g_free (unit); -} - -CoglTextureUnit * -_cogl_get_texture_unit (int index_) -{ - GList *l; - CoglTextureUnit *unit; - - _COGL_GET_CONTEXT (ctx, NULL); - - for (l = ctx->texture_units; l; l = l->next) - { - unit = l->data; - - if (unit->index == index_) - return unit; - - /* The units are always sorted, so at this point we know this unit - * doesn't exist */ - if (unit->index > index_) - break; - } - /* NB: if we now insert a new layer before l, that will maintain order. - */ - - unit = _cogl_texture_unit_new (index_); - - /* Note: see comment after for() loop above */ - ctx->texture_units = - g_list_insert_before (ctx->texture_units, l, unit); - - return unit; -} - -void -_cogl_destroy_texture_units (void) -{ - GList *l; - - _COGL_GET_CONTEXT (ctx, NO_RETVAL); - - for (l = ctx->texture_units; l; l = l->next) - _cogl_texture_unit_free (l->data); - g_list_free (ctx->texture_units); -} - -/* - * This is more complicated than that, another pass needs to be done when - * cogl have a neat way of saying if we are using the fixed function pipeline - * or not (for the GL case): - * MAX_TEXTURE_UNITS: fixed function pipeline, a texture unit has both a - * sampler and a set of texture coordinates - * MAX_TEXTURE_IMAGE_UNITS: number of samplers one can use from a fragment - * program/shader (ARBfp1.0 asm/GLSL) - * MAX_VERTEX_TEXTURE_UNITS: number of samplers one can use from a vertex - * program/shader (can be 0) - * MAX_COMBINED_TEXTURE_IMAGE_UNITS: Maximum samplers one can use, counting both - * the vertex and fragment shaders - * - * If both the vertex shader and the fragment processing stage access the same - * texture image unit, then that counts as using two texture image units - * against the latter limit: http://www.opengl.org/sdk/docs/man/xhtml/glGet.xml - * - * Note that, for now, we use GL_MAX_TEXTURE_UNITS as we are exposing the - * fixed function pipeline. - */ -unsigned int -_cogl_get_max_texture_image_units (void) -{ - _COGL_GET_CONTEXT (ctx, 0); - - /* This function is called quite often so we cache the value to - avoid too many GL calls */ - if (ctx->max_texture_units == -1) - { - ctx->max_texture_units = 1; - GE( glGetIntegerv (GL_MAX_TEXTURE_UNITS, &ctx->max_texture_units) ); - } - - return ctx->max_texture_units; -} - void cogl_push_matrix (void) { diff --git a/clutter/cogl/cogl/driver/gl/cogl-context-driver.h b/clutter/cogl/cogl/driver/gl/cogl-context-driver.h index 7ae8385..13cffe3 100644 --- a/clutter/cogl/cogl/driver/gl/cogl-context-driver.h +++ b/clutter/cogl/cogl/driver/gl/cogl-context-driver.h @@ -41,6 +41,8 @@ typedef struct _CoglContextDriver { /* This defines a list of function pointers */ #include "cogl-feature-functions.h" + + GLint gl_max_program_temoraries_arb; } CoglContextDriver; #undef COGL_FEATURE_BEGIN diff --git a/clutter/cogl/cogl/driver/gl/cogl-feature-functions.h b/clutter/cogl/cogl/driver/gl/cogl-feature-functions.h index cf4a8f3..d26c647 100644 --- a/clutter/cogl/cogl/driver/gl/cogl-feature-functions.h +++ b/clutter/cogl/cogl/driver/gl/cogl-feature-functions.h @@ -119,6 +119,9 @@ COGL_FEATURE_BEGIN (arbfp, 255, 255, COGL_FEATURE_FUNCTION (void, glGenPrograms, (GLsizei n, GLuint *programs)) +COGL_FEATURE_FUNCTION (void, glDeletePrograms, + (GLsizei n, + GLuint *programs)) COGL_FEATURE_FUNCTION (void, glBindProgram, (GLenum target, GLuint program)) @@ -127,47 +130,51 @@ COGL_FEATURE_FUNCTION (void, glProgramString, GLenum format, GLsizei len, const void *program)) +COGL_FEATURE_FUNCTION (void, glProgramLocalParameter4fv, + (GLenum target, + GLuint index, + GLfloat *params)) COGL_FEATURE_END () /* The function names in OpenGL 2.0 are different so we can't easily just check for GL 2.0 */ -COGL_FEATURE_BEGIN (shaders_glsl, 255, 255, - "ARB\0", - "shader_objects\0" - "vertex_shader\0" - "fragment_shader\0", +COGL_FEATURE_BEGIN (shaders_glsl, 2, 0, + "\0", + "\0", COGL_FEATURE_SHADERS_GLSL, 0) -COGL_FEATURE_FUNCTION (GLhandleARB, glCreateProgramObject, +COGL_FEATURE_FUNCTION (GLuint, glCreateProgram, (void)) -COGL_FEATURE_FUNCTION (GLhandleARB, glCreateShaderObject, +COGL_FEATURE_FUNCTION (GLuint, glCreateShader, (GLenum shaderType)) COGL_FEATURE_FUNCTION (void, glShaderSource, - (GLhandleARB shaderObj, + (GLuint shader, GLsizei count, - const GLcharARB* *string, + const GLchar **string, const GLint *length)) COGL_FEATURE_FUNCTION (void, glCompileShader, - (GLhandleARB shaderObj)) -COGL_FEATURE_FUNCTION (void, glAttachObject, - (GLhandleARB containerObj, - GLhandleARB obj)) + (GLuint shader)) +COGL_FEATURE_FUNCTION (void, glDeleteShader, + (GLuint shader)) +COGL_FEATURE_FUNCTION (void, glAttachShader, + (GLuint program, + GLuint shader)) COGL_FEATURE_FUNCTION (void, glLinkProgram, - (GLhandleARB programObj)) -COGL_FEATURE_FUNCTION (void, glUseProgramObject, - (GLhandleARB programObj)) + (GLuint program)) +COGL_FEATURE_FUNCTION (void, glUseProgram, + (GLuint program)) COGL_FEATURE_FUNCTION (GLint, glGetUniformLocation, - (GLhandleARB programObj, - const GLcharARB *name)) -COGL_FEATURE_FUNCTION (void, glDeleteObject, - (GLhandleARB obj)) -COGL_FEATURE_FUNCTION (void, glGetInfoLog, - (GLhandleARB obj, + (GLuint program, + const GLchar *name)) +COGL_FEATURE_FUNCTION (void, glDeleteProgram, + (GLuint program)) +COGL_FEATURE_FUNCTION (void, glGetShaderInfoLog, + (GLuint shader, GLsizei maxLength, GLsizei *length, - GLcharARB *infoLog)) -COGL_FEATURE_FUNCTION (void, glGetObjectParameteriv, - (GLhandleARB obj, + GLchar *infoLog)) +COGL_FEATURE_FUNCTION (void, glGetShaderiv, + (GLuint shader, GLenum pname, GLint *params)) diff --git a/clutter/cogl/cogl/driver/gl/cogl-program.c b/clutter/cogl/cogl/driver/gl/cogl-program.c index 33691c9..84dd604 100644 --- a/clutter/cogl/cogl/driver/gl/cogl-program.c +++ b/clutter/cogl/cogl/driver/gl/cogl-program.c @@ -3,7 +3,7 @@ * * An object oriented GL/GLES Abstraction/Utility Layer * - * Copyright (C) 2008,2009 Intel Corporation. + * Copyright (C) 2008,2009,2010 Intel Corporation. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -35,9 +35,9 @@ #include -#define glCreateProgramObject ctx->drv.pf_glCreateProgramObject -#define glAttachObject ctx->drv.pf_glAttachObject -#define glUseProgramObject ctx->drv.pf_glUseProgramObject +#define glCreateProgram ctx->drv.pf_glCreateProgram +#define glAttachShader ctx->drv.pf_glAttachShader +#define glUseProgram ctx->drv.pf_glUseProgram #define glLinkProgram ctx->drv.pf_glLinkProgram #define glGetUniformLocation ctx->drv.pf_glGetUniformLocation #define glUniform1f ctx->drv.pf_glUniform1f @@ -59,7 +59,7 @@ #define glUniformMatrix2fv ctx->drv.pf_glUniformMatrix2fv #define glUniformMatrix3fv ctx->drv.pf_glUniformMatrix3fv #define glUniformMatrix4fv ctx->drv.pf_glUniformMatrix4fv -#define glDeleteObject ctx->drv.pf_glDeleteObject +#define glDeleteProgram ctx->drv.pf_glDeleteProgram static void _cogl_program_free (CoglProgram *program); @@ -71,7 +71,7 @@ _cogl_program_free (CoglProgram *program) /* Frees program resources but its handle is not released! Do that separately before this! */ _COGL_GET_CONTEXT (ctx, NO_RETVAL); - glDeleteObject (program->gl_handle); + GE (glDeleteProgram (program->gl_handle)); } CoglHandle @@ -81,7 +81,7 @@ cogl_create_program (void) _COGL_GET_CONTEXT (ctx, NULL); program = g_slice_new (CoglProgram); - program->gl_handle = glCreateProgramObject (); + program->gl_handle = glCreateProgram (); return _cogl_program_handle_new (program); } @@ -101,7 +101,7 @@ cogl_program_attach_shader (CoglHandle program_handle, program = _cogl_program_pointer_from_handle (program_handle); shader = _cogl_shader_pointer_from_handle (shader_handle); - glAttachObject (program->gl_handle, shader->gl_handle); + GE (glAttachShader (program->gl_handle, shader->gl_handle)); } void @@ -115,33 +115,27 @@ cogl_program_link (CoglHandle handle) program = _cogl_program_pointer_from_handle (handle); - glLinkProgram (program->gl_handle); + GE (glLinkProgram (program->gl_handle)); } void cogl_program_use (CoglHandle handle) { - CoglProgram *program; - GLhandleARB gl_handle; _COGL_GET_CONTEXT (ctx, NO_RETVAL); - if (handle != COGL_INVALID_HANDLE && !cogl_is_program (handle)) - return; - - /* The Cogl journal doesn't currently cope with the use of - * shaders so we have to flush all priitives whenever the - * current shader changes... */ - _cogl_journal_flush (); + g_return_if_fail (handle == COGL_INVALID_HANDLE || + cogl_is_program (handle)); - if (handle == COGL_INVALID_HANDLE) - gl_handle = 0; - else - { - program = _cogl_program_pointer_from_handle (handle); - gl_handle = program->gl_handle; - } + if (ctx->current_program == 0 && handle != 0) + ctx->legacy_state_set++; + else if (handle == 0 && ctx->current_program != 0) + ctx->legacy_state_set--; - glUseProgramObject (gl_handle); + if (handle != COGL_INVALID_HANDLE) + cogl_handle_ref (handle); + if (ctx->current_program != COGL_INVALID_HANDLE) + cogl_handle_unref (ctx->current_program); + ctx->current_program = handle; } int @@ -163,16 +157,34 @@ void cogl_program_uniform_1f (int uniform_no, float value) { + CoglProgram *program; + _COGL_GET_CONTEXT (ctx, NO_RETVAL); - glUniform1f (uniform_no, value); + + program = ctx->current_program; + + g_return_if_fail (program != NULL); + + _cogl_gl_use_program_wrapper (program->gl_handle); + + GE (glUniform1f (uniform_no, value)); } void cogl_program_uniform_1i (int uniform_no, int value) { + CoglProgram *program; + _COGL_GET_CONTEXT (ctx, NO_RETVAL); - glUniform1i (uniform_no, value); + + program = ctx->current_program; + + g_return_if_fail (program != NULL); + + _cogl_gl_use_program_wrapper (program->gl_handle); + + GE (glUniform1i (uniform_no, value)); } void @@ -181,21 +193,29 @@ cogl_program_uniform_float (int uniform_no, int count, const GLfloat *value) { + CoglProgram *program; + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + program = ctx->current_program; + + g_return_if_fail (program != NULL); + + _cogl_gl_use_program_wrapper (program->gl_handle); + switch (size) { case 1: - glUniform1fv (uniform_no, count, value); + GE (glUniform1fv (uniform_no, count, value)); break; case 2: - glUniform2fv (uniform_no, count, value); + GE (glUniform2fv (uniform_no, count, value)); break; case 3: - glUniform3fv (uniform_no, count, value); + GE (glUniform3fv (uniform_no, count, value)); break; case 4: - glUniform4fv (uniform_no, count, value); + GE (glUniform4fv (uniform_no, count, value)); break; default: g_warning ("%s called with invalid size parameter", G_STRFUNC); @@ -208,8 +228,16 @@ cogl_program_uniform_int (int uniform_no, int count, const int *value) { + CoglProgram *program; + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + program = ctx->current_program; + + g_return_if_fail (program != NULL); + + _cogl_gl_use_program_wrapper (program->gl_handle); + switch (size) { case 1: @@ -236,18 +264,26 @@ cogl_program_uniform_matrix (int uniform_no, gboolean transpose, const GLfloat *value) { + CoglProgram *program; + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + program = ctx->current_program; + + g_return_if_fail (program != NULL); + + _cogl_gl_use_program_wrapper (program->gl_handle); + switch (size) { case 2 : - glUniformMatrix2fv (uniform_no, count, transpose, value); + GE (glUniformMatrix2fv (uniform_no, count, transpose, value)); break; case 3 : - glUniformMatrix3fv (uniform_no, count, transpose, value); + GE (glUniformMatrix3fv (uniform_no, count, transpose, value)); break; case 4 : - glUniformMatrix4fv (uniform_no, count, transpose, value); + GE (glUniformMatrix4fv (uniform_no, count, transpose, value)); break; default : g_warning ("%s called with invalid size parameter", G_STRFUNC); diff --git a/clutter/cogl/cogl/driver/gl/cogl-program.h b/clutter/cogl/cogl/driver/gl/cogl-program.h index 4388d6a..5d5d7ec 100644 --- a/clutter/cogl/cogl/driver/gl/cogl-program.h +++ b/clutter/cogl/cogl/driver/gl/cogl-program.h @@ -31,7 +31,7 @@ typedef struct _CoglProgram CoglProgram; struct _CoglProgram { CoglHandleObject _parent; - GLhandleARB gl_handle; + GLuint gl_handle; }; CoglProgram *_cogl_program_pointer_from_handle (CoglHandle handle); diff --git a/clutter/cogl/cogl/driver/gl/cogl-shader.c b/clutter/cogl/cogl/driver/gl/cogl-shader.c index 8704366..dad2ba7 100644 --- a/clutter/cogl/cogl/driver/gl/cogl-shader.c +++ b/clutter/cogl/cogl/driver/gl/cogl-shader.c @@ -3,7 +3,7 @@ * * An object oriented GL/GLES Abstraction/Utility Layer * - * Copyright (C) 2007,2008,2009 Intel Corporation. + * Copyright (C) 2007,2008,2009,2010 Intel Corporation. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -33,12 +33,12 @@ #include -#define glCreateShaderObject ctx->drv.pf_glCreateShaderObject -#define glGetObjectParameteriv ctx->drv.pf_glGetObjectParameteriv -#define glGetInfoLog ctx->drv.pf_glGetInfoLog -#define glCompileShader ctx->drv.pf_glCompileShader -#define glShaderSource ctx->drv.pf_glShaderSource -#define glDeleteObject ctx->drv.pf_glDeleteObject +#define glCreateShader ctx->drv.pf_glCreateShader +#define glGetShaderiv ctx->drv.pf_glGetShaderiv +#define glGetShaderInfoLog ctx->drv.pf_glGetShaderInfoLog +#define glCompileShader ctx->drv.pf_glCompileShader +#define glShaderSource ctx->drv.pf_glShaderSource +#define glDeleteShader ctx->drv.pf_glDeleteShader static void _cogl_shader_free (CoglShader *shader); @@ -50,7 +50,7 @@ _cogl_shader_free (CoglShader *shader) /* Frees shader resources but its handle is not released! Do that separately before this! */ _COGL_GET_CONTEXT (ctx, NO_RETVAL); - glDeleteObject (shader->gl_handle); + glDeleteShader (shader->gl_handle); } CoglHandle @@ -73,7 +73,7 @@ cogl_create_shader (CoglShaderType type) } shader = g_slice_new (CoglShader); - shader->gl_handle = glCreateShaderObject (gl_type); + shader->gl_handle = glCreateShader (gl_type); return _cogl_shader_handle_new (shader); } @@ -112,7 +112,7 @@ cogl_shader_get_info_log (CoglHandle handle) { CoglShader *shader; char buffer[512]; - int len; + GLsizei len; _COGL_GET_CONTEXT (ctx, NULL); if (!cogl_is_shader (handle)) @@ -120,7 +120,7 @@ cogl_shader_get_info_log (CoglHandle handle) shader = _cogl_shader_pointer_from_handle (handle); - glGetInfoLog (shader->gl_handle, 511, &len, buffer); + glGetShaderInfoLog (shader->gl_handle, 511, &len, buffer); buffer[len]='\0'; return g_strdup (buffer); @@ -142,7 +142,7 @@ cogl_shader_get_type (CoglHandle handle) shader = _cogl_shader_pointer_from_handle (handle); - GE (glGetObjectParameteriv (shader->gl_handle, GL_SHADER_TYPE, &type)); + GE (glGetShaderiv (shader->gl_handle, GL_SHADER_TYPE, &type)); if (type == GL_VERTEX_SHADER) return COGL_SHADER_TYPE_VERTEX; else if (type == GL_FRAGMENT_SHADER) @@ -167,7 +167,7 @@ cogl_shader_is_compiled (CoglHandle handle) shader = _cogl_shader_pointer_from_handle (handle); - GE (glGetObjectParameteriv (shader->gl_handle, GL_COMPILE_STATUS, &status)); + GE (glGetShaderiv (shader->gl_handle, GL_COMPILE_STATUS, &status)); if (status == GL_TRUE) return TRUE; else diff --git a/clutter/cogl/cogl/driver/gles/cogl-program.c b/clutter/cogl/cogl/driver/gles/cogl-program.c index 309778b..9387a8b 100644 --- a/clutter/cogl/cogl/driver/gles/cogl-program.c +++ b/clutter/cogl/cogl/driver/gles/cogl-program.c @@ -115,11 +115,22 @@ cogl_program_use (CoglHandle handle) { _COGL_GET_CONTEXT (ctx, NO_RETVAL); - if (handle != COGL_INVALID_HANDLE && !cogl_is_program (handle)) - return; + g_return_if_fail (handle == COGL_INVALID_HANDLE || + cogl_is_program (handle)); + + if (ctx->current_program == 0 && handle != 0) + ctx->legacy_state_set++; + else if (handle == 0 && ctx->current_program != 0) + ctx->legacy_state_set--; ctx->drv.gles2.settings.user_program = handle; ctx->drv.gles2.settings_dirty = TRUE; + + if (handle != COGL_INVALID_HANDLE) + cogl_handle_ref (handle); + if (ctx->current_program != COGL_INVALID_HANDLE) + cogl_handle_unref (ctx->current_program); + ctx->current_program = handle; } int -- 2.7.4